plum-e2e 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CLAUDE.md +201 -0
  2. package/README.md +237 -90
  3. package/backend/_scaffold/utils/browser.ts +5 -2
  4. package/backend/app.js +9 -1
  5. package/backend/config/scripts/generate-report.js +34 -73
  6. package/backend/config/scripts/run-tests.js +7 -3
  7. package/backend/constants/triggers.js +67 -0
  8. package/backend/lib/reportFilename.js +37 -0
  9. package/backend/lib/testChunker.js +73 -0
  10. package/backend/middleware/auth.js +32 -0
  11. package/backend/package.json +4 -2
  12. package/backend/prisma/migrations/20260616000000_add_runners_and_browser/migration.sql +26 -0
  13. package/backend/prisma/migrations/20260616000001_cron_runner_ids/migration.sql +6 -0
  14. package/backend/prisma/migrations/20260617000000_cron_enabled/migration.sql +1 -0
  15. package/backend/prisma/migrations/20260617000001_report_content/migration.sql +8 -0
  16. package/backend/prisma/schema.prisma +21 -1
  17. package/backend/routes/cron.routes.js +28 -0
  18. package/backend/routes/node.routes.js +121 -0
  19. package/backend/routes/reports.routes.js +23 -20
  20. package/backend/routes/runners.routes.js +83 -0
  21. package/backend/scripts/add-local-runner.js +120 -0
  22. package/backend/scripts/create-test.js +148 -0
  23. package/backend/server.js +16 -7
  24. package/backend/services/backupService.js +3 -30
  25. package/backend/services/cronService.js +220 -36
  26. package/backend/services/reportService.js +227 -55
  27. package/backend/services/runnerService.js +179 -0
  28. package/backend/websockets/socketHandler.js +162 -21
  29. package/bin/plum.js +191 -47
  30. package/docker-compose.node.yml +59 -0
  31. package/docker-compose.yml +2 -0
  32. package/frontend/package.json +1 -4
  33. package/frontend/src/app.css +20 -254
  34. package/frontend/src/app.html +1 -1
  35. package/frontend/src/lib/api/reports.js +17 -36
  36. package/frontend/src/lib/api/runners.js +61 -0
  37. package/frontend/src/lib/api/schedules.js +34 -5
  38. package/frontend/src/lib/api/settings.js +5 -5
  39. package/frontend/src/lib/api/tests.js +2 -19
  40. package/frontend/src/lib/components/icons/BrowserIcon.svelte +75 -0
  41. package/frontend/src/lib/components/layout/Nav.svelte +42 -47
  42. package/frontend/src/lib/components/layout/RunnerPanel.svelte +913 -253
  43. package/frontend/src/lib/components/ui/Badge.svelte +6 -1
  44. package/frontend/src/lib/components/ui/ConfirmModal.svelte +98 -0
  45. package/frontend/{tailwind.config.js → src/lib/components/ui/EmptyState.svelte} +27 -8
  46. package/frontend/{postcss.config.js → src/lib/components/ui/Toast.svelte} +20 -7
  47. package/frontend/src/lib/constants.js +36 -0
  48. package/frontend/src/lib/stores/runner.js +23 -12
  49. package/frontend/src/lib/styles/global.css +176 -0
  50. package/frontend/src/lib/styles/reset.css +86 -0
  51. package/frontend/src/lib/styles/tokens.css +90 -0
  52. package/frontend/src/lib/utils/format.js +46 -0
  53. package/frontend/src/routes/+page.svelte +16 -35
  54. package/frontend/src/routes/reports/+page.svelte +84 -167
  55. package/frontend/src/routes/reports/{[slug] → [id]}/+page.svelte +304 -76
  56. package/frontend/src/routes/reports/live/+page.svelte +704 -0
  57. package/frontend/src/routes/scheduled-tests/+page.svelte +328 -88
  58. package/frontend/src/routes/settings/+page.svelte +774 -127
  59. package/frontend/static/favicon-32x32.png +0 -0
  60. package/frontend/static/favicon.ico +0 -0
  61. package/package.json +2 -2
  62. package/frontend/static/favicon.png +0 -0
package/CLAUDE.md ADDED
@@ -0,0 +1,201 @@
1
+ # Plum — Claude Code Instructions
2
+
3
+ ## Stack
4
+
5
+ - **Frontend**: SvelteKit 5, port 5173. ESM, no TypeScript.
6
+ - **Backend**: Express + Socket.io, port 3001. CommonJS (`require`/`module.exports`).
7
+ - **Database**: PostgreSQL via Prisma ORM (`backend/services/prisma.js`).
8
+ - **Infrastructure**: Docker Compose. `docker-compose.yml` (standard), `docker-compose.node.yml` (multi-node).
9
+
10
+ ---
11
+
12
+ ## Frontend Standards
13
+
14
+ ### CSS
15
+
16
+ - All design tokens are in `frontend/src/lib/styles/tokens.css` as CSS custom properties (`--accent`, `--bg`, `--text`, etc.).
17
+ - Never use Tailwind. Never use inline `style=` attributes for themeable values.
18
+ - Component styles go in the component's `<style>` block (scoped by default in Svelte).
19
+ - When adding a new token color, add it to `tokens.css` first, then reference `var(--your-token)`.
20
+
21
+ ### Constants and utilities
22
+
23
+ - Shared constants live in `frontend/src/lib/constants.js`: `API_BASE`, `BROWSERS`, `TRIGGER_TYPES`, `WORKER_OPTIONS`, `REPORTS_PER_PAGE`, `COPY_TIMEOUT_MS`, `TOAST_TIMEOUT_MS`.
24
+ - Format/display utilities live in `frontend/src/lib/utils/format.js`: `isScheduled`, `triggerLabel`, `triggerVariant`, `stagger`, `fmtDuration`.
25
+ - Never hardcode the API URL — always use `API_BASE` from constants.
26
+ - Never hardcode magic numbers that belong in constants.
27
+
28
+ ### API layer
29
+
30
+ - All backend calls go through `frontend/src/lib/api/` modules (e.g., `reports.js`, `runners.js`).
31
+ - Each module exports typed async functions. Route pages import from there, not from raw `fetch`.
32
+
33
+ ### Components
34
+
35
+ Reuse shared components before creating new ones:
36
+
37
+ | Component | Path | Purpose |
38
+ | -------------- | ------------------------------------------ | -------------------------------- |
39
+ | `EmptyState` | `$lib/components/ui/EmptyState.svelte` | Zero-item states |
40
+ | `ConfirmModal` | `$lib/components/ui/ConfirmModal.svelte` | Destructive action confirmations |
41
+ | `Toast` | `$lib/components/ui/Toast.svelte` | Transient feedback messages |
42
+ | `BrowserIcon` | `$lib/components/icons/BrowserIcon.svelte` | Browser logo icons |
43
+
44
+ ### State management
45
+
46
+ - Global runtime state lives in `frontend/src/lib/stores/runner.js` (`runnerState`, `runnerConfig`, `panelExpanded`, etc.).
47
+ - `reportsVersion` and `testsVersion` are increment-to-refresh signals — increment them to trigger dependent reactive blocks without page reloads.
48
+
49
+ ### Route pages
50
+
51
+ - Pages are thin: they load data and delegate display to components.
52
+ - `+page.js` (load function) handles server-side or initial data fetching.
53
+ - No business logic inside route page files.
54
+
55
+ ---
56
+
57
+ ## Backend Standards
58
+
59
+ ### File layout
60
+
61
+ ```
62
+ backend/
63
+ app.js — Express setup, middleware, static routes
64
+ routes/ — Thin HTTP handlers only
65
+ services/ — All business logic
66
+ websockets/ — Socket.io event handlers
67
+ lib/ — Pure utility functions
68
+ constants/ — Shared constant definitions
69
+ config/scripts/ — CLI scripts (generate-report, install, etc.)
70
+ prisma/ — Schema and migrations
71
+ ```
72
+
73
+ ### Routes
74
+
75
+ Routes delegate immediately to services. No business logic in route handlers.
76
+
77
+ ```js
78
+ // Good
79
+ router.get('/:id', async (req, res, next) => {
80
+ try {
81
+ const report = await reportService.getReportDetail(Number(req.params.id));
82
+ if (!report) return res.status(404).json({ error: 'Not found' });
83
+ res.json(report);
84
+ } catch (e) { next(e); }
85
+ });
86
+
87
+ // Bad — business logic in the route
88
+ router.get('/:id', async (req, res) => {
89
+ const row = await prisma.report.findUnique({ where: { id: ... }, include: { ... } });
90
+ const features = row.content.features.map(f => { /* transform */ });
91
+ res.json(features);
92
+ });
93
+ ```
94
+
95
+ ### Services
96
+
97
+ - Services own all DB queries, data transformation, and side effects (file I/O, etc.).
98
+ - Use `backend/services/prisma.js` as the shared Prisma client — do not create new instances.
99
+ - Services return plain JS objects, not Prisma model instances with extra methods.
100
+
101
+ ### Constants
102
+
103
+ - Trigger types and runner IDs live in `backend/constants/triggers.js`.
104
+ - Never use raw string literals for values that are used in more than one place.
105
+
106
+ ### Screenshots / file storage
107
+
108
+ - Screenshot files are stored in `reports/screenshots/` (resolved at runtime via `REPORTS_DIR`).
109
+ - Express serves them via `app.use('/screenshots', express.static(SCREENSHOTS_DIR))`.
110
+ - DB stores only the filename, not the path or full URL.
111
+ - Frontend constructs the full URL with `screenshotUrl(filename)` from `$lib/api/reports.js`.
112
+
113
+ ---
114
+
115
+ ## Comments
116
+
117
+ Default: **no comments**.
118
+
119
+ Add a comment only when the **WHY** is non-obvious — a hidden constraint, a subtle invariant, a workaround for a specific bug, or behavior that would surprise a future reader.
120
+
121
+ Never write comments that:
122
+
123
+ - Explain what the code does (well-named identifiers already do that)
124
+ - Describe the current task or fix ("added for the live page", "handles issue #123")
125
+ - Are multi-line docblocks explaining parameters
126
+
127
+ The GPL license header at the top of every file is a legal requirement — do not remove it.
128
+
129
+ ---
130
+
131
+ ## General Rules
132
+
133
+ - **No Tailwind, no Bootstrap, no utility-class frameworks.**
134
+ - **No hardcoded URLs or magic strings** — use constants.
135
+ - **No premature abstractions** — three similar lines is better than a helper created for two cases.
136
+ - **No backwards-compat hacks** — rename, re-export, or add `_unused` prefixes only when explicitly asked.
137
+ - **No error handling for scenarios that cannot happen** — trust internal guarantees; only validate at system boundaries (user input, external APIs).
138
+ - **No half-finished implementations** — if a feature cannot be completed, say so before starting.
139
+ - **Prefer editing existing files** over creating new ones.
140
+
141
+ ---
142
+
143
+ ## Database
144
+
145
+ - All schema changes require a Prisma migration file in `backend/prisma/migrations/`.
146
+ - Run `npx prisma migrate deploy` (or rebuild Docker) to apply.
147
+ - Report content is stored as JSONB in the `Report.content` column — never reconstruct report metadata from filenames.
148
+ - The `getAllReports()` service function deliberately excludes `content` for list performance — only `getReportDetail(id)` fetches it.
149
+
150
+ ---
151
+
152
+ ## Docker
153
+
154
+ - Rebuild after any backend dependency or schema change: `docker-compose up --build -d`
155
+ - The backend container runs `prisma migrate deploy` on startup before starting Express.
156
+ - Frontend dev server (`npm run dev`) runs outside Docker for fast HMR.
157
+
158
+ ---
159
+
160
+ ## Windows Compatibility
161
+
162
+ Plum runs on Windows. Every script and service must work on Windows without modification.
163
+
164
+ ### `spawn` — always use `{ shell: true }` for npm/npx/docker
165
+
166
+ On Windows, `npm`, `npx`, and `docker` are `.cmd` wrappers, not binaries. Without `shell: true`, `spawn` cannot find them.
167
+
168
+ ```js
169
+ // Good
170
+ spawn('npm', ['run', 'test'], { env, shell: true });
171
+
172
+ // Bad — breaks on Windows
173
+ spawn('npm', ['run', 'test'], { env });
174
+ ```
175
+
176
+ `execSync` and `exec` use the system shell by default, so they are fine without `shell: true`.
177
+
178
+ ### Spawning Node.js — use `process.execPath`, not `'node'`
179
+
180
+ ```js
181
+ // Good
182
+ spawn(process.execPath, [scriptPath], { stdio: 'inherit' });
183
+
184
+ // Bad — 'node' may not be in PATH or may resolve to the wrong version
185
+ spawn('node', [scriptPath]);
186
+ ```
187
+
188
+ ### File paths — always use `path.join()` or `path.resolve()`
189
+
190
+ Never build paths by concatenating strings with `/`. Use `path.join()` everywhere.
191
+
192
+ When a path is written into a YAML or config file (e.g., docker-compose override), normalise Windows backslashes: `absPath.replace(/\\/g, '/')`.
193
+
194
+ ### Forbidden Unix-only APIs in Node.js scripts
195
+
196
+ Never call these from `.js` scripts — they do not exist on Windows:
197
+
198
+ - `chmod` / `chown` — use `fs` permissions APIs if needed
199
+ - `which` — use `which` npm package or check `PATH` manually
200
+ - `curl` / `wget` — use Node.js `fetch` or `https` module
201
+ - Shell operators (`&&`, `||`, `$(cmd)`) inside strings passed to `execSync` — split into separate `execSync` calls instead
package/README.md CHANGED
@@ -1,4 +1,4 @@
1
- ![Plum social preview](https://repository-images.githubusercontent.com/936477779/e928fce3-6d4c-4609-92a0-0a1091c99752)
1
+ ![Plum social preview](https://repository-images.githubusercontent.com/936477779/3accb0f2-72b4-447c-b255-d171f6284104)
2
2
 
3
3
  <p align="center">
4
4
  <a href="https://www.npmjs.com/package/plum-e2e"><img src="https://img.shields.io/npm/v/plum-e2e?color=7c3aed&label=plum-e2e" alt="npm version" /></a>
@@ -15,130 +15,159 @@
15
15
 
16
16
  ## Requirements
17
17
 
18
- - [Node.js](https://nodejs.org) (v18+)
19
- - [Docker](https://www.docker.com) — only needed for `plum start`
18
+ - [Node.js](https://nodejs.org) v18 or higher
19
+ - [Docker](https://www.docker.com) — required for `plum server start` and `plum node start`
20
20
 
21
21
  ---
22
22
 
23
- ## Quick Start
23
+ ## 1. Installation
24
+
25
+ ### Install Plum globally
24
26
 
25
27
  ```bash
26
28
  npm install -g plum-e2e
27
- mkdir my-tests && cd my-tests
28
- plum init
29
29
  ```
30
30
 
31
- ---
32
-
33
- ## Setup & Configuration
31
+ > The `-g` flag is required. Plum is a CLI tool — it must be installed globally to use `plum` commands anywhere on your machine.
34
32
 
35
- ### `plum init`
33
+ ### Initialize a new project
36
34
 
37
- Scaffolds a new project in the current directory. Run this once after creating your project folder.
35
+ Create a folder for your tests and run `plum init` inside it:
38
36
 
39
37
  ```bash
38
+ mkdir my-tests
39
+ cd my-tests
40
40
  plum init
41
41
  ```
42
42
 
43
- Creates the following structure:
43
+ This sets up your project with a working test scaffold:
44
44
 
45
- <pre>
46
- ╠═ tests/
47
- ║ ╠═ features/ — Gherkin .feature files (your test cases)
48
- ║ ╠═ step_definitions/ TypeScript step implementations
49
- ║ ╠═ pages/ Page Object Models
50
- ║ ╚═ utils/ — Browser setup, hooks, shared helpers
51
- ╠═ .vscode/
52
- ║ ╚═ settings.json Cucumber extension config (step linking)
53
- ╠═ .env Environment config (BASE_URL, IS_HEADLESS)
54
- ╠═ .gitignore Pre-configured to ignore .plum/ and reports/
55
- ╚═ plum.plugins.json Add extra npm packages for your tests here
56
- </pre>
45
+ ```
46
+ my-tests/
47
+ ├── tests/
48
+ │ ├── features/ Gherkin .feature files (your test cases)
49
+ │ ├── step_definitions/ TypeScript step implementations
50
+ │ ├── pages/ — Page Object Models
51
+ │ └── utils/ — Browser setup, hooks, shared helpers
52
+ ├── .env Base URL and browser settings
53
+ ├── .gitignore Pre-configured to exclude reports/
54
+ ├── plum.plugins.json Add extra npm packages for your tests
55
+ └── tsconfig.json IDE type resolution (no local install needed)
56
+ ```
57
57
 
58
- Also installs all Plum dependencies and the [Cucumber VS Code extension](https://marketplace.visualstudio.com/items?itemName=cucumberopen.cucumber-official) for step definition linking.
58
+ The `tests/` folder includes working example tests against [SauceDemo](https://www.saucedemo.com/v1/) so you can run something immediately.
59
59
 
60
- ---
60
+ ### Configure your target URL
61
61
 
62
- ### `.env` Environment Config
63
-
64
- Plum creates a `.env` in your project root. Edit it to point at your app:
62
+ Open `.env` and set your application's URL:
65
63
 
66
64
  ```env
67
65
  BASE_URL=https://your-app.com
68
66
  IS_HEADLESS=false
69
67
  ```
70
68
 
71
- | Variable | Description |
72
- | ------------- | -------------------------------------------------------- |
73
- | `BASE_URL` | The base URL Playwright navigates to during tests |
74
- | `IS_HEADLESS` | Run the browser headlessly (`true`) or visibly (`false`) |
75
-
76
- ---
69
+ | Variable | Description |
70
+ | ------------- | ---------------------------------------------------- |
71
+ | `BASE_URL` | The URL Playwright opens at the start of tests |
72
+ | `IS_HEADLESS` | `true` to run headlessly, `false` to see the browser |
77
73
 
78
- ### `plum.plugins.json` Adding Packages
74
+ ### Add extra packages (optional)
79
75
 
80
- If your tests need extra npm packages (e.g. a faker library, an assertion helper), add them to `plum.plugins.json`:
76
+ If your tests need additional npm packages, add them to `plum.plugins.json`:
81
77
 
82
78
  ```json
83
79
  {
84
80
  "dependencies": {
85
- "@faker-js/faker": "^9.0.0",
86
- "dayjs": "^1.11.0"
81
+ "@faker-js/faker": "^9.0.0"
87
82
  }
88
83
  }
89
84
  ```
90
85
 
91
- Plum installs these automatically before running tests. Commit this file so your whole team shares the same dependencies.
86
+ Plum installs these automatically before each run. Commit this file so your whole team uses the same packages.
92
87
 
93
88
  ---
94
89
 
95
- ## Development Commands
90
+ ## 2. Starting the Server
96
91
 
97
- ### `plum dev` Run Tests Locally
92
+ Plum includes a full web UI for triggering tests, viewing reports, and scheduling runs.
98
93
 
99
- Runs tests directly on your machine without Docker. This is the fastest way to write and debug tests.
94
+ ### Start
100
95
 
101
96
  ```bash
102
- plum dev # run all tests
103
- plum dev @test-login-1 # run a single scenario by tag
104
- plum dev @suite-login # run a whole suite by tag
105
- plum dev --parallel 4 # run all tests across 4 workers
106
- plum dev --parallel 4 @suite-x # run a suite in parallel
97
+ plum server start
107
98
  ```
108
99
 
109
- > Syncs your `tests/` folder into Plum, installs dependencies, and runs Cucumber.
100
+ or the shorthand:
110
101
 
111
- ---
102
+ ```bash
103
+ plum start
104
+ ```
105
+
106
+ Once running, open **http://localhost:5173** in your browser.
112
107
 
113
- ### `plum start` Run via Docker
108
+ > Docker must be running before you use this command. Plum builds and starts the backend, database, and UI automatically.
114
109
 
115
- Starts the full Plum stack (backend + UI) in Docker. Use this when you want the web interface at `http://localhost:5173` to trigger, monitor, and schedule tests.
110
+ ### Stop
116
111
 
117
112
  ```bash
118
- plum start
113
+ plum server stop
114
+ ```
115
+
116
+ or the shorthand:
117
+
118
+ ```bash
119
+ plum stop
119
120
  ```
120
121
 
121
- > Requires Docker to be running. Automatically rebuilds if you've changed plugins or config.
122
+ > Your data (reports, schedules, settings) is preserved in the database volume. Only the running containers are stopped.
122
123
 
123
124
  ---
124
125
 
125
- ### `plum create-step` — Generate a Step
126
+ ## 3. Writing Tests
126
127
 
127
- Interactive prompt that generates a new step definition and its Page Object method.
128
+ Plum uses [Cucumber](https://cucumber.io) and [Gherkin](https://cucumber.io/docs/gherkin/) to write human-readable test cases.
128
129
 
129
- ```bash
130
- plum create-step
130
+ ### Cucumber Basics
131
+
132
+ Gherkin is a plain-English language for writing test scenarios. Each test lives in a `.feature` file and follows this structure:
133
+
134
+ ```gherkin
135
+ @suite-login
136
+ Feature: Login
137
+
138
+ @test-login-1
139
+ Scenario: User can log in with valid credentials
140
+ Given I am on the login page
141
+ When I enter valid credentials
142
+ Then I should see the dashboard
131
143
  ```
132
144
 
133
- Follow the prompts to describe the action — Plum writes the boilerplate so you just fill in the implementation.
145
+ **Key concepts:**
146
+
147
+ | Term | What it is |
148
+ | ------------------ | ---------------------------------------------------------------------- |
149
+ | `Feature` | A group of related scenarios (one per file) |
150
+ | `Scenario` | A single test case |
151
+ | `Given` | Sets up the initial state |
152
+ | `When` | Performs an action |
153
+ | `Then` | Asserts the expected outcome |
154
+ | `Background` | Steps that run before every scenario in a file |
155
+ | `Scenario Outline` | A parameterized scenario that runs once per row in an `Examples` table |
156
+ | `@tag` | A label used to run specific tests or suites |
157
+
158
+ **Useful links:**
159
+
160
+ - [Gherkin syntax reference](https://cucumber.io/docs/gherkin/reference/)
161
+ - [Cucumber step definitions](https://cucumber.io/docs/cucumber/step-definitions/)
162
+ - [Playwright documentation](https://playwright.dev/docs/intro)
134
163
 
135
164
  ---
136
165
 
137
- ## Writing a Test
166
+ ### Project Structure
138
167
 
139
168
  Tests follow a three-layer structure: **Feature → Step Definition → Page Object**.
140
169
 
141
- ### 1. Feature File
170
+ #### Feature File — what to test
142
171
 
143
172
  ```gherkin
144
173
  # tests/features/Login.feature
@@ -149,13 +178,13 @@ Feature: Login
149
178
  @test-login-1
150
179
  Scenario: User can log in with valid credentials
151
180
  Given I am on the login page
152
- When I log in with valid credentials
181
+ When I enter valid credentials
153
182
  Then I should see the dashboard
154
183
  ```
155
184
 
156
- Use tags (`@suite-login`, `@test-login-1`) to filter which tests to run.
185
+ Tags (`@suite-login`, `@test-login-1`) let you run specific tests or entire suites. Every suite and scenario should have its own tag.
157
186
 
158
- ### 2. Page Object
187
+ #### Page Object — how to interact with the page
159
188
 
160
189
  ```typescript
161
190
  // tests/pages/LoginPage.ts
@@ -167,8 +196,8 @@ export class LoginPage {
167
196
  await page().goto(process.env.BASE_URL as string);
168
197
  }
169
198
 
170
- static async login(email: string, password: string) {
171
- await page().fill('#email', email);
199
+ static async enterCredentials(username: string, password: string) {
200
+ await page().fill('#username', username);
172
201
  await page().fill('#password', password);
173
202
  await page().click('button[type="submit"]');
174
203
  }
@@ -179,9 +208,9 @@ export class LoginPage {
179
208
  }
180
209
  ```
181
210
 
182
- Methods are **static** they use `page()` internally so you never pass a page instance around.
211
+ Methods are `static` and use the `page()` helper from `utils/browser.ts` — you never pass a page instance around.
183
212
 
184
- ### 3. Step Definition
213
+ #### Step Definition — connects Gherkin to code
185
214
 
186
215
  ```typescript
187
216
  // tests/step_definitions/LoginSteps.ts
@@ -193,8 +222,8 @@ Given('I am on the login page', async () => {
193
222
  await LoginPage.goToLoginPage();
194
223
  });
195
224
 
196
- When('I log in with valid credentials', async () => {
197
- await LoginPage.login('user@example.com', 'password');
225
+ When('I enter valid credentials', async () => {
226
+ await LoginPage.enterCredentials('user@example.com', 'password123');
198
227
  });
199
228
 
200
229
  Then('I should see the dashboard', async () => {
@@ -204,47 +233,165 @@ Then('I should see the dashboard', async () => {
204
233
 
205
234
  ---
206
235
 
236
+ ### Generate a Step
237
+
238
+ Use `plum create-step` to interactively scaffold a new step definition and page object method:
239
+
240
+ ```bash
241
+ plum create-step
242
+ ```
243
+
244
+ Follow the prompts — Plum writes the boilerplate, you fill in the implementation.
245
+
246
+ ---
247
+
248
+ ### Run Tests Locally
249
+
250
+ Use `plum dev` to run tests directly on your machine without Docker:
251
+
252
+ ```bash
253
+ plum dev # run all tests
254
+ plum dev @test-login-1 # run a single scenario
255
+ plum dev @suite-login # run an entire suite
256
+ plum dev --parallel 4 # run all tests across 4 workers
257
+ plum dev --parallel 4 @suite-login # run a suite in parallel
258
+ ```
259
+
260
+ > `plum dev` syncs your tests, installs dependencies, and runs Cucumber. No Docker needed.
261
+
262
+ ---
263
+
264
+ ## 4. Runner Setup
265
+
266
+ Runners are additional machines that can execute tests in parallel alongside the primary server. This lets you distribute a large test suite across multiple nodes.
267
+
268
+ ### Add a local runner node
269
+
270
+ On the machine that will act as a runner, navigate to your Plum project and run:
271
+
272
+ ```bash
273
+ plum node start --token your-secret-token
274
+ ```
275
+
276
+ | Flag | Description |
277
+ | --------- | ------------------------------------------------------------------------------- |
278
+ | `--token` | A secret token the primary server must send to authenticate. Keep this private. |
279
+
280
+ > The runner starts a Docker container on port `3001` by default. Make sure Docker is running.
281
+
282
+ ### Register the runner in the UI
283
+
284
+ Once the node is running:
285
+
286
+ 1. Open the Plum UI at **http://localhost:5173**
287
+ 2. Go to **Settings → Runners**
288
+ 3. Click **Add Runner** and enter the node's URL, token, and a name
289
+
290
+ The runner will appear in the UI and can be selected when triggering tests.
291
+
292
+ ### Stop a runner node
293
+
294
+ ```bash
295
+ plum node stop
296
+ ```
297
+
298
+ ---
299
+
207
300
  ## Command Reference
208
301
 
209
- | Command | When to use |
210
- | ----------------------- | -------------------------------------------- |
211
- | `plum init` | First-time setup in a new project folder |
212
- | `plum dev` | Run all tests locally |
213
- | `plum dev @tag` | Run tests matching a tag locally |
214
- | `plum dev --parallel N` | Run tests across N parallel workers locally |
215
- | `plum start` | Start the full UI stack via Docker |
216
- | `plum create-step` | Interactively scaffold a new step definition |
302
+ | Command | Description |
303
+ | ----------------------------- | -------------------------------------------------------- |
304
+ | `plum init` | Initialize a new project in the current folder |
305
+ | `plum server start` | Start the full UI stack via Docker (alias: `plum start`) |
306
+ | `plum server stop` | Stop the server and preserve data (alias: `plum stop`) |
307
+ | `plum dev` | Run all tests locally without Docker |
308
+ | `plum dev @tag` | Run tests matching a tag |
309
+ | `plum dev --parallel N` | Run tests across N parallel workers |
310
+ | `plum create-step` | Interactively scaffold a new step definition |
311
+ | `plum node start --token <t>` | Start a runner node on this machine |
312
+ | `plum node stop` | Stop the runner node |
217
313
 
218
314
  ---
219
315
 
220
- ## For Contributors
316
+ ## Development
221
317
 
222
- > Only relevant if you're developing Plum itself.
318
+ > This section is for contributors developing Plum itself. If you're a user, the sections above are all you need.
319
+
320
+ ### Clone and install
223
321
 
224
322
  ```bash
225
323
  git clone https://github.com/silverlunah/plum.git
226
324
  cd plum
227
- npm run init # installs root + backend + frontend dependencies
325
+ npm run init
228
326
  ```
229
327
 
230
- ### Backend
328
+ `npm run init` installs all dependencies across the monorepo (root, backend, and frontend) in one command.
329
+
330
+ ### Start the stack
331
+
332
+ ```bash
333
+ npm run docker:up # build and start all services (detached)
334
+ npm run docker:down # stop all services
335
+ ```
336
+
337
+ The UI will be available at **http://localhost:5173** after `docker:up` completes.
338
+
339
+ > After any backend dependency or schema change, re-run `npm run docker:up` to rebuild the containers.
340
+
341
+ ### Backend — writing and running tests
342
+
343
+ All test development happens inside the `backend/` directory, where the Playwright + Cucumber runner lives:
231
344
 
232
345
  ```bash
233
346
  cd backend
234
- npm run init # set up .env and local test scaffold
235
- npm test # run all tests
236
- npm test -- @test-1 # run a specific test by tag
237
- npm test -- --parallel 4 # run tests in parallel
238
- npm run create-step # generate a step definition interactively
239
347
  ```
240
348
 
241
- ### Docker
349
+ **Run tests:**
242
350
 
243
351
  ```bash
244
- npm run docker:up # build and start all services in detached mode
245
- npm run docker:down # stop all services
352
+ npm test # run all tests
353
+ npm test -- @test-1 # run a specific scenario by tag
354
+ npm test -- @suite-login # run an entire suite
355
+ npm test -- --parallel 4 # run all tests in parallel
246
356
  ```
247
357
 
358
+ **Generate a step definition:**
359
+
360
+ ```bash
361
+ npm run create-step
362
+ ```
363
+
364
+ Interactive prompt that creates a new step and its page object method.
365
+
366
+ **Scaffold a new test:**
367
+
368
+ ```bash
369
+ npm run create-test
370
+ ```
371
+
372
+ Interactive prompt that creates a new `.feature` file, page object, and step definition file from a template — ready for you to implement.
373
+
374
+ ### Test file locations
375
+
376
+ ```
377
+ backend/tests/
378
+ features/ — Gherkin .feature files
379
+ step_definitions/ — TypeScript step implementations
380
+ pages/ — Page Object Models
381
+ utils/ — Browser setup, hooks, helpers
382
+ ```
383
+
384
+ ### Frontend
385
+
386
+ The frontend dev server runs outside Docker for fast hot module replacement:
387
+
388
+ ```bash
389
+ cd frontend
390
+ npm run dev
391
+ ```
392
+
393
+ Available at **http://localhost:5173**. The backend (via Docker) serves API requests at **http://localhost:3001**.
394
+
248
395
  ---
249
396
 
250
397
  <p align="center">