Add Playwright E2E testing infrastructure
- Add playwright-web-flake to flake.nix for NixOS browser support - Pin @playwright/test@1.56.1 to match nixpkgs version - Create playwright.config.ts with Chromium-only, auto-start dev server - Add e2e/smoke.spec.ts with initial smoke tests - Add .mcp.json for Claude browser control via MCP - Update .gitignore for playwright artifacts - Remove E2E test skip from spec.md Known Limitations - Update specs/testing.md to require three-tier testing approach Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -13,6 +13,10 @@
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# playwright
|
||||
/playwright-report/
|
||||
/test-results/
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
8
.mcp.json
Normal file
8
.mcp.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"playwright": {
|
||||
"command": "npx",
|
||||
"args": ["@playwright/mcp@latest"]
|
||||
}
|
||||
}
|
||||
}
|
||||
36
e2e/smoke.spec.ts
Normal file
36
e2e/smoke.spec.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
// ABOUTME: Smoke tests to verify basic application functionality.
|
||||
// ABOUTME: Tests that the app loads and critical pages are accessible.
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("smoke tests", () => {
|
||||
test("app loads with correct title", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
// Verify the app loads by checking the page title
|
||||
await expect(page).toHaveTitle("PhaseFlow");
|
||||
});
|
||||
|
||||
test("login page is accessible", async ({ page }) => {
|
||||
await page.goto("/login");
|
||||
|
||||
// Verify login page loads
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
|
||||
test("unauthenticated root redirects or shows login option", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/");
|
||||
|
||||
// The app should either redirect to login or show a login link
|
||||
// Check for either condition
|
||||
const url = page.url();
|
||||
const hasLoginInUrl = url.includes("/login");
|
||||
const loginLink = page.getByRole("link", { name: /login|sign in/i });
|
||||
|
||||
// At least one should be true: either we're on login page or there's a login link
|
||||
if (!hasLoginInUrl) {
|
||||
await expect(loginLink).toBeVisible();
|
||||
}
|
||||
});
|
||||
});
|
||||
68
flake.lock
generated
68
flake.lock
generated
@@ -1,5 +1,23 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1726560853,
|
||||
"narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1767892417,
|
||||
@@ -16,9 +34,57 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 0,
|
||||
"narHash": "sha256-u+rxA79a0lyhG+u+oPBRtTDtzz8kvkc9a6SWSt9ekVc=",
|
||||
"path": "/nix/store/0283cbhm47kd3lr9zmc5fvdrx9qkav8s-source",
|
||||
"type": "path"
|
||||
},
|
||||
"original": {
|
||||
"id": "nixpkgs",
|
||||
"type": "indirect"
|
||||
}
|
||||
},
|
||||
"playwright-web-flake": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1764622772,
|
||||
"narHash": "sha256-WCvvlB9sH6u8MQUkFnlxx7jDh7kIebTDK/JHi6pPqSA=",
|
||||
"owner": "pietdevries94",
|
||||
"repo": "playwright-web-flake",
|
||||
"rev": "88e0e6c69b9086619b0c4d8713b2bfaf81a21c40",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "pietdevries94",
|
||||
"ref": "1.56.1",
|
||||
"repo": "playwright-web-flake",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
"nixpkgs": "nixpkgs",
|
||||
"playwright-web-flake": "playwright-web-flake"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
30
flake.nix
30
flake.nix
@@ -2,11 +2,13 @@
|
||||
# ABOUTME: Provides Node.js 24, pnpm, turbo, lefthook, and Docker image output.
|
||||
{
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
inputs.playwright-web-flake.url = "github:pietdevries94/playwright-web-flake/1.56.1";
|
||||
|
||||
outputs = { nixpkgs, ... }:
|
||||
outputs = { nixpkgs, playwright-web-flake, ... }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
playwright-driver = playwright-web-flake.packages.${system}.playwright-driver;
|
||||
|
||||
# Custom Python package: garth (not in nixpkgs)
|
||||
garth = pkgs.python3Packages.buildPythonPackage {
|
||||
@@ -48,32 +50,40 @@
|
||||
devShells.${system} = {
|
||||
# Default development shell with all tools
|
||||
default = pkgs.mkShell {
|
||||
packages = commonPackages ++ (with pkgs; [
|
||||
turbo
|
||||
lefthook
|
||||
]);
|
||||
packages = commonPackages ++ [
|
||||
pkgs.turbo
|
||||
pkgs.lefthook
|
||||
playwright-driver
|
||||
];
|
||||
|
||||
# For native modules (sharp, better-sqlite3, etc.)
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc ];
|
||||
|
||||
# Playwright browser configuration for NixOS (from playwright-web-flake)
|
||||
PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers}";
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
|
||||
};
|
||||
|
||||
# Ralph sandbox shell with minimal permissions
|
||||
# Used for autonomous Ralph loop execution
|
||||
ralph = pkgs.mkShell {
|
||||
packages = commonPackages ++ (with pkgs; [
|
||||
# Claude CLI (assumes installed globally or via npm)
|
||||
# Add any other tools Ralph needs here
|
||||
]);
|
||||
packages = commonPackages ++ [
|
||||
playwright-driver
|
||||
];
|
||||
|
||||
# Restrictive environment for sandboxed execution
|
||||
shellHook = ''
|
||||
echo "🔒 Ralph Sandbox Environment"
|
||||
echo " Limited to: nodejs, pnpm, git"
|
||||
echo " Limited to: nodejs, pnpm, git, playwright"
|
||||
echo ""
|
||||
'';
|
||||
|
||||
# For native modules
|
||||
LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc ];
|
||||
|
||||
# Playwright browser configuration for NixOS (from playwright-web-flake)
|
||||
PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers}";
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
"lint:fix": "biome check --write .",
|
||||
"test": "vitest",
|
||||
"test:run": "vitest run",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"db:setup": "npx tsx scripts/setup-db.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -31,6 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "2.3.11",
|
||||
"@playwright/test": "1.56.1",
|
||||
"@tailwindcss/postcss": "^4",
|
||||
"@testing-library/dom": "^10.4.1",
|
||||
"@testing-library/jest-dom": "^6.9.1",
|
||||
|
||||
46
playwright.config.ts
Normal file
46
playwright.config.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
// ABOUTME: Playwright E2E test configuration for browser-based testing.
|
||||
// ABOUTME: Configures Chromium-only headless testing with automatic dev server startup.
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
// Test directory for E2E tests
|
||||
testDir: "./e2e",
|
||||
|
||||
// Run tests in parallel
|
||||
fullyParallel: true,
|
||||
|
||||
// Fail the build on CI if you accidentally left test.only in the source code
|
||||
forbidOnly: !!process.env.CI,
|
||||
|
||||
// Retry failed tests on CI only
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
|
||||
// Limit parallel workers on CI to avoid resource issues
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
|
||||
// Reporter configuration
|
||||
reporter: [["html", { open: "never" }], ["list"]],
|
||||
|
||||
// Shared settings for all projects
|
||||
use: {
|
||||
// Base URL for navigation actions like page.goto('/')
|
||||
baseURL: "http://localhost:3000",
|
||||
|
||||
// Collect trace on first retry for debugging
|
||||
trace: "on-first-retry",
|
||||
|
||||
// Take screenshot on failure
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
|
||||
// Configure projects - Chromium only per requirements
|
||||
projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
|
||||
|
||||
// Run dev server before starting tests
|
||||
webServer: {
|
||||
command: "pnpm dev",
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120 * 1000, // 2 minutes for Next.js to start
|
||||
},
|
||||
});
|
||||
43
pnpm-lock.yaml
generated
43
pnpm-lock.yaml
generated
@@ -25,7 +25,7 @@ importers:
|
||||
version: 0.562.0(react@19.2.3)
|
||||
next:
|
||||
specifier: 16.1.1
|
||||
version: 16.1.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
version: 16.1.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
node-cron:
|
||||
specifier: ^4.2.1
|
||||
version: 4.2.1
|
||||
@@ -57,6 +57,9 @@ importers:
|
||||
'@biomejs/biome':
|
||||
specifier: 2.3.11
|
||||
version: 2.3.11
|
||||
'@playwright/test':
|
||||
specifier: 1.56.1
|
||||
version: 1.56.1
|
||||
'@tailwindcss/postcss':
|
||||
specifier: ^4
|
||||
version: 4.1.18
|
||||
@@ -974,6 +977,11 @@ packages:
|
||||
'@pinojs/redact@0.4.0':
|
||||
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
resolution: {integrity: sha512-vSMYtL/zOcFpvJCW71Q/OEGQb7KYBPAdKh35WNSkaZA75JlAO8ED8UN6GUNTm3drWomcbcqRPFqQbLae8yBTdg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.53':
|
||||
resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==}
|
||||
|
||||
@@ -1553,6 +1561,11 @@ packages:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1778,6 +1791,16 @@ packages:
|
||||
resolution: {integrity: sha512-3qqVfpJtRQUCAOs4rTOEwLH6mwJJ/CSAlbis8fKOiMzTtXh0HN/VLsn3UWVTJ7U8DsWmxeNon2IpGb+wORXH4g==}
|
||||
hasBin: true
|
||||
|
||||
playwright-core@1.56.1:
|
||||
resolution: {integrity: sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.56.1:
|
||||
resolution: {integrity: sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
pocketbase@0.26.5:
|
||||
resolution: {integrity: sha512-SXcq+sRvVpNxfLxPB1C+8eRatL7ZY4o3EVl/0OdE3MeR9fhPyZt0nmmxLqYmkLvXCN9qp3lXWV/0EUYb3MmMXQ==}
|
||||
|
||||
@@ -2723,6 +2746,10 @@ snapshots:
|
||||
|
||||
'@pinojs/redact@0.4.0': {}
|
||||
|
||||
'@playwright/test@1.56.1':
|
||||
dependencies:
|
||||
playwright: 1.56.1
|
||||
|
||||
'@rolldown/pluginutils@1.0.0-beta.53': {}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.55.1':
|
||||
@@ -3213,6 +3240,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -3363,7 +3393,7 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
next@16.1.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
next@16.1.1(@babel/core@7.28.5)(@opentelemetry/api@1.9.0)(@playwright/test@1.56.1)(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
|
||||
dependencies:
|
||||
'@next/env': 16.1.1
|
||||
'@swc/helpers': 0.5.15
|
||||
@@ -3383,6 +3413,7 @@ snapshots:
|
||||
'@next/swc-win32-arm64-msvc': 16.1.1
|
||||
'@next/swc-win32-x64-msvc': 16.1.1
|
||||
'@opentelemetry/api': 1.9.0
|
||||
'@playwright/test': 1.56.1
|
||||
sharp: 0.34.5
|
||||
transitivePeerDependencies:
|
||||
- '@babel/core'
|
||||
@@ -3426,6 +3457,14 @@ snapshots:
|
||||
sonic-boom: 4.2.0
|
||||
thread-stream: 4.0.0
|
||||
|
||||
playwright-core@1.56.1: {}
|
||||
|
||||
playwright@1.56.1:
|
||||
dependencies:
|
||||
playwright-core: 1.56.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
pocketbase@0.26.5: {}
|
||||
|
||||
postcss@8.4.31:
|
||||
|
||||
2
spec.md
2
spec.md
@@ -835,7 +835,7 @@ The following are **out of scope** for MVP:
|
||||
| Hormonal birth control | May disrupt natural cycle phases |
|
||||
| API versioning | Single version; breaking changes via deprecation |
|
||||
| Formal API documentation | Endpoints documented in spec only |
|
||||
| E2E tests | Unit + integration tests only (authorized skip) |
|
||||
| Multi-user support | Single-user design only |
|
||||
|
||||
---
|
||||
|
||||
|
||||
130
specs/testing.md
130
specs/testing.md
@@ -6,7 +6,7 @@ When I make changes to the codebase, I want automated tests to catch regressions
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
PhaseFlow uses **unit and integration tests** with Vitest. End-to-end tests are not required for MVP (authorized skip).
|
||||
PhaseFlow uses a **three-tier testing approach**: unit tests, integration tests, and end-to-end tests.
|
||||
|
||||
### Test Types
|
||||
|
||||
@@ -14,6 +14,7 @@ PhaseFlow uses **unit and integration tests** with Vitest. End-to-end tests are
|
||||
|------|-------|-------|----------|
|
||||
| Unit | Pure functions, utilities | Vitest | Colocated `*.test.ts` |
|
||||
| Integration | API routes, PocketBase interactions | Vitest + supertest | Colocated `*.test.ts` |
|
||||
| E2E | Full user flows, browser interactions | Playwright | `e2e/*.spec.ts` |
|
||||
|
||||
## Framework
|
||||
|
||||
@@ -148,9 +149,96 @@ describe('GET /api/today', () => {
|
||||
});
|
||||
```
|
||||
|
||||
## End-to-End Tests
|
||||
|
||||
Test complete user flows through the browser using Playwright.
|
||||
|
||||
### Framework
|
||||
|
||||
**Playwright** - Cross-browser E2E testing with auto-waiting and tracing.
|
||||
|
||||
**Configuration (`playwright.config.ts`):**
|
||||
```typescript
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./e2e",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
reporter: [["html", { open: "never" }], ["list"]],
|
||||
use: {
|
||||
baseURL: "http://localhost:3000",
|
||||
trace: "on-first-retry",
|
||||
screenshot: "only-on-failure",
|
||||
},
|
||||
projects: [
|
||||
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
|
||||
],
|
||||
webServer: {
|
||||
command: "pnpm dev",
|
||||
url: "http://localhost:3000",
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### Priority Targets
|
||||
|
||||
| Flow | Tests |
|
||||
|------|-------|
|
||||
| Authentication | Login page loads, login redirects, logout works |
|
||||
| Dashboard | Shows decision, displays cycle info, override toggles work |
|
||||
| Settings | Garmin token paste, preferences save |
|
||||
| Calendar | ICS feed accessible, calendar view renders |
|
||||
| Period logging | "Period Started" updates cycle |
|
||||
|
||||
### Example Test
|
||||
|
||||
```typescript
|
||||
// e2e/dashboard.spec.ts
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test.describe("dashboard", () => {
|
||||
test("shows training decision for authenticated user", async ({ page }) => {
|
||||
// Login first (or use auth state)
|
||||
await page.goto("/");
|
||||
|
||||
// Verify decision card is visible
|
||||
await expect(page.getByTestId("decision-card")).toBeVisible();
|
||||
|
||||
// Verify cycle day is displayed
|
||||
await expect(page.getByText(/Day \d+ of \d+/)).toBeVisible();
|
||||
});
|
||||
|
||||
test("override toggles update decision", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
|
||||
// Enable flare mode
|
||||
await page.getByLabel("Flare Mode").click();
|
||||
|
||||
// Decision should change to gentle
|
||||
await expect(page.getByText(/GENTLE/)).toBeVisible();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### NixOS Setup
|
||||
|
||||
E2E tests require browser binaries. On NixOS, use `playwright-web-flake`:
|
||||
|
||||
```nix
|
||||
# In flake.nix inputs
|
||||
inputs.playwright-web-flake.url = "github:pietdevries94/playwright-web-flake/1.56.1";
|
||||
|
||||
# In devShell
|
||||
PLAYWRIGHT_BROWSERS_PATH = "${playwright-driver.browsers}";
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD = "1";
|
||||
```
|
||||
|
||||
## File Naming
|
||||
|
||||
Tests colocated with source files:
|
||||
Tests colocated with source files (unit/integration) or in `e2e/` directory (E2E):
|
||||
|
||||
```
|
||||
src/
|
||||
@@ -164,22 +252,33 @@ src/
|
||||
today/
|
||||
route.ts
|
||||
route.test.ts
|
||||
e2e/
|
||||
smoke.spec.ts
|
||||
dashboard.spec.ts
|
||||
auth.spec.ts
|
||||
settings.spec.ts
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
npm test
|
||||
|
||||
# Run with coverage
|
||||
npm run test:coverage
|
||||
# Run unit/integration tests
|
||||
pnpm test:run
|
||||
|
||||
# Run in watch mode
|
||||
npm run test:watch
|
||||
pnpm test
|
||||
|
||||
# Run specific file
|
||||
npm test -- src/lib/cycle.test.ts
|
||||
# Run E2E tests (headless)
|
||||
pnpm test:e2e
|
||||
|
||||
# Run E2E tests (visible browser)
|
||||
pnpm test:e2e:headed
|
||||
|
||||
# Run E2E tests with UI mode
|
||||
pnpm test:e2e:ui
|
||||
|
||||
# Run all tests (unit + E2E)
|
||||
pnpm test:run && pnpm test:e2e
|
||||
```
|
||||
|
||||
## Coverage Expectations
|
||||
@@ -190,15 +289,20 @@ No strict coverage thresholds for MVP, but aim for:
|
||||
|
||||
## Success Criteria
|
||||
|
||||
1. All tests pass in CI before merge
|
||||
2. Core decision engine logic has comprehensive tests
|
||||
1. All tests (unit, integration, E2E) pass in CI before merge
|
||||
2. Core decision engine logic has comprehensive unit tests
|
||||
3. Phase scaling tested for multiple cycle lengths
|
||||
4. API auth tested for protected routes
|
||||
5. Critical user flows covered by E2E tests
|
||||
|
||||
## Acceptance Tests
|
||||
|
||||
- [ ] `npm test` runs without errors
|
||||
- [ ] `pnpm test:run` runs without errors
|
||||
- [ ] `pnpm test:e2e` runs without errors
|
||||
- [ ] Unit tests cover decision engine logic
|
||||
- [ ] Unit tests cover cycle phase calculations
|
||||
- [ ] Integration tests verify API authentication
|
||||
- [ ] E2E tests verify login flow
|
||||
- [ ] E2E tests verify dashboard displays correctly
|
||||
- [ ] E2E tests verify period logging works
|
||||
- [ ] Tests run in CI pipeline
|
||||
|
||||
Reference in New Issue
Block a user