playwright-core
Version:
A high-level API to automate web browsers
262 lines (257 loc) • 9.65 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var session_exports = {};
__export(session_exports, {
Session: () => Session
});
module.exports = __toCommonJS(session_exports);
var import_child_process = require("child_process");
var import_fs = __toESM(require("fs"));
var import_net = __toESM(require("net"));
var import_os = __toESM(require("os"));
var import_path = __toESM(require("path"));
var import_package = require("../../package");
var import_socketConnection = require("../utils/socketConnection");
var import_registry = require("./registry");
class Session {
constructor(sessionFile) {
this.config = sessionFile.config;
this.name = this.config.name;
this._sessionFile = sessionFile;
}
isCompatible(clientInfo) {
return (0, import_socketConnection.compareSemver)(clientInfo.version, this.config.version) >= 0;
}
async run(clientInfo, args, options) {
if (!this.isCompatible(clientInfo))
throw new Error(`Client is v${clientInfo.version}, session '${this.name}' is v${this.config.version}. Run
playwright-cli${this.name !== "default" ? ` -s=${this.name}` : ""} open
to restart the browser session.`);
const { socket } = await this._connect();
if (!socket)
throw new Error(`Browser '${this.name}' is not open. Run
playwright-cli${this.name !== "default" ? ` -s=${this.name}` : ""} open
to start the browser session.`);
return await SocketConnectionClient.sendAndClose(socket, "run", { args, cwd: process.cwd(), raw: options?.raw, json: options?.json });
}
async stop() {
if (!await this.canConnect())
return { wasOpen: false };
await this._stopDaemon();
return { wasOpen: true };
}
async deleteData() {
await this.stop();
const dataDirs = await import_fs.default.promises.readdir(this._sessionFile.daemonDir).catch(() => []);
const matchingEntries = dataDirs.filter((file) => file === `${this.name}.session` || file.startsWith(`ud-${this.name}-`));
if (matchingEntries.length === 0)
return { existed: false, deletedUserDataDir: false };
let deletedUserDataDir = false;
for (const entry of matchingEntries) {
const userDataDir = import_path.default.resolve(this._sessionFile.daemonDir, entry);
for (let i = 0; i < 5; i++) {
try {
await import_fs.default.promises.rm(userDataDir, { recursive: true });
if (entry.startsWith("ud-"))
deletedUserDataDir = true;
break;
} catch (e) {
if (e.code === "ENOENT")
break;
await new Promise((resolve) => setTimeout(resolve, 1e3));
if (i === 4)
throw e;
}
}
}
return { existed: true, deletedUserDataDir };
}
async _connect() {
return await new Promise((resolve) => {
const socket = import_net.default.createConnection(this.config.socketPath, () => {
resolve({ socket });
});
socket.on("error", (error) => {
if (import_os.default.platform() !== "win32")
void import_fs.default.promises.unlink(this.config.socketPath).catch(() => {
}).then(() => resolve({ error }));
else
resolve({ error });
});
});
}
async canConnect() {
const { socket } = await this._connect();
if (socket) {
socket.destroy();
return true;
}
return false;
}
static async startDaemon(clientInfo, cliArgs, mode) {
await import_fs.default.promises.mkdir(clientInfo.daemonProfilesDir, { recursive: true });
const cliPath = (0, import_package.libPath)("entry", "cliDaemon.js");
const sessionName = (0, import_registry.resolveSessionName)(cliArgs.session);
const errLog = import_path.default.join(clientInfo.daemonProfilesDir, sessionName + ".err");
const err = import_fs.default.openSync(errLog, "w");
const args = [
cliPath,
sessionName
];
if (cliArgs.headed)
args.push("--headed");
if (cliArgs.mobile)
args.push("--mobile");
if (cliArgs.device)
args.push(`--device=${cliArgs.device}`);
if (cliArgs.browser)
args.push(`--browser=${cliArgs.browser}`);
if (cliArgs.persistent)
args.push("--persistent");
if (cliArgs.profile)
args.push(`--profile=${cliArgs.profile}`);
if (cliArgs.config)
args.push(`--config=${cliArgs.config}`);
if (cliArgs["idle-timeout"] !== void 0)
args.push(`--idle-timeout=${cliArgs["idle-timeout"]}`);
if (cliArgs.extension)
args.push("--extension");
else if (cliArgs.cdp)
args.push(`--cdp=${cliArgs.cdp}`);
else if (cliArgs.endpoint)
args.push(`--endpoint=${cliArgs.endpoint}`);
const child = (0, import_child_process.spawn)(process.execPath, args, {
detached: true,
stdio: ["ignore", "pipe", err],
cwd: process.cwd(),
// Will be used as root.
windowsHide: true
});
let signalled = false;
const sigintHandler = () => {
signalled = true;
child.kill("SIGINT");
};
const sigtermHandler = () => {
signalled = true;
child.kill("SIGTERM");
};
process.on("SIGINT", sigintHandler);
process.on("SIGTERM", sigtermHandler);
let outLog = "";
const rejectWithPid = (reject, message) => reject(Object.assign(new Error(`Daemon pid=${child.pid}: ${message}`), { daemonPid: child.pid }));
await new Promise((resolve, reject) => {
child.stdout.on("data", (data) => {
outLog += data.toString();
if (outLog.includes("Daemon listening on"))
resolve();
});
child.on("close", (code) => {
if (!signalled) {
const errLogContent = import_fs.default.readFileSync(errLog, "utf-8");
rejectWithPid(reject, `Daemon process exited with code ${code}` + (outLog ? "\n" + outLog : "") + (errLogContent ? "\n" + errLogContent : ""));
}
});
});
process.off("SIGINT", sigintHandler);
process.off("SIGTERM", sigtermHandler);
child.stdout.destroy();
child.unref();
return { pid: child.pid, sessionName, endpoint: cliArgs.endpoint };
}
async _stopDaemon() {
const { socket } = await this._connect();
if (!socket)
return;
let error;
await SocketConnectionClient.sendAndClose(socket, "stop", {}).catch((e) => error = e);
if (error && !error?.message?.includes("Session closed"))
throw error;
}
async deleteSessionConfig() {
await import_fs.default.promises.rm(this._sessionFile.file).catch(() => {
});
}
}
class SocketConnectionClient {
constructor(socket) {
this._nextMessageId = 1;
this._callbacks = /* @__PURE__ */ new Map();
this._connection = new import_socketConnection.SocketConnection(socket);
this._connection.onmessage = (message) => this._onMessage(message);
this._connection.onclose = () => this._rejectCallbacks();
}
async send(method, params = {}) {
const messageId = this._nextMessageId++;
const message = {
id: messageId,
method,
params
};
const responsePromise = new Promise((resolve, reject) => {
this._callbacks.set(messageId, { resolve, reject, method, params });
});
const [result] = await Promise.all([responsePromise, this._connection.send(message)]);
return result;
}
static async sendAndClose(socket, method, params = {}) {
const connection = new SocketConnectionClient(socket);
try {
return await connection.send(method, params);
} finally {
connection.close();
}
}
close() {
this._connection.close();
}
_onMessage(object) {
if (object.id && this._callbacks.has(object.id)) {
const callback = this._callbacks.get(object.id);
this._callbacks.delete(object.id);
if (object.error)
callback.reject(new Error(object.error));
else
callback.resolve(object.result);
} else if (object.id) {
throw new Error(`Unexpected message id: ${object.id}`);
} else {
throw new Error(`Unexpected message without id: ${JSON.stringify(object)}`);
}
}
_rejectCallbacks() {
for (const callback of this._callbacks.values())
callback.reject(new Error("Session closed"));
this._callbacks.clear();
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Session
});