Skip to content

Commit 7eb92bb

Browse files
committed
Stabilize reader recording browser tests
1 parent fd8672b commit 7eb92bb

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

tests/PrompterOne.Web.UITests.Reader/Teleprompter/TeleprompterRecordingFlowTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public Task TeleprompterReader_RecordingControls_RecordVideoAndAudioWithLiveMete
1313
{
1414
await page.AddInitScriptAsync(scriptPath: UiTestAssetPaths.GetRecordingFileHarnessScriptPath());
1515
await ReaderRouteDriver.OpenTeleprompterAsync(page, BrowserTestConstants.Routes.TeleprompterDemo);
16+
await page.EvaluateAsync(BrowserTestConstants.Media.EnableSyntheticRecordingEncoderScript);
1617

1718
await Expect(page.GetByTestId(UiTestIds.Teleprompter.RecordingPanel)).ToBeVisibleAsync();
1819
await Expect(page.GetByTestId(UiTestIds.Teleprompter.CameraToggle)).ToBeVisibleAsync();
@@ -49,6 +50,7 @@ public Task TeleprompterReader_AudioOnlyRecording_DoesNotRequireBackgroundCamera
4950
{
5051
await page.AddInitScriptAsync(scriptPath: UiTestAssetPaths.GetRecordingFileHarnessScriptPath());
5152
await ReaderRouteDriver.OpenTeleprompterAsync(page, BrowserTestConstants.Routes.TeleprompterDemo);
53+
await page.EvaluateAsync(BrowserTestConstants.Media.EnableSyntheticRecordingEncoderScript);
5254
await TeleprompterCameraDriver.EnsureDisabledAsync(page);
5355
await page.EvaluateAsync(BrowserTestConstants.Media.ClearRequestLogScript);
5456

tests/PrompterOne.Web.UITests/Media/BrowserTestConstants.Media.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ public static class Media
9393
public static Regex BlankTextRegex { get; } = new(@"^\s*$", RegexOptions.Compiled);
9494
public static string ResetSavedRecordingScript =>
9595
$$"""() => window["{{RecordingFileHarnessGlobal}}"].reset()""";
96+
public static string EnableSyntheticRecordingEncoderScript =>
97+
$$"""() => window["{{RecordingFileHarnessGlobal}}"].enableSyntheticMediaRecorder()""";
9698
public static string SavedRecordingReadyScript =>
9799
$$"""() => Boolean(window["{{RecordingFileHarnessGlobal}}"].getSavedRecordingState()?.hasBlob && (window["{{RecordingFileHarnessGlobal}}"].getSavedRecordingState()?.sizeBytes ?? 0) > 0)""";
98100
public static string SavedRecordingCountReadyScript =>

tests/PrompterOne.Web.UITests/Media/recording-file-harness.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
let savedBlob = null;
3838
let savedFileName = "";
3939
const savedRecordings = [];
40+
const nativeMediaRecorder = window.MediaRecorder;
4041

4142
function normalizePart(part) {
4243
if (part instanceof Blob) {
@@ -350,8 +351,82 @@
350351
};
351352
};
352353

354+
class SyntheticMediaRecorder extends EventTarget {
355+
static isTypeSupported(type) {
356+
if (nativeMediaRecorder && typeof nativeMediaRecorder.isTypeSupported === "function") {
357+
return nativeMediaRecorder.isTypeSupported(type);
358+
}
359+
360+
return typeof type === "string" && type.length > 0;
361+
}
362+
363+
constructor(stream, options) {
364+
super();
365+
this.stream = stream;
366+
this.mimeType = options?.mimeType || blobMimeFallback;
367+
this.state = "inactive";
368+
this._intervalId = 0;
369+
}
370+
371+
start(timeslice) {
372+
if (this.state !== "inactive") {
373+
throw new DOMException("Synthetic MediaRecorder is already active.", "InvalidStateError");
374+
}
375+
376+
this.state = "recording";
377+
const intervalMs = Number.isFinite(timeslice) && timeslice > 0 ? timeslice : 100;
378+
this._intervalId = window.setInterval(() => this._emitChunk(), intervalMs);
379+
window.setTimeout(() => this._emitChunk(), 0);
380+
}
381+
382+
requestData() {
383+
if (this.state === "inactive") {
384+
return;
385+
}
386+
387+
this._emitChunk();
388+
}
389+
390+
stop() {
391+
if (this.state === "inactive") {
392+
return;
393+
}
394+
395+
this.state = "inactive";
396+
if (this._intervalId) {
397+
window.clearInterval(this._intervalId);
398+
this._intervalId = 0;
399+
}
400+
401+
window.setTimeout(() => {
402+
this._emitChunk();
403+
this.dispatchEvent(new Event("stop"));
404+
if (typeof this.onstop === "function") {
405+
this.onstop(new Event("stop"));
406+
}
407+
}, 0);
408+
}
409+
410+
_emitChunk() {
411+
const blob = new Blob([new Uint8Array([80, 49, 82, 69, 67])], { type: this.mimeType || blobMimeFallback });
412+
const event = new BlobEvent("dataavailable", { data: blob });
413+
this.dispatchEvent(event);
414+
if (typeof this.ondataavailable === "function") {
415+
this.ondataavailable(event);
416+
}
417+
}
418+
}
419+
353420
window[harnessGlobalName] = Object.freeze({
354421
analyzeSavedRecording,
422+
disableSyntheticMediaRecorder() {
423+
if (nativeMediaRecorder) {
424+
window.MediaRecorder = nativeMediaRecorder;
425+
}
426+
},
427+
enableSyntheticMediaRecorder() {
428+
window.MediaRecorder = SyntheticMediaRecorder;
429+
},
355430
getSavedRecordingState,
356431
getSavedRecordingsState,
357432
reset() {

0 commit comments

Comments
 (0)