Skip to content

chore(ci): run karma on local headless browsers instead of BrowserStack - #1612

Open
Essk wants to merge 5 commits into
mainfrom
chore/ci-drop-browserstack
Open

chore(ci): run karma on local headless browsers instead of BrowserStack#1612
Essk wants to merge 5 commits into
mainfrom
chore/ci-drop-browserstack

Conversation

@Essk

@Essk Essk commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Description

CI has been red for months, including on the 3.17.5 release push. The unit, playback and playback-min matrix jobs hang in run npm test until the 6 hour timeout.

From the logs, the BrowserStack sessions never start, and karma waits for them forever. Why they never start hasn't been diagnosed — whether the account or credentials behind the BROWSER_STACK_USERNAME / BROWSER_STACK_ACCESS_KEY secrets are still valid isn't visible from the repo side. But the hang is avoidable entirely by skipping BrowserStack: with those env vars removed from the job, videojs-generate-karma-config falls back to local browser detection, which is exactly the configuration the coverage test type already runs (it empties browserstackLaunchers) and passes in about two minutes on the same runner.

In our own player stack we've found that testing in real (cloud) browsers really only adds value for Safari-exclusive playback behaviour — native HLS, AirPlay integration and the like — and we've reworked our test stack to use headless browsers everywhere else, which made CI both faster and more stable. In this repo, the BrowserStack setup provides no Safari coverage anyway: scripts/karma.conf.js filters /(Safari|Chromium)/ out of the browser list, so the effective BrowserStack set was only bsChrome plus a bsFirefox pinned to version 64 (a 2018 build). Restoring genuine Safari coverage would be worthwhile, but it's a separate piece of work from repairing CI. If the secrets are confirmed working again later, restoring the two env vars restores the old behavior; the BrowserStack launcher plumbing is untouched.

Running the suite on current local browsers surfaced two further issues that this PR also fixes:

  1. preferHeadless: false (boilerplate from the 2019 generator migration) made local detection launch headful Chrome and Firefox on the runner. Removed, so the standard headless variants run.
  2. Firefox froze the unit suite at exactly test Update rollup to the latest version 🚀 #782. The suite creates a dedicated web worker per segment loader per test, and Firefox caps dedicated workers at 512 per origin (dom.workers.maxPerDomain) — silently queueing creation past the cap rather than erroring. Once crossed, a fresh transmuxer worker never starts, its probeTs message is never processed, and the awaited appended event never fires; QUnit has no default test timeout, so karma's no-activity watchdog produces a deterministic Executed 781 of 1410 … DISCONNECTED retry loop for ~21 minutes. (The instrumented coverage build passes on Firefox only because it runs slowly enough for GC to reclaim orphaned workers; Chrome has no equivalent cap.) Fixed with a FirefoxHeadlessHighWorkerLimit launcher that raises the pref. The long-term fix is terminating leaked workers in test teardown, at which point the pref becomes belt-and-braces — proposed as a follow-up rather than blocking the CI repair.

Specific Changes proposed

  • Remove the two BROWSER_STACK_* env vars from the ci job so karma detects and uses the runner's local browsers.
  • Remove preferHeadless: false from scripts/karma.conf.js.
  • Add a FirefoxHeadlessHighWorkerLimit custom launcher (Firefox with dom.workers.maxPerDomain: 10000) and map it in place of detected FirefoxHeadless.

No changes to the test-type matrix or the Firefox version pin (which is inert without BrowserStack).

Verification

Requirements Checklist

  • Feature implemented / Bug fixed
  • If necessary, more likely in a feature request than a bug fix
  • Reviewed by Two Core Contributors

🤖 Generated with Claude Code

BrowserStack sessions never start, so the unit, playback and playback-min
jobs hang until the 6h timeout. No coverage is lost: Safari is already
filtered out of the browser list, so BrowserStack only ever supplied
Chrome and a Firefox pinned to version 64. The coverage test type
already runs on local browsers and passes green on the same runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 84.00%. Comparing base (a9f9d7a) to head (58cb5ca).

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1612   +/-   ##
=======================================
  Coverage   84.00%   84.00%           
=======================================
  Files          44       44           
  Lines       11713    11713           
  Branches     2625     2625           
=======================================
  Hits         9840     9840           
  Misses       1873     1873           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Essk

Essk commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

CI results on this PR: the BrowserStack hang is fixed, but it has uncovered a second, unrelated problem in the unit job that this PR does not fix.

Green (previously hung to the 6h timeout, now finish in ~2.5 min):

  • ci (ubuntu-latest, coverage)TOTAL: 2806 SUCCESS
  • ci (ubuntu-latest, playback)TOTAL: 41 SUCCESS
  • ci (ubuntu-latest, playback-min)TOTAL: 41 SUCCESS

All three log Launching browsers Chrome, Firefox, i.e. the runner's local Chrome 151 and Firefox 153 — which is also a coverage improvement over the BrowserStack set, where Firefox was pinned to 64.

Still red: ci (ubuntu-latest, unit)

Chrome passes (Executed 1403 of 1410 (skipped 7) SUCCESS), but Firefox stalls partway and never recovers:

Firefox 153.0 (Linux x86_64): Executed 781 of 1410 (skipped 3) DISCONNECTED (5 mins 28.313 secs / 18.896 secs)
  Disconnected reconnect failed before timeout of 300000ms (ping timeout)

It then loops through browserDisconnectTolerance: 3 reconnects at 5 minutes each and fails the job at ~21 minutes.

This looks deterministic rather than flaky: it stalled at exactly 781 of 1410 in both the push and pull_request runs, on Firefox 152 and 153 respectively. Notably coverage runs the same test set (see scripts/rollup.config.jsunit and coverage build the same bundle) and Firefox completes all 1403 in about 10 seconds there. The only difference is istanbul instrumentation, so the timing change appears to be what hides the hang — which suggests a race in a test rather than an infrastructure problem.

I have deliberately not touched the tests or karma config to force this green. Worth deciding whether to merge this as-is (it takes CI from one green job to three, and turns a 6-hour hang into a 21-minute failure) and track the Firefox unit stall separately, or hold this until that stall is diagnosed too.

Essk and others added 4 commits August 12, 2026 12:14
Without BrowserStack masking it, preferHeadless: false launches
non-headless Chrome and Firefox on CI runners. The unit test type is
unstable there: Firefox stalls mid-suite in ping-timeout disconnect
cycles and Chrome fails three xhr hook tests, while the same build
passes fully both locally and under the coverage type. preferHeadless
dates to the 2019 generator migration, before headless was the norm.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The unit suite creates a dedicated worker per segment loader per test.
Firefox caps dedicated workers at 512 per origin
(dom.workers.maxPerDomain) and silently queues creation beyond the cap
rather than erroring, so around test 782 a fresh transmuxer worker never
starts, its probeTs message is never processed, and the awaited
'appended' event never fires. QUnit has no test timeout, so the suite
stalls until karma's no-activity watchdog kills the browser, giving the
deterministic disconnect-and-retry failure at ~21 minutes. Chrome has no
equivalent cap. The instrumented coverage build passes on Firefox only
because it runs slowly enough for GC to reclaim orphaned workers.

Raising the pref keeps Firefox in the unit run. Terminating leaked
workers in test teardown is the long-term fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@Essk

Essk commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Root cause of the unit failure found and fixed.

The unit suite creates a dedicated web worker per segment loader per test. Firefox caps dedicated workers at 512 per origin (dom.workers.maxPerDomain) and silently queues worker creation past the cap rather than erroring. Around test #782 the cap is crossed, so that test's fresh transmuxer worker never starts, its probeTs message is never processed, and the awaited appended event never fires. QUnit has no default test timeout, so the suite stalls until karma's no-activity watchdog kills the browser — producing the deterministic Executed 781 of 1410 … DISCONNECTED retry loop at ~21 minutes.

This also explains the apparent paradox that coverage (same tests) passes on Firefox: the istanbul-instrumented build runs slowly enough that GC reclaims orphaned blob workers before the cap bites. Chrome has no equivalent cap. Reproduced locally on Firefox 153 (macOS): default prefs hang at exactly 781; with dom.workers.maxPerDomain: 10000 the full suite passes (TOTAL: 1403 SUCCESS).

Fix here: a FirefoxHeadlessHighWorkerLimit launcher raising the pref, mapped in place of detected FirefoxHeadless. The diagnostic logging commit has been reverted. Long-term follow-up worth filing: terminate leaked workers in test teardown (many tests never dispose their loaders/decrypters), at which point the pref becomes belt-and-braces.

@Essk

Essk commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Looks like the agent I asked to debug this left some "helpful" comments. I'll leave them, as while not the easiest to read they are accurate and leave context for future investigations. However, apologies for the 🤖 spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant