Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/playwright-browser-tunnel/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@rushstack/playwright-browser-tunnel",
"version": "0.2.3",
"version": "0.2.4",
Comment thread
anarmawala marked this conversation as resolved.
Outdated
"description": "Run a remote Playwright Browser Tunnel. Useful in remote development environments.",
"license": "MIT",
"repository": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { Browser } from 'playwright-core';

/**
* Disposable handle returned by {@link createTunneledBrowserAsync}.
* @beta
*/
export interface IDisposableTunneledBrowser {
/**
* The connected Playwright Browser instance.
*/
browser: Browser;
/**
* Async dispose method that closes the browser connection.
* Called automatically when using `await using` syntax.
*/
[Symbol.asyncDispose]: () => Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { LaunchOptions } from 'playwright-core';

import type { BrowserName } from '../PlaywrightBrowserTunnel';

export interface IHandshake {
action: 'handshake';
browserName: BrowserName;
launchOptions: LaunchOptions;
playwrightVersion: string;
}

export interface IHandshakeAck {
action: 'handshakeAck';
}

/**
* Disposable handle returned by {@link tunneledBrowserConnection}.
* @beta
*/
export interface IDisposableTunneledBrowserConnection {
/**
* The WebSocket endpoint URL that the local Playwright client should connect to.
*/
remoteEndpoint: string;
/**
* Dispose method that closes the WebSocket servers.
* Called automatically when using `using` syntax.
*/
[Symbol.dispose]: () => void;
/**
* Promise that resolves when the remote WebSocket server closes.
*/
closePromise: Promise<void>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import type { Browser, LaunchOptions } from 'playwright-core';
import playwright from 'playwright-core';

import type { ITerminal } from '@rushstack/terminal';
import { ConsoleTerminalProvider, Terminal } from '@rushstack/terminal';

import type { BrowserName } from '../PlaywrightBrowserTunnel';
import { DEFAULT_LISTEN_PORT } from './constants';
import type { IDisposableTunneledBrowser } from './ITunneledBrowser';
import type { IDisposableTunneledBrowserConnection } from './ITunneledBrowserConnection';
import { tunneledBrowserConnection } from './TunneledBrowserConnection';

/**
* Creates a Playwright Browser instance connected via a tunneled WebSocket connection.
* @beta
*/
export async function createTunneledBrowserAsync(
browserName: BrowserName,
launchOptions: LaunchOptions,
logger?: ITerminal,
port: number = DEFAULT_LISTEN_PORT
): Promise<IDisposableTunneledBrowser> {
// Establish the tunnel first (remoteEndpoint here refers to local proxy endpoint for connect())

if (!logger) {
const terminalProvider: ConsoleTerminalProvider = new ConsoleTerminalProvider();
logger = new Terminal(terminalProvider);
}

const connection: IDisposableTunneledBrowserConnection = await tunneledBrowserConnection(logger, port);
const { remoteEndpoint } = connection;
// Append query params for browser and launchOptions
const urlObj: URL = new URL(remoteEndpoint);
urlObj.searchParams.set('browser', browserName);
urlObj.searchParams.set('launchOptions', JSON.stringify(launchOptions || {}));
const connectEndpoint: string = urlObj.toString();
const browser: Browser = await playwright[browserName].connect(connectEndpoint);
logger.writeLine(`Connected to remote browser at ${connectEndpoint}`);

return {
browser,
async [Symbol.asyncDispose]() {
logger.writeLine('Disposing browser');
await browser.close();
// Dispose the tunnel connection after browser is closed
connection[Symbol.dispose]();
}
};
}
Original file line number Diff line number Diff line change
@@ -1,59 +1,30 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

import playwright from 'playwright-core';
import type { Browser, LaunchOptions } from 'playwright-core';
import { WebSocketServer, WebSocket, type RawData } from 'ws';
import type { LaunchOptions } from 'playwright-core';
import playwrightPackageJson from 'playwright-core/package.json';
import type { RawData } from 'ws';
import { WebSocket, WebSocketServer } from 'ws';

import { type ITerminal, Terminal, ConsoleTerminalProvider } from '@rushstack/terminal';
import type { ITerminal } from '@rushstack/terminal';

import type { BrowserName } from './PlaywrightBrowserTunnel';
import { HttpServer } from './HttpServer';
import { HttpServer } from '../HttpServer';
import type { BrowserName } from '../PlaywrightBrowserTunnel';
import {
getNormalizedErrorString,
getWebSocketCloseReason,
getWebSocketReadyStateString,
WebSocketCloseCode
} from './utilities';
} from '../utilities';
import type {
IDisposableTunneledBrowserConnection,
IHandshake,
IHandshakeAck
} from './ITunneledBrowserConnection';
import { DEFAULT_LISTEN_PORT, SUPPORTED_BROWSER_NAMES } from './constants';

const { version: playwrightVersion } = playwrightPackageJson;

const SUPPORTED_BROWSER_NAMES: Set<string> = new Set(['chromium', 'firefox', 'webkit']);

interface IHandshake {
action: 'handshake';
browserName: BrowserName;
launchOptions: LaunchOptions;
playwrightVersion: string;
}

interface IHandshakeAck {
action: 'handshakeAck';
}

const DEFAULT_LISTEN_PORT: number = 56767;

/**
* Disposable handle returned by {@link tunneledBrowserConnection}.
* @beta
*/
export interface IDisposableTunneledBrowserConnection {
/**
* The WebSocket endpoint URL that the local Playwright client should connect to.
*/
remoteEndpoint: string;
/**
* Dispose method that closes the WebSocket servers.
* Called automatically when using `using` syntax.
*/
[Symbol.dispose]: () => void;
/**
* Promise that resolves when the remote WebSocket server closes.
*/
closePromise: Promise<void>;
}

/**
* Creates a tunneled WebSocket endpoint that a local Playwright client can connect to.
* @beta
Expand Down Expand Up @@ -259,57 +230,3 @@ export async function tunneledBrowserConnection(
});
});
}

/**
* Disposable handle returned by {@link createTunneledBrowserAsync}.
* @beta
*/
export interface IDisposableTunneledBrowser {
/**
* The connected Playwright Browser instance.
*/
browser: Browser;
/**
* Async dispose method that closes the browser connection.
* Called automatically when using `await using` syntax.
*/
[Symbol.asyncDispose]: () => Promise<void>;
}

/**
* Creates a Playwright Browser instance connected via a tunneled WebSocket connection.
* @beta
*/
export async function createTunneledBrowserAsync(
browserName: BrowserName,
launchOptions: LaunchOptions,
logger?: ITerminal,
port: number = DEFAULT_LISTEN_PORT
): Promise<IDisposableTunneledBrowser> {
// Establish the tunnel first (remoteEndpoint here refers to local proxy endpoint for connect())

if (!logger) {
const terminalProvider: ConsoleTerminalProvider = new ConsoleTerminalProvider();
logger = new Terminal(terminalProvider);
}

const connection: IDisposableTunneledBrowserConnection = await tunneledBrowserConnection(logger, port);
const { remoteEndpoint } = connection;
// Append query params for browser and launchOptions
const urlObj: URL = new URL(remoteEndpoint);
urlObj.searchParams.set('browser', browserName);
urlObj.searchParams.set('launchOptions', JSON.stringify(launchOptions || {}));
const connectEndpoint: string = urlObj.toString();
const browser: Browser = await playwright[browserName].connect(connectEndpoint);
logger.writeLine(`Connected to remote browser at ${connectEndpoint}`);

return {
browser,
async [Symbol.asyncDispose]() {
logger.writeLine('Disposing browser');
await browser.close();
// Dispose the tunnel connection after browser is closed
connection[Symbol.dispose]();
}
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

export const SUPPORTED_BROWSER_NAMES: Set<string> = new Set(['chromium', 'firefox', 'webkit']);
export const DEFAULT_LISTEN_PORT: number = 56767;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

export { createTunneledBrowserAsync } from './TunneledBrowser';
export { tunneledBrowserConnection } from './TunneledBrowserConnection';

export type { IDisposableTunneledBrowser } from './ITunneledBrowser';
export type { IDisposableTunneledBrowserConnection } from './ITunneledBrowserConnection';
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/playwright-browser-tunnel",
"comment": "Update dependency chart to not always require playwright",
Comment thread
anarmawala marked this conversation as resolved.
Outdated
"type": "patch"
}
],
"packageName": "@rushstack/playwright-browser-tunnel"
}