From 563ac877f6f9cc9702f213ec3adf8bd862cae468 Mon Sep 17 00:00:00 2001 From: Nawid3333 <102778966+Nawid3333@users.noreply.github.com> Date: Mon, 9 Nov 2026 14:56:28 +0100 Subject: [PATCH 1/2] Tests that failed update PRs for no reason of theirs: progress loop, uBlock Origin pin progress-loop.e2e counted the loop's updates in a second of the clock. Some Linux CI runners present only 1 to 5 frames in that second (others 23 to 28, Windows 28), so it failed twice in a row and held back update PRs (#641; #644 lists it 5 times). It now counts the loop's rounds against the frames the video really presented. Both mutations are caught: the screen's every frame gives 70 rounds for 12 frames, a loop that dies 0 or 1. toolchain-updates.test.sh took uBlock Origin's version from main and expected 1.75.0, so the toolchain's own bump went red in Lint workflows (#646). Its fixture now starts at 1.75.0 whatever main pins: 232 checks pass with the pin moved to 1.75.4. Co-Authored-By: Claude Opus 5.5 --- tests/e2e/specs/progress-loop.e2e.mjs | 63 +++++++++++++++-------- tests/workflows/toolchain-updates.test.sh | 3 ++ 2 files changed, 45 insertions(+), 21 deletions(-) diff --git a/tests/e2e/specs/progress-loop.e2e.mjs b/tests/e2e/specs/progress-loop.e2e.mjs index 868ded899..a87370d9d 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,35 @@ async function openEmptyPlayer() { } /** - * How often the client was told the time during a second of playback. - * @return {Promise} + * The progress loop's rounds while the video presents the next FRAMES frames. + * @return {Promise<{rounds: number, frames: number, ms: number}>} */ -async function updatesInASecond() { +async function roundsOverFrames() { await browser.execute(() => { const client = window.fastStream; - if (!client.__zzCount) { - const update = client.updateTime.bind(client); - client.__zzCount = {n: 0}; - client.updateTime = (time) => { - client.__zzCount.n++; - return update(time); + if (!window.__zzCount) { + window.__zzCount = {rounds: 0, frames: 0, start: 0}; + 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, 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, frames, start} = window.__zzCount; + return {rounds, frames, ms: Math.round(performance.now() - start)}; }); - await browser.pause(1000); - return browser.execute(() => window.fastStream.__zzCount.n); } describe('The progress loop of a short video', function() { @@ -59,21 +77,24 @@ 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)}`); + // One round a frame. At the screen's 60 a second it would be 2.5 rounds for this 24 fps video. + expect(playing.rounds).toBeGreaterThanOrEqual(playing.frames - 2); + expect(playing.rounds).toBeLessThanOrEqual(playing.frames * 1.5); 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)}`); + expect(again.rounds).toBeGreaterThanOrEqual(again.frames - 2); }); }); 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 From 3e2a17924b2260917bcb882fb57e80044b90fe8b Mon Sep 17 00:00:00 2001 From: Nawid3333 <102778966+Nawid3333@users.noreply.github.com> Date: Mon, 9 Nov 2026 15:16:52 +0100 Subject: [PATCH 2/2] progress-loop.e2e: each round tells the client the time, and after a pause too The rewritten spec counted the loop's rounds only: a loop that stopped telling the client the time passed, and so did one that fell back to the screen's rate after a pause. Mutations, both caught: no updateTime gives 2 times for 12 rounds; the screen's rate after a pause, 57 rounds for 12 frames. Co-Authored-By: Claude Opus 5.5 --- tests/e2e/specs/progress-loop.e2e.mjs | 35 +++++++++++++++++++-------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/tests/e2e/specs/progress-loop.e2e.mjs b/tests/e2e/specs/progress-loop.e2e.mjs index a87370d9d..21b07251b 100644 --- a/tests/e2e/specs/progress-loop.e2e.mjs +++ b/tests/e2e/specs/progress-loop.e2e.mjs @@ -29,14 +29,20 @@ async function openEmptyPlayer() { } /** - * The progress loop's rounds while the video presents the next FRAMES frames. - * @return {Promise<{rounds: number, frames: number, ms: number}>} + * 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 roundsOverFrames() { await browser.execute(() => { const client = window.fastStream; if (!window.__zzCount) { - window.__zzCount = {rounds: 0, frames: 0, start: 0}; + window.__zzCount = {rounds: 0, times: 0, frames: 0, start: 0}; + const update = client.updateTime.bind(client); + client.updateTime = (time) => { + window.__zzCount.times++; + return update(time); + }; const controller = client.interfaceController; const schedule = controller.scheduleProgressFrame.bind(controller); controller.scheduleProgressFrame = () => { @@ -50,16 +56,27 @@ async function roundsOverFrames() { }; video.requestVideoFrameCallback(onFrame); } - Object.assign(window.__zzCount, {rounds: 0, frames: 0, start: performance.now()}); + 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, frames, start} = window.__zzCount; - return {rounds, frames, ms: Math.round(performance.now() - start)}; + const {rounds, times, frames, start} = window.__zzCount; + return {rounds, times, frames, ms: Math.round(performance.now() - start)}; }); } +/** + * 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() { it('runs at the video\'s frames, and goes on after a pause', async function() { await openEmptyPlayer(); @@ -86,15 +103,13 @@ describe('The progress loop of a short video', function() { await play(); const playing = await roundsOverFrames(); console.log(` playing: ${JSON.stringify(playing)}`); - // One round a frame. At the screen's 60 a second it would be 2.5 rounds for this 24 fps video. - expect(playing.rounds).toBeGreaterThanOrEqual(playing.frames - 2); - expect(playing.rounds).toBeLessThanOrEqual(playing.frames * 1.5); + expectOneRoundAFrame(playing); await browser.execute(() => window.fastStream.pause()); await browser.pause(300); await play(); const again = await roundsOverFrames(); console.log(` after a pause: ${JSON.stringify(again)}`); - expect(again.rounds).toBeGreaterThanOrEqual(again.frames - 2); + expectOneRoundAFrame(again); }); });