diff --git a/tests/e2e/specs/progress-loop.e2e.mjs b/tests/e2e/specs/progress-loop.e2e.mjs index 868ded899..21b07251b 100644 --- a/tests/e2e/specs/progress-loop.e2e.mjs +++ b/tests/e2e/specs/progress-loop.e2e.mjs @@ -2,11 +2,18 @@ // the screen's every frame, up to 144 times a second for a video of 24 to 60. It waits for the // video's next frame now (requestVideoFrameCallback), and goes on after a pause, which such a // wait never sees end: play() asks for it again. +// +// Counted against the frames the video really presented, not against a second of the clock: +// some Linux CI runners present only 1 to 5 frames in the first second, where others show 23 +// to 28 and Windows 28, and a count per second failed there twice in a row (#641, #644). import {browser, expect} from '@wdio/globals'; const fixtureUrl = () => `${globalThis.__E2E_FIXTURES_ORIGIN__}/fixtures/frames-24fps.mp4`; +/** How many frames each measurement waits for. At 24 fps, half a second of playback. */ +const FRAMES = 12; + /** * Opens the player with no source, autoplay and remembered positions off. * @return {Promise} @@ -22,24 +29,52 @@ async function openEmptyPlayer() { } /** - * How often the client was told the time during a second of playback. - * @return {Promise} + * The progress loop's rounds, and how often the client was told the time, while the video + * presents the next FRAMES frames. + * @return {Promise<{rounds: number, times: number, frames: number, ms: number}>} */ -async function updatesInASecond() { +async function roundsOverFrames() { await browser.execute(() => { const client = window.fastStream; - if (!client.__zzCount) { + if (!window.__zzCount) { + window.__zzCount = {rounds: 0, times: 0, frames: 0, start: 0}; const update = client.updateTime.bind(client); - client.__zzCount = {n: 0}; client.updateTime = (time) => { - client.__zzCount.n++; + window.__zzCount.times++; return update(time); }; + const controller = client.interfaceController; + const schedule = controller.scheduleProgressFrame.bind(controller); + controller.scheduleProgressFrame = () => { + window.__zzCount.rounds++; + schedule(); + }; + const video = client.currentVideo; + const onFrame = () => { + window.__zzCount.frames++; + video.requestVideoFrameCallback(onFrame); + }; + video.requestVideoFrameCallback(onFrame); } - client.__zzCount.n = 0; + Object.assign(window.__zzCount, {rounds: 0, times: 0, frames: 0, start: performance.now()}); + }); + await browser.waitUntil(async () => (await browser.execute(() => window.__zzCount.frames)) >= FRAMES, + {timeout: 30000, interval: 50, timeoutMsg: `the video did not present ${FRAMES} frames in 30 s`}); + return browser.execute(() => { + const {rounds, times, frames, start} = window.__zzCount; + return {rounds, times, frames, ms: Math.round(performance.now() - start)}; }); - await browser.pause(1000); - return browser.execute(() => window.fastStream.__zzCount.n); +} + +/** + * One round a frame, each telling the client the time (timeupdate tells it too, so the times can + * be more). At the screen's 60 a second it would be 2.5 rounds for this 24 fps video. + * @param {{rounds: number, times: number, frames: number}} seen + */ +function expectOneRoundAFrame(seen) { + expect(seen.rounds).toBeGreaterThanOrEqual(seen.frames - 2); + expect(seen.rounds).toBeLessThanOrEqual(seen.frames * 1.5); + expect(seen.times).toBeGreaterThanOrEqual(seen.rounds); } describe('The progress loop of a short video', function() { @@ -59,21 +94,22 @@ describe('The progress loop of a short video', function() { }), {timeout: 30000, timeoutMsg: 'the video never loaded'}); const play = () => browser.execute(() => { - window.fastStream.currentVideo.muted = true; + const video = window.fastStream.currentVideo; + video.muted = true; + // The fixture lasts 4 s: a runner that presents a frame a second must not reach its end. + video.loop = true; return window.fastStream.play(); }); await play(); - const playing = await updatesInASecond(); - console.log(` updates in a second of playback: ${playing}`); - // A 24 fps video: about 24 rounds, not the screen's 60 (or more). - expect(playing).toBeGreaterThan(10); - expect(playing).toBeLessThan(40); + const playing = await roundsOverFrames(); + console.log(` playing: ${JSON.stringify(playing)}`); + expectOneRoundAFrame(playing); await browser.execute(() => window.fastStream.pause()); await browser.pause(300); await play(); - const again = await updatesInASecond(); - console.log(` updates in a second after a pause: ${again}`); - expect(again).toBeGreaterThan(10); + const again = await roundsOverFrames(); + console.log(` after a pause: ${JSON.stringify(again)}`); + expectOneRoundAFrame(again); }); }); diff --git a/tests/workflows/toolchain-updates.test.sh b/tests/workflows/toolchain-updates.test.sh index 43233b70d..2210d2258 100644 --- a/tests/workflows/toolchain-updates.test.sh +++ b/tests/workflows/toolchain-updates.test.sh @@ -153,6 +153,9 @@ setup() { # The recipes' own scripts, as main has them. mkdir -p tools tests/e2e/live-specs cp "$REPO/tools/check-toolchain.mjs" "$REPO/tools/check-patched-updates.mjs" "$REPO/tools/setup-dev-profile.mjs" tools/ + # uBlock Origin at the version these scenarios start from, whatever main pins: the + # toolchain's own pull request moves that pin, and went red here for it (#646). + sed -i "s/^ version: '[^']*',\$/ version: '1.75.0',/" tools/setup-dev-profile.mjs # The player libraries, as main has them: two, at old releases. printf '{\n "plyr": "3.8.5",\n "dplayer": "1.27.1"\n}\n' > tests/e2e/live-specs/player-libraries.json git add -A && git commit -q -m init && git remote add origin "$T/origin.git" && git push -q origin main