diff --git a/.github/workflows/tests_primary.yml b/.github/workflows/tests_primary.yml index cae05b22f343c..bd94cbee098f6 100644 --- a/.github/workflows/tests_primary.yml +++ b/.github/workflows/tests_primary.yml @@ -26,207 +26,22 @@ env: DEBUG_GIT_COMMIT_INFO: 1 jobs: - test_linux: - name: ${{ matrix.os }} (${{ matrix.browser }} - Node.js ${{ matrix.node-version }}) - environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} - strategy: - fail-fast: false - matrix: - browser: [chromium, firefox, webkit] - os: [ubuntu-22.04] - node-version: [18] - include: - - os: ubuntu-22.04 - node-version: 20 - browser: chromium - - os: ubuntu-22.04 - node-version: 22 - browser: chromium - runs-on: ${{ matrix.os }} - permissions: - id-token: write # This is required for OIDC login (azure/login) to succeed - contents: read # This is required for actions/checkout to succeed + test_webkit_wsl: + name: Webkit WSL + runs-on: windows-2025 steps: - uses: actions/checkout@v4 + - name: Setup tmate session + uses: mxschmitt/action-tmate@v3 - uses: ./.github/actions/run-test with: - node-version: ${{ matrix.node-version }} - browsers-to-install: ${{ matrix.browser }} chromium - command: npm run test -- --project=${{ matrix.browser }}-* - bot-name: "${{ matrix.browser }}-${{ matrix.os }}-node${{ matrix.node-version }}" - flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} - flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} - flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - - test_linux_chromium_tot: - name: ${{ matrix.os }} (chromium tip-of-tree) - environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-22.04] - runs-on: ${{ matrix.os }} - permissions: - id-token: write # This is required for OIDC login (azure/login) to succeed - contents: read # This is required for actions/checkout to succeed - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/run-test - with: - browsers-to-install: chromium-tip-of-tree - command: npm run test -- --project=chromium-* - bot-name: "${{ matrix.os }}-chromium-tip-of-tree" + node-version: 22 + browsers-to-install: webkit-wsl + command: npm run test -- --project=webkit-* --reporter=list + bot-name: "webkit-wsl-headed" flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} env: - PWTEST_CHANNEL: chromium-tip-of-tree - - test_test_runner: - name: Test Runner - environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [18] - shardIndex: [1, 2] - shardTotal: [2] - include: - - os: ubuntu-latest - node-version: 20 - shardIndex: 1 - shardTotal: 2 - - os: ubuntu-latest - node-version: 20 - shardIndex: 2 - shardTotal: 2 - - os: ubuntu-latest - node-version: 22 - shardIndex: 1 - shardTotal: 2 - - os: ubuntu-latest - node-version: 22 - shardIndex: 2 - shardTotal: 2 - runs-on: ${{ matrix.os }} - permissions: - id-token: write # This is required for OIDC login (azure/login) to succeed - contents: read # This is required for actions/checkout to succeed - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/run-test - with: - node-version: ${{matrix.node-version}} - command: npm run ttest -- --shard ${{ matrix.shardIndex }}/${{ matrix.shardTotal }} - bot-name: "${{ matrix.os }}-node${{ matrix.node-version }}-${{ matrix.shardIndex }}" - flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} - flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} - flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} - env: - PWTEST_CHANNEL: firefox-beta - - test_web_components: - name: Web Components - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - run: npm ci - - run: npm run build - - - run: npx playwright install --with-deps - - run: npm run test-html-reporter - env: - PWTEST_BOT_NAME: "web-components-html-reporter" - - name: Upload blob report - if: ${{ !cancelled() }} - uses: ./.github/actions/upload-blob-report - with: - report_dir: packages/html-reporter/blob-report - job_name: "web-components-html-reporter" - - - run: npm run test-web - if: ${{ !cancelled() }} - env: - PWTEST_BOT_NAME: "web-components-web" - - name: Upload blob report - if: ${{ !cancelled() }} - uses: ./.github/actions/upload-blob-report - with: - report_dir: packages/web/blob-report - job_name: "web-components-web" - - test_vscode_extension: - name: VSCode Extension - runs-on: ubuntu-latest - env: - PWTEST_BOT_NAME: "vscode-extension" - DEBUG_GIT_COMMIT_INFO: "" - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: 18 - - run: npm ci - env: - DEBUG: pw:install - - run: npm run build - - run: npx playwright install chromium - - name: Checkout extension - run: git clone https://github.com/microsoft/playwright-vscode.git - - name: Print extension revision - run: git rev-parse HEAD - working-directory: ./playwright-vscode - - name: Remove @playwright/test from extension dependencies - run: node -e "const p = require('./package.json'); delete p.devDependencies['@playwright/test']; fs.writeFileSync('./package.json', JSON.stringify(p, null, 2));" - working-directory: ./playwright-vscode - - name: Build extension - run: npm ci && npm run build - working-directory: ./playwright-vscode - - name: Run extension tests - run: npm run test -- --workers=1 - working-directory: ./playwright-vscode - - name: Upload blob report - if: ${{ !cancelled() }} - uses: ./.github/actions/upload-blob-report - with: - report_dir: playwright-vscode/blob-report - job_name: ${{ env.PWTEST_BOT_NAME }} - - test_package_installations: - name: "Installation Test ${{ matrix.os }}" - environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} - strategy: - fail-fast: false - matrix: - os: - - ubuntu-latest - - macos-latest - - windows-latest - runs-on: ${{ matrix.os }} - timeout-minutes: 30 - permissions: - id-token: write # This is required for OIDC login (azure/login) to succeed - contents: read # This is required for actions/checkout to succeed - steps: - - uses: actions/checkout@v4 - - run: npm install -g yarn@1 - - run: npm install -g pnpm@8 - - name: Setup Ubuntu Binary Installation # TODO: Remove when https://github.com/electron/electron/issues/42510 is fixed - if: ${{ runner.os == 'Linux' }} - run: | - if grep -q "Ubuntu 24" /etc/os-release; then - sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 - fi - shell: bash - - uses: ./.github/actions/run-test - with: - command: npm run itest - bot-name: "package-installations-${{ matrix.os }}" - shell: ${{ matrix.os == 'windows-latest' && 'pwsh' || 'bash' }} - flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} - flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} - flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} + PWTEST_CHANNEL: webkit-wsl + DEBUG: pw:api,pw:browser diff --git a/examples/todomvc/playwright.config.ts b/examples/todomvc/playwright.config.ts index 831aa9035595b..1eec7134721a8 100644 --- a/examples/todomvc/playwright.config.ts +++ b/examples/todomvc/playwright.config.ts @@ -12,7 +12,7 @@ export default defineConfig({ /* Maximum time one test can run for. */ timeout: 15_000, - captureGitInfo: { commit: true, diff: true }, + captureGitInfo: { commit: false, diff: false }, expect: { @@ -50,26 +50,27 @@ export default defineConfig({ /* Configure projects for major browsers */ projects: [ - { - name: 'chromium', + // { + // name: 'chromium', - /* Project-specific settings. */ - use: { - ...devices['Desktop Chrome'], - }, - }, + // /* Project-specific settings. */ + // use: { + // ...devices['Desktop Chrome'], + // }, + // }, - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, + // { + // name: 'firefox', + // use: { + // ...devices['Desktop Firefox'], + // }, + // }, { name: 'webkit', use: { ...devices['Desktop Safari'], + channel: 'webkit-wsl', }, }, diff --git a/packages/playwright-core/bin/install_webkit_wsl.ps1 b/packages/playwright-core/bin/install_webkit_wsl.ps1 new file mode 100644 index 0000000000000..4001e6d8d7b5d --- /dev/null +++ b/packages/playwright-core/bin/install_webkit_wsl.ps1 @@ -0,0 +1,28 @@ +$ErrorActionPreference = 'Stop' + +$Distribution = "playwright" +$Username = "pwuser" + +$distributions = (wsl --list --quiet) -split "\r?\n" +if ($distributions -contains $Distribution) { + Write-Host "WSL distribution '$Distribution' already exists. Skipping installation." +} else { + Write-Host "Installing new WSL distribution '$Distribution'..." + wsl --install -d Ubuntu-24.04 --name $Distribution --no-launch + wsl -d $Distribution -u root adduser --gecos GECOS --disabled-password $Username +} + +$pwshDirname = (Resolve-Path -Path $PSScriptRoot).Path; +$playwrightCoreRoot = Resolve-Path (Join-Path $pwshDirname "..") + +$initScript = @" +if ! command -v node >/dev/null 2>&1; then + curl -fsSL https://deb.nodesource.com/setup_22.x | bash - + apt-get install -y nodejs +fi +node cli.js install-deps +cp bin/webkit-wsl-pipe-wrapper.mjs /home/$Username/ +sudo -u $Username PLAYWRIGHT_SKIP_BROWSER_GC=1 node cli.js install webkit +"@ -replace "\r\n", "`n" + +wsl -d $Distribution --cd $playwrightCoreRoot -u root -- bash -c "$initScript" diff --git a/packages/playwright-core/bin/webkit-wsl-pipe-wrapper.mjs b/packages/playwright-core/bin/webkit-wsl-pipe-wrapper.mjs new file mode 100644 index 0000000000000..966a69136186f --- /dev/null +++ b/packages/playwright-core/bin/webkit-wsl-pipe-wrapper.mjs @@ -0,0 +1,49 @@ +// @ts-check +import net from 'net'; +import { execSync, spawn } from 'child_process'; +import { readFileSync } from 'fs'; + +const socketPort = process.env.SOCKET_ADDRESS; + +if (!socketPort) + throw new Error('SOCKET_ADDRESS is not set'); +const address = (() => { + if (execSync('wslinfo --networking-mode', { encoding: 'utf8' }).trim() === 'nat') { + const nameserverLine = readFileSync('/etc/resolv.conf', 'utf8').split('\n').find(line => line.startsWith('nameserver')); + return nameserverLine?.split(' ')[1] || '127.0.0.1'; + } + return '127.0.0.1'; +})(); + +const socket = net.createConnection(parseInt(socketPort), address); +socket.on('error', (error) => console.log('socket error from wrapper', error)); + +await new Promise((resolve, reject) => { + socket.on('connect', resolve); + socket.on('error', reject); +}); + +const [executable, ...args] = process.argv.slice(2); + +// 3 is readFD and 4 is writeFD +const child = spawn(executable, args, { + stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'pipe'] +}); + +// Connect socket to child process pipes +socket.pipe(child.stdio[3]); +child.stdio[4].pipe(socket); + +// Handle cleanup +socket.on('end', () => { + child.kill(); +}); + +child.on('exit', () => { + socket.end(); +}); + +await new Promise((resolve, reject) => { + child.on('exit', resolve); + child.on('error', reject); +}); diff --git a/packages/playwright-core/src/server/browser.ts b/packages/playwright-core/src/server/browser.ts index f19c124afdfeb..288d7a093e3ec 100644 --- a/packages/playwright-core/src/server/browser.ts +++ b/packages/playwright-core/src/server/browser.ts @@ -141,6 +141,7 @@ export abstract class Browser extends SdkObject { const download = this._downloads.get(uuid); if (!download) return; + console.log('downloadFilenameSuggested', uuid, suggestedFilename); download._filenameSuggested(suggestedFilename); } diff --git a/packages/playwright-core/src/server/browserType.ts b/packages/playwright-core/src/server/browserType.ts index ba0cfa4aa148e..9ec4695155457 100644 --- a/packages/playwright-core/src/server/browserType.ts +++ b/packages/playwright-core/src/server/browserType.ts @@ -17,6 +17,8 @@ import fs from 'fs'; import os from 'os'; import path from 'path'; +import net from 'net'; +import { PassThrough } from 'stream'; import { normalizeProxySettings, validateBrowserContextOptions } from './browserContext'; import { debugMode } from './utils/debug'; @@ -171,7 +173,7 @@ export abstract class BrowserType extends SdkObject { } await this.prepareUserDataDir(options, userDataDir); - const browserArguments: string[] = []; + let browserArguments: string[] = []; if (ignoreAllDefaultArgs) browserArguments.push(...args); else if (ignoreDefaultArgs) @@ -179,6 +181,7 @@ export abstract class BrowserType extends SdkObject { else browserArguments.push(...this.defaultArgs(options, isPersistent, userDataDir)); + let tcpTransport = false; let executable: string; if (executablePath) { if (!(await existsAsync(executablePath))) @@ -189,10 +192,13 @@ export abstract class BrowserType extends SdkObject { if (!registryExecutable || registryExecutable.browserName !== this._name) throw new Error(`Unsupported ${this._name} channel "${options.channel}"`); executable = registryExecutable.executablePathOrDie(this.attribution.playwright.options.sdkLanguage); + if (registryExecutable.wrapArgs) + browserArguments = registryExecutable.wrapArgs(browserArguments); + tcpTransport = registryExecutable.tcpTransport ?? false; await registry.validateHostRequirementsForExecutablesIfNeeded([registryExecutable], this.attribution.playwright.options.sdkLanguage); } - return { executable, browserArguments, userDataDir, artifactsDir, tempDirectories }; + return { executable, browserArguments, userDataDir, artifactsDir, tempDirectories, tcpTransport }; } private async _launchProcess(progress: Progress, options: types.LaunchOptions, isPersistent: boolean, browserLogsCollector: RecentLogsCollector, userDataDir?: string): Promise<{ browserProcess: BrowserProcess, artifactsDir: string, userDataDir: string, transport: ConnectionTransport }> { @@ -211,10 +217,22 @@ export abstract class BrowserType extends SdkObject { let transport: ConnectionTransport | undefined = undefined; let browserProcess: BrowserProcess | undefined = undefined; const exitPromise = new ManualPromise(); + const [readPipe, writePipe] = prepared.tcpTransport ? [new PassThrough(), new PassThrough()] : [undefined, undefined] + console.error(new Date(), "creating transport server") + const transportServer = prepared.tcpTransport ? net.createServer((socket) => { + writePipe!.pipe(socket); + socket.pipe(readPipe!); + }) : undefined; + transportServer && await new Promise((resolve) => transportServer.listen(0, resolve)); + console.error(new Date(), "launching process") const { launchedProcess, gracefullyClose, kill } = await launchProcess({ command: prepared.executable, args: prepared.browserArguments, - env: this.amendEnvironment(env, prepared.userDataDir, prepared.executable, prepared.browserArguments), + env: { + ...this.amendEnvironment(env, prepared.userDataDir, prepared.executable, prepared.browserArguments, options.channel), + "WSLENV": "SOCKET_ADDRESS", + 'SOCKET_ADDRESS': (transportServer?.address() as any)?.port?.toString() ?? '', + }, handleSIGINT, handleSIGTERM, handleSIGHUP, @@ -233,6 +251,7 @@ export abstract class BrowserType extends SdkObject { this.attemptToGracefullyCloseBrowser(transport!); }, onExit: (exitCode, signal) => { + transportServer?.close(); // Unblock launch when browser prematurely exits. exitPromise.resolve(); if (browserProcess && browserProcess.onclose) @@ -268,7 +287,7 @@ export abstract class BrowserType extends SdkObject { transport = await WebSocketTransport.connect(progress, wsEndpoint!); } else { const stdio = launchedProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream]; - transport = new PipeTransport(stdio[3], stdio[4]); + transport = new PipeTransport(writePipe ?? stdio[3], readPipe ?? stdio[4]); } progress.cleanupWhenAborted(() => transport.close()); return { browserProcess, artifactsDir: prepared.artifactsDir, userDataDir: prepared.userDataDir, transport }; @@ -337,7 +356,7 @@ export abstract class BrowserType extends SdkObject { abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[]; abstract connectToTransport(transport: ConnectionTransport, options: BrowserOptions, browserLogsCollector: RecentLogsCollector): Promise; - abstract amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env; + abstract amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[], channel?: string): Env; abstract doRewriteStartupLog(error: ProtocolError): ProtocolError; abstract attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void; } diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 433a6f35f6ca9..8ab2d0cd7d11c 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -490,7 +490,7 @@ const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', ' export interface Executable { type: 'browser' | 'tool' | 'channel'; - name: BrowserName | InternalTool | ChromiumChannel | BidiChannel; + name: BrowserName | InternalTool | ChromiumChannel | BidiChannel | 'webkit-wsl'; browserName: BrowserName | undefined; installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none'; directory: string | undefined; @@ -499,6 +499,8 @@ export interface Executable { executablePathOrDie(sdkLanguage: string): string; executablePath(sdkLanguage: string): string | undefined; _validateHostRequirements(sdkLanguage: string): Promise; + wrapArgs?: (args: string[]) => string[]; + tcpTransport?: boolean; } interface ExecutableImpl extends Executable { @@ -796,6 +798,43 @@ export class Registry { _dependencyGroup: 'webkit', _isHermeticInstallation: true, }); + this._executables.push({ + type: 'channel', + name: 'webkit-wsl', + browserName: 'webkit', + directory: webkit.dir, + executablePath: () => 'wsl', + wrapArgs: (args: string[]) => { + return [ + '-d', + 'playwright', + '--cd', + '/home/pwuser', + 'node', + '/home/pwuser/webkit-wsl-pipe-wrapper.mjs', + `/home/pwuser/.cache/ms-playwright/webkit-${webkit.revision}/pw_run.sh`, + ...args, + ]; + }, + tcpTransport: true, + executablePathOrDie: (sdkLanguage: string) => 'wsl', + installType: webkit.installByDefault ? 'download-by-default' : 'download-on-demand', + _validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, webkit.dir, webkitLinuxLddDirectories, ['libGLESv2.so.2', 'libx264.so'], ['']), + _isHermeticInstallation: true, + _install: async () => { + if (process.platform !== 'win32') + throw new Error(`WebKit via WSL is only supported on Windows`); + const script = path.join(BIN_PATH, 'install_webkit_wsl.ps1'); + const { code } = await spawnAsync('powershell.exe', [ + '-ExecutionPolicy', 'Bypass', + '-File', script, + ], { + stdio: 'inherit', + }); + if (code !== 0) + throw new Error(`Failed to install WebKit via WSL`); + }, + }); const ffmpeg = descriptors.find(d => d.name === 'ffmpeg')!; const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg'); diff --git a/packages/playwright-core/src/server/webkit/webkit.ts b/packages/playwright-core/src/server/webkit/webkit.ts index 5b5519264cc2c..6dc810a4d1847 100644 --- a/packages/playwright-core/src/server/webkit/webkit.ts +++ b/packages/playwright-core/src/server/webkit/webkit.ts @@ -28,6 +28,7 @@ import type { Env } from '../utils/processLauncher'; import type { ProtocolError } from '../protocolError'; import type { ConnectionTransport } from '../transport'; import type * as types from '../types'; +import { spawnSync } from 'child_process'; export class WebKit extends BrowserType { constructor(parent: SdkObject) { @@ -38,8 +39,11 @@ export class WebKit extends BrowserType { return WKBrowser.connect(this.attribution.playwright, transport, options); } - override amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[]): Env { - return { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir, 'cookiejar.db') }; + override amendEnvironment(env: Env, userDataDir: string, executable: string, browserArguments: string[], channel?: string): Env { + return { + ...env, + // CURL_COOKIE_JAR_PATH: path.join(channel === 'webkit-wsl' ? translatePathToWSL(userDataDir) : userDataDir, 'cookiejar.db'), + }; } override doRewriteStartupLog(error: ProtocolError): ProtocolError { @@ -62,12 +66,12 @@ export class WebKit extends BrowserType { if (args.find(arg => !arg.startsWith('-'))) throw new Error('Arguments can not specify page to be opened'); const webkitArguments = ['--inspector-pipe']; - if (process.platform === 'win32') + if (process.platform === 'win32' && options.channel !== 'webkit-wsl') webkitArguments.push('--disable-accelerated-compositing'); if (headless) webkitArguments.push('--headless'); if (isPersistent) - webkitArguments.push(`--user-data-dir=${userDataDir}`); + webkitArguments.push(`--user-data-dir=${options.channel === 'webkit-wsl' ? translatePathToWSL(userDataDir) : userDataDir}`); else webkitArguments.push(`--no-startup-window`); const proxy = options.proxyOverride || options.proxy; @@ -76,7 +80,7 @@ export class WebKit extends BrowserType { webkitArguments.push(`--proxy=${proxy.server}`); if (proxy.bypass) webkitArguments.push(`--proxy-bypass-list=${proxy.bypass}`); - } else if (process.platform === 'linux') { + } else if (process.platform === 'linux' || process.platform === 'win32' && options.channel == 'webkit-wsl') { webkitArguments.push(`--proxy=${proxy.server}`); if (proxy.bypass) webkitArguments.push(...proxy.bypass.split(',').map(t => `--ignore-host=${t}`)); @@ -94,3 +98,10 @@ export class WebKit extends BrowserType { return webkitArguments; } } + +export function translatePathToWSL(path: string): string { + console.time('translatePathToWSL:' + path); + const result = spawnSync('wsl', ['-d', 'playwright', 'wslpath', path.replace(/\\/g, '\\\\'), '--cd', '/home/pwuser']).stdout.toString().trim() + console.timeEnd('translatePathToWSL:' + path); + return result; +} diff --git a/packages/playwright-core/src/server/webkit/wkBrowser.ts b/packages/playwright-core/src/server/webkit/wkBrowser.ts index aaf4c197a4659..bc0d0e9190a55 100644 --- a/packages/playwright-core/src/server/webkit/wkBrowser.ts +++ b/packages/playwright-core/src/server/webkit/wkBrowser.ts @@ -87,7 +87,7 @@ export class WKBrowser extends Browser { const createOptions = proxy ? { // Enable socks5 hostname resolution on Windows. // See https://github.com/microsoft/playwright/issues/20451 - proxyServer: process.platform === 'win32' ? proxy.server.replace(/^socks5:\/\//, 'socks5h://') : proxy.server, + proxyServer: process.platform === 'win32' && this.attribution.browser?.options.channel !== 'webkit-wsl' ? proxy.server.replace(/^socks5:\/\//, 'socks5h://') : proxy.server, proxyBypassList: proxy.bypass } : undefined; const { browserContextId } = await this._browserSession.send('Playwright.createContext', createOptions); diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 36f66daaa7c68..235f81c674554 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -49,6 +49,7 @@ import type { JSHandle } from '../javascript'; import type { InitScript, PageDelegate } from '../page'; import type { Progress } from '../progress'; import type * as types from '../types'; +import { translatePathToWSL } from './webkit'; const UTILITY_WORLD_NAME = '__playwright_utility_world__'; @@ -837,7 +838,7 @@ export class WKPage implements PageDelegate { private async _startVideo(options: types.PageScreencastOptions): Promise { assert(!this._recordingVideoFile); const { screencastId } = await this._pageProxySession.send('Screencast.startVideo', { - file: options.outputFile, + file: translatePathToWSL(options.outputFile), width: options.width, height: options.height, toolbarHeight: this._toolbarHeight() @@ -971,6 +972,8 @@ export class WKPage implements PageDelegate { async setInputFilePaths(handle: dom.ElementHandle, paths: string[]): Promise { const pageProxyId = this._pageProxySession.sessionId; const objectId = handle._objectId; + if (this._browserContext._browser?.options.channel === 'webkit-wsl') + paths = paths.map(path => translatePathToWSL(path)); await Promise.all([ this._pageProxySession.connection.browserSession.send('Playwright.grantFileReadAccess', { pageProxyId, paths }), this._session.send('DOM.setInputFiles', { objectId, paths }) diff --git a/tests/config/browserTest.ts b/tests/config/browserTest.ts index 82111b24a1231..1f2bedc454201 100644 --- a/tests/config/browserTest.ts +++ b/tests/config/browserTest.ts @@ -28,6 +28,7 @@ import type { RemoteServerOptions, PlaywrightServer } from './remoteServer'; import type { BrowserContext, BrowserContextOptions, BrowserType, Page } from 'playwright-core'; import type { Log } from '../../packages/trace/src/har'; import type { TestInfo } from '@playwright/test'; +import { execSync } from 'child_process'; export type BrowserTestWorkerFixtures = PageWorkerFixtures & { browserVersion: string; @@ -57,6 +58,21 @@ type BrowserTestTestFixtures = PageTestFixtures & { }; const test = baseTest.extend({ + loopback: [async ({ channel }, use) => { + if (!channel) + return await use(undefined); + if (execSync('wsl -d playwright --cd /home/pwuser wslinfo --networking-mode').includes('nat')) + return await use(execSync('wsl -d playwright --cd /home/pwuser cat /etc/resolv.conf').toString().split('\n').find(line => line.startsWith('nameserver')).split(' ')[1]); + return await use(undefined); + }, { scope: 'worker', timeout: 10000 }], + loopback2: [async ({ channel }, use) => { + if (!channel) + return await use(undefined); + if (execSync('wsl -d playwright --cd /home/pwuser wslinfo --networking-mode').includes('nat')) + return await use(execSync('wsl -d playwright --cd /home/pwuser cat /etc/resolv.conf').toString().split('\n').find(line => line.startsWith('nameserver')).split(' ')[1]); + return await use(undefined); + }, { scope: 'worker', timeout: 10000 }], + browserVersion: [async ({ browser }, run) => { await run(browser.version()); }, { scope: 'worker' }], diff --git a/tests/config/testserver/index.ts b/tests/config/testserver/index.ts index 7538badf9de6c..e99a4e5821786 100644 --- a/tests/config/testserver/index.ts +++ b/tests/config/testserver/index.ts @@ -56,14 +56,14 @@ export class TestServer { readonly CROSS_PROCESS_PREFIX: string; readonly EMPTY_PAGE: string; - static async create(dirPath: string, port: number, loopback?: string): Promise { - const server = new TestServer(dirPath, port, loopback); + static async create(dirPath: string, port: number, loopback?: string, loopback2?: string): Promise { + const server = new TestServer(dirPath, port, loopback, loopback2); await server.waitUntilReady(); return server; } - static async createHTTPS(dirPath: string, port: number, loopback?: string): Promise { - const server = new TestServer(dirPath, port, loopback, { + static async createHTTPS(dirPath: string, port: number, loopback?: string, loopback2?: string): Promise { + const server = new TestServer(dirPath, port, loopback, loopback2, { key: await fs.promises.readFile(path.join(__dirname, 'key.pem')), cert: await fs.promises.readFile(path.join(__dirname, 'cert.pem')), passphrase: 'aaaa', @@ -72,7 +72,7 @@ export class TestServer { return server; } - constructor(dirPath: string, port: number, loopback?: string, sslOptions?: object) { + constructor(dirPath: string, port: number, loopback?: string, loopback2?: string, sslOptions?: object) { if (sslOptions) this._server = createHttpsServer(sslOptions, this._onRequest.bind(this)); else @@ -112,8 +112,8 @@ export class TestServer { this._startTime = new Date(); this._cachedPathPrefix = null; - const cross_origin = loopback || '127.0.0.1'; const same_origin = loopback || 'localhost'; + const cross_origin = loopback2 || '127.0.0.1'; const protocol = sslOptions ? 'https' : 'http'; this.PORT = port; this.PREFIX = `${protocol}://${same_origin}:${port}`;