Skip to content

fix(core): re-land asyncWrapper + promise-core error-path fixes onto 4.x (#5624, #5633)#5637

Merged
DavertMik merged 2 commits into
4.xfrom
reland/asyncwrapper-and-core-fixes
Jun 14, 2026
Merged

fix(core): re-land asyncWrapper + promise-core error-path fixes onto 4.x (#5624, #5633)#5637
DavertMik merged 2 commits into
4.xfrom
reland/asyncwrapper-and-core-fixes

Conversation

@DavertMik

Copy link
Copy Markdown
Contributor

Why this PR exists

#5624 and #5633 show as merged, but their changes never reached 4.x. Both were stacked on the advisor/001-promise-core-characterization branch (the base of #5622). #5622 was squash-merged to 4.x first; #5624 and #5633 then merged into the advisor/001 branch afterward. So their commits live on advisor/001 and are not on 4.x.

Verified on current 4.x (20607b70): asyncWrapper.js has no .catch hardening, and session.js/effects.js have none of the error-path fixes.

This PR re-lands both deltas directly on 4.x (cherry-picked; no stacking this time).

What's included

1. fix(mocha) — fail fast when test hook import chain rejects (was #5624)

  • setup()/teardown() in lib/mocha/asyncWrapper.js now .catch(err => doneFn(err)) on the dynamic import('./test.js') chain (mirrors suiteSetup/suiteTeardown), so a rejected import reports an error instead of hanging the hook forever.
  • 3 regression tests; reverting the fix makes the error-path tests hang.

2. fix(core) — restore sessions & propagate errors on session/within/retryTo error paths (was #5633)

  • within() async error now runs _withinEnd (finishHelpers()) and return recorder.promise() (error propagates, not detached).
  • session() async/sync error now restores the recorder session (fixes the session-id leak) — surgical: only adds recorder.session.restore, keeping the original restoreVars call so BROWSER_RESTART=session doesn't close the browser context (this was the regression that failed Playwright CI on fix(core): restore sessions and propagate errors on session/within/retryTo error paths #5633 and was fixed before merge).
  • retryTo(): a thrown callback routes through recorder.throw (no premature reject) so throw-then-succeed resolves and reject happens only after maxTries; first attempt is tries === 1 (was 2), retry count preserved.
  • Characterization assertions in session_composition_test.js updated to the fixed behavior.

Verification (cherry-picked onto current 4.x)

  • npx mocha target unit files → 44 passing
  • BROWSER_RESTART=session acceptance (session_test.js) → 9 passed, 2 skipped, 0 browser context has been closed
  • lint + prettier clean

Both changes were already CI-green on #5624/#5633 (incl. the full playwright.yml suite across browsers + restart modes); CI will re-run here against current 4.x.

After merge

The advisor/001, advisor/003, advisor/006 branches can be deleted — their content is now fully represented on 4.x (via #5622 + this PR).

🤖 Generated with Claude Code

DavertMik and others added 2 commits June 14, 2026 11:37
In 4.x the per-test setup()/teardown() hooks load ./test.js via a dynamic
import() with no .catch(). If that import rejects, or the .then body throws
(e.g. enhanceMochaTest on an undefined test, a listener throwing), done() is
never called and the mocha hook hangs forever — the silent-hang failure mode
the 4.0 release is most concerned with.

This mirrors the existing suiteSetup()/suiteTeardown() shape exactly: append a
.catch(err => doneFn(err)) to both import chains. No recorder.errHandler is
added (the per-test handler owns the single errFn slot). makeDoneCallableOnce
already guards against a double done() call.

Adds 3 regression tests: setup() and teardown() with a throwing then-body call
done with the error within 1s instead of hanging, plus a happy-path check.
Reverting the fix makes the two error-path tests hang (verified).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit ac59a40)
…tryTo error paths

Fixes four of the latent error-path divergences characterized in #5622, by
making the error paths symmetric with the success paths. The characterization
assertions are updated to the corrected behavior in the same commit.

- within() async error (lib/effects.js): the catch now calls finishHelpers()
  (so helpers' _withinEnd runs on error, not just success) and returns
  recorder.promise() (so the error propagates through within() instead of being
  detached onto a trailing task that a caller awaiting within() never sees).

- session() async error (lib/session.js): schedules a recorder task that runs
  recorder.session.restore so the recorder session is restored on error (it
  was only restored on success, leaking the session id). The existing
  restoreVars/listener cleanup is kept as-is — switching to finalize()'s real
  restoreVars() closes the browser context under BROWSER_RESTART=session.

- session() sync error (lib/session.js): the finally recorder.catch now calls
  recorder.session.restore before re-throwing (the only place that runs on a
  rejected chain).

- retryTo() (lib/effects.js): a thrown callback no longer reject()s the outer
  promise prematurely — it routes through recorder.throw so the retry logic
  owns the outcome (retry, or reject once maxTries is exhausted). A callback
  that throws then succeeds on a later attempt now resolves instead of
  rejecting. tries now starts at 1 on the first attempt (was 2); the retry
  count is preserved (tries < maxTries).

Verified: unit 748/0, runner 273/0 (incl. all retryFailedStep/rerun tests),
acceptance within/session/els green under both BROWSER_RESTART=browser and
=session.

Not changed here (would be unsafe or out of scope, see PR):
recorder.retries clearing (retryFailedStep depends on it), the stopped-recorder
no-op contract, and nested cross-level restore ordering.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit dca7626)
@DavertMik DavertMik merged commit eba4a62 into 4.x Jun 14, 2026
13 checks passed
@DavertMik DavertMik deleted the reland/asyncwrapper-and-core-fixes branch June 14, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant