create-run-e2e-tests
Skill321 starsChanged 54 days agoApache-2.0View on GitHub
Guide for writing and running new Playwright end-to-end tests in the `apps/e2e-tests/` directory of the Packmind monorepo. Use this skill whenever you add or modify a spec that drives the real frontend and API — for example testing a user flow, a new page/route, a feature behind a flag, or a UI behavior end-to-end. Triggers on "write an e2e test", "add a Playwright test", "test this flow end-to-end", "cover this page with an e2e", "e2e for the frontend", or any work that lands a `*.spec.ts` under apps/e2e-tests/src/. Prefer this over hand-rolling raw Playwright `test()` calls — the codebase has mandatory fixtures and a Page Object Model you must follow.
In this repository92 other files it gives its agents.
- AGENTS.mdAGENTS.md
- apps/AGENTS.mdAGENTS.md
- packages/AGENTS.mdAGENTS.md
- apps/CLAUDE.mdCLAUDE.md
- CLAUDE.mdCLAUDE.md
- packages/CLAUDE.mdCLAUDE.md
- .cursor/rules/packmind/standard-backend-tests-redaction.mdcCursor rule
- .cursor/rules/packmind/standard-changelog.mdcCursor rule
- .cursor/rules/packmind/standard-code-comments.mdcCursor rule
- .cursor/rules/packmind/standard-compliance-logging-personal-information.mdcCursor rule
- .cursor/rules/packmind/standard-typescript-good-practices.mdcCursor rule
- agent-skill-frontmatter-auditSkill
- amplitude-listener-events-adoptionSkill
- cli-e2e-test-authoringSkill
- datadog-analysisSkill
- doc-auditSkill
- feature-specSkill
- feature-sprintSkill
- packmind-onboardSkill
- packmind-update-playbookSkill
- playbook-cli-auditSkill
- product-mapSkill
- qa-reviewSkill
- release-proprietarySkill
- upgrade-runtime-stackSkill
- working-with-playground-appSkill
- working-with-pm-design-kitSkill
- agentic-design-sessionSkill
- agentic-doc-ingestSkill
- agentic-feature-framingSkill
- agentic-orchestratorSkill
- agent-skill-frontmatter-auditSkill
- amplitude-listener-events-adoptionSkill
- cli-e2e-test-authoringSkill
- creating-end-user-documentation-for-packmindSkill
- datadog-analysisSkill
- doc-auditSkill
- feature-flags-auditSkill
- feature-flags-authoringSkill
- feature-specSkill
- feature-sprintSkill
- how-to-write-typeorm-migrations-in-packmindSkill
- improve-claude-mdSkill
- packmind-onboardSkill
- packmind-update-playbookSkill
- playbook-cli-auditSkill
- product-mapSkill
- qa-reviewSkill
- release-proprietarySkill
- upgrade-runtime-stackSkill
- working-with-playground-appSkill
- working-with-pm-design-kitSkill
- agent-skill-frontmatter-auditSkill
- amplitude-listener-events-adoptionSkill
- cli-e2e-test-authoringSkill
- creating-end-user-documentation-for-packmindSkill
- datadog-analysisSkill
- doc-auditSkill
- feature-flags-auditSkill
- feature-flags-authoringSkill
- The first 60 shown.
The file
We split this skill into the rules it gives an agent
8 rules. About 0 are common, 3 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 14
Never use Playwright's raw
test: Always use one of the project fixtures. - Very specific: line 40
All three live in src/fixtures/packmindTest.ts.
- Distinctive: line 126
When a test needs a resource that already exists in the product, create it via the API rather than clicking through the UI.
- Very specific: line 128
Add myThing: Gateway<ICreateMyThingUseCase>.
- Distinctive: line 136
Before writing, ask: is the feature under test gated by a feature flag?
- Very specific: line 138
If yes, the test user must have a @packmind.com email so the flag resolves to on.
- Very specific: line 149
Read state through a page-object method (listInvoices(), listStandards()) and assert on the returned plain object.
- Distinctive: line 180
Use local iteration while authoring a spec.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Authoring Packmind E2E Tests
Overview
apps/e2e-tests/ runs Playwright against the real frontend (http://localhost:4200) and API. Tests drive the browser through a Page Object Model and seed irrelevant setup data through the API, not the UI. The dev stack must be running first (see michel-run-local-dev-stack).
Two rules dominate everything here, and both come from the project's .packmind standards:
- Never use Playwright's raw
test. Always use one of the project fixtures. They handle user creation, sign-up, and API-key setup so each test starts from a clean, authenticated state. - Drive the UI through Page Objects, never raw selectors in specs. A spec should read like a user story; selectors live inside page objects so a markup change breaks one file, not twenty tests.
Where files go
| File type | Location | Naming |
| Spec | src/features/<area>/ | <Feature>.spec.ts — one spec per feature |
| Page object interface | src/domain/pages/index.ts | IXxxPage |
| Page object impl | src/infra/pages/ | XxxPage.ts |
| API gateway type | src/domain/api/IPackmindApi.ts | Gateway<IXxxUseCase> |
| API gateway impl | src/infra/api/PackmindApi.ts | — |
| API data factory | src/domain/apiDataFactories/ | apiXxxFactory.ts |
This mirrors the hexagonal split used across the repo: domain/ holds interfaces, infra/ holds implementations.
Choosing a fixture
The three fixtures form a chain — each extends the previous and adds one capability. Pick the lowest one that gives you what the test needs, so you don't pay for setup you won't use.
| Fixture | Provides | Use when |
testWithUserData | userData (email/password), page | Testing sign-up / activation / trial itself — i.e. flows that run before a session exists. You drive the PageFactory yourself. |
testWithUserSignedUp | everything above + dashboardPage (already signed in) | Testing in-app UI where you don't need to seed API data. |
testWithApi | everything above + packmindApi | You must seed standards/packages/skills/etc. before exercising the UI. |
All three live in src/fixtures/packmindTest.ts. Import the one you need:
import { testWithApi } from '../../fixtures/packmindTest';Example — UI-only test
import { testWithUserSignedUp } from '../../fixtures/packmindTest';
import { expect } from '@playwright/test';
testWithUserSignedUp('user sees an empty standards list', async ({ dashboardPage }) => {
const standardsPage = await dashboardPage.openStandards();
// eslint-disable-next-line playwright/no-standalone-expect
expect(await standardsPage.hasNoStandards()).toBe(true);
});Example — seed via API, assert via UI
Seed everything not under test through packmindApi (it's faster and less brittle than clicking through setup), then exercise the actual feature in the browser:
import { testWithApi } from '../../fixtures/packmindTest';
import { apiStandardFactory } from '../../domain/apiDataFactories/apiStandardFactory';
import { expect } from '@playwright/test';
testWithApi.describe('packages page', () => {
testWithApi('lists a standard added to a package', async ({ packmindApi, dashboardPage }) => {
const standard = await apiStandardFactory(packmindApi);
// ...seed package referencing standard.id...
const packagesPage = await dashboardPage.openPackages();
const packagePage = await packagesPage.openPackage('My package');
const standards = await packagePage.listStandardsInPackage();
// eslint-disable-next-line playwright/no-standalone-expect
expect(standards).toEqual([{ name: standard.name }]);
});
});The
eslint-disable playwright/no-standalone-expectline is required: the lint rule can't tell that a fixture-extendedtestWithApi(...)callback is a real test body. Add it on anyexpectthat ESLint flags.
Writing a Page Object
A page object is the typed API a spec uses to talk to one route. Adding one is four mechanical steps — keep them in sync or TypeScript will complain.
1. Declare the interface in src/domain/pages/index.ts. In-app pages extend IPackmindAppPage (gives openStandards, openSettings, etc. for free); pre-login pages extend IPackmindPage.
export interface IBillingPage extends IPackmindAppPage {
listInvoices(): Promise<{ date: string; amount: string }[]>;
}2. Implement it in src/infra/pages/BillingPage.ts, extending the matching abstract base, and define expectedUrl() as a RegExp (the project standard — Playwright's glob matching is too loose):
import { IBillingPage } from '../../domain/pages';
import { AbstractPackmindAppPage } from './AbstractPackmindAppPage';
export class BillingPage extends AbstractPackmindAppPage implements IBillingPage {
async listInvoices(): Promise<{ date: string; amount: string }[]> {
await this.page.locator('table tbody tr').first().waitFor();
// ...read rows...
}
expectedUrl(): RegExp {
return /.*\/billing$/;
}
}3. Register it in the factory — add a getter to IPageFactory and PageFactory. Navigation methods that land on this page return the page object, so specs chain naturally (dashboardPage.openBilling() → IBillingPage).
4. Prefer data-testid over text/role selectors for app chrome that's likely to be reworded. The codebase exports test-id enums from @packmind/frontend (e.g. SidebarNavigationDataTestId) — reuse them.
Why navigation returns a page object
After any navigation, the factory calls waitForLoaded() (which awaits expectedUrl) before handing back the typed page. That's the project's this.pageFactory()-after-navigation rule: it guarantees the URL actually changed before the next interaction runs, killing a whole class of race conditions. Never page.goto + interact directly in a spec — go through the factory.
Seeding data through the API
When a test needs a resource that already exists in the product, create it via the API rather than clicking through the UI. Add the capability bottom-up:
- Add
myThing: Gateway<ICreateMyThingUseCase>;toIPackmindApi(src/domain/api/IPackmindApi.ts). All gateway methods are typed withGateway<IXxxUseCase>from@packmind/types. - Implement it in
PackmindApi(src/infra/api/PackmindApi.ts) using the privatepost/gethelpers — they inject the auth header and assert the status code. - Wrap it in an
apiXxxFactoryundersrc/domain/apiDataFactories/, reusing the shared@packmind/<domain>/testfactory for default field values (seeapiStandardFactory.ts/apiPackageFactory.tsfor the shape).
This keeps specs declarative: const standard = await apiStandardFactory(packmindApi) instead of a paragraph of POST plumbing.
Feature flags
Before writing, ask: is the feature under test gated by a feature flag?
If yes, the test user must have a @packmind.com email so the flag resolves to on. Flip the fixture option at the top of the file, before any describe/test:
testWithApi.use({ underFeatureFlag: true });Without it the fixture creates an @example.com user, the flag stays off, and the feature is invisible — the test fails for the wrong reason. (Implementation: see the underFeatureFlag option in packmindTest.ts.)
Assertion style
- Split distinct expectations into separate tests or
describeblocks rather than one mega-test — a failure name then tells you exactly what broke. - Read state through a page-object method (
listInvoices(),listStandards()) and assert on the returned plain object. Don't reach into the DOM from the spec. - For multi-step scenarios (sign up → create → verify), nest
describeblocks with their ownbeforeEach, each building on the parent's state. SeeCliInstallDistribution.spec.tsfor the pattern.
Running the tests
There are two ways to run, and they share the same entry point — npm run e2e (i.e. npx playwright test). Never run the suite through Nx; it isn't wired for it.
Local iteration — fast feedback while writing a spec
Bring the stack up first (michel-run-local-dev-stack) so localhost:4200 serves the frontend. Then, from apps/e2e-tests/:
npm run e2e # all specs (BASE_URL defaults to http://localhost:4200)
npx playwright test <Feature>.spec.ts # one file
npx playwright test --headed # watch it run
npx playwright test --debug # step through with the inspector
npx playwright show-report # open the last HTML reportFull / CI run — containerized, the canonical way
CI runs the suite inside the run-e2e-tests Docker Compose service (Playwright image), whose entrypoint is the same npm run e2e but with BASE_URL=http://frontend:4200. It lives behind the e2e profile, so it only starts when you ask for it. Launch it, then block on its exit code with the helper script — this is what CI does and what keeps the two consistent:
# From the repo root, with PACKMIND_EDITION=oss already exported
docker compose --profile=e2e up -d run-e2e-tests
./scripts/wait-for-e2e-tests.sh # waits for the container, returns its exit codewait-for-e2e-tests.sh matches the container by the run-e2e-tests name pattern, waits for it to finish, prints its logs, and exits with the container's code — so a non-zero exit means a failing suite. Reports land in apps/e2e-tests/playwright-report/ and apps/e2e-tests/test-results/ via the volume mount, exactly as in local runs.
Use local iteration while authoring a spec; use the containerized path to reproduce a CI result or run the whole suite the way the pipeline does.
Checklist before you call it done
- Spec uses a
testWith*fixture, not rawtest. - No raw selectors in the spec — all interaction goes through page objects.
- New page object: interface in
domain/pages, impl ininfra/pages, registered inPageFactory+IPageFactory,expectedUrl()is a RegExp. - Setup data not under test is seeded via
packmindApi/ anapiXxxFactory. testWithApi.use({ underFeatureFlag: true })added iff the feature is flagged.npx playwright test <file>passes locally against a running stack../node_modules/.bin/nx lint e2e-testsis clean.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

