@matware/e2e-runner 1.0.0 → 1.0.2
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 +12 -1
- package/package.json +2 -1
- package/src/config.js +2 -2
- package/src/mcp-server.js +46 -18
- package/src/pool.js +8 -7
package/README.md
CHANGED
|
@@ -318,13 +318,22 @@ import {
|
|
|
318
318
|
|
|
319
319
|
## Claude Code Integration (MCP)
|
|
320
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
|
|
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
|
+
**Via npm** (requires Node.js):
|
|
322
324
|
|
|
323
325
|
```bash
|
|
324
326
|
claude mcp add --transport stdio --scope user e2e-runner \
|
|
325
327
|
-- npx -y -p @matware/e2e-runner e2e-runner-mcp
|
|
326
328
|
```
|
|
327
329
|
|
|
330
|
+
**Via Docker** (no Node.js required):
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
claude mcp add --transport stdio --scope user e2e-runner \
|
|
334
|
+
-- docker run -i --rm fastslack/e2e-runner-mcp
|
|
335
|
+
```
|
|
336
|
+
|
|
328
337
|
### MCP Tools
|
|
329
338
|
|
|
330
339
|
| Tool | Description |
|
|
@@ -336,6 +345,8 @@ claude mcp add --transport stdio --scope user e2e-runner \
|
|
|
336
345
|
| `e2e_pool_start` | Start the Chrome pool Docker container |
|
|
337
346
|
| `e2e_pool_stop` | Stop the Chrome pool |
|
|
338
347
|
|
|
348
|
+
All tools accept an optional `cwd` parameter (absolute path to the project root). Claude Code passes its current working directory so the MCP server resolves `e2e/tests/`, `e2e.config.js`, and `.e2e-pool/` relative to the correct project — even when switching between multiple projects in the same session.
|
|
349
|
+
|
|
339
350
|
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
351
|
|
|
341
352
|
> "Run all E2E tests"
|
package/package.json
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@matware/e2e-runner",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"mcpName": "io.github.fastslack/e2e-runner",
|
|
4
5
|
"description": "E2E test runner using Chrome Pool (browserless/chrome) with parallel execution",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "src/index.js",
|
package/src/config.js
CHANGED
|
@@ -68,8 +68,8 @@ async function loadConfigFile(cwd) {
|
|
|
68
68
|
return {};
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export async function loadConfig(cliArgs = {}) {
|
|
72
|
-
|
|
71
|
+
export async function loadConfig(cliArgs = {}, cwd = null) {
|
|
72
|
+
cwd = cwd || process.cwd();
|
|
73
73
|
const fileConfig = await loadConfigFile(cwd);
|
|
74
74
|
const envConfig = loadEnvVars();
|
|
75
75
|
|
package/src/mcp-server.js
CHANGED
|
@@ -61,6 +61,10 @@ const TOOLS = [
|
|
|
61
61
|
type: 'number',
|
|
62
62
|
description: 'Number of retries for failed tests',
|
|
63
63
|
},
|
|
64
|
+
cwd: {
|
|
65
|
+
type: 'string',
|
|
66
|
+
description: 'Absolute path to the project root directory. Claude Code should pass its current working directory.',
|
|
67
|
+
},
|
|
64
68
|
},
|
|
65
69
|
},
|
|
66
70
|
},
|
|
@@ -70,7 +74,12 @@ const TOOLS = [
|
|
|
70
74
|
'List all available E2E test suites with their test names and counts.',
|
|
71
75
|
inputSchema: {
|
|
72
76
|
type: 'object',
|
|
73
|
-
properties: {
|
|
77
|
+
properties: {
|
|
78
|
+
cwd: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'Absolute path to the project root directory. Claude Code should pass its current working directory.',
|
|
81
|
+
},
|
|
82
|
+
},
|
|
74
83
|
},
|
|
75
84
|
},
|
|
76
85
|
{
|
|
@@ -122,6 +131,10 @@ const TOOLS = [
|
|
|
122
131
|
afterEach: { type: 'array', items: { type: 'object' } },
|
|
123
132
|
},
|
|
124
133
|
},
|
|
134
|
+
cwd: {
|
|
135
|
+
type: 'string',
|
|
136
|
+
description: 'Absolute path to the project root directory. Claude Code should pass its current working directory.',
|
|
137
|
+
},
|
|
125
138
|
},
|
|
126
139
|
required: ['name', 'tests'],
|
|
127
140
|
},
|
|
@@ -132,7 +145,12 @@ const TOOLS = [
|
|
|
132
145
|
'Get the status of the Chrome pool (browserless/chrome). Shows availability, running sessions, capacity, and queued requests.',
|
|
133
146
|
inputSchema: {
|
|
134
147
|
type: 'object',
|
|
135
|
-
properties: {
|
|
148
|
+
properties: {
|
|
149
|
+
cwd: {
|
|
150
|
+
type: 'string',
|
|
151
|
+
description: 'Absolute path to the project root directory. Claude Code should pass its current working directory.',
|
|
152
|
+
},
|
|
153
|
+
},
|
|
136
154
|
},
|
|
137
155
|
},
|
|
138
156
|
{
|
|
@@ -150,6 +168,10 @@ const TOOLS = [
|
|
|
150
168
|
type: 'number',
|
|
151
169
|
description: 'Max concurrent Chrome sessions (default 10)',
|
|
152
170
|
},
|
|
171
|
+
cwd: {
|
|
172
|
+
type: 'string',
|
|
173
|
+
description: 'Absolute path to the project root directory. Claude Code should pass its current working directory.',
|
|
174
|
+
},
|
|
153
175
|
},
|
|
154
176
|
},
|
|
155
177
|
},
|
|
@@ -158,7 +180,12 @@ const TOOLS = [
|
|
|
158
180
|
description: 'Stop the Chrome pool Docker container.',
|
|
159
181
|
inputSchema: {
|
|
160
182
|
type: 'object',
|
|
161
|
-
properties: {
|
|
183
|
+
properties: {
|
|
184
|
+
cwd: {
|
|
185
|
+
type: 'string',
|
|
186
|
+
description: 'Absolute path to the project root directory. Claude Code should pass its current working directory.',
|
|
187
|
+
},
|
|
188
|
+
},
|
|
162
189
|
},
|
|
163
190
|
},
|
|
164
191
|
];
|
|
@@ -171,7 +198,7 @@ async function handleRun(args) {
|
|
|
171
198
|
if (args.baseUrl) configOverrides.baseUrl = args.baseUrl;
|
|
172
199
|
if (args.retries !== undefined) configOverrides.retries = args.retries;
|
|
173
200
|
|
|
174
|
-
const config = await loadConfig(configOverrides);
|
|
201
|
+
const config = await loadConfig(configOverrides, args.cwd);
|
|
175
202
|
|
|
176
203
|
await waitForPool(config.poolUrl);
|
|
177
204
|
|
|
@@ -182,7 +209,8 @@ async function handleRun(args) {
|
|
|
182
209
|
} else if (args.suite) {
|
|
183
210
|
({ tests, hooks } = loadTestSuite(args.suite, config.testsDir));
|
|
184
211
|
} else if (args.file) {
|
|
185
|
-
const
|
|
212
|
+
const cwd = args.cwd || process.cwd();
|
|
213
|
+
const filePath = path.isAbsolute(args.file) ? args.file : path.resolve(cwd, args.file);
|
|
186
214
|
({ tests, hooks } = loadTestFile(filePath));
|
|
187
215
|
} else {
|
|
188
216
|
return errorResult('Provide one of: all (true), suite (name), or file (path)');
|
|
@@ -219,8 +247,8 @@ async function handleRun(args) {
|
|
|
219
247
|
return textResult(JSON.stringify(summary, null, 2));
|
|
220
248
|
}
|
|
221
249
|
|
|
222
|
-
async function handleList() {
|
|
223
|
-
const config = await loadConfig({});
|
|
250
|
+
async function handleList(args) {
|
|
251
|
+
const config = await loadConfig({}, args.cwd);
|
|
224
252
|
const suites = listSuites(config.testsDir);
|
|
225
253
|
|
|
226
254
|
if (suites.length === 0) {
|
|
@@ -235,7 +263,7 @@ async function handleList() {
|
|
|
235
263
|
}
|
|
236
264
|
|
|
237
265
|
async function handleCreateTest(args) {
|
|
238
|
-
const config = await loadConfig({});
|
|
266
|
+
const config = await loadConfig({}, args.cwd);
|
|
239
267
|
|
|
240
268
|
if (!fs.existsSync(config.testsDir)) {
|
|
241
269
|
fs.mkdirSync(config.testsDir, { recursive: true });
|
|
@@ -259,8 +287,8 @@ async function handleCreateTest(args) {
|
|
|
259
287
|
return textResult(`Created test file: ${filePath}\n\n${args.tests.length} test(s) defined.`);
|
|
260
288
|
}
|
|
261
289
|
|
|
262
|
-
async function handlePoolStatus() {
|
|
263
|
-
const config = await loadConfig({});
|
|
290
|
+
async function handlePoolStatus(args) {
|
|
291
|
+
const config = await loadConfig({}, args.cwd);
|
|
264
292
|
const status = await getPoolStatus(config.poolUrl);
|
|
265
293
|
|
|
266
294
|
const lines = [
|
|
@@ -282,14 +310,14 @@ async function handlePoolStart(args) {
|
|
|
282
310
|
if (args.port) overrides.poolPort = args.port;
|
|
283
311
|
if (args.maxSessions) overrides.maxSessions = args.maxSessions;
|
|
284
312
|
|
|
285
|
-
const config = await loadConfig(overrides);
|
|
286
|
-
startPool(config);
|
|
313
|
+
const config = await loadConfig(overrides, args.cwd);
|
|
314
|
+
startPool(config, args.cwd);
|
|
287
315
|
return textResult(`Chrome pool started on port ${config.poolPort}`);
|
|
288
316
|
}
|
|
289
317
|
|
|
290
|
-
async function handlePoolStop() {
|
|
291
|
-
const config = await loadConfig({});
|
|
292
|
-
stopPool(config);
|
|
318
|
+
async function handlePoolStop(args) {
|
|
319
|
+
const config = await loadConfig({}, args.cwd);
|
|
320
|
+
stopPool(config, args.cwd);
|
|
293
321
|
return textResult('Chrome pool stopped');
|
|
294
322
|
}
|
|
295
323
|
|
|
@@ -323,15 +351,15 @@ export async function startMcpServer() {
|
|
|
323
351
|
case 'e2e_run':
|
|
324
352
|
return await handleRun(args);
|
|
325
353
|
case 'e2e_list':
|
|
326
|
-
return await handleList();
|
|
354
|
+
return await handleList(args);
|
|
327
355
|
case 'e2e_create_test':
|
|
328
356
|
return await handleCreateTest(args);
|
|
329
357
|
case 'e2e_pool_status':
|
|
330
|
-
return await handlePoolStatus();
|
|
358
|
+
return await handlePoolStatus(args);
|
|
331
359
|
case 'e2e_pool_start':
|
|
332
360
|
return await handlePoolStart(args);
|
|
333
361
|
case 'e2e_pool_stop':
|
|
334
|
-
return await handlePoolStop();
|
|
362
|
+
return await handlePoolStop(args);
|
|
335
363
|
default:
|
|
336
364
|
return errorResult(`Unknown tool: ${name}`);
|
|
337
365
|
}
|
package/src/pool.js
CHANGED
|
@@ -61,8 +61,8 @@ export async function connectToPool(poolUrl, retries = 3, delay = 2000) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/** Generates docker-compose.yml and starts the pool */
|
|
64
|
-
export function startPool(config) {
|
|
65
|
-
|
|
64
|
+
export function startPool(config, cwd = null) {
|
|
65
|
+
cwd = cwd || process.cwd();
|
|
66
66
|
const poolDir = path.join(cwd, '.e2e-pool');
|
|
67
67
|
|
|
68
68
|
if (!fs.existsSync(poolDir)) {
|
|
@@ -93,8 +93,9 @@ export function startPool(config) {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
/** Stops the pool */
|
|
96
|
-
export function stopPool(config) {
|
|
97
|
-
|
|
96
|
+
export function stopPool(config, cwd = null) {
|
|
97
|
+
cwd = cwd || process.cwd();
|
|
98
|
+
const composePath = path.join(cwd, '.e2e-pool', 'docker-compose.yml');
|
|
98
99
|
if (!fs.existsSync(composePath)) {
|
|
99
100
|
log('⚠️', '.e2e-pool/docker-compose.yml not found');
|
|
100
101
|
return;
|
|
@@ -106,9 +107,9 @@ export function stopPool(config) {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
/** Restarts the pool */
|
|
109
|
-
export function restartPool(config) {
|
|
110
|
-
stopPool(config);
|
|
111
|
-
startPool(config);
|
|
110
|
+
export function restartPool(config, cwd = null) {
|
|
111
|
+
stopPool(config, cwd);
|
|
112
|
+
startPool(config, cwd);
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
/** Gets pool status */
|