Conversation
- Install @playwright/test in demo package - playwright.config.ts: loads .env, starts Vite via webServer, wires globalSetup/globalTeardown - e2e/global-setup.ts: starts Postgres (docker compose -d), waits for readiness, seeds deterministic test data, clears the zero-cache replica, then spawns zero-cache-dev and waits for port 4848 - e2e/global-teardown.ts: kills the zero-cache process on exit - e2e/seed-test.ts: 10 fixed items (Alpha–Kappa) with inverted created/modified timestamps so all four sort orderings are distinct - e2e/tests/app.spec.ts: heading, item count, row rendering, default sort - e2e/tests/sort.spec.ts: sort field and direction toggle assertions - e2e/tests/item-detail.spec.ts: panel open/close, URL hash permalink, aria-selected, description and ID display Run with: cd demo && pnpm test:e2e https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
- Use node: protocol prefix for all Node.js built-in module imports
- Expand seed data from 10 to 200 items so the virtualizer exercises
paging (min page size is 100); extra items have inverted created/
modified so all four sort orderings still produce distinct first rows:
modified DESC → Alpha Item, modified ASC → Test Item 200,
created DESC → Kappa Item, created ASC → Test Item 011
- Remove manual loadDotEnv() helper from playwright.config.ts; env vars
are now loaded exclusively via `node --env-file=.env` in test:e2e
- Update sort.spec.ts assertions to match the new 200-item dataset
- Add scroll.spec.ts: scrolls the virtualizer to the bottom and verifies
the last row (index 199) becomes visible (tests paging)
- Add .github/workflows/e2e.yml CI workflow that installs Playwright,
runs `pnpm test:e2e` (globalSetup handles postgres + zero-cache), and
uploads the playwright-report artifact on failure
https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
`node --env-file=.env ./node_modules/.bin/playwright` fails because pnpm generates a bash wrapper in .bin/, not a JS file. Node.js can't execute bash and throws a SyntaxError before playwright even starts. The package.json bin field for @playwright/test points to cli.js, so use that path directly: node --env-file=.env ./node_modules/@playwright/test/cli.js test https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
tsconfig.app.json's "./*.ts" glob picked up playwright.config.ts and tsgo failed with TS2614 because @playwright/test is a CJS package whose named exports conflict with the project's verbatimModuleSyntax + NodeNext settings. Fix: - Exclude ./playwright.config.ts from demo/tsconfig.app.json - Add demo/e2e/tsconfig.json that extends tsconfig-shared.json and explicitly adds "types": ["@playwright/test"], covering both playwright.config.ts and all e2e/**/*.ts files - Add demo/e2e/tsconfig.json to the root check-types script so the playwright files are still type-checked https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
Two root causes:
1. Stale locator after scroll (scroll.spec.ts)
The virtualizer unmounts row 0 when it scrolls off-screen, so
locator('[data-index="0"]').locator('../..') can't be re-evaluated
after scrolling to the bottom — Playwright waits forever for the
unmounted element and the test exceeds the 30 s budget.
Fix: capture an ElementHandle *before* any scrolling; the handle
holds a direct DOM reference that remains valid even after row 0 is
unmounted.
2. Timeout budget too tight for CI (playwright.config.ts + app.spec.ts)
Each test's beforeEach waits up to 20 s for zero-cache to replicate
data; stacked with per-assertion timeouts this easily exceeds the
default 30 s test timeout in CI.
Fix: set timeout to 60 s in CI.
Also accept the estimated count "(~200)" in the item-count test
rather than requiring the exact "(200)", which only appears once
every page has been fetched.
https://claude.ai/code/session_01H3p3JUgo2y2389e3qKBo7N
…components - Added VITE_PUBLIC_CACHE_PORT to .env and updated main.tsx to use this variable for cache URL. - Modified global-setup.ts to spawn zero-cache on the specified port. - Updated tests to reflect changes in item selection and visibility based on new caching behavior. - Enhanced scroll and sort tests to ensure proper functionality with the new cache settings. - Updated dependencies to use Playwright 1.59.0 and adjusted test configurations accordingly. - Improved virtualizer behavior to handle scroll adjustments more effectively.
- Add Playwright test suite covering app rendering, item detail panel, sort controls, scroll/paging, permalink navigation, and back/forward scroll restore - Add global setup/teardown that starts Postgres, seeds deterministic test data, and launches zero-cache - Add GitHub Actions workflow to run e2e tests on push/PR - Make zero-cache port configurable via VITE_PUBLIC_CACHE_PORT env var - Fix race in useZeroVirtualizer where paging effects acted on stale virtual items after programmatic scrollToOffset/scrollToIndex before the browser scroll event fired (awaitingScrollSettleRef) - Restrict vitest to src/**/*.test.ts to avoid picking up Playwright tests
There was a problem hiding this comment.
Pull request overview
This PR introduces a Playwright-based end-to-end test suite for the demo app and adjusts the virtualizer logic to avoid paging/anchor updates based on stale virtual items immediately after programmatic scrolls.
Changes:
- Added Playwright e2e infrastructure (config, global setup/teardown, deterministic DB seeding) plus multiple e2e specs covering rendering, sorting, paging/scroll, permalinks, and history restoration.
- Fixed a paging/anchor update race in
useZeroVirtualizerby gating paging decisions until the next real browser scroll event after programmatic scroll adjustments. - Updated tooling/config: constrain Vitest test discovery to
src/**/*.test.{ts,tsx}, add CI workflow to run e2e, and make demo cache port configurable viaVITE_PUBLIC_CACHE_PORT.
Reviewed changes
Copilot reviewed 17 out of 20 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
vitest.config.ts |
Restricts Vitest discovery to avoid picking up Playwright specs. |
src/react/use-zero-virtualizer.ts |
Adds “awaiting scroll settle” gating around paging decisions after programmatic scrolls. |
package.json |
Ensures type-checking includes the new e2e tsconfig. |
demo/main.tsx / demo/vite-env.d.ts / demo/.env |
Makes zero-cache port configurable via Vite env. |
demo/playwright.config.ts |
Adds Playwright runner configuration and webServer integration. |
demo/e2e/* |
Adds global setup/teardown + deterministic test seeding + e2e specs. |
.github/workflows/e2e.yml |
Adds GitHub Actions workflow to run e2e tests on push/PR. |
.gitignore |
Ignores Playwright’s last-run metadata file. |
pnpm-lock.yaml / demo/package.json |
Adds Playwright-related dependencies. |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const env: NodeJS.ProcessEnv = { | ||
| ...process.env, | ||
| // Ensure node_modules/.bin is on PATH so zero-cache-dev can find zero-cache. | ||
| PATH: `${binDir}:${process.env['PATH'] ?? ''}`, | ||
| ZERO_REPLICA_FILE: REPLICA_FILE, | ||
| ZERO_LOG_LEVEL: 'error', |
There was a problem hiding this comment.
PATH is being constructed with a hard-coded : separator. That breaks on Windows where the path delimiter is ;. Use path.delimiter when concatenating PATH entries so the e2e setup works cross-platform.
| const proc = spawn(command, ['--port', String(port)], { | ||
| cwd: DEMO_DIR, | ||
| env, | ||
| stdio: ['ignore', 'pipe', 'pipe'], | ||
| detached: true, | ||
| }); |
There was a problem hiding this comment.
spawnZeroCache uses detached: true, but teardown only sends SIGTERM to the recorded pid. If zero-cache-dev spawns child processes, killing only the parent pid may leave orphaned processes and ports in use.
Consider either avoiding detached: true, or (on POSIX) killing the whole process group (e.g. signal -pid) and/or adding a more robust shutdown mechanism.
Uh oh!
There was an error while loading. Please reload this page.