@cotestdev/mcp_playwright 0.0.12 → 0.0.15

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 (48) hide show
  1. package/lib/mcp/browser/browserContextFactory.js +49 -13
  2. package/lib/mcp/browser/browserServerBackend.js +5 -2
  3. package/lib/mcp/browser/config.js +95 -23
  4. package/lib/mcp/browser/context.js +28 -3
  5. package/lib/mcp/browser/response.js +240 -57
  6. package/lib/mcp/browser/sessionLog.js +1 -1
  7. package/lib/mcp/browser/tab.js +96 -69
  8. package/lib/mcp/browser/tools/common.js +8 -8
  9. package/lib/mcp/browser/tools/console.js +6 -3
  10. package/lib/mcp/browser/tools/dialogs.js +13 -13
  11. package/lib/mcp/browser/tools/evaluate.js +9 -20
  12. package/lib/mcp/browser/tools/files.js +10 -5
  13. package/lib/mcp/browser/tools/form.js +11 -22
  14. package/lib/mcp/browser/tools/install.js +3 -3
  15. package/lib/mcp/browser/tools/keyboard.js +12 -12
  16. package/lib/mcp/browser/tools/mouse.js +14 -14
  17. package/lib/mcp/browser/tools/navigate.js +5 -5
  18. package/lib/mcp/browser/tools/network.js +16 -5
  19. package/lib/mcp/browser/tools/pdf.js +7 -18
  20. package/lib/mcp/browser/tools/runCode.js +77 -0
  21. package/lib/mcp/browser/tools/screenshot.js +44 -33
  22. package/lib/mcp/browser/tools/snapshot.js +42 -33
  23. package/lib/mcp/browser/tools/tabs.js +7 -10
  24. package/lib/mcp/browser/tools/tool.js +8 -7
  25. package/lib/mcp/browser/tools/tracing.js +4 -4
  26. package/lib/mcp/browser/tools/utils.js +50 -52
  27. package/lib/mcp/browser/tools/verify.js +23 -34
  28. package/lib/mcp/browser/tools/wait.js +6 -6
  29. package/lib/mcp/browser/tools.js +4 -3
  30. package/lib/mcp/cli.js +17 -17
  31. package/lib/mcp/extension/cdpRelay.js +1 -1
  32. package/lib/mcp/extension/extensionContextFactory.js +4 -3
  33. package/lib/mcp/log.js +2 -2
  34. package/lib/mcp/program.js +21 -29
  35. package/lib/mcp/sdk/exports.js +1 -5
  36. package/lib/mcp/sdk/http.js +37 -50
  37. package/lib/mcp/sdk/server.js +61 -9
  38. package/lib/mcp/sdk/tool.js +5 -4
  39. package/lib/mcp/test/browserBackend.js +67 -61
  40. package/lib/mcp/test/generatorTools.js +122 -0
  41. package/lib/mcp/test/plannerTools.js +144 -0
  42. package/lib/mcp/test/seed.js +82 -0
  43. package/lib/mcp/test/streams.js +10 -7
  44. package/lib/mcp/test/testBackend.js +44 -24
  45. package/lib/mcp/test/testContext.js +243 -14
  46. package/lib/mcp/test/testTools.js +23 -109
  47. package/lib/util.js +12 -6
  48. package/package.json +1 -1
@@ -30,36 +30,25 @@ var testTools_exports = {};
30
30
  __export(testTools_exports, {
31
31
  debugTest: () => debugTest,
32
32
  listTests: () => listTests,
33
- runTests: () => runTests,
34
- setupPage: () => setupPage
33
+ runTests: () => runTests
35
34
  });
36
35
  module.exports = __toCommonJS(testTools_exports);
37
- var import_fs = __toESM(require("fs"));
38
- var import_path = __toESM(require("path"));
39
- var import_utils = require("playwright-core/lib/utils");
40
- var import_bundle = require("../sdk/bundle");
41
- var import_base = require("../../reporters/base");
42
- var import_list = __toESM(require("../../reporters/list"));
36
+ var import_mcpBundle = require("playwright-core/lib/mcpBundle");
43
37
  var import_listModeReporter = __toESM(require("../../reporters/listModeReporter"));
44
- var import_projectUtils = require("../../runner/projectUtils");
45
38
  var import_testTool = require("./testTool");
46
- var import_streams = require("./streams");
47
39
  const listTests = (0, import_testTool.defineTestTool)({
48
40
  schema: {
49
41
  name: "test_list",
50
42
  title: "List tests",
51
43
  description: "List tests",
52
- inputSchema: import_bundle.z.object({}),
44
+ inputSchema: import_mcpBundle.z.object({}),
53
45
  type: "readOnly"
54
46
  },
55
47
  handle: async (context) => {
56
- const { screen, stream } = createScreen();
48
+ const { testRunner, screen, output } = await context.createTestRunner();
57
49
  const reporter = new import_listModeReporter.default({ screen, includeTestId: true });
58
- const testRunner = await context.createTestRunner();
59
50
  await testRunner.listTests(reporter, {});
60
- return {
61
- content: [{ type: "text", text: stream.content() }]
62
- };
51
+ return { content: output.map((text) => ({ type: "text", text })) };
63
52
  }
64
53
  });
65
54
  const runTests = (0, import_testTool.defineTestTool)({
@@ -67,28 +56,19 @@ const runTests = (0, import_testTool.defineTestTool)({
67
56
  name: "test_run",
68
57
  title: "Run tests",
69
58
  description: "Run tests",
70
- inputSchema: import_bundle.z.object({
71
- locations: import_bundle.z.array(import_bundle.z.string()).optional().describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
72
- projects: import_bundle.z.array(import_bundle.z.string()).optional().describe('Projects to run, projects from playwright.config.ts, by default runs all projects. Running with "chromium" is a good start')
59
+ inputSchema: import_mcpBundle.z.object({
60
+ locations: import_mcpBundle.z.array(import_mcpBundle.z.string()).optional().describe('Folder, file or location to run: "test/e2e" or "test/e2e/file.spec.ts" or "test/e2e/file.spec.ts:20"'),
61
+ projects: import_mcpBundle.z.array(import_mcpBundle.z.string()).optional().describe('Projects to run, projects from playwright.config.ts, by default runs all projects. Running with "chromium" is a good start')
73
62
  }),
74
63
  type: "readOnly"
75
64
  },
76
65
  handle: async (context, params) => {
77
- const { screen, stream } = createScreen();
78
- const configDir = context.configLocation.configDir;
79
- const reporter = new import_list.default({ configDir, screen, includeTestId: true });
80
- const testRunner = await context.createTestRunner();
81
- await testRunner.runTests(reporter, {
82
- locations: params.locations,
66
+ const { output } = await context.runTestsWithGlobalSetupAndPossiblePause({
67
+ locations: params.locations ?? [],
83
68
  projects: params.projects,
84
69
  disableConfigReporters: true
85
70
  });
86
- const text = stream.content();
87
- return {
88
- content: [
89
- { type: "text", text }
90
- ]
91
- };
71
+ return { content: [{ type: "text", text: output }] };
92
72
  }
93
73
  });
94
74
  const debugTest = (0, import_testTool.defineTestTool)({
@@ -96,99 +76,33 @@ const debugTest = (0, import_testTool.defineTestTool)({
96
76
  name: "test_debug",
97
77
  title: "Debug single test",
98
78
  description: "Debug single test",
99
- inputSchema: import_bundle.z.object({
100
- test: import_bundle.z.object({
101
- id: import_bundle.z.string().describe("Test ID to debug."),
102
- title: import_bundle.z.string().describe("Human readable test title for granting permission to debug the test.")
79
+ inputSchema: import_mcpBundle.z.object({
80
+ test: import_mcpBundle.z.object({
81
+ id: import_mcpBundle.z.string().describe("Test ID to debug."),
82
+ title: import_mcpBundle.z.string().describe("Human readable test title for granting permission to debug the test.")
103
83
  })
104
84
  }),
105
85
  type: "readOnly"
106
86
  },
107
87
  handle: async (context, params) => {
108
- const { screen, stream } = createScreen();
109
- const configDir = context.configLocation.configDir;
110
- const reporter = new import_list.default({ configDir, screen });
111
- const testRunner = await context.createTestRunner();
112
- const result = await testRunner.runTests(reporter, {
113
- headed: !context.options?.headless,
88
+ const { output, status } = await context.runTestsWithGlobalSetupAndPossiblePause({
89
+ headed: context.computedHeaded,
90
+ locations: [],
91
+ // we can make this faster by passing the test's location, so we don't need to scan all tests to find the ID
114
92
  testIds: [params.test.id],
115
93
  // For automatic recovery
116
94
  timeout: 0,
117
95
  workers: 1,
118
96
  pauseOnError: true,
119
- disableConfigReporters: true
120
- });
121
- const text = stream.content();
122
- return {
123
- content: [
124
- { type: "text", text }
125
- ],
126
- isError: result.status !== "passed"
127
- };
128
- }
129
- });
130
- const setupPage = (0, import_testTool.defineTestTool)({
131
- schema: {
132
- name: "test_setup_page",
133
- title: "Setup page",
134
- description: "Setup the page for test",
135
- inputSchema: import_bundle.z.object({
136
- project: import_bundle.z.string().optional().describe('Project to use for setup. For example: "chromium", if no project is provided uses the first project in the config.'),
137
- testLocation: import_bundle.z.string().optional().describe('Location of the seed test to use for setup. For example: "test/seed/default.spec.ts:20".')
138
- }),
139
- type: "readOnly"
140
- },
141
- handle: async (context, params) => {
142
- const { screen, stream } = createScreen();
143
- const configDir = context.configLocation.configDir;
144
- const reporter = new import_list.default({ configDir, screen });
145
- const testRunner = await context.createTestRunner();
146
- let testLocation = params.testLocation;
147
- if (!testLocation) {
148
- testLocation = "default.seed.spec.ts";
149
- const config = await testRunner.loadConfig();
150
- const project = params.project ? config.projects.find((p) => p.project.name === params.project) : (0, import_projectUtils.findTopLevelProjects)(config)[0];
151
- const testDir = project?.project.testDir || configDir;
152
- const seedFile = import_path.default.join(testDir, testLocation);
153
- if (!import_fs.default.existsSync(seedFile)) {
154
- await import_fs.default.promises.mkdir(import_path.default.dirname(seedFile), { recursive: true });
155
- await import_fs.default.promises.writeFile(seedFile, `import { test, expect } from '@playwright/test';
156
-
157
- test('seed', async ({ page }) => {});
158
- `);
159
- }
160
- }
161
- const result = await testRunner.runTests(reporter, {
162
- headed: !context.options?.headless,
163
- locations: [testLocation],
164
- projects: params.project ? [params.project] : void 0,
165
- timeout: 0,
166
- workers: 1,
167
- pauseAtEnd: true,
168
- disableConfigReporters: true
97
+ disableConfigReporters: true,
98
+ actionTimeout: 5e3
169
99
  });
170
- const text = stream.content();
171
- return {
172
- content: [{ type: "text", text }],
173
- isError: result.status !== "passed"
174
- };
100
+ return { content: [{ type: "text", text: output }], isError: status !== "paused" && status !== "passed" };
175
101
  }
176
102
  });
177
- function createScreen() {
178
- const stream = new import_streams.StringWriteStream();
179
- const screen = {
180
- ...import_base.terminalScreen,
181
- isTTY: false,
182
- colors: import_utils.noColors,
183
- stdout: stream,
184
- stderr: stream
185
- };
186
- return { screen, stream };
187
- }
188
103
  // Annotate the CommonJS export names for ESM import in node:
189
104
  0 && (module.exports = {
190
105
  debugTest,
191
106
  listTests,
192
- runTests,
193
- setupPage
107
+ runTests
194
108
  });
package/lib/util.js CHANGED
@@ -48,6 +48,7 @@ __export(util_exports, {
48
48
  getPackageJsonPath: () => getPackageJsonPath,
49
49
  mergeObjects: () => mergeObjects,
50
50
  normalizeAndSaveAttachment: () => normalizeAndSaveAttachment,
51
+ parseLocationArg: () => parseLocationArg,
51
52
  relativeFilePath: () => relativeFilePath,
52
53
  removeDirAndLogToConsole: () => removeDirAndLogToConsole,
53
54
  resolveImportSpecifierAfterMapping: () => resolveImportSpecifierAfterMapping,
@@ -105,14 +106,18 @@ function serializeError(error) {
105
106
  value: import_util.default.inspect(error)
106
107
  };
107
108
  }
109
+ function parseLocationArg(arg) {
110
+ const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg);
111
+ return {
112
+ file: match ? match[1] : arg,
113
+ line: match ? parseInt(match[2], 10) : null,
114
+ column: match?.[3] ? parseInt(match[3], 10) : null
115
+ };
116
+ }
108
117
  function createFileFiltersFromArguments(args) {
109
118
  return args.map((arg) => {
110
- const match = /^(.*?):(\d+):?(\d+)?$/.exec(arg);
111
- return {
112
- re: forceRegExp(match ? match[1] : arg),
113
- line: match ? parseInt(match[2], 10) : null,
114
- column: match?.[3] ? parseInt(match[3], 10) : null
115
- };
119
+ const parsed = parseLocationArg(arg);
120
+ return { re: forceRegExp(parsed.file), line: parsed.line, column: parsed.column };
116
121
  });
117
122
  }
118
123
  function createFileMatcherFromArguments(args) {
@@ -385,6 +390,7 @@ function stripAnsiEscapes(str) {
385
390
  getPackageJsonPath,
386
391
  mergeObjects,
387
392
  normalizeAndSaveAttachment,
393
+ parseLocationArg,
388
394
  relativeFilePath,
389
395
  removeDirAndLogToConsole,
390
396
  resolveImportSpecifierAfterMapping,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cotestdev/mcp_playwright",
3
- "version": "0.0.12",
3
+ "version": "0.0.15",
4
4
  "description": "Playwright MCP (Model Context Protocol) tools for browser automation",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",