Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7956637
playwright browser server
bmiddha Oct 30, 2025
d0d71c2
fixup server listening log message
bmiddha Oct 30, 2025
3325d49
Implement playwright-on-codespaces vscode extension and impl
TheLarkInn Dec 11, 2025
de6a58f
DROP: rush update
TheLarkInn Dec 11, 2025
234d090
fix wsEndpoint
TheLarkInn Dec 12, 2025
46f7c0b
revert some browser options
TheLarkInn Dec 12, 2025
9aef4a1
remove check about remote
TheLarkInn Dec 12, 2025
8528353
vscode status bar icon
bmiddha Dec 15, 2025
92fd7dc
demo: disable loading bundled extension
bmiddha Dec 15, 2025
e7a38d5
Actually use bufferutils and utf-8-validate in bundled websocket code
TheLarkInn Dec 15, 2025
3b85d15
update webpack config comment
TheLarkInn Dec 15, 2025
646b76b
auto start the tunnel on activation
TheLarkInn Dec 15, 2025
a186ab1
Add todos and update version
TheLarkInn Dec 15, 2025
9db3c84
Merge branch 'bmiddha/playwright-browser-server' of https://github.co…
TheLarkInn Dec 15, 2025
93a0594
Refactoring workspaceCommand fn and minor changes
TheLarkInn Dec 18, 2025
4ff682a
Merge branch 'main' into bmiddha/playwright-browser-server
TheLarkInn Dec 18, 2025
cce4517
DROP: rush update
TheLarkInn Dec 18, 2025
29a8e03
repo-toolbox readme
TheLarkInn Dec 18, 2025
48408b3
add README's, api-extractor and removed unused CLI and start commands
TheLarkInn Dec 18, 2025
1d849b3
Update vscode-extensions/playwright-on-codespaces-vscode-extension/LI…
TheLarkInn Dec 18, 2025
d03eecc
Update apps/playwright-browser-tunnel/src/tunneledBrowserConnection.ts
TheLarkInn Dec 18, 2025
bcd1cf8
fix spelling mistake
TheLarkInn Dec 18, 2025
2e52a89
remove console.log usage for terminal
TheLarkInn Dec 18, 2025
e43651c
refactor: improve WebSocket connection handling and logging in Playwr…
TheLarkInn Dec 18, 2025
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
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ jspm_packages/
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/debug-certificate-manager.json
!.vscode/mcp.json

# Rush temporary files
common/deploy/
Expand Down Expand Up @@ -128,3 +129,8 @@ dist-storybook/

# VS Code test runner files
.vscode-test/

# Playwright test outputs
playwright-report/
test-results/

13 changes: 13 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,19 @@
"outFiles": [
"${workspaceFolder}/vscode-extensions/debug-certificate-manager-vscode-extension/**"
]
},
{
"name": "Launch Playwright on Codespaces VS Code Extension",
"type": "extensionHost",
"request": "launch",
"cwd": "${workspaceFolder}/vscode-extensions/playwright-on-codespaces-vscode-extension/dist/vsix/unpacked",
"args": [
"--extensionDevelopmentPath=${workspaceFolder}/vscode-extensions/playwright-on-codespaces-vscode-extension"
],
"sourceMaps": true,
"outFiles": [
"${workspaceFolder}/vscode-extensions/playwright-on-codespaces-vscode-extension/**"
]
}
]
}
12 changes: 12 additions & 0 deletions .vscode/mcp.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"servers": {
"playwright": {
"type": "stdio",
"command": "node",
"args": [
"${workspaceFolder}/apps/playwright-browser-tunnel/lib/PlaywrightMcpBrowserTunnelClientCommandLine.js"
]
}
},
"inputs": []
}
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ These GitHub repositories provide supplementary resources for Rush Stack:
| [/apps/cpu-profile-summarizer](./apps/cpu-profile-summarizer/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fcpu-profile-summarizer.svg)](https://badge.fury.io/js/%40rushstack%2Fcpu-profile-summarizer) | [changelog](./apps/cpu-profile-summarizer/CHANGELOG.md) | [@rushstack/cpu-profile-summarizer](https://www.npmjs.com/package/@rushstack/cpu-profile-summarizer) |
| [/apps/heft](./apps/heft/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fheft.svg)](https://badge.fury.io/js/%40rushstack%2Fheft) | [changelog](./apps/heft/CHANGELOG.md) | [@rushstack/heft](https://www.npmjs.com/package/@rushstack/heft) |
| [/apps/lockfile-explorer](./apps/lockfile-explorer/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Flockfile-explorer.svg)](https://badge.fury.io/js/%40rushstack%2Flockfile-explorer) | [changelog](./apps/lockfile-explorer/CHANGELOG.md) | [@rushstack/lockfile-explorer](https://www.npmjs.com/package/@rushstack/lockfile-explorer) |
| [/apps/playwright-browser-tunnel](./apps/playwright-browser-tunnel/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fplaywright-browser-tunnel.svg)](https://badge.fury.io/js/%40rushstack%2Fplaywright-browser-tunnel) | [changelog](./apps/playwright-browser-tunnel/CHANGELOG.md) | [@rushstack/playwright-browser-tunnel](https://www.npmjs.com/package/@rushstack/playwright-browser-tunnel) |
| [/apps/rundown](./apps/rundown/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Frundown.svg)](https://badge.fury.io/js/%40rushstack%2Frundown) | [changelog](./apps/rundown/CHANGELOG.md) | [@rushstack/rundown](https://www.npmjs.com/package/@rushstack/rundown) |
| [/apps/rush](./apps/rush/) | [![npm version](https://badge.fury.io/js/%40microsoft%2Frush.svg)](https://badge.fury.io/js/%40microsoft%2Frush) | [changelog](./apps/rush/CHANGELOG.md) | [@microsoft/rush](https://www.npmjs.com/package/@microsoft/rush) |
| [/apps/rush-mcp-server](./apps/rush-mcp-server/) | [![npm version](https://badge.fury.io/js/%40rushstack%2Fmcp-server.svg)](https://badge.fury.io/js/%40rushstack%2Fmcp-server) | [changelog](./apps/rush-mcp-server/CHANGELOG.md) | [@rushstack/mcp-server](https://www.npmjs.com/package/@rushstack/mcp-server) |
Expand Down Expand Up @@ -226,6 +227,7 @@ These GitHub repositories provide supplementary resources for Rush Stack:
| [/rigs/local-web-rig](./rigs/local-web-rig/) | A rig package for Web projects that build using Heft inside the RushStack repository. |
| [/rush-plugins/rush-litewatch-plugin](./rush-plugins/rush-litewatch-plugin/) | An experimental alternative approach for multi-project watch mode |
| [/vscode-extensions/debug-certificate-manager-vscode-extension](./vscode-extensions/debug-certificate-manager-vscode-extension/) | VS Code extension to manage debug TLS certificates and sync them to the VS Code workspace. Works with VS Code remote development (Codespaces, SSH, Dev Containers, WSL, VS Code Tunnels). |
| [/vscode-extensions/playwright-on-codespaces-vscode-extension](./vscode-extensions/playwright-on-codespaces-vscode-extension/) | VS Code extension to enable Playwright testing in GitHub Codespaces. |
| [/vscode-extensions/rush-vscode-command-webview](./vscode-extensions/rush-vscode-command-webview/) | Part of the Rush Stack VSCode extension, provides a UI for invoking Rush commands |
| [/vscode-extensions/rush-vscode-extension](./vscode-extensions/rush-vscode-extension/) | Enhanced experience for monorepos that use the Rush Stack toolchain |
| [/vscode-extensions/vscode-shared](./vscode-extensions/vscode-shared/) | |
Expand Down
131 changes: 131 additions & 0 deletions apps/playwright-browser-tunnel/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@

# @rushstack/playwright-browser-tunnel

Run a Playwright browser server in one environment and drive it from another environment by forwarding Playwright’s WebSocket traffic through a tunnel.

This package is intended for remote development / CI scenarios (for example: Codespaces, devcontainers, or a separate “browser host” machine) where you want tests to run “here” but the actual browser process to run “there”.

## Relationship to the Playwright on Codespaces VS Code extension

This package is the core tunneling/runtime layer used by the **Playwright on Codespaces** VS Code extension (located at [vscode-extensions/playwright-on-codespaces-vscode-extension](../../vscode-extensions/playwright-on-codespaces-vscode-extension)).

In a typical Codespaces workflow:

- Your **tests** run inside the Codespace and call `tunneledBrowserConnection()`.
- `tunneledBrowserConnection()` starts a WebSocket server (by default on port `3000`) that a browser host can attach to.
- The VS Code extension runs on the **UI side** and starts a `PlaywrightTunnel` which connects to `ws://127.0.0.1:3000`.
- In Codespaces, this works when port `3000` is forwarded to your local machine (VS Code port forwarding makes the remote port reachable as `localhost:3000`).
- Once connected, the extension hosts the actual Playwright browser process locally, while your tests continue to run remotely.

The extension provides a UI wrapper around this library (start/stop commands, status bar state, and logs), while `@rushstack/playwright-browser-tunnel` provides the underlying protocol forwarding and browser lifecycle management.

### Detecting whether the VS Code extension is present

Some remote test fixtures want to detect whether the **Playwright on Codespaces** extension is installed/active (for example, to skip local-browser-only scenarios when the extension isn’t available).

The extension writes a marker file named `.playwright-codespaces-extension-installed.txt` into the remote environment’s `os.tmpdir()` using VS Code’s remote filesystem APIs.

On the remote side, `extensionIsInstalled()` checks for that marker file and returns `true` if it exists:

```ts
import { extensionIsInstalled } from '@rushstack/playwright-browser-tunnel';

if (!(await extensionIsInstalled())) {
throw new Error('Playwright on Codespaces extension is not installed/active in this environment');
}
```

## Status

This package’s API surface is currently tagged as `@alpha`.

## Requirements

- Node.js `>= 20` (see `engines` in `package.json`)
- A compatible Playwright version (this package is built/tested with Playwright `1.56.x`)

## Exports

From [src/index.ts](src/index.ts):

- `PlaywrightTunnel` (class)
- `IPlaywrightTunnelOptions` (type)
- `TunnelStatus` (type)
- `BrowserNames` (type)
- `tunneledBrowserConnection()` (function)
- `tunneledBrowser()` (function)
- `IDisposableTunneledBrowserConnection` (type)
- `extensionIsInstalled()` (function)

## Usage

There are two pieces:

1) **Browser host**: run a `PlaywrightTunnel` to launch the real browser server and forward messages.
2) **Test runner**: create a local endpoint via `tunneledBrowserConnection()` that your Playwright client can connect to (it forwards to the browser host).

### 1) Browser host: run the tunnel

Use `PlaywrightTunnel` in the environment where you want the browser process to run.

```ts
import { ConsoleTerminalProvider, Terminal, TerminalProviderSeverity } from '@rushstack/terminal';
import { PlaywrightTunnel } from '@rushstack/playwright-browser-tunnel';
import path from 'node:path';
import os from 'node:os';

const terminalProvider = new ConsoleTerminalProvider();
const terminal = new Terminal(terminalProvider);

const tunnel = new PlaywrightTunnel({
mode: 'wait-for-incoming-connection',
listenPort: 3000,
tmpPath: path.join(os.tmpdir(), 'playwright-browser-tunnel'),
terminal,
onStatusChange: (status) => terminal.writeLine(`status: ${status}`)
});

await tunnel.startAsync({ keepRunning: true });
```

Notes:

- `mode: 'wait-for-incoming-connection'` starts a WebSocket server and waits for the other side to connect.
- `mode: 'poll-connection'` repeatedly attempts to connect to a WebSocket endpoint you provide (`wsEndpoint`).
- `tmpPath` is used as a working directory to install the requested `playwright-core` version and run its CLI.

### 2) Test runner: create a local endpoint to connect()

Use `tunneledBrowserConnection()` in the environment where your tests run.

It starts:

- a **remote** WebSocket server (port `3000`) that the browser host connects to
- a **local** WebSocket endpoint (random port) that your Playwright client connects to

```ts
import { tunneledBrowserConnection } from '@rushstack/playwright-browser-tunnel';
import playwright from 'playwright-core';

using connection = await tunneledBrowserConnection();

// Build the connect URL with query parameters consumed by the local proxy.
const url = new URL(connection.remoteEndpoint);
url.searchParams.set('browser', 'chromium');
url.searchParams.set('launchOptions', JSON.stringify({ headless: true }));

const browser = await playwright.chromium.connect(url.toString());
// ...run tests...
await browser.close();
```

## Development

- Build: `rush build --to playwright-browser-tunnel`
- Demo script (if configured): `rushx demo`

## Troubleshooting

- If the tunnel is stuck in `waiting-for-connection`, ensure the counterpart process is reachable and ports are forwarded correctly.
- If browser installation is slow/repeated, ensure `tmpPath` is stable and writable for the host environment.

19 changes: 19 additions & 0 deletions apps/playwright-browser-tunnel/config/api-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",

"mainEntryPointFilePath": "<projectFolder>/lib/index.d.ts",

"apiReport": {
"enabled": true,
"reportFolder": "../../../common/reviews/api"
},

"docModel": {
"enabled": true,
"apiJsonFilePath": "../../../common/temp/api/<unscopedPackageName>.api.json"
},

"dtsRollup": {
"enabled": true
}
}
7 changes: 7 additions & 0 deletions apps/playwright-browser-tunnel/config/rig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
// The "rig.json" file directs tools to look for their config files in an external package.
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",

"rigPackageName": "local-node-rig"
}
21 changes: 21 additions & 0 deletions apps/playwright-browser-tunnel/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

const nodeTrustedToolProfile = require('local-node-rig/profiles/default/includes/eslint/flat/profile/node-trusted-tool');
const friendlyLocalsMixin = require('local-node-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals');

module.exports = [
...nodeTrustedToolProfile,
...friendlyLocalsMixin,
{
files: ['**/*.ts', '**/*.tsx'],
languageOptions: {
parserOptions: {
tsconfigRootDir: __dirname
}
},
rules: {
'no-console': 'off'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally use ITerminal.

}
}
];
44 changes: 44 additions & 0 deletions apps/playwright-browser-tunnel/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "@rushstack/playwright-browser-tunnel",
"version": "0.0.1",
"description": "Run a remote Playwright Browser Tunnel. Useful in remote development environments.",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/rushstack.git",
"directory": "apps/playwright-browser-tunnel"
},
"main": "lib/index.js",
"engines": {
"node": ">=20.0.0"
},
"engineStrict": true,
"homepage": "https://rushstack.io",
"scripts": {
"build": "heft build --clean",
"_phase:build": "heft run --only build -- --clean",
"demo": "playwright test --config=playwright.config.ts"
},
"license": "MIT",
Comment on lines +5 to +21
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"repository": {
"type": "git",
"url": "https://github.com/microsoft/rushstack.git",
"directory": "apps/playwright-browser-tunnel"
},
"main": "lib/index.js",
"engines": {
"node": ">=20.0.0"
},
"engineStrict": true,
"homepage": "https://rushstack.io",
"scripts": {
"build": "heft build --clean",
"_phase:build": "heft run --only build -- --clean",
"demo": "playwright test --config=playwright.config.ts"
},
"license": "MIT",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/rushstack.git",
"directory": "apps/playwright-browser-tunnel"
},
"main": "lib/index.js",
"engines": {
"node": ">=20.0.0"
},
"engineStrict": true,
"homepage": "https://rushstack.io",
"scripts": {
"build": "heft build --clean",
"_phase:build": "heft run --only build -- --clean",
"demo": "playwright test --config=playwright.config.ts"
},

"dependencies": {
"@rushstack/node-core-library": "workspace:*",
"@rushstack/terminal": "workspace:*",
"@rushstack/ts-command-line": "workspace:*",
"string-argv": "~0.3.1",
"semver": "~7.5.4",
"ws": "~8.14.1",
"playwright": "1.56.1"
},
"devDependencies": {
"@rushstack/heft": "workspace:*",
"eslint": "~9.37.0",
"local-node-rig": "workspace:*",
"@types/semver": "7.5.0",
"@types/ws": "8.5.5",
"playwright-core": "~1.56.1",
"@playwright/test": "~1.56.1",
"@types/node": "20.17.19"
},
"peerDependencies": {
"playwright-core": "~1.56.1"
}
Comment on lines +22 to +43
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"dependencies": {
"@rushstack/node-core-library": "workspace:*",
"@rushstack/terminal": "workspace:*",
"@rushstack/ts-command-line": "workspace:*",
"string-argv": "~0.3.1",
"semver": "~7.5.4",
"ws": "~8.14.1",
"playwright": "1.56.1"
},
"devDependencies": {
"@rushstack/heft": "workspace:*",
"eslint": "~9.37.0",
"local-node-rig": "workspace:*",
"@types/semver": "7.5.0",
"@types/ws": "8.5.5",
"playwright-core": "~1.56.1",
"@playwright/test": "~1.56.1",
"@types/node": "20.17.19"
},
"peerDependencies": {
"playwright-core": "~1.56.1"
}
"peerDependencies": {
"playwright-core": "~1.56.1"
}
"dependencies": {
"@rushstack/node-core-library": "workspace:*",
"@rushstack/terminal": "workspace:*",
"@rushstack/ts-command-line": "workspace:*",
"string-argv": "~0.3.1",
"semver": "~7.5.4",
"ws": "~8.14.1",
"playwright": "1.56.1"
},
"devDependencies": {
"@rushstack/heft": "workspace:*",
"eslint": "~9.37.0",
"local-node-rig": "workspace:*",
"@types/semver": "7.5.0",
"@types/ws": "8.5.5",
"playwright-core": "~1.56.1",
"@playwright/test": "~1.56.1",
"@types/node": "20.17.19"
},

}
45 changes: 45 additions & 0 deletions apps/playwright-browser-tunnel/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
testDir: './tests',
/* Run tests in files in parallel */
fullyParallel: true,
/* Retry on CI only */
retries: 0,
/* Opt out of parallel tests on CI. */
workers: 1,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://localhost:3000',

/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on'
},

/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] }
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] }
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] }
},
{
name: 'Google Chrome',
use: { ...devices['Desktop Chrome'], channel: 'chrome' } // or 'chrome-beta'
},
{
name: 'Microsoft Edge',
use: { ...devices['Desktop Edge'], channel: 'msedge' } // or "msedge-beta" or 'msedge-dev'
}
]
});
Loading