Skip to content

test: add Playwright e2e testing framework with core scenario coverage#229

Merged
cosarah merged 12 commits intomainfrom
worktree-playwright-e2e
Mar 24, 2026
Merged

test: add Playwright e2e testing framework with core scenario coverage#229
cosarah merged 12 commits intomainfrom
worktree-playwright-e2e

Conversation

@wyuc
Copy link
Contributor

@wyuc wyuc commented Mar 23, 2026

Closes #230

Summary

  • Set up Playwright e2e testing infrastructure (playwright.config.ts, fixtures, Page Object Models)
  • Implement MockApi class using page.route() to intercept API calls with fixture data (zero source code invasion)
  • Add 3 test specs covering the main happy-path user flow:
    • home-to-generation: Home page loads → type requirement → click "Enter Classroom" → navigate to generation-preview
    • generation-flow: Mock SSE outline streaming + scene content/actions APIs → generation pipeline completes → auto-redirect to classroom
    • classroom-interaction: Seed IndexedDB with stage + scenes → classroom loads → sidebar shows scenes → switch scenes
  • Add e2e CI job to GitHub Actions (Chromium only, production build)

Test plan

  • pnpm test:e2e — all 3 specs pass locally (11.2s)
  • pnpm check — Prettier clean
  • pnpm lint — no new errors
  • CI e2e job runs on this PR

🤖 Generated with Claude Code

wyuc and others added 9 commits March 23, 2026 19:34
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Also exclude e2e/ from ESLint to avoid react-hooks false positives
on Playwright's fixture `use` callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds the first Playwright spec covering the home page UI and navigation
to generation-preview. Also updates playwright config to use port 3002
to avoid conflicts with other running services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Seeds IndexedDB with a 3-scene stage by navigating to / first (so
Dexie initializes the DB at v8), then writing data without re-triggering
onupgradeneeded. Verifies the sidebar renders 3 scenes and that clicking
a scene switches the active scene heading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… type annotations

- Add data-testid="scene-list", "scene-item", "scene-title" to scene-sidebar.tsx
- Update classroom.page.ts selectors to use data-testid instead of CSS class chains
- Extract createSettingsStorage() helper into e2e/fixtures/test-data/settings.ts
- Update all 3 spec files to use createSettingsStorage() and import defaultTheme from scene-content
- Add SceneOutline[] type annotation to mockOutlines via relative import
- Add SlideTheme type annotation to defaultTheme in scene-content.ts
- Remove redundant mockServerProviders() call from setupGenerationMocks()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy link
Collaborator

@cosarah cosarah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review: Playwright E2E Testing Framework

Overall this is a well-structured PR that introduces a clean Playwright e2e testing infrastructure with good separation of concerns (fixtures, Page Object Models, test data). The zero-source-code-invasion approach using page.route() for API mocking is solid. A few issues to address:


Bug: Fragile / Incorrect Selector

e2e/tests/classroom-interaction.spec.ts:162

await expect(page.getByRole('heading', { name: '光反应' })).toBeVisible();

Classroom page and scene-sidebar.tsx do not render any <h1><h6> or role="heading" element for the current scene name. The scene title in the sidebar is a plain <span class="...truncate...">. This assertion will likely fail at runtime.

Suggestion: Either change the assertion to match the actual sidebar markup (e.g. check which scene card has an "active" class), or add a heading element to the classroom page to display the current scene title.


Bug: Fragile CSS-class Selectors

e2e/pages/classroom.page.ts:12

this.sidebarScenes = page.locator('.overflow-y-auto.space-y-2 > div');

Targeting Tailwind utility class names is brittle — they can change during routine styling work. Consider adding data-testid attributes to the sidebar container and scene items, e.g. data-testid="scene-list" and data-testid="scene-item".

Same applies to span.truncate (line 28) for scene titles.


Suggestion: Duplicated Constants

SETTINGS_STORAGE and defaultTheme are defined in multiple test files (classroom-interaction.spec.ts, generation-flow.spec.ts, home-to-generation.spec.ts). Consider extracting them into a shared fixture file like e2e/fixtures/test-data/constants.ts to keep things DRY and make future schema changes easier.


Suggestion: setupGenerationMocks double-mocks serverProviders

e2e/fixtures/mock-api.ts:70-75

setupGenerationMocks() calls mockServerProviders(), but the base.ts fixture already calls mockServerProviders() on every test via the mockApi fixture. Calling it twice is harmless but misleading — either remove it from setupGenerationMocks() or document that it's idempotent.


Minor: CI Job — Missing pnpm test in existing lint job

The diff adds a Unit Tests step (pnpm test) to the existing lint job. This is fine but the job name is "Lint / Type Check" — it may be worth updating the name to reflect that it now also runs unit tests, or splitting it into its own job.


Minor: Hardcoded Port

playwright.config.ts:11baseURL: 'http://localhost:3002'

Port 3002 is fine as a convention, but if another service is running on that port, tests will silently connect to the wrong server. The webServer.env.PORT is set to match, which is good — just worth noting in a comment or README.


Summary

Area Verdict
Architecture ✅ Good — fixtures, POM, test-data separation
API Mocking ✅ Clean page.route() approach
Test Coverage ✅ Covers core happy-path flow
Selectors ⚠️ Needs fix — heading assertion will fail; CSS-class selectors are fragile
DRY ⚠️ Minor — duplicated constants
CI Integration ✅ Good — artifact upload on failure

Main blocker: fix the getByRole('heading') assertion that doesn't match actual markup.

🤖 Generated with Claude Code

DataCrumbsAI pushed a commit to DataCrumbsAI/OpenMAIC that referenced this pull request Mar 23, 2026
…and dedup

Must-fix issues from @cosarah's review:
- Add data-testid="scene-title-heading" to <h1> in header.tsx and update
  classroom-interaction test to use getByTestId instead of getByRole("heading"),
  making the selector explicit and independent of ARIA role assumptions
- Add data-testid="scene-list", "scene-item", "scene-item-title" to
  scene-sidebar.tsx; update classroom.page.ts to use data-testid locators
  instead of fragile Tailwind class selectors (.overflow-y-auto.space-y-2 > div)

Should-fix issues:
- Extract SETTINGS_STORAGE and defaultTheme constants duplicated across 3
  test files into e2e/fixtures/test-data/constants.ts
- Remove redundant mockServerProviders() call from setupGenerationMocks();
  base fixture already mocks it for every test

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@DataCrumbsAI
Copy link

Review Feedback Addressed

Thanks for the thorough review @cosarah! All must-fix and should-fix items have been implemented. Here's a summary of what changed:

Bug Fixes (Must Fix)

1. Wrong heading selector (classroom-interaction.spec.ts:162)

  • Added data-testid="scene-title-heading" to the <h1> in components/header.tsx
  • Updated ClassroomPage to expose sceneTitleHeading = page.getByTestId('scene-title-heading')
  • Test assertion updated to await expect(classroom.sceneTitleHeading).toContainText('光反应') — explicit and independent of ARIA role assumptions

2. Fragile CSS-class selectors (classroom.page.ts:12)

  • Added data-testid="scene-list" to the scenes list container in components/stage/scene-sidebar.tsx
  • Added data-testid="scene-item" to each scene item <div>
  • Added data-testid="scene-item-title" to the title <span>
  • ClassroomPage now uses page.locator('[data-testid="scene-item"]') and getByTestId('scene-item-title') — no more Tailwind class dependency

Refactoring (Should Fix)

3. Duplicated constants

  • Extracted SETTINGS_STORAGE and defaultTheme into e2e/fixtures/test-data/constants.ts
  • All three test files now import from the shared module

4. Double-mocked serverProviders

  • Removed the redundant this.mockServerProviders() call from setupGenerationMocks() — the base fixture already covers it

Commit: ed35ad3 on DataCrumbsAI/OpenMAIC@worktree-playwright-e2e

Note: The minor items (CI job name, port comment) are acknowledged — they can be addressed as a follow-up if desired.

🤖 Generated with Claude Code

@wyuc
Copy link
Contributor Author

wyuc commented Mar 24, 2026

Thanks for the review!

Already fixed in 9fcba8b:

  • CSS-class selectors → replaced with data-testid attributes
  • Duplicated constants → extracted to shared helpers
  • Double mockServerProviders() → removed from setupGenerationMocks()

Re: getByRole('heading') concern — the current scene title is rendered as <h1> in components/header.tsx:97, so the assertion is correct. CI confirms it passes.

Copy link
Collaborator

@cosarah cosarah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review

Previous round feedback all addressed, confirmed working. LGTM overall — clean architecture, solid mock strategy.

One thing to fix before merge:

e2e/ should be excluded from tsconfig.json

tsconfig.json currently includes all **/*.ts files, which pulls e2e tests into Next.js compilation. As the test suite grows and uses Node/Playwright-specific APIs, this will cause false type errors.

Fix: add "e2e" to the exclude array in tsconfig.json, or create a dedicated e2e/tsconfig.json.

🤖 Generated with Claude Code

- Add "e2e" to tsconfig.json exclude array to prevent Next.js
  compilation from pulling in Playwright/Node APIs
- Rename CI job from "Lint & Typecheck" to "Lint, Typecheck & Unit Tests"
  to reflect that it now also runs unit tests (added in PR #144)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Copy link
Collaborator

@cosarah cosarah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@cosarah cosarah merged commit 37e0455 into main Mar 24, 2026
3 checks passed
ifishcool pushed a commit to box3lab/Linksy that referenced this pull request Mar 24, 2026
THU-MAIC#229)

* chore: add Playwright e2e testing infrastructure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add e2e mock fixture data

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add MockApi route interception helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add base fixture and page object models

Also exclude e2e/ from ESLint to avoid react-hooks false positives
on Playwright's fixture `use` callback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add home-to-generation e2e spec

Adds the first Playwright spec covering the home page UI and navigation
to generation-preview. Also updates playwright config to use port 3002
to avoid conflicts with other running services.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add generation-flow e2e spec

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add classroom-interaction e2e spec

Seeds IndexedDB with a 3-scene stage by navigating to / first (so
Dexie initializes the DB at v8), then writing data without re-triggering
onupgradeneeded. Verifies the sidebar renders 3 scenes and that clicking
a scene switches the active scene heading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: add Playwright e2e test job

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test(e2e): fix review issues — data-testid selectors, shared helpers, type annotations

- Add data-testid="scene-list", "scene-item", "scene-title" to scene-sidebar.tsx
- Update classroom.page.ts selectors to use data-testid instead of CSS class chains
- Extract createSettingsStorage() helper into e2e/fixtures/test-data/settings.ts
- Update all 3 spec files to use createSettingsStorage() and import defaultTheme from scene-content
- Add SceneOutline[] type annotation to mockOutlines via relative import
- Add SlideTheme type annotation to defaultTheme in scene-content.ts
- Remove redundant mockServerProviders() call from setupGenerationMocks()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: exclude e2e/ from tsconfig, rename CI job

- Add "e2e" to tsconfig.json exclude array to prevent Next.js
  compilation from pulling in Playwright/Node APIs
- Rename CI job from "Lint & Typecheck" to "Lint, Typecheck & Unit Tests"
  to reflect that it now also runs unit tests (added in PR THU-MAIC#144)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: 杨慎 <117187635+cosarah@users.noreply.github.com>
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.

test: add Playwright e2e testing framework

3 participants