@playwright/mcp 0.0.18 → 0.0.19
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 +14 -2
- package/cli.js +1 -1
- package/config.d.ts +1 -1
- package/index.js +2 -2
- package/lib/config.js +23 -31
- package/lib/context.js +36 -48
- package/lib/index.js +41 -44
- package/lib/javascript.js +3 -8
- package/lib/manualPromise.js +2 -7
- package/lib/pageSnapshot.js +6 -13
- package/lib/program.js +12 -15
- package/lib/resources/resource.js +1 -2
- package/lib/server.js +11 -16
- package/lib/tab.js +6 -7
- package/lib/tools/common.js +12 -14
- package/lib/tools/console.js +5 -7
- package/lib/tools/dialogs.js +7 -9
- package/lib/tools/files.js +6 -8
- package/lib/tools/install.js +9 -14
- package/lib/tools/keyboard.js +6 -8
- package/lib/tools/navigate.js +10 -12
- package/lib/tools/network.js +5 -7
- package/lib/tools/pdf.js +8 -43
- package/lib/tools/screen.js +23 -58
- package/lib/tools/snapshot.js +31 -67
- package/lib/tools/tabs.js +14 -16
- package/lib/tools/testing.js +58 -0
- package/lib/tools/tool.js +1 -4
- package/lib/tools/utils.js +7 -7
- package/lib/transport.js +31 -31
- package/package.json +4 -3
package/lib/tools/screen.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Copyright (c) Microsoft Corporation.
|
|
4
3
|
*
|
|
@@ -14,52 +13,18 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
Object.defineProperty(o, k2, desc);
|
|
24
|
-
}) : (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
o[k2] = m[k];
|
|
27
|
-
}));
|
|
28
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
-
}) : function(o, v) {
|
|
31
|
-
o["default"] = v;
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
import * as javascript from '../javascript.js';
|
|
19
|
+
const elementSchema = z.object({
|
|
20
|
+
element: z.string().describe('Human-readable element description used to obtain permission to interact with the element'),
|
|
32
21
|
});
|
|
33
|
-
|
|
34
|
-
var ownKeys = function(o) {
|
|
35
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
-
var ar = [];
|
|
37
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
-
return ar;
|
|
39
|
-
};
|
|
40
|
-
return ownKeys(o);
|
|
41
|
-
};
|
|
42
|
-
return function (mod) {
|
|
43
|
-
if (mod && mod.__esModule) return mod;
|
|
44
|
-
var result = {};
|
|
45
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
-
__setModuleDefault(result, mod);
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
49
|
-
})();
|
|
50
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
-
const zod_1 = require("zod");
|
|
52
|
-
const tool_1 = require("./tool");
|
|
53
|
-
const javascript = __importStar(require("../javascript"));
|
|
54
|
-
const elementSchema = zod_1.z.object({
|
|
55
|
-
element: zod_1.z.string().describe('Human-readable element description used to obtain permission to interact with the element'),
|
|
56
|
-
});
|
|
57
|
-
const screenshot = (0, tool_1.defineTool)({
|
|
22
|
+
const screenshot = defineTool({
|
|
58
23
|
capability: 'core',
|
|
59
24
|
schema: {
|
|
60
25
|
name: 'browser_screen_capture',
|
|
61
26
|
description: 'Take a screenshot of the current page',
|
|
62
|
-
inputSchema:
|
|
27
|
+
inputSchema: z.object({}),
|
|
63
28
|
},
|
|
64
29
|
handle: async (context) => {
|
|
65
30
|
const tab = await context.ensureTab();
|
|
@@ -81,14 +46,14 @@ const screenshot = (0, tool_1.defineTool)({
|
|
|
81
46
|
};
|
|
82
47
|
},
|
|
83
48
|
});
|
|
84
|
-
const moveMouse =
|
|
49
|
+
const moveMouse = defineTool({
|
|
85
50
|
capability: 'core',
|
|
86
51
|
schema: {
|
|
87
52
|
name: 'browser_screen_move_mouse',
|
|
88
53
|
description: 'Move mouse to a given position',
|
|
89
54
|
inputSchema: elementSchema.extend({
|
|
90
|
-
x:
|
|
91
|
-
y:
|
|
55
|
+
x: z.number().describe('X coordinate'),
|
|
56
|
+
y: z.number().describe('Y coordinate'),
|
|
92
57
|
}),
|
|
93
58
|
},
|
|
94
59
|
handle: async (context, params) => {
|
|
@@ -106,14 +71,14 @@ const moveMouse = (0, tool_1.defineTool)({
|
|
|
106
71
|
};
|
|
107
72
|
},
|
|
108
73
|
});
|
|
109
|
-
const click =
|
|
74
|
+
const click = defineTool({
|
|
110
75
|
capability: 'core',
|
|
111
76
|
schema: {
|
|
112
77
|
name: 'browser_screen_click',
|
|
113
78
|
description: 'Click left mouse button',
|
|
114
79
|
inputSchema: elementSchema.extend({
|
|
115
|
-
x:
|
|
116
|
-
y:
|
|
80
|
+
x: z.number().describe('X coordinate'),
|
|
81
|
+
y: z.number().describe('Y coordinate'),
|
|
117
82
|
}),
|
|
118
83
|
},
|
|
119
84
|
handle: async (context, params) => {
|
|
@@ -137,16 +102,16 @@ const click = (0, tool_1.defineTool)({
|
|
|
137
102
|
};
|
|
138
103
|
},
|
|
139
104
|
});
|
|
140
|
-
const drag =
|
|
105
|
+
const drag = defineTool({
|
|
141
106
|
capability: 'core',
|
|
142
107
|
schema: {
|
|
143
108
|
name: 'browser_screen_drag',
|
|
144
109
|
description: 'Drag left mouse button',
|
|
145
110
|
inputSchema: elementSchema.extend({
|
|
146
|
-
startX:
|
|
147
|
-
startY:
|
|
148
|
-
endX:
|
|
149
|
-
endY:
|
|
111
|
+
startX: z.number().describe('Start X coordinate'),
|
|
112
|
+
startY: z.number().describe('Start Y coordinate'),
|
|
113
|
+
endX: z.number().describe('End X coordinate'),
|
|
114
|
+
endY: z.number().describe('End Y coordinate'),
|
|
150
115
|
}),
|
|
151
116
|
},
|
|
152
117
|
handle: async (context, params) => {
|
|
@@ -172,14 +137,14 @@ const drag = (0, tool_1.defineTool)({
|
|
|
172
137
|
};
|
|
173
138
|
},
|
|
174
139
|
});
|
|
175
|
-
const type =
|
|
140
|
+
const type = defineTool({
|
|
176
141
|
capability: 'core',
|
|
177
142
|
schema: {
|
|
178
143
|
name: 'browser_screen_type',
|
|
179
144
|
description: 'Type text',
|
|
180
|
-
inputSchema:
|
|
181
|
-
text:
|
|
182
|
-
submit:
|
|
145
|
+
inputSchema: z.object({
|
|
146
|
+
text: z.string().describe('Text to type into the element'),
|
|
147
|
+
submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'),
|
|
183
148
|
}),
|
|
184
149
|
},
|
|
185
150
|
handle: async (context, params) => {
|
|
@@ -205,7 +170,7 @@ const type = (0, tool_1.defineTool)({
|
|
|
205
170
|
};
|
|
206
171
|
},
|
|
207
172
|
});
|
|
208
|
-
|
|
173
|
+
export default [
|
|
209
174
|
screenshot,
|
|
210
175
|
moveMouse,
|
|
211
176
|
click,
|
package/lib/tools/snapshot.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Copyright (c) Microsoft Corporation.
|
|
4
3
|
*
|
|
@@ -14,51 +13,16 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
Object.defineProperty(o, k2, desc);
|
|
24
|
-
}) : (function(o, m, k, k2) {
|
|
25
|
-
if (k2 === undefined) k2 = k;
|
|
26
|
-
o[k2] = m[k];
|
|
27
|
-
}));
|
|
28
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
29
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
30
|
-
}) : function(o, v) {
|
|
31
|
-
o["default"] = v;
|
|
32
|
-
});
|
|
33
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
34
|
-
var ownKeys = function(o) {
|
|
35
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
36
|
-
var ar = [];
|
|
37
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
38
|
-
return ar;
|
|
39
|
-
};
|
|
40
|
-
return ownKeys(o);
|
|
41
|
-
};
|
|
42
|
-
return function (mod) {
|
|
43
|
-
if (mod && mod.__esModule) return mod;
|
|
44
|
-
var result = {};
|
|
45
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
46
|
-
__setModuleDefault(result, mod);
|
|
47
|
-
return result;
|
|
48
|
-
};
|
|
49
|
-
})();
|
|
50
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
51
|
-
exports.generateLocator = generateLocator;
|
|
52
|
-
const zod_1 = require("zod");
|
|
53
|
-
const tool_1 = require("./tool");
|
|
54
|
-
const javascript = __importStar(require("../javascript"));
|
|
55
|
-
const config_1 = require("../config");
|
|
56
|
-
const snapshot = (0, tool_1.defineTool)({
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
import * as javascript from '../javascript.js';
|
|
19
|
+
import { outputFile } from '../config.js';
|
|
20
|
+
const snapshot = defineTool({
|
|
57
21
|
capability: 'core',
|
|
58
22
|
schema: {
|
|
59
23
|
name: 'browser_snapshot',
|
|
60
24
|
description: 'Capture accessibility snapshot of the current page, this is better than screenshot',
|
|
61
|
-
inputSchema:
|
|
25
|
+
inputSchema: z.object({}),
|
|
62
26
|
},
|
|
63
27
|
handle: async (context) => {
|
|
64
28
|
await context.ensureTab();
|
|
@@ -69,11 +33,11 @@ const snapshot = (0, tool_1.defineTool)({
|
|
|
69
33
|
};
|
|
70
34
|
},
|
|
71
35
|
});
|
|
72
|
-
const elementSchema =
|
|
73
|
-
element:
|
|
74
|
-
ref:
|
|
36
|
+
const elementSchema = z.object({
|
|
37
|
+
element: z.string().describe('Human-readable element description used to obtain permission to interact with the element'),
|
|
38
|
+
ref: z.string().describe('Exact target element reference from the page snapshot'),
|
|
75
39
|
});
|
|
76
|
-
const click =
|
|
40
|
+
const click = defineTool({
|
|
77
41
|
capability: 'core',
|
|
78
42
|
schema: {
|
|
79
43
|
name: 'browser_click',
|
|
@@ -95,16 +59,16 @@ const click = (0, tool_1.defineTool)({
|
|
|
95
59
|
};
|
|
96
60
|
},
|
|
97
61
|
});
|
|
98
|
-
const drag =
|
|
62
|
+
const drag = defineTool({
|
|
99
63
|
capability: 'core',
|
|
100
64
|
schema: {
|
|
101
65
|
name: 'browser_drag',
|
|
102
66
|
description: 'Perform drag and drop between two elements',
|
|
103
|
-
inputSchema:
|
|
104
|
-
startElement:
|
|
105
|
-
startRef:
|
|
106
|
-
endElement:
|
|
107
|
-
endRef:
|
|
67
|
+
inputSchema: z.object({
|
|
68
|
+
startElement: z.string().describe('Human-readable source element description used to obtain the permission to interact with the element'),
|
|
69
|
+
startRef: z.string().describe('Exact source element reference from the page snapshot'),
|
|
70
|
+
endElement: z.string().describe('Human-readable target element description used to obtain the permission to interact with the element'),
|
|
71
|
+
endRef: z.string().describe('Exact target element reference from the page snapshot'),
|
|
108
72
|
}),
|
|
109
73
|
},
|
|
110
74
|
handle: async (context, params) => {
|
|
@@ -123,7 +87,7 @@ const drag = (0, tool_1.defineTool)({
|
|
|
123
87
|
};
|
|
124
88
|
},
|
|
125
89
|
});
|
|
126
|
-
const hover =
|
|
90
|
+
const hover = defineTool({
|
|
127
91
|
capability: 'core',
|
|
128
92
|
schema: {
|
|
129
93
|
name: 'browser_hover',
|
|
@@ -146,11 +110,11 @@ const hover = (0, tool_1.defineTool)({
|
|
|
146
110
|
},
|
|
147
111
|
});
|
|
148
112
|
const typeSchema = elementSchema.extend({
|
|
149
|
-
text:
|
|
150
|
-
submit:
|
|
151
|
-
slowly:
|
|
113
|
+
text: z.string().describe('Text to type into the element'),
|
|
114
|
+
submit: z.boolean().optional().describe('Whether to submit entered text (press Enter after)'),
|
|
115
|
+
slowly: z.boolean().optional().describe('Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once.'),
|
|
152
116
|
});
|
|
153
|
-
const type =
|
|
117
|
+
const type = defineTool({
|
|
154
118
|
capability: 'core',
|
|
155
119
|
schema: {
|
|
156
120
|
name: 'browser_type',
|
|
@@ -186,9 +150,9 @@ const type = (0, tool_1.defineTool)({
|
|
|
186
150
|
},
|
|
187
151
|
});
|
|
188
152
|
const selectOptionSchema = elementSchema.extend({
|
|
189
|
-
values:
|
|
153
|
+
values: z.array(z.string()).describe('Array of values to select in the dropdown. This can be a single value or multiple values.'),
|
|
190
154
|
});
|
|
191
|
-
const selectOption =
|
|
155
|
+
const selectOption = defineTool({
|
|
192
156
|
capability: 'core',
|
|
193
157
|
schema: {
|
|
194
158
|
name: 'browser_select_option',
|
|
@@ -210,17 +174,17 @@ const selectOption = (0, tool_1.defineTool)({
|
|
|
210
174
|
};
|
|
211
175
|
},
|
|
212
176
|
});
|
|
213
|
-
const screenshotSchema =
|
|
214
|
-
raw:
|
|
215
|
-
element:
|
|
216
|
-
ref:
|
|
177
|
+
const screenshotSchema = z.object({
|
|
178
|
+
raw: z.boolean().optional().describe('Whether to return without compression (in PNG format). Default is false, which returns a JPEG image.'),
|
|
179
|
+
element: z.string().optional().describe('Human-readable element description used to obtain permission to screenshot the element. If not provided, the screenshot will be taken of viewport. If element is provided, ref must be provided too.'),
|
|
180
|
+
ref: z.string().optional().describe('Exact target element reference from the page snapshot. If not provided, the screenshot will be taken of viewport. If ref is provided, element must be provided too.'),
|
|
217
181
|
}).refine(data => {
|
|
218
182
|
return !!data.element === !!data.ref;
|
|
219
183
|
}, {
|
|
220
184
|
message: 'Both element and ref must be provided or neither.',
|
|
221
185
|
path: ['ref', 'element']
|
|
222
186
|
});
|
|
223
|
-
const screenshot =
|
|
187
|
+
const screenshot = defineTool({
|
|
224
188
|
capability: 'core',
|
|
225
189
|
schema: {
|
|
226
190
|
name: 'browser_take_screenshot',
|
|
@@ -231,7 +195,7 @@ const screenshot = (0, tool_1.defineTool)({
|
|
|
231
195
|
const tab = context.currentTabOrDie();
|
|
232
196
|
const snapshot = tab.snapshotOrDie();
|
|
233
197
|
const fileType = params.raw ? 'png' : 'jpeg';
|
|
234
|
-
const fileName = await
|
|
198
|
+
const fileName = await outputFile(context.config, `page-${new Date().toISOString()}.${fileType}`);
|
|
235
199
|
const options = { type: fileType, quality: fileType === 'png' ? undefined : 50, scale: 'css', path: fileName };
|
|
236
200
|
const isElementScreenshot = params.element && params.ref;
|
|
237
201
|
const code = [
|
|
@@ -261,10 +225,10 @@ const screenshot = (0, tool_1.defineTool)({
|
|
|
261
225
|
};
|
|
262
226
|
}
|
|
263
227
|
});
|
|
264
|
-
async function generateLocator(locator) {
|
|
228
|
+
export async function generateLocator(locator) {
|
|
265
229
|
return locator._generateLocatorString();
|
|
266
230
|
}
|
|
267
|
-
|
|
231
|
+
export default [
|
|
268
232
|
snapshot,
|
|
269
233
|
click,
|
|
270
234
|
drag,
|
package/lib/tools/tabs.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Copyright (c) Microsoft Corporation.
|
|
4
3
|
*
|
|
@@ -14,15 +13,14 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const listTabs = (0, tool_1.defineTool)({
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
const listTabs = defineTool({
|
|
21
19
|
capability: 'tabs',
|
|
22
20
|
schema: {
|
|
23
21
|
name: 'browser_tab_list',
|
|
24
22
|
description: 'List browser tabs',
|
|
25
|
-
inputSchema:
|
|
23
|
+
inputSchema: z.object({}),
|
|
26
24
|
},
|
|
27
25
|
handle: async (context) => {
|
|
28
26
|
await context.ensureTab();
|
|
@@ -39,13 +37,13 @@ const listTabs = (0, tool_1.defineTool)({
|
|
|
39
37
|
};
|
|
40
38
|
},
|
|
41
39
|
});
|
|
42
|
-
const selectTab = captureSnapshot =>
|
|
40
|
+
const selectTab = captureSnapshot => defineTool({
|
|
43
41
|
capability: 'tabs',
|
|
44
42
|
schema: {
|
|
45
43
|
name: 'browser_tab_select',
|
|
46
44
|
description: 'Select a tab by index',
|
|
47
|
-
inputSchema:
|
|
48
|
-
index:
|
|
45
|
+
inputSchema: z.object({
|
|
46
|
+
index: z.number().describe('The index of the tab to select'),
|
|
49
47
|
}),
|
|
50
48
|
},
|
|
51
49
|
handle: async (context, params) => {
|
|
@@ -60,13 +58,13 @@ const selectTab = captureSnapshot => (0, tool_1.defineTool)({
|
|
|
60
58
|
};
|
|
61
59
|
},
|
|
62
60
|
});
|
|
63
|
-
const newTab = captureSnapshot =>
|
|
61
|
+
const newTab = captureSnapshot => defineTool({
|
|
64
62
|
capability: 'tabs',
|
|
65
63
|
schema: {
|
|
66
64
|
name: 'browser_tab_new',
|
|
67
65
|
description: 'Open a new tab',
|
|
68
|
-
inputSchema:
|
|
69
|
-
url:
|
|
66
|
+
inputSchema: z.object({
|
|
67
|
+
url: z.string().optional().describe('The URL to navigate to in the new tab. If not provided, the new tab will be blank.'),
|
|
70
68
|
}),
|
|
71
69
|
},
|
|
72
70
|
handle: async (context, params) => {
|
|
@@ -83,13 +81,13 @@ const newTab = captureSnapshot => (0, tool_1.defineTool)({
|
|
|
83
81
|
};
|
|
84
82
|
},
|
|
85
83
|
});
|
|
86
|
-
const closeTab = captureSnapshot =>
|
|
84
|
+
const closeTab = captureSnapshot => defineTool({
|
|
87
85
|
capability: 'tabs',
|
|
88
86
|
schema: {
|
|
89
87
|
name: 'browser_tab_close',
|
|
90
88
|
description: 'Close a tab',
|
|
91
|
-
inputSchema:
|
|
92
|
-
index:
|
|
89
|
+
inputSchema: z.object({
|
|
90
|
+
index: z.number().optional().describe('The index of the tab to close. Closes current tab if not provided.'),
|
|
93
91
|
}),
|
|
94
92
|
},
|
|
95
93
|
handle: async (context, params) => {
|
|
@@ -104,7 +102,7 @@ const closeTab = captureSnapshot => (0, tool_1.defineTool)({
|
|
|
104
102
|
};
|
|
105
103
|
},
|
|
106
104
|
});
|
|
107
|
-
|
|
105
|
+
export default (captureSnapshot) => [
|
|
108
106
|
listTabs,
|
|
109
107
|
newTab(captureSnapshot),
|
|
110
108
|
selectTab(captureSnapshot),
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Microsoft Corporation.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
import { z } from 'zod';
|
|
17
|
+
import { defineTool } from './tool.js';
|
|
18
|
+
const generateTestSchema = z.object({
|
|
19
|
+
name: z.string().describe('The name of the test'),
|
|
20
|
+
description: z.string().describe('The description of the test'),
|
|
21
|
+
steps: z.array(z.string()).describe('The steps of the test'),
|
|
22
|
+
});
|
|
23
|
+
const generateTest = defineTool({
|
|
24
|
+
capability: 'testing',
|
|
25
|
+
schema: {
|
|
26
|
+
name: 'browser_generate_playwright_test',
|
|
27
|
+
description: 'Generate a Playwright test for given scenario',
|
|
28
|
+
inputSchema: generateTestSchema,
|
|
29
|
+
},
|
|
30
|
+
handle: async (context, params) => {
|
|
31
|
+
return {
|
|
32
|
+
resultOverride: {
|
|
33
|
+
content: [{
|
|
34
|
+
type: 'text',
|
|
35
|
+
text: instructions(params),
|
|
36
|
+
}],
|
|
37
|
+
},
|
|
38
|
+
code: [],
|
|
39
|
+
captureSnapshot: false,
|
|
40
|
+
waitForNetwork: false,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const instructions = (params) => [
|
|
45
|
+
`## Instructions`,
|
|
46
|
+
`- You are a playwright test generator.`,
|
|
47
|
+
`- You are given a scenario and you need to generate a playwright test for it.`,
|
|
48
|
+
'- DO NOT generate test code based on the scenario alone. DO run steps one by one using the tools provided instead.',
|
|
49
|
+
'- Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history',
|
|
50
|
+
'- Save generated test file in the tests directory',
|
|
51
|
+
`Test name: ${params.name}`,
|
|
52
|
+
`Description: ${params.description}`,
|
|
53
|
+
`Steps:`,
|
|
54
|
+
...params.steps.map((step, index) => `- ${index + 1}. ${step}`),
|
|
55
|
+
].join('\n');
|
|
56
|
+
export default [
|
|
57
|
+
generateTest,
|
|
58
|
+
];
|
package/lib/tools/tool.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Copyright (c) Microsoft Corporation.
|
|
4
3
|
*
|
|
@@ -14,8 +13,6 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
exports.defineTool = defineTool;
|
|
19
|
-
function defineTool(tool) {
|
|
16
|
+
export function defineTool(tool) {
|
|
20
17
|
return tool;
|
|
21
18
|
}
|
package/lib/tools/utils.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Copyright (c) Microsoft Corporation.
|
|
4
3
|
*
|
|
@@ -14,10 +13,7 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
exports.waitForCompletion = waitForCompletion;
|
|
19
|
-
exports.sanitizeForFilePath = sanitizeForFilePath;
|
|
20
|
-
async function waitForCompletion(context, page, callback) {
|
|
16
|
+
export async function waitForCompletion(context, page, callback) {
|
|
21
17
|
const requests = new Set();
|
|
22
18
|
let frameNavigated = false;
|
|
23
19
|
let waitCallback = () => { };
|
|
@@ -64,6 +60,10 @@ async function waitForCompletion(context, page, callback) {
|
|
|
64
60
|
dispose();
|
|
65
61
|
}
|
|
66
62
|
}
|
|
67
|
-
function sanitizeForFilePath(s) {
|
|
68
|
-
|
|
63
|
+
export function sanitizeForFilePath(s) {
|
|
64
|
+
const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
|
|
65
|
+
const separator = s.lastIndexOf('.');
|
|
66
|
+
if (separator === -1)
|
|
67
|
+
return sanitize(s);
|
|
68
|
+
return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1));
|
|
69
69
|
}
|
package/lib/transport.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Copyright (c) Microsoft Corporation.
|
|
4
3
|
*
|
|
@@ -14,21 +13,15 @@
|
|
|
14
13
|
* See the License for the specific language governing permissions and
|
|
15
14
|
* limitations under the License.
|
|
16
15
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const node_assert_1 = __importDefault(require("node:assert"));
|
|
25
|
-
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
26
|
-
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
27
|
-
const sse_js_1 = require("@modelcontextprotocol/sdk/server/sse.js");
|
|
28
|
-
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
29
|
-
async function startStdioTransport(serverList) {
|
|
16
|
+
import http from 'node:http';
|
|
17
|
+
import assert from 'node:assert';
|
|
18
|
+
import crypto from 'node:crypto';
|
|
19
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
20
|
+
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
|
|
21
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
22
|
+
export async function startStdioTransport(serverList) {
|
|
30
23
|
const server = await serverList.create();
|
|
31
|
-
await server.connect(new
|
|
24
|
+
await server.connect(new StdioServerTransport());
|
|
32
25
|
}
|
|
33
26
|
async function handleSSE(req, res, url, serverList, sessions) {
|
|
34
27
|
if (req.method === 'POST') {
|
|
@@ -45,12 +38,15 @@ async function handleSSE(req, res, url, serverList, sessions) {
|
|
|
45
38
|
return await transport.handlePostMessage(req, res);
|
|
46
39
|
}
|
|
47
40
|
else if (req.method === 'GET') {
|
|
48
|
-
const transport = new
|
|
41
|
+
const transport = new SSEServerTransport('/sse', res);
|
|
49
42
|
sessions.set(transport.sessionId, transport);
|
|
50
43
|
const server = await serverList.create();
|
|
51
44
|
res.on('close', () => {
|
|
52
45
|
sessions.delete(transport.sessionId);
|
|
53
|
-
serverList.close(server).catch(e =>
|
|
46
|
+
serverList.close(server).catch(e => {
|
|
47
|
+
// eslint-disable-next-line no-console
|
|
48
|
+
console.error(e);
|
|
49
|
+
});
|
|
54
50
|
});
|
|
55
51
|
return await server.connect(transport);
|
|
56
52
|
}
|
|
@@ -69,8 +65,8 @@ async function handleStreamable(req, res, serverList, sessions) {
|
|
|
69
65
|
return await transport.handleRequest(req, res);
|
|
70
66
|
}
|
|
71
67
|
if (req.method === 'POST') {
|
|
72
|
-
const transport = new
|
|
73
|
-
sessionIdGenerator: () =>
|
|
68
|
+
const transport = new StreamableHTTPServerTransport({
|
|
69
|
+
sessionIdGenerator: () => crypto.randomUUID(),
|
|
74
70
|
onsessioninitialized: sessionId => {
|
|
75
71
|
sessions.set(sessionId, transport);
|
|
76
72
|
}
|
|
@@ -86,10 +82,10 @@ async function handleStreamable(req, res, serverList, sessions) {
|
|
|
86
82
|
res.statusCode = 400;
|
|
87
83
|
res.end('Invalid request');
|
|
88
84
|
}
|
|
89
|
-
function startHttpTransport(port, hostname, serverList) {
|
|
85
|
+
export function startHttpTransport(port, hostname, serverList) {
|
|
90
86
|
const sseSessions = new Map();
|
|
91
87
|
const streamableSessions = new Map();
|
|
92
|
-
const httpServer =
|
|
88
|
+
const httpServer = http.createServer(async (req, res) => {
|
|
93
89
|
const url = new URL(`http://localhost${req.url}`);
|
|
94
90
|
if (url.pathname.startsWith('/mcp'))
|
|
95
91
|
await handleStreamable(req, res, serverList, streamableSessions);
|
|
@@ -98,7 +94,7 @@ function startHttpTransport(port, hostname, serverList) {
|
|
|
98
94
|
});
|
|
99
95
|
httpServer.listen(port, hostname, () => {
|
|
100
96
|
const address = httpServer.address();
|
|
101
|
-
(
|
|
97
|
+
assert(address, 'Could not bind server socket');
|
|
102
98
|
let url;
|
|
103
99
|
if (typeof address === 'string') {
|
|
104
100
|
url = address;
|
|
@@ -110,15 +106,19 @@ function startHttpTransport(port, hostname, serverList) {
|
|
|
110
106
|
resolvedHost = 'localhost';
|
|
111
107
|
url = `http://${resolvedHost}:${resolvedPort}`;
|
|
112
108
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
'
|
|
118
|
-
'
|
|
109
|
+
const message = [
|
|
110
|
+
`Listening on ${url}`,
|
|
111
|
+
'Put this in your client config:',
|
|
112
|
+
JSON.stringify({
|
|
113
|
+
'mcpServers': {
|
|
114
|
+
'playwright': {
|
|
115
|
+
'url': `${url}/sse`
|
|
116
|
+
}
|
|
119
117
|
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
118
|
+
}, undefined, 2),
|
|
119
|
+
'If your client supports streamable HTTP, you can use the /mcp endpoint instead.',
|
|
120
|
+
].join('\n');
|
|
121
|
+
// eslint-disable-next-line no-console
|
|
122
|
+
console.log(message);
|
|
123
123
|
});
|
|
124
124
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@playwright/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.19",
|
|
4
4
|
"description": "Playwright Tools for MCP",
|
|
5
|
+
"type": "module",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
8
|
"url": "git+https://github.com/microsoft/playwright-mcp.git"
|
|
@@ -36,14 +37,14 @@
|
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"@modelcontextprotocol/sdk": "^1.10.1",
|
|
38
39
|
"commander": "^13.1.0",
|
|
39
|
-
"playwright": "1.53.0-alpha-
|
|
40
|
+
"playwright": "1.53.0-alpha-1746218818000",
|
|
40
41
|
"yaml": "^2.7.1",
|
|
41
42
|
"zod-to-json-schema": "^3.24.4"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@eslint/eslintrc": "^3.2.0",
|
|
45
46
|
"@eslint/js": "^9.19.0",
|
|
46
|
-
"@playwright/test": "1.53.0-alpha-
|
|
47
|
+
"@playwright/test": "1.53.0-alpha-1746218818000",
|
|
47
48
|
"@stylistic/eslint-plugin": "^3.0.1",
|
|
48
49
|
"@types/node": "^22.13.10",
|
|
49
50
|
"@typescript-eslint/eslint-plugin": "^8.26.1",
|