chore: starter template
This commit is contained in:
34
e2e/README.md
Normal file
34
e2e/README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# e2e — end-to-end tests (Cucumber + Playwright)
|
||||
|
||||
Two tiers:
|
||||
|
||||
## `@smoke` — the build gate (mutationless, runs against the live server)
|
||||
|
||||
Fast, read-only checks that run against the **already-running** app (the sandbox
|
||||
dev server, or a local `yarn dev`). They never mutate domain data — they sign in
|
||||
(an idempotent test-user sign-up) and navigate, nothing more — so they are safe
|
||||
to run against the normal server on every build.
|
||||
|
||||
- `features/auth.feature` — sign in through the real login form.
|
||||
- `features/pages.feature` — signed in, every static route must render with no
|
||||
HTTP error, no failed/5xx app API call, no uncaught exception, and no console
|
||||
error. New routes are picked up automatically from the generated route tree.
|
||||
|
||||
Run: `yarn workspace @project/e2e test` (the build agent calls this via the
|
||||
`fabric-app-smoke` tool).
|
||||
|
||||
## `@e2e` — deeper behaviour tests (mutating, isolated server + DB)
|
||||
|
||||
Create/edit/delete flows and permission checks. Because they mutate state they
|
||||
must be **deterministic**, so they run like the seminarhof harness: a separate
|
||||
pikku dev server with its own golden/isolated DB per scenario, against the same
|
||||
frontend (`E2E_MANAGE_SERVERS=1 E2E_ISOLATED_DB=1`). Add these as the app grows —
|
||||
do NOT tag them `@smoke` (they are not part of the fast build gate).
|
||||
|
||||
## Extend
|
||||
|
||||
Add a `features/<domain>.feature` plus `steps/<domain>.steps.ts` per area. Reuse
|
||||
the generic steps in `steps/common.steps.ts` and the `AppWorld` primitives
|
||||
(`gotoApp`, `signIn`, `expectText`). Keep `common.steps.ts` and `world.ts`
|
||||
generic. Tag a scenario `@smoke` only if it is mutationless and must pass on
|
||||
every build.
|
||||
21
e2e/package.json
Normal file
21
e2e/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@project/e2e",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"description": "End-to-end tests \u2014 Cucumber + Playwright. Runs against the live app (sandbox dev server or a local dev server).",
|
||||
"scripts": {
|
||||
"test": "cucumber-js --config tests/cucumber.mjs --tags 'not @skip'",
|
||||
"test:tag": "cucumber-js --config tests/cucumber.mjs --tags",
|
||||
"test:headed": "HEADED=1 cucumber-js --config tests/cucumber.mjs --tags 'not @skip'",
|
||||
"tsc": "tsc --noEmit"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cucumber/cucumber": "^11.0.0",
|
||||
"@pikku/cucumber": "^0.12.11",
|
||||
"@playwright/test": "^1.50.0",
|
||||
"@types/node": "^22",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "~5.8.0"
|
||||
}
|
||||
}
|
||||
6
e2e/tests/cucumber.mjs
Normal file
6
e2e/tests/cucumber.mjs
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
require: ['tests/support/**/*.ts', 'tests/steps/**/*.ts'],
|
||||
paths: ['tests/features/**/*.feature'],
|
||||
format: ['progress'],
|
||||
forceExit: true,
|
||||
}
|
||||
8
e2e/tests/features/auth.feature
Normal file
8
e2e/tests/features/auth.feature
Normal file
@@ -0,0 +1,8 @@
|
||||
@auth @smoke
|
||||
Feature: Authentication
|
||||
Users sign in with email + password; the session is cookie-based.
|
||||
|
||||
Scenario: a registered user signs in through the login form
|
||||
Given a test account exists
|
||||
When "the user" signs in through the login form
|
||||
Then they land on the app
|
||||
10
e2e/tests/features/pages.feature
Normal file
10
e2e/tests/features/pages.feature
Normal file
@@ -0,0 +1,10 @@
|
||||
@pages @smoke
|
||||
Feature: Every page loads
|
||||
Signed in, every static route must render without an HTTP error, a failed or
|
||||
5xx app API call, an uncaught exception, or a console error. This is the
|
||||
baseline reliability gate — extend it with per-domain behaviour features as
|
||||
the app grows (create/edit/list flows, permissions, etc.).
|
||||
|
||||
Scenario: all pages load cleanly when signed in
|
||||
Given "the user" is signed in
|
||||
Then every page loads without errors
|
||||
28
e2e/tests/support/register.ts
Normal file
28
e2e/tests/support/register.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
Given,
|
||||
When,
|
||||
Then,
|
||||
Before,
|
||||
After,
|
||||
defineParameterType,
|
||||
setDefaultTimeout,
|
||||
} from '@cucumber/cucumber'
|
||||
import { registerBrowserSteps, registerBrowserHooks } from '@pikku/cucumber/browser'
|
||||
|
||||
// The shared browser vocabulary ({actor} grammar, form/click/see steps, the
|
||||
// "every page loads without errors" sweep) and the browser lifecycle hooks.
|
||||
// Per-domain business steps belong in ../steps/*.steps.ts.
|
||||
registerBrowserSteps({ Given, When, Then, defineParameterType })
|
||||
registerBrowserHooks({ Before, After, setDefaultTimeout })
|
||||
|
||||
// The page-load sweep visits every route in one step, so give scenarios plenty
|
||||
// of room. Per-action waits are still bounded by the package's config.timeout.
|
||||
setDefaultTimeout(Number(process.env.E2E_SCENARIO_TIMEOUT ?? 300_000))
|
||||
|
||||
// Don't start a scenario while the dev server is mid-restart (it 5xx's /api
|
||||
// for a few seconds on boot / file change) — that race reads as a phantom
|
||||
// failure. Server orchestration itself stays outside these tests.
|
||||
Before(async function (this: import('./world.js').AppWorld) {
|
||||
const actor = await this.actor(undefined)
|
||||
await actor.waitForServerReady()
|
||||
})
|
||||
15
e2e/tests/support/world.ts
Normal file
15
e2e/tests/support/world.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { setWorldConstructor } from '@cucumber/cucumber'
|
||||
import { BrowserWorld } from '@pikku/cucumber/browser'
|
||||
|
||||
/**
|
||||
* AppWorld — the app's cucumber world, built on @pikku/cucumber/browser.
|
||||
*
|
||||
* The package owns the browser lifecycle, the actor grammar ("the user",
|
||||
* "the admin", they) and the generic step vocabulary. Extend this class for
|
||||
* app-specific needs: override createClients() to expose the generated
|
||||
* PikkuRPC/PikkuFetch on actors, override resetAppData() to call the app's
|
||||
* reset RPC, or add helpers for per-domain *.steps.ts files.
|
||||
*/
|
||||
export class AppWorld extends BrowserWorld {}
|
||||
|
||||
setWorldConstructor(AppWorld)
|
||||
14
e2e/tsconfig.json
Normal file
14
e2e/tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["tests/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user