tests: set up basic playwright E2E test (#823) master
* test(e2e): bootstrap Playwright suite with login flow Stand up a dedicated /e2e Playwright package (own package.json/lockfile, matching the per-package repo layout) and the first moderator-critical flow from #485: login + session. Also adds a draft CI workflow that runs the suite on PRs and pushes to main, gated by dorny/paths-filter so it only fires on app-affecting changes, and caching the Playwright browser binaries. The full-stack bring-up mirrors the documented local setup and still needs maintainer review + end-to-end CI validation. Refs #485 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(e2e): fix server/client startup hang Redirect backgrounded server/client output to log files so they don't hold the step's stdout pipe open, and wait on the server's /ready health endpoint (a reliable 200) instead of the GraphQL endpoint, which returns 400 on a bare GET. Dump the logs if readiness times out. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci(e2e): wait on server TCP port, not load-gated /ready The /ready endpoint returns 500 when CPU usage exceeds 75% (api.ts), which is always the case while tsc-watch and vite are compiling on a CI runner, so wait-on never saw a 200. Wait on the server's listening TCP port instead (the server only logs readiness after all middleware, including GraphQL, is wired). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(e2e): select login email field by input type, not role getByRole('textbox') matched both the email and password inputs in this Ant version (strict-mode violation in CI). The email field is the only type=text input, so select by type to stay unambiguous. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(e2e): move Playwright into server/ and self-seed via DI factories Relocate the Playwright suite from a standalone top-level package into server/e2e/ so tests can import the server's DI container and the existing test/fixtureHelpers factories. Each test now seeds the state it needs (committed) and tears it down, instead of depending on a pre-seeded org. - fixtures/coop.ts extends Playwright's test with a worker-scoped `deps` fixture (getBottle(), dynamically imported so the heavy graph defers to run time) and a test-scoped `seed` fixture exposing factory wrappers (seed.orgWithAdmin seeds an org + password-login admin). - login.spec.ts seeds its own admin and logs in; no env credentials. - jest testPathIgnorePatterns excludes /e2e/ so its *.spec.ts files never run under the unit-test runner; eslint test-rules + devDep allowlist extended to e2e/. - CI: drop the create-org seed step; @playwright/test is a server devDep so only the browser binary is installed; cache keyed on server/package-lock.json; report uploaded from server/e2e/playwright-report. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(e2e): load server runtime from transpiled/ to dodge esbuild type-import bug Playwright's esbuild loader transpiles each file in isolation and can't elide type-only imports written with value syntax (e.g. `import { JSON }`), so importing the server's TS source graph threw "does not provide an export named 'JSON'" at run time. Load the compiled output (transpiled/, emitted by tsc with those imports correctly elided) instead, via computed specifiers so tsc doesn't resolve transpiled/ statically; types still come from the .ts source through `typeof import(<source>)` casts. transpiled/ is present whenever the server runs (tsc-watch / the Docker build emit it). Relax `consistent-type-imports` (disallowTypeAnnotations: false) for test/e2e files so the `typeof import()` casts pass lint. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * test(e2e): drop per-test cleanup; isolate by tenant and run fully parallel Each test seeds a unique-id org, so the app's multi-tenancy isolates tests from one another and no cleanup is needed (the CI database is disposable). This lets the suite run with `fullyParallel: true` — Playwright has no random-order flag, and concurrent execution with no fixed order is the idiomatic way to prevent implicit ordering dependencies. Removes the Seeder's cleanup tracking entirely. Documents the two disciplines that keep this valid (seed your own data; never assert on cross-tenant/global state) and a "Scaling to per-worker databases" note for when the suite grows — that future change is infra-only because the deps fixture is already worker-scoped and tests already self-seed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * clean up * ci(e2e): cover root deps in path filter; drop runtime wait-on fetch Add .nvmrc, root package manifests/lock, and docker-compose.yaml to the paths-filter so the E2E job runs when files it depends on change. Replace the dynamic `npx --yes wait-on@8` readiness check (an undeclared runtime dependency that bypasses the lockfile) with a built-in bash wait loop polling tcp:8080 and http:3000. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Cassidy James <cassidyjames@roost.tools>