create-run-e2e-tests — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited create-run-e2e-tests (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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:
| 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.
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';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);
});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 }]);
});
});Theeslint-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.
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.
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.
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:
myThing: Gateway<ICreateMyThingUseCase>; to IPackmindApi (src/domain/api/IPackmindApi.ts). All gateway methods are typed with Gateway<IXxxUseCase> from @packmind/types.PackmindApi (src/infra/api/PackmindApi.ts) using the private post/get helpers — they inject the auth header and assert the status code.apiXxxFactory under src/domain/apiDataFactories/, reusing the shared @packmind/<domain>/test factory for default field values (see apiStandardFactory.ts / apiPackageFactory.ts for the shape).This keeps specs declarative: const standard = await apiStandardFactory(packmindApi) instead of a paragraph of POST plumbing.
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.)
describe blocks rather than one mega-test — a failure name then tells you exactly what broke.listInvoices(), listStandards()) and assert on the returned plain object. Don't reach into the DOM from the spec.describe blocks with their own beforeEach, each building on the parent's state. See CliInstallDistribution.spec.ts for the pattern.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.
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 reportCI 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.
testWith* fixture, not raw test.domain/pages, impl in infra/pages, registered in PageFactory + IPageFactory, expectedUrl() is a RegExp.packmindApi / an apiXxxFactory.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-tests is clean.~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.