@cotestdev/mcp_playwright 0.0.57 → 0.0.59
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/lib/mcp/browser/config.js +3 -3
- package/lib/mcp/browser/tools/devtools.js +42 -0
- package/lib/mcp/browser/tools.js +2 -0
- package/lib/mcp/extension/extensionContextFactory.js +2 -3
- package/lib/mcp/program.js +3 -4
- package/lib/mcp/sdk/tool.js +1 -1
- package/lib/mcp/terminal/cli.js +1 -23
- package/lib/mcp/terminal/commands.js +30 -2
- package/lib/mcp/terminal/devtoolsApp.js +248 -0
- package/lib/mcp/terminal/program.js +87 -435
- package/lib/mcp/terminal/registry.js +146 -0
- package/lib/mcp/terminal/session.js +309 -0
- package/package.json +1 -1
- package/lib/index.d.ts +0 -23
- package/lib/index.d.ts.map +0 -1
- package/lib/index.js +0 -24
- package/lib/index.js.map +0 -1
|
@@ -32,358 +32,18 @@ __export(program_exports, {
|
|
|
32
32
|
});
|
|
33
33
|
module.exports = __toCommonJS(program_exports);
|
|
34
34
|
var import_child_process = require("child_process");
|
|
35
|
-
var import_crypto = __toESM(require("crypto"));
|
|
36
35
|
var import_fs = __toESM(require("fs"));
|
|
37
|
-
var import_net = __toESM(require("net"));
|
|
38
36
|
var import_os = __toESM(require("os"));
|
|
39
37
|
var import_path = __toESM(require("path"));
|
|
40
|
-
var
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
config() {
|
|
50
|
-
return this._config;
|
|
51
|
-
}
|
|
52
|
-
isCompatible() {
|
|
53
|
-
return this._clientInfo.version === this._config.version;
|
|
54
|
-
}
|
|
55
|
-
checkCompatible() {
|
|
56
|
-
if (!this.isCompatible()) {
|
|
57
|
-
throw new Error(`Client is v${this._clientInfo.version}, session '${this.name}' is v${this._config.version}. Run
|
|
58
|
-
|
|
59
|
-
playwright-cli${this.name !== "default" ? ` -s=${this.name}` : ""} open
|
|
60
|
-
|
|
61
|
-
to restart the browser session.`);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
async run(args) {
|
|
65
|
-
this.checkCompatible();
|
|
66
|
-
return await this._send("run", { args, cwd: process.cwd() });
|
|
67
|
-
}
|
|
68
|
-
async stop(quiet = false) {
|
|
69
|
-
if (!await this.canConnect()) {
|
|
70
|
-
if (!quiet)
|
|
71
|
-
console.log(`Browser '${this.name}' is not open.`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
await this._stopDaemon();
|
|
75
|
-
if (!quiet)
|
|
76
|
-
console.log(`Browser '${this.name}' closed
|
|
77
|
-
`);
|
|
78
|
-
}
|
|
79
|
-
async _send(method, params = {}) {
|
|
80
|
-
const connection = await this._startDaemonIfNeeded();
|
|
81
|
-
const messageId = this._nextMessageId++;
|
|
82
|
-
const message = {
|
|
83
|
-
id: messageId,
|
|
84
|
-
method,
|
|
85
|
-
params,
|
|
86
|
-
version: this._config.version
|
|
87
|
-
};
|
|
88
|
-
const responsePromise = new Promise((resolve, reject) => {
|
|
89
|
-
this._callbacks.set(messageId, { resolve, reject, method, params });
|
|
90
|
-
});
|
|
91
|
-
const [result] = await Promise.all([responsePromise, connection.send(message)]);
|
|
92
|
-
return result;
|
|
93
|
-
}
|
|
94
|
-
disconnect() {
|
|
95
|
-
if (!this._connection)
|
|
96
|
-
return;
|
|
97
|
-
for (const callback of this._callbacks.values())
|
|
98
|
-
callback.reject(new Error("Session closed"));
|
|
99
|
-
this._callbacks.clear();
|
|
100
|
-
this._connection.close();
|
|
101
|
-
this._connection = void 0;
|
|
102
|
-
}
|
|
103
|
-
async deleteData() {
|
|
104
|
-
await this.stop();
|
|
105
|
-
const dataDirs = await import_fs.default.promises.readdir(this._clientInfo.daemonProfilesDir).catch(() => []);
|
|
106
|
-
const matchingEntries = dataDirs.filter((file) => file === `${this.name}.session` || file.startsWith(`ud-${this.name}-`));
|
|
107
|
-
if (matchingEntries.length === 0) {
|
|
108
|
-
console.log(`No user data found for browser '${this.name}'.`);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
for (const entry of matchingEntries) {
|
|
112
|
-
const userDataDir = import_path.default.resolve(this._clientInfo.daemonProfilesDir, entry);
|
|
113
|
-
for (let i = 0; i < 5; i++) {
|
|
114
|
-
try {
|
|
115
|
-
await import_fs.default.promises.rm(userDataDir, { recursive: true });
|
|
116
|
-
if (entry.startsWith("ud-"))
|
|
117
|
-
console.log(`Deleted user data for browser '${this.name}'.`);
|
|
118
|
-
break;
|
|
119
|
-
} catch (e) {
|
|
120
|
-
if (e.code === "ENOENT") {
|
|
121
|
-
console.log(`No user data found for browser '${this.name}'.`);
|
|
122
|
-
break;
|
|
123
|
-
}
|
|
124
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
125
|
-
if (i === 4)
|
|
126
|
-
throw e;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
async _connect() {
|
|
132
|
-
return await new Promise((resolve) => {
|
|
133
|
-
const socket = import_net.default.createConnection(this._config.socketPath, () => {
|
|
134
|
-
resolve({ socket });
|
|
135
|
-
});
|
|
136
|
-
socket.on("error", (error) => {
|
|
137
|
-
if (import_os.default.platform() !== "win32")
|
|
138
|
-
void import_fs.default.promises.unlink(this._config.socketPath).catch(() => {
|
|
139
|
-
}).then(() => resolve({ error }));
|
|
140
|
-
else
|
|
141
|
-
resolve({ error });
|
|
142
|
-
});
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
async canConnect() {
|
|
146
|
-
const { socket } = await this._connect();
|
|
147
|
-
if (socket) {
|
|
148
|
-
socket.destroy();
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
async _startDaemonIfNeeded() {
|
|
154
|
-
if (this._connection)
|
|
155
|
-
return this._connection;
|
|
156
|
-
let { socket } = await this._connect();
|
|
157
|
-
if (!socket)
|
|
158
|
-
socket = await this._startDaemon();
|
|
159
|
-
this._connection = new import_socketConnection.SocketConnection(socket, this._config.version);
|
|
160
|
-
this._connection.onmessage = (message) => this._onMessage(message);
|
|
161
|
-
this._connection.onclose = () => this.disconnect();
|
|
162
|
-
return this._connection;
|
|
163
|
-
}
|
|
164
|
-
_onMessage(object) {
|
|
165
|
-
if (object.id && this._callbacks.has(object.id)) {
|
|
166
|
-
const callback = this._callbacks.get(object.id);
|
|
167
|
-
this._callbacks.delete(object.id);
|
|
168
|
-
if (object.error)
|
|
169
|
-
callback.reject(new Error(object.error));
|
|
170
|
-
else
|
|
171
|
-
callback.resolve(object.result);
|
|
172
|
-
} else if (object.id) {
|
|
173
|
-
throw new Error(`Unexpected message id: ${object.id}`);
|
|
174
|
-
} else {
|
|
175
|
-
throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`);
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
_sessionFile(suffix) {
|
|
179
|
-
return import_path.default.resolve(this._clientInfo.daemonProfilesDir, `${this.name}${suffix}`);
|
|
180
|
-
}
|
|
181
|
-
async _startDaemon() {
|
|
182
|
-
await import_fs.default.promises.mkdir(this._clientInfo.daemonProfilesDir, { recursive: true });
|
|
183
|
-
const cliPath = import_path.default.join(__dirname, "../../../cli.js");
|
|
184
|
-
const sessionConfigFile = this._sessionFile(".session");
|
|
185
|
-
this._config.version = this._clientInfo.version;
|
|
186
|
-
await import_fs.default.promises.writeFile(sessionConfigFile, JSON.stringify(this._config, null, 2));
|
|
187
|
-
const errLog = this._sessionFile(".err");
|
|
188
|
-
const err = import_fs.default.openSync(errLog, "w");
|
|
189
|
-
const args = [
|
|
190
|
-
cliPath,
|
|
191
|
-
"run-mcp-server",
|
|
192
|
-
`--daemon-session=${sessionConfigFile}`
|
|
193
|
-
];
|
|
194
|
-
const child = (0, import_child_process.spawn)(process.execPath, args, {
|
|
195
|
-
detached: true,
|
|
196
|
-
stdio: ["ignore", "pipe", err],
|
|
197
|
-
cwd: process.cwd()
|
|
198
|
-
// Will be used as root.
|
|
199
|
-
});
|
|
200
|
-
let signalled = false;
|
|
201
|
-
const sigintHandler = () => {
|
|
202
|
-
signalled = true;
|
|
203
|
-
child.kill("SIGINT");
|
|
204
|
-
};
|
|
205
|
-
const sigtermHandler = () => {
|
|
206
|
-
signalled = true;
|
|
207
|
-
child.kill("SIGTERM");
|
|
208
|
-
};
|
|
209
|
-
process.on("SIGINT", sigintHandler);
|
|
210
|
-
process.on("SIGTERM", sigtermHandler);
|
|
211
|
-
let outLog = "";
|
|
212
|
-
await new Promise((resolve, reject) => {
|
|
213
|
-
child.stdout.on("data", (data) => {
|
|
214
|
-
outLog += data.toString();
|
|
215
|
-
if (!outLog.includes("<EOF>"))
|
|
216
|
-
return;
|
|
217
|
-
const errorMatch = outLog.match(/### Error\n([\s\S]*)<EOF>/);
|
|
218
|
-
const error = errorMatch ? errorMatch[1].trim() : void 0;
|
|
219
|
-
if (error) {
|
|
220
|
-
const errLogContent = import_fs.default.readFileSync(errLog, "utf-8");
|
|
221
|
-
const message = error + (errLogContent ? "\n" + errLogContent : "");
|
|
222
|
-
reject(new Error(message));
|
|
223
|
-
}
|
|
224
|
-
const successMatch = outLog.match(/### Success\nDaemon listening on (.*)\n<EOF>/);
|
|
225
|
-
if (successMatch)
|
|
226
|
-
resolve();
|
|
227
|
-
});
|
|
228
|
-
child.on("close", (code) => {
|
|
229
|
-
if (!signalled)
|
|
230
|
-
reject(new Error(`Daemon process exited with code ${code}`));
|
|
231
|
-
});
|
|
232
|
-
});
|
|
233
|
-
process.off("SIGINT", sigintHandler);
|
|
234
|
-
process.off("SIGTERM", sigtermHandler);
|
|
235
|
-
child.stdout.destroy();
|
|
236
|
-
child.unref();
|
|
237
|
-
const { socket } = await this._connect();
|
|
238
|
-
if (socket) {
|
|
239
|
-
console.log(`### Browser \`${this.name}\` opened with pid ${child.pid}.`);
|
|
240
|
-
const resolvedConfig = await parseResolvedConfig(outLog);
|
|
241
|
-
if (resolvedConfig) {
|
|
242
|
-
this._config.resolvedConfig = resolvedConfig;
|
|
243
|
-
console.log(`- ${this.name}:`);
|
|
244
|
-
console.log(renderResolvedConfig(resolvedConfig).join("\n"));
|
|
245
|
-
}
|
|
246
|
-
console.log(`---`);
|
|
247
|
-
await import_fs.default.promises.writeFile(sessionConfigFile, JSON.stringify(this._config, null, 2));
|
|
248
|
-
return socket;
|
|
249
|
-
}
|
|
250
|
-
console.error(`Failed to connect to daemon at ${this._config.socketPath}`);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
async _stopDaemon() {
|
|
254
|
-
let error;
|
|
255
|
-
await this._send("stop").catch((e) => {
|
|
256
|
-
error = e;
|
|
257
|
-
});
|
|
258
|
-
if (import_os.default.platform() !== "win32")
|
|
259
|
-
await import_fs.default.promises.unlink(this._config.socketPath).catch(() => {
|
|
260
|
-
});
|
|
261
|
-
this.disconnect();
|
|
262
|
-
if (!this._config.cli.persistent)
|
|
263
|
-
await this.deleteSessionConfig();
|
|
264
|
-
if (error && !error?.message?.includes("Session closed"))
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
async deleteSessionConfig() {
|
|
268
|
-
await import_fs.default.promises.rm(this._sessionFile(".session")).catch(() => {
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
class SessionManager {
|
|
273
|
-
constructor(clientInfo, sessions) {
|
|
274
|
-
this.clientInfo = clientInfo;
|
|
275
|
-
this.sessions = sessions;
|
|
276
|
-
}
|
|
277
|
-
static async create(clientInfo) {
|
|
278
|
-
const dir = clientInfo.daemonProfilesDir;
|
|
279
|
-
const sessions = /* @__PURE__ */ new Map();
|
|
280
|
-
const files = await import_fs.default.promises.readdir(dir).catch(() => []);
|
|
281
|
-
for (const file of files) {
|
|
282
|
-
if (!file.endsWith(".session"))
|
|
283
|
-
continue;
|
|
284
|
-
try {
|
|
285
|
-
const sessionName = import_path.default.basename(file, ".session");
|
|
286
|
-
const sessionConfig = await import_fs.default.promises.readFile(import_path.default.join(dir, file), "utf-8").then((data) => JSON.parse(data));
|
|
287
|
-
sessions.set(sessionName, new Session(clientInfo, sessionName, sessionConfig));
|
|
288
|
-
} catch {
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
return new SessionManager(clientInfo, sessions);
|
|
292
|
-
}
|
|
293
|
-
async open(args) {
|
|
294
|
-
const sessionName = this._resolveSessionName(args.session);
|
|
295
|
-
let session = this.sessions.get(sessionName);
|
|
296
|
-
if (session)
|
|
297
|
-
await session.stop(true);
|
|
298
|
-
session = new Session(this.clientInfo, sessionName, sessionConfigFromArgs(this.clientInfo, sessionName, args));
|
|
299
|
-
this.sessions.set(sessionName, session);
|
|
300
|
-
await this.run(args);
|
|
301
|
-
}
|
|
302
|
-
async run(args) {
|
|
303
|
-
const sessionName = this._resolveSessionName(args.session);
|
|
304
|
-
const session = this.sessions.get(sessionName);
|
|
305
|
-
if (!session) {
|
|
306
|
-
console.log(`The browser '${sessionName}' is not open, please run open first`);
|
|
307
|
-
console.log("");
|
|
308
|
-
console.log(` playwright-cli${sessionName !== "default" ? ` -s=${sessionName}` : ""} open [params]`);
|
|
309
|
-
process.exit(1);
|
|
310
|
-
}
|
|
311
|
-
for (const globalOption of globalOptions)
|
|
312
|
-
delete args[globalOption];
|
|
313
|
-
const result = await session.run(args);
|
|
314
|
-
console.log(result.text);
|
|
315
|
-
session.disconnect();
|
|
316
|
-
}
|
|
317
|
-
async close(options) {
|
|
318
|
-
const sessionName = this._resolveSessionName(options.session);
|
|
319
|
-
const session = this.sessions.get(sessionName);
|
|
320
|
-
if (!session || !await session.canConnect()) {
|
|
321
|
-
console.log(`Browser '${sessionName}' is not open.`);
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
324
|
-
await session.stop();
|
|
325
|
-
}
|
|
326
|
-
async deleteData(options) {
|
|
327
|
-
const sessionName = this._resolveSessionName(options.session);
|
|
328
|
-
const session = this.sessions.get(sessionName);
|
|
329
|
-
if (!session) {
|
|
330
|
-
console.log(`No user data found for browser '${sessionName}'.`);
|
|
331
|
-
return;
|
|
332
|
-
}
|
|
333
|
-
await session.deleteData();
|
|
334
|
-
this.sessions.delete(sessionName);
|
|
335
|
-
}
|
|
336
|
-
_resolveSessionName(sessionName) {
|
|
337
|
-
if (sessionName)
|
|
338
|
-
return sessionName;
|
|
339
|
-
if (process.env.PLAYWRIGHT_CLI_SESSION)
|
|
340
|
-
return process.env.PLAYWRIGHT_CLI_SESSION;
|
|
341
|
-
return "default";
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
function createClientInfo(packageLocation) {
|
|
345
|
-
const packageJSON = require(packageLocation);
|
|
346
|
-
const workspaceDir = findWorkspaceDir(process.cwd());
|
|
347
|
-
const version = process.env.PLAYWRIGHT_CLI_VERSION_FOR_TEST || packageJSON.version;
|
|
348
|
-
const hash = import_crypto.default.createHash("sha1");
|
|
349
|
-
hash.update(workspaceDir || packageLocation);
|
|
350
|
-
const workspaceDirHash = hash.digest("hex").substring(0, 16);
|
|
351
|
-
return {
|
|
352
|
-
version,
|
|
353
|
-
workspaceDir,
|
|
354
|
-
workspaceDirHash,
|
|
355
|
-
daemonProfilesDir: daemonProfilesDir(workspaceDirHash)
|
|
356
|
-
};
|
|
38
|
+
var import_registry = require("./registry");
|
|
39
|
+
var import_session = require("./session");
|
|
40
|
+
function resolveSessionName(sessionName) {
|
|
41
|
+
if (sessionName)
|
|
42
|
+
return sessionName;
|
|
43
|
+
if (process.env.PLAYWRIGHT_CLI_SESSION)
|
|
44
|
+
return process.env.PLAYWRIGHT_CLI_SESSION;
|
|
45
|
+
return "default";
|
|
357
46
|
}
|
|
358
|
-
function findWorkspaceDir(startDir) {
|
|
359
|
-
let dir = startDir;
|
|
360
|
-
for (let i = 0; i < 10; i++) {
|
|
361
|
-
if (import_fs.default.existsSync(import_path.default.join(dir, ".playwright")))
|
|
362
|
-
return dir;
|
|
363
|
-
const parentDir = import_path.default.dirname(dir);
|
|
364
|
-
if (parentDir === dir)
|
|
365
|
-
break;
|
|
366
|
-
dir = parentDir;
|
|
367
|
-
}
|
|
368
|
-
return void 0;
|
|
369
|
-
}
|
|
370
|
-
const baseDaemonDir = (() => {
|
|
371
|
-
if (process.env.PLAYWRIGHT_DAEMON_SESSION_DIR)
|
|
372
|
-
return process.env.PLAYWRIGHT_DAEMON_SESSION_DIR;
|
|
373
|
-
let localCacheDir;
|
|
374
|
-
if (process.platform === "linux")
|
|
375
|
-
localCacheDir = process.env.XDG_CACHE_HOME || import_path.default.join(import_os.default.homedir(), ".cache");
|
|
376
|
-
if (process.platform === "darwin")
|
|
377
|
-
localCacheDir = import_path.default.join(import_os.default.homedir(), "Library", "Caches");
|
|
378
|
-
if (process.platform === "win32")
|
|
379
|
-
localCacheDir = process.env.LOCALAPPDATA || import_path.default.join(import_os.default.homedir(), "AppData", "Local");
|
|
380
|
-
if (!localCacheDir)
|
|
381
|
-
throw new Error("Unsupported platform: " + process.platform);
|
|
382
|
-
return import_path.default.join(localCacheDir, "ms-playwright", "daemon");
|
|
383
|
-
})();
|
|
384
|
-
const daemonProfilesDir = (workspaceDirHash) => {
|
|
385
|
-
return import_path.default.join(baseDaemonDir, workspaceDirHash);
|
|
386
|
-
};
|
|
387
47
|
const globalOptions = [
|
|
388
48
|
"browser",
|
|
389
49
|
"config",
|
|
@@ -400,12 +60,12 @@ const booleanOptions = [
|
|
|
400
60
|
"help",
|
|
401
61
|
"version"
|
|
402
62
|
];
|
|
403
|
-
async function program(
|
|
404
|
-
const clientInfo = createClientInfo(
|
|
63
|
+
async function program() {
|
|
64
|
+
const clientInfo = (0, import_registry.createClientInfo)();
|
|
405
65
|
const help = require("./help.json");
|
|
406
66
|
const argv = process.argv.slice(2);
|
|
407
67
|
const boolean = [...help.booleanOptions, ...booleanOptions];
|
|
408
|
-
const args = require("minimist")(argv, { boolean });
|
|
68
|
+
const args = require("minimist")(argv, { boolean, string: ["_"] });
|
|
409
69
|
for (const [key, value] of Object.entries(args)) {
|
|
410
70
|
if (key !== "_" && typeof value !== "boolean")
|
|
411
71
|
args[key] = String(value);
|
|
@@ -445,38 +105,77 @@ async function program(packageLocation) {
|
|
|
445
105
|
console.log(help.global);
|
|
446
106
|
process.exit(1);
|
|
447
107
|
}
|
|
448
|
-
const
|
|
108
|
+
const registry = await import_registry.Registry.load();
|
|
109
|
+
const sessionName = resolveSessionName(args.session);
|
|
449
110
|
switch (commandName) {
|
|
450
111
|
case "list": {
|
|
451
|
-
|
|
452
|
-
await listAllSessions(clientInfo);
|
|
453
|
-
else
|
|
454
|
-
await listSessions(sessionManager);
|
|
112
|
+
await listSessions(registry, clientInfo, args.all);
|
|
455
113
|
return;
|
|
456
114
|
}
|
|
457
115
|
case "close-all": {
|
|
458
|
-
const
|
|
459
|
-
for (const
|
|
460
|
-
await
|
|
116
|
+
const entries = registry.entries(clientInfo);
|
|
117
|
+
for (const entry of entries)
|
|
118
|
+
await new import_session.Session(clientInfo, entry.config).stop(true);
|
|
461
119
|
return;
|
|
462
120
|
}
|
|
463
|
-
case "delete-data":
|
|
464
|
-
|
|
121
|
+
case "delete-data": {
|
|
122
|
+
const entry = registry.entry(clientInfo, sessionName);
|
|
123
|
+
if (!entry) {
|
|
124
|
+
console.log(`No user data found for browser '${sessionName}'.`);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
await new import_session.Session(clientInfo, entry.config).deleteData();
|
|
465
128
|
return;
|
|
466
|
-
|
|
129
|
+
}
|
|
130
|
+
case "kill-all": {
|
|
467
131
|
await killAllDaemons();
|
|
468
132
|
return;
|
|
469
|
-
|
|
470
|
-
|
|
133
|
+
}
|
|
134
|
+
case "open": {
|
|
135
|
+
const entry = registry.entry(clientInfo, sessionName);
|
|
136
|
+
if (entry)
|
|
137
|
+
await new import_session.Session(clientInfo, entry.config).stop(true);
|
|
138
|
+
const session2 = new import_session.Session(clientInfo, sessionConfigFromArgs(clientInfo, sessionName, args));
|
|
139
|
+
for (const globalOption of globalOptions)
|
|
140
|
+
delete args[globalOption];
|
|
141
|
+
const result = await session2.run(args);
|
|
142
|
+
console.log(result.text);
|
|
471
143
|
return;
|
|
144
|
+
}
|
|
472
145
|
case "close":
|
|
473
|
-
|
|
146
|
+
const closeEntry = registry.entry(clientInfo, sessionName);
|
|
147
|
+
const session = closeEntry ? new import_session.Session(clientInfo, closeEntry.config) : void 0;
|
|
148
|
+
if (!session || !await session.canConnect()) {
|
|
149
|
+
console.log(`Browser '${sessionName}' is not open.`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
await session.stop();
|
|
474
153
|
return;
|
|
475
154
|
case "install":
|
|
476
155
|
await install(args);
|
|
477
156
|
return;
|
|
478
|
-
|
|
479
|
-
|
|
157
|
+
case "show": {
|
|
158
|
+
const daemonScript = import_path.default.join(__dirname, "devtoolsApp.js");
|
|
159
|
+
const child = (0, import_child_process.spawn)(process.execPath, [daemonScript], {
|
|
160
|
+
detached: true,
|
|
161
|
+
stdio: "ignore"
|
|
162
|
+
});
|
|
163
|
+
child.unref();
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
default: {
|
|
167
|
+
const defaultEntry = registry.entry(clientInfo, sessionName);
|
|
168
|
+
if (!defaultEntry) {
|
|
169
|
+
console.log(`The browser '${sessionName}' is not open, please run open first`);
|
|
170
|
+
console.log("");
|
|
171
|
+
console.log(` playwright-cli${sessionName !== "default" ? ` -s=${sessionName}` : ""} open [params]`);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
for (const globalOption of globalOptions)
|
|
175
|
+
delete args[globalOption];
|
|
176
|
+
const result = await new import_session.Session(clientInfo, defaultEntry.config).run(args);
|
|
177
|
+
console.log(result.text);
|
|
178
|
+
}
|
|
480
179
|
}
|
|
481
180
|
}
|
|
482
181
|
async function install(args) {
|
|
@@ -561,7 +260,9 @@ function sessionConfigFromArgs(clientInfo, sessionName, args) {
|
|
|
561
260
|
if (!args.persistent && args.profile)
|
|
562
261
|
args.persistent = true;
|
|
563
262
|
return {
|
|
263
|
+
name: sessionName,
|
|
564
264
|
version: clientInfo.version,
|
|
265
|
+
timestamp: 0,
|
|
565
266
|
socketPath: daemonSocketPath(clientInfo, sessionName),
|
|
566
267
|
cli: {
|
|
567
268
|
headed: args.headed,
|
|
@@ -613,55 +314,34 @@ async function killAllDaemons() {
|
|
|
613
314
|
else if (killed > 0)
|
|
614
315
|
console.log(`Killed ${killed} daemon process${killed === 1 ? "" : "es"}.`);
|
|
615
316
|
}
|
|
616
|
-
async function listSessions(
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
const hashDir = import_path.default.join(baseDaemonDir, hash);
|
|
626
|
-
const stat = await import_fs.default.promises.stat(hashDir).catch(() => null);
|
|
627
|
-
if (!stat?.isDirectory())
|
|
628
|
-
continue;
|
|
629
|
-
const files = await import_fs.default.promises.readdir(hashDir).catch(() => []);
|
|
630
|
-
for (const file of files) {
|
|
631
|
-
if (!file.endsWith(".session"))
|
|
317
|
+
async function listSessions(registry, clientInfo, all) {
|
|
318
|
+
if (all) {
|
|
319
|
+
const entries = registry.entryMap();
|
|
320
|
+
if (entries.size === 0) {
|
|
321
|
+
console.log("No browsers found.");
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
for (const [workspace, list] of entries) {
|
|
325
|
+
if (!list.length)
|
|
632
326
|
continue;
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
const sessionConfig = await import_fs.default.promises.readFile(import_path.default.join(hashDir, file), "utf-8").then((data) => JSON.parse(data));
|
|
636
|
-
const session = new Session(clientInfo, sessionName, sessionConfig);
|
|
637
|
-
const workspaceKey = sessionConfig.workspaceDir || "<global>";
|
|
638
|
-
if (!sessionsByWorkspace.has(workspaceKey))
|
|
639
|
-
sessionsByWorkspace.set(workspaceKey, []);
|
|
640
|
-
sessionsByWorkspace.get(workspaceKey).push(session);
|
|
641
|
-
} catch {
|
|
642
|
-
}
|
|
327
|
+
console.log(`${workspace}:`);
|
|
328
|
+
await gcAndPrintSessions(list.map((entry) => new import_session.Session(clientInfo, entry.config)));
|
|
643
329
|
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
const sortedWorkspaces = [...sessionsByWorkspace.keys()].sort();
|
|
650
|
-
for (const workspace of sortedWorkspaces) {
|
|
651
|
-
const sessions = sessionsByWorkspace.get(workspace);
|
|
652
|
-
console.log(`${workspace}:`);
|
|
653
|
-
await gcAndPrintSessions(sessions);
|
|
330
|
+
} else {
|
|
331
|
+
console.log("### Browsers");
|
|
332
|
+
const entries = registry.entries(clientInfo);
|
|
333
|
+
await gcAndPrintSessions(entries.map((entry) => new import_session.Session(clientInfo, entry.config)));
|
|
654
334
|
}
|
|
655
335
|
}
|
|
656
336
|
async function gcAndPrintSessions(sessions) {
|
|
657
337
|
const running = [];
|
|
658
338
|
const stopped = [];
|
|
659
|
-
for (const session of sessions
|
|
339
|
+
for (const session of sessions) {
|
|
660
340
|
const canConnect = await session.canConnect();
|
|
661
341
|
if (canConnect) {
|
|
662
342
|
running.push(session);
|
|
663
343
|
} else {
|
|
664
|
-
if (session.config
|
|
344
|
+
if (session.config.cli.persistent)
|
|
665
345
|
stopped.push(session);
|
|
666
346
|
else
|
|
667
347
|
await session.deleteSessionConfig();
|
|
@@ -676,44 +356,16 @@ async function gcAndPrintSessions(sessions) {
|
|
|
676
356
|
}
|
|
677
357
|
async function renderSessionStatus(session) {
|
|
678
358
|
const text = [];
|
|
679
|
-
const config = session.config
|
|
359
|
+
const config = session.config;
|
|
680
360
|
const canConnect = await session.canConnect();
|
|
681
361
|
text.push(`- ${session.name}:`);
|
|
682
362
|
text.push(` - status: ${canConnect ? "open" : "closed"}`);
|
|
683
363
|
if (canConnect && !session.isCompatible())
|
|
684
364
|
text.push(` - version: v${config.version} [incompatible please re-open]`);
|
|
685
365
|
if (config.resolvedConfig)
|
|
686
|
-
text.push(...renderResolvedConfig(config.resolvedConfig));
|
|
366
|
+
text.push(...(0, import_session.renderResolvedConfig)(config.resolvedConfig));
|
|
687
367
|
return text.join("\n");
|
|
688
368
|
}
|
|
689
|
-
function renderResolvedConfig(resolvedConfig) {
|
|
690
|
-
const channel = resolvedConfig.browser.launchOptions.channel ?? resolvedConfig.browser.browserName;
|
|
691
|
-
const lines = [];
|
|
692
|
-
if (channel)
|
|
693
|
-
lines.push(` - browser-type: ${channel}`);
|
|
694
|
-
if (resolvedConfig.browser.isolated)
|
|
695
|
-
lines.push(` - user-data-dir: <in-memory>`);
|
|
696
|
-
else
|
|
697
|
-
lines.push(` - user-data-dir: ${resolvedConfig.browser.userDataDir}`);
|
|
698
|
-
lines.push(` - headed: ${!resolvedConfig.browser.launchOptions.headless}`);
|
|
699
|
-
return lines;
|
|
700
|
-
}
|
|
701
|
-
async function parseResolvedConfig(errLog) {
|
|
702
|
-
const marker = "### Config\n```json\n";
|
|
703
|
-
const markerIndex = errLog.indexOf(marker);
|
|
704
|
-
if (markerIndex === -1)
|
|
705
|
-
return null;
|
|
706
|
-
const jsonStart = markerIndex + marker.length;
|
|
707
|
-
const jsonEnd = errLog.indexOf("\n```", jsonStart);
|
|
708
|
-
if (jsonEnd === -1)
|
|
709
|
-
throw null;
|
|
710
|
-
const jsonString = errLog.substring(jsonStart, jsonEnd).trim();
|
|
711
|
-
try {
|
|
712
|
-
return JSON.parse(jsonString);
|
|
713
|
-
} catch {
|
|
714
|
-
return null;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
369
|
// Annotate the CommonJS export names for ESM import in node:
|
|
718
370
|
0 && (module.exports = {
|
|
719
371
|
program
|