Skip to content

Type-check the e2e specs and packages/shared strictly, and collect the suite before it gates a deploy - #625

Merged
jehanazad merged 5 commits into
mainfrom
web/strict-e2e-shared
Oct 1, 2026
Merged

jehanazad merged 5 commits into
mainfrom
web/strict-e2e-shared

Conversation

@Babissimo

Copy link
Copy Markdown
Contributor

ClickUp: 3.2 Turn on strict TypeScript, starting with the e2e specs, e2e and packages/shared. Also 2.8's stale-reference item (123zgec4mxy). The dashboard half is 123zgec549h.

Why

The e2e specs gate production: a failed production E2E rolls production back. A spec that uses a host which is null on the environment it runs against throws while Playwright collects the file, because test.skip stops the tests but not the file's top-level code, and that takes the whole run down. The base config has strictNullChecks off, and the only guard was a comment beside each as string or !.

What changes

  • One strict config. A root tsconfig.strict.json turns on strict, noImplicitAny and strictNullChecks, each by name: the base sets the last two to false outright, and strict alone does not override that. e2e and packages/shared extend it. Both already compiled clean under it.
  • A probe that keeps it strict. strict-probe.ts is compiled into every workspace that extends the strict config, through that config's files. It has one @ts-expect-error per setting, so switching any setting off fails tsc.
  • Nullable hosts go through one helper. hosts now carries only the roles every environment has (api, map, dash), typed as non-null strings. app, admin and testmap are reachable only through hostOrSkip(role, reason). It skips the file, group or test it is called in where the role is null, and there it returns an unroutable .invalid stand-in, so it is safe at module scope. A spec's skip and its host can no longer disagree. An unknown E2E_ENV now fails by name.
  • Collection on every environment before merge. e2e's typecheck script now also runs playwright test --list for staging, prod and local. tsc cannot see every way a spec throws during collection; listing can, with no browser and no network. CI's web-build already runs this script, so no workflow changes.
  • The image copies every root tsconfig (COPY tsconfig*.json ./). Vite follows shared's extends chain while it builds the console, and with tsconfig.base.json alone the image build failed to resolve tsconfig.strict.json.
  • 2.8: e2e/specs/env.d.ts now names the config that compiles it.

Checked

  • tsc is clean at every commit.
  • Switching off any of the three flags fails tsc in both workspaces. So does a nullable role read off hosts, or a null in a non-null role.
  • A top-level throw that tsc accepts fails npm run typecheck -w e2e at the listing step. hostOrSkip at module scope (even parsed as a URL) and inside a describe collects cleanly on all three environments.
  • Every environment still lists 98 tests. A review pass compared per-file run and skip counts against main and found them identical.
  • app-surface passes against staging. On prod, app-surface, live-map and the dashboard's admin group all skip.
  • I replayed the Dockerfile's web-deps and dashboard-build stages outside Docker. The old COPY fails with failed to resolve "extends", and the glob builds.
  • pre-commit, test_dockerfiles.py and test_web_build_matrix.py pass, as do shared's unit tests and lint, and the dashboard build.

Notes for review

  • The ci.yml comment on web-build's typecheck step still says it typechecks the specs and config. It now also collects them. I left the comment alone to keep this PR off workflow files; the step log prints collecting for <env> before any collection error.
  • Lane A's branch for 2.8 sets forbidOnly: !!process.env.CI in the same config. I checked that setting against this listing: with CI=true, a committed test.only then fails the typecheck step on the PR, before merge. The two branches edit nearby lines of e2e/playwright.config.ts, so whichever lands second needs a small rebase.
  • The Node 24 PR (123zgec37p1, not yet open) edits the Dockerfile's FROM line, a few lines above this PR's COPY change.

🤖 Generated with Claude Code

Babissimo and others added 4 commits September 25, 2026 15:37
…ugh one helper

The e2e specs gate production: a failed production E2E rolls production back. A spec that uses a host that is null on the environment it runs against (hosts.app, hosts.admin and hosts.testmap are null on production, and one or another on staging and the dev server) throws at module scope while Playwright collects the file, since test.skip stops the tests but not the file's own top-level code. That crashes the whole run, not the one spec. tsconfig.base.json turns strictNullChecks off, and the only guard was a comment beside each cast.

e2e/tsconfig.json now extends a new root tsconfig.strict.json, which turns strict, noImplicitAny and strictNullChecks on by name (the base sets the last two to false outright, which strict alone does not override). The specs compiled clean under it already.

What changes in them is how they reach a nullable host. app-surface paired a test.skip with an `as string` cast, live-map built `${TESTMAP}/sim` at module scope, and dashboard used `ADMIN!` assertions. The exported `hosts` now carries only the roles every environment has (api, map, dash); app, admin and testmap are reached through hostOrSkip(role, reason) alone, so a spec that reads one any other way does not compile. hostOrSkip skips the file, group or test it is called in where the role is null and returns an unroutable .invalid stand-in there, so it is safe at module scope and a spec's skip and host cannot disagree. It has to be called from the spec itself, since a shared module runs once and only its first importer would skip; its docstring says so. The config also refuses an E2E_ENV it has no hosts for, by name, where the old cast left the table undefined to fail later as a TypeError.

tsconfig.strict.json lists strict-probe.ts in its files, so every workspace that extends it compiles the probe: one line per setting that has to stay a type error, which fails tsc as an unused @ts-expect-error the day that setting stops applying.

Checked: tsc is clean; switching off any of the three flags fails it, and so does a nullable role read off hosts; hostOrSkip at module scope, even parsed as a URL, and inside a describe collects cleanly on every environment; app-surface passes against staging, and it, live-map and the dashboard's admin group skip on prod.

ClickUp 3.2 (123zgec4my9).

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

A spec that throws at module scope while Playwright collects it takes the whole E2E run down, and the production E2E rolls production back when it fails. Nothing ran collection before a merge: CI's e2e entry typechecks the specs, and tsc sees only some of the ways a spec can throw there, such as a URL built at module scope from a value that parses on one environment and not on another.

e2e's typecheck script now runs `playwright test --list` for staging, prod and local after tsc. Listing loads every spec as a run would, with no browser and no network, so a collection-time throw on any environment fails the typecheck on the pull request, through the npm script CI's web-build already runs, with no workflow change. The listing goes to /dev/null; Playwright reports a collection error on stderr, after a "collecting for <env>" line naming the environment. The environments are listed by name, and playwright.config.ts says so beside its table. A probe spec whose top level throws although tsc accepts it fails here; the suite as it stands collects 98 tests on each environment.

ClickUp 3.2 (123zgec4my9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
packages/shared is the request layer and the sign-in state every surface imports, and it compiled under the base config's strict: false, noImplicitAny: false and strictNullChecks: false like everything else. It already compiles clean with all three on, so its tsconfig.json now extends tsconfig.strict.json as e2e's does. That also compiles strict-probe.ts into it, so switching off any of the three fails its typecheck, which CI's web-build runs per workspace.

The dashboard compiles shared's source as part of its own program, still under the base config, so this binds shared's typecheck and not the dashboard's view of it. The dashboard is the rest of 3.2 and is tracked on its own.

The image's web stage now copies every root tsconfig rather than tsconfig.base.json alone. Vite's TypeScript transform follows shared's extends chain while it builds the console, and inside the image it could not resolve tsconfig.strict.json.

docs/production-hardening.md's section on strict TypeScript says the two already compile strictly.

ClickUp 3.2 (123zgec4my9).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
e2e/specs/env.d.ts said only tsconfig.e2e.json compiles its directory. That file went when the browser suite got a workspace of its own (f3cb599); e2e/tsconfig.json compiles it now, and the comment's warning (keep this process global out of the browser program) is only actionable if it names the right file.

ClickUp 2.8 (123zgec4mxy), its stale-reference item.

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

This comment has been minimized.

@claude

claude Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

Claude finished @jehanazad's task in 1m 48s —— View job


Review complete

  • Gather context (PR description, CLAUDE.md, prior review comment)
  • Review diff against origin/main (git diff origin/main...HEAD)
  • Check what changed since the last @claude review on this PR
  • Check strict tsconfig / probe wiring, hostOrSkip, Dockerfile, e2e/package.json
  • Post review feedback

What's new since the last review

A prior review on 2026-09-25 (commit 41e8afc) already went through this PR's substance in detail and found no bugs. Since then the branch only picked up a merge of main (429d2f4), which is unrelated to this PR's own commits. I diffed 41e8afc..429d2f4 restricted to this PR's 12 files and the only change is one line that came in from main, not from this PR:

-      use: { ...devices["Desktop Chrome"] },
+      use: { ...devices["Desktop Chrome"], channel: "chrome" },

That's already in origin/main (pins the Chrome channel so CI uses the runner's installed browser) and isn't part of git diff origin/main...HEAD, so it's outside this PR's scope — nothing to review there.

Independent verification

I re-derived the key claims rather than just trusting the description:

  • tsconfig.strict.json → probe inheritance: e2e/tsconfig.json and packages/shared/tsconfig.json each declare their own include but no files. Per tsconfig's extends semantics, an unset files is inherited from the base verbatim (resolved relative to the base config's own directory), so ./strict-probe.ts resolves to the repo root in both workspaces — confirmed by reading tsconfig.base.json/tsconfig.strict.json/the two workspace configs directly.
  • hostOrSkip's type: TABLE's static type is the union of all three HOSTS entries (since ENV narrows to keyof typeof HOSTS via the isEnv guard). All three entries share identical keys, so Exclude<keyof typeof TABLE, keyof typeof hosts> cleanly resolves to "admin" | "app" | "testmap" — a caller can't pass "api" | "map" | "dash" and can't typo a role name.
  • Call-site skip scoping: app-surface.spec.ts and live-map.spec.ts call hostOrSkip at module scope (file-level skip); dashboard.spec.ts calls it directly inside the "Admin surface selection" describe body (group-level skip, same place the old test.skip(!ADMIN, ...) sat) — skip granularity is unchanged from before this PR, just centralized.
  • Dockerfile glob: ls tsconfig*.json at repo root returns exactly tsconfig.base.json and tsconfig.strict.json, so COPY tsconfig*.json ./ isn't picking up anything unintended.
  • e2e/package.json's collect script: plain POSIX for, redirects only stdout, so a playwright test --list failure still prints to the CI log before || exit 1 fails the step.

No bugs found. This is a careful, well-scoped change, and the design (one helper that ties a nullable host's read to its own skip, a probe that fails compilation if any strict flag regresses) directly addresses the failure mode described in the PR ("Why" section) rather than papering over it.
(branch: web/strict-e2e-shared)

@jehanazad
jehanazad merged commit 72c5658 into main Oct 1, 2026
21 checks passed
@jehanazad
jehanazad deleted the web/strict-e2e-shared branch October 1, 2026 00:48
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.

2 participants