@matware/e2e-runner 1.0.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/README.md +396 -0
- package/bin/cli.js +332 -0
- package/bin/mcp-server.js +15 -0
- package/package.json +43 -0
- package/src/actions.js +159 -0
- package/src/config.js +104 -0
- package/src/index.js +75 -0
- package/src/logger.js +19 -0
- package/src/mcp-server.js +345 -0
- package/src/pool.js +144 -0
- package/src/reporter.js +116 -0
- package/src/runner.js +309 -0
- package/templates/docker-compose.yml +13 -0
- package/templates/e2e.config.js +55 -0
- package/templates/sample-test.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://img.shields.io/npm/v/@matware/e2e-runner?color=blue" alt="npm version" />
|
|
3
|
+
<img src="https://img.shields.io/node/v/@matware/e2e-runner" alt="node version" />
|
|
4
|
+
<img src="https://img.shields.io/npm/l/@matware/e2e-runner" alt="license" />
|
|
5
|
+
<img src="https://img.shields.io/badge/MCP-compatible-green" alt="MCP compatible" />
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
# @matware/e2e-runner
|
|
9
|
+
|
|
10
|
+
JSON-driven E2E test runner. Define browser tests as simple JSON action arrays, run them in parallel against a Chrome pool. No JavaScript test files, no complex setup.
|
|
11
|
+
|
|
12
|
+
```json
|
|
13
|
+
[
|
|
14
|
+
{
|
|
15
|
+
"name": "login-flow",
|
|
16
|
+
"actions": [
|
|
17
|
+
{ "type": "goto", "value": "/login" },
|
|
18
|
+
{ "type": "type", "selector": "#email", "value": "user@test.com" },
|
|
19
|
+
{ "type": "type", "selector": "#password", "value": "secret" },
|
|
20
|
+
{ "type": "click", "text": "Sign In" },
|
|
21
|
+
{ "type": "assert_text", "text": "Welcome back" },
|
|
22
|
+
{ "type": "screenshot", "value": "logged-in.png" }
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Why
|
|
31
|
+
|
|
32
|
+
- **No code** -- Tests are JSON files. QA, product, and devs can all write them.
|
|
33
|
+
- **Parallel** -- Run N tests simultaneously against a shared Chrome pool.
|
|
34
|
+
- **Portable** -- Chrome runs in Docker, tests run anywhere.
|
|
35
|
+
- **CI-ready** -- JUnit XML output, exit code 1 on failure, error screenshots.
|
|
36
|
+
- **AI-native** -- Built-in MCP server for Claude Code integration.
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Install
|
|
42
|
+
npm install @matware/e2e-runner
|
|
43
|
+
|
|
44
|
+
# Scaffold project structure
|
|
45
|
+
npx e2e-runner init
|
|
46
|
+
|
|
47
|
+
# Start Chrome pool (requires Docker)
|
|
48
|
+
npx e2e-runner pool start
|
|
49
|
+
|
|
50
|
+
# Run all tests
|
|
51
|
+
npx e2e-runner run --all
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The `init` command creates:
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
e2e/
|
|
58
|
+
tests/
|
|
59
|
+
01-sample.json # Sample test suite
|
|
60
|
+
screenshots/ # Reports and error screenshots
|
|
61
|
+
e2e.config.js # Configuration file
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Test Format
|
|
65
|
+
|
|
66
|
+
Each `.json` file in `e2e/tests/` contains an array of tests. Each test has a `name` and sequential `actions`:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
[
|
|
70
|
+
{
|
|
71
|
+
"name": "homepage-loads",
|
|
72
|
+
"actions": [
|
|
73
|
+
{ "type": "goto", "value": "/" },
|
|
74
|
+
{ "type": "wait", "selector": ".hero" },
|
|
75
|
+
{ "type": "assert_text", "text": "Welcome" },
|
|
76
|
+
{ "type": "assert_url", "value": "/" },
|
|
77
|
+
{ "type": "screenshot", "value": "homepage.png" }
|
|
78
|
+
]
|
|
79
|
+
}
|
|
80
|
+
]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Suite files can have numeric prefixes for ordering (`01-auth.json`, `02-dashboard.json`). The `--suite` flag matches with or without the prefix, so `--suite auth` finds `01-auth.json`.
|
|
84
|
+
|
|
85
|
+
### Available Actions
|
|
86
|
+
|
|
87
|
+
| Action | Fields | Description |
|
|
88
|
+
|--------|--------|-------------|
|
|
89
|
+
| `goto` | `value` | Navigate to URL (relative to `baseUrl` or absolute) |
|
|
90
|
+
| `click` | `selector` or `text` | Click by CSS selector or visible text content |
|
|
91
|
+
| `type` / `fill` | `selector`, `value` | Clear field and type text |
|
|
92
|
+
| `wait` | `selector`, `text`, or `value` (ms) | Wait for element, text, or fixed delay |
|
|
93
|
+
| `assert_text` | `text` | Assert text exists on the page |
|
|
94
|
+
| `assert_url` | `value` | Assert current URL contains value |
|
|
95
|
+
| `assert_visible` | `selector` | Assert element is visible |
|
|
96
|
+
| `assert_count` | `selector`, `value` | Assert element count matches |
|
|
97
|
+
| `screenshot` | `value` (filename) | Capture a screenshot |
|
|
98
|
+
| `select` | `selector`, `value` | Select a dropdown option |
|
|
99
|
+
| `clear` | `selector` | Clear an input field |
|
|
100
|
+
| `press` | `value` | Press a keyboard key (e.g. `Enter`, `Tab`) |
|
|
101
|
+
| `scroll` | `selector` or `value` (px) | Scroll to element or by pixel amount |
|
|
102
|
+
| `hover` | `selector` | Hover over an element |
|
|
103
|
+
| `evaluate` | `value` | Execute JavaScript in the browser context |
|
|
104
|
+
|
|
105
|
+
### Click by Text
|
|
106
|
+
|
|
107
|
+
When `click` uses `text` instead of `selector`, it searches across interactive elements:
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
button, a, [role="button"], [role="tab"], [role="menuitem"], div[class*="cursor"], span
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```json
|
|
114
|
+
{ "type": "click", "text": "Sign In" }
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## CLI
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
# Run tests
|
|
121
|
+
npx e2e-runner run --all # All suites
|
|
122
|
+
npx e2e-runner run --suite auth # Single suite
|
|
123
|
+
npx e2e-runner run --tests path/to.json # Specific file
|
|
124
|
+
npx e2e-runner run --inline '<json>' # Inline JSON
|
|
125
|
+
|
|
126
|
+
# Pool management
|
|
127
|
+
npx e2e-runner pool start # Start Chrome container
|
|
128
|
+
npx e2e-runner pool stop # Stop Chrome container
|
|
129
|
+
npx e2e-runner pool status # Check pool health
|
|
130
|
+
|
|
131
|
+
# Other
|
|
132
|
+
npx e2e-runner list # List available suites
|
|
133
|
+
npx e2e-runner init # Scaffold project
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### CLI Options
|
|
137
|
+
|
|
138
|
+
| Flag | Default | Description |
|
|
139
|
+
|------|---------|-------------|
|
|
140
|
+
| `--base-url <url>` | `http://host.docker.internal:3000` | Application base URL |
|
|
141
|
+
| `--pool-url <ws>` | `ws://localhost:3333` | Chrome pool WebSocket URL |
|
|
142
|
+
| `--tests-dir <dir>` | `e2e/tests` | Tests directory |
|
|
143
|
+
| `--screenshots-dir <dir>` | `e2e/screenshots` | Screenshots/reports directory |
|
|
144
|
+
| `--concurrency <n>` | `3` | Parallel test workers |
|
|
145
|
+
| `--timeout <ms>` | `10000` | Default action timeout |
|
|
146
|
+
| `--retries <n>` | `0` | Retry failed tests N times |
|
|
147
|
+
| `--retry-delay <ms>` | `1000` | Delay between retries |
|
|
148
|
+
| `--test-timeout <ms>` | `60000` | Per-test timeout |
|
|
149
|
+
| `--output <format>` | `json` | Report format: `json`, `junit`, `both` |
|
|
150
|
+
| `--env <name>` | `default` | Environment profile |
|
|
151
|
+
| `--pool-port <port>` | `3333` | Chrome pool port |
|
|
152
|
+
| `--max-sessions <n>` | `10` | Max concurrent Chrome sessions |
|
|
153
|
+
|
|
154
|
+
## Configuration
|
|
155
|
+
|
|
156
|
+
Create `e2e.config.js` (or `e2e.config.json`) in your project root:
|
|
157
|
+
|
|
158
|
+
```js
|
|
159
|
+
export default {
|
|
160
|
+
baseUrl: 'http://host.docker.internal:3000',
|
|
161
|
+
concurrency: 4,
|
|
162
|
+
retries: 2,
|
|
163
|
+
testTimeout: 30000,
|
|
164
|
+
outputFormat: 'both',
|
|
165
|
+
|
|
166
|
+
hooks: {
|
|
167
|
+
beforeEach: [{ type: 'goto', value: '/' }],
|
|
168
|
+
afterEach: [{ type: 'screenshot', value: 'after-test.png' }],
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
environments: {
|
|
172
|
+
staging: { baseUrl: 'https://staging.example.com' },
|
|
173
|
+
production: { baseUrl: 'https://example.com', concurrency: 5 },
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Config Priority (highest wins)
|
|
179
|
+
|
|
180
|
+
1. CLI flags (`--base-url`, `--concurrency`, ...)
|
|
181
|
+
2. Environment variables (`BASE_URL`, `CONCURRENCY`, ...)
|
|
182
|
+
3. Config file (`e2e.config.js` or `e2e.config.json`)
|
|
183
|
+
4. Defaults
|
|
184
|
+
|
|
185
|
+
When `--env <name>` is set, the matching profile from `environments` overrides everything.
|
|
186
|
+
|
|
187
|
+
### Environment Variables
|
|
188
|
+
|
|
189
|
+
| Variable | Maps to |
|
|
190
|
+
|----------|---------|
|
|
191
|
+
| `BASE_URL` | `baseUrl` |
|
|
192
|
+
| `CHROME_POOL_URL` | `poolUrl` |
|
|
193
|
+
| `TESTS_DIR` | `testsDir` |
|
|
194
|
+
| `SCREENSHOTS_DIR` | `screenshotsDir` |
|
|
195
|
+
| `CONCURRENCY` | `concurrency` |
|
|
196
|
+
| `DEFAULT_TIMEOUT` | `defaultTimeout` |
|
|
197
|
+
| `POOL_PORT` | `poolPort` |
|
|
198
|
+
| `MAX_SESSIONS` | `maxSessions` |
|
|
199
|
+
| `RETRIES` | `retries` |
|
|
200
|
+
| `RETRY_DELAY` | `retryDelay` |
|
|
201
|
+
| `TEST_TIMEOUT` | `testTimeout` |
|
|
202
|
+
| `OUTPUT_FORMAT` | `outputFormat` |
|
|
203
|
+
| `E2E_ENV` | `env` |
|
|
204
|
+
|
|
205
|
+
## Hooks
|
|
206
|
+
|
|
207
|
+
Hooks run actions at lifecycle points. Define them globally in config or per-suite in the JSON file:
|
|
208
|
+
|
|
209
|
+
```json
|
|
210
|
+
{
|
|
211
|
+
"hooks": {
|
|
212
|
+
"beforeAll": [{ "type": "goto", "value": "/login" }],
|
|
213
|
+
"beforeEach": [{ "type": "goto", "value": "/" }],
|
|
214
|
+
"afterEach": [],
|
|
215
|
+
"afterAll": []
|
|
216
|
+
},
|
|
217
|
+
"tests": [
|
|
218
|
+
{ "name": "test-1", "actions": [...] }
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Suite-level hooks override global hooks per key (non-empty array wins). The plain array format (`[{ name, actions }]`) is still supported.
|
|
224
|
+
|
|
225
|
+
## Retries and Timeouts
|
|
226
|
+
|
|
227
|
+
Override globally or per-test:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"name": "flaky-test",
|
|
232
|
+
"retries": 3,
|
|
233
|
+
"timeout": 15000,
|
|
234
|
+
"actions": [...]
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- **Retries**: Each attempt gets its own fresh timeout. Tests that pass after retry are flagged as "flaky" in the report.
|
|
239
|
+
- **Timeout**: Applied via `Promise.race()`. Defaults to 60s.
|
|
240
|
+
|
|
241
|
+
## CI/CD
|
|
242
|
+
|
|
243
|
+
### JUnit XML
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
npx e2e-runner run --all --output junit
|
|
247
|
+
# or: --output both (JSON + XML)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Output saved to `e2e/screenshots/junit.xml`.
|
|
251
|
+
|
|
252
|
+
### GitHub Actions
|
|
253
|
+
|
|
254
|
+
```yaml
|
|
255
|
+
jobs:
|
|
256
|
+
e2e:
|
|
257
|
+
runs-on: ubuntu-latest
|
|
258
|
+
steps:
|
|
259
|
+
- uses: actions/checkout@v4
|
|
260
|
+
- uses: actions/setup-node@v4
|
|
261
|
+
with:
|
|
262
|
+
node-version: 20
|
|
263
|
+
- run: npm ci
|
|
264
|
+
- run: npx e2e-runner pool start
|
|
265
|
+
- run: npx e2e-runner run --all --output junit
|
|
266
|
+
- uses: mikepenz/action-junit-report@v4
|
|
267
|
+
if: always()
|
|
268
|
+
with:
|
|
269
|
+
report_paths: e2e/screenshots/junit.xml
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Exit Codes
|
|
273
|
+
|
|
274
|
+
| Code | Meaning |
|
|
275
|
+
|------|---------|
|
|
276
|
+
| `0` | All tests passed |
|
|
277
|
+
| `1` | One or more tests failed |
|
|
278
|
+
|
|
279
|
+
## Programmatic API
|
|
280
|
+
|
|
281
|
+
```js
|
|
282
|
+
import { createRunner } from '@matware/e2e-runner';
|
|
283
|
+
|
|
284
|
+
const runner = await createRunner({ baseUrl: 'http://localhost:3000' });
|
|
285
|
+
|
|
286
|
+
// Run all suites
|
|
287
|
+
const report = await runner.runAll();
|
|
288
|
+
|
|
289
|
+
// Run a specific suite
|
|
290
|
+
const report = await runner.runSuite('auth');
|
|
291
|
+
|
|
292
|
+
// Run a specific file
|
|
293
|
+
const report = await runner.runFile('e2e/tests/login.json');
|
|
294
|
+
|
|
295
|
+
// Run inline test objects
|
|
296
|
+
const report = await runner.runTests([
|
|
297
|
+
{
|
|
298
|
+
name: 'quick-check',
|
|
299
|
+
actions: [
|
|
300
|
+
{ type: 'goto', value: '/' },
|
|
301
|
+
{ type: 'assert_text', text: 'Hello' },
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
]);
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Lower-Level Exports
|
|
308
|
+
|
|
309
|
+
```js
|
|
310
|
+
import {
|
|
311
|
+
loadConfig,
|
|
312
|
+
waitForPool, connectToPool, getPoolStatus, startPool, stopPool,
|
|
313
|
+
runTest, runTestsParallel, loadTestFile, loadTestSuite, loadAllSuites, listSuites,
|
|
314
|
+
generateReport, generateJUnitXML, saveReport, printReport,
|
|
315
|
+
executeAction,
|
|
316
|
+
} from '@matware/e2e-runner';
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Claude Code Integration (MCP)
|
|
320
|
+
|
|
321
|
+
The package includes a built-in [MCP server](https://modelcontextprotocol.io/) that gives Claude Code native access to the test runner. Install once and it's available in every project:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
claude mcp add --transport stdio --scope user e2e-runner \
|
|
325
|
+
-- npx -y -p @matware/e2e-runner e2e-runner-mcp
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### MCP Tools
|
|
329
|
+
|
|
330
|
+
| Tool | Description |
|
|
331
|
+
|------|-------------|
|
|
332
|
+
| `e2e_run` | Run tests (all suites, by suite name, or by file path) |
|
|
333
|
+
| `e2e_list` | List available test suites with test names and counts |
|
|
334
|
+
| `e2e_create_test` | Create a new test JSON file |
|
|
335
|
+
| `e2e_pool_status` | Check Chrome pool availability and capacity |
|
|
336
|
+
| `e2e_pool_start` | Start the Chrome pool Docker container |
|
|
337
|
+
| `e2e_pool_stop` | Stop the Chrome pool |
|
|
338
|
+
|
|
339
|
+
Once installed, Claude Code can run tests, analyze failures, create new test files, and manage the Chrome pool as part of its normal workflow. Just ask:
|
|
340
|
+
|
|
341
|
+
> "Run all E2E tests"
|
|
342
|
+
> "Create a test that verifies the checkout flow"
|
|
343
|
+
> "What's the status of the Chrome pool?"
|
|
344
|
+
|
|
345
|
+
### Verify Installation
|
|
346
|
+
|
|
347
|
+
```bash
|
|
348
|
+
claude mcp list
|
|
349
|
+
# e2e-runner: ... - Connected
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
## Architecture
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
bin/cli.js CLI entry point (manual argv parsing)
|
|
356
|
+
bin/mcp-server.js MCP server entry point (stdio transport)
|
|
357
|
+
src/config.js Config cascade: defaults -> file -> env -> CLI -> profile
|
|
358
|
+
src/pool.js Chrome pool: Docker Compose lifecycle + WebSocket
|
|
359
|
+
src/runner.js Parallel test executor with retries and timeouts
|
|
360
|
+
src/actions.js Action engine: maps JSON actions to Puppeteer calls
|
|
361
|
+
src/reporter.js JSON reports, JUnit XML, console output
|
|
362
|
+
src/mcp-server.js MCP server: exposes tools for Claude Code
|
|
363
|
+
src/logger.js ANSI colored logger
|
|
364
|
+
src/index.js Programmatic API (createRunner)
|
|
365
|
+
templates/ Scaffolding templates for init command
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### How It Works
|
|
369
|
+
|
|
370
|
+
1. **Pool**: A Docker container running [browserless/chrome](https://github.com/browserless/browserless) provides shared Chrome instances via WebSocket.
|
|
371
|
+
2. **Runner**: Spawns N parallel workers. Each worker connects to the pool, opens a new page, and executes actions sequentially.
|
|
372
|
+
3. **Actions**: Each JSON action maps to a Puppeteer call (`page.goto`, `page.click`, `page.type`, etc.).
|
|
373
|
+
4. **Reports**: Results are collected, aggregated into a report, and saved as JSON and/or JUnit XML.
|
|
374
|
+
|
|
375
|
+
The `baseUrl` defaults to `http://host.docker.internal:3000` because Chrome runs inside Docker and needs to reach the host machine.
|
|
376
|
+
|
|
377
|
+
## Requirements
|
|
378
|
+
|
|
379
|
+
- **Node.js** >= 20
|
|
380
|
+
- **Docker** (for the Chrome pool)
|
|
381
|
+
|
|
382
|
+
## License
|
|
383
|
+
|
|
384
|
+
Copyright 2025 Matias Aguirre (fastslack)
|
|
385
|
+
|
|
386
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
387
|
+
you may not use this file except in compliance with the License.
|
|
388
|
+
You may obtain a copy of the License at
|
|
389
|
+
|
|
390
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
391
|
+
|
|
392
|
+
Unless required by applicable law or agreed to in writing, software
|
|
393
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
394
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
395
|
+
See the License for the specific language governing permissions and
|
|
396
|
+
limitations under the License.
|