Skip to content

Commit 9262485

Browse files
bmiddhaTheLarkInn
andauthored
Playwright Browser Server (#5424)
* playwright browser server * redo the vscode extension icon * always deny launch options and prompt for adding each time * Refactor launch options prompt handling to always auto-approve browser launches and only prompt for allowlist changes * Add configure allowlist from modal and remove dupe cancel --------- Co-authored-by: Sean Larkin <thelarkinn@users.noreply.github.com>
1 parent 4d22170 commit 9262485

42 files changed

Lines changed: 2792 additions & 2 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ jspm_packages/
9999
!.vscode/tasks.json
100100
!.vscode/launch.json
101101
!.vscode/debug-certificate-manager.json
102+
!.vscode/mcp.json
102103

103104
# Rush temporary files
104105
common/deploy/
@@ -128,3 +129,8 @@ dist-storybook/
128129

129130
# VS Code test runner files
130131
.vscode-test/
132+
133+
# Playwright test outputs
134+
playwright-report/
135+
test-results/
136+

.vscode/launch.json

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,19 @@
117117
"outFiles": [
118118
"${workspaceFolder}/vscode-extensions/debug-certificate-manager-vscode-extension/**"
119119
]
120+
},
121+
{
122+
"name": "Launch Playwright on Codespaces VS Code Extension",
123+
"type": "extensionHost",
124+
"request": "launch",
125+
"cwd": "${workspaceFolder}/vscode-extensions/playwright-on-codespaces-vscode-extension/dist/vsix/unpacked",
126+
"args": [
127+
"--extensionDevelopmentPath=${workspaceFolder}/vscode-extensions/playwright-on-codespaces-vscode-extension/dist/vsix/unpacked"
128+
],
129+
"sourceMaps": true,
130+
"outFiles": [
131+
"${workspaceFolder}/vscode-extensions/playwright-on-codespaces-vscode-extension/**/*.js"
132+
]
120133
}
121134
]
122135
}

.vscode/mcp.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"servers": {
3+
"playwright": {
4+
"type": "stdio",
5+
"command": "node",
6+
"args": [
7+
"${workspaceFolder}/apps/playwright-browser-tunnel/lib/PlaywrightMcpBrowserTunnelClientCommandLine.js"
8+
]
9+
}
10+
},
11+
"inputs": []
12+
}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ These GitHub repositories provide supplementary resources for Rush Stack:
5353
| [/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) |
5454
| [/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) |
5555
| [/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) |
56+
| [/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) |
5657
| [/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) |
5758
| [/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) |
5859
| [/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) |
@@ -232,6 +233,7 @@ These GitHub repositories provide supplementary resources for Rush Stack:
232233
| [/rigs/local-web-rig](./rigs/local-web-rig/) | A rig package for Web projects that build using Heft inside the RushStack repository. |
233234
| [/rush-plugins/rush-litewatch-plugin](./rush-plugins/rush-litewatch-plugin/) | An experimental alternative approach for multi-project watch mode |
234235
| [/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). |
236+
| [/vscode-extensions/playwright-on-codespaces-vscode-extension](./vscode-extensions/playwright-on-codespaces-vscode-extension/) | VS Code extension to enable Playwright testing in GitHub Codespaces. |
235237
| [/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 |
236238
| [/vscode-extensions/rush-vscode-extension](./vscode-extensions/rush-vscode-extension/) | Enhanced experience for monorepos that use the Rush Stack toolchain |
237239
| [/vscode-extensions/vscode-shared](./vscode-extensions/vscode-shared/) | |
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
2+
# @rushstack/playwright-browser-tunnel
3+
4+
Run a Playwright browser server in one environment and drive it from another environment by forwarding Playwright’s WebSocket traffic through a tunnel.
5+
6+
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”.
7+
8+
## Relationship to the Playwright on Codespaces VS Code extension
9+
10+
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)).
11+
12+
In a typical Codespaces workflow:
13+
14+
- Your **tests** run inside the Codespace and call `tunneledBrowserConnection()`.
15+
- `tunneledBrowserConnection()` starts a WebSocket server (by default on port `3000`) that a browser host can attach to.
16+
- The VS Code extension runs on the **UI side** and starts a `PlaywrightTunnel` which connects to `ws://127.0.0.1:3000`.
17+
- 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`).
18+
- Once connected, the extension hosts the actual Playwright browser process locally, while your tests continue to run remotely.
19+
20+
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.
21+
22+
### Detecting whether the VS Code extension is present
23+
24+
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).
25+
26+
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.
27+
28+
On the remote side, `isExtensionInstalledAsync()` checks for that marker file and returns `true` if it exists:
29+
30+
```ts
31+
import { isExtensionInstalledAsync } from '@rushstack/playwright-browser-tunnel';
32+
33+
if (!(await isExtensionInstalledAsync())) {
34+
throw new Error('Playwright on Codespaces extension is not installed/active in this environment');
35+
}
36+
```
37+
38+
39+
## Requirements
40+
41+
- Node.js `>= 20` (see `engines` in `package.json`)
42+
- A compatible Playwright version (this package is built/tested with Playwright `1.56.x`)
43+
44+
## Exports
45+
46+
From [src/index.ts](src/index.ts):
47+
48+
- `PlaywrightTunnel` (class)
49+
- `IPlaywrightTunnelOptions` (type)
50+
- `TunnelStatus` (type)
51+
- `BrowserNames` (type)
52+
- `tunneledBrowserConnection()` (function)
53+
- `tunneledBrowser()` (function)
54+
- `IDisposableTunneledBrowserConnection` (type)
55+
- `isExtensionInstalledAsync()` (function)
56+
57+
## Usage
58+
59+
There are two pieces:
60+
61+
1) **Browser host**: run a `PlaywrightTunnel` to launch the real browser server and forward messages.
62+
2) **Test runner**: create a local endpoint via `tunneledBrowserConnection()` that your Playwright client can connect to (it forwards to the browser host).
63+
64+
### 1) Browser host: run the tunnel
65+
66+
Use `PlaywrightTunnel` in the environment where you want the browser process to run.
67+
68+
```ts
69+
import { ConsoleTerminalProvider, Terminal, TerminalProviderSeverity } from '@rushstack/terminal';
70+
import { PlaywrightTunnel } from '@rushstack/playwright-browser-tunnel';
71+
import path from 'node:path';
72+
import os from 'node:os';
73+
74+
const terminalProvider = new ConsoleTerminalProvider();
75+
const terminal = new Terminal(terminalProvider);
76+
77+
const tunnel = new PlaywrightTunnel({
78+
mode: 'wait-for-incoming-connection',
79+
listenPort: 3000,
80+
tmpPath: path.join(os.tmpdir(), 'playwright-browser-tunnel'),
81+
terminal,
82+
onStatusChange: (status) => terminal.writeLine(`status: ${status}`)
83+
});
84+
85+
await tunnel.startAsync({ keepRunning: true });
86+
```
87+
88+
Notes:
89+
90+
- `mode: 'wait-for-incoming-connection'` starts a WebSocket server and waits for the other side to connect.
91+
- `mode: 'poll-connection'` repeatedly attempts to connect to a WebSocket endpoint you provide (`wsEndpoint`).
92+
- `tmpPath` is used as a working directory to install the requested `playwright-core` version and run its CLI.
93+
94+
### 2) Test runner: create a local endpoint to connect()
95+
96+
Use `tunneledBrowserConnection()` in the environment where your tests run.
97+
98+
It starts:
99+
100+
- a **remote** WebSocket server (port `3000`) that the browser host connects to
101+
- a **local** WebSocket endpoint (random port) that your Playwright client connects to
102+
103+
```ts
104+
import { tunneledBrowserConnection } from '@rushstack/playwright-browser-tunnel';
105+
import playwright from 'playwright-core';
106+
107+
using connection = await tunneledBrowserConnection();
108+
109+
// Build the connect URL with query parameters consumed by the local proxy.
110+
const url = new URL(connection.remoteEndpoint);
111+
url.searchParams.set('browser', 'chromium');
112+
url.searchParams.set('launchOptions', JSON.stringify({ headless: true }));
113+
114+
const browser = await playwright.chromium.connect(url.toString());
115+
// ...run tests...
116+
await browser.close();
117+
```
118+
119+
## Development
120+
121+
- Build: `rush build --to playwright-browser-tunnel`
122+
- Demo script (if configured): `rushx demo`
123+
124+
## Troubleshooting
125+
126+
- If the tunnel is stuck in `waiting-for-connection`, ensure the counterpart process is reachable and ports are forwarded correctly.
127+
- If browser installation is slow/repeated, ensure `tmpPath` is stable and writable for the host environment.
128+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
3+
4+
"mainEntryPointFilePath": "<projectFolder>/lib/index.d.ts",
5+
6+
"apiReport": {
7+
"enabled": true,
8+
"reportFolder": "../../../common/reviews/api"
9+
},
10+
11+
"docModel": {
12+
"enabled": true,
13+
"apiJsonFilePath": "../../../common/temp/api/<unscopedPackageName>.api.json"
14+
},
15+
16+
"dtsRollup": {
17+
"enabled": true
18+
}
19+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
// The "rig.json" file directs tools to look for their config files in an external package.
3+
// Documentation for this system: https://www.npmjs.com/package/@rushstack/rig-package
4+
"$schema": "https://developer.microsoft.com/json-schemas/rig-package/rig.schema.json",
5+
6+
"rigPackageName": "local-node-rig"
7+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
const nodeTrustedToolProfile = require('local-node-rig/profiles/default/includes/eslint/flat/profile/node-trusted-tool');
5+
const friendlyLocalsMixin = require('local-node-rig/profiles/default/includes/eslint/flat/mixins/friendly-locals');
6+
7+
module.exports = [
8+
...nodeTrustedToolProfile,
9+
...friendlyLocalsMixin,
10+
{
11+
files: ['**/*.ts', '**/*.tsx'],
12+
languageOptions: {
13+
parserOptions: {
14+
tsconfigRootDir: __dirname
15+
}
16+
}
17+
}
18+
];
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"name": "@rushstack/playwright-browser-tunnel",
3+
"version": "0.0.1",
4+
"description": "Run a remote Playwright Browser Tunnel. Useful in remote development environments.",
5+
"license": "MIT",
6+
"repository": {
7+
"type": "git",
8+
"url": "https://github.com/microsoft/rushstack.git",
9+
"directory": "apps/playwright-browser-tunnel"
10+
},
11+
"main": "lib/index.js",
12+
"engines": {
13+
"node": ">=20.0.0"
14+
},
15+
"engineStrict": true,
16+
"homepage": "https://rushstack.io",
17+
"scripts": {
18+
"build": "heft build --clean",
19+
"_phase:build": "heft run --only build -- --clean",
20+
"demo": "playwright test --config=playwright.config.ts"
21+
},
22+
"dependencies": {
23+
"@rushstack/node-core-library": "workspace:*",
24+
"@rushstack/terminal": "workspace:*",
25+
"@rushstack/ts-command-line": "workspace:*",
26+
"string-argv": "~0.3.1",
27+
"semver": "~7.5.4",
28+
"ws": "~8.14.1",
29+
"playwright": "1.56.1"
30+
},
31+
"devDependencies": {
32+
"@rushstack/heft": "workspace:*",
33+
"eslint": "~9.37.0",
34+
"local-node-rig": "workspace:*",
35+
"@types/semver": "7.5.0",
36+
"@types/ws": "8.5.5",
37+
"playwright-core": "~1.56.1",
38+
"@playwright/test": "~1.56.1",
39+
"@types/node": "20.17.19"
40+
},
41+
"peerDependencies": {
42+
"playwright-core": "~1.56.1"
43+
}
44+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { defineConfig, devices } from '@playwright/test';
2+
3+
export default defineConfig({
4+
testDir: './tests',
5+
/* Run tests in files in parallel */
6+
fullyParallel: true,
7+
/* Retry on CI only */
8+
retries: 0,
9+
/* Opt out of parallel tests on CI. */
10+
workers: 1,
11+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
12+
reporter: 'html',
13+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
14+
use: {
15+
/* Base URL to use in actions like `await page.goto('/')`. */
16+
// baseURL: 'http://localhost:3000',
17+
18+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
19+
trace: 'on'
20+
},
21+
22+
/* Configure projects for major browsers */
23+
projects: [
24+
{
25+
name: 'chromium',
26+
use: { ...devices['Desktop Chrome'] }
27+
},
28+
{
29+
name: 'firefox',
30+
use: { ...devices['Desktop Firefox'] }
31+
},
32+
{
33+
name: 'webkit',
34+
use: { ...devices['Desktop Safari'] }
35+
},
36+
{
37+
name: 'Google Chrome',
38+
use: { ...devices['Desktop Chrome'], channel: 'chrome' } // or 'chrome-beta'
39+
},
40+
{
41+
name: 'Microsoft Edge',
42+
use: { ...devices['Desktop Edge'], channel: 'msedge' } // or "msedge-beta" or 'msedge-dev'
43+
}
44+
]
45+
});

0 commit comments

Comments
 (0)