@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 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.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
- const cwd = process.cwd();
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 filePath = path.isAbsolute(args.file) ? args.file : path.resolve(args.file);
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
- const cwd = process.cwd();
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
- const composePath = path.join(process.cwd(), '.e2e-pool', 'docker-compose.yml');
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 */