Skip to content

fix(ci): stop swallowing web test failures; pin dart:io fixture tests to vm#363

Merged
ahmednfwela merged 1 commit into
mainfrom
fix/web-test-swallow
Jul 10, 2026
Merged

fix(ci): stop swallowing web test failures; pin dart:io fixture tests to vm#363
ahmednfwela merged 1 commit into
mainfrom
fix/web-test-swallow

Conversation

@ahmednfwela

@ahmednfwela ahmednfwela commented Jul 10, 2026

Copy link
Copy Markdown
Member

Closes #353.

The test:web:single melos script's exit status was that of its last statement — the WITH_WASM if-block, which is 0 whenever WITH_WASM != true — so web test failures were silently discarded and unit_tests stayed green while 27 web tests failed on main. set -e restores propagation (applied to the other chained melos scripts too).

The hidden failures were dart:io fixture reads, impossible on the web platform. The fixture-reading groups are now pinned testOn: 'vm' (x509_plus: 11 groups across 3 files, jose_plus: 1); pure-parsing groups keep their web coverage.

Verified on real browsers: x509_plus 47/47, jose_plus 212/212, oidc_web_core 27/27 on both Chrome and Firefox (the oidc_web_core Firefox failures needed #360).

… to vm

Closes the remaining two parts of #353.

Un-swallow: the `test:web:single` melos script's exit status was that of
its LAST statement -- the trailing WITH_WASM if-block, which evaluates to 0
whenever WITH_WASM != true -- so `dart test --platform chrome/firefox`
failures were silently discarded and `unit_tests` stayed green while 27 web
tests failed on main. `set -e` restores propagation. The same trailing-
statement shape existed latently in five other chained scripts (`test`,
`test:web`, `coverage:format`, `coverage:combine`, `generate`); all get
`set -e`. In `coverage:combine` this is load-bearing: a combine_coverage
failure was maskable by remove_from_coverage succeeding on a stale file.

Web-correctness: the tests those failures pointed at read PEM/DER fixtures
from disk via dart:io, which cannot exist on the web platform
("Unsupported operation: _Namespace" from _File.readAsBytesSync). Pin
exactly the fixture-reading groups to `testOn: 'vm'`, keeping every
pure-parsing group cross-platform:

  x509_plus/test/x509_test.dart            6 groups (rsa, ec, csr, rfc5280,
                                           keys from auth services,
                                           Certificate fields)
  x509_plus/test/util_extra_test.dart      3 groups
  x509_plus/test/x509_base_extra_test.dart 2 groups
  jose_plus/test/jwk_test.dart             1 group (JWK from pem files)

The util_extra/x509_base_extra pins also cover the two dart:io-using files
added by #355, which would otherwise have joined the hidden-failure list.

Verified locally with the un-swallowed semantics, all six runs green:
  x509_plus      chrome 47/47   firefox 47/47   (was 8 pass / 17 fail)
  jose_plus      chrome 212/212 firefox 212/212 (was 98 pass / 6 fail)
  oidc_web_core  chrome 27/27   firefox 27/27   (Firefox needed #360)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 982d4de9-8d24-4486-8e5c-86a793576c9a

📥 Commits

Reviewing files that changed from the base of the PR and between 656025d and 0ffef05.

📒 Files selected for processing (5)
  • packages/jose_plus/test/jwk_test.dart
  • packages/x509_plus/test/util_extra_test.dart
  • packages/x509_plus/test/x509_base_extra_test.dart
  • packages/x509_plus/test/x509_test.dart
  • pubspec.yaml

📝 Walkthrough

Walkthrough

Test groups involving VM-only operations are now restricted to the Dart VM. Melos generation, test, web-test, and coverage scripts now use fail-fast shell behavior.

Changes

Test and CI execution

Layer / File(s) Summary
VM-only test scoping
packages/jose_plus/test/jwk_test.dart, packages/x509_plus/test/*
PEM, certificate, key, ASN.1, RSA, CSR, RFC 5280, authentication-service, and certificate-field tests are configured with testOn: 'vm'.
Fail-fast Melos scripts
pubspec.yaml
Generation, test, web-test, single web-test, and coverage scripts add set -e so command failures stop execution.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two main changes: fail-fast CI and VM-only fixture tests.
Linked Issues check ✅ Passed The PR fixes failure propagation in melos web scripts and scopes dart:io fixture tests to vm, matching #353.
Out of Scope Changes check ✅ Passed All changes align with the stated CI fail-fast and VM-scoping objectives; no unrelated code changes appear.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/web-test-swallow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ahmednfwela ahmednfwela merged commit 6102d83 into main Jul 10, 2026
11 checks passed
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 71.66%. Comparing base (c9a1ee8) to head (0ffef05).
⚠️ Report is 4 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main     #363       +/-   ##
===========================================
+ Coverage   61.53%   71.66%   +10.13%     
===========================================
  Files         158      158               
  Lines        8392     8446       +54     
  Branches     1572     1572               
===========================================
+ Hits         5164     6053      +889     
+ Misses       3228     2393      -835     

☔ 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.

@ahmednfwela ahmednfwela deleted the fix/web-test-swallow branch July 10, 2026 12:30
ahmednfwela added a commit that referenced this pull request Jul 11, 2026
…wser loads fast

Run 29118828702's unit_tests jobs died at GitHub's 6-hour limit: on the
Firefox step, four packages finished by 21:07 and the remaining two
(oidc_web_core, oidc_loopback_listener) never printed a single line for
4.5 hours -- the job-kill cleanup shows a pile of orphaned Firefox helper
processes, i.e. a wedged browser launch. The code is exonerated: the full
oidc_web_core suite passes 36/36 on local Firefox in 2 seconds, and
loopback's suite is vm-pinned.

The amplifier was our own flag: --ignore-timeouts disables ALL
package:test timeouts including the browser-load timeout, so a routine
launch flake became an infinite hang. Keep --timeout 60m (jose_plus's
crypto tests are legitimately slow under dart2js) and drop
--ignore-timeouts from the three web-test invocations: a wedged load now
fails loudly in minutes and the job can simply be rerun. The iOS
integration script's --ignore-timeouts is a different context and is
untouched.

Also adds the set -e that the #363 sweep missed on test:coverage (same
trailing-statement swallow shape; not CI-invoked, fixed for consistency).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2
ahmednfwela added a commit that referenced this pull request Jul 11, 2026
#368)

* ci(coverage): unify noise policy — ignore-marker parity and generated-file strips

Three gaps in what "covered" means, found while auditing the denominator:

* Pure-Dart packages ignored // coverage:ignore-* markers: flutter test
  honors them natively (ignore-marked platform.dart never appears in Flutter
  lcov) but coverage:format_coverage only does so with --check-ignore
  (bin/format_coverage.dart:223, checkIgnoredLines). Added the flag so both
  halves of the repo obey the same ignore semantics.
* oidc_cli/lib/src/version.dart is generated ("Generated code. Do not
  modify.") but does not match the *.g.dart convention -- added a second
  remove_from_coverage pattern (multi -r verified empirically against
  remove_from_coverage 2.0.0).
* The upload-coverage job combined integration lcov straight into the
  Codecov upload with NO generated-file strip at all -- *.g.dart from
  integration runs reached Codecov. Added the same strip step there, and
  codecov.yml ignore globs as the reporting-side backstop (generated files,
  example app, version.dart).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* ci(coverage): harvest full workspace coverage from patrol integration runs

The android/ios/linux/windows patrol jobs passed `--coverage-package oidc`.
patrol_cli_plus treats that as an UNANCHORED regex over package names
(commands/test.dart:222 -> RegExp.hasMatch in coverage_tool.dart:355), so
all oidc* packages were already harvested -- but jose_plus, x509_plus and
crypto_keys_plus were not, despite the conformance flows exercising them
heavily (JWT signature verification, JWKS certificate parsing). The
artifact numbers agree: jose_plus integration delta over unit coverage was
exactly zero.

Switch the four native patrol jobs to `--coverage-workspace`, the fork's
purpose-built flag that includes every `workspace:` member from the root
pubspec (coverage_tool.dart:358-371). Verified the flag exists in the
published patrol_cli_plus 5.4.1 that CI installs: it landed in dee5f944a,
which is contained in the patrol_cli_plus-v5.4.1 tag.

The macos job uses plain `flutter test`, whose --coverage-package is a
regex over package names (flutter test --help); replace the sloppy
`"oidc*"` (regex, not glob: matches "oid" + any number of "c") with an
anchored `"^(oidc|jose_plus|x509_plus|crypto_keys_plus)"`.

The web job keeps `--coverage-package oidc`: its V8->lcov collector is a
separate best-effort path (test.dart:402 guards the VM-service coverage
route to !isWeb).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* fix(oidc_core): actually clear query/fragment when deriving issuer from well-known URI

Closes #356. `Uri.replace(query: null, fragment: null)` keeps the existing
components (null means "unchanged"), so getIssuerFromOpenIdConfigWellKnownUri
returned issuers carrying the original query/fragment, contradicting its own
doc. `query: ''` is no fix either -- it serializes a dangling '?'. Build the
issuer fresh from components instead (userInfo omitted when empty, port
omitted when !hasPort so no default port is injected).

Behavior note: a query-carrying well-known URL with the standard path layout
(e.g. Entra's `?appid=` form) now inverts to a clean issuer instead of a
query-carrying one; the doc comment previously called that form
"cannot be inverted" and has been corrected. Downstream issuer mix-up
defense stays with issuersAreIdentical.

Tests: both pre-existing happy-path tests pass byte-identical; new tests
cover query+fragment stripping (hasQuery/hasFragment false, no dangling
'?'), the Entra `?appid=` derivation, and userInfo/non-default-port
preservation. Adversarially verified by an independent reviewer lane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* fix(x509_plus): stop double-encoding OIDs in toAsn1; parse NoticeReference numbers

Closes #357, #358.

#357: ObjectIdentifier.toAsn1() manually base-128-encoded the OID content
bytes and handed them to ASN1ObjectIdentifier(...), whose constructor
(asn1lib 1.6.5) expects plain arc components and encodes them itself --
every re-encode path (AlgorithmIdentifier/Name/SubjectPublicKeyInfo/
X509Certificate.toAsn1, toPem) emitted doubly-encoded, corrupt DER. Now
`return ASN1ObjectIdentifier(nodes);`. Proven by round-trip tests over
five real OIDs, an independent DER cross-check re-parsing
toAsn1().encodedBytes with ASN1Parser, and a vm-only test re-encoding
rfc5280_cert1.cer's subject/issuer Names and recovering the original OIDs.

#358: NoticeReference.fromAsn1 assigned toDart(...)'s List<dynamic> (of
BigInt) to List<int> noticeNumbers -- a TypeError making any UserNotice
with a noticeRef unparseable. Now mapped explicitly to List<int>. Proven
by an in-memory noticeRef parse test ([1,2,300]).

Both replace tests that previously had to avoid the buggy paths.
Adversarially verified by an independent reviewer lane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* fix(jose_plus): JoseHeader.critical no longer throws on headers carrying crit

Closes #359. `getTyped('crit')` returned the raw List<dynamic> JSON value
cast to List<String>?, throwing a TypeError on any header that actually
uses the parameter the getter exists for. Use getTypedList('crit'),
exactly as `audience` does for 'aud' (lib/src/jwt.dart:37). Tests cover
both the crit-present (['exp']) and crit-absent (null) reads.
Adversarially verified by an independent reviewer lane.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* test: surface-load every package and drive unit coverage toward 95%

Second campaign wave (10-lane parallel workflow, every lane independently
adversarially verified in a fresh context).

Surface honesty: each of the 16 packages gains test/library_surface_test.dart
importing its public barrels with meaningful assertions, so every file with
coverable lines enters the coverage denominator -- lcov only ever sees
LOADED libraries, so real code could previously hide entirely (and did:
oidc_web_core/src/user_manager_web.dart, 94 raw lines, appeared in no
report). Files still absent after loading are provably noise: the VM
collector emits no source entry for zero-executable-statement files
(verified against the raw coverage JSON for the empty-class par/req.dart
and loopback mock.dart, and const-only native_channel_constants.dart).

Cover lanes (verifier-measured, per own probes):
  oidc_core        1838/2247 -> 2135/2247 (95.0%)  3 new suites incl.
                   user-manager flow/internals coverage
  oidc_cli          444/624  ->  539/581  (92.8%)
  crypto/x509/jose 2320/2367 -> 2360/2367 (99.7%)
  flutter tails     429/457  ->  474/474  (100%)
  web pair          216/237  ->  284/421  (oidc_web.dart is now visible and
                   honestly uncovered -- see the structural finding below)

Policy-compliant coverage:ignore markers (each with justification, each
audited by the lane verifier as genuinely unit-untestable): oidc_cli's
real-browser Process.run launches and the `dart pub token add` shell-out
(mutates the developer's real pub credential store; no injection seam).
Also removes a stale coverage:ignore-file from oidc_platform_interface's
platform.dart, whose 17 lines were already fully covered.

Known limitation recorded, not hidden: oidc_web/lib/oidc_web.dart compiles
only for browser targets, and CI's `flutter test` runs on the VM, so its
@teston('chrome') surface test is silently skipped there; its lines stay
visible-and-uncovered in the unit artifact until the tooling gap is closed
(tracked in a follow-up issue). Dead-code findings in oidc_cli login
commands are documented in the coverage tests rather than enshrined.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* test: pin certificate fixture test to vm; make pub-proxy forward hermetic

Review findings on #368 (one blocking, two advisory), all three addressed:

* certificate_extra_test.dart read DER fixtures in setUpAll with no platform
  pin -- the one unpinned dart:io fixture reader among the new tests, and a
  guaranteed failure on the un-swallowed `dart test --platform chrome` CI
  step. Now @teston('vm'), matching the #353 convention. Verified: x509_plus
  on chrome 60/60 (previously failed in isolation), vm 99/99.

* The one pub-proxy test that reaches the real `dart` binary forwarded
  `cache list`, whose runtime scales with the machine's global pub cache
  (timed out at 30s on a large dev cache). Forward `token list` instead:
  read-only and O(token store). 5/5 in ~8s.

* dart_pub.dart's ignore-file rationale leaked an authoring-session phrase
  ("the hard constraint elsewhere in this task"); trimmed to the technical
  rationale only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* ci(tests): drop --ignore-timeouts from web test runs; fail wedged browser loads fast

Run 29118828702's unit_tests jobs died at GitHub's 6-hour limit: on the
Firefox step, four packages finished by 21:07 and the remaining two
(oidc_web_core, oidc_loopback_listener) never printed a single line for
4.5 hours -- the job-kill cleanup shows a pile of orphaned Firefox helper
processes, i.e. a wedged browser launch. The code is exonerated: the full
oidc_web_core suite passes 36/36 on local Firefox in 2 seconds, and
loopback's suite is vm-pinned.

The amplifier was our own flag: --ignore-timeouts disables ALL
package:test timeouts including the browser-load timeout, so a routine
launch flake became an infinite hang. Keep --timeout 60m (jose_plus's
crypto tests are legitimately slow under dart2js) and drop
--ignore-timeouts from the three web-test invocations: a wedged load now
fails loudly in minutes and the job can simply be rerun. The iOS
integration script's --ignore-timeouts is a different context and is
untouched.

Also adds the set -e that the #363 sweep missed on test:coverage (same
trailing-statement swallow shape; not CI-invoked, fixed for consistency).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* ci(tests): bound web suite loads at 4m — wedged browser launches fail fast

The ed7b535 run proved the Firefox launch wedge recurs: its unit_tests
jobs sat on the Firefox step for 3h13m (vs ~66min for the entire step on
green runs). Dropping --ignore-timeouts made the wedge BOUNDED but not
cheap: with no explicit --suite-load-timeout, package:test scales the
suite connect/load timeout up to the per-test cap (60m), so every wedged
launch still burned up to an hour before failing.

--suite-load-timeout 4m (flag verified in dart test --help; load includes
the dart2js compile) is ~5x the worst legitimate suite compile+load
observed on green CI and converts a wedge into a fast, named failure that
a simple job rerun clears. Applied to all three web-test invocations;
implements the reviewer's advisory from the ed7b535 approval.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* ci(tests): exclude slow web-crypto suites from PR web runs; drop suite-load bound

My --suite-load-timeout 4m in melos test:web:single false-failed 8 suites
on the beta chrome step of run 29140552735 ("loading <file> (failed)" +
"TimeoutException after 0:04:00"). The timer evidently spans queue/
starvation time caused by jose_plus's crypto suites (keygen/sign/encrypt),
which take ~55min per browser under dart2js's ~200x slower BigInt
(#371) and starve other suites' loads while they run -- so any bound
under that window is wrong. Remove the flag entirely; --timeout 60m and
set -e stay, --ignore-timeouts stays removed (see the existing comment
on why: a wedged Firefox launch silently hung for 4.5h without it,
run 29118828702).

Instead, tag jose_plus's crypto-executor suites (jwa/jwe/jwk/ecdh/jws/
jwt_test.dart) as slow-web-crypto via a new packages/jose_plus/
dart_test.yaml and exclude them from PULL-REQUEST web runs only, via a
new WEB_EXCLUDE_TAGS env var threaded through melos's test:web:single
into `dart test --exclude-tags`. VM runs always execute every tag (full
PR coverage), and main-branch pushes keep full web crypto signal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* ci(tests): tag pss_eddsa suite as slow-web-crypto too

Review finding on 3e070c4: pss_eddsa_jws_test.dart generates RSA-PSS keys
(PS256/PS384/PS512) and signs/verifies -- measured 4:06 standalone under
dart2js, the dominant slice of the remaining PR-web time -- but was missed
by the six-file tag sweep. Tagged identically. PR-web chrome run drops
from 6:01 (140 tests) to 2:23 (129 tests); VM still runs all 233.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* ci(tests): scope PR browser testing to platform-divergent packages

With the crypto suites excluded, the measured remainder of the browser
steps is dart2js COMPILATION: dart test compiles every test file
separately (~30-60s each on CI runners), and the web sweep spans ~100
suites across six packages -- oidc_core alone is 60+ -- twice per browser
per matrix leg. Most of that buys nothing per-PR: crypto_keys_plus,
x509_plus, jose_plus and oidc_loopback_listener have zero web-conditional
or js-interop lib code (verified by grep), so their browser runs duplicate
the VM signal exactly. Only oidc_web_core (4 js-interop files) genuinely
diverges; oidc_core's divergence is a single 6-line conditional stub.

Pull requests now browser-test only oidc_web_core (new
test:web:pr:chrome/firefox melos scripts, packageFilters scope); pushes to
main keep the full sweep on both browsers. Expected PR web time: minutes
instead of the better part of an hour per browser per leg. Full-platform
coverage semantics are unchanged on main.

Windows note: melos exec runs multi-line scripts through cmd.exe locally,
so these scripts (like the existing test:web:single) are CI/sh-only; the
scope filter was verified locally (resolves to exactly oidc_web_core) and
the underlying per-package browser suites pass on both chrome and firefox.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BYvJ2hcGrcZbY3aKGy7JW2

* fix(ci): bound browser suite loads — a wedged load hangs forever by default

package:test 1.31 puts no timeout on suite loads (suiteLoadTimeout
defaults to Timeout.none), so a wedged browser suite load hangs until
GitHub's 6h job kill. Run 29146393931 hit this twice on the PR-scoped
steps: Firefox, then Chrome on the rerun, each finished one suite and
stalled on the next load (63+ min on a ~20s step, past the --timeout
60m mark — proving no timer covers loads; --timeout only bounds tests).

- test:web:pr:single: --suite-load-timeout 4m (three ~10s suite loads,
  no queue starvation) and drop --timeout 60m (sub-second tests; the
  30s default also bounds a mid-test wedge).
- test:web:single: --suite-load-timeout 2h on the full sweep — the load
  timer keeps running while suites queue behind jose's ~55min crypto
  executors, so the bound must clear that (a 4m bound false-failed 8
  healthy suites, run 29140552735). 2h turns a wedge into a loud,
  rerunnable failure instead of a 6h hang.
- Fix the stale comment claiming wedged loads fail loudly by default.

Verified locally: the new PR-scope command runs oidc_web_core's 36
tests in 13s (chrome, coverage on, exit 0) under the locked test 1.31.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McVLYD2qr9r6Hec2MpKZaU

* fix(ci): step-level timeout backstop on browser test steps

The dart-level bounds added in e362d42 cover suite loads and test
execution, but run 29148902320 proved a browser wedge can strike
outside both: a Firefox PR-scope step hung 57+ minutes WITH the 4m
suite-load bound active, so the hang was in browser launch, shutdown,
or loader orchestration — phases no package:test timer covers.

step timeout-minutes is the layer that bounds every hang shape: the
runner kills the process tree and fails the step loudly, rerunnable
instead of idling to GitHub's 6h job kill. PR-scope steps: 15m
(observed 19-31s). Full-sweep steps: 150m (observed ~55-66min per
browser on main pushes).

Also corrects the suite count in the test:web:pr:single comment
(oidc_web_core has five test suites, not three).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McVLYD2qr9r6Hec2MpKZaU

* fix(ci): serialize PR-scope browser suites; the wedge is a post-success exit hang

Log forensics across all three wedged runs (29146393931, 29148902320,
29150605150) show the same shape: every suite loads, every test passes,
then dart test never exits — the runner idles from the last passing
test until an outer timeout. Both browsers, both matrix legs. The
earlier load-wedge reading was wrong: the hang is in the post-success
phase (browser/suite teardown + Chrome coverage collection).

dart-lang/test#2294 documents a multi-suite post-success exit hang
where the runner reaches a successful end and a leaked async task keeps
the process alive; --concurrency=1 is the reporter-verified workaround.

- test:web:pr:single: add --concurrency=1 (five ~10s suites; serial
  costs seconds) and drop --coverage (removes the other post-success
  moving part; PR browser coverage is informational-only — codecov gets
  browser coverage from the full sweep on main pushes).
- Keep --suite-load-timeout 4m and the step-level timeout-minutes
  backstop from the previous commits.

Verified locally: the exact new command passes oidc_web_core's 36
tests (chrome, 1m26s, exit 0); 12 consecutive local runs of the OLD
flags also pass on Windows — the hang reproduces only on CI Linux.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McVLYD2qr9r6Hec2MpKZaU

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
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.

unit_tests: web test failures are silently swallowed — 27 tests failing on main while CI reports green

1 participant