feat: integrate Playwright for E2E testing and enhance testing framework

- Added Playwright for end-to-end testing in the `apps/web` module, including a new `login.spec.ts` for testing login functionality.
- Updated package.json to include Playwright dependencies and new test commands for E2E testing.
- Enhanced .gitignore to exclude Playwright test results and reports.
- Modified existing documentation to reflect the integration of Playwright and updated testing guidelines.
- Refactored test commands to streamline the testing process, including a dedicated command for E2E tests.

These changes improve the testing framework by providing robust E2E testing capabilities, enhancing the reliability and quality of the application.
This commit is contained in:
shancheas
2026-08-31 11:52:27 +07:00
parent 166e0d40ac
commit 67e1e0a74f
23 changed files with 318 additions and 108 deletions
+8 -7
View File
@@ -3,7 +3,7 @@ name: archify
description: Create polished, validated architecture, workflow, sequence, data-flow, and lifecycle/state diagrams as explorable standalone HTML with inline SVG, dark/light themes, optional trace motion, and PNG/JPEG/WebP/SVG/WebM export. Accept plain-language requirements or pasted Mermaid flowchart, sequenceDiagram, and stateDiagram input; inspect repository evidence when the diagram must reflect real code. Use when the user asks to visualize system architecture, infrastructure, cloud/security/network topology, technical workflows, API call sequences, request lifecycles, data pipelines, ETL/ELT, data lineage, state machines, or to convert/beautify Mermaid.
license: MIT
metadata:
version: "2.16"
version: '2.16'
author: tt-a1i
based_on: Cocoon-AI/architecture-diagram-generator (MIT, v1.0)
---
@@ -26,6 +26,7 @@ Use this bounded path for ordinary generation. Do not read the optional Viewer R
```
A receipt with only 4 artifact checks is basic validation, never showcase acceptance. A showcase pass must report all 9 artifact checks with 0 composition errors and 0 warnings. If the candidate omits or misspells the exact `meta.quality_profile` field, fix it before geometry. For a workflow v2 geometry diagnosis, run `node bin/archify.mjs validate workflow <candidate.json> --layout-json` and use the stable compiler receipt; solver internals are not authoring controls. A passing final validation freezes the candidate: never edit it afterward.
5. For a delivered HTML, `deliver` is the final acceptance command:
```bash
@@ -54,13 +55,13 @@ Lifecycle note: phase columns `0..4` occupy the main rail; event/terminal column
## Type router
| Type | Use for |
|---|---|
| Type | Use for |
| -------------- | --------------------------------------------------------------- |
| `architecture` | Components, services, cloud/security boundaries, infrastructure |
| `workflow` | Processes, approval gates, tool calls, runbooks, CI/CD |
| `sequence` | API call chains, request lifecycles, async traces, returns |
| `dataflow` | Pipelines, ETL/ELT, lineage, governance, consumers |
| `lifecycle` | State/status transitions, retries, waiting and terminal states |
| `workflow` | Processes, approval gates, tool calls, runbooks, CI/CD |
| `sequence` | API call chains, request lifecycles, async traces, returns |
| `dataflow` | Pipelines, ETL/ELT, lineage, governance, consumers |
| `lifecycle` | State/status transitions, retries, waiting and terminal states |
When ambiguous, run `node bin/archify.mjs guide "<scenario>" --json`. Scenario proof examples are structural references, not facts to copy.
@@ -25,7 +25,7 @@ Reference this skill when working on this project. It contains:
- **Apps**: React 19 + Vite (`apps/web`, `apps/showcase`, `apps/landing`) + Electron (`apps/desktop`) + VitePress (`apps/docs-dev`)
- **Packages**: `@repo/ui` (Mantine), `@repo/core-api`, `@repo/core-storage`, `@repo/core-i18n`, `@repo/core-events`, `@repo/utils`, `@repo/brand`, `packages/configs`
- **Testing**: Vitest; Testing Library in `packages/ui` and `packages/core-events`
- **Testing**: Vitest; Playwright E2E in `apps/web/e2e/`; Testing Library in `packages/ui` and `packages/core-events`
- **Package manager**: pnpm 8.15.6 + Turbo
**Where to work:** product in `apps/web`; copy UI/API usage from `apps/showcase`; concepts from `apps/docs-dev`. Do not invent a parallel UI kit.
@@ -128,6 +128,7 @@ export const createFullPageSchema = (t: (key: string) => string) =>
```bash
pnpm test
pnpm --filter web test
pnpm test:e2e:web
pnpm --filter @repo/ui test
pnpm typecheck:web
pnpm check:all
@@ -135,7 +136,7 @@ pnpm check:all
- Unit tests colocated as `*.test.ts(x)`
- Component tests with Testing Library in packages that already have it
- App journeys: browser-verify login and FULL_PAGE index / form / detail
- App journeys: Playwright in `apps/web/e2e/` (login is the first sample)
- Minimum 80% coverage; TDD (red → green → refactor)
---
@@ -149,6 +150,7 @@ pnpm dev:docs-dev
pnpm lint
pnpm typecheck:web
pnpm test
pnpm test:e2e:web
pnpm check:all
```
+5 -4
View File
@@ -1,6 +1,6 @@
---
name: tdd-workflow
description: Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage using Vitest, Testing Library, and browser journeys.
description: Use this skill when writing new features, fixing bugs, or refactoring code. Enforces test-driven development with 80%+ coverage using Vitest, Testing Library, and Playwright E2E.
---
# Test-Driven Development Workflow
@@ -24,7 +24,7 @@ TDD for this frontend monorepo (Vitest, not NestJS/Supertest).
- **Unit** — validators, transformers, utils, stores (`*.test.ts`)
- **Component** — Testing Library in `packages/ui` / `packages/core-events`
- **Journeys** — login and FULL_PAGE index/form/detail; mock HTTP services; browser-verify layout
- **Journeys** — Playwright in `apps/web/e2e/` (login sample first); mock HTTP with `page.route`
## Workflow
@@ -77,7 +77,7 @@ vi.mock('@repo/core-api/http-client', () => ({
## File organization
Colocate `*.test.ts(x)` next to source. No `test/*.e2e-spec.ts` NestJS tree.
Colocate unit tests as `*.test.ts(x)` next to source. Playwright E2E lives in `apps/web/e2e/` (see `login.spec.ts`). Do not use a NestJS `test/*.e2e-spec.ts` tree.
## Commands
@@ -85,6 +85,7 @@ Colocate `*.test.ts(x)` next to source. No `test/*.e2e-spec.ts` NestJS tree.
pnpm test
pnpm --filter @repo/ui test
pnpm --filter web test -- --watch
pnpm test:e2e:web
pnpm lint
```
@@ -99,4 +100,4 @@ pnpm lint
- 80%+ coverage
- All tests green
- Critical FULL_PAGE flows covered or browser-verified
- Critical FULL_PAGE flows covered by Playwright or browser-verified
+20 -35
View File
@@ -1,43 +1,46 @@
---
name: e2e-runner
description: Frontend journey specialist using Vitest, Testing Library, and browser verification for apps/web module flows. Use PROACTIVELY for critical UI journeys (login, index, form, detail).
description: Frontend journey specialist using Playwright for apps/web E2E, plus Vitest and Testing Library for package journeys. Use PROACTIVELY for critical UI journeys (login, index, form, detail).
tools: Read, Write, Edit, Bash, Grep, Glob
model: opus
---
# E2E / Journey Runner
You are a frontend journey specialist for this pnpm + Turborepo React monorepo. There is no NestJS, Supertest, or Playwright suite. Cover critical user journeys with Vitest (+ Testing Library where the package already uses it) and browser verification for `apps/web`.
You are a frontend journey specialist for this pnpm + Turborepo React monorepo. Cover critical user journeys with Playwright in `apps/web/e2e/` and with Vitest (+ Testing Library where the package already uses it) for packages.
## Core Responsibilities
1. **Package / component journeys** — Vitest + Testing Library in `packages/ui` and `packages/core-events`
2. **App journeys** — browser-verify `apps/web` flows (login, FULL_PAGE index / form / detail)
3. **Isolation** — mock `@repo/core-api` HTTP services; never hit a real backend unless the user asks
1. **App E2E** — Playwright specs in `apps/web/e2e/` (first sample: `e2e/login.spec.ts`)
2. **Package / component journeys** — Vitest + Testing Library in `packages/ui` and `packages/core-events`
3. **Isolation** — mock the backend with Playwright `page.route` (see `e2e/fixtures/mock-api.ts`); never hit a real API unless the user asks
4. **Flaky management** — no arbitrary sleeps; wait for UI or network conditions
5. **Reporting** — Vitest output and a short pass/fail summary
5. **Reporting**Playwright/Vitest output and a short pass/fail summary
## Commands
```bash
pnpm test:e2e:web
pnpm --filter web test:e2e
pnpm --filter web test:e2e:ui
pnpm test
pnpm --filter @repo/ui test
pnpm --filter @repo/core-events test
pnpm --filter web test
pnpm check:all
```
First-time browsers: `pnpm --filter web exec playwright install chromium`
## What to test
### Critical `apps/web` journeys
### Critical `apps/web` journeys (Playwright)
1. Login (`src/apps/auth/login`)
1. Login (`e2e/login.spec.ts`) — copy this sample for new journeys
2. FULL_PAGE index — table + filters
3. FULL_PAGE form — create / edit / duplicate
4. FULL_PAGE detail
5. Auth session teardown (`terminateAuthSession`)
Canonical sample: `apps/web/src/apps/main/modules/example/full-page/`. Copy that pattern; do not invent a third page style.
Canonical UI sample: `apps/web/src/apps/main/modules/example/full-page/`. Copy that page pattern; do not invent a third page style.
### Package component tests (Testing Library)
@@ -52,36 +55,18 @@ it('renders the field label', () => {
});
```
### Mock remote data services (not a database)
### Mock the HTTP backend (Playwright)
```ts
vi.mock('../../domain/factories', () => ({
fullPageDataService: {
list: vi.fn(),
get: vi.fn(),
create: vi.fn(),
update: vi.fn(),
delete: vi.fn(),
},
}));
```
Reuse `mockBackend(page)` from `apps/web/e2e/fixtures/mock-api.ts`. Extend that helper for new endpoints instead of calling a live server.
## Browser verification (`apps/web`)
When the change is routing, layout, or a flow Vitest cannot see:
1. Use `pnpm dev:web`
2. Drive login → index → form → detail the way a user would
3. Check empty, error, and success states
4. Confirm related routes that share module state stay consistent
Do not add Playwright unless the user explicitly asks.
Unit tests still mock `@repo/core-api` services — not a database.
## Flaky-test rules
- Prefer `getByRole` / `getByLabelText` over CSS classes
- Prefer `getByRole` / `getByLabel` over CSS classes
- Wait for elements or responses, never fixed sleeps
- Each test sets up its own data
- Keep Playwright specs in `apps/web/e2e/`; do not colocate them next to source (Vitest would pick them up)
## Report format
@@ -89,7 +74,7 @@ Do not add Playwright unless the user explicitly asks.
# Journey Report
**Status:** PASSING / FAILING
**Command:** pnpm test
**Command:** pnpm test:e2e:web
## Summary
+1 -1
View File
@@ -56,7 +56,7 @@ export const createFullPageSchema = (t: (key: string) => string) =>
1. **Unit** — validators, transformers, utils, stores (`*.test.ts`)
2. **Component** — Testing Library in packages that already have it
3. **Journeys**login and FULL_PAGE index / form / detail; mock `fullPageDataService`. Browser-verify layout/routing. See **e2e-runner**.
3. **Journeys**Playwright in `apps/web/e2e/` (copy `login.spec.ts`). Mock HTTP with `mockBackend`. See **e2e-runner**.
## Mocking
+10 -12
View File
@@ -1,18 +1,17 @@
---
description: Generate and run frontend journey tests with Vitest/Testing Library, plus browser verification for apps/web. Covers login and FULL_PAGE index/form/detail.
description: Generate and run frontend journey tests with Playwright for apps/web, plus Vitest/Testing Library for packages. Covers login and FULL_PAGE index/form/detail.
---
# E2E / Journey Command
This command invokes the **e2e-runner** agent to cover critical UI journeys. There is no NestJS or Playwright suite.
This command invokes the **e2e-runner** agent to cover critical UI journeys.
## What This Command Does
1. **Identify journeys** — login, FULL_PAGE index / form / detail
2. **Write or update Vitest tests** — mock `@repo/core-api` services
3. **Run** `pnpm test` (or `pnpm --filter web test`)
4. **Browser-verify** `apps/web` when the change is routing or layout
5. **Report** pass/fail
2. **Write or update Playwright specs** in `apps/web/e2e/` (copy `e2e/login.spec.ts`)
3. **Run** `pnpm test:e2e:web` (or `pnpm --filter web test:e2e`)
4. **Report** pass/fail
## When to Use
@@ -27,11 +26,10 @@ Use `/e2e` when:
The e2e-runner agent will:
1. Copy patterns from `apps/web/src/apps/main/modules/example/full-page/`
2. Mock `fullPageDataService` (or the module's factory) — not a database
3. Run `pnpm test`
4. Drive the browser for layout/routing if needed (`pnpm dev:web`)
5. Summarize failures
1. Copy the login sample in `apps/web/e2e/login.spec.ts` and UI patterns from `example/full-page`
2. Mock the backend with `mockBackend` (`e2e/fixtures/mock-api.ts`) — not a live API
3. Run `pnpm test:e2e:web`
4. Summarize failures
## Example
@@ -39,7 +37,7 @@ The e2e-runner agent will:
User: /e2e Test the example full-page index and detail flow
```
Canonical sample: `example/full-page`. Do not invent a third page style. Do not add Playwright unless asked.
Canonical UI sample: `example/full-page`. Canonical E2E sample: `apps/web/e2e/login.spec.ts`.
## Related
+1 -1
View File
@@ -22,7 +22,7 @@ alwaysApply: true
| code-reviewer | Code review | After writing code |
| security-reviewer | Security analysis | Before commits |
| build-error-resolver | Fix build errors | When build fails |
| e2e-runner | Frontend journeys | Vitest + RTL, browser flows |
| e2e-runner | Frontend journeys | Playwright in `apps/web/e2e/` |
| refactor-cleaner | Dead code cleanup | Code maintenance |
| doc-updater | Documentation | Updating docs |
+1 -1
View File
@@ -31,7 +31,7 @@ pnpm + Turborepo monorepo. Work from the repository root. Package manager: `pnpm
- Before inventing UI or package usage, match `apps/showcase` demos and `apps/docs-dev` docs.
- Prefer `@repo/ui`, `@repo/core-*`, `@repo/utils` over app-local duplicates or raw Mantine/axios.
- Env files live **inside the app** (`apps/web/.env*`), never at monorepo root. Read env via `src/core/environment` (`ENV`), not `import.meta.env` in components.
- Run scripts from root: `pnpm dev:web`, `pnpm dev:showcase`, `pnpm lint`, `pnpm typecheck:web`, `pnpm test`, `pnpm check:all`.
- Run scripts from root: `pnpm dev:web`, `pnpm dev:showcase`, `pnpm lint`, `pnpm typecheck:web`, `pnpm test`, `pnpm test:e2e:web`, `pnpm check:all`.
## Import map (preferred)
+5 -5
View File
@@ -1,5 +1,5 @@
---
description: TDD workflow, 80% coverage minimum, Vitest unit tests, and browser journey verification
description: TDD workflow, 80% coverage minimum, Vitest unit tests, and Playwright E2E for apps/web
alwaysApply: true
---
@@ -11,9 +11,9 @@ Test types:
1. **Unit tests** — pure functions, transformers, validators, stores, utilities (`*.test.ts` / `*.test.tsx`, colocated or `__tests__/`)
2. **Component tests** — Testing Library in packages that already have it (`packages/ui`, `packages/core-events`)
3. **App journeys** — browser verification of `apps/web` flows (login, index / form / detail). There is no Playwright or NestJS E2E suite in this repo.
3. **App journeys** — Playwright E2E in `apps/web/e2e/` (login is the first sample). Mock the backend with `page.route`; do not hit a live API unless the user asks. There is no NestJS/Supertest suite in this frontend repo.
Runner: **Vitest**. Root command: `pnpm test`. Per-package: `pnpm --filter <pkg> test`. Also `pnpm typecheck` and `pnpm check:all`.
Runner: **Vitest** for unit tests (`pnpm test`). Playwright for web E2E (`pnpm test:e2e:web` or `pnpm --filter web test:e2e`). Also `pnpm typecheck` and `pnpm check:all` (unit/lint/typecheck only).
## Test-Driven Development
@@ -30,11 +30,11 @@ MANDATORY workflow:
1. Use **tdd-guide** agent
2. Check test isolation
3. Verify mocks are correct (`@repo/core-api` services, HTTP client — not a database)
3. Verify mocks are correct (`@repo/core-api` / HTTP client for unit tests; `mockBackend` in `apps/web/e2e/fixtures/mock-api.ts` for Playwright)
4. Fix implementation, not tests (unless tests are wrong)
## Agent Support
- **tdd-guide** — Use PROACTIVELY for new features, enforces write-tests-first
- **e2e-runner** — Frontend journeys: Vitest + Testing Library, plus browser verification for `apps/web`
- **e2e-runner** — Playwright E2E in `apps/web/e2e/`, plus Vitest + Testing Library for package journeys
- Skill: `.agents/skills/tdd-workflow/`
+6
View File
@@ -52,3 +52,9 @@ legacy/
# VitePress
**/.vitepress/cache/
**/.vitepress/dist/
# Playwright
**/test-results/
**/playwright-report/
**/blob-report/
**/playwright/.cache/
+28 -28
View File
@@ -10,10 +10,10 @@ You need permission to view each menu. Status buttons (Process, Complete, Submit
Set up the records that every sales document needs:
| You need | Where to create it |
| --- | --- |
| Division, branch, customer, product | **Settings → Data** |
| Salesperson | **Sales → Data → Employees** |
| You need | Where to create it |
| ----------------------------------- | ---------------------------- |
| Division, branch, customer, product | **Settings → Data** |
| Salesperson | **Sales → Data → Employees** |
If a menu is missing, ask an administrator to give your privilege **View** (and **Update** for status buttons) on that area.
@@ -70,7 +70,7 @@ A request is optional. You can go straight to a sales order.
1. Click **Create**. Fill in the same kind of details as a request. On a new order you can pick a sales request under **Source**; that copies the request into the form. Save. A new order starts as **Draft**.
2. On a **Draft** order, click **Process**.
- You will see: *Processing creates a sales invoice and, unless skipped, a packing slip.*
- You will see: _Processing creates a sales invoice and, unless skipped, a packing slip._
- **Generate packing slip** is checked by default. Uncheck it if you do not want a packing slip.
3. On **Draft** or **Processed**, you can **Cancel**. There is no **Complete** button on orders. If the order later shows **Completed**, that was not something you clicked on this screen.
4. On the order detail page you can see linked packing slips and invoices, and click **Create Sales Invoice**.
@@ -134,32 +134,32 @@ Approving a payment does not change the invoice status from this screen. Refresh
Both use Draft → Pending → Approved or Rejected.
| Current status | Buttons | Change status |
| --- | --- | --- |
| Draft | Submit | Rejected |
| Pending | Approve, Reject. Payments also have Rollback (back to Draft) | Draft, Approved, Rejected |
| Approved | — | — |
| Rejected | — | Draft |
| Current status | Buttons | Change status |
| -------------- | ------------------------------------------------------------ | ------------------------- |
| Draft | Submit | Rejected |
| Pending | Approve, Reject. Payments also have Rollback (back to Draft) | Draft, Approved, Rejected |
| Approved | — | — |
| Rejected | — | Draft |
### Sales order
| Current status | Buttons | Change status |
| --- | --- | --- |
| Draft | Process, Cancel | Processed, Cancelled |
| Processed | Cancel | Cancelled |
| Completed | — | — |
| Cancelled | — | — |
| Current status | Buttons | Change status |
| -------------- | --------------- | -------------------- |
| Draft | Process, Cancel | Processed, Cancelled |
| Processed | Cancel | Cancelled |
| Completed | — | — |
| Cancelled | — | — |
**Process** always opens the packing-slip checkbox dialog (not Change status).
### Packing slip
| Current status | Buttons | Change status |
| --- | --- | --- |
| Draft | Cancel | Processed, Cancelled |
| Processed | Complete, Cancel | Completed, Cancelled |
| Completed | — | — |
| Cancelled | — | — |
| Current status | Buttons | Change status |
| -------------- | ---------------- | -------------------- |
| Draft | Cancel | Processed, Cancelled |
| Processed | Complete, Cancel | Completed, Cancelled |
| Completed | — | — |
| Cancelled | — | — |
**Complete** always asks for delivered quantities.
@@ -167,11 +167,11 @@ Both use Draft → Pending → Approved or Rejected.
You may see Draft, Processed, Partial, Completed, or Cancelled.
| Current status | What you can do |
| --- | --- |
| Draft, Processed, or Partial | Cancel |
| Completed | No status buttons |
| Cancelled | No status buttons |
| Current status | What you can do |
| ---------------------------- | ----------------- |
| Draft, Processed, or Partial | Cancel |
| Completed | No status buttons |
| Cancelled | No status buttons |
---
+6 -6
View File
@@ -8,12 +8,12 @@ Use the sidebar to open **Sales** for day-to-day selling, **Logistics** for pack
## Where to find things
| Area | Menu | What you do there |
| --- | --- | --- |
| Sales | Sales → Activities | Requests, orders, invoices, payments, visit plans |
| Logistics | Logistics → Activities | Packing slips, delivery plans |
| Company data | Settings → Data | Divisions, branches, customers, products |
| Access | Settings → User | Users and privileges |
| Area | Menu | What you do there |
| ------------ | ---------------------- | ------------------------------------------------- |
| Sales | Sales → Activities | Requests, orders, invoices, payments, visit plans |
| Logistics | Logistics → Activities | Packing slips, delivery plans |
| Company data | Settings → Data | Divisions, branches, customers, products |
| Access | Settings → User | Users and privileges |
If a menu item is missing, your privilege does not include **View** for that area.
+1
View File
@@ -48,6 +48,7 @@ This repository uses **[Turborepo](https://turbo.build/repo)** to orchestrate ta
| `pnpm build:docs-dev` | Build only the docs-dev application |
| `pnpm build:desktop` | Build the web app, then compile the Electron app |
| `pnpm test` | Run unit tests ([Vitest](https://vitest.dev/)) across all packages |
| `pnpm test:e2e:web` | Run Playwright E2E for `apps/web` (starts Vite on port 4173) |
| `pnpm lint` | Run [ESLint](https://eslint.org/) across the workspace |
| `pnpm format` | Format code using [Prettier](https://prettier.io/) |
+4
View File
@@ -0,0 +1,4 @@
export const E2E_LOGIN = {
username: 'administrator',
password: 'password123',
} as const;
+11
View File
@@ -0,0 +1,11 @@
export function createFakeJwt(expiresInSeconds = 3600): string {
const header = Buffer.from(JSON.stringify({ alg: 'none', typ: 'JWT' })).toString('base64url');
const payload = Buffer.from(
JSON.stringify({
sub: 'e2e-user',
exp: Math.floor(Date.now() / 1000) + expiresInSeconds,
}),
).toString('base64url');
return `${header}.${payload}.e2e`;
}
+91
View File
@@ -0,0 +1,91 @@
import type { Page, Route } from '@playwright/test';
import { E2E_LOGIN } from './credentials';
import { createFakeJwt } from './fake-jwt';
const APP_HOSTS = new Set(['127.0.0.1', 'localhost']);
const APP_PORT = Number(process.env.E2E_PORT ?? 4173);
const EMPTY_LIST = {
data: [],
meta: {
currentPage: 1,
itemsPerPage: 10,
totalItems: 0,
totalPages: 0,
itemCount: 0,
},
};
const E2E_USER = {
id: 'e2e-user-id',
username: E2E_LOGIN.username,
isSuperadmin: true,
privilege: null,
permissions: {},
};
export type LoginApiOutcome = 'success' | 'unauthorized';
export async function mockBackend(page: Page, login: LoginApiOutcome = 'success'): Promise<void> {
await page.route('**/*', async (route) => {
const requestUrl = new URL(route.request().url());
if (isAppRequest(requestUrl)) {
await route.continue();
return;
}
await fulfillBackend(route, login);
});
}
function isAppRequest(requestUrl: URL): boolean {
const port = Number(requestUrl.port || (requestUrl.protocol === 'https:' ? 443 : 80));
return APP_HOSTS.has(requestUrl.hostname) && port === APP_PORT;
}
async function fulfillBackend(route: Route, login: LoginApiOutcome): Promise<void> {
const request = route.request();
const pathname = new URL(request.url()).pathname.replace(/\/$/, '');
if (pathname.endsWith('/auth/login') && request.method() === 'POST') {
if (login === 'unauthorized') {
await route.fulfill({
status: 401,
contentType: 'application/json',
body: JSON.stringify({ statusCode: 401, message: 'Unauthorized' }),
});
return;
}
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
accessToken: createFakeJwt(),
refreshToken: createFakeJwt(86_400),
}),
});
return;
}
if (pathname.endsWith('/auth/me') && request.method() === 'GET') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(E2E_USER),
});
return;
}
const resourceType = request.resourceType();
if (resourceType === 'xhr' || resourceType === 'fetch') {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(EMPTY_LIST),
});
return;
}
await route.abort();
}
+40
View File
@@ -0,0 +1,40 @@
import { expect, test } from '@playwright/test';
import { E2E_LOGIN } from './fixtures/credentials';
import { mockBackend } from './fixtures/mock-api';
test.describe('login', () => {
test('renders the login form', async ({ page }) => {
await mockBackend(page);
await page.goto('/auth/login');
await expect(page.getByRole('heading', { name: 'Login to your account!' })).toBeVisible();
await expect(page.getByLabel('Username')).toBeVisible();
await expect(page.getByLabel('Password')).toBeVisible();
await expect(page.getByRole('button', { name: 'Login' })).toBeVisible();
});
test('signs in with valid credentials and lands in the app', async ({ page }) => {
await mockBackend(page, 'success');
await page.goto('/auth/login');
await page.getByLabel('Username').fill(E2E_LOGIN.username);
await page.getByLabel('Password').fill(E2E_LOGIN.password);
await page.getByRole('button', { name: 'Login' }).click();
await expect(page).toHaveURL(/\/app(\/|$)/);
await expect(page.getByRole('heading', { name: 'Full Page' })).toBeVisible();
});
test('shows an error and stays on login when credentials are rejected', async ({ page }) => {
await mockBackend(page, 'unauthorized');
await page.goto('/auth/login');
await page.getByLabel('Username').fill(E2E_LOGIN.username);
await page.getByLabel('Password').fill(E2E_LOGIN.password);
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Invalid username or password')).toBeVisible();
await expect(page).toHaveURL(/\/auth\/login/);
await expect(page.getByRole('heading', { name: 'Login to your account!' })).toBeVisible();
});
});
+5 -1
View File
@@ -8,9 +8,12 @@
"dev": "pnpm run copy:brand && vite --clearScreen false",
"build": "pnpm run copy:brand && tsc && vite build",
"preview": "vite preview",
"lint": "eslint \"src/**/*.ts\"",
"lint": "eslint \"src/**/*.ts\" \"e2e/**/*.ts\" \"playwright.config.ts\"",
"test": "vitest run",
"test:watch": "vitest --watch",
"test:e2e": "playwright test",
"test:e2e:ui": "playwright test --ui",
"test:e2e:install": "playwright install chromium",
"typecheck": "tsc --noEmit"
},
"dependencies": {
@@ -37,6 +40,7 @@
"zustand": "^5.0.14"
},
"devDependencies": {
"@playwright/test": "^1.55.0",
"@repo/eslint-config": "workspace:*",
"@repo/typescript-config": "workspace:*",
"@types/react": "^19.2.7",
+25
View File
@@ -0,0 +1,25 @@
import { defineConfig, devices } from '@playwright/test';
const E2E_PORT = Number(process.env.E2E_PORT ?? 4173);
const E2E_ORIGIN = `http://127.0.0.1:${E2E_PORT}`;
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: Boolean(process.env.CI),
retries: process.env.CI ? 2 : 0,
reporter: 'list',
timeout: 60_000,
use: {
baseURL: E2E_ORIGIN,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
},
webServer: {
command: `pnpm run copy:brand && vite --clearScreen false --host 127.0.0.1 --port ${E2E_PORT} --strictPort`,
url: E2E_ORIGIN,
reuseExistingServer: !process.env.CI,
timeout: 120_000,
},
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
});
+1
View File
@@ -21,5 +21,6 @@ export default defineConfig({
test: {
environment: 'node',
globals: false,
exclude: ['**/node_modules/**', '**/dist/**', '**/e2e/**'],
},
});
+1
View File
@@ -22,6 +22,7 @@
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"",
"test": "turbo run test",
"test:e2e:web": "pnpm --filter web test:e2e",
"typecheck": "turbo run typecheck",
"typecheck:web": "turbo run typecheck --filter=web",
"typecheck:landing": "turbo run typecheck --filter=landing",
+39 -4
View File
@@ -239,7 +239,7 @@ importers:
version: 5.4.17(@types/node@22.19.3)
vitest:
specifier: ^4.0.17
version: 4.0.17(@opentelemetry/api@1.9.1)
version: 4.0.17(jsdom@26.1.0)
apps/web:
dependencies:
@@ -307,6 +307,9 @@ importers:
specifier: ^5.0.14
version: 5.0.14(@types/react@19.2.7)(react@19.2.3)
devDependencies:
'@playwright/test':
specifier: ^1.55.0
version: 1.62.1
'@repo/eslint-config':
specifier: workspace:*
version: link:../../packages/configs/eslint
@@ -333,7 +336,7 @@ importers:
version: 5.4.17(@types/node@22.19.3)
vitest:
specifier: ^4.0.17
version: 4.0.17(@opentelemetry/api@1.9.1)
version: 4.0.17(jsdom@26.1.0)
packages/brand:
devDependencies:
@@ -551,7 +554,7 @@ importers:
version: 5.5.4
vitest:
specifier: ^4.0.17
version: 4.0.17(@opentelemetry/api@1.9.1)
version: 4.0.17(jsdom@26.1.0)
packages/ui:
dependencies:
@@ -733,7 +736,7 @@ importers:
version: 5.5.4
vitest:
specifier: ^4.0.17
version: 4.0.17(@opentelemetry/api@1.9.1)
version: 4.0.17(jsdom@26.1.0)
packages:
@@ -2607,6 +2610,14 @@ packages:
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
dev: false
/@playwright/test@1.62.1:
resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==}
engines: {node: '>=20'}
hasBin: true
dependencies:
playwright: 1.62.1
dev: true
/@protobufjs/aspromise@1.1.2:
resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==}
dev: false
@@ -7682,6 +7693,14 @@ packages:
/fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
/fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@@ -10098,6 +10117,22 @@ packages:
engines: {node: '>=12'}
dev: false
/playwright-core@1.62.1:
resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
engines: {node: '>=20'}
hasBin: true
dev: true
/playwright@1.62.1:
resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
engines: {node: '>=20'}
hasBin: true
dependencies:
playwright-core: 1.62.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/plist@3.1.0:
resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==}
engines: {node: '>=10.4.0'}
+4
View File
@@ -52,6 +52,10 @@
"test:watch": {
"cache": false,
"persistent": true
},
"test:e2e": {
"cache": false,
"dependsOn": []
}
}
}