Skip to content

test: CI without demo video sites, tests that check what they say, harness fixes - #314

Merged
Nawid3333 merged 26 commits into
mainfrom
claude/issues-tests
Oct 3, 2026
Merged

Nawid3333 merged 26 commits into
mainfrom
claude/issues-tests

Conversation

@Nawid3333

@Nawid3333 Nawid3333 commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Draft until the other code-review PRs are merged. This one touches the same test files as #303, #304, #306 and #302, so I'll update it against main afterwards and then mark it ready.

Test suite fixes: CI no longer depends on demo video sites, tests check what their names say, harness fixes

What was wrong:

  • CI played and saved videos from two public demo sites. When either was slow or down, CI went red and the release was held back.
  • Several unit tests passed even when the code they guard was broken: the mpv host's message size limit, the HLS "missing IV" case, mpv header filtering, and the content-script message check (which could also hang for a minute on an apostrophe in a comment).
  • A video address with no file extension got an "extension" like com/stream, and https://example.mp4 was taken for an MP4 file.
  • The test video was downloaded without checking it was the right file, and the files made from it were never rebuilt after their recipe changed.
  • The extension tests' server ignored byte ranges, so a broken multi-range MP4 would have gone unnoticed.
  • The archive test server (fsaunpack) was reachable from the whole network, together with the recorded stream's tokens.
  • Several end-to-end tests could fail on a slow machine although the player was fine, or pass without checking anything.

What changed:

  • The tests CI runs use only local videos, and a unit test stops a public site from coming back.
  • Tests check exact results, and each fix was proven by breaking the code and watching the test fail.
  • The file extension is read only from the last part of the address's path.
  • The test video is pinned by size and checksum, and the files made from it are rebuilt when their recipe changes.
  • The extension test server answers byte ranges, and a new test seeks deep into a 17 MB video.
  • The archive server listens on this computer only.
  • The sources browser's Clear button is now tested where the background exists.
  • Ten more small modules are in the weekly mutation report.
  • CI keeps the test video in GitHub's cache, filed under its checksum, so the video site is needed once instead of on every run. A cached copy is checked like a download and fetched again if it is wrong.
  • On Linux CI, Firefox now gets a sound device (PulseAudio with a silent output). The sound checks, which used to skip on every CI run, now run there and fail if there is no sound. The Windows CI machine has no sound device, so they still skip there.

How it's proven: lint, typecheck and 997 unit tests pass, and the new CI steps have their own tests (each checked to fail with its part taken out). The end-to-end changes weren't run locally (they launch Firefox, and the sound device exists only on CI); this PR's CI runs them.

Fixes #250
Fixes #252
Fixes #254
Fixes #255
Fixes #256
Fixes #257
Fixes #258
Fixes #259
Fixes #260
Fixes #261
Fixes #262
Fixes #263
Fixes #264

Not fixed here (your call):

🤖 Generated with Claude Code

Nawid3333 and others added 20 commits October 3, 2026 15:45
…y name

The property "resolves null when the length prefix is 0 or past
MaxMessageBytes" wrote a 0-length header before the generated one, so
every run settled before the generated length was read, and its
fc.assert was not awaited: a host that accepted an over-limit length
passed it, and a failing run showed up only as an unnamed unhandled
rejection. It now writes one header per run, leaves the input open (an
end would settle an accepted length with null too) and is awaited.

"reads a message right at the limit" read a body 13 bytes under it; it is
now exactly MaxMessageBytes, with a companion one byte over through the
same frame path. An ESLint rule flags an unawaited fc.assert of an
fc.asyncProperty in tests/.

Mutation-checked: a cap raised to MaxMessageBytes * 100 fails the
property (counterexample 2097152) and the one-byte-over case; a '>='
at the host's length check fails the at-limit case. Both survived before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… a scanner

contentMessages.test.mjs scanned content.js by hand for the arguments of
each chrome.runtime.sendMessage call. A quote that never closed (an
apostrophe in a comment, a quote in a regex) sent the scan back to the
start of the file: with `(r) => { /* don't */ }` next to a callback, the
file ran 23-54 s and ended in "no tests" or a RangeError instead of
naming the bare call after it.

It now parses content.js with acorn (already a devDependency, used by
tools/ast-compare.mjs and recut-patch) and checks the call nodes: each has
a function as its last of two or more arguments, or is the one call with a
.catch, inside notifyBackground and sending its own argument.

Checked: with the issue's two probe calls appended to content.js, the new
test fails in 0.2 s naming content.js:2000 (the bare PROBE2 call); the old
one ran 23 s and reported no tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Connection's gaps

- HLSDecrypt "fails the segment when its key or IV is missing" only ever
  lacked the key, so HLSFragmentRequester's missing-IV branch was never
  taken. It is now one case each.
- MpvBackend's "keeps Referer and Origin" and "keeps lowercase variants"
  asserted only a length of 2; they now assert the headers picked.
- MpvBackend.testConnection (the options page's "Test mpv connection")
  had no test: the ping and its mpvPath, mpv found or not, the host
  unreachable (lastError) and a throw.
- TabTracker's hasPlayer, getMainPlayer, add/removeChildFrame, getFrames,
  createFrame, removeFrame and the tracker's tab methods had none either.

Mutation-checked, each failing the new test and passing before: the IV
check reduced to `!key`; testConnection ignoring lastError, always
reporting mpv, or dropping mpvPath; hasPlayer counting grandchildren;
getMainPlayer without its player-URL check; removeTab keeping the saved
state; removeChildFrame keeping the parent link.

Part of #254 (items 1 and 6).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st's

URLUtils.get_url_extension took whatever followed the last dot of the
whole URL. For a URL with no extension that was the host's last label and
the path ("https://e.com/stream" gave "com/stream"), so DirectVideoPlayer's
`|| 'webm'` fallback never applied and a save of such a source was named
with ".com/stream" as its extension; and a bare host such as
https://example.mp4 was taken for an MP4 stream. A unit test pinned the
"com/stream" answer.

It now reads the extension of the last path segment only, after the
scheme and host (file names, which SaveManager passes, have neither).
Wherever the last segment has a dot, the answer is the same as before.

Tests: no extension gives '', the host gives nothing (a property over
fc.webUrl origins), a file name still works, and the extension never
holds '/' or '.'. With the old implementation 5 of them fail.

Part of #254 (item 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…of it

The migrateKeybinds tests built their input with mergedOver, a private
re-implementation of what Utils.mergeOptions does with the keybinds. A
change to mergeOptions (its null and array handling, say) went unseen
there. They now call Utils.mergeOptions(DefaultOptions, saved), as
Utils.getOptionsFromStorage does.

Mutation-checked: with mergeOptions ignoring saved nested objects, 12 of
these tests fail; the old file passed all 56.

Part of #254 (item 3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iled test

Real time:
- mpvIpcRequest's tests gave a real pipe 200-400 ms budgets, so a loaded
  runner could take longer than the connect budget just to connect. The
  pipe stays real; the budgets now run on fake timers, moved on by the
  test once its request has reached the server.
- StreamLengths' "reads at most four at once" and vadModel's "waits for a
  run in progress" slept 10 ms where all they need is the promise work
  done: they wait one turn of the event loop (setImmediate) instead.

Clean-up:
- MpvNativeHost's pipeName() left an fs-ipc-* folder per test on Linux.
- DownloadEntry restored console.error only when the test passed, and
  DashLoader and e2eHarness restored their spies (and a temp folder)
  after the asserts: a failure left them for the rest of the file. All
  now restore in afterEach.

Mutation-checked: mpvIpcRequest keeping the connect budget after
connecting, reporting a silent instance as a timeout, or ignoring a
connect error each fail one IPC test; StreamLengths with MAX_ACTIVE 5 and
the VAD release not waiting for a run fail theirs.

Part of #254 (items 4 and 5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nge until watched

hls-live and mp4-fragmented-end both served on 41887. That held only while
specs never run at once and each closes its server; a worker killed at the
mocha cap leaves one open. mp4-fragmented-end moves to 41889, and
e2ePorts.test.mjs now fails when two servers share a port (it named
41887 and these two files before the move).

The spec's server answered the second range 6 s after it was asked for,
and the test only started watching once 7 s were buffered. Where the
player's start takes long (the Windows runner), the 6 s were gone by
then, the watch loop ran zero times and the test failed "expected 0 to be
greater than 3" with the player doing the right thing. The server now
holds that answer until the test has watched the stream ten times
(about 2.5 s) and releases it; the test also waits for the request
itself, with its own message if it never comes. `after` releases a held
answer, so close() does not wait on it.

e2e: not run here (it launches Firefox); the file parses and lints, CI
runs it.

Fixes #261. Part of #262 (item 1).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fsaunpack/testserver.mjs called app.listen(port) with no host, so express
listened on every interface, with Access-Control-Allow-Origin: * on each
file. The archive it serves is a recording of a stream, manifests with the
CDN's tokens included: a developer serving one on a shared network served
it to everyone there. Every other test server here binds 127.0.0.1; this
one now does too.

verify-fsaunpack (pnpm run verify, CI) now also connects to the machine's
first non-loopback IPv4 address and fails if the server answers there.

Checked with a self-ending probe that runs the server in-process on a
small archive: the old server answered on 192.168.0.41 (this PC's LAN
address), the new one only on 127.0.0.1. verify-fsaunpack itself was not
run here (its npm ci would write into the shared node_modules); CI runs it.

Fixes #259.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…them

streams.e2e.mjs cached the npm releases it serves to its pages in
tests/e2e/live-specs/fixtures/live-libs, which no .gitignore entry
covers (only tests/e2e/fixtures/ is ignored): after one
`pnpm run test:live`, git status showed about 1.5 MB of third-party
bundles, one `git add -A` away from the tree. The comment said "next to
the suite's gitignored fixtures directory" and CLAUDE.md "in the OS temp
dir"; neither was so.

The cache is now tests/e2e/fixtures/live-libs, inside the ignored
directory (git check-ignore: the old path is not ignored, the new one
is), and the comment and CLAUDE.md say where it is.

Fixes #260.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
playback.e2e.mjs played test-streams.mux.dev's HLS and dash.akamaized.net's
DASH, and save-video.e2e.mjs saved both, in the web suite that ci.yml runs
on Linux and Windows and auto-release waits for. Either host slow or down
timed those cases out twice (spec retry), turned CI red and held back the
release, though CLAUDE.md says third-party streams are never part of CI.

They are gone. The local cases already cover the same paths: playback's
hls-ts, hls-fmp4, hls-audio and the four dash-* fixtures; save-video's
local MPEG-TS, fMP4 and separate-audio HLS saves (the chunkMeta
regression is on the fMP4 path); and save-fmp4's DASH save, which decodes
the file and counts its frames, a stronger check than the remote DASH
case's "over 1 MB". Real streams stay in the live suite.

A unit test now fails when a spec under specs/, ext-specs/,
classic-specs/ or pbm-specs/ names a public host (it named both hosts
with the old playback spec put back).

e2e: not run here; the files parse and lint, CI runs them.

Fixes #255.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every e2e fixture is made from sample.mp4, fetched from
test-videos.co.uk, and the only check on the download was its ftyp box.
The specs count on its frames (300 at 30 fps, 250 of them B-frames,
fmp4-bframes, hls-fmp4-cut's -frames:v 298, the 96/300 frame
expectations): a host that re-encoded the file would move them all at
once, with nothing saying why.

mp4Fixture.mjs now pins the file (991017 bytes, SHA-256 77145c94...,
checked with ffprobe on 2026-10-03: H.264 High, 640x360, 300 frames, 250
B, no audio). A download that is another file fails with "the MP4 fixture
changed upstream", naming both. A local copy is checked on each run too
(1 MB, milliseconds): one that is another file is fetched again, and the
fixtures made from it are removed for the suites to make again.

Run here against the real host: a fresh download passes the pin; a
garbage sample.mp4 beside a made-up dash-template and long-av.mp4 is
fetched again and those two are removed (live-libs kept); a third run
does nothing. mp4FixtureMismatch has unit tests; with its hash compare
removed, the same-size case fails.

The host being down still fails every e2e job; a cache or a committed
copy is the owner's call (see #256).

Part of #256.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixtures made from sample.mp4 were rebuilt only when their marker
(expected.json, .complete, or a non-empty file) was missing, never when
the ffmpeg arguments that make them changed. CI starts from nothing and
picked a change up; a developer's fixtures directory, or verify:linux's
tree, kept the old fixture: "passes locally, fails on CI".

The builders move from wdio.conf.mjs to tests/e2e/buildFixtures.mjs,
unchanged but for how each decides it is built. Each now keeps a recipe
(a hash of its builder's code, its own arguments, this machine's H.264
encoder and the MP4 fixture's pin) in <file>.recipe or <dir>/.recipe,
written last, and is built again when the recipe differs.

sample-av.mp4 (analyzer.e2e.mjs and video-delay.e2e.mjs) was made in both
specs' before hooks straight to its final name; with +faststart, a run
killed while ffmpeg wrote it left a file without a moov that both specs
then trusted. It is now built with the others, through writeFixture.
The extension config builds them as well, so test:ext alone works on a
fresh clone (source-length needs long-av.mp4 and hls-ts).

Run here in Node with the local ffmpeg (no browser): all fixtures built
in 6.7 s; a second run 0.2 s with no file touched; after changing an
argument in the WebM and HLS builders, exactly sample.webm and the three
HLS directories were rebuilt.

e2e: not run here; the files parse and lint, CI runs them.

Fixes #263. Part of #262 (item 2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
importText dispatched the file picker's change, paused 300 ms and read
the alerts. The page reads the file (FileReader) before it answers, so on
a loaded runner the alert came later, importText returned [] and "says so
when the file is no settings file" failed with the page doing the right
thing. It now waits up to 10 s for the alert. The import that must say
nothing waits for its value (seekStepSize 7) to be saved, then checks
that nothing was alerted.

e2e: not run here; the file parses and lints, CI runs it.

Fixes #262 (item 3; items 1 and 2 in the commits before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Five parts of #265:

- save-flow's this.timeout(180000) did nothing under WebdriverIO (mocha's
  cap is the config's 120 s), and its wait was one executeAsync polling
  up to 120 s, past WebDriver's 30 s script timeout and the cap, so the
  "settled: false" outcome it was built to log never was. It now polls
  from the spec once a second for at most 75 s and logs the page state
  either way. The private-browsing spec's three dead this.timeout calls
  are gone.
- storage.e2e.mjs: "falls through to the next backend" returned early,
  reported as passed, when OPFS was not in FSBlob's chain. It now skips
  visibly, and the first case asserts usedOPFS (true on both CI jobs'
  logs of 2026-10-02, run 37065133847).
- The web config's traversal guard compared startsWith(base) without a
  separator, so /fixtures/../fixtures-other/x passed for any sibling
  named like it. It is serveFile.mjs's resolveInside now, which needs
  base + path.sep, with unit tests (a plain startsWith fails them).
- mozLog.mjs kept a MOZ_LOG only when afterTest saw a failure, but a
  failed hook (loader-retry's server not starting in before: the case the
  log is for) reaches no afterTest, so that log was deleted as passed.
  afterHook now marks the attempt failed, and an attempt in which no test
  ran keeps its log. Unit tests; each half fails without its line.
- example/embed.html says it must be served over http, and says so on
  the page when opened as a file: its target origin is then "null",
  which postMessage refuses.

Left: the sound pin on CI (item 2, an owner decision) and the stall
comments (item 7, low confidence; needs repeated e2e runs).

e2e: not run here; the files parse and lint, CI runs them.

Part of #265.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s past the first

wdio.extension.conf.mjs served every /fixtures/ request whole, with 200
and Accept-Ranges: none, while the web config answers ranges and says
why they matter. On the 1 MB sample.mp4, which fits MP4Player's first
range, save-flow, save-dialog-regression, save-transport, embed-page-query
and the private-window MP4 case all passed; nothing in the extension
suites would show accelerated MP4 broken on a real multi-range file, and
a fix for #139 (refusing a 200 for a Range request) could break them.

The /fixtures/ route now answers Range with 206 or 416 through
serveFile.byteRange, as the web config does; embed-page-query's /stream
proxy passes the player's Range on and the answer back. save-flow has a
new case: the embedded player on long-av.mp4 (17 MB, 160 s, now made by
the extension config too) seeks to 100 s and must play on there, in
MP4Player.

Checked in Node by running the config's own onPrepare and onComplete
(with a stand-in package so it loads): whole file 200; bytes=0-999999 of
sample.mp4 206 "0-991016/991017"; past its end 416; long-av.mp4's second
MB 206 with 1000000 bytes; bytes=-8 the last 8. The new case is not run
here (it launches Firefox); CI runs it.

Fixes #257.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…unInPage

extension.e2e, save-transport and vad still opened the extension page and
switched to the newest window handle. A fresh temporary install opens
welcome.html at no fixed moment, on the same origin, so a startsWith
check passed on it: "serves its player page from the extension origin"
could pass on the welcome page, and save-transport's probes ran there.
All three now use extension-page.mjs's openExtensionPage, which finds the
page by its URL, and the first test also asserts the page's path.

runInPage had three copies. save-transport's cleared window.____err (four
underscores) while its catch wrote window.__err, and it and extension.e2e
reported a page-side throw by e.stack alone, which in Firefox holds only
the frames. One shared tests/e2e/runInPage.mjs now has modules.e2e's
format (message, then stack), and all three specs use it.

e2e: not run here; the files parse and lint, CI runs them.

Fixes #264.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…exists

sources-browser-ui.e2e.mjs lives in specs/, which only the web config
runs, so its isExt branch never ran: it only checked that nothing was
sent from a page with no chrome.runtime. Nothing anywhere sent the clear
and looked at the background afterwards; removing the CLEAR_SOURCES call
from SourcesBrowser.mjs, or its handler from background.mjs, left every
suite green.

The web spec's dead case is gone (its header says where the extension
half is). embed-page-query.e2e.mjs, which already opens the player over a
page with a detected stream, has a new case: the player's list lists the
page's file; Clear empties it; the player then asks the background for
the page's sources (REQUEST_SOURCES, as it does when it loads) and the
background's answer must be an empty list. It asks again until it is,
for up to 15 s, so a request that overtakes the clear message, or a
later re-detection, cannot fail it; without the handler it is never
empty.

e2e: not run here; the files parse and lint, CI runs them.

Fixes #258.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h unit tests

stryker.config.mjs mutated 15 modules, while about 25 more pure-logic
modules with unit tests were left out of the weekly report: a gap like
SpeedTracker's prune (#251) is exactly what the run would show.

Counted with Stryker's own instrumenter (dry runs, 2026-10-03): the 15 are
3,833 mutants; ten small ones (SpeedTracker, OpQueue, StallWatchdog,
SourceBufferWrapper, FrameStepper, LargeBuffer, TimestampRebaser,
ptsNormalize, ManifestTypes, HLSDecrypter) are 441, and are added; the
initial test run passes with them instrumented. The rest are 2,790 (the
downloader, loaders, LevelManager, SyncedAudioPlayer and others) plus
3,084 (tools and e2e harness modules): over twice the run, past the job's
240-minute limit before the first weekly run has said how long it takes.
The config comment lists them and says why they wait, and that the mpv
host's Windows-only tests never run in the Linux job, so its PowerShell
and WMI mutants can only survive.

Part of #253.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t they waited for

From the #266 proposals, the small ones:

- source-length.e2e.mjs had its own range server: bytes=5-2 made
  createReadStream throw inside the request listener, which takes the
  worker down, and a read error went unhandled. It now uses the harness's
  byteRange and sendFile (serveFile.mjs), as both configs do: 416 for that
  range, a destroyed response for a failed read.
- subtitle-search, hls-live and mp4-loading had waits with no timeout
  message (two of subtitle-search's on WebdriverIO's default 5 s, failing
  with its generic "waitUntil condition timed out"); each now names what
  never came, and those two get 15 s.
- playback's expectEveryHlsSegmentListed swallowed its wait's timeout and
  went on to compare two lists; it now logs how many fragments were
  listed of how many, and that the playlist never arrived.

e2e: not run here; the files parse and lint, CI runs them.

Part of #266.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The case added for #257 opened the embed page with browser.url() while
the driver was still in the player frame the save case before it left it
in, and browser.url() navigates the frame the driver is in. It now
switches to the top level first, as CLAUDE.md asks of every spec that
switches into a frame.

Found reviewing the e2e changes, which are not run here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread tests/e2e/wdio.conf.mjs Fixed
Nawid3333 and others added 2 commits October 3, 2026 18:23
…ch the file

For the Actions cache of sample.mp4 (#256, next commit): the e2e setup
needs the SHA-256 the file is pinned to, as its cache key, and a way to
check a restored file the way a fetched one is checked.

tests/e2e/mp4FixtureCli.mjs: `pin` prints MP4_FIXTURE_PIN's SHA-256;
`ensure` runs ensureMp4Fixture and says what it found, for the job's
log. ensureMp4Fixture now returns that: kept (the pinned file was
there), fetched (none was) or replaced (another file was, with how it
differed). Its callers only awaited it.

Run here: `pin` prints 77145c94...; `ensure` with the pinned file says
kept; with a 3-byte file, replaced ("it had 3 bytes, expected 991017")
and the file is the pinned one again; with none, fetched; another
command exits 2 with its usage.

Part of #256.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…refox on Linux

#256: every e2e job fetched sample.mp4 from test-videos.co.uk, so that
host being down failed every e2e job on both systems. The e2e setup now
restores it from the Actions cache under the SHA-256 it is pinned to
(actions/cache/restore and save, v6.1.0 by commit, as the repository's
other cache steps), checks it with `node tests/e2e/mp4FixtureCli.mjs
ensure` (a restored file that is not the pinned one is fetched again),
and saves it only after that check, only when the cache had no entry.
The host is needed once per cache entry, not once per job.

#265: the specs that listen to the player (firefox.e2e's 8x pin,
audio-tools) skipped on every CI job: the Linux runner has no sound
server, so Firefox's AudioContext stayed "suspended" (CI run 37065133847
logged it on both jobs). On Linux the e2e setup now installs PulseAudio
when it is missing (GitHub's Ubuntu 24.04 and 26.04 images list neither
PulseAudio nor PipeWire), starts it with no idle exit, loads a null sink
"faststream_e2e", makes it the default, exports PULSE_SERVER and
E2E_SOUND_SINK, and logs `pactl info` and the sinks. A sound server
already running is used as it is. The daemon belongs to the job; the
runner ends it with the job's other processes. tests/e2e/soundCheck.mjs
decides: no sound skips a case, as before, except on Linux CI, where it
fails it, naming the sink and the server. The Windows runner has no
audio endpoint (the same log: "suspended" there too) and keeps skipping.

Tests:
- tests/workflows/e2e-setup.test.sh runs the sound step against stub
  pactl, pulseaudio, sudo and apt-get (PATH holds only bash and sed
  besides): fresh runner, installed, already running, daemon failing,
  sink failing, apt failing three times; and the pin step's output.
  Eight mutations of the step each fail it (no default sink, no unix:
  prefix, no running-server check, an idle exit, no E2E_SOUND_SINK, one
  apt attempt, another output name, no install check).
- tests/unit/e2eSetupAction.test.mjs checks the cache wiring: order,
  key, path (mp4Fixture's own), save only on a miss, pinned SHAs. Five
  mutations each fail it.
- tests/unit/soundCheck.test.mjs: Linux CI only; skip elsewhere, fail
  there with the sink and server named. Four mutations each fail it.

Not run here: the e2e jobs themselves; this pull request's CI is the
proof. actionlint and the workflow suite pass (WSL).

Fixes #256. Part of #265 (item 2; items 1, 3-6 in 827cc97; item 7 left).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread tests/e2e/mp4FixtureCli.mjs Fixed
Comment thread tests/e2e/mp4FixtureCli.mjs Fixed
claude added 3 commits October 3, 2026 17:26
…rough (CodeQL js/path-injection)

CodeQL flagged the test server's file reads (wdio.conf.mjs, and serveFile.mjs's
createReadStream): the request path reaches them through resolveInside, whose check was
`abs === base || abs.startsWith(base + path.sep) ? abs : null`, and CodeQL takes only the
startsWith half as a check. It now returns abs where startsWith holds, and base itself
(no request data) for the directory, so every path it returns is one CodeQL sees checked.
What it returns is unchanged: the same as before for 14 paths, among them the unit test's
cases, '/..', '/../fixtures-other/x.mp4' and '//x'.

Checked with CodeQL 2.27.1, the suite and threat model this repository's code scanning
uses (security-extended, local).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017JiuRmQkMrNTDSUbPnwKsn
…ting (CodeQL js/user-controlled-bypass)

CodeQL counts process.exit() as a security decision, and the two command checks above it
as a bypass of it. It is the script's last statement, so process.exitCode = 2 ends the
run with the same status (checked: "bogus" and no argument exit 2 with the usage line, as
before; pin prints the pin and exits 0).

Checked with CodeQL 2.27.1, the suite and threat model this repository's code scanning
uses (security-extended, local): no new alert left but mp4Fixture.mjs's http-to-file-access,
main's own one moved down the file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017JiuRmQkMrNTDSUbPnwKsn
Brings in the other code-review PRs (#237, #300, #302-#313). Conflicts, each
kept from both sides:
- storage.e2e.mjs: main's per-identifier loop, plus this branch's usedOPFS check.
- subtitle-search.e2e.mjs: main's OpenSubtitles DOWNLOAD_LINK, plus this
  branch's wait timeouts.
- TabTracker.test.mjs: main's and this branch's new describe blocks.
- MpvNativeHost.test.mjs: main's imports (a superset).
- MpvNativeHostReadMessage.property.test.mjs: main's version of the
  over-size test, which matches #303's host (an over-size length now waits
  for the body and answers TooLarge, null only when the input ends).

And one test that merged cleanly but no longer held: mpvHostSecurity's
"refuses a message one byte over the limit" now expects TooLarge, the
answer #303 gives once the message has been read.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JzfFWJzzaUDto87UaqoJGy
@Nawid3333
Nawid3333 marked this pull request as ready for review October 3, 2026 20:31

Copy link
Copy Markdown
Owner Author

The red github-advanced-security check is not this PR's: it's GitHub Copilot's code scanner, and its log ends in 402 ... You have exceeded your monthly quota before it reads any code. It failed the same way on every PR merged today (#302–#313). Nothing to fix here, and re-running it would hit the same quota. The PR's own CI (lint, unit tests, build, e2e on Linux and Windows, CodeQL) is what decides the merge.


Generated by Claude Code

…ayer was handed

#306 (#288) keeps what a page frame handed its player (handedTo), so a player that
loads again in its frame gets its stream back. CLEAR_SOURCES emptied every frame's
source list but not that record, so the player's next REQUEST_SOURCES sent the cleared
stream again: embed-page-query.e2e.mjs's new Clear test (#258) got [1] on every ask
on Firefox Beta, once this branch had #306 from main.

CLEAR_SOURCES now also calls forgetHandedToPlayer() on every frame. Unit tests drive
the real background: a player asking again still gets its stream, and gets nothing
after a Clear (fails without the fix).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JzfFWJzzaUDto87UaqoJGy
@Nawid3333
Nawid3333 merged commit 21b17ba into main Oct 3, 2026
15 of 16 checks passed
@Nawid3333
Nawid3333 deleted the claude/issues-tests branch October 3, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants