Create end-to-end tests using Playwright with page objects and fixtures following project conventions
Create end-to-end tests for the user flow described below.
First, analyze the User Flow to identify:
Before writing code, explore relevant existing files:
apps/e2e-tests/
├── pages/ # Page Objects - check for reusable pages
├── fixtures/ # Base fixtures (auth.fixtures.ts)
└── tests/
└── [feature]/ # Existing test patterns
├── [feature].fixtures.ts
└── [feature].spec.ts
Key files to reference:
apps/e2e-tests/fixtures/auth.fixtures.ts - Authentication fixturesapps/e2e-tests/pages/SiteCreationPage.ts - Page object pattern exampleapps/e2e-tests/tests/site-creation/site-creation.fixtures.ts - Fixture composition exampleFor each page/screen in the flow, create or update a page object in apps/e2e-tests/pages/:
Page Object Pattern:
import { expect, type Page } from "@playwright/test";
export class FeaturePage {
readonly page: Page;
constructor(page: Page) {
this.page = page;
}
// Navigation
async goto(): Promise<void> {
await this.page.goto("/route-path");
}
// Assertions (prefix with expect*)
async expectStepVisible(): Promise<void> {
await expect(this.page.getByRole("heading", { name: "Step Title" })).toBeVisible();
}
// User Actions (verb-based names)
async clickButton(): Promise<void> {
await this.page.getByRole("button", { name: "Button Text" }).click();
}
async fillInput(value: string): Promise<void> {
await this.page.getByLabel("Input Label").fill(value);
}
async selectOption(value: string): Promise<void> {
await this.page.getByRole("radio", { name: value }).check({ force: true });
await this.submit();
}
// Private helpers
private async submit(): Promise<void> {
await this.page.getByRole("button", { name: /Valider|Suivant/ }).click();
}
}
Multi-step wizards: assert each step's title as you walk through it (expectStepTitle(title) — see apps/e2e-tests/pages/PhotovoltaicProjectCreationPage.ts for the pattern). Find each title's exact text with grep -rn "WizardFormLayout title" apps/web/src rather than guessing — many step components (e.g. create-mode selection, reinstatement question) are shared across wizards, so the same title string applies in multiple flows.
Selector Priority (accessibility-first):
getByRole() - buttons, links, headings, textboxesgetByLabel() - form inputs with labelsgetByText() - visible text contentlocator() - CSS selectors (last resort)Create apps/e2e-tests/tests/[feature]/[feature].fixtures.ts:
import { test as authTest } from "../../fixtures/auth.fixtures";
import { FeaturePage } from "../../pages/FeaturePage";
type FeatureFixtures = {
featurePage: FeaturePage;
};
export const test = authTest.extend<FeatureFixtures>({
featurePage: async ({ authenticatedPage }, use) => {
const featurePage = new FeaturePage(authenticatedPage);
await use(featurePage);
},
});
export { expect } from "@playwright/test";
If authentication is NOT needed, extend from base Playwright test instead:
import { test as base, expect } from "@playwright/test";
Create apps/e2e-tests/tests/[feature]/[feature].spec.ts:
import { test } from "./[feature].fixtures";
test.describe("Feature Name", () => {
test("describes what the user can do", async ({ featurePage }) => {
// Navigate to starting point
await featurePage.goto();
// Step 1: Action + optional assertion
await featurePage.expectStepVisible();
await featurePage.clickStart();
// Step 2: Another action
await featurePage.fillInput("value");
await featurePage.submit();
// Final assertions
await featurePage.expectSuccessMessage();
});
});
Test Naming: Use descriptive names that explain what the user can do:
"allows authenticated user to create a new project""shows error when required field is empty""redirects to login when not authenticated"# Start the e2e stack (builds fresh images and waits for readiness)
make e2e-up-build
# Run specific test file
pnpm --filter e2e-tests test:headless tests/[feature]/[feature].spec.ts
# Run with browser visible (for debugging)
pnpm --filter e2e-tests test:headed tests/[feature]/[feature].spec.ts
# Type check
pnpm --filter e2e-tests typecheck
Before completing, ensure you have created/updated:
apps/e2e-tests/pages/[PageName].tsapps/e2e-tests/tests/[feature]/[feature].fixtures.tsapps/e2e-tests/tests/[feature]/[feature].spec.tspnpm --filter e2e-tests test:headless tests/[feature]/pnpm --filter e2e-tests typecheckimport type { SiteNature, FricheActivity } from "shared";
import { getLabelForSiteNature } from "shared";
// Wait for element to appear
await this.page.getByRole("option").first().waitFor({ state: "visible", timeout: 10000 });
// Wait for navigation
await expect(this.page).toHaveURL("/expected-path");
async fillAutocomplete(searchText: string): Promise<void> {
const input = this.page.getByRole("searchbox", { name: /Label/i });
await input.pressSequentially(searchText, { delay: 50 });
const firstOption = this.page.getByRole("option").first();
await firstOption.waitFor({ state: "visible", timeout: 10000 });
await firstOption.click();
}
async expectDataInList(expectedData: [label: string, value: string][]): Promise<void> {
for (const [label, value] of expectedData) {
await expect(
this.page.locator("dl").filter({ hasText: label }).locator("dt")
).toHaveText(value);
}
}
$ARGUMENTS
npx skills add incubateur-ademe/create-e2e-test下载完整 Skill 目录,包含 SKILL.md 及所有相关文件
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer