@playwright/mcp 0.0.36-alpha-2025-09-02 → 0.0.36-alpha-2025-09-04
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 +1 -1
- package/cli.js +1 -1
- package/index.d.ts +1 -1
- package/index.js +2 -2
- package/lib/{browserContextFactory.js → browser/browserContextFactory.js} +76 -36
- package/lib/{browserServerBackend.js → browser/browserServerBackend.js} +24 -22
- package/lib/{utils → browser}/codegen.js +8 -3
- package/lib/{config.js → browser/config.js} +43 -26
- package/lib/{context.js → browser/context.js} +27 -30
- package/lib/{response.js → browser/response.js} +14 -14
- package/lib/{sessionLog.js → browser/sessionLog.js} +23 -18
- package/lib/{tab.js → browser/tab.js} +29 -27
- package/lib/{tools → browser/tools}/common.js +11 -9
- package/lib/{tools → browser/tools}/console.js +7 -5
- package/lib/{tools → browser/tools}/dialogs.js +9 -7
- package/lib/browser/tools/evaluate.js +88 -0
- package/lib/{tools → browser/tools}/files.js +8 -6
- package/lib/browser/tools/form.js +92 -0
- package/lib/{tools → browser/tools}/install.js +18 -14
- package/lib/browser/tools/keyboard.js +113 -0
- package/lib/{tools → browser/tools}/mouse.js +18 -16
- package/lib/{tools → browser/tools}/navigate.js +10 -8
- package/lib/{tools → browser/tools}/network.js +7 -5
- package/lib/browser/tools/pdf.js +76 -0
- package/lib/browser/tools/screenshot.js +115 -0
- package/lib/browser/tools/snapshot.js +175 -0
- package/lib/{tools → browser/tools}/tabs.js +9 -7
- package/lib/{tools → browser/tools}/tool.js +6 -2
- package/lib/{tools → browser/tools}/utils.js +10 -5
- package/lib/{tools → browser/tools}/verify.js +59 -24
- package/lib/{tools → browser/tools}/wait.js +10 -8
- package/lib/browser/tools.js +61 -0
- package/lib/extension/cdpRelay.js +85 -48
- package/lib/extension/extensionContextFactory.js +48 -11
- package/lib/extension/protocol.js +4 -1
- package/lib/index.js +47 -12
- package/lib/{utils/log.js → log.js} +11 -4
- package/lib/{utils/package.js → package.js} +9 -5
- package/lib/program.js +68 -39
- package/lib/sdk/bundle.js +79 -0
- package/lib/{mcp → sdk}/http.js +57 -17
- package/lib/{mcp → sdk}/inProcessTransport.js +15 -20
- package/lib/{mcp → sdk}/manualPromise.js +11 -9
- package/lib/{mcp → sdk}/mdb.js +77 -38
- package/lib/{mcp → sdk}/proxyBackend.js +53 -16
- package/lib/sdk/server.js +164 -0
- package/lib/{mcp → sdk}/tool.js +8 -4
- package/lib/vscode/host.js +64 -35
- package/lib/vscode/main.js +48 -13
- package/package.json +6 -7
- package/lib/loop/loop.js +0 -69
- package/lib/loop/loopClaude.js +0 -152
- package/lib/loop/loopOpenAI.js +0 -141
- package/lib/loop/main.js +0 -60
- package/lib/loopTools/context.js +0 -67
- package/lib/loopTools/main.js +0 -54
- package/lib/loopTools/perform.js +0 -32
- package/lib/loopTools/snapshot.js +0 -29
- package/lib/loopTools/tool.js +0 -18
- package/lib/mcp/server.js +0 -123
- package/lib/tools/evaluate.js +0 -53
- package/lib/tools/form.js +0 -57
- package/lib/tools/keyboard.js +0 -78
- package/lib/tools/pdf.js +0 -40
- package/lib/tools/screenshot.js +0 -79
- package/lib/tools/snapshot.js +0 -139
- package/lib/tools.js +0 -54
- package/lib/utils/fileUtils.js +0 -36
- package/lib/utils/guid.js +0 -22
package/README.md
CHANGED
|
@@ -76,7 +76,7 @@ For more information, see the [Codex MCP documentation](https://github.com/opena
|
|
|
76
76
|
|
|
77
77
|
#### Click the button to install:
|
|
78
78
|
|
|
79
|
-
[
|
|
79
|
+
[<img src="https://cursor.com/deeplink/mcp-install-dark.svg" alt="Install in Cursor">](cursor://anysphere.cursor-deeplink/mcp/install?name=Playwright&config=eyJjb21tYW5kIjoibnB4IEBwbGF5d3JpZ2h0L21jcEBsYXRlc3QifQ%3D%3D)
|
|
80
80
|
|
|
81
81
|
#### Or install manually:
|
|
82
82
|
|
package/cli.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import type { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
19
|
-
import type { Config } from './config
|
|
19
|
+
import type { Config } from './config';
|
|
20
20
|
import type { BrowserContext } from 'playwright';
|
|
21
21
|
|
|
22
22
|
export declare function createConnection(config?: Config, contextGetter?: () => Promise<BrowserContext>): Promise<Server>;
|
package/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Microsoft Corporation.
|
|
3
4
|
*
|
|
@@ -13,18 +14,56 @@
|
|
|
13
14
|
* See the License for the specific language governing permissions and
|
|
14
15
|
* limitations under the License.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
20
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
21
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
51
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
52
|
+
};
|
|
53
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
+
exports.contextFactory = contextFactory;
|
|
55
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
56
|
+
const fs_1 = __importDefault(require("fs"));
|
|
57
|
+
const net_1 = __importDefault(require("net"));
|
|
58
|
+
const path_1 = __importDefault(require("path"));
|
|
59
|
+
const playwright = __importStar(require("playwright"));
|
|
20
60
|
// @ts-ignore
|
|
21
|
-
|
|
61
|
+
const index_1 = require("playwright-core/lib/server/registry/index");
|
|
22
62
|
// @ts-ignore
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export function contextFactory(config) {
|
|
63
|
+
const server_1 = require("playwright-core/lib/server");
|
|
64
|
+
const log_1 = require("../log");
|
|
65
|
+
const config_1 = require("./config");
|
|
66
|
+
function contextFactory(config) {
|
|
28
67
|
if (config.browser.remoteEndpoint)
|
|
29
68
|
return new RemoteContextFactory(config);
|
|
30
69
|
if (config.browser.cdpEndpoint)
|
|
@@ -34,9 +73,6 @@ export function contextFactory(config) {
|
|
|
34
73
|
return new PersistentContextFactory(config);
|
|
35
74
|
}
|
|
36
75
|
class BaseContextFactory {
|
|
37
|
-
config;
|
|
38
|
-
_logName;
|
|
39
|
-
_browserPromise;
|
|
40
76
|
constructor(name, config) {
|
|
41
77
|
this._logName = name;
|
|
42
78
|
this.config = config;
|
|
@@ -44,7 +80,7 @@ class BaseContextFactory {
|
|
|
44
80
|
async _obtainBrowser(clientInfo) {
|
|
45
81
|
if (this._browserPromise)
|
|
46
82
|
return this._browserPromise;
|
|
47
|
-
testDebug(`obtain browser (${this._logName})`);
|
|
83
|
+
(0, log_1.testDebug)(`obtain browser (${this._logName})`);
|
|
48
84
|
this._browserPromise = this._doObtainBrowser(clientInfo);
|
|
49
85
|
void this._browserPromise.then(browser => {
|
|
50
86
|
browser.on('disconnected', () => {
|
|
@@ -59,7 +95,7 @@ class BaseContextFactory {
|
|
|
59
95
|
throw new Error('Not implemented');
|
|
60
96
|
}
|
|
61
97
|
async createContext(clientInfo) {
|
|
62
|
-
testDebug(`create browser context (${this._logName})`);
|
|
98
|
+
(0, log_1.testDebug)(`create browser context (${this._logName})`);
|
|
63
99
|
const browser = await this._obtainBrowser(clientInfo);
|
|
64
100
|
const browserContext = await this._doCreateContext(browser);
|
|
65
101
|
return { browserContext, close: () => this._closeBrowserContext(browserContext, browser) };
|
|
@@ -68,13 +104,13 @@ class BaseContextFactory {
|
|
|
68
104
|
throw new Error('Not implemented');
|
|
69
105
|
}
|
|
70
106
|
async _closeBrowserContext(browserContext, browser) {
|
|
71
|
-
testDebug(`close browser context (${this._logName})`);
|
|
107
|
+
(0, log_1.testDebug)(`close browser context (${this._logName})`);
|
|
72
108
|
if (browser.contexts().length === 1)
|
|
73
109
|
this._browserPromise = undefined;
|
|
74
|
-
await browserContext.close().catch(logUnhandledError);
|
|
110
|
+
await browserContext.close().catch(log_1.logUnhandledError);
|
|
75
111
|
if (browser.contexts().length === 0) {
|
|
76
|
-
testDebug(`close browser (${this._logName})`);
|
|
77
|
-
await browser.close().catch(logUnhandledError);
|
|
112
|
+
(0, log_1.testDebug)(`close browser (${this._logName})`);
|
|
113
|
+
await browser.close().catch(log_1.logUnhandledError);
|
|
78
114
|
}
|
|
79
115
|
}
|
|
80
116
|
}
|
|
@@ -127,20 +163,20 @@ class RemoteContextFactory extends BaseContextFactory {
|
|
|
127
163
|
}
|
|
128
164
|
}
|
|
129
165
|
class PersistentContextFactory {
|
|
130
|
-
config;
|
|
131
|
-
name = 'persistent';
|
|
132
|
-
description = 'Create a new persistent browser context';
|
|
133
|
-
_userDataDirs = new Set();
|
|
134
166
|
constructor(config) {
|
|
167
|
+
this.name = 'persistent';
|
|
168
|
+
this.description = 'Create a new persistent browser context';
|
|
169
|
+
this._userDataDirs = new Set();
|
|
135
170
|
this.config = config;
|
|
136
171
|
}
|
|
137
172
|
async createContext(clientInfo) {
|
|
173
|
+
var _a;
|
|
138
174
|
await injectCdpPort(this.config.browser);
|
|
139
|
-
testDebug('create browser context (persistent)');
|
|
140
|
-
const userDataDir = this.config.browser.userDataDir
|
|
175
|
+
(0, log_1.testDebug)('create browser context (persistent)');
|
|
176
|
+
const userDataDir = (_a = this.config.browser.userDataDir) !== null && _a !== void 0 ? _a : await this._createUserDataDir(clientInfo.rootPath);
|
|
141
177
|
const tracesDir = await startTraceServer(this.config, clientInfo.rootPath);
|
|
142
178
|
this._userDataDirs.add(userDataDir);
|
|
143
|
-
testDebug('lock user data dir', userDataDir);
|
|
179
|
+
(0, log_1.testDebug)('lock user data dir', userDataDir);
|
|
144
180
|
const browserType = playwright[this.config.browser.browserName];
|
|
145
181
|
for (let i = 0; i < 5; i++) {
|
|
146
182
|
try {
|
|
@@ -168,19 +204,20 @@ class PersistentContextFactory {
|
|
|
168
204
|
throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
|
|
169
205
|
}
|
|
170
206
|
async _closeBrowserContext(browserContext, userDataDir) {
|
|
171
|
-
testDebug('close browser context (persistent)');
|
|
172
|
-
testDebug('release user data dir', userDataDir);
|
|
207
|
+
(0, log_1.testDebug)('close browser context (persistent)');
|
|
208
|
+
(0, log_1.testDebug)('release user data dir', userDataDir);
|
|
173
209
|
await browserContext.close().catch(() => { });
|
|
174
210
|
this._userDataDirs.delete(userDataDir);
|
|
175
|
-
testDebug('close browser context complete (persistent)');
|
|
211
|
+
(0, log_1.testDebug)('close browser context complete (persistent)');
|
|
176
212
|
}
|
|
177
213
|
async _createUserDataDir(rootPath) {
|
|
178
|
-
|
|
179
|
-
const
|
|
214
|
+
var _a, _b, _c, _d;
|
|
215
|
+
const dir = (_a = process.env.PWMCP_PROFILES_DIR_FOR_TEST) !== null && _a !== void 0 ? _a : index_1.registryDirectory;
|
|
216
|
+
const browserToken = (_c = (_b = this.config.browser.launchOptions) === null || _b === void 0 ? void 0 : _b.channel) !== null && _c !== void 0 ? _c : (_d = this.config.browser) === null || _d === void 0 ? void 0 : _d.browserName;
|
|
180
217
|
// Hesitant putting hundreds of files into the user's workspace, so using it for hashing instead.
|
|
181
218
|
const rootPathToken = rootPath ? `-${createHash(rootPath)}` : '';
|
|
182
|
-
const result =
|
|
183
|
-
await
|
|
219
|
+
const result = path_1.default.join(dir, `mcp-${browserToken}${rootPathToken}`);
|
|
220
|
+
await fs_1.default.promises.mkdir(result, { recursive: true });
|
|
184
221
|
return result;
|
|
185
222
|
}
|
|
186
223
|
}
|
|
@@ -190,7 +227,7 @@ async function injectCdpPort(browserConfig) {
|
|
|
190
227
|
}
|
|
191
228
|
async function findFreePort() {
|
|
192
229
|
return new Promise((resolve, reject) => {
|
|
193
|
-
const server =
|
|
230
|
+
const server = net_1.default.createServer();
|
|
194
231
|
server.listen(0, () => {
|
|
195
232
|
const { port } = server.address();
|
|
196
233
|
server.close(() => resolve(port));
|
|
@@ -201,11 +238,14 @@ async function findFreePort() {
|
|
|
201
238
|
async function startTraceServer(config, rootPath) {
|
|
202
239
|
if (!config.saveTrace)
|
|
203
240
|
return undefined;
|
|
204
|
-
const tracesDir = await outputFile(config, rootPath, `traces-${Date.now()}`);
|
|
205
|
-
const server = await startTraceViewerServer();
|
|
241
|
+
const tracesDir = await (0, config_1.outputFile)(config, rootPath, `traces-${Date.now()}`);
|
|
242
|
+
const server = await (0, server_1.startTraceViewerServer)();
|
|
206
243
|
const urlPrefix = server.urlPrefix('human-readable');
|
|
207
244
|
const url = urlPrefix + '/trace/index.html?trace=' + tracesDir + '/trace.json';
|
|
208
245
|
// eslint-disable-next-line no-console
|
|
209
246
|
console.error('\nTrace viewer listening on ' + url);
|
|
210
247
|
return tracesDir;
|
|
211
248
|
}
|
|
249
|
+
function createHash(data) {
|
|
250
|
+
return crypto_1.default.createHash('sha256').update(data).digest('hex').slice(0, 7);
|
|
251
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Microsoft Corporation.
|
|
3
4
|
*
|
|
@@ -13,33 +14,31 @@
|
|
|
13
14
|
* See the License for the specific language governing permissions and
|
|
14
15
|
* limitations under the License.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
_sessionLog;
|
|
27
|
-
_config;
|
|
28
|
-
_browserContextFactory;
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.BrowserServerBackend = void 0;
|
|
19
|
+
const url_1 = require("url");
|
|
20
|
+
const context_1 = require("./context");
|
|
21
|
+
const log_1 = require("../log");
|
|
22
|
+
const response_1 = require("./response");
|
|
23
|
+
const sessionLog_1 = require("./sessionLog");
|
|
24
|
+
const tools_1 = require("./tools");
|
|
25
|
+
const tool_1 = require("../sdk/tool");
|
|
26
|
+
class BrowserServerBackend {
|
|
29
27
|
constructor(config, factory) {
|
|
30
28
|
this._config = config;
|
|
31
29
|
this._browserContextFactory = factory;
|
|
32
|
-
this._tools = filteredTools(config);
|
|
30
|
+
this._tools = (0, tools_1.filteredTools)(config);
|
|
33
31
|
}
|
|
34
32
|
async initialize(server, clientVersion, roots) {
|
|
33
|
+
var _a;
|
|
35
34
|
let rootPath;
|
|
36
35
|
if (roots.length > 0) {
|
|
37
|
-
const firstRootUri = roots[0]
|
|
36
|
+
const firstRootUri = (_a = roots[0]) === null || _a === void 0 ? void 0 : _a.uri;
|
|
38
37
|
const url = firstRootUri ? new URL(firstRootUri) : undefined;
|
|
39
|
-
rootPath = url ? fileURLToPath(url) : undefined;
|
|
38
|
+
rootPath = url ? (0, url_1.fileURLToPath)(url) : undefined;
|
|
40
39
|
}
|
|
41
|
-
this._sessionLog = this._config.saveSession ? await SessionLog.create(this._config, rootPath) : undefined;
|
|
42
|
-
this._context = new Context({
|
|
40
|
+
this._sessionLog = this._config.saveSession ? await sessionLog_1.SessionLog.create(this._config, rootPath) : undefined;
|
|
41
|
+
this._context = new context_1.Context({
|
|
43
42
|
tools: this._tools,
|
|
44
43
|
config: this._config,
|
|
45
44
|
browserContextFactory: this._browserContextFactory,
|
|
@@ -48,20 +47,21 @@ export class BrowserServerBackend {
|
|
|
48
47
|
});
|
|
49
48
|
}
|
|
50
49
|
async listTools() {
|
|
51
|
-
return this._tools.map(tool => toMcpTool(tool.schema));
|
|
50
|
+
return this._tools.map(tool => (0, tool_1.toMcpTool)(tool.schema));
|
|
52
51
|
}
|
|
53
52
|
async callTool(name, rawArguments) {
|
|
53
|
+
var _a;
|
|
54
54
|
const tool = this._tools.find(tool => tool.schema.name === name);
|
|
55
55
|
if (!tool)
|
|
56
56
|
throw new Error(`Tool "${name}" not found`);
|
|
57
57
|
const parsedArguments = tool.schema.inputSchema.parse(rawArguments || {});
|
|
58
58
|
const context = this._context;
|
|
59
|
-
const response = new Response(context, name, parsedArguments);
|
|
59
|
+
const response = new response_1.Response(context, name, parsedArguments);
|
|
60
60
|
context.setRunningTool(name);
|
|
61
61
|
try {
|
|
62
62
|
await tool.handle(context, parsedArguments, response);
|
|
63
63
|
await response.finish();
|
|
64
|
-
this._sessionLog
|
|
64
|
+
(_a = this._sessionLog) === null || _a === void 0 ? void 0 : _a.logResponse(response);
|
|
65
65
|
}
|
|
66
66
|
catch (error) {
|
|
67
67
|
response.addError(String(error));
|
|
@@ -72,6 +72,8 @@ export class BrowserServerBackend {
|
|
|
72
72
|
return response.serialize();
|
|
73
73
|
}
|
|
74
74
|
serverClosed() {
|
|
75
|
-
|
|
75
|
+
var _a;
|
|
76
|
+
void ((_a = this._context) === null || _a === void 0 ? void 0 : _a.dispose().catch(log_1.logUnhandledError));
|
|
76
77
|
}
|
|
77
78
|
}
|
|
79
|
+
exports.BrowserServerBackend = BrowserServerBackend;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Microsoft Corporation.
|
|
3
4
|
*
|
|
@@ -13,11 +14,15 @@
|
|
|
13
14
|
* See the License for the specific language governing permissions and
|
|
14
15
|
* limitations under the License.
|
|
15
16
|
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.escapeWithQuotes = escapeWithQuotes;
|
|
19
|
+
exports.quote = quote;
|
|
20
|
+
exports.formatObject = formatObject;
|
|
16
21
|
// adapted from:
|
|
17
22
|
// - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/utils/isomorphic/stringUtils.ts
|
|
18
23
|
// - https://github.com/microsoft/playwright/blob/76ee48dc9d4034536e3ec5b2c7ce8be3b79418a8/packages/playwright-core/src/server/codegen/javascript.ts
|
|
19
24
|
// NOTE: this function should not be used to escape any selectors.
|
|
20
|
-
|
|
25
|
+
function escapeWithQuotes(text, char = '\'') {
|
|
21
26
|
const stringified = JSON.stringify(text);
|
|
22
27
|
const escapedText = stringified.substring(1, stringified.length - 1).replace(/\\"/g, '"');
|
|
23
28
|
if (char === '\'')
|
|
@@ -28,10 +33,10 @@ export function escapeWithQuotes(text, char = '\'') {
|
|
|
28
33
|
return char + escapedText.replace(/[`]/g, '\\`') + char;
|
|
29
34
|
throw new Error('Invalid escape char');
|
|
30
35
|
}
|
|
31
|
-
|
|
36
|
+
function quote(text) {
|
|
32
37
|
return escapeWithQuotes(text, '\'');
|
|
33
38
|
}
|
|
34
|
-
|
|
39
|
+
function formatObject(value, indent = ' ') {
|
|
35
40
|
if (typeof value === 'string')
|
|
36
41
|
return quote(value);
|
|
37
42
|
if (Array.isArray(value))
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Microsoft Corporation.
|
|
3
4
|
*
|
|
@@ -13,17 +14,26 @@
|
|
|
13
14
|
* See the License for the specific language governing permissions and
|
|
14
15
|
* limitations under the License.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.resolveConfig = resolveConfig;
|
|
22
|
+
exports.resolveCLIConfig = resolveCLIConfig;
|
|
23
|
+
exports.configFromCLIOptions = configFromCLIOptions;
|
|
24
|
+
exports.outputFile = outputFile;
|
|
25
|
+
exports.semicolonSeparatedList = semicolonSeparatedList;
|
|
26
|
+
exports.commaSeparatedList = commaSeparatedList;
|
|
27
|
+
const fs_1 = __importDefault(require("fs"));
|
|
28
|
+
const os_1 = __importDefault(require("os"));
|
|
29
|
+
const path_1 = __importDefault(require("path"));
|
|
30
|
+
const playwright_1 = require("playwright");
|
|
21
31
|
const defaultConfig = {
|
|
22
32
|
browser: {
|
|
23
33
|
browserName: 'chromium',
|
|
24
34
|
launchOptions: {
|
|
25
35
|
channel: 'chrome',
|
|
26
|
-
headless:
|
|
36
|
+
headless: os_1.default.platform() === 'linux' && !process.env.DISPLAY,
|
|
27
37
|
chromiumSandbox: true,
|
|
28
38
|
},
|
|
29
39
|
contextOptions: {
|
|
@@ -37,10 +47,10 @@ const defaultConfig = {
|
|
|
37
47
|
server: {},
|
|
38
48
|
saveTrace: false,
|
|
39
49
|
};
|
|
40
|
-
|
|
50
|
+
async function resolveConfig(config) {
|
|
41
51
|
return mergeConfig(defaultConfig, config);
|
|
42
52
|
}
|
|
43
|
-
|
|
53
|
+
async function resolveCLIConfig(cliOptions) {
|
|
44
54
|
const configInFile = await loadConfig(cliOptions.config);
|
|
45
55
|
const envOverrides = configFromEnv();
|
|
46
56
|
const cliOverrides = configFromCLIOptions(cliOptions);
|
|
@@ -50,7 +60,7 @@ export async function resolveCLIConfig(cliOptions) {
|
|
|
50
60
|
result = mergeConfig(result, cliOverrides);
|
|
51
61
|
return result;
|
|
52
62
|
}
|
|
53
|
-
|
|
63
|
+
function configFromCLIOptions(cliOptions) {
|
|
54
64
|
let browserName;
|
|
55
65
|
let channel;
|
|
56
66
|
switch (cliOptions.browser) {
|
|
@@ -92,7 +102,7 @@ export function configFromCLIOptions(cliOptions) {
|
|
|
92
102
|
if (cliOptions.device && cliOptions.cdpEndpoint)
|
|
93
103
|
throw new Error('Device emulation is not supported with cdpEndpoint.');
|
|
94
104
|
// Context options
|
|
95
|
-
const contextOptions = cliOptions.device ? devices[cliOptions.device] : {};
|
|
105
|
+
const contextOptions = cliOptions.device ? playwright_1.devices[cliOptions.device] : {};
|
|
96
106
|
if (cliOptions.storageState)
|
|
97
107
|
contextOptions.storageState = cliOptions.storageState;
|
|
98
108
|
if (cliOptions.userAgent)
|
|
@@ -170,37 +180,37 @@ async function loadConfig(configFile) {
|
|
|
170
180
|
if (!configFile)
|
|
171
181
|
return {};
|
|
172
182
|
try {
|
|
173
|
-
return JSON.parse(await
|
|
183
|
+
return JSON.parse(await fs_1.default.promises.readFile(configFile, 'utf8'));
|
|
174
184
|
}
|
|
175
185
|
catch (error) {
|
|
176
186
|
throw new Error(`Failed to load config file: ${configFile}, ${error}`);
|
|
177
187
|
}
|
|
178
188
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
189
|
+
async function outputFile(config, rootPath, name) {
|
|
190
|
+
var _a, _b;
|
|
191
|
+
const outputDir = (_b = (_a = config.outputDir) !== null && _a !== void 0 ? _a : (rootPath ? path_1.default.join(rootPath, '.playwright-mcp') : undefined)) !== null && _b !== void 0 ? _b : path_1.default.join(os_1.default.tmpdir(), 'playwright-mcp-output', sanitizeForFilePath(new Date().toISOString()));
|
|
192
|
+
await fs_1.default.promises.mkdir(outputDir, { recursive: true });
|
|
184
193
|
const fileName = sanitizeForFilePath(name);
|
|
185
|
-
return
|
|
194
|
+
return path_1.default.join(outputDir, fileName);
|
|
186
195
|
}
|
|
187
196
|
function pickDefined(obj) {
|
|
188
|
-
return Object.fromEntries(Object.entries(obj
|
|
197
|
+
return Object.fromEntries(Object.entries(obj !== null && obj !== void 0 ? obj : {}).filter(([_, v]) => v !== undefined));
|
|
189
198
|
}
|
|
190
199
|
function mergeConfig(base, overrides) {
|
|
200
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
|
|
191
201
|
const browser = {
|
|
192
202
|
...pickDefined(base.browser),
|
|
193
203
|
...pickDefined(overrides.browser),
|
|
194
|
-
browserName: overrides.browser
|
|
195
|
-
isolated: overrides.browser
|
|
204
|
+
browserName: (_d = (_b = (_a = overrides.browser) === null || _a === void 0 ? void 0 : _a.browserName) !== null && _b !== void 0 ? _b : (_c = base.browser) === null || _c === void 0 ? void 0 : _c.browserName) !== null && _d !== void 0 ? _d : 'chromium',
|
|
205
|
+
isolated: (_h = (_f = (_e = overrides.browser) === null || _e === void 0 ? void 0 : _e.isolated) !== null && _f !== void 0 ? _f : (_g = base.browser) === null || _g === void 0 ? void 0 : _g.isolated) !== null && _h !== void 0 ? _h : false,
|
|
196
206
|
launchOptions: {
|
|
197
|
-
...pickDefined(base.browser
|
|
198
|
-
...pickDefined(overrides.browser
|
|
207
|
+
...pickDefined((_j = base.browser) === null || _j === void 0 ? void 0 : _j.launchOptions),
|
|
208
|
+
...pickDefined((_k = overrides.browser) === null || _k === void 0 ? void 0 : _k.launchOptions),
|
|
199
209
|
...{ assistantMode: true },
|
|
200
210
|
},
|
|
201
211
|
contextOptions: {
|
|
202
|
-
...pickDefined(base.browser
|
|
203
|
-
...pickDefined(overrides.browser
|
|
212
|
+
...pickDefined((_l = base.browser) === null || _l === void 0 ? void 0 : _l.contextOptions),
|
|
213
|
+
...pickDefined((_m = overrides.browser) === null || _m === void 0 ? void 0 : _m.contextOptions),
|
|
204
214
|
},
|
|
205
215
|
};
|
|
206
216
|
if (browser.browserName !== 'chromium' && browser.launchOptions)
|
|
@@ -219,12 +229,12 @@ function mergeConfig(base, overrides) {
|
|
|
219
229
|
},
|
|
220
230
|
};
|
|
221
231
|
}
|
|
222
|
-
|
|
232
|
+
function semicolonSeparatedList(value) {
|
|
223
233
|
if (!value)
|
|
224
234
|
return undefined;
|
|
225
235
|
return value.split(';').map(v => v.trim());
|
|
226
236
|
}
|
|
227
|
-
|
|
237
|
+
function commaSeparatedList(value) {
|
|
228
238
|
if (!value)
|
|
229
239
|
return undefined;
|
|
230
240
|
return value.split(',').map(v => v.trim());
|
|
@@ -244,3 +254,10 @@ function envToBoolean(value) {
|
|
|
244
254
|
function envToString(value) {
|
|
245
255
|
return value ? value.trim() : undefined;
|
|
246
256
|
}
|
|
257
|
+
function sanitizeForFilePath(s) {
|
|
258
|
+
const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
|
|
259
|
+
const separator = s.lastIndexOf('.');
|
|
260
|
+
if (separator === -1)
|
|
261
|
+
return sanitize(s);
|
|
262
|
+
return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1));
|
|
263
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Copyright (c) Microsoft Corporation.
|
|
3
4
|
*
|
|
@@ -13,26 +14,20 @@
|
|
|
13
14
|
* See the License for the specific language governing permissions and
|
|
14
15
|
* limitations under the License.
|
|
15
16
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
_browserContextFactory;
|
|
28
|
-
_tabs = [];
|
|
29
|
-
_currentTab;
|
|
30
|
-
_clientInfo;
|
|
31
|
-
static _allContexts = new Set();
|
|
32
|
-
_closeBrowserContextPromise;
|
|
33
|
-
_runningToolName;
|
|
34
|
-
_abortController = new AbortController();
|
|
17
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
18
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
19
|
+
};
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.InputRecorder = exports.Context = void 0;
|
|
22
|
+
const debug_1 = __importDefault(require("debug"));
|
|
23
|
+
const log_1 = require("../log");
|
|
24
|
+
const tab_1 = require("./tab");
|
|
25
|
+
const config_1 = require("./config");
|
|
26
|
+
const testDebug = (0, debug_1.default)('pw:mcp:test');
|
|
27
|
+
class Context {
|
|
35
28
|
constructor(options) {
|
|
29
|
+
this._tabs = [];
|
|
30
|
+
this._abortController = new AbortController();
|
|
36
31
|
this.tools = options.tools;
|
|
37
32
|
this.config = options.config;
|
|
38
33
|
this.sessionLog = options.sessionLog;
|
|
@@ -85,10 +80,10 @@ export class Context {
|
|
|
85
80
|
return url;
|
|
86
81
|
}
|
|
87
82
|
async outputFile(name) {
|
|
88
|
-
return outputFile(this.config, this._clientInfo.rootPath, name);
|
|
83
|
+
return (0, config_1.outputFile)(this.config, this._clientInfo.rootPath, name);
|
|
89
84
|
}
|
|
90
85
|
_onPageCreated(page) {
|
|
91
|
-
const tab = new Tab(this, page, tab => this._onPageClosed(tab));
|
|
86
|
+
const tab = new tab_1.Tab(this, page, tab => this._onPageClosed(tab));
|
|
92
87
|
this._tabs.push(tab);
|
|
93
88
|
if (!this._currentTab)
|
|
94
89
|
this._currentTab = tab;
|
|
@@ -105,7 +100,7 @@ export class Context {
|
|
|
105
100
|
}
|
|
106
101
|
async closeBrowserContext() {
|
|
107
102
|
if (!this._closeBrowserContextPromise)
|
|
108
|
-
this._closeBrowserContextPromise = this._closeBrowserContextImpl().catch(logUnhandledError);
|
|
103
|
+
this._closeBrowserContextPromise = this._closeBrowserContextImpl().catch(log_1.logUnhandledError);
|
|
109
104
|
await this._closeBrowserContextPromise;
|
|
110
105
|
this._closeBrowserContextPromise = undefined;
|
|
111
106
|
}
|
|
@@ -133,12 +128,13 @@ export class Context {
|
|
|
133
128
|
Context._allContexts.delete(this);
|
|
134
129
|
}
|
|
135
130
|
async _setupRequestInterception(context) {
|
|
136
|
-
|
|
131
|
+
var _a, _b, _c, _d;
|
|
132
|
+
if ((_b = (_a = this.config.network) === null || _a === void 0 ? void 0 : _a.allowedOrigins) === null || _b === void 0 ? void 0 : _b.length) {
|
|
137
133
|
await context.route('**', route => route.abort('blockedbyclient'));
|
|
138
134
|
for (const origin of this.config.network.allowedOrigins)
|
|
139
135
|
await context.route(`*://${origin}/**`, route => route.continue());
|
|
140
136
|
}
|
|
141
|
-
if (this.config.network
|
|
137
|
+
if ((_d = (_c = this.config.network) === null || _c === void 0 ? void 0 : _c.blockedOrigins) === null || _d === void 0 ? void 0 : _d.length) {
|
|
142
138
|
for (const origin of this.config.network.blockedOrigins)
|
|
143
139
|
await context.route(`*://${origin}/**`, route => route.abort('blockedbyclient'));
|
|
144
140
|
}
|
|
@@ -175,9 +171,9 @@ export class Context {
|
|
|
175
171
|
return result;
|
|
176
172
|
}
|
|
177
173
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
174
|
+
exports.Context = Context;
|
|
175
|
+
Context._allContexts = new Set();
|
|
176
|
+
class InputRecorder {
|
|
181
177
|
constructor(context, browserContext) {
|
|
182
178
|
this._context = context;
|
|
183
179
|
this._browserContext = browserContext;
|
|
@@ -196,14 +192,14 @@ export class InputRecorder {
|
|
|
196
192
|
actionAdded: (page, data, code) => {
|
|
197
193
|
if (this._context.isRunningTool())
|
|
198
194
|
return;
|
|
199
|
-
const tab = Tab.forPage(page);
|
|
195
|
+
const tab = tab_1.Tab.forPage(page);
|
|
200
196
|
if (tab)
|
|
201
197
|
sessionLog.logUserAction(data.action, tab, code, false);
|
|
202
198
|
},
|
|
203
199
|
actionUpdated: (page, data, code) => {
|
|
204
200
|
if (this._context.isRunningTool())
|
|
205
201
|
return;
|
|
206
|
-
const tab = Tab.forPage(page);
|
|
202
|
+
const tab = tab_1.Tab.forPage(page);
|
|
207
203
|
if (tab)
|
|
208
204
|
sessionLog.logUserAction(data.action, tab, code, true);
|
|
209
205
|
},
|
|
@@ -212,7 +208,7 @@ export class InputRecorder {
|
|
|
212
208
|
return;
|
|
213
209
|
if (data.signal.name !== 'navigation')
|
|
214
210
|
return;
|
|
215
|
-
const tab = Tab.forPage(page);
|
|
211
|
+
const tab = tab_1.Tab.forPage(page);
|
|
216
212
|
const navigateAction = {
|
|
217
213
|
name: 'navigate',
|
|
218
214
|
url: data.signal.url,
|
|
@@ -224,3 +220,4 @@ export class InputRecorder {
|
|
|
224
220
|
});
|
|
225
221
|
}
|
|
226
222
|
}
|
|
223
|
+
exports.InputRecorder = InputRecorder;
|