Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ dist/
.pre-commit-config.yaml
node_modules/
test-results/
tests/reports/
25 changes: 10 additions & 15 deletions justfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Prefix for commands that need a Nix devshell; empty if already inside one.

nix_shell := if env('IN_NIX_SHELL', '') != '' { '' } else { 'nix develop -c' }

# List available recipes
Expand All @@ -11,29 +12,23 @@ dev:

# Run server with cargo watch (auto-reload)
server:
cd server && {{nix_shell}} cargo watch -x run
cd server && {{ nix_shell }} cargo watch -x run

# Run client with trunk serve (WASM hot-reload)
client:
cd client && {{nix_shell}} trunk serve
cd client && {{ nix_shell }} trunk serve

# Run Playwright e2e tests (starts server via nix run)
# Run Cucumber e2e tests (starts server via nix run)
test:
cd tests \
&& {{nix_shell}} npm install \
&& {{nix_shell}} npx playwright test
&& {{ nix_shell }} npm install \
&& {{ nix_shell }} npx tsx node_modules/.bin/cucumber-js --profile ui

# Run Playwright e2e tests against an already-running dev server (just dev)
# Run Cucumber e2e tests against an already-running dev server (just dev)
test-dev:
cd tests \
&& {{nix_shell}} npm install \
&& PLAYWRIGHT_REUSE_SERVER=1 {{nix_shell}} npx playwright test

# Run Playwright e2e tests with interactive UI
test-ui:
cd tests \
&& {{nix_shell}} npm install \
&& {{nix_shell}} npx playwright test --ui
&& {{ nix_shell }} npm install \
&& REUSE_SERVER=1 {{ nix_shell }} npx tsx node_modules/.bin/cucumber-js --profile ui

# Run full nix build (via vira), e2e tests, and post signoff status to GitHub
ci:
Expand Down Expand Up @@ -72,7 +67,7 @@ ci:

# Run pre-commit hooks on all files
pc:
{{nix_shell}} pre-commit run -a
{{ nix_shell }} pre-commit run -a

# Nix build (server + client WASM)
build:
Expand Down
4 changes: 3 additions & 1 deletion nix/modules/process-compose.nix
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
processes = {
server.command = "cd server && cargo watch -x run";
client = {
command = "cd client && trunk serve";
# Build first so missing deps (npm) fail fast instead of
# trunk silently serving an empty site.
command = "cd client && npm install && trunk build && trunk serve";
is_tty = true;
};
};
Expand Down
81 changes: 81 additions & 0 deletions tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# E2E Tests

End-to-end tests using [Cucumber.js](https://github.com/cucumber/cucumber-js) with [Playwright](https://playwright.dev/) for browser automation.

## Structure

```
tests/
├── cucumber.js # Cucumber config (profiles)
├── features/ # Gherkin scenarios
│ ├── smoke.feature # Page load, health endpoint
│ └── terminal.feature # Canvas rendering, resize, zoom, WebSocket
├── step_definitions/ # Step implementations (Playwright)
│ ├── smoke_steps.ts
│ └── terminal_steps.ts
└── support/
├── world.ts # KoluWorld — shared state + terminal helpers
└── hooks.ts # Browser lifecycle, server startup, screenshots
```

## Running

```bash
# Against a running dev server (just dev)
just test-dev

# Full build + test (starts server via nix run)
just test
```

Set `HEADLESS=false` to see the browser:

```bash
cd tests && REUSE_SERVER=1 HEADLESS=false nix develop ..#default -c npx tsx node_modules/.bin/cucumber-js --profile ui
```

## How it works

- **Cucumber.js** is the test runner. It parses `.feature` files and matches steps to TypeScript functions.
- **Playwright** is the browser automation library. `chromium.launch()` starts a real Chromium instance.
- **KoluWorld** (in `support/world.ts`) holds per-scenario state: the Playwright `page`, collected errors, and terminal helper methods (`zoomIn()`, `resizeViewport()`, `terminalRun()`, etc.).
- **Hooks** (`support/hooks.ts`) manage lifecycle: one browser for the entire run (`BeforeAll`), fresh context + page per scenario (`Before`/`After`), screenshot on failure.

### Adding a new test

1. Write a scenario in a `.feature` file.
2. Run — Cucumber prints snippet stubs for undefined steps.
3. Implement the steps in `step_definitions/`.

### Future: shared scenarios for API + UI

When REST endpoints land (Phase 2+), the same `.feature` files can drive both API-level and UI-level tests via separate profiles:

```
step_definitions/ # UI steps (Playwright browser)
step_definitions_api/ # API steps (direct HTTP calls)
support/ # PlaywrightWorld
support_api/ # ApiWorld (no browser)
```

Run with `--profile ui` or `--profile api` against the same scenarios.

## Why Cucumber over Playwright's test runner

We migrated from `@playwright/test` to Cucumber + Playwright (as library) in Phase 0.

### What we gained

- **Readable scenarios as documentation.** `.feature` files describe behavior in plain language. Useful for reviewing what the app does without reading TypeScript.
- **Test-first workflow.** Write the scenario before the implementation — Cucumber prints stub snippets for missing steps, giving you a clear checklist.
- **Shared scenarios across test profiles.** One `.feature` file can be executed by different step definition sets (UI via Playwright, API via `fetch`). Avoids duplicating test logic.
- **Step reuse across scenarios.** Steps like `the terminal is ready` or `there should be no page errors` are defined once and composed freely. Playwright tests reuse code via helper functions too, but Cucumber makes the composition visible in the `.feature` file.
- **Lower runner overhead.** Cucumber-js is a thinner runner than `@playwright/test`. Same Chromium, same Playwright API, but ~30% faster in practice due to less worker/config/reporter machinery.

### What we lost

- **Interactive UI mode.** `npx playwright test --ui` provides a visual test explorer with step-through replay. No Cucumber equivalent. Workaround: `HEADLESS=false` + `PWDEBUG=1` opens Playwright Inspector.
- **Auto-waiting assertions.** `@playwright/test` wraps `expect()` with auto-retry (e.g., `expect(locator).toBeVisible()` polls until true or timeout). With raw Playwright, we use `locator.waitFor()` explicitly — slightly more verbose.
- **Trace viewer.** Playwright's trace recording (`--trace on`) and viewer (`npx playwright show-trace`) aren't available out of the box. Could be wired manually in hooks but we haven't needed it.
- **Parallel workers.** `@playwright/test` parallelizes across worker processes with isolated contexts by default. Cucumber has `--parallel N` but requires more careful state management. Not relevant yet with 9 scenarios completing in ~10s.
- **Snapshot/visual testing.** `@playwright/test` has built-in screenshot comparison (`expect(page).toHaveScreenshot()`). Would need a separate library if we ever want pixel-level regression testing.
8 changes: 8 additions & 0 deletions tests/cucumber.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const ui = {
paths: ['features/**/*.feature'],
import: ['step_definitions/**/*.ts', 'support/**/*.ts'],
format: ['progress-bar', 'html:reports/report.html'],
formatOptions: { snippetInterface: 'async-await' },
};

export default {};
38 changes: 0 additions & 38 deletions tests/e2e/dsl/index.ts

This file was deleted.

90 changes: 0 additions & 90 deletions tests/e2e/dsl/terminal.ts

This file was deleted.

67 changes: 0 additions & 67 deletions tests/e2e/dsl/types.ts

This file was deleted.

12 changes: 0 additions & 12 deletions tests/e2e/smoke.spec.ts

This file was deleted.

Loading