feat(cli): add e2e run --max-duration and maxDuration config

Merged
OK
12d ago
+347-39

Closes #1118.

e2e run had only the per-attempt timeout, so a suite where every test hangs holds its CI slot for tests x attempts x timeout. This adds a run-level budget that stops the way --max-failures does.

npx e2e run --max-duration 900000
export default { targets, maxDuration: 900_000 } satisfies E2EConfig;
  • --max-duration <ms> overrides the config's maxDuration. Both take a positive integer of ms; 0 is INVALID_CONFIG.
  • The clock starts with the scheduler (packages/e2e/src/run/scheduler.ts), so app startup stays bounded by startupTimeout and is not counted.
  • When it runs out, the scheduler goes through the --max-failures stop path (stopEarly with a SkipInfo): nothing more is dispatched, running tests end as interrupted, and tests that never started are skipped with the new cause time-limit.
  • The runner records one RUN_TIMEOUT run error (class test) and exits 1, not 130. --last-failed picks up the interrupted and time-limit tests.
  • A maxDuration past setTimeout's cap (2^31-1 ms, now shared as MAX_TIMER_MS in internal/time.ts) sets no timer instead of firing at once.
  • e2e explore ignores the config's maxDuration and keeps its own --timeout.
  • Wire: the report schema's skip causes gain time-limit (additive). maxDuration stays out of the config digest, so turning it on leaves recordings valid.
  • Docs: reference/config#maxduration, the reference/cli flag row, reference/errors RUN_TIMEOUT, the CI guide, and skills/e2e/references/running.md.

Verified

Ran it locally: yes

  • Built CLI, scratch project on the testbed app (web engine, 1 worker), config maxDuration: 600_000. The suite has one passing test, one that sleeps 30 s, one after it, and a second file.
  • e2e run --max-duration 5000: the flag wins over the config, the run stops at about 5 s, and it exits 1.
 ↓ |web| tests/b-quick.e2e.ts (1 test | 1 skipped)
 ❯ |web| tests/a-slow.e2e.ts (3 tests | 1 interrupted | 1 skipped) 4.78s
   ✓ loads the home page 150ms
   × waits on a slow backend (interrupted) 4.63s
   ↓ never starts [run stopped at its time limit of 5000 ms (maxDuration)]

 ERROR  test error RUN_TIMEOUT
run stopped at its time limit of 5000 ms (maxDuration): the running tests end as interrupted, the rest are skipped

      Tests  1 interrupted | 1 passed | 2 skipped (4)
     Errors  1 error
   Duration  5.13s (startup 91ms)
exit=1
  • report.json: status: failed, exitCode: 1, errors: [{ code: RUN_TIMEOUT, category: test }], skip cause time-limit on the two tests that never started.
  • With the config set to maxDuration: 3_000 and no flag, e2e run --last-failed selected exactly the 3 tests that had not passed and stopped at 3 s with the same output.