@rushstack/playwright-browser-tunnel 0.3.4 → 0.3.6
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/CHANGELOG.json +47 -0
- package/CHANGELOG.md +13 -1
- package/dist/playwright-browser-tunnel.d.ts +2 -3
- package/lib-commonjs/PlaywrightBrowserTunnel.js +1 -9
- package/lib-commonjs/PlaywrightBrowserTunnel.js.map +1 -1
- package/lib-commonjs/tunneledBrowserConnection/TunneledBrowserConnection.js +1 -2
- package/lib-commonjs/tunneledBrowserConnection/TunneledBrowserConnection.js.map +1 -1
- package/lib-dts/PlaywrightBrowserTunnel.d.ts +1 -2
- package/lib-dts/PlaywrightBrowserTunnel.d.ts.map +1 -1
- package/lib-dts/tsdoc-metadata.json +1 -1
- package/lib-dts/tunneledBrowserConnection/TunneledBrowserConnection.d.ts +1 -1
- package/lib-dts/tunneledBrowserConnection/TunneledBrowserConnection.d.ts.map +1 -1
- package/lib-esm/PlaywrightBrowserTunnel.js +1 -6
- package/lib-esm/PlaywrightBrowserTunnel.js.map +1 -1
- package/lib-esm/tunneledBrowserConnection/TunneledBrowserConnection.js +1 -2
- package/lib-esm/tunneledBrowserConnection/TunneledBrowserConnection.js.map +1 -1
- package/package.json +6 -7
package/CHANGELOG.json
CHANGED
|
@@ -1,6 +1,53 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rushstack/playwright-browser-tunnel",
|
|
3
3
|
"entries": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.3.6",
|
|
6
|
+
"tag": "@rushstack/playwright-browser-tunnel_v0.3.6",
|
|
7
|
+
"date": "Wed, 25 Feb 2026 00:34:29 GMT",
|
|
8
|
+
"comments": {
|
|
9
|
+
"patch": [
|
|
10
|
+
{
|
|
11
|
+
"comment": "Add playwright-versioning and remove semver"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"dependency": [
|
|
15
|
+
{
|
|
16
|
+
"comment": "Updating dependency \"@rushstack/node-core-library\" to `5.20.3`"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"comment": "Updating dependency \"@rushstack/terminal\" to `0.22.3`"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"comment": "Updating dependency \"@rushstack/ts-command-line\" to `5.3.3`"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"comment": "Updating dependency \"@rushstack/heft\" to `1.2.5`"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"version": "0.3.5",
|
|
32
|
+
"tag": "@rushstack/playwright-browser-tunnel_v0.3.5",
|
|
33
|
+
"date": "Tue, 24 Feb 2026 01:13:27 GMT",
|
|
34
|
+
"comments": {
|
|
35
|
+
"dependency": [
|
|
36
|
+
{
|
|
37
|
+
"comment": "Updating dependency \"@rushstack/node-core-library\" to `5.20.2`"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"comment": "Updating dependency \"@rushstack/terminal\" to `0.22.2`"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"comment": "Updating dependency \"@rushstack/ts-command-line\" to `5.3.2`"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"comment": "Updating dependency \"@rushstack/heft\" to `1.2.4`"
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
}
|
|
50
|
+
},
|
|
4
51
|
{
|
|
5
52
|
"version": "0.3.4",
|
|
6
53
|
"tag": "@rushstack/playwright-browser-tunnel_v0.3.4",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
# Change Log - @rushstack/playwright-browser-tunnel
|
|
2
2
|
|
|
3
|
-
This log was last generated on
|
|
3
|
+
This log was last generated on Wed, 25 Feb 2026 00:34:29 GMT and should not be manually modified.
|
|
4
|
+
|
|
5
|
+
## 0.3.6
|
|
6
|
+
Wed, 25 Feb 2026 00:34:29 GMT
|
|
7
|
+
|
|
8
|
+
### Patches
|
|
9
|
+
|
|
10
|
+
- Add playwright-versioning and remove semver
|
|
11
|
+
|
|
12
|
+
## 0.3.5
|
|
13
|
+
Tue, 24 Feb 2026 01:13:27 GMT
|
|
14
|
+
|
|
15
|
+
_Version update only_
|
|
4
16
|
|
|
5
17
|
## 0.3.4
|
|
6
18
|
Mon, 23 Feb 2026 00:42:21 GMT
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
import type { Browser } from 'playwright-core';
|
|
18
18
|
import { ITerminal } from '@rushstack/terminal';
|
|
19
19
|
import type { LaunchOptions } from 'playwright-core';
|
|
20
|
-
import semver from 'semver';
|
|
21
20
|
|
|
22
21
|
/**
|
|
23
22
|
* Allowed Playwright browser names.
|
|
@@ -87,7 +86,7 @@ export declare interface IHandshake {
|
|
|
87
86
|
action: 'handshake';
|
|
88
87
|
browserName: BrowserName;
|
|
89
88
|
launchOptions: LaunchOptions;
|
|
90
|
-
playwrightVersion:
|
|
89
|
+
playwrightVersion: string;
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
/**
|
|
@@ -265,7 +264,7 @@ export declare class PlaywrightTunnel {
|
|
|
265
264
|
* Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.
|
|
266
265
|
* @beta
|
|
267
266
|
*/
|
|
268
|
-
export declare function tunneledBrowserConnection(logger: ITerminal, port?: number): Promise<IDisposableTunneledBrowserConnection>;
|
|
267
|
+
export declare function tunneledBrowserConnection(logger: ITerminal, port?: number, playwrightVersion?: string): Promise<IDisposableTunneledBrowserConnection>;
|
|
269
268
|
|
|
270
269
|
/**
|
|
271
270
|
* Status values reported by {@link PlaywrightTunnel}.
|
|
@@ -34,14 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
return result;
|
|
35
35
|
};
|
|
36
36
|
})();
|
|
37
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
38
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
39
|
-
};
|
|
40
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
41
38
|
exports.PlaywrightTunnel = void 0;
|
|
42
39
|
const node_events_1 = require("node:events");
|
|
43
40
|
const ws_1 = require("ws");
|
|
44
|
-
const semver_1 = __importDefault(require("semver"));
|
|
45
41
|
const terminal_1 = require("@rushstack/terminal");
|
|
46
42
|
const node_core_library_1 = require("@rushstack/node-core-library");
|
|
47
43
|
const utilities_1 = require("./utilities");
|
|
@@ -320,17 +316,13 @@ class PlaywrightTunnel {
|
|
|
320
316
|
if (action !== 'handshake') {
|
|
321
317
|
throw new Error(`Invalid action: ${action}. Expected 'handshake'.`);
|
|
322
318
|
}
|
|
323
|
-
const playwrightVersionSemver = semver_1.default.coerce(playwrightVersion);
|
|
324
|
-
if (!playwrightVersionSemver) {
|
|
325
|
-
throw new Error(`Invalid Playwright version: ${playwrightVersion}. Must be a valid semver version.`);
|
|
326
|
-
}
|
|
327
319
|
if (!isValidBrowserName(browserName)) {
|
|
328
320
|
throw new Error(`Invalid browser name: ${browserName}. Must be one of ${Array.from(validBrowserNames).join(', ')}.`);
|
|
329
321
|
}
|
|
330
322
|
return {
|
|
331
323
|
action,
|
|
332
324
|
launchOptions: launchOptions,
|
|
333
|
-
playwrightVersion
|
|
325
|
+
playwrightVersion,
|
|
334
326
|
browserName
|
|
335
327
|
};
|
|
336
328
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaywrightBrowserTunnel.js","sourceRoot":"","sources":["../src/PlaywrightBrowserTunnel.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\n\nimport type { BrowserServer, BrowserType, LaunchOptions } from 'playwright-core';\nimport { type RawData, WebSocket, type WebSocketServer } from 'ws';\nimport semver from 'semver';\n\nimport { TerminalProviderSeverity, TerminalStreamWritable, type ITerminal } from '@rushstack/terminal';\nimport { Executable, FileSystem, Async } from '@rushstack/node-core-library';\n\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from './utilities';\nimport { LaunchOptionsValidator, type ILaunchOptionsValidationResult } from './LaunchOptionsValidator';\n\n/**\n * Allowed Playwright browser names.\n * @beta\n */\nexport type BrowserName = 'chromium' | 'firefox' | 'webkit';\nconst validBrowserNames: Set<string> = new Set(['chromium', 'firefox', 'webkit'] satisfies BrowserName[]);\nfunction isValidBrowserName(browserName: string): browserName is BrowserName {\n return validBrowserNames.has(browserName);\n}\n\n/**\n * Status values reported by {@link PlaywrightTunnel}.\n * @beta\n */\nexport type TunnelStatus =\n | 'waiting-for-connection'\n | 'browser-server-running'\n | 'stopped'\n | 'setting-up-browser-server'\n | 'error';\n\n/**\n * Handshake data exchanged during the initial WebSocket connection.\n * @beta\n */\nexport interface IHandshake {\n action: 'handshake';\n browserName: BrowserName;\n launchOptions: LaunchOptions;\n playwrightVersion: semver.SemVer;\n}\n\ntype TunnelMode = 'poll-connection' | 'wait-for-incoming-connection';\n\n/**\n * Options for configuring a {@link PlaywrightTunnel} instance.\n * @beta\n */\nexport type IPlaywrightTunnelOptions = {\n terminal: ITerminal;\n onStatusChange: (status: TunnelStatus) => void;\n playwrightInstallPath: string;\n /**\n * Optional callback invoked before launching the browser server.\n * Receives the handshake data including launch options.\n * If the callback returns false, the browser server launch will be aborted.\n * This allows the client to prompt the user for approval before starting.\n */\n onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n} & (\n | {\n mode: 'poll-connection';\n wsEndpoint: string;\n }\n | {\n mode: 'wait-for-incoming-connection';\n listenPort: number;\n }\n);\n\ninterface IBrowserServerProxy {\n browserServer: BrowserServer;\n client: WebSocket;\n}\n\n/**\n * Hosts a Playwright browser server and forwards traffic over a WebSocket tunnel.\n * @beta\n */\nexport class PlaywrightTunnel {\n private readonly _terminal: ITerminal;\n private readonly _onStatusChange: (status: TunnelStatus) => void;\n private readonly _onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n private readonly _playwrightBrowsersInstalled: Set<string> = new Set();\n private readonly _wsEndpoint: string | undefined;\n private readonly _listenPort: number | undefined;\n private readonly _playwrightInstallPath: string;\n private _status: TunnelStatus = 'stopped';\n private _initWsPromise?: Promise<WebSocket>;\n private _keepRunning: boolean = false;\n private _ws?: WebSocket;\n private _mode: TunnelMode;\n private _pendingConnectionAttempt?: Promise<WebSocket>;\n private _pollInterval?: NodeJS.Timeout;\n\n public constructor(options: IPlaywrightTunnelOptions) {\n const { mode, terminal, onStatusChange, playwrightInstallPath, onBeforeLaunch } = options;\n\n switch (mode) {\n case 'poll-connection':\n if (!options.wsEndpoint) {\n throw new Error('wsEndpoint is required for poll-connection mode');\n }\n this._wsEndpoint = options.wsEndpoint;\n this._listenPort = undefined;\n break;\n case 'wait-for-incoming-connection':\n if (options.listenPort === undefined) {\n throw new Error('listenPort is required for wait-for-incoming-connection mode');\n }\n this._wsEndpoint = undefined;\n this._listenPort = options.listenPort;\n break;\n default:\n throw new Error(`Invalid mode: ${mode}`);\n }\n\n this._mode = mode;\n this._terminal = terminal;\n this._onStatusChange = onStatusChange;\n this._onBeforeLaunch = onBeforeLaunch;\n this._playwrightInstallPath = playwrightInstallPath;\n }\n\n public get status(): TunnelStatus {\n return this._status;\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n private set status(newStatus: TunnelStatus) {\n this._status = newStatus;\n this._onStatusChange(newStatus);\n }\n\n public async waitForCloseAsync(): Promise<void> {\n const terminal: ITerminal = this._terminal;\n const initWsPromise: Promise<WebSocket> | undefined = this._initWsPromise;\n if (initWsPromise) {\n const ws: WebSocket = await initWsPromise;\n await once(ws, 'close');\n terminal.writeDebugLine('WebSocket connection closed. resolving init promise.');\n this._initWsPromise = undefined;\n }\n }\n\n public async startAsync(options: { keepRunning?: boolean } = {}): Promise<void> {\n this._keepRunning = options.keepRunning ?? true;\n const terminal: ITerminal = this._terminal;\n terminal.writeLine(`keepRunning: ${this._keepRunning}`);\n while (this._keepRunning) {\n if (!this._initWsPromise) {\n this._initWsPromise = this._initPlaywrightBrowserTunnelAsync();\n } else {\n terminal.writeLine(`Tunnel is already running with status: ${this.status}`);\n }\n await this.waitForCloseAsync();\n }\n }\n\n public async stopAsync(): Promise<void> {\n this._keepRunning = false;\n if (this._pollInterval) {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n }\n await this._initWsPromise?.finally(() => {\n this._ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel stopped');\n });\n }\n\n public async [Symbol.asyncDispose](): Promise<void> {\n this._terminal.writeLine('Disposing WebSocket connection.');\n await this.stopAsync();\n }\n\n public async cleanTempFilesAsync(): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n this._terminal.writeLine(`Cleaning up temporary files in ${tmpPath}`);\n try {\n await FileSystem.ensureEmptyFolderAsync(tmpPath);\n this._terminal.writeLine(`Temporary files cleaned up.`);\n } catch (error) {\n this._terminal.writeLine(`Failed to clean up temporary files: ${getNormalizedErrorString(error)}`);\n }\n }\n\n // TODO: We should implement an uninstall command to remove installed Playwright browsers\n // public async uninstallPlaywrightBrowsersAsync(): Promise<void> {}\n\n private async _runCommandAsync(command: string, args: string[]): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n await FileSystem.ensureFolderAsync(tmpPath);\n this._terminal.writeLine(`Running command: ${command} ${args.join(' ')} in ${tmpPath}`);\n\n const cp: ChildProcess = Executable.spawn(command, args, {\n stdio: [\n 'ignore', // stdin\n 'pipe', // stdout\n 'pipe' // stderr\n ],\n currentWorkingDirectory: tmpPath\n });\n\n cp.stdout?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.log\n })\n );\n cp.stderr?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.error\n })\n );\n\n await Executable.waitForExitAsync(cp, { throwOnNonZeroExitCode: true, throwOnSignal: true });\n }\n\n private async _installPlaywrightCoreAsync({\n playwrightVersion\n }: Pick<IHandshake, 'playwrightVersion'>): Promise<void> {\n this._terminal.writeLine(`Installing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('npm', [\n 'install',\n `playwright-core-${playwrightVersion}@npm:playwright-core@${playwrightVersion}`\n ]);\n }\n\n private async _installPlaywrightBrowsersAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<void> {\n await this._installPlaywrightCoreAsync({ playwrightVersion });\n this._terminal.writeLine(`Executing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('node', [\n `node_modules/playwright-core-${playwrightVersion}/cli.js`,\n 'install',\n browserName\n ]);\n }\n\n private async _tryConnectAsync(): Promise<WebSocket> {\n const wsEndpoint: string | undefined = this._wsEndpoint;\n if (!wsEndpoint) {\n throw new Error('WebSocket endpoint is not defined');\n }\n return await new Promise<WebSocket>((resolve, reject) => {\n const ws: WebSocket = new WebSocket(wsEndpoint);\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection opened`);\n resolve(ws);\n });\n ws.once('error', (error) => {\n reject(error);\n });\n });\n }\n\n // TODO: Only supporting one test at a time.\n // Need to support multiple simultaneous connections for parallel tests.\n private async _pollConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine(`Waiting for WebSocket connection`);\n return await new Promise((resolve, reject) => {\n this._pollInterval = setInterval(() => {\n if (this._pendingConnectionAttempt) {\n return; // Skip if a connection attempt is already in progress\n }\n const connectionPromise: Promise<WebSocket> = this._tryConnectAsync();\n this._pendingConnectionAttempt = connectionPromise;\n connectionPromise\n .then((ws: WebSocket) => {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n ws.removeAllListeners();\n this._pendingConnectionAttempt = undefined;\n resolve(ws);\n })\n .catch(() => {\n // no-op - will retry on next interval\n this._pendingConnectionAttempt = undefined;\n });\n }, 500);\n });\n }\n\n private async _waitForIncomingConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine('Waiting for incoming WebSocket connection');\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const server: WebSocketServer = new WebSocket.Server({ port: this._listenPort });\n\n const cleanup = (): void => {\n server.removeAllListeners();\n };\n\n server.once('connection', (ws) => {\n this._terminal.writeLine('Incoming WebSocket connection established');\n\n // Stop listening immediately so the port is released\n cleanup();\n server.close((closeError?: Error) => {\n if (closeError) {\n this._terminal.writeLine(\n `Failed to close WebSocket server: ${\n closeError instanceof Error ? closeError.message : closeError\n }`\n );\n }\n resolve(ws);\n });\n });\n\n server.once('error', (error) => {\n this._terminal.writeLine(`WebSocket server error: ${getNormalizedErrorString(error)}`);\n\n cleanup();\n // Try to close (best-effort), then reject\n server.close(() => reject(error));\n });\n });\n }\n\n // TODO: If a user runs this for the first time, `this._playwrightBrowsersInstalled` will be empty\n // and it will try to install the browsers every time. We should persist this information. Maybe a cache file with text per\n // machine instance?\n private async _setupPlaywrightAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<typeof import('playwright-core')> {\n const browserKey: string = `${playwrightVersion}-${browserName}`;\n this._terminal.writeLine(`Checking for installed playwright browsers. Installed browsers: ${browserKey}`);\n if (!this._playwrightBrowsersInstalled.has(browserKey)) {\n this._terminal.writeLine(\n `Playwright browser not found. Installing playwright-core version ${playwrightVersion}`\n );\n await this._installPlaywrightBrowsersAsync({ playwrightVersion, browserName });\n this._playwrightBrowsersInstalled.add(browserKey);\n }\n\n this._terminal.writeLine(`Using playwright-core version ${playwrightVersion} for browser server`);\n return await import(`${this._playwrightInstallPath}/node_modules/playwright-core-${playwrightVersion}`);\n }\n\n private async _getPlaywrightBrowserServerProxyAsync({\n browserName,\n playwrightVersion,\n launchOptions\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName' | 'launchOptions'>): Promise<IBrowserServerProxy> {\n const terminal: ITerminal = this._terminal;\n\n // Validate launch options against security allowlist\n terminal.writeLine('Validating launch options against security allowlist...');\n const validationResult: ILaunchOptionsValidationResult =\n await LaunchOptionsValidator.validateLaunchOptionsAsync(launchOptions, terminal);\n\n if (!validationResult.isValid) {\n terminal.writeWarningLine(\n `Some launch options were denied: ${validationResult.deniedOptions.join(', ')}`\n );\n terminal.writeWarningLine(`Using filtered launch options. Denied options have been removed.`);\n }\n\n // Use filtered options and ensure headless: false for headed tests in codespaces\n // This is critical for the extension's purpose - enabling headed Playwright tests remotely\n const safeOptions: LaunchOptions = {\n ...validationResult.filteredOptions,\n headless: false\n };\n\n // Log the validated options, excluding 'headless' since it's always false for this extension\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = safeOptions;\n terminal.writeLine(\n `Launch options after validation: ${JSON.stringify(logOptions)} (headless: false enforced)`\n );\n\n const playwright: typeof import('playwright-core') = await this._setupPlaywrightAsync({\n playwrightVersion,\n browserName\n });\n\n const { chromium, firefox, webkit } = playwright;\n const browsers: Record<BrowserName, BrowserType> = { chromium, firefox, webkit };\n\n const browserServer: BrowserServer = await browsers[browserName].launchServer(safeOptions);\n\n if (!browserServer) {\n throw new Error(\n `Failed to launch browser server for ${browserName} with options: ${JSON.stringify(safeOptions)}`\n );\n }\n\n terminal.writeLine(`Launched ${browserName} browser server`);\n const client: WebSocket = new WebSocket(browserServer.wsEndpoint());\n\n return {\n browserServer,\n client\n };\n }\n\n private _validateHandshake(rawHandshake: unknown): IHandshake {\n if (\n typeof rawHandshake !== 'object' ||\n rawHandshake === null ||\n 'action' in rawHandshake === false ||\n 'browserName' in rawHandshake === false ||\n 'playwrightVersion' in rawHandshake === false ||\n 'launchOptions' in rawHandshake === false ||\n typeof rawHandshake.action !== 'string' ||\n typeof rawHandshake.browserName !== 'string' ||\n typeof rawHandshake.playwrightVersion !== 'string' ||\n typeof rawHandshake.launchOptions !== 'object'\n ) {\n throw new Error(`Invalid handshake: ${JSON.stringify(rawHandshake)}. Must be an object.`);\n }\n\n const { action, browserName, playwrightVersion, launchOptions } = rawHandshake;\n\n if (action !== 'handshake') {\n throw new Error(`Invalid action: ${action}. Expected 'handshake'.`);\n }\n const playwrightVersionSemver: semver.SemVer | null = semver.coerce(playwrightVersion);\n if (!playwrightVersionSemver) {\n throw new Error(`Invalid Playwright version: ${playwrightVersion}. Must be a valid semver version.`);\n }\n if (!isValidBrowserName(browserName)) {\n throw new Error(\n `Invalid browser name: ${browserName}. Must be one of ${Array.from(validBrowserNames).join(', ')}.`\n );\n }\n\n return {\n action,\n launchOptions: launchOptions as LaunchOptions,\n playwrightVersion: playwrightVersionSemver,\n browserName\n };\n }\n\n // ws1 is the tunnel websocket, ws2 is the browser server websocket\n private async _setupForwardingAsync(ws1: WebSocket, ws2: WebSocket): Promise<void> {\n this._terminal.writeLine('Setting up message forwarding between ws1 and ws2');\n this._terminal.writeLine(` ws1 (tunnel) readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n this._terminal.writeLine(` ws2 (browser) readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n\n const messageCount: { ws1ToWs2: number; ws2ToWs1: number } = { ws1ToWs2: 0, ws2ToWs1: 0 };\n\n ws1.on('message', (data) => {\n messageCount.ws1ToWs2++;\n if (ws2.readyState === WebSocket.OPEN) {\n ws2.send(data);\n } else {\n this._terminal.writeLine(\n `ws2 not open (state: ${getWebSocketReadyStateString(ws2.readyState)}). Dropping message #${messageCount.ws1ToWs2}`\n );\n }\n });\n ws2.on('message', (data) => {\n messageCount.ws2ToWs1++;\n if (ws1.readyState === WebSocket.OPEN) {\n ws1.send(data);\n } else {\n this._terminal.writeLine(\n `ws1 not open (state: ${getWebSocketReadyStateString(ws1.readyState)}). Dropping message #${messageCount.ws2ToWs1}`\n );\n }\n });\n\n ws1.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws1 (tunnel) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws2.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws2 (browser) in response');\n ws2.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel closed');\n }\n });\n ws2.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws2 (browser) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws1.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws1 (tunnel) in response');\n ws1.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Browser closed');\n }\n });\n\n ws1.once('error', (error) => {\n this._terminal.writeErrorLine(`ws1 (tunnel) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws1 readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n });\n ws2.once('error', (error) => {\n this._terminal.writeErrorLine(`ws2 (browser) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws2 readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n });\n }\n\n /**\n * Initializes the Playwright browser tunnel by establishing a WebSocket connection\n * and setting up the browser server.\n * Returns when the handshake is complete and the browser server is running.\n */\n private async _initPlaywrightBrowserTunnelAsync(): Promise<WebSocket> {\n let handshake: IHandshake | undefined = undefined;\n let client: WebSocket | undefined = undefined;\n let browserServer: BrowserServer | undefined = undefined;\n\n this.status = 'waiting-for-connection';\n const ws: WebSocket =\n this._mode === 'poll-connection'\n ? await this._pollConnectionAsync()\n : await this._waitForIncomingConnectionAsync();\n\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection established`);\n handshake = undefined;\n });\n\n ws.on('error', (error) => {\n this._terminal.writeLine(`WebSocket error occurred: ${getNormalizedErrorString(error)}`);\n });\n\n ws.on('close', async (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._initWsPromise = undefined;\n this.status = 'stopped';\n this._terminal.writeLine(\n `WebSocket connection closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(` handshake received: ${handshake !== undefined}`);\n this._terminal.writeLine(` browserServer active: ${browserServer !== undefined}`);\n if (browserServer) {\n this._terminal.writeLine(' Closing browser server...');\n await browserServer.close();\n this._terminal.writeLine(' Browser server closed');\n }\n });\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const onMessageHandler = async (data: RawData): Promise<void> => {\n const terminal: ITerminal = this._terminal;\n if (!handshake) {\n try {\n const rawHandshakeString: string = data.toString();\n const rawHandshake: unknown = JSON.parse(rawHandshakeString);\n terminal.writeLine(`Received handshake: ${rawHandshakeString}`);\n handshake = this._validateHandshake(rawHandshake);\n\n // Call the onBeforeLaunch callback if provided\n if (this._onBeforeLaunch) {\n terminal.writeLine('Requesting user approval before launching browser server...');\n const shouldProceed: boolean = await this._onBeforeLaunch(handshake);\n if (!shouldProceed) {\n terminal.writeLine('Browser server launch cancelled by user.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Launch cancelled by user');\n reject(new Error('Browser server launch cancelled by user'));\n return;\n }\n terminal.writeLine('User approved browser server launch.');\n }\n\n this.status = 'setting-up-browser-server';\n const browserServerProxy: IBrowserServerProxy =\n await this._getPlaywrightBrowserServerProxyAsync(handshake);\n client = browserServerProxy.client;\n browserServer = browserServerProxy.browserServer;\n\n // Monitor browser server process for crashes\n const browserProcess: ChildProcess | null = browserServer.process();\n if (browserProcess) {\n browserProcess.on('exit', (code: number | null, signal: string | null) => {\n terminal.writeErrorLine(`Browser server process exited - code: ${code}, signal: ${signal}`);\n });\n browserProcess.on('error', (err: Error) => {\n terminal.writeErrorLine(`Browser server process error: ${getNormalizedErrorString(err)}`);\n });\n terminal.writeDebugLine(`Browser server process started with PID: ${browserProcess.pid}`);\n } else {\n terminal.writeDebugLine('Warning: Browser server process handle not available for monitoring');\n }\n\n this.status = 'browser-server-running';\n\n // Send ack so that the counterpart also knows to start forwarding messages.\n // NOTE: The 1-second delay is an intentional workaround. In the current\n // protocol, the remote tunnel endpoint does not expose an explicit \"ready\"\n // signal for when it has finished initializing its own forwarding logic\n // after receiving the initial handshake. This\n // delay avoids races where early messages could be dropped or mishandled\n // if they arrive before the remote side is fully ready.\n //\n // TODO: A future improvement would be to replace this delay with a deterministic\n // synchronization mechanism (e.g. an explicit \"ready\" message or event)\n // instead of relying on a fixed timeout.\n await Async.sleepAsync(2000);\n\n ws.send(JSON.stringify({ action: 'handshakeAck' }));\n await this._setupForwardingAsync(ws, client);\n\n // Clean up message handler after successful handshake\n ws.off('message', onMessageHandler);\n resolve(ws);\n } catch (error) {\n terminal.writeLine(`Error processing handshake: ${error}`);\n this.status = 'error';\n\n // Cleanup and close connection on error\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Handshake error');\n reject(error);\n return;\n }\n } else {\n if (!client) {\n terminal.writeLine('Browser WebSocket client is not initialized.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Browser client not initialized');\n return;\n }\n }\n };\n ws.on('message', onMessageHandler);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PlaywrightBrowserTunnel.js","sourceRoot":"","sources":["../src/PlaywrightBrowserTunnel.ts"],"names":[],"mappings":";AAAA,4FAA4F;AAC5F,2DAA2D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAG3D,6CAAmC;AAGnC,2BAAmE;AAEnE,kDAAuG;AACvG,oEAA6E;AAE7E,2CAKqB;AACrB,qEAAuG;AAOvG,MAAM,iBAAiB,GAAgB,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAyB,CAAC,CAAC;AAC1G,SAAS,kBAAkB,CAAC,WAAmB;IAC7C,OAAO,iBAAiB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;AAC5C,CAAC;AAyDD;;;GAGG;AACH,MAAa,gBAAgB;IAgB3B,YAAmB,OAAiC;QAZnC,iCAA4B,GAAgB,IAAI,GAAG,EAAE,CAAC;QAI/D,YAAO,GAAiB,SAAS,CAAC;QAElC,iBAAY,GAAY,KAAK,CAAC;QAOpC,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,qBAAqB,EAAE,cAAc,EAAE,GAAG,OAAO,CAAC;QAE1F,QAAQ,IAAI,EAAE,CAAC;YACb,KAAK,iBAAiB;gBACpB,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC;oBACxB,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;gBACrE,CAAC;gBACD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;gBACtC,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;gBAC7B,MAAM;YACR,KAAK,8BAA8B;gBACjC,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;oBACrC,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAC;gBAClF,CAAC;gBACD,IAAI,CAAC,WAAW,GAAG,SAAS,CAAC;gBAC7B,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;gBACtC,MAAM;YACR;gBACE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QAED,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC;QAClB,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,IAAI,CAAC,eAAe,GAAG,cAAc,CAAC;QACtC,IAAI,CAAC,sBAAsB,GAAG,qBAAqB,CAAC;IACtD,CAAC;IAED,IAAW,MAAM;QACf,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,gEAAgE;IAChE,IAAY,MAAM,CAAC,SAAuB;QACxC,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;QACzB,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;IAClC,CAAC;IAEM,KAAK,CAAC,iBAAiB;QAC5B,MAAM,QAAQ,GAAc,IAAI,CAAC,SAAS,CAAC;QAC3C,MAAM,aAAa,GAAmC,IAAI,CAAC,cAAc,CAAC;QAC1E,IAAI,aAAa,EAAE,CAAC;YAClB,MAAM,EAAE,GAAc,MAAM,aAAa,CAAC;YAC1C,MAAM,IAAA,kBAAI,EAAC,EAAE,EAAE,OAAO,CAAC,CAAC;YACxB,QAAQ,CAAC,cAAc,CAAC,sDAAsD,CAAC,CAAC;YAChF,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;QAClC,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,UAAU,CAAC,UAAqC,EAAE;;QAC7D,IAAI,CAAC,YAAY,GAAG,MAAA,OAAO,CAAC,WAAW,mCAAI,IAAI,CAAC;QAChD,MAAM,QAAQ,GAAc,IAAI,CAAC,SAAS,CAAC;QAC3C,QAAQ,CAAC,SAAS,CAAC,gBAAgB,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC;QACxD,OAAO,IAAI,CAAC,YAAY,EAAE,CAAC;YACzB,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,CAAC;gBACzB,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,iCAAiC,EAAE,CAAC;YACjE,CAAC;iBAAM,CAAC;gBACN,QAAQ,CAAC,SAAS,CAAC,0CAA0C,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;YAC9E,CAAC;YACD,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;QACjC,CAAC;IACH,CAAC;IAEM,KAAK,CAAC,SAAS;;QACpB,IAAI,CAAC,YAAY,GAAG,KAAK,CAAC;QAC1B,IAAI,IAAI,CAAC,aAAa,EAAE,CAAC;YACvB,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YAClC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;QACjC,CAAC;QACD,MAAM,CAAA,MAAA,IAAI,CAAC,cAAc,0CAAE,OAAO,CAAC,GAAG,EAAE;;YACtC,MAAA,IAAI,CAAC,GAAG,0CAAE,KAAK,CAAC,8BAAkB,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;QACvE,CAAC,CAAC,CAAA,CAAC;IACL,CAAC;IAEM,KAAK,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC;QAChC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,iCAAiC,CAAC,CAAC;QAC5D,MAAM,IAAI,CAAC,SAAS,EAAE,CAAC;IACzB,CAAC;IAEM,KAAK,CAAC,mBAAmB;QAC9B,MAAM,OAAO,GAAW,IAAI,CAAC,sBAAsB,CAAC;QACpD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,kCAAkC,OAAO,EAAE,CAAC,CAAC;QACtE,IAAI,CAAC;YACH,MAAM,8BAAU,CAAC,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACjD,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;QAC1D,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,uCAAuC,IAAA,oCAAwB,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACrG,CAAC;IACH,CAAC;IAED,yFAAyF;IACzF,oEAAoE;IAE5D,KAAK,CAAC,gBAAgB,CAAC,OAAe,EAAE,IAAc;;QAC5D,MAAM,OAAO,GAAW,IAAI,CAAC,sBAAsB,CAAC;QACpD,MAAM,8BAAU,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC;QAC5C,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,oBAAoB,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,OAAO,EAAE,CAAC,CAAC;QAExF,MAAM,EAAE,GAAiB,8BAAU,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YACvD,KAAK,EAAE;gBACL,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,SAAS;gBACjB,MAAM,CAAC,SAAS;aACjB;YACD,uBAAuB,EAAE,OAAO;SACjC,CAAC,CAAC;QAEH,MAAA,EAAE,CAAC,MAAM,0CAAE,IAAI,CACb,IAAI,iCAAsB,CAAC;YACzB,QAAQ,EAAE,IAAI,CAAC,SAAS;YACxB,QAAQ,EAAE,mCAAwB,CAAC,GAAG;SACvC,CAAC,CACH,CAAC;QACF,MAAA,EAAE,CAAC,MAAM,0CAAE,IAAI,CACb,IAAI,iCAAsB,CAAC;YACzB,QAAQ,EAAE,IAAI,CAAC,SAAS;YACxB,QAAQ,EAAE,mCAAwB,CAAC,KAAK;SACzC,CAAC,CACH,CAAC;QAEF,MAAM,8BAAU,CAAC,gBAAgB,CAAC,EAAE,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/F,CAAC;IAEO,KAAK,CAAC,2BAA2B,CAAC,EACxC,iBAAiB,EACqB;QACtC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,sCAAsC,iBAAiB,EAAE,CAAC,CAAC;QACpF,MAAM,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE;YACjC,SAAS;YACT,mBAAmB,iBAAiB,wBAAwB,iBAAiB,EAAE;SAChF,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,+BAA+B,CAAC,EAC5C,iBAAiB,EACjB,WAAW,EAC2C;QACtD,MAAM,IAAI,CAAC,2BAA2B,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC;QAC9D,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,qCAAqC,iBAAiB,EAAE,CAAC,CAAC;QACnF,MAAM,IAAI,CAAC,gBAAgB,CAAC,MAAM,EAAE;YAClC,gCAAgC,iBAAiB,SAAS;YAC1D,SAAS;YACT,WAAW;SACZ,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,gBAAgB;QAC5B,MAAM,UAAU,GAAuB,IAAI,CAAC,WAAW,CAAC;QACxD,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC;QACvD,CAAC;QACD,OAAO,MAAM,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtD,MAAM,EAAE,GAAc,IAAI,cAAS,CAAC,UAAU,CAAC,CAAC;YAChD,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;gBACjB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;gBACxD,OAAO,CAAC,EAAE,CAAC,CAAC;YACd,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBACzB,MAAM,CAAC,KAAK,CAAC,CAAC;YAChB,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,4CAA4C;IAC5C,wEAAwE;IAChE,KAAK,CAAC,oBAAoB;QAChC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,kCAAkC,CAAC,CAAC;QAC7D,OAAO,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YAC3C,IAAI,CAAC,aAAa,GAAG,WAAW,CAAC,GAAG,EAAE;gBACpC,IAAI,IAAI,CAAC,yBAAyB,EAAE,CAAC;oBACnC,OAAO,CAAC,sDAAsD;gBAChE,CAAC;gBACD,MAAM,iBAAiB,GAAuB,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBACtE,IAAI,CAAC,yBAAyB,GAAG,iBAAiB,CAAC;gBACnD,iBAAiB;qBACd,IAAI,CAAC,CAAC,EAAa,EAAE,EAAE;oBACtB,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;oBAClC,IAAI,CAAC,aAAa,GAAG,SAAS,CAAC;oBAC/B,EAAE,CAAC,kBAAkB,EAAE,CAAC;oBACxB,IAAI,CAAC,yBAAyB,GAAG,SAAS,CAAC;oBAC3C,OAAO,CAAC,EAAE,CAAC,CAAC;gBACd,CAAC,CAAC;qBACD,KAAK,CAAC,GAAG,EAAE;oBACV,sCAAsC;oBACtC,IAAI,CAAC,yBAAyB,GAAG,SAAS,CAAC;gBAC7C,CAAC,CAAC,CAAC;YACP,CAAC,EAAE,GAAG,CAAC,CAAC;QACV,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,+BAA+B;QAC3C,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,2CAA2C,CAAC,CAAC;QAEtE,OAAO,MAAM,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtD,MAAM,MAAM,GAAoB,IAAI,cAAS,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;YAEjF,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC9B,CAAC,CAAC;YAEF,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;gBAC/B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,2CAA2C,CAAC,CAAC;gBAEtE,qDAAqD;gBACrD,OAAO,EAAE,CAAC;gBACV,MAAM,CAAC,KAAK,CAAC,CAAC,UAAkB,EAAE,EAAE;oBAClC,IAAI,UAAU,EAAE,CAAC;wBACf,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,qCACE,UAAU,YAAY,KAAK,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,UACrD,EAAE,CACH,CAAC;oBACJ,CAAC;oBACD,OAAO,CAAC,EAAE,CAAC,CAAC;gBACd,CAAC,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;gBAC7B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,2BAA2B,IAAA,oCAAwB,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC;gBAEvF,OAAO,EAAE,CAAC;gBACV,0CAA0C;gBAC1C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACpC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,kGAAkG;IAClG,2HAA2H;IAC3H,oBAAoB;IACZ,KAAK,CAAC,qBAAqB,CAAC,EAClC,iBAAiB,EACjB,WAAW,EAC2C;QACtD,MAAM,UAAU,GAAW,GAAG,iBAAiB,IAAI,WAAW,EAAE,CAAC;QACjE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,mEAAmE,UAAU,EAAE,CAAC,CAAC;QAC1G,IAAI,CAAC,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,oEAAoE,iBAAiB,EAAE,CACxF,CAAC;YACF,MAAM,IAAI,CAAC,+BAA+B,CAAC,EAAE,iBAAiB,EAAE,WAAW,EAAE,CAAC,CAAC;YAC/E,IAAI,CAAC,4BAA4B,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,iCAAiC,iBAAiB,qBAAqB,CAAC,CAAC;QAClG,OAAO,yBAAa,GAAG,IAAI,CAAC,sBAAsB,iCAAiC,iBAAiB,EAAE,uCAAC,CAAC;IAC1G,CAAC;IAEO,KAAK,CAAC,qCAAqC,CAAC,EAClD,WAAW,EACX,iBAAiB,EACjB,aAAa,EAC2D;QACxE,MAAM,QAAQ,GAAc,IAAI,CAAC,SAAS,CAAC;QAE3C,qDAAqD;QACrD,QAAQ,CAAC,SAAS,CAAC,yDAAyD,CAAC,CAAC;QAC9E,MAAM,gBAAgB,GACpB,MAAM,+CAAsB,CAAC,0BAA0B,CAAC,aAAa,EAAE,QAAQ,CAAC,CAAC;QAEnF,IAAI,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC;YAC9B,QAAQ,CAAC,gBAAgB,CACvB,oCAAoC,gBAAgB,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAChF,CAAC;YACF,QAAQ,CAAC,gBAAgB,CAAC,kEAAkE,CAAC,CAAC;QAChG,CAAC;QAED,iFAAiF;QACjF,2FAA2F;QAC3F,MAAM,WAAW,GAAkB;YACjC,GAAG,gBAAgB,CAAC,eAAe;YACnC,QAAQ,EAAE,KAAK;SAChB,CAAC;QAEF,6FAA6F;QAC7F,6DAA6D;QAC7D,MAAM,EAAE,QAAQ,EAAE,GAAG,UAAU,EAAE,GAAG,WAAW,CAAC;QAChD,QAAQ,CAAC,SAAS,CAChB,oCAAoC,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,6BAA6B,CAC5F,CAAC;QAEF,MAAM,UAAU,GAAqC,MAAM,IAAI,CAAC,qBAAqB,CAAC;YACpF,iBAAiB;YACjB,WAAW;SACZ,CAAC,CAAC;QAEH,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,UAAU,CAAC;QACjD,MAAM,QAAQ,GAAqC,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC;QAEjF,MAAM,aAAa,GAAkB,MAAM,QAAQ,CAAC,WAAW,CAAC,CAAC,YAAY,CAAC,WAAW,CAAC,CAAC;QAE3F,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CACb,uCAAuC,WAAW,kBAAkB,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,EAAE,CAClG,CAAC;QACJ,CAAC;QAED,QAAQ,CAAC,SAAS,CAAC,YAAY,WAAW,iBAAiB,CAAC,CAAC;QAC7D,MAAM,MAAM,GAAc,IAAI,cAAS,CAAC,aAAa,CAAC,UAAU,EAAE,CAAC,CAAC;QAEpE,OAAO;YACL,aAAa;YACb,MAAM;SACP,CAAC;IACJ,CAAC;IAEO,kBAAkB,CAAC,YAAqB;QAC9C,IACE,OAAO,YAAY,KAAK,QAAQ;YAChC,YAAY,KAAK,IAAI;YACrB,QAAQ,IAAI,YAAY,KAAK,KAAK;YAClC,aAAa,IAAI,YAAY,KAAK,KAAK;YACvC,mBAAmB,IAAI,YAAY,KAAK,KAAK;YAC7C,eAAe,IAAI,YAAY,KAAK,KAAK;YACzC,OAAO,YAAY,CAAC,MAAM,KAAK,QAAQ;YACvC,OAAO,YAAY,CAAC,WAAW,KAAK,QAAQ;YAC5C,OAAO,YAAY,CAAC,iBAAiB,KAAK,QAAQ;YAClD,OAAO,YAAY,CAAC,aAAa,KAAK,QAAQ,EAC9C,CAAC;YACD,MAAM,IAAI,KAAK,CAAC,sBAAsB,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,sBAAsB,CAAC,CAAC;QAC5F,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,EAAE,aAAa,EAAE,GAAG,YAAY,CAAC;QAE/E,IAAI,MAAM,KAAK,WAAW,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,mBAAmB,MAAM,yBAAyB,CAAC,CAAC;QACtE,CAAC;QAED,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;YACrC,MAAM,IAAI,KAAK,CACb,yBAAyB,WAAW,oBAAoB,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACpG,CAAC;QACJ,CAAC;QAED,OAAO;YACL,MAAM;YACN,aAAa,EAAE,aAA8B;YAC7C,iBAAiB;YACjB,WAAW;SACZ,CAAC;IACJ,CAAC;IAED,mEAAmE;IAC3D,KAAK,CAAC,qBAAqB,CAAC,GAAc,EAAE,GAAc;QAChE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,mDAAmD,CAAC,CAAC;QAC9E,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,8BAA8B,IAAA,wCAA4B,EAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,+BAA+B,IAAA,wCAA4B,EAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAExG,MAAM,YAAY,GAA2C,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAE1F,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,YAAY,CAAC,QAAQ,EAAE,CAAC;YACxB,IAAI,GAAG,CAAC,UAAU,KAAK,cAAS,CAAC,IAAI,EAAE,CAAC;gBACtC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,wBAAwB,IAAA,wCAA4B,EAAC,GAAG,CAAC,UAAU,CAAC,wBAAwB,YAAY,CAAC,QAAQ,EAAE,CACpH,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACzB,YAAY,CAAC,QAAQ,EAAE,CAAC;YACxB,IAAI,GAAG,CAAC,UAAU,KAAK,cAAS,CAAC,IAAI,EAAE,CAAC;gBACtC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,wBAAwB,IAAA,wCAA4B,EAAC,GAAG,CAAC,UAAU,CAAC,wBAAwB,YAAY,CAAC,QAAQ,EAAE,CACpH,CAAC;YACJ,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;YACjD,MAAM,SAAS,GAAW,MAAM,CAAC,QAAQ,EAAE,IAAI,oBAAoB,CAAC;YACpE,MAAM,eAAe,GAAW,IAAA,mCAAuB,EAAC,IAAI,CAAC,CAAC;YAC9D,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,+BAA+B,IAAI,KAAK,eAAe,cAAc,SAAS,EAAE,CACjF,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,mCAAmC,YAAY,CAAC,QAAQ,eAAe,YAAY,CAAC,QAAQ,EAAE,CAC/F,CAAC;YACF,IAAI,GAAG,CAAC,UAAU,KAAK,cAAS,CAAC,IAAI,EAAE,CAAC;gBACtC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,qCAAqC,CAAC,CAAC;gBAChE,GAAG,CAAC,KAAK,CAAC,8BAAkB,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC;YAChE,CAAC;QACH,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAY,EAAE,MAAc,EAAE,EAAE;YACjD,MAAM,SAAS,GAAW,MAAM,CAAC,QAAQ,EAAE,IAAI,oBAAoB,CAAC;YACpE,MAAM,eAAe,GAAW,IAAA,mCAAuB,EAAC,IAAI,CAAC,CAAC;YAC9D,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,gCAAgC,IAAI,KAAK,eAAe,cAAc,SAAS,EAAE,CAClF,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,mCAAmC,YAAY,CAAC,QAAQ,eAAe,YAAY,CAAC,QAAQ,EAAE,CAC/F,CAAC;YACF,IAAI,GAAG,CAAC,UAAU,KAAK,cAAS,CAAC,IAAI,EAAE,CAAC;gBACtC,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,oCAAoC,CAAC,CAAC;gBAC/D,GAAG,CAAC,KAAK,CAAC,8BAAkB,CAAC,cAAc,EAAE,gBAAgB,CAAC,CAAC;YACjE,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,iCAAiC,IAAA,oCAAwB,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAClG,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,qBAAqB,IAAA,wCAA4B,EAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACrG,CAAC,CAAC,CAAC;QACH,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC1B,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,kCAAkC,IAAA,oCAAwB,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACnG,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,qBAAqB,IAAA,wCAA4B,EAAC,GAAG,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACrG,CAAC,CAAC,CAAC;IACL,CAAC;IAED;;;;OAIG;IACK,KAAK,CAAC,iCAAiC;QAC7C,IAAI,SAAS,GAA2B,SAAS,CAAC;QAClD,IAAI,MAAM,GAA0B,SAAS,CAAC;QAC9C,IAAI,aAAa,GAA8B,SAAS,CAAC;QAEzD,IAAI,CAAC,MAAM,GAAG,wBAAwB,CAAC;QACvC,MAAM,EAAE,GACN,IAAI,CAAC,KAAK,KAAK,iBAAiB;YAC9B,CAAC,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE;YACnC,CAAC,CAAC,MAAM,IAAI,CAAC,+BAA+B,EAAE,CAAC;QAEnD,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;YACjB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,kCAAkC,CAAC,CAAC;YAC7D,SAAS,GAAG,SAAS,CAAC;QACxB,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACvB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,6BAA6B,IAAA,oCAAwB,EAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC3F,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,MAAc,EAAE,EAAE;YACpD,MAAM,SAAS,GAAW,MAAM,CAAC,QAAQ,EAAE,IAAI,oBAAoB,CAAC;YACpE,MAAM,eAAe,GAAW,IAAA,mCAAuB,EAAC,IAAI,CAAC,CAAC;YAC9D,IAAI,CAAC,cAAc,GAAG,SAAS,CAAC;YAChC,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;YACxB,IAAI,CAAC,SAAS,CAAC,SAAS,CACtB,uCAAuC,IAAI,KAAK,eAAe,cAAc,SAAS,EAAE,CACzF,CAAC;YACF,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,yBAAyB,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC;YAC7E,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,2BAA2B,aAAa,KAAK,SAAS,EAAE,CAAC,CAAC;YACnF,IAAI,aAAa,EAAE,CAAC;gBAClB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,6BAA6B,CAAC,CAAC;gBACxD,MAAM,aAAa,CAAC,KAAK,EAAE,CAAC;gBAC5B,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;YACtD,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,OAAO,MAAM,IAAI,OAAO,CAAY,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACtD,MAAM,gBAAgB,GAAG,KAAK,EAAE,IAAa,EAAiB,EAAE;gBAC9D,MAAM,QAAQ,GAAc,IAAI,CAAC,SAAS,CAAC;gBAC3C,IAAI,CAAC,SAAS,EAAE,CAAC;oBACf,IAAI,CAAC;wBACH,MAAM,kBAAkB,GAAW,IAAI,CAAC,QAAQ,EAAE,CAAC;wBACnD,MAAM,YAAY,GAAY,IAAI,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;wBAC7D,QAAQ,CAAC,SAAS,CAAC,uBAAuB,kBAAkB,EAAE,CAAC,CAAC;wBAChE,SAAS,GAAG,IAAI,CAAC,kBAAkB,CAAC,YAAY,CAAC,CAAC;wBAElD,+CAA+C;wBAC/C,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;4BACzB,QAAQ,CAAC,SAAS,CAAC,6DAA6D,CAAC,CAAC;4BAClF,MAAM,aAAa,GAAY,MAAM,IAAI,CAAC,eAAe,CAAC,SAAS,CAAC,CAAC;4BACrE,IAAI,CAAC,aAAa,EAAE,CAAC;gCACnB,QAAQ,CAAC,SAAS,CAAC,0CAA0C,CAAC,CAAC;gCAC/D,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;gCACpC,EAAE,CAAC,KAAK,CAAC,8BAAkB,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC;gCACxE,MAAM,CAAC,IAAI,KAAK,CAAC,yCAAyC,CAAC,CAAC,CAAC;gCAC7D,OAAO;4BACT,CAAC;4BACD,QAAQ,CAAC,SAAS,CAAC,sCAAsC,CAAC,CAAC;wBAC7D,CAAC;wBAED,IAAI,CAAC,MAAM,GAAG,2BAA2B,CAAC;wBAC1C,MAAM,kBAAkB,GACtB,MAAM,IAAI,CAAC,qCAAqC,CAAC,SAAS,CAAC,CAAC;wBAC9D,MAAM,GAAG,kBAAkB,CAAC,MAAM,CAAC;wBACnC,aAAa,GAAG,kBAAkB,CAAC,aAAa,CAAC;wBAEjD,6CAA6C;wBAC7C,MAAM,cAAc,GAAwB,aAAa,CAAC,OAAO,EAAE,CAAC;wBACpE,IAAI,cAAc,EAAE,CAAC;4BACnB,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAmB,EAAE,MAAqB,EAAE,EAAE;gCACvE,QAAQ,CAAC,cAAc,CAAC,yCAAyC,IAAI,aAAa,MAAM,EAAE,CAAC,CAAC;4BAC9F,CAAC,CAAC,CAAC;4BACH,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAU,EAAE,EAAE;gCACxC,QAAQ,CAAC,cAAc,CAAC,iCAAiC,IAAA,oCAAwB,EAAC,GAAG,CAAC,EAAE,CAAC,CAAC;4BAC5F,CAAC,CAAC,CAAC;4BACH,QAAQ,CAAC,cAAc,CAAC,4CAA4C,cAAc,CAAC,GAAG,EAAE,CAAC,CAAC;wBAC5F,CAAC;6BAAM,CAAC;4BACN,QAAQ,CAAC,cAAc,CAAC,qEAAqE,CAAC,CAAC;wBACjG,CAAC;wBAED,IAAI,CAAC,MAAM,GAAG,wBAAwB,CAAC;wBAEvC,4EAA4E;wBAC5E,wEAAwE;wBACxE,2EAA2E;wBAC3E,wEAAwE;wBACxE,8CAA8C;wBAC9C,yEAAyE;wBACzE,wDAAwD;wBACxD,EAAE;wBACF,iFAAiF;wBACjF,wEAAwE;wBACxE,yCAAyC;wBACzC,MAAM,yBAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;wBAE7B,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;wBACpD,MAAM,IAAI,CAAC,qBAAqB,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;wBAE7C,sDAAsD;wBACtD,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;wBACpC,OAAO,CAAC,EAAE,CAAC,CAAC;oBACd,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,QAAQ,CAAC,SAAS,CAAC,+BAA+B,KAAK,EAAE,CAAC,CAAC;wBAC3D,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC;wBAEtB,wCAAwC;wBACxC,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;wBACpC,EAAE,CAAC,KAAK,CAAC,8BAAkB,CAAC,cAAc,EAAE,iBAAiB,CAAC,CAAC;wBAC/D,MAAM,CAAC,KAAK,CAAC,CAAC;wBACd,OAAO;oBACT,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,MAAM,EAAE,CAAC;wBACZ,QAAQ,CAAC,SAAS,CAAC,8CAA8C,CAAC,CAAC;wBACnE,EAAE,CAAC,GAAG,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;wBACpC,EAAE,CAAC,KAAK,CAAC,8BAAkB,CAAC,cAAc,EAAE,gCAAgC,CAAC,CAAC;wBAC9E,OAAO;oBACT,CAAC;gBACH,CAAC;YACH,CAAC,CAAC;YACF,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC;QACrC,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AA5iBD,4CA4iBC","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\n\nimport type { BrowserServer, BrowserType, LaunchOptions } from 'playwright-core';\nimport { type RawData, WebSocket, type WebSocketServer } from 'ws';\n\nimport { TerminalProviderSeverity, TerminalStreamWritable, type ITerminal } from '@rushstack/terminal';\nimport { Executable, FileSystem, Async } from '@rushstack/node-core-library';\n\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from './utilities';\nimport { LaunchOptionsValidator, type ILaunchOptionsValidationResult } from './LaunchOptionsValidator';\n\n/**\n * Allowed Playwright browser names.\n * @beta\n */\nexport type BrowserName = 'chromium' | 'firefox' | 'webkit';\nconst validBrowserNames: Set<string> = new Set(['chromium', 'firefox', 'webkit'] satisfies BrowserName[]);\nfunction isValidBrowserName(browserName: string): browserName is BrowserName {\n return validBrowserNames.has(browserName);\n}\n\n/**\n * Status values reported by {@link PlaywrightTunnel}.\n * @beta\n */\nexport type TunnelStatus =\n | 'waiting-for-connection'\n | 'browser-server-running'\n | 'stopped'\n | 'setting-up-browser-server'\n | 'error';\n\n/**\n * Handshake data exchanged during the initial WebSocket connection.\n * @beta\n */\nexport interface IHandshake {\n action: 'handshake';\n browserName: BrowserName;\n launchOptions: LaunchOptions;\n playwrightVersion: string;\n}\n\ntype TunnelMode = 'poll-connection' | 'wait-for-incoming-connection';\n\n/**\n * Options for configuring a {@link PlaywrightTunnel} instance.\n * @beta\n */\nexport type IPlaywrightTunnelOptions = {\n terminal: ITerminal;\n onStatusChange: (status: TunnelStatus) => void;\n playwrightInstallPath: string;\n /**\n * Optional callback invoked before launching the browser server.\n * Receives the handshake data including launch options.\n * If the callback returns false, the browser server launch will be aborted.\n * This allows the client to prompt the user for approval before starting.\n */\n onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n} & (\n | {\n mode: 'poll-connection';\n wsEndpoint: string;\n }\n | {\n mode: 'wait-for-incoming-connection';\n listenPort: number;\n }\n);\n\ninterface IBrowserServerProxy {\n browserServer: BrowserServer;\n client: WebSocket;\n}\n\n/**\n * Hosts a Playwright browser server and forwards traffic over a WebSocket tunnel.\n * @beta\n */\nexport class PlaywrightTunnel {\n private readonly _terminal: ITerminal;\n private readonly _onStatusChange: (status: TunnelStatus) => void;\n private readonly _onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n private readonly _playwrightBrowsersInstalled: Set<string> = new Set();\n private readonly _wsEndpoint: string | undefined;\n private readonly _listenPort: number | undefined;\n private readonly _playwrightInstallPath: string;\n private _status: TunnelStatus = 'stopped';\n private _initWsPromise?: Promise<WebSocket>;\n private _keepRunning: boolean = false;\n private _ws?: WebSocket;\n private _mode: TunnelMode;\n private _pendingConnectionAttempt?: Promise<WebSocket>;\n private _pollInterval?: NodeJS.Timeout;\n\n public constructor(options: IPlaywrightTunnelOptions) {\n const { mode, terminal, onStatusChange, playwrightInstallPath, onBeforeLaunch } = options;\n\n switch (mode) {\n case 'poll-connection':\n if (!options.wsEndpoint) {\n throw new Error('wsEndpoint is required for poll-connection mode');\n }\n this._wsEndpoint = options.wsEndpoint;\n this._listenPort = undefined;\n break;\n case 'wait-for-incoming-connection':\n if (options.listenPort === undefined) {\n throw new Error('listenPort is required for wait-for-incoming-connection mode');\n }\n this._wsEndpoint = undefined;\n this._listenPort = options.listenPort;\n break;\n default:\n throw new Error(`Invalid mode: ${mode}`);\n }\n\n this._mode = mode;\n this._terminal = terminal;\n this._onStatusChange = onStatusChange;\n this._onBeforeLaunch = onBeforeLaunch;\n this._playwrightInstallPath = playwrightInstallPath;\n }\n\n public get status(): TunnelStatus {\n return this._status;\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n private set status(newStatus: TunnelStatus) {\n this._status = newStatus;\n this._onStatusChange(newStatus);\n }\n\n public async waitForCloseAsync(): Promise<void> {\n const terminal: ITerminal = this._terminal;\n const initWsPromise: Promise<WebSocket> | undefined = this._initWsPromise;\n if (initWsPromise) {\n const ws: WebSocket = await initWsPromise;\n await once(ws, 'close');\n terminal.writeDebugLine('WebSocket connection closed. resolving init promise.');\n this._initWsPromise = undefined;\n }\n }\n\n public async startAsync(options: { keepRunning?: boolean } = {}): Promise<void> {\n this._keepRunning = options.keepRunning ?? true;\n const terminal: ITerminal = this._terminal;\n terminal.writeLine(`keepRunning: ${this._keepRunning}`);\n while (this._keepRunning) {\n if (!this._initWsPromise) {\n this._initWsPromise = this._initPlaywrightBrowserTunnelAsync();\n } else {\n terminal.writeLine(`Tunnel is already running with status: ${this.status}`);\n }\n await this.waitForCloseAsync();\n }\n }\n\n public async stopAsync(): Promise<void> {\n this._keepRunning = false;\n if (this._pollInterval) {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n }\n await this._initWsPromise?.finally(() => {\n this._ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel stopped');\n });\n }\n\n public async [Symbol.asyncDispose](): Promise<void> {\n this._terminal.writeLine('Disposing WebSocket connection.');\n await this.stopAsync();\n }\n\n public async cleanTempFilesAsync(): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n this._terminal.writeLine(`Cleaning up temporary files in ${tmpPath}`);\n try {\n await FileSystem.ensureEmptyFolderAsync(tmpPath);\n this._terminal.writeLine(`Temporary files cleaned up.`);\n } catch (error) {\n this._terminal.writeLine(`Failed to clean up temporary files: ${getNormalizedErrorString(error)}`);\n }\n }\n\n // TODO: We should implement an uninstall command to remove installed Playwright browsers\n // public async uninstallPlaywrightBrowsersAsync(): Promise<void> {}\n\n private async _runCommandAsync(command: string, args: string[]): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n await FileSystem.ensureFolderAsync(tmpPath);\n this._terminal.writeLine(`Running command: ${command} ${args.join(' ')} in ${tmpPath}`);\n\n const cp: ChildProcess = Executable.spawn(command, args, {\n stdio: [\n 'ignore', // stdin\n 'pipe', // stdout\n 'pipe' // stderr\n ],\n currentWorkingDirectory: tmpPath\n });\n\n cp.stdout?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.log\n })\n );\n cp.stderr?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.error\n })\n );\n\n await Executable.waitForExitAsync(cp, { throwOnNonZeroExitCode: true, throwOnSignal: true });\n }\n\n private async _installPlaywrightCoreAsync({\n playwrightVersion\n }: Pick<IHandshake, 'playwrightVersion'>): Promise<void> {\n this._terminal.writeLine(`Installing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('npm', [\n 'install',\n `playwright-core-${playwrightVersion}@npm:playwright-core@${playwrightVersion}`\n ]);\n }\n\n private async _installPlaywrightBrowsersAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<void> {\n await this._installPlaywrightCoreAsync({ playwrightVersion });\n this._terminal.writeLine(`Executing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('node', [\n `node_modules/playwright-core-${playwrightVersion}/cli.js`,\n 'install',\n browserName\n ]);\n }\n\n private async _tryConnectAsync(): Promise<WebSocket> {\n const wsEndpoint: string | undefined = this._wsEndpoint;\n if (!wsEndpoint) {\n throw new Error('WebSocket endpoint is not defined');\n }\n return await new Promise<WebSocket>((resolve, reject) => {\n const ws: WebSocket = new WebSocket(wsEndpoint);\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection opened`);\n resolve(ws);\n });\n ws.once('error', (error) => {\n reject(error);\n });\n });\n }\n\n // TODO: Only supporting one test at a time.\n // Need to support multiple simultaneous connections for parallel tests.\n private async _pollConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine(`Waiting for WebSocket connection`);\n return await new Promise((resolve, reject) => {\n this._pollInterval = setInterval(() => {\n if (this._pendingConnectionAttempt) {\n return; // Skip if a connection attempt is already in progress\n }\n const connectionPromise: Promise<WebSocket> = this._tryConnectAsync();\n this._pendingConnectionAttempt = connectionPromise;\n connectionPromise\n .then((ws: WebSocket) => {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n ws.removeAllListeners();\n this._pendingConnectionAttempt = undefined;\n resolve(ws);\n })\n .catch(() => {\n // no-op - will retry on next interval\n this._pendingConnectionAttempt = undefined;\n });\n }, 500);\n });\n }\n\n private async _waitForIncomingConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine('Waiting for incoming WebSocket connection');\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const server: WebSocketServer = new WebSocket.Server({ port: this._listenPort });\n\n const cleanup = (): void => {\n server.removeAllListeners();\n };\n\n server.once('connection', (ws) => {\n this._terminal.writeLine('Incoming WebSocket connection established');\n\n // Stop listening immediately so the port is released\n cleanup();\n server.close((closeError?: Error) => {\n if (closeError) {\n this._terminal.writeLine(\n `Failed to close WebSocket server: ${\n closeError instanceof Error ? closeError.message : closeError\n }`\n );\n }\n resolve(ws);\n });\n });\n\n server.once('error', (error) => {\n this._terminal.writeLine(`WebSocket server error: ${getNormalizedErrorString(error)}`);\n\n cleanup();\n // Try to close (best-effort), then reject\n server.close(() => reject(error));\n });\n });\n }\n\n // TODO: If a user runs this for the first time, `this._playwrightBrowsersInstalled` will be empty\n // and it will try to install the browsers every time. We should persist this information. Maybe a cache file with text per\n // machine instance?\n private async _setupPlaywrightAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<typeof import('playwright-core')> {\n const browserKey: string = `${playwrightVersion}-${browserName}`;\n this._terminal.writeLine(`Checking for installed playwright browsers. Installed browsers: ${browserKey}`);\n if (!this._playwrightBrowsersInstalled.has(browserKey)) {\n this._terminal.writeLine(\n `Playwright browser not found. Installing playwright-core version ${playwrightVersion}`\n );\n await this._installPlaywrightBrowsersAsync({ playwrightVersion, browserName });\n this._playwrightBrowsersInstalled.add(browserKey);\n }\n\n this._terminal.writeLine(`Using playwright-core version ${playwrightVersion} for browser server`);\n return await import(`${this._playwrightInstallPath}/node_modules/playwright-core-${playwrightVersion}`);\n }\n\n private async _getPlaywrightBrowserServerProxyAsync({\n browserName,\n playwrightVersion,\n launchOptions\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName' | 'launchOptions'>): Promise<IBrowserServerProxy> {\n const terminal: ITerminal = this._terminal;\n\n // Validate launch options against security allowlist\n terminal.writeLine('Validating launch options against security allowlist...');\n const validationResult: ILaunchOptionsValidationResult =\n await LaunchOptionsValidator.validateLaunchOptionsAsync(launchOptions, terminal);\n\n if (!validationResult.isValid) {\n terminal.writeWarningLine(\n `Some launch options were denied: ${validationResult.deniedOptions.join(', ')}`\n );\n terminal.writeWarningLine(`Using filtered launch options. Denied options have been removed.`);\n }\n\n // Use filtered options and ensure headless: false for headed tests in codespaces\n // This is critical for the extension's purpose - enabling headed Playwright tests remotely\n const safeOptions: LaunchOptions = {\n ...validationResult.filteredOptions,\n headless: false\n };\n\n // Log the validated options, excluding 'headless' since it's always false for this extension\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = safeOptions;\n terminal.writeLine(\n `Launch options after validation: ${JSON.stringify(logOptions)} (headless: false enforced)`\n );\n\n const playwright: typeof import('playwright-core') = await this._setupPlaywrightAsync({\n playwrightVersion,\n browserName\n });\n\n const { chromium, firefox, webkit } = playwright;\n const browsers: Record<BrowserName, BrowserType> = { chromium, firefox, webkit };\n\n const browserServer: BrowserServer = await browsers[browserName].launchServer(safeOptions);\n\n if (!browserServer) {\n throw new Error(\n `Failed to launch browser server for ${browserName} with options: ${JSON.stringify(safeOptions)}`\n );\n }\n\n terminal.writeLine(`Launched ${browserName} browser server`);\n const client: WebSocket = new WebSocket(browserServer.wsEndpoint());\n\n return {\n browserServer,\n client\n };\n }\n\n private _validateHandshake(rawHandshake: unknown): IHandshake {\n if (\n typeof rawHandshake !== 'object' ||\n rawHandshake === null ||\n 'action' in rawHandshake === false ||\n 'browserName' in rawHandshake === false ||\n 'playwrightVersion' in rawHandshake === false ||\n 'launchOptions' in rawHandshake === false ||\n typeof rawHandshake.action !== 'string' ||\n typeof rawHandshake.browserName !== 'string' ||\n typeof rawHandshake.playwrightVersion !== 'string' ||\n typeof rawHandshake.launchOptions !== 'object'\n ) {\n throw new Error(`Invalid handshake: ${JSON.stringify(rawHandshake)}. Must be an object.`);\n }\n\n const { action, browserName, playwrightVersion, launchOptions } = rawHandshake;\n\n if (action !== 'handshake') {\n throw new Error(`Invalid action: ${action}. Expected 'handshake'.`);\n }\n\n if (!isValidBrowserName(browserName)) {\n throw new Error(\n `Invalid browser name: ${browserName}. Must be one of ${Array.from(validBrowserNames).join(', ')}.`\n );\n }\n\n return {\n action,\n launchOptions: launchOptions as LaunchOptions,\n playwrightVersion,\n browserName\n };\n }\n\n // ws1 is the tunnel websocket, ws2 is the browser server websocket\n private async _setupForwardingAsync(ws1: WebSocket, ws2: WebSocket): Promise<void> {\n this._terminal.writeLine('Setting up message forwarding between ws1 and ws2');\n this._terminal.writeLine(` ws1 (tunnel) readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n this._terminal.writeLine(` ws2 (browser) readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n\n const messageCount: { ws1ToWs2: number; ws2ToWs1: number } = { ws1ToWs2: 0, ws2ToWs1: 0 };\n\n ws1.on('message', (data) => {\n messageCount.ws1ToWs2++;\n if (ws2.readyState === WebSocket.OPEN) {\n ws2.send(data);\n } else {\n this._terminal.writeLine(\n `ws2 not open (state: ${getWebSocketReadyStateString(ws2.readyState)}). Dropping message #${messageCount.ws1ToWs2}`\n );\n }\n });\n ws2.on('message', (data) => {\n messageCount.ws2ToWs1++;\n if (ws1.readyState === WebSocket.OPEN) {\n ws1.send(data);\n } else {\n this._terminal.writeLine(\n `ws1 not open (state: ${getWebSocketReadyStateString(ws1.readyState)}). Dropping message #${messageCount.ws2ToWs1}`\n );\n }\n });\n\n ws1.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws1 (tunnel) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws2.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws2 (browser) in response');\n ws2.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel closed');\n }\n });\n ws2.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws2 (browser) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws1.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws1 (tunnel) in response');\n ws1.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Browser closed');\n }\n });\n\n ws1.once('error', (error) => {\n this._terminal.writeErrorLine(`ws1 (tunnel) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws1 readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n });\n ws2.once('error', (error) => {\n this._terminal.writeErrorLine(`ws2 (browser) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws2 readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n });\n }\n\n /**\n * Initializes the Playwright browser tunnel by establishing a WebSocket connection\n * and setting up the browser server.\n * Returns when the handshake is complete and the browser server is running.\n */\n private async _initPlaywrightBrowserTunnelAsync(): Promise<WebSocket> {\n let handshake: IHandshake | undefined = undefined;\n let client: WebSocket | undefined = undefined;\n let browserServer: BrowserServer | undefined = undefined;\n\n this.status = 'waiting-for-connection';\n const ws: WebSocket =\n this._mode === 'poll-connection'\n ? await this._pollConnectionAsync()\n : await this._waitForIncomingConnectionAsync();\n\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection established`);\n handshake = undefined;\n });\n\n ws.on('error', (error) => {\n this._terminal.writeLine(`WebSocket error occurred: ${getNormalizedErrorString(error)}`);\n });\n\n ws.on('close', async (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._initWsPromise = undefined;\n this.status = 'stopped';\n this._terminal.writeLine(\n `WebSocket connection closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(` handshake received: ${handshake !== undefined}`);\n this._terminal.writeLine(` browserServer active: ${browserServer !== undefined}`);\n if (browserServer) {\n this._terminal.writeLine(' Closing browser server...');\n await browserServer.close();\n this._terminal.writeLine(' Browser server closed');\n }\n });\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const onMessageHandler = async (data: RawData): Promise<void> => {\n const terminal: ITerminal = this._terminal;\n if (!handshake) {\n try {\n const rawHandshakeString: string = data.toString();\n const rawHandshake: unknown = JSON.parse(rawHandshakeString);\n terminal.writeLine(`Received handshake: ${rawHandshakeString}`);\n handshake = this._validateHandshake(rawHandshake);\n\n // Call the onBeforeLaunch callback if provided\n if (this._onBeforeLaunch) {\n terminal.writeLine('Requesting user approval before launching browser server...');\n const shouldProceed: boolean = await this._onBeforeLaunch(handshake);\n if (!shouldProceed) {\n terminal.writeLine('Browser server launch cancelled by user.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Launch cancelled by user');\n reject(new Error('Browser server launch cancelled by user'));\n return;\n }\n terminal.writeLine('User approved browser server launch.');\n }\n\n this.status = 'setting-up-browser-server';\n const browserServerProxy: IBrowserServerProxy =\n await this._getPlaywrightBrowserServerProxyAsync(handshake);\n client = browserServerProxy.client;\n browserServer = browserServerProxy.browserServer;\n\n // Monitor browser server process for crashes\n const browserProcess: ChildProcess | null = browserServer.process();\n if (browserProcess) {\n browserProcess.on('exit', (code: number | null, signal: string | null) => {\n terminal.writeErrorLine(`Browser server process exited - code: ${code}, signal: ${signal}`);\n });\n browserProcess.on('error', (err: Error) => {\n terminal.writeErrorLine(`Browser server process error: ${getNormalizedErrorString(err)}`);\n });\n terminal.writeDebugLine(`Browser server process started with PID: ${browserProcess.pid}`);\n } else {\n terminal.writeDebugLine('Warning: Browser server process handle not available for monitoring');\n }\n\n this.status = 'browser-server-running';\n\n // Send ack so that the counterpart also knows to start forwarding messages.\n // NOTE: The 1-second delay is an intentional workaround. In the current\n // protocol, the remote tunnel endpoint does not expose an explicit \"ready\"\n // signal for when it has finished initializing its own forwarding logic\n // after receiving the initial handshake. This\n // delay avoids races where early messages could be dropped or mishandled\n // if they arrive before the remote side is fully ready.\n //\n // TODO: A future improvement would be to replace this delay with a deterministic\n // synchronization mechanism (e.g. an explicit \"ready\" message or event)\n // instead of relying on a fixed timeout.\n await Async.sleepAsync(2000);\n\n ws.send(JSON.stringify({ action: 'handshakeAck' }));\n await this._setupForwardingAsync(ws, client);\n\n // Clean up message handler after successful handshake\n ws.off('message', onMessageHandler);\n resolve(ws);\n } catch (error) {\n terminal.writeLine(`Error processing handshake: ${error}`);\n this.status = 'error';\n\n // Cleanup and close connection on error\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Handshake error');\n reject(error);\n return;\n }\n } else {\n if (!client) {\n terminal.writeLine('Browser WebSocket client is not initialized.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Browser client not initialized');\n return;\n }\n }\n };\n ws.on('message', onMessageHandler);\n });\n }\n}\n"]}
|
|
@@ -11,12 +11,11 @@ const ws_1 = require("ws");
|
|
|
11
11
|
const HttpServer_1 = require("../HttpServer");
|
|
12
12
|
const utilities_1 = require("../utilities");
|
|
13
13
|
const constants_1 = require("./constants");
|
|
14
|
-
const { version: playwrightVersion } = package_json_1.default;
|
|
15
14
|
/**
|
|
16
15
|
* Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.
|
|
17
16
|
* @beta
|
|
18
17
|
*/
|
|
19
|
-
async function tunneledBrowserConnection(logger, port = constants_1.DEFAULT_LISTEN_PORT) {
|
|
18
|
+
async function tunneledBrowserConnection(logger, port = constants_1.DEFAULT_LISTEN_PORT, playwrightVersion = package_json_1.default.version) {
|
|
20
19
|
// Server that remote peer (actual browser host) connects to
|
|
21
20
|
const remoteWsServer = new ws_1.WebSocketServer({ port });
|
|
22
21
|
// Local HTTP + WebSocket server where the playwright client will connect providing params
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunneledBrowserConnection.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { LaunchOptions } from 'playwright-core';\nimport playwrightPackageJson from 'playwright-core/package.json';\nimport type { RawData } from 'ws';\nimport { WebSocket, WebSocketServer } from 'ws';\n\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport { HttpServer } from '../HttpServer';\nimport type { BrowserName } from '../PlaywrightBrowserTunnel';\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from '../utilities';\nimport type {\n IDisposableTunneledBrowserConnection,\n IHandshake,\n IHandshakeAck\n} from './ITunneledBrowserConnection';\nimport { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';\n\nconst { version: playwrightVersion } = playwrightPackageJson;\n\n/**\n * Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.\n * @beta\n */\nexport async function tunneledBrowserConnection(\n logger: ITerminal,\n port: number = DEFAULT_LISTEN_PORT\n): Promise<IDisposableTunneledBrowserConnection> {\n // Server that remote peer (actual browser host) connects to\n const remoteWsServer: WebSocketServer = new WebSocketServer({ port });\n // Local HTTP + WebSocket server where the playwright client will connect providing params\n const httpServer: HttpServer = new HttpServer(logger);\n await httpServer.listenAsync();\n logger.writeLine(`Remote WebSocket server listening on ws://localhost:${port}`);\n\n const localProxyWs: WebSocketServer = httpServer.wsServer;\n const localProxyWsEndpoint: string = httpServer.endpoint;\n\n let browserName: BrowserName | undefined;\n let launchOptions: LaunchOptions | undefined;\n let remoteSocket: WebSocket | undefined;\n let handshakeAck: boolean = false;\n let handshakeSent: boolean = false;\n\n function maybeSendHandshake(): void {\n if (!handshakeSent && remoteSocket && browserName && launchOptions) {\n const handshake: IHandshake = {\n action: 'handshake',\n browserName,\n launchOptions,\n playwrightVersion\n };\n // Log handshake without 'headless' to avoid confusion (tunnel enforces headless: false)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = launchOptions;\n const logHandshake: Omit<IHandshake, 'launchOptions'> & {\n launchOptions: Omit<LaunchOptions, 'headless'>;\n } = {\n ...handshake,\n launchOptions: logOptions\n };\n logger.writeLine(`Sending handshake to remote: ${JSON.stringify(logHandshake)}`);\n handshakeSent = true;\n remoteSocket.send(JSON.stringify(handshake));\n }\n }\n\n return await new Promise((resolve) => {\n remoteWsServer.on('error', (error) => {\n logger.writeErrorLine(`Remote WebSocket server error: ${error}`);\n });\n\n remoteWsServer.on('close', () => {\n logger.writeLine('Remote WebSocket server closed');\n });\n\n const bufferedLocalMessages: Array<RawData> = [];\n\n remoteWsServer.on('connection', (ws) => {\n logger.writeLine('Remote websocket connected');\n remoteSocket = ws;\n handshakeAck = false;\n maybeSendHandshake();\n\n ws.on('message', (message) => {\n if (!handshakeAck) {\n try {\n const receivedHandshake: IHandshakeAck = JSON.parse(message.toString());\n if (receivedHandshake.action === 'handshakeAck') {\n handshakeAck = true;\n logger.writeLine('Received handshakeAck from remote');\n } else {\n logger.writeErrorLine('Invalid handshake ack message');\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Invalid handshake ack');\n return;\n }\n } catch (e) {\n logger.writeErrorLine(`Failed parsing handshake ack: ${e}`);\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Failed parsing handshake');\n return;\n }\n // Resolve only once local proxy available and handshake acknowledged\n if (handshakeAck) {\n // Flush any buffered local messages now that tunnel is active\n const activeRemote: WebSocket | undefined = remoteSocket;\n if (activeRemote && activeRemote.readyState === WebSocket.OPEN) {\n while (bufferedLocalMessages.length > 0) {\n const m: Buffer | ArrayBuffer | Buffer[] | string | undefined = bufferedLocalMessages.shift();\n if (m !== undefined) {\n logger.writeLine(`Flushing buffered local message to remote: ${m}`);\n activeRemote.send(m);\n }\n }\n }\n }\n } else {\n // Forward from remote to all local clients\n localProxyWs.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n });\n }\n });\n\n ws.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Remote websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Connection state at close: handshakeSent=${handshakeSent}, handshakeAck=${handshakeAck}`\n );\n logger.writeDebugLine(` Buffered messages pending: ${bufferedLocalMessages.length}`);\n });\n ws.on('error', (err: Error) => {\n logger.writeErrorLine(`Remote websocket error: ${getNormalizedErrorString(err)}`);\n logger.writeErrorLine(` Socket readyState: ${getWebSocketReadyStateString(ws.readyState)}`);\n });\n });\n\n localProxyWs.on('connection', (localWs, request) => {\n try {\n const urlString: string | undefined = request?.url;\n if (urlString) {\n const parsed: URL = new URL(urlString, 'http://localhost');\n logger.writeLine(`Local client connected with query params: ${parsed.searchParams.toString()}`);\n const bName: string | null = parsed.searchParams.get('browser');\n if (bName && SUPPORTED_BROWSER_NAMES.has(bName)) {\n browserName = bName as BrowserName;\n }\n const launchOptionsParam: string | null = parsed.searchParams.get('launchOptions');\n if (launchOptionsParam) {\n try {\n launchOptions = JSON.parse(launchOptionsParam);\n } catch (e) {\n logger.writeErrorLine('Invalid launchOptions JSON provided');\n }\n }\n }\n } catch (e) {\n logger.writeErrorLine(`Error parsing local connection query params: ${e}`);\n }\n\n if (!browserName) {\n const supportedBrowsersString: string = Array.from(SUPPORTED_BROWSER_NAMES).join('|');\n logger.writeErrorLine(`browser query param required (${supportedBrowsersString})`);\n localWs.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Missing browser param');\n return;\n }\n if (!launchOptions) {\n launchOptions = {} as LaunchOptions; // default empty if not provided\n }\n\n maybeSendHandshake();\n\n localWs.on('message', (message) => {\n if (handshakeAck && remoteSocket?.readyState === WebSocket.OPEN) {\n remoteSocket.send(message);\n } else {\n // Buffer until handshakeAck to avoid losing early protocol messages from Playwright\n bufferedLocalMessages.push(message);\n }\n });\n localWs.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Local client websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Remote socket state: ${remoteSocket ? getWebSocketReadyStateString(remoteSocket.readyState) : 'undefined'}`\n );\n logger.writeDebugLine(` handshakeAck: ${handshakeAck}`);\n });\n localWs.on('error', (err: Error) => {\n logger.writeErrorLine(`Local client websocket error: ${getNormalizedErrorString(err)}`);\n });\n });\n\n // Resolve immediately so caller can initiate local connection with query params (handshake completes later)\n resolve({\n remoteEndpoint: localProxyWsEndpoint,\n [Symbol.dispose]() {\n try {\n remoteWsServer.close();\n } catch {\n // ignore errors during remote WebSocket server shutdown\n }\n try {\n httpServer[Symbol.dispose]();\n } catch {\n // ignore errors during HTTP/WebSocket server shutdown\n }\n },\n // eslint-disable-next-line promise/param-names\n closePromise: new Promise<void>((resolve2) => {\n remoteWsServer.once('close', () => {\n resolve2();\n });\n })\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TunneledBrowserConnection.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { LaunchOptions } from 'playwright-core';\nimport playwrightPackageJson from 'playwright-core/package.json';\nimport type { RawData } from 'ws';\nimport { WebSocket, WebSocketServer } from 'ws';\n\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport { HttpServer } from '../HttpServer';\nimport type { BrowserName } from '../PlaywrightBrowserTunnel';\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from '../utilities';\nimport type {\n IDisposableTunneledBrowserConnection,\n IHandshake,\n IHandshakeAck\n} from './ITunneledBrowserConnection';\nimport { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';\n\n/**\n * Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.\n * @beta\n */\nexport async function tunneledBrowserConnection(\n logger: ITerminal,\n port: number = DEFAULT_LISTEN_PORT,\n playwrightVersion: string = playwrightPackageJson.version\n): Promise<IDisposableTunneledBrowserConnection> {\n // Server that remote peer (actual browser host) connects to\n const remoteWsServer: WebSocketServer = new WebSocketServer({ port });\n // Local HTTP + WebSocket server where the playwright client will connect providing params\n const httpServer: HttpServer = new HttpServer(logger);\n await httpServer.listenAsync();\n logger.writeLine(`Remote WebSocket server listening on ws://localhost:${port}`);\n\n const localProxyWs: WebSocketServer = httpServer.wsServer;\n const localProxyWsEndpoint: string = httpServer.endpoint;\n\n let browserName: BrowserName | undefined;\n let launchOptions: LaunchOptions | undefined;\n let remoteSocket: WebSocket | undefined;\n let handshakeAck: boolean = false;\n let handshakeSent: boolean = false;\n\n function maybeSendHandshake(): void {\n if (!handshakeSent && remoteSocket && browserName && launchOptions) {\n const handshake: IHandshake = {\n action: 'handshake',\n browserName,\n launchOptions,\n playwrightVersion\n };\n // Log handshake without 'headless' to avoid confusion (tunnel enforces headless: false)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = launchOptions;\n const logHandshake: Omit<IHandshake, 'launchOptions'> & {\n launchOptions: Omit<LaunchOptions, 'headless'>;\n } = {\n ...handshake,\n launchOptions: logOptions\n };\n logger.writeLine(`Sending handshake to remote: ${JSON.stringify(logHandshake)}`);\n handshakeSent = true;\n remoteSocket.send(JSON.stringify(handshake));\n }\n }\n\n return await new Promise((resolve) => {\n remoteWsServer.on('error', (error) => {\n logger.writeErrorLine(`Remote WebSocket server error: ${error}`);\n });\n\n remoteWsServer.on('close', () => {\n logger.writeLine('Remote WebSocket server closed');\n });\n\n const bufferedLocalMessages: Array<RawData> = [];\n\n remoteWsServer.on('connection', (ws) => {\n logger.writeLine('Remote websocket connected');\n remoteSocket = ws;\n handshakeAck = false;\n maybeSendHandshake();\n\n ws.on('message', (message) => {\n if (!handshakeAck) {\n try {\n const receivedHandshake: IHandshakeAck = JSON.parse(message.toString());\n if (receivedHandshake.action === 'handshakeAck') {\n handshakeAck = true;\n logger.writeLine('Received handshakeAck from remote');\n } else {\n logger.writeErrorLine('Invalid handshake ack message');\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Invalid handshake ack');\n return;\n }\n } catch (e) {\n logger.writeErrorLine(`Failed parsing handshake ack: ${e}`);\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Failed parsing handshake');\n return;\n }\n // Resolve only once local proxy available and handshake acknowledged\n if (handshakeAck) {\n // Flush any buffered local messages now that tunnel is active\n const activeRemote: WebSocket | undefined = remoteSocket;\n if (activeRemote && activeRemote.readyState === WebSocket.OPEN) {\n while (bufferedLocalMessages.length > 0) {\n const m: Buffer | ArrayBuffer | Buffer[] | string | undefined = bufferedLocalMessages.shift();\n if (m !== undefined) {\n logger.writeLine(`Flushing buffered local message to remote: ${m}`);\n activeRemote.send(m);\n }\n }\n }\n }\n } else {\n // Forward from remote to all local clients\n localProxyWs.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n });\n }\n });\n\n ws.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Remote websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Connection state at close: handshakeSent=${handshakeSent}, handshakeAck=${handshakeAck}`\n );\n logger.writeDebugLine(` Buffered messages pending: ${bufferedLocalMessages.length}`);\n });\n ws.on('error', (err: Error) => {\n logger.writeErrorLine(`Remote websocket error: ${getNormalizedErrorString(err)}`);\n logger.writeErrorLine(` Socket readyState: ${getWebSocketReadyStateString(ws.readyState)}`);\n });\n });\n\n localProxyWs.on('connection', (localWs, request) => {\n try {\n const urlString: string | undefined = request?.url;\n if (urlString) {\n const parsed: URL = new URL(urlString, 'http://localhost');\n logger.writeLine(`Local client connected with query params: ${parsed.searchParams.toString()}`);\n const bName: string | null = parsed.searchParams.get('browser');\n if (bName && SUPPORTED_BROWSER_NAMES.has(bName)) {\n browserName = bName as BrowserName;\n }\n const launchOptionsParam: string | null = parsed.searchParams.get('launchOptions');\n if (launchOptionsParam) {\n try {\n launchOptions = JSON.parse(launchOptionsParam);\n } catch (e) {\n logger.writeErrorLine('Invalid launchOptions JSON provided');\n }\n }\n }\n } catch (e) {\n logger.writeErrorLine(`Error parsing local connection query params: ${e}`);\n }\n\n if (!browserName) {\n const supportedBrowsersString: string = Array.from(SUPPORTED_BROWSER_NAMES).join('|');\n logger.writeErrorLine(`browser query param required (${supportedBrowsersString})`);\n localWs.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Missing browser param');\n return;\n }\n if (!launchOptions) {\n launchOptions = {} as LaunchOptions; // default empty if not provided\n }\n\n maybeSendHandshake();\n\n localWs.on('message', (message) => {\n if (handshakeAck && remoteSocket?.readyState === WebSocket.OPEN) {\n remoteSocket.send(message);\n } else {\n // Buffer until handshakeAck to avoid losing early protocol messages from Playwright\n bufferedLocalMessages.push(message);\n }\n });\n localWs.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Local client websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Remote socket state: ${remoteSocket ? getWebSocketReadyStateString(remoteSocket.readyState) : 'undefined'}`\n );\n logger.writeDebugLine(` handshakeAck: ${handshakeAck}`);\n });\n localWs.on('error', (err: Error) => {\n logger.writeErrorLine(`Local client websocket error: ${getNormalizedErrorString(err)}`);\n });\n });\n\n // Resolve immediately so caller can initiate local connection with query params (handshake completes later)\n resolve({\n remoteEndpoint: localProxyWsEndpoint,\n [Symbol.dispose]() {\n try {\n remoteWsServer.close();\n } catch {\n // ignore errors during remote WebSocket server shutdown\n }\n try {\n httpServer[Symbol.dispose]();\n } catch {\n // ignore errors during HTTP/WebSocket server shutdown\n }\n },\n // eslint-disable-next-line promise/param-names\n closePromise: new Promise<void>((resolve2) => {\n remoteWsServer.once('close', () => {\n resolve2();\n });\n })\n });\n });\n}\n"]}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { LaunchOptions } from 'playwright-core';
|
|
2
|
-
import semver from 'semver';
|
|
3
2
|
import { type ITerminal } from '@rushstack/terminal';
|
|
4
3
|
/**
|
|
5
4
|
* Allowed Playwright browser names.
|
|
@@ -19,7 +18,7 @@ export interface IHandshake {
|
|
|
19
18
|
action: 'handshake';
|
|
20
19
|
browserName: BrowserName;
|
|
21
20
|
launchOptions: LaunchOptions;
|
|
22
|
-
playwrightVersion:
|
|
21
|
+
playwrightVersion: string;
|
|
23
22
|
}
|
|
24
23
|
/**
|
|
25
24
|
* Options for configuring a {@link PlaywrightTunnel} instance.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaywrightBrowserTunnel.d.ts","sourceRoot":"","sources":["../src/PlaywrightBrowserTunnel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA8B,aAAa,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"PlaywrightBrowserTunnel.d.ts","sourceRoot":"","sources":["../src/PlaywrightBrowserTunnel.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA8B,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAGjF,OAAO,EAAoD,KAAK,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAWvG;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,SAAS,GAAG,QAAQ,CAAC;AAM5D;;;GAGG;AACH,MAAM,MAAM,YAAY,GACpB,wBAAwB,GACxB,wBAAwB,GACxB,SAAS,GACT,2BAA2B,GAC3B,OAAO,CAAC;AAEZ;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,WAAW,CAAC;IACpB,WAAW,EAAE,WAAW,CAAC;IACzB,aAAa,EAAE,aAAa,CAAC;IAC7B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAID;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,EAAE,SAAS,CAAC;IACpB,cAAc,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAC;IAC/C,qBAAqB,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CACxE,GAAG,CACA;IACE,IAAI,EAAE,iBAAiB,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB,GACD;IACE,IAAI,EAAE,8BAA8B,CAAC;IACrC,UAAU,EAAE,MAAM,CAAC;CACpB,CACJ,CAAC;AAOF;;;GAGG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;IACtC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAiC;IACjE,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAwD;IACzF,OAAO,CAAC,QAAQ,CAAC,4BAA4B,CAA0B;IACvE,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAS;IAChD,OAAO,CAAC,OAAO,CAA2B;IAC1C,OAAO,CAAC,cAAc,CAAC,CAAqB;IAC5C,OAAO,CAAC,YAAY,CAAkB;IACtC,OAAO,CAAC,GAAG,CAAC,CAAY;IACxB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,yBAAyB,CAAC,CAAqB;IACvD,OAAO,CAAC,aAAa,CAAC,CAAiB;gBAEpB,OAAO,EAAE,wBAAwB;IA6BpD,IAAW,MAAM,IAAI,YAAY,CAEhC;IAGD,OAAO,KAAK,MAAM,QAGjB;IAEY,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC;IAWlC,UAAU,CAAC,OAAO,GAAE;QAAE,WAAW,CAAC,EAAE,OAAO,CAAA;KAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAclE,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAW1B,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtC,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC;YAcnC,gBAAgB;YA8BhB,2BAA2B;YAU3B,+BAA+B;YAa/B,gBAAgB;YAmBhB,oBAAoB;YAyBpB,+BAA+B;YAwC/B,qBAAqB;YAkBrB,qCAAqC;IA0DnD,OAAO,CAAC,kBAAkB;YAqCZ,qBAAqB;IAmEnC;;;;OAIG;YACW,iCAAiC;CA4HhD"}
|
|
@@ -4,5 +4,5 @@ import type { IDisposableTunneledBrowserConnection } from './ITunneledBrowserCon
|
|
|
4
4
|
* Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.
|
|
5
5
|
* @beta
|
|
6
6
|
*/
|
|
7
|
-
export declare function tunneledBrowserConnection(logger: ITerminal, port?: number): Promise<IDisposableTunneledBrowserConnection>;
|
|
7
|
+
export declare function tunneledBrowserConnection(logger: ITerminal, port?: number, playwrightVersion?: string): Promise<IDisposableTunneledBrowserConnection>;
|
|
8
8
|
//# sourceMappingURL=TunneledBrowserConnection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunneledBrowserConnection.d.ts","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAUrD,OAAO,KAAK,EACV,oCAAoC,EAGrC,MAAM,8BAA8B,CAAC;
|
|
1
|
+
{"version":3,"file":"TunneledBrowserConnection.d.ts","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAUrD,OAAO,KAAK,EACV,oCAAoC,EAGrC,MAAM,8BAA8B,CAAC;AAGtC;;;GAGG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,SAAS,EACjB,IAAI,GAAE,MAA4B,EAClC,iBAAiB,GAAE,MAAsC,GACxD,OAAO,CAAC,oCAAoC,CAAC,CAqM/C"}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// See LICENSE in the project root for license information.
|
|
3
3
|
import { once } from 'node:events';
|
|
4
4
|
import { WebSocket } from 'ws';
|
|
5
|
-
import semver from 'semver';
|
|
6
5
|
import { TerminalProviderSeverity, TerminalStreamWritable } from '@rushstack/terminal';
|
|
7
6
|
import { Executable, FileSystem, Async } from '@rushstack/node-core-library';
|
|
8
7
|
import { getNormalizedErrorString, getWebSocketCloseReason, getWebSocketReadyStateString, WebSocketCloseCode } from './utilities';
|
|
@@ -281,17 +280,13 @@ export class PlaywrightTunnel {
|
|
|
281
280
|
if (action !== 'handshake') {
|
|
282
281
|
throw new Error(`Invalid action: ${action}. Expected 'handshake'.`);
|
|
283
282
|
}
|
|
284
|
-
const playwrightVersionSemver = semver.coerce(playwrightVersion);
|
|
285
|
-
if (!playwrightVersionSemver) {
|
|
286
|
-
throw new Error(`Invalid Playwright version: ${playwrightVersion}. Must be a valid semver version.`);
|
|
287
|
-
}
|
|
288
283
|
if (!isValidBrowserName(browserName)) {
|
|
289
284
|
throw new Error(`Invalid browser name: ${browserName}. Must be one of ${Array.from(validBrowserNames).join(', ')}.`);
|
|
290
285
|
}
|
|
291
286
|
return {
|
|
292
287
|
action,
|
|
293
288
|
launchOptions: launchOptions,
|
|
294
|
-
playwrightVersion
|
|
289
|
+
playwrightVersion,
|
|
295
290
|
browserName
|
|
296
291
|
};
|
|
297
292
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaywrightBrowserTunnel.js","sourceRoot":"","sources":["../src/PlaywrightBrowserTunnel.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\n\nimport type { BrowserServer, BrowserType, LaunchOptions } from 'playwright-core';\nimport { type RawData, WebSocket, type WebSocketServer } from 'ws';\nimport semver from 'semver';\n\nimport { TerminalProviderSeverity, TerminalStreamWritable, type ITerminal } from '@rushstack/terminal';\nimport { Executable, FileSystem, Async } from '@rushstack/node-core-library';\n\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from './utilities';\nimport { LaunchOptionsValidator, type ILaunchOptionsValidationResult } from './LaunchOptionsValidator';\n\n/**\n * Allowed Playwright browser names.\n * @beta\n */\nexport type BrowserName = 'chromium' | 'firefox' | 'webkit';\nconst validBrowserNames: Set<string> = new Set(['chromium', 'firefox', 'webkit'] satisfies BrowserName[]);\nfunction isValidBrowserName(browserName: string): browserName is BrowserName {\n return validBrowserNames.has(browserName);\n}\n\n/**\n * Status values reported by {@link PlaywrightTunnel}.\n * @beta\n */\nexport type TunnelStatus =\n | 'waiting-for-connection'\n | 'browser-server-running'\n | 'stopped'\n | 'setting-up-browser-server'\n | 'error';\n\n/**\n * Handshake data exchanged during the initial WebSocket connection.\n * @beta\n */\nexport interface IHandshake {\n action: 'handshake';\n browserName: BrowserName;\n launchOptions: LaunchOptions;\n playwrightVersion: semver.SemVer;\n}\n\ntype TunnelMode = 'poll-connection' | 'wait-for-incoming-connection';\n\n/**\n * Options for configuring a {@link PlaywrightTunnel} instance.\n * @beta\n */\nexport type IPlaywrightTunnelOptions = {\n terminal: ITerminal;\n onStatusChange: (status: TunnelStatus) => void;\n playwrightInstallPath: string;\n /**\n * Optional callback invoked before launching the browser server.\n * Receives the handshake data including launch options.\n * If the callback returns false, the browser server launch will be aborted.\n * This allows the client to prompt the user for approval before starting.\n */\n onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n} & (\n | {\n mode: 'poll-connection';\n wsEndpoint: string;\n }\n | {\n mode: 'wait-for-incoming-connection';\n listenPort: number;\n }\n);\n\ninterface IBrowserServerProxy {\n browserServer: BrowserServer;\n client: WebSocket;\n}\n\n/**\n * Hosts a Playwright browser server and forwards traffic over a WebSocket tunnel.\n * @beta\n */\nexport class PlaywrightTunnel {\n private readonly _terminal: ITerminal;\n private readonly _onStatusChange: (status: TunnelStatus) => void;\n private readonly _onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n private readonly _playwrightBrowsersInstalled: Set<string> = new Set();\n private readonly _wsEndpoint: string | undefined;\n private readonly _listenPort: number | undefined;\n private readonly _playwrightInstallPath: string;\n private _status: TunnelStatus = 'stopped';\n private _initWsPromise?: Promise<WebSocket>;\n private _keepRunning: boolean = false;\n private _ws?: WebSocket;\n private _mode: TunnelMode;\n private _pendingConnectionAttempt?: Promise<WebSocket>;\n private _pollInterval?: NodeJS.Timeout;\n\n public constructor(options: IPlaywrightTunnelOptions) {\n const { mode, terminal, onStatusChange, playwrightInstallPath, onBeforeLaunch } = options;\n\n switch (mode) {\n case 'poll-connection':\n if (!options.wsEndpoint) {\n throw new Error('wsEndpoint is required for poll-connection mode');\n }\n this._wsEndpoint = options.wsEndpoint;\n this._listenPort = undefined;\n break;\n case 'wait-for-incoming-connection':\n if (options.listenPort === undefined) {\n throw new Error('listenPort is required for wait-for-incoming-connection mode');\n }\n this._wsEndpoint = undefined;\n this._listenPort = options.listenPort;\n break;\n default:\n throw new Error(`Invalid mode: ${mode}`);\n }\n\n this._mode = mode;\n this._terminal = terminal;\n this._onStatusChange = onStatusChange;\n this._onBeforeLaunch = onBeforeLaunch;\n this._playwrightInstallPath = playwrightInstallPath;\n }\n\n public get status(): TunnelStatus {\n return this._status;\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n private set status(newStatus: TunnelStatus) {\n this._status = newStatus;\n this._onStatusChange(newStatus);\n }\n\n public async waitForCloseAsync(): Promise<void> {\n const terminal: ITerminal = this._terminal;\n const initWsPromise: Promise<WebSocket> | undefined = this._initWsPromise;\n if (initWsPromise) {\n const ws: WebSocket = await initWsPromise;\n await once(ws, 'close');\n terminal.writeDebugLine('WebSocket connection closed. resolving init promise.');\n this._initWsPromise = undefined;\n }\n }\n\n public async startAsync(options: { keepRunning?: boolean } = {}): Promise<void> {\n this._keepRunning = options.keepRunning ?? true;\n const terminal: ITerminal = this._terminal;\n terminal.writeLine(`keepRunning: ${this._keepRunning}`);\n while (this._keepRunning) {\n if (!this._initWsPromise) {\n this._initWsPromise = this._initPlaywrightBrowserTunnelAsync();\n } else {\n terminal.writeLine(`Tunnel is already running with status: ${this.status}`);\n }\n await this.waitForCloseAsync();\n }\n }\n\n public async stopAsync(): Promise<void> {\n this._keepRunning = false;\n if (this._pollInterval) {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n }\n await this._initWsPromise?.finally(() => {\n this._ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel stopped');\n });\n }\n\n public async [Symbol.asyncDispose](): Promise<void> {\n this._terminal.writeLine('Disposing WebSocket connection.');\n await this.stopAsync();\n }\n\n public async cleanTempFilesAsync(): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n this._terminal.writeLine(`Cleaning up temporary files in ${tmpPath}`);\n try {\n await FileSystem.ensureEmptyFolderAsync(tmpPath);\n this._terminal.writeLine(`Temporary files cleaned up.`);\n } catch (error) {\n this._terminal.writeLine(`Failed to clean up temporary files: ${getNormalizedErrorString(error)}`);\n }\n }\n\n // TODO: We should implement an uninstall command to remove installed Playwright browsers\n // public async uninstallPlaywrightBrowsersAsync(): Promise<void> {}\n\n private async _runCommandAsync(command: string, args: string[]): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n await FileSystem.ensureFolderAsync(tmpPath);\n this._terminal.writeLine(`Running command: ${command} ${args.join(' ')} in ${tmpPath}`);\n\n const cp: ChildProcess = Executable.spawn(command, args, {\n stdio: [\n 'ignore', // stdin\n 'pipe', // stdout\n 'pipe' // stderr\n ],\n currentWorkingDirectory: tmpPath\n });\n\n cp.stdout?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.log\n })\n );\n cp.stderr?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.error\n })\n );\n\n await Executable.waitForExitAsync(cp, { throwOnNonZeroExitCode: true, throwOnSignal: true });\n }\n\n private async _installPlaywrightCoreAsync({\n playwrightVersion\n }: Pick<IHandshake, 'playwrightVersion'>): Promise<void> {\n this._terminal.writeLine(`Installing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('npm', [\n 'install',\n `playwright-core-${playwrightVersion}@npm:playwright-core@${playwrightVersion}`\n ]);\n }\n\n private async _installPlaywrightBrowsersAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<void> {\n await this._installPlaywrightCoreAsync({ playwrightVersion });\n this._terminal.writeLine(`Executing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('node', [\n `node_modules/playwright-core-${playwrightVersion}/cli.js`,\n 'install',\n browserName\n ]);\n }\n\n private async _tryConnectAsync(): Promise<WebSocket> {\n const wsEndpoint: string | undefined = this._wsEndpoint;\n if (!wsEndpoint) {\n throw new Error('WebSocket endpoint is not defined');\n }\n return await new Promise<WebSocket>((resolve, reject) => {\n const ws: WebSocket = new WebSocket(wsEndpoint);\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection opened`);\n resolve(ws);\n });\n ws.once('error', (error) => {\n reject(error);\n });\n });\n }\n\n // TODO: Only supporting one test at a time.\n // Need to support multiple simultaneous connections for parallel tests.\n private async _pollConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine(`Waiting for WebSocket connection`);\n return await new Promise((resolve, reject) => {\n this._pollInterval = setInterval(() => {\n if (this._pendingConnectionAttempt) {\n return; // Skip if a connection attempt is already in progress\n }\n const connectionPromise: Promise<WebSocket> = this._tryConnectAsync();\n this._pendingConnectionAttempt = connectionPromise;\n connectionPromise\n .then((ws: WebSocket) => {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n ws.removeAllListeners();\n this._pendingConnectionAttempt = undefined;\n resolve(ws);\n })\n .catch(() => {\n // no-op - will retry on next interval\n this._pendingConnectionAttempt = undefined;\n });\n }, 500);\n });\n }\n\n private async _waitForIncomingConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine('Waiting for incoming WebSocket connection');\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const server: WebSocketServer = new WebSocket.Server({ port: this._listenPort });\n\n const cleanup = (): void => {\n server.removeAllListeners();\n };\n\n server.once('connection', (ws) => {\n this._terminal.writeLine('Incoming WebSocket connection established');\n\n // Stop listening immediately so the port is released\n cleanup();\n server.close((closeError?: Error) => {\n if (closeError) {\n this._terminal.writeLine(\n `Failed to close WebSocket server: ${\n closeError instanceof Error ? closeError.message : closeError\n }`\n );\n }\n resolve(ws);\n });\n });\n\n server.once('error', (error) => {\n this._terminal.writeLine(`WebSocket server error: ${getNormalizedErrorString(error)}`);\n\n cleanup();\n // Try to close (best-effort), then reject\n server.close(() => reject(error));\n });\n });\n }\n\n // TODO: If a user runs this for the first time, `this._playwrightBrowsersInstalled` will be empty\n // and it will try to install the browsers every time. We should persist this information. Maybe a cache file with text per\n // machine instance?\n private async _setupPlaywrightAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<typeof import('playwright-core')> {\n const browserKey: string = `${playwrightVersion}-${browserName}`;\n this._terminal.writeLine(`Checking for installed playwright browsers. Installed browsers: ${browserKey}`);\n if (!this._playwrightBrowsersInstalled.has(browserKey)) {\n this._terminal.writeLine(\n `Playwright browser not found. Installing playwright-core version ${playwrightVersion}`\n );\n await this._installPlaywrightBrowsersAsync({ playwrightVersion, browserName });\n this._playwrightBrowsersInstalled.add(browserKey);\n }\n\n this._terminal.writeLine(`Using playwright-core version ${playwrightVersion} for browser server`);\n return await import(`${this._playwrightInstallPath}/node_modules/playwright-core-${playwrightVersion}`);\n }\n\n private async _getPlaywrightBrowserServerProxyAsync({\n browserName,\n playwrightVersion,\n launchOptions\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName' | 'launchOptions'>): Promise<IBrowserServerProxy> {\n const terminal: ITerminal = this._terminal;\n\n // Validate launch options against security allowlist\n terminal.writeLine('Validating launch options against security allowlist...');\n const validationResult: ILaunchOptionsValidationResult =\n await LaunchOptionsValidator.validateLaunchOptionsAsync(launchOptions, terminal);\n\n if (!validationResult.isValid) {\n terminal.writeWarningLine(\n `Some launch options were denied: ${validationResult.deniedOptions.join(', ')}`\n );\n terminal.writeWarningLine(`Using filtered launch options. Denied options have been removed.`);\n }\n\n // Use filtered options and ensure headless: false for headed tests in codespaces\n // This is critical for the extension's purpose - enabling headed Playwright tests remotely\n const safeOptions: LaunchOptions = {\n ...validationResult.filteredOptions,\n headless: false\n };\n\n // Log the validated options, excluding 'headless' since it's always false for this extension\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = safeOptions;\n terminal.writeLine(\n `Launch options after validation: ${JSON.stringify(logOptions)} (headless: false enforced)`\n );\n\n const playwright: typeof import('playwright-core') = await this._setupPlaywrightAsync({\n playwrightVersion,\n browserName\n });\n\n const { chromium, firefox, webkit } = playwright;\n const browsers: Record<BrowserName, BrowserType> = { chromium, firefox, webkit };\n\n const browserServer: BrowserServer = await browsers[browserName].launchServer(safeOptions);\n\n if (!browserServer) {\n throw new Error(\n `Failed to launch browser server for ${browserName} with options: ${JSON.stringify(safeOptions)}`\n );\n }\n\n terminal.writeLine(`Launched ${browserName} browser server`);\n const client: WebSocket = new WebSocket(browserServer.wsEndpoint());\n\n return {\n browserServer,\n client\n };\n }\n\n private _validateHandshake(rawHandshake: unknown): IHandshake {\n if (\n typeof rawHandshake !== 'object' ||\n rawHandshake === null ||\n 'action' in rawHandshake === false ||\n 'browserName' in rawHandshake === false ||\n 'playwrightVersion' in rawHandshake === false ||\n 'launchOptions' in rawHandshake === false ||\n typeof rawHandshake.action !== 'string' ||\n typeof rawHandshake.browserName !== 'string' ||\n typeof rawHandshake.playwrightVersion !== 'string' ||\n typeof rawHandshake.launchOptions !== 'object'\n ) {\n throw new Error(`Invalid handshake: ${JSON.stringify(rawHandshake)}. Must be an object.`);\n }\n\n const { action, browserName, playwrightVersion, launchOptions } = rawHandshake;\n\n if (action !== 'handshake') {\n throw new Error(`Invalid action: ${action}. Expected 'handshake'.`);\n }\n const playwrightVersionSemver: semver.SemVer | null = semver.coerce(playwrightVersion);\n if (!playwrightVersionSemver) {\n throw new Error(`Invalid Playwright version: ${playwrightVersion}. Must be a valid semver version.`);\n }\n if (!isValidBrowserName(browserName)) {\n throw new Error(\n `Invalid browser name: ${browserName}. Must be one of ${Array.from(validBrowserNames).join(', ')}.`\n );\n }\n\n return {\n action,\n launchOptions: launchOptions as LaunchOptions,\n playwrightVersion: playwrightVersionSemver,\n browserName\n };\n }\n\n // ws1 is the tunnel websocket, ws2 is the browser server websocket\n private async _setupForwardingAsync(ws1: WebSocket, ws2: WebSocket): Promise<void> {\n this._terminal.writeLine('Setting up message forwarding between ws1 and ws2');\n this._terminal.writeLine(` ws1 (tunnel) readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n this._terminal.writeLine(` ws2 (browser) readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n\n const messageCount: { ws1ToWs2: number; ws2ToWs1: number } = { ws1ToWs2: 0, ws2ToWs1: 0 };\n\n ws1.on('message', (data) => {\n messageCount.ws1ToWs2++;\n if (ws2.readyState === WebSocket.OPEN) {\n ws2.send(data);\n } else {\n this._terminal.writeLine(\n `ws2 not open (state: ${getWebSocketReadyStateString(ws2.readyState)}). Dropping message #${messageCount.ws1ToWs2}`\n );\n }\n });\n ws2.on('message', (data) => {\n messageCount.ws2ToWs1++;\n if (ws1.readyState === WebSocket.OPEN) {\n ws1.send(data);\n } else {\n this._terminal.writeLine(\n `ws1 not open (state: ${getWebSocketReadyStateString(ws1.readyState)}). Dropping message #${messageCount.ws2ToWs1}`\n );\n }\n });\n\n ws1.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws1 (tunnel) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws2.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws2 (browser) in response');\n ws2.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel closed');\n }\n });\n ws2.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws2 (browser) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws1.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws1 (tunnel) in response');\n ws1.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Browser closed');\n }\n });\n\n ws1.once('error', (error) => {\n this._terminal.writeErrorLine(`ws1 (tunnel) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws1 readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n });\n ws2.once('error', (error) => {\n this._terminal.writeErrorLine(`ws2 (browser) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws2 readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n });\n }\n\n /**\n * Initializes the Playwright browser tunnel by establishing a WebSocket connection\n * and setting up the browser server.\n * Returns when the handshake is complete and the browser server is running.\n */\n private async _initPlaywrightBrowserTunnelAsync(): Promise<WebSocket> {\n let handshake: IHandshake | undefined = undefined;\n let client: WebSocket | undefined = undefined;\n let browserServer: BrowserServer | undefined = undefined;\n\n this.status = 'waiting-for-connection';\n const ws: WebSocket =\n this._mode === 'poll-connection'\n ? await this._pollConnectionAsync()\n : await this._waitForIncomingConnectionAsync();\n\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection established`);\n handshake = undefined;\n });\n\n ws.on('error', (error) => {\n this._terminal.writeLine(`WebSocket error occurred: ${getNormalizedErrorString(error)}`);\n });\n\n ws.on('close', async (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._initWsPromise = undefined;\n this.status = 'stopped';\n this._terminal.writeLine(\n `WebSocket connection closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(` handshake received: ${handshake !== undefined}`);\n this._terminal.writeLine(` browserServer active: ${browserServer !== undefined}`);\n if (browserServer) {\n this._terminal.writeLine(' Closing browser server...');\n await browserServer.close();\n this._terminal.writeLine(' Browser server closed');\n }\n });\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const onMessageHandler = async (data: RawData): Promise<void> => {\n const terminal: ITerminal = this._terminal;\n if (!handshake) {\n try {\n const rawHandshakeString: string = data.toString();\n const rawHandshake: unknown = JSON.parse(rawHandshakeString);\n terminal.writeLine(`Received handshake: ${rawHandshakeString}`);\n handshake = this._validateHandshake(rawHandshake);\n\n // Call the onBeforeLaunch callback if provided\n if (this._onBeforeLaunch) {\n terminal.writeLine('Requesting user approval before launching browser server...');\n const shouldProceed: boolean = await this._onBeforeLaunch(handshake);\n if (!shouldProceed) {\n terminal.writeLine('Browser server launch cancelled by user.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Launch cancelled by user');\n reject(new Error('Browser server launch cancelled by user'));\n return;\n }\n terminal.writeLine('User approved browser server launch.');\n }\n\n this.status = 'setting-up-browser-server';\n const browserServerProxy: IBrowserServerProxy =\n await this._getPlaywrightBrowserServerProxyAsync(handshake);\n client = browserServerProxy.client;\n browserServer = browserServerProxy.browserServer;\n\n // Monitor browser server process for crashes\n const browserProcess: ChildProcess | null = browserServer.process();\n if (browserProcess) {\n browserProcess.on('exit', (code: number | null, signal: string | null) => {\n terminal.writeErrorLine(`Browser server process exited - code: ${code}, signal: ${signal}`);\n });\n browserProcess.on('error', (err: Error) => {\n terminal.writeErrorLine(`Browser server process error: ${getNormalizedErrorString(err)}`);\n });\n terminal.writeDebugLine(`Browser server process started with PID: ${browserProcess.pid}`);\n } else {\n terminal.writeDebugLine('Warning: Browser server process handle not available for monitoring');\n }\n\n this.status = 'browser-server-running';\n\n // Send ack so that the counterpart also knows to start forwarding messages.\n // NOTE: The 1-second delay is an intentional workaround. In the current\n // protocol, the remote tunnel endpoint does not expose an explicit \"ready\"\n // signal for when it has finished initializing its own forwarding logic\n // after receiving the initial handshake. This\n // delay avoids races where early messages could be dropped or mishandled\n // if they arrive before the remote side is fully ready.\n //\n // TODO: A future improvement would be to replace this delay with a deterministic\n // synchronization mechanism (e.g. an explicit \"ready\" message or event)\n // instead of relying on a fixed timeout.\n await Async.sleepAsync(2000);\n\n ws.send(JSON.stringify({ action: 'handshakeAck' }));\n await this._setupForwardingAsync(ws, client);\n\n // Clean up message handler after successful handshake\n ws.off('message', onMessageHandler);\n resolve(ws);\n } catch (error) {\n terminal.writeLine(`Error processing handshake: ${error}`);\n this.status = 'error';\n\n // Cleanup and close connection on error\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Handshake error');\n reject(error);\n return;\n }\n } else {\n if (!client) {\n terminal.writeLine('Browser WebSocket client is not initialized.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Browser client not initialized');\n return;\n }\n }\n };\n ws.on('message', onMessageHandler);\n });\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"PlaywrightBrowserTunnel.js","sourceRoot":"","sources":["../src/PlaywrightBrowserTunnel.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { ChildProcess } from 'node:child_process';\nimport { once } from 'node:events';\n\nimport type { BrowserServer, BrowserType, LaunchOptions } from 'playwright-core';\nimport { type RawData, WebSocket, type WebSocketServer } from 'ws';\n\nimport { TerminalProviderSeverity, TerminalStreamWritable, type ITerminal } from '@rushstack/terminal';\nimport { Executable, FileSystem, Async } from '@rushstack/node-core-library';\n\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from './utilities';\nimport { LaunchOptionsValidator, type ILaunchOptionsValidationResult } from './LaunchOptionsValidator';\n\n/**\n * Allowed Playwright browser names.\n * @beta\n */\nexport type BrowserName = 'chromium' | 'firefox' | 'webkit';\nconst validBrowserNames: Set<string> = new Set(['chromium', 'firefox', 'webkit'] satisfies BrowserName[]);\nfunction isValidBrowserName(browserName: string): browserName is BrowserName {\n return validBrowserNames.has(browserName);\n}\n\n/**\n * Status values reported by {@link PlaywrightTunnel}.\n * @beta\n */\nexport type TunnelStatus =\n | 'waiting-for-connection'\n | 'browser-server-running'\n | 'stopped'\n | 'setting-up-browser-server'\n | 'error';\n\n/**\n * Handshake data exchanged during the initial WebSocket connection.\n * @beta\n */\nexport interface IHandshake {\n action: 'handshake';\n browserName: BrowserName;\n launchOptions: LaunchOptions;\n playwrightVersion: string;\n}\n\ntype TunnelMode = 'poll-connection' | 'wait-for-incoming-connection';\n\n/**\n * Options for configuring a {@link PlaywrightTunnel} instance.\n * @beta\n */\nexport type IPlaywrightTunnelOptions = {\n terminal: ITerminal;\n onStatusChange: (status: TunnelStatus) => void;\n playwrightInstallPath: string;\n /**\n * Optional callback invoked before launching the browser server.\n * Receives the handshake data including launch options.\n * If the callback returns false, the browser server launch will be aborted.\n * This allows the client to prompt the user for approval before starting.\n */\n onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n} & (\n | {\n mode: 'poll-connection';\n wsEndpoint: string;\n }\n | {\n mode: 'wait-for-incoming-connection';\n listenPort: number;\n }\n);\n\ninterface IBrowserServerProxy {\n browserServer: BrowserServer;\n client: WebSocket;\n}\n\n/**\n * Hosts a Playwright browser server and forwards traffic over a WebSocket tunnel.\n * @beta\n */\nexport class PlaywrightTunnel {\n private readonly _terminal: ITerminal;\n private readonly _onStatusChange: (status: TunnelStatus) => void;\n private readonly _onBeforeLaunch?: (handshake: IHandshake) => Promise<boolean> | boolean;\n private readonly _playwrightBrowsersInstalled: Set<string> = new Set();\n private readonly _wsEndpoint: string | undefined;\n private readonly _listenPort: number | undefined;\n private readonly _playwrightInstallPath: string;\n private _status: TunnelStatus = 'stopped';\n private _initWsPromise?: Promise<WebSocket>;\n private _keepRunning: boolean = false;\n private _ws?: WebSocket;\n private _mode: TunnelMode;\n private _pendingConnectionAttempt?: Promise<WebSocket>;\n private _pollInterval?: NodeJS.Timeout;\n\n public constructor(options: IPlaywrightTunnelOptions) {\n const { mode, terminal, onStatusChange, playwrightInstallPath, onBeforeLaunch } = options;\n\n switch (mode) {\n case 'poll-connection':\n if (!options.wsEndpoint) {\n throw new Error('wsEndpoint is required for poll-connection mode');\n }\n this._wsEndpoint = options.wsEndpoint;\n this._listenPort = undefined;\n break;\n case 'wait-for-incoming-connection':\n if (options.listenPort === undefined) {\n throw new Error('listenPort is required for wait-for-incoming-connection mode');\n }\n this._wsEndpoint = undefined;\n this._listenPort = options.listenPort;\n break;\n default:\n throw new Error(`Invalid mode: ${mode}`);\n }\n\n this._mode = mode;\n this._terminal = terminal;\n this._onStatusChange = onStatusChange;\n this._onBeforeLaunch = onBeforeLaunch;\n this._playwrightInstallPath = playwrightInstallPath;\n }\n\n public get status(): TunnelStatus {\n return this._status;\n }\n\n // eslint-disable-next-line @typescript-eslint/naming-convention\n private set status(newStatus: TunnelStatus) {\n this._status = newStatus;\n this._onStatusChange(newStatus);\n }\n\n public async waitForCloseAsync(): Promise<void> {\n const terminal: ITerminal = this._terminal;\n const initWsPromise: Promise<WebSocket> | undefined = this._initWsPromise;\n if (initWsPromise) {\n const ws: WebSocket = await initWsPromise;\n await once(ws, 'close');\n terminal.writeDebugLine('WebSocket connection closed. resolving init promise.');\n this._initWsPromise = undefined;\n }\n }\n\n public async startAsync(options: { keepRunning?: boolean } = {}): Promise<void> {\n this._keepRunning = options.keepRunning ?? true;\n const terminal: ITerminal = this._terminal;\n terminal.writeLine(`keepRunning: ${this._keepRunning}`);\n while (this._keepRunning) {\n if (!this._initWsPromise) {\n this._initWsPromise = this._initPlaywrightBrowserTunnelAsync();\n } else {\n terminal.writeLine(`Tunnel is already running with status: ${this.status}`);\n }\n await this.waitForCloseAsync();\n }\n }\n\n public async stopAsync(): Promise<void> {\n this._keepRunning = false;\n if (this._pollInterval) {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n }\n await this._initWsPromise?.finally(() => {\n this._ws?.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel stopped');\n });\n }\n\n public async [Symbol.asyncDispose](): Promise<void> {\n this._terminal.writeLine('Disposing WebSocket connection.');\n await this.stopAsync();\n }\n\n public async cleanTempFilesAsync(): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n this._terminal.writeLine(`Cleaning up temporary files in ${tmpPath}`);\n try {\n await FileSystem.ensureEmptyFolderAsync(tmpPath);\n this._terminal.writeLine(`Temporary files cleaned up.`);\n } catch (error) {\n this._terminal.writeLine(`Failed to clean up temporary files: ${getNormalizedErrorString(error)}`);\n }\n }\n\n // TODO: We should implement an uninstall command to remove installed Playwright browsers\n // public async uninstallPlaywrightBrowsersAsync(): Promise<void> {}\n\n private async _runCommandAsync(command: string, args: string[]): Promise<void> {\n const tmpPath: string = this._playwrightInstallPath;\n await FileSystem.ensureFolderAsync(tmpPath);\n this._terminal.writeLine(`Running command: ${command} ${args.join(' ')} in ${tmpPath}`);\n\n const cp: ChildProcess = Executable.spawn(command, args, {\n stdio: [\n 'ignore', // stdin\n 'pipe', // stdout\n 'pipe' // stderr\n ],\n currentWorkingDirectory: tmpPath\n });\n\n cp.stdout?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.log\n })\n );\n cp.stderr?.pipe(\n new TerminalStreamWritable({\n terminal: this._terminal,\n severity: TerminalProviderSeverity.error\n })\n );\n\n await Executable.waitForExitAsync(cp, { throwOnNonZeroExitCode: true, throwOnSignal: true });\n }\n\n private async _installPlaywrightCoreAsync({\n playwrightVersion\n }: Pick<IHandshake, 'playwrightVersion'>): Promise<void> {\n this._terminal.writeLine(`Installing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('npm', [\n 'install',\n `playwright-core-${playwrightVersion}@npm:playwright-core@${playwrightVersion}`\n ]);\n }\n\n private async _installPlaywrightBrowsersAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<void> {\n await this._installPlaywrightCoreAsync({ playwrightVersion });\n this._terminal.writeLine(`Executing playwright-core version ${playwrightVersion}`);\n await this._runCommandAsync('node', [\n `node_modules/playwright-core-${playwrightVersion}/cli.js`,\n 'install',\n browserName\n ]);\n }\n\n private async _tryConnectAsync(): Promise<WebSocket> {\n const wsEndpoint: string | undefined = this._wsEndpoint;\n if (!wsEndpoint) {\n throw new Error('WebSocket endpoint is not defined');\n }\n return await new Promise<WebSocket>((resolve, reject) => {\n const ws: WebSocket = new WebSocket(wsEndpoint);\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection opened`);\n resolve(ws);\n });\n ws.once('error', (error) => {\n reject(error);\n });\n });\n }\n\n // TODO: Only supporting one test at a time.\n // Need to support multiple simultaneous connections for parallel tests.\n private async _pollConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine(`Waiting for WebSocket connection`);\n return await new Promise((resolve, reject) => {\n this._pollInterval = setInterval(() => {\n if (this._pendingConnectionAttempt) {\n return; // Skip if a connection attempt is already in progress\n }\n const connectionPromise: Promise<WebSocket> = this._tryConnectAsync();\n this._pendingConnectionAttempt = connectionPromise;\n connectionPromise\n .then((ws: WebSocket) => {\n clearInterval(this._pollInterval);\n this._pollInterval = undefined;\n ws.removeAllListeners();\n this._pendingConnectionAttempt = undefined;\n resolve(ws);\n })\n .catch(() => {\n // no-op - will retry on next interval\n this._pendingConnectionAttempt = undefined;\n });\n }, 500);\n });\n }\n\n private async _waitForIncomingConnectionAsync(): Promise<WebSocket> {\n this._terminal.writeLine('Waiting for incoming WebSocket connection');\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const server: WebSocketServer = new WebSocket.Server({ port: this._listenPort });\n\n const cleanup = (): void => {\n server.removeAllListeners();\n };\n\n server.once('connection', (ws) => {\n this._terminal.writeLine('Incoming WebSocket connection established');\n\n // Stop listening immediately so the port is released\n cleanup();\n server.close((closeError?: Error) => {\n if (closeError) {\n this._terminal.writeLine(\n `Failed to close WebSocket server: ${\n closeError instanceof Error ? closeError.message : closeError\n }`\n );\n }\n resolve(ws);\n });\n });\n\n server.once('error', (error) => {\n this._terminal.writeLine(`WebSocket server error: ${getNormalizedErrorString(error)}`);\n\n cleanup();\n // Try to close (best-effort), then reject\n server.close(() => reject(error));\n });\n });\n }\n\n // TODO: If a user runs this for the first time, `this._playwrightBrowsersInstalled` will be empty\n // and it will try to install the browsers every time. We should persist this information. Maybe a cache file with text per\n // machine instance?\n private async _setupPlaywrightAsync({\n playwrightVersion,\n browserName\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName'>): Promise<typeof import('playwright-core')> {\n const browserKey: string = `${playwrightVersion}-${browserName}`;\n this._terminal.writeLine(`Checking for installed playwright browsers. Installed browsers: ${browserKey}`);\n if (!this._playwrightBrowsersInstalled.has(browserKey)) {\n this._terminal.writeLine(\n `Playwright browser not found. Installing playwright-core version ${playwrightVersion}`\n );\n await this._installPlaywrightBrowsersAsync({ playwrightVersion, browserName });\n this._playwrightBrowsersInstalled.add(browserKey);\n }\n\n this._terminal.writeLine(`Using playwright-core version ${playwrightVersion} for browser server`);\n return await import(`${this._playwrightInstallPath}/node_modules/playwright-core-${playwrightVersion}`);\n }\n\n private async _getPlaywrightBrowserServerProxyAsync({\n browserName,\n playwrightVersion,\n launchOptions\n }: Pick<IHandshake, 'playwrightVersion' | 'browserName' | 'launchOptions'>): Promise<IBrowserServerProxy> {\n const terminal: ITerminal = this._terminal;\n\n // Validate launch options against security allowlist\n terminal.writeLine('Validating launch options against security allowlist...');\n const validationResult: ILaunchOptionsValidationResult =\n await LaunchOptionsValidator.validateLaunchOptionsAsync(launchOptions, terminal);\n\n if (!validationResult.isValid) {\n terminal.writeWarningLine(\n `Some launch options were denied: ${validationResult.deniedOptions.join(', ')}`\n );\n terminal.writeWarningLine(`Using filtered launch options. Denied options have been removed.`);\n }\n\n // Use filtered options and ensure headless: false for headed tests in codespaces\n // This is critical for the extension's purpose - enabling headed Playwright tests remotely\n const safeOptions: LaunchOptions = {\n ...validationResult.filteredOptions,\n headless: false\n };\n\n // Log the validated options, excluding 'headless' since it's always false for this extension\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = safeOptions;\n terminal.writeLine(\n `Launch options after validation: ${JSON.stringify(logOptions)} (headless: false enforced)`\n );\n\n const playwright: typeof import('playwright-core') = await this._setupPlaywrightAsync({\n playwrightVersion,\n browserName\n });\n\n const { chromium, firefox, webkit } = playwright;\n const browsers: Record<BrowserName, BrowserType> = { chromium, firefox, webkit };\n\n const browserServer: BrowserServer = await browsers[browserName].launchServer(safeOptions);\n\n if (!browserServer) {\n throw new Error(\n `Failed to launch browser server for ${browserName} with options: ${JSON.stringify(safeOptions)}`\n );\n }\n\n terminal.writeLine(`Launched ${browserName} browser server`);\n const client: WebSocket = new WebSocket(browserServer.wsEndpoint());\n\n return {\n browserServer,\n client\n };\n }\n\n private _validateHandshake(rawHandshake: unknown): IHandshake {\n if (\n typeof rawHandshake !== 'object' ||\n rawHandshake === null ||\n 'action' in rawHandshake === false ||\n 'browserName' in rawHandshake === false ||\n 'playwrightVersion' in rawHandshake === false ||\n 'launchOptions' in rawHandshake === false ||\n typeof rawHandshake.action !== 'string' ||\n typeof rawHandshake.browserName !== 'string' ||\n typeof rawHandshake.playwrightVersion !== 'string' ||\n typeof rawHandshake.launchOptions !== 'object'\n ) {\n throw new Error(`Invalid handshake: ${JSON.stringify(rawHandshake)}. Must be an object.`);\n }\n\n const { action, browserName, playwrightVersion, launchOptions } = rawHandshake;\n\n if (action !== 'handshake') {\n throw new Error(`Invalid action: ${action}. Expected 'handshake'.`);\n }\n\n if (!isValidBrowserName(browserName)) {\n throw new Error(\n `Invalid browser name: ${browserName}. Must be one of ${Array.from(validBrowserNames).join(', ')}.`\n );\n }\n\n return {\n action,\n launchOptions: launchOptions as LaunchOptions,\n playwrightVersion,\n browserName\n };\n }\n\n // ws1 is the tunnel websocket, ws2 is the browser server websocket\n private async _setupForwardingAsync(ws1: WebSocket, ws2: WebSocket): Promise<void> {\n this._terminal.writeLine('Setting up message forwarding between ws1 and ws2');\n this._terminal.writeLine(` ws1 (tunnel) readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n this._terminal.writeLine(` ws2 (browser) readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n\n const messageCount: { ws1ToWs2: number; ws2ToWs1: number } = { ws1ToWs2: 0, ws2ToWs1: 0 };\n\n ws1.on('message', (data) => {\n messageCount.ws1ToWs2++;\n if (ws2.readyState === WebSocket.OPEN) {\n ws2.send(data);\n } else {\n this._terminal.writeLine(\n `ws2 not open (state: ${getWebSocketReadyStateString(ws2.readyState)}). Dropping message #${messageCount.ws1ToWs2}`\n );\n }\n });\n ws2.on('message', (data) => {\n messageCount.ws2ToWs1++;\n if (ws1.readyState === WebSocket.OPEN) {\n ws1.send(data);\n } else {\n this._terminal.writeLine(\n `ws1 not open (state: ${getWebSocketReadyStateString(ws1.readyState)}). Dropping message #${messageCount.ws2ToWs1}`\n );\n }\n });\n\n ws1.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws1 (tunnel) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws2.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws2 (browser) in response');\n ws2.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Tunnel closed');\n }\n });\n ws2.once('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._terminal.writeLine(\n `ws2 (browser) closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(\n ` Messages forwarded: ws1->ws2: ${messageCount.ws1ToWs2}, ws2->ws1: ${messageCount.ws2ToWs1}`\n );\n if (ws1.readyState === WebSocket.OPEN) {\n this._terminal.writeLine(' Closing ws1 (tunnel) in response');\n ws1.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Browser closed');\n }\n });\n\n ws1.once('error', (error) => {\n this._terminal.writeErrorLine(`ws1 (tunnel) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws1 readyState: ${getWebSocketReadyStateString(ws1.readyState)}`);\n });\n ws2.once('error', (error) => {\n this._terminal.writeErrorLine(`ws2 (browser) WebSocket error: ${getNormalizedErrorString(error)}`);\n this._terminal.writeErrorLine(` ws2 readyState: ${getWebSocketReadyStateString(ws2.readyState)}`);\n });\n }\n\n /**\n * Initializes the Playwright browser tunnel by establishing a WebSocket connection\n * and setting up the browser server.\n * Returns when the handshake is complete and the browser server is running.\n */\n private async _initPlaywrightBrowserTunnelAsync(): Promise<WebSocket> {\n let handshake: IHandshake | undefined = undefined;\n let client: WebSocket | undefined = undefined;\n let browserServer: BrowserServer | undefined = undefined;\n\n this.status = 'waiting-for-connection';\n const ws: WebSocket =\n this._mode === 'poll-connection'\n ? await this._pollConnectionAsync()\n : await this._waitForIncomingConnectionAsync();\n\n ws.on('open', () => {\n this._terminal.writeLine(`WebSocket connection established`);\n handshake = undefined;\n });\n\n ws.on('error', (error) => {\n this._terminal.writeLine(`WebSocket error occurred: ${getNormalizedErrorString(error)}`);\n });\n\n ws.on('close', async (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n this._initWsPromise = undefined;\n this.status = 'stopped';\n this._terminal.writeLine(\n `WebSocket connection closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n this._terminal.writeLine(` handshake received: ${handshake !== undefined}`);\n this._terminal.writeLine(` browserServer active: ${browserServer !== undefined}`);\n if (browserServer) {\n this._terminal.writeLine(' Closing browser server...');\n await browserServer.close();\n this._terminal.writeLine(' Browser server closed');\n }\n });\n\n return await new Promise<WebSocket>((resolve, reject) => {\n const onMessageHandler = async (data: RawData): Promise<void> => {\n const terminal: ITerminal = this._terminal;\n if (!handshake) {\n try {\n const rawHandshakeString: string = data.toString();\n const rawHandshake: unknown = JSON.parse(rawHandshakeString);\n terminal.writeLine(`Received handshake: ${rawHandshakeString}`);\n handshake = this._validateHandshake(rawHandshake);\n\n // Call the onBeforeLaunch callback if provided\n if (this._onBeforeLaunch) {\n terminal.writeLine('Requesting user approval before launching browser server...');\n const shouldProceed: boolean = await this._onBeforeLaunch(handshake);\n if (!shouldProceed) {\n terminal.writeLine('Browser server launch cancelled by user.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.NORMAL_CLOSURE, 'Launch cancelled by user');\n reject(new Error('Browser server launch cancelled by user'));\n return;\n }\n terminal.writeLine('User approved browser server launch.');\n }\n\n this.status = 'setting-up-browser-server';\n const browserServerProxy: IBrowserServerProxy =\n await this._getPlaywrightBrowserServerProxyAsync(handshake);\n client = browserServerProxy.client;\n browserServer = browserServerProxy.browserServer;\n\n // Monitor browser server process for crashes\n const browserProcess: ChildProcess | null = browserServer.process();\n if (browserProcess) {\n browserProcess.on('exit', (code: number | null, signal: string | null) => {\n terminal.writeErrorLine(`Browser server process exited - code: ${code}, signal: ${signal}`);\n });\n browserProcess.on('error', (err: Error) => {\n terminal.writeErrorLine(`Browser server process error: ${getNormalizedErrorString(err)}`);\n });\n terminal.writeDebugLine(`Browser server process started with PID: ${browserProcess.pid}`);\n } else {\n terminal.writeDebugLine('Warning: Browser server process handle not available for monitoring');\n }\n\n this.status = 'browser-server-running';\n\n // Send ack so that the counterpart also knows to start forwarding messages.\n // NOTE: The 1-second delay is an intentional workaround. In the current\n // protocol, the remote tunnel endpoint does not expose an explicit \"ready\"\n // signal for when it has finished initializing its own forwarding logic\n // after receiving the initial handshake. This\n // delay avoids races where early messages could be dropped or mishandled\n // if they arrive before the remote side is fully ready.\n //\n // TODO: A future improvement would be to replace this delay with a deterministic\n // synchronization mechanism (e.g. an explicit \"ready\" message or event)\n // instead of relying on a fixed timeout.\n await Async.sleepAsync(2000);\n\n ws.send(JSON.stringify({ action: 'handshakeAck' }));\n await this._setupForwardingAsync(ws, client);\n\n // Clean up message handler after successful handshake\n ws.off('message', onMessageHandler);\n resolve(ws);\n } catch (error) {\n terminal.writeLine(`Error processing handshake: ${error}`);\n this.status = 'error';\n\n // Cleanup and close connection on error\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Handshake error');\n reject(error);\n return;\n }\n } else {\n if (!client) {\n terminal.writeLine('Browser WebSocket client is not initialized.');\n ws.off('message', onMessageHandler);\n ws.close(WebSocketCloseCode.INTERNAL_ERROR, 'Browser client not initialized');\n return;\n }\n }\n };\n ws.on('message', onMessageHandler);\n });\n }\n}\n"]}
|
|
@@ -5,12 +5,11 @@ import { WebSocket, WebSocketServer } from 'ws';
|
|
|
5
5
|
import { HttpServer } from '../HttpServer';
|
|
6
6
|
import { getNormalizedErrorString, getWebSocketCloseReason, getWebSocketReadyStateString, WebSocketCloseCode } from '../utilities';
|
|
7
7
|
import { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';
|
|
8
|
-
const { version: playwrightVersion } = playwrightPackageJson;
|
|
9
8
|
/**
|
|
10
9
|
* Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.
|
|
11
10
|
* @beta
|
|
12
11
|
*/
|
|
13
|
-
export async function tunneledBrowserConnection(logger, port = DEFAULT_LISTEN_PORT) {
|
|
12
|
+
export async function tunneledBrowserConnection(logger, port = DEFAULT_LISTEN_PORT, playwrightVersion = playwrightPackageJson.version) {
|
|
14
13
|
// Server that remote peer (actual browser host) connects to
|
|
15
14
|
const remoteWsServer = new WebSocketServer({ port });
|
|
16
15
|
// Local HTTP + WebSocket server where the playwright client will connect providing params
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TunneledBrowserConnection.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { LaunchOptions } from 'playwright-core';\nimport playwrightPackageJson from 'playwright-core/package.json';\nimport type { RawData } from 'ws';\nimport { WebSocket, WebSocketServer } from 'ws';\n\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport { HttpServer } from '../HttpServer';\nimport type { BrowserName } from '../PlaywrightBrowserTunnel';\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from '../utilities';\nimport type {\n IDisposableTunneledBrowserConnection,\n IHandshake,\n IHandshakeAck\n} from './ITunneledBrowserConnection';\nimport { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';\n\nconst { version: playwrightVersion } = playwrightPackageJson;\n\n/**\n * Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.\n * @beta\n */\nexport async function tunneledBrowserConnection(\n logger: ITerminal,\n port: number = DEFAULT_LISTEN_PORT\n): Promise<IDisposableTunneledBrowserConnection> {\n // Server that remote peer (actual browser host) connects to\n const remoteWsServer: WebSocketServer = new WebSocketServer({ port });\n // Local HTTP + WebSocket server where the playwright client will connect providing params\n const httpServer: HttpServer = new HttpServer(logger);\n await httpServer.listenAsync();\n logger.writeLine(`Remote WebSocket server listening on ws://localhost:${port}`);\n\n const localProxyWs: WebSocketServer = httpServer.wsServer;\n const localProxyWsEndpoint: string = httpServer.endpoint;\n\n let browserName: BrowserName | undefined;\n let launchOptions: LaunchOptions | undefined;\n let remoteSocket: WebSocket | undefined;\n let handshakeAck: boolean = false;\n let handshakeSent: boolean = false;\n\n function maybeSendHandshake(): void {\n if (!handshakeSent && remoteSocket && browserName && launchOptions) {\n const handshake: IHandshake = {\n action: 'handshake',\n browserName,\n launchOptions,\n playwrightVersion\n };\n // Log handshake without 'headless' to avoid confusion (tunnel enforces headless: false)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = launchOptions;\n const logHandshake: Omit<IHandshake, 'launchOptions'> & {\n launchOptions: Omit<LaunchOptions, 'headless'>;\n } = {\n ...handshake,\n launchOptions: logOptions\n };\n logger.writeLine(`Sending handshake to remote: ${JSON.stringify(logHandshake)}`);\n handshakeSent = true;\n remoteSocket.send(JSON.stringify(handshake));\n }\n }\n\n return await new Promise((resolve) => {\n remoteWsServer.on('error', (error) => {\n logger.writeErrorLine(`Remote WebSocket server error: ${error}`);\n });\n\n remoteWsServer.on('close', () => {\n logger.writeLine('Remote WebSocket server closed');\n });\n\n const bufferedLocalMessages: Array<RawData> = [];\n\n remoteWsServer.on('connection', (ws) => {\n logger.writeLine('Remote websocket connected');\n remoteSocket = ws;\n handshakeAck = false;\n maybeSendHandshake();\n\n ws.on('message', (message) => {\n if (!handshakeAck) {\n try {\n const receivedHandshake: IHandshakeAck = JSON.parse(message.toString());\n if (receivedHandshake.action === 'handshakeAck') {\n handshakeAck = true;\n logger.writeLine('Received handshakeAck from remote');\n } else {\n logger.writeErrorLine('Invalid handshake ack message');\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Invalid handshake ack');\n return;\n }\n } catch (e) {\n logger.writeErrorLine(`Failed parsing handshake ack: ${e}`);\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Failed parsing handshake');\n return;\n }\n // Resolve only once local proxy available and handshake acknowledged\n if (handshakeAck) {\n // Flush any buffered local messages now that tunnel is active\n const activeRemote: WebSocket | undefined = remoteSocket;\n if (activeRemote && activeRemote.readyState === WebSocket.OPEN) {\n while (bufferedLocalMessages.length > 0) {\n const m: Buffer | ArrayBuffer | Buffer[] | string | undefined = bufferedLocalMessages.shift();\n if (m !== undefined) {\n logger.writeLine(`Flushing buffered local message to remote: ${m}`);\n activeRemote.send(m);\n }\n }\n }\n }\n } else {\n // Forward from remote to all local clients\n localProxyWs.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n });\n }\n });\n\n ws.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Remote websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Connection state at close: handshakeSent=${handshakeSent}, handshakeAck=${handshakeAck}`\n );\n logger.writeDebugLine(` Buffered messages pending: ${bufferedLocalMessages.length}`);\n });\n ws.on('error', (err: Error) => {\n logger.writeErrorLine(`Remote websocket error: ${getNormalizedErrorString(err)}`);\n logger.writeErrorLine(` Socket readyState: ${getWebSocketReadyStateString(ws.readyState)}`);\n });\n });\n\n localProxyWs.on('connection', (localWs, request) => {\n try {\n const urlString: string | undefined = request?.url;\n if (urlString) {\n const parsed: URL = new URL(urlString, 'http://localhost');\n logger.writeLine(`Local client connected with query params: ${parsed.searchParams.toString()}`);\n const bName: string | null = parsed.searchParams.get('browser');\n if (bName && SUPPORTED_BROWSER_NAMES.has(bName)) {\n browserName = bName as BrowserName;\n }\n const launchOptionsParam: string | null = parsed.searchParams.get('launchOptions');\n if (launchOptionsParam) {\n try {\n launchOptions = JSON.parse(launchOptionsParam);\n } catch (e) {\n logger.writeErrorLine('Invalid launchOptions JSON provided');\n }\n }\n }\n } catch (e) {\n logger.writeErrorLine(`Error parsing local connection query params: ${e}`);\n }\n\n if (!browserName) {\n const supportedBrowsersString: string = Array.from(SUPPORTED_BROWSER_NAMES).join('|');\n logger.writeErrorLine(`browser query param required (${supportedBrowsersString})`);\n localWs.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Missing browser param');\n return;\n }\n if (!launchOptions) {\n launchOptions = {} as LaunchOptions; // default empty if not provided\n }\n\n maybeSendHandshake();\n\n localWs.on('message', (message) => {\n if (handshakeAck && remoteSocket?.readyState === WebSocket.OPEN) {\n remoteSocket.send(message);\n } else {\n // Buffer until handshakeAck to avoid losing early protocol messages from Playwright\n bufferedLocalMessages.push(message);\n }\n });\n localWs.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Local client websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Remote socket state: ${remoteSocket ? getWebSocketReadyStateString(remoteSocket.readyState) : 'undefined'}`\n );\n logger.writeDebugLine(` handshakeAck: ${handshakeAck}`);\n });\n localWs.on('error', (err: Error) => {\n logger.writeErrorLine(`Local client websocket error: ${getNormalizedErrorString(err)}`);\n });\n });\n\n // Resolve immediately so caller can initiate local connection with query params (handshake completes later)\n resolve({\n remoteEndpoint: localProxyWsEndpoint,\n [Symbol.dispose]() {\n try {\n remoteWsServer.close();\n } catch {\n // ignore errors during remote WebSocket server shutdown\n }\n try {\n httpServer[Symbol.dispose]();\n } catch {\n // ignore errors during HTTP/WebSocket server shutdown\n }\n },\n // eslint-disable-next-line promise/param-names\n closePromise: new Promise<void>((resolve2) => {\n remoteWsServer.once('close', () => {\n resolve2();\n });\n })\n });\n });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"TunneledBrowserConnection.js","sourceRoot":"","sources":["../../src/tunneledBrowserConnection/TunneledBrowserConnection.ts"],"names":[],"mappings":"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","sourcesContent":["// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.\n// See LICENSE in the project root for license information.\n\nimport type { LaunchOptions } from 'playwright-core';\nimport playwrightPackageJson from 'playwright-core/package.json';\nimport type { RawData } from 'ws';\nimport { WebSocket, WebSocketServer } from 'ws';\n\nimport type { ITerminal } from '@rushstack/terminal';\n\nimport { HttpServer } from '../HttpServer';\nimport type { BrowserName } from '../PlaywrightBrowserTunnel';\nimport {\n getNormalizedErrorString,\n getWebSocketCloseReason,\n getWebSocketReadyStateString,\n WebSocketCloseCode\n} from '../utilities';\nimport type {\n IDisposableTunneledBrowserConnection,\n IHandshake,\n IHandshakeAck\n} from './ITunneledBrowserConnection';\nimport { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';\n\n/**\n * Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.\n * @beta\n */\nexport async function tunneledBrowserConnection(\n logger: ITerminal,\n port: number = DEFAULT_LISTEN_PORT,\n playwrightVersion: string = playwrightPackageJson.version\n): Promise<IDisposableTunneledBrowserConnection> {\n // Server that remote peer (actual browser host) connects to\n const remoteWsServer: WebSocketServer = new WebSocketServer({ port });\n // Local HTTP + WebSocket server where the playwright client will connect providing params\n const httpServer: HttpServer = new HttpServer(logger);\n await httpServer.listenAsync();\n logger.writeLine(`Remote WebSocket server listening on ws://localhost:${port}`);\n\n const localProxyWs: WebSocketServer = httpServer.wsServer;\n const localProxyWsEndpoint: string = httpServer.endpoint;\n\n let browserName: BrowserName | undefined;\n let launchOptions: LaunchOptions | undefined;\n let remoteSocket: WebSocket | undefined;\n let handshakeAck: boolean = false;\n let handshakeSent: boolean = false;\n\n function maybeSendHandshake(): void {\n if (!handshakeSent && remoteSocket && browserName && launchOptions) {\n const handshake: IHandshake = {\n action: 'handshake',\n browserName,\n launchOptions,\n playwrightVersion\n };\n // Log handshake without 'headless' to avoid confusion (tunnel enforces headless: false)\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n const { headless, ...logOptions } = launchOptions;\n const logHandshake: Omit<IHandshake, 'launchOptions'> & {\n launchOptions: Omit<LaunchOptions, 'headless'>;\n } = {\n ...handshake,\n launchOptions: logOptions\n };\n logger.writeLine(`Sending handshake to remote: ${JSON.stringify(logHandshake)}`);\n handshakeSent = true;\n remoteSocket.send(JSON.stringify(handshake));\n }\n }\n\n return await new Promise((resolve) => {\n remoteWsServer.on('error', (error) => {\n logger.writeErrorLine(`Remote WebSocket server error: ${error}`);\n });\n\n remoteWsServer.on('close', () => {\n logger.writeLine('Remote WebSocket server closed');\n });\n\n const bufferedLocalMessages: Array<RawData> = [];\n\n remoteWsServer.on('connection', (ws) => {\n logger.writeLine('Remote websocket connected');\n remoteSocket = ws;\n handshakeAck = false;\n maybeSendHandshake();\n\n ws.on('message', (message) => {\n if (!handshakeAck) {\n try {\n const receivedHandshake: IHandshakeAck = JSON.parse(message.toString());\n if (receivedHandshake.action === 'handshakeAck') {\n handshakeAck = true;\n logger.writeLine('Received handshakeAck from remote');\n } else {\n logger.writeErrorLine('Invalid handshake ack message');\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Invalid handshake ack');\n return;\n }\n } catch (e) {\n logger.writeErrorLine(`Failed parsing handshake ack: ${e}`);\n ws.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Failed parsing handshake');\n return;\n }\n // Resolve only once local proxy available and handshake acknowledged\n if (handshakeAck) {\n // Flush any buffered local messages now that tunnel is active\n const activeRemote: WebSocket | undefined = remoteSocket;\n if (activeRemote && activeRemote.readyState === WebSocket.OPEN) {\n while (bufferedLocalMessages.length > 0) {\n const m: Buffer | ArrayBuffer | Buffer[] | string | undefined = bufferedLocalMessages.shift();\n if (m !== undefined) {\n logger.writeLine(`Flushing buffered local message to remote: ${m}`);\n activeRemote.send(m);\n }\n }\n }\n }\n } else {\n // Forward from remote to all local clients\n localProxyWs.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(message);\n }\n });\n }\n });\n\n ws.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Remote websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Connection state at close: handshakeSent=${handshakeSent}, handshakeAck=${handshakeAck}`\n );\n logger.writeDebugLine(` Buffered messages pending: ${bufferedLocalMessages.length}`);\n });\n ws.on('error', (err: Error) => {\n logger.writeErrorLine(`Remote websocket error: ${getNormalizedErrorString(err)}`);\n logger.writeErrorLine(` Socket readyState: ${getWebSocketReadyStateString(ws.readyState)}`);\n });\n });\n\n localProxyWs.on('connection', (localWs, request) => {\n try {\n const urlString: string | undefined = request?.url;\n if (urlString) {\n const parsed: URL = new URL(urlString, 'http://localhost');\n logger.writeLine(`Local client connected with query params: ${parsed.searchParams.toString()}`);\n const bName: string | null = parsed.searchParams.get('browser');\n if (bName && SUPPORTED_BROWSER_NAMES.has(bName)) {\n browserName = bName as BrowserName;\n }\n const launchOptionsParam: string | null = parsed.searchParams.get('launchOptions');\n if (launchOptionsParam) {\n try {\n launchOptions = JSON.parse(launchOptionsParam);\n } catch (e) {\n logger.writeErrorLine('Invalid launchOptions JSON provided');\n }\n }\n }\n } catch (e) {\n logger.writeErrorLine(`Error parsing local connection query params: ${e}`);\n }\n\n if (!browserName) {\n const supportedBrowsersString: string = Array.from(SUPPORTED_BROWSER_NAMES).join('|');\n logger.writeErrorLine(`browser query param required (${supportedBrowsersString})`);\n localWs.close(WebSocketCloseCode.PROTOCOL_ERROR, 'Missing browser param');\n return;\n }\n if (!launchOptions) {\n launchOptions = {} as LaunchOptions; // default empty if not provided\n }\n\n maybeSendHandshake();\n\n localWs.on('message', (message) => {\n if (handshakeAck && remoteSocket?.readyState === WebSocket.OPEN) {\n remoteSocket.send(message);\n } else {\n // Buffer until handshakeAck to avoid losing early protocol messages from Playwright\n bufferedLocalMessages.push(message);\n }\n });\n localWs.on('close', (code: number, reason: Buffer) => {\n const reasonStr: string = reason.toString() || 'no reason provided';\n const codeDescription: string = getWebSocketCloseReason(code);\n logger.writeDebugLine(\n `Local client websocket closed - code: ${code} (${codeDescription}), reason: ${reasonStr}`\n );\n logger.writeDebugLine(\n ` Remote socket state: ${remoteSocket ? getWebSocketReadyStateString(remoteSocket.readyState) : 'undefined'}`\n );\n logger.writeDebugLine(` handshakeAck: ${handshakeAck}`);\n });\n localWs.on('error', (err: Error) => {\n logger.writeErrorLine(`Local client websocket error: ${getNormalizedErrorString(err)}`);\n });\n });\n\n // Resolve immediately so caller can initiate local connection with query params (handshake completes later)\n resolve({\n remoteEndpoint: localProxyWsEndpoint,\n [Symbol.dispose]() {\n try {\n remoteWsServer.close();\n } catch {\n // ignore errors during remote WebSocket server shutdown\n }\n try {\n httpServer[Symbol.dispose]();\n } catch {\n // ignore errors during HTTP/WebSocket server shutdown\n }\n },\n // eslint-disable-next-line promise/param-names\n closePromise: new Promise<void>((resolve2) => {\n remoteWsServer.once('close', () => {\n resolve2();\n });\n })\n });\n });\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rushstack/playwright-browser-tunnel",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "Run a remote Playwright Browser Tunnel. Useful in remote development environments.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -40,12 +40,11 @@
|
|
|
40
40
|
"homepage": "https://rushstack.io",
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"string-argv": "~0.3.1",
|
|
43
|
-
"semver": "~7.5.4",
|
|
44
43
|
"ws": "~8.14.1",
|
|
45
44
|
"playwright": "1.56.1",
|
|
46
|
-
"@rushstack/
|
|
47
|
-
"@rushstack/
|
|
48
|
-
"@rushstack/
|
|
45
|
+
"@rushstack/node-core-library": "5.20.3",
|
|
46
|
+
"@rushstack/terminal": "0.22.3",
|
|
47
|
+
"@rushstack/ts-command-line": "5.3.3"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
50
|
"eslint": "~9.37.0",
|
|
@@ -54,8 +53,8 @@
|
|
|
54
53
|
"playwright-core": "~1.56.1",
|
|
55
54
|
"@playwright/test": "~1.56.1",
|
|
56
55
|
"@types/node": "20.17.19",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
56
|
+
"@rushstack/heft": "1.2.5",
|
|
57
|
+
"local-node-rig": "1.0.0"
|
|
59
58
|
},
|
|
60
59
|
"peerDependencies": {
|
|
61
60
|
"playwright-core": "~1.56.1"
|