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.
- package/CLAUDE.md +201 -0
- package/README.md +237 -90
- package/backend/_scaffold/utils/browser.ts +5 -2
- package/backend/app.js +9 -1
- package/backend/config/scripts/generate-report.js +34 -73
- package/backend/config/scripts/run-tests.js +7 -3
- package/backend/constants/triggers.js +67 -0
- package/backend/lib/reportFilename.js +37 -0
- package/backend/lib/testChunker.js +73 -0
- package/backend/middleware/auth.js +32 -0
- package/backend/package.json +4 -2
- package/backend/prisma/migrations/20260616000000_add_runners_and_browser/migration.sql +26 -0
- package/backend/prisma/migrations/20260616000001_cron_runner_ids/migration.sql +6 -0
- package/backend/prisma/migrations/20260617000000_cron_enabled/migration.sql +1 -0
- package/backend/prisma/migrations/20260617000001_report_content/migration.sql +8 -0
- package/backend/prisma/schema.prisma +21 -1
- package/backend/routes/cron.routes.js +28 -0
- package/backend/routes/node.routes.js +121 -0
- package/backend/routes/reports.routes.js +23 -20
- package/backend/routes/runners.routes.js +83 -0
- package/backend/scripts/add-local-runner.js +120 -0
- package/backend/scripts/create-test.js +148 -0
- package/backend/server.js +16 -7
- package/backend/services/backupService.js +3 -30
- package/backend/services/cronService.js +220 -36
- package/backend/services/reportService.js +227 -55
- package/backend/services/runnerService.js +179 -0
- package/backend/websockets/socketHandler.js +162 -21
- package/bin/plum.js +191 -47
- package/docker-compose.node.yml +59 -0
- package/docker-compose.yml +2 -0
- package/frontend/package.json +1 -4
- package/frontend/src/app.css +20 -254
- package/frontend/src/app.html +1 -1
- package/frontend/src/lib/api/reports.js +17 -36
- package/frontend/src/lib/api/runners.js +61 -0
- package/frontend/src/lib/api/schedules.js +34 -5
- package/frontend/src/lib/api/settings.js +5 -5
- package/frontend/src/lib/api/tests.js +2 -19
- package/frontend/src/lib/components/icons/BrowserIcon.svelte +75 -0
- package/frontend/src/lib/components/layout/Nav.svelte +42 -47
- package/frontend/src/lib/components/layout/RunnerPanel.svelte +913 -253
- package/frontend/src/lib/components/ui/Badge.svelte +6 -1
- package/frontend/src/lib/components/ui/ConfirmModal.svelte +98 -0
- package/frontend/{tailwind.config.js → src/lib/components/ui/EmptyState.svelte} +27 -8
- package/frontend/{postcss.config.js → src/lib/components/ui/Toast.svelte} +20 -7
- package/frontend/src/lib/constants.js +36 -0
- package/frontend/src/lib/stores/runner.js +23 -12
- package/frontend/src/lib/styles/global.css +176 -0
- package/frontend/src/lib/styles/reset.css +86 -0
- package/frontend/src/lib/styles/tokens.css +90 -0
- package/frontend/src/lib/utils/format.js +46 -0
- package/frontend/src/routes/+page.svelte +16 -35
- package/frontend/src/routes/reports/+page.svelte +84 -167
- package/frontend/src/routes/reports/{[slug] → [id]}/+page.svelte +304 -76
- package/frontend/src/routes/reports/live/+page.svelte +704 -0
- package/frontend/src/routes/scheduled-tests/+page.svelte +328 -88
- package/frontend/src/routes/settings/+page.svelte +774 -127
- package/frontend/static/favicon-32x32.png +0 -0
- package/frontend/static/favicon.ico +0 -0
- package/package.json +2 -2
- 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
|
-

|
|
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)
|
|
19
|
-
- [Docker](https://www.docker.com) —
|
|
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
|
-
##
|
|
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
|
-
###
|
|
33
|
+
### Initialize a new project
|
|
36
34
|
|
|
37
|
-
|
|
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
|
-
|
|
43
|
+
This sets up your project with a working test scaffold:
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
74
|
-
| `IS_HEADLESS` |
|
|
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
|
-
###
|
|
74
|
+
### Add extra packages (optional)
|
|
79
75
|
|
|
80
|
-
If your tests need
|
|
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
|
|
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
|
-
##
|
|
90
|
+
## 2. Starting the Server
|
|
96
91
|
|
|
97
|
-
|
|
92
|
+
Plum includes a full web UI for triggering tests, viewing reports, and scheduling runs.
|
|
98
93
|
|
|
99
|
-
|
|
94
|
+
### Start
|
|
100
95
|
|
|
101
96
|
```bash
|
|
102
|
-
plum
|
|
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
|
-
|
|
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
|
-
|
|
108
|
+
> Docker must be running before you use this command. Plum builds and starts the backend, database, and UI automatically.
|
|
114
109
|
|
|
115
|
-
|
|
110
|
+
### Stop
|
|
116
111
|
|
|
117
112
|
```bash
|
|
118
|
-
plum
|
|
113
|
+
plum server stop
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
or the shorthand:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
plum stop
|
|
119
120
|
```
|
|
120
121
|
|
|
121
|
-
>
|
|
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
|
-
|
|
126
|
+
## 3. Writing Tests
|
|
126
127
|
|
|
127
|
-
|
|
128
|
+
Plum uses [Cucumber](https://cucumber.io) and [Gherkin](https://cucumber.io/docs/gherkin/) to write human-readable test cases.
|
|
128
129
|
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
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
|
-
|
|
166
|
+
### Project Structure
|
|
138
167
|
|
|
139
168
|
Tests follow a three-layer structure: **Feature → Step Definition → Page Object**.
|
|
140
169
|
|
|
141
|
-
|
|
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
|
|
181
|
+
When I enter valid credentials
|
|
153
182
|
Then I should see the dashboard
|
|
154
183
|
```
|
|
155
184
|
|
|
156
|
-
|
|
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
|
-
|
|
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
|
|
171
|
-
await page().fill('#
|
|
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
|
|
211
|
+
Methods are `static` and use the `page()` helper from `utils/browser.ts` — you never pass a page instance around.
|
|
183
212
|
|
|
184
|
-
|
|
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
|
|
197
|
-
await LoginPage.
|
|
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
|
|
210
|
-
|
|
|
211
|
-
| `plum init`
|
|
212
|
-
| `plum
|
|
213
|
-
| `plum
|
|
214
|
-
| `plum dev
|
|
215
|
-
| `plum
|
|
216
|
-
| `plum
|
|
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
|
-
##
|
|
316
|
+
## Development
|
|
221
317
|
|
|
222
|
-
>
|
|
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
|
|
325
|
+
npm run init
|
|
228
326
|
```
|
|
229
327
|
|
|
230
|
-
|
|
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
|
-
|
|
349
|
+
**Run tests:**
|
|
242
350
|
|
|
243
351
|
```bash
|
|
244
|
-
npm
|
|
245
|
-
npm
|
|
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">
|