feat(webkit): allow running WebKit via WSL on Windows (#36358)
This commit is contained in:
parent
ec61c0324f
commit
573441cad7
|
|
@ -0,0 +1,35 @@
|
|||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# WebKit WSL Installation Script
|
||||
# See webkit-wsl-transport-server.ts for the complete architecture diagram.
|
||||
# This script sets up a WSL distribution that will be used to run WebKit.
|
||||
|
||||
$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'..."
|
||||
$VhdSize = "10GB"
|
||||
wsl --install -d Ubuntu-24.04 --name $Distribution --no-launch --vhd-size $VhdSize
|
||||
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 [ ! -f "/home/$Username/node/bin/node" ]; then
|
||||
mkdir -p /home/$Username/node
|
||||
curl -fsSL https://nodejs.org/dist/v22.17.0/node-v22.17.0-linux-x64.tar.xz -o /home/$Username/node/node-v22.17.0-linux-x64.tar.xz
|
||||
tar -xJf /home/$Username/node/node-v22.17.0-linux-x64.tar.xz -C /home/$Username/node --strip-components=1
|
||||
fi
|
||||
/home/$Username/node/bin/node cli.js install-deps webkit
|
||||
cp lib/server/webkit/wsl/webkit-wsl-transport-client.js /home/$Username/
|
||||
sudo -u $Username PLAYWRIGHT_SKIP_BROWSER_GC=1 /home/$Username/node/bin/node cli.js install webkit
|
||||
"@ -replace "\r\n", "`n"
|
||||
|
||||
wsl -d $Distribution --cd $playwrightCoreRoot -u root -- bash -c "$initScript"
|
||||
Write-Host "Done!"
|
||||
|
|
@ -92,7 +92,7 @@ export class BidiChromium extends BrowserType {
|
|||
return false;
|
||||
}
|
||||
|
||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||
override async defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string) {
|
||||
const chromeArguments = this._innerDefaultArgs(options);
|
||||
chromeArguments.push(`--user-data-dir=${userDataDir}`);
|
||||
chromeArguments.push('--remote-debugging-port=0');
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ export class BidiFirefox extends BrowserType {
|
|||
});
|
||||
}
|
||||
|
||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||
override async defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string) {
|
||||
const { args = [], headless } = options;
|
||||
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
|
||||
if (userDataDirArg)
|
||||
|
|
|
|||
|
|
@ -174,9 +174,9 @@ export abstract class BrowserType extends SdkObject {
|
|||
if (ignoreAllDefaultArgs)
|
||||
browserArguments.push(...args);
|
||||
else if (ignoreDefaultArgs)
|
||||
browserArguments.push(...this.defaultArgs(options, isPersistent, userDataDir).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
|
||||
browserArguments.push(...(await this.defaultArgs(options, isPersistent, userDataDir)).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
|
||||
else
|
||||
browserArguments.push(...this.defaultArgs(options, isPersistent, userDataDir));
|
||||
browserArguments.push(...await this.defaultArgs(options, isPersistent, userDataDir));
|
||||
|
||||
let executable: string;
|
||||
if (executablePath) {
|
||||
|
|
@ -212,7 +212,7 @@ export abstract class BrowserType extends SdkObject {
|
|||
const { launchedProcess, gracefullyClose, kill } = await launchProcess({
|
||||
command: prepared.executable,
|
||||
args: prepared.browserArguments,
|
||||
env: this.amendEnvironment(env, prepared.userDataDir, isPersistent),
|
||||
env: this.amendEnvironment(env, prepared.userDataDir, isPersistent, options),
|
||||
handleSIGINT,
|
||||
handleSIGTERM,
|
||||
handleSIGHUP,
|
||||
|
|
@ -338,9 +338,9 @@ export abstract class BrowserType extends SdkObject {
|
|||
return options.channel || this._name;
|
||||
}
|
||||
|
||||
abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[];
|
||||
abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): Promise<string[]>;
|
||||
abstract connectToTransport(transport: ConnectionTransport, options: BrowserOptions, browserLogsCollector: RecentLogsCollector): Promise<Browser>;
|
||||
abstract amendEnvironment(env: NodeJS.ProcessEnv, userDataDir: string, isPersistent: boolean): NodeJS.ProcessEnv;
|
||||
abstract amendEnvironment(env: NodeJS.ProcessEnv, userDataDir: string, isPersistent: boolean, options: types.LaunchOptions): NodeJS.ProcessEnv;
|
||||
abstract doRewriteStartupLog(error: ProtocolError): ProtocolError;
|
||||
abstract attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -280,7 +280,7 @@ export class Chromium extends BrowserType {
|
|||
}
|
||||
}
|
||||
|
||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||
override async defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string) {
|
||||
const chromeArguments = this._innerDefaultArgs(options);
|
||||
chromeArguments.push(`--user-data-dir=${userDataDir}`);
|
||||
if (options.cdpPort !== undefined)
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ export class Firefox extends BrowserType {
|
|||
transport.send(message);
|
||||
}
|
||||
|
||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||
override async defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string) {
|
||||
const { args = [], headless } = options;
|
||||
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));
|
||||
if (userDataDirArg)
|
||||
|
|
|
|||
|
|
@ -510,7 +510,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;
|
||||
|
|
@ -519,6 +519,7 @@ export interface Executable {
|
|||
executablePathOrDie(sdkLanguage: string): string;
|
||||
executablePath(sdkLanguage: string): string | undefined;
|
||||
_validateHostRequirements(sdkLanguage: string): Promise<void>;
|
||||
wslExecutablePath?: string
|
||||
}
|
||||
|
||||
interface ExecutableImpl extends Executable {
|
||||
|
|
@ -816,6 +817,31 @@ export class Registry {
|
|||
_dependencyGroup: 'webkit',
|
||||
_isHermeticInstallation: true,
|
||||
});
|
||||
this._executables.push({
|
||||
type: 'channel',
|
||||
name: 'webkit-wsl',
|
||||
browserName: 'webkit',
|
||||
directory: webkit.dir,
|
||||
executablePath: () => process.execPath,
|
||||
executablePathOrDie: () => process.execPath,
|
||||
wslExecutablePath: `/home/pwuser/.cache/ms-playwright/webkit-${webkit.revision}/pw_run.sh`,
|
||||
installType: 'download-on-demand',
|
||||
_validateHostRequirements: (sdkLanguage: string) => Promise.resolve(),
|
||||
_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');
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ import { kBrowserCloseMessageId } from './wkConnection';
|
|||
import { wrapInASCIIBox } from '../utils/ascii';
|
||||
import { BrowserType, kNoXServerRunningError } from '../browserType';
|
||||
import { WKBrowser } from '../webkit/wkBrowser';
|
||||
import { spawnAsync } from '../utils/spawnAsync';
|
||||
import { registry } from '../registry';
|
||||
|
||||
import type { BrowserOptions } from '../browser';
|
||||
import type { SdkObject } from '../instrumentation';
|
||||
|
|
@ -37,10 +39,11 @@ export class WebKit extends BrowserType {
|
|||
return WKBrowser.connect(this.attribution.playwright, transport, options);
|
||||
}
|
||||
|
||||
override amendEnvironment(env: NodeJS.ProcessEnv, userDataDir: string, isPersistent: boolean): NodeJS.ProcessEnv {
|
||||
override amendEnvironment(env: NodeJS.ProcessEnv, userDataDir: string, isPersistent: boolean, options: types.LaunchOptions): NodeJS.ProcessEnv {
|
||||
return {
|
||||
...env,
|
||||
CURL_COOKIE_JAR_PATH: process.platform === 'win32' && isPersistent ? path.join(userDataDir, 'cookiejar.db') : undefined,
|
||||
WEBKIT_EXECUTABLE: options.channel === 'webkit-wsl' ? registry.findExecutable('webkit-wsl')!.wslExecutablePath! : undefined
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -57,7 +60,7 @@ export class WebKit extends BrowserType {
|
|||
transport.send({ method: 'Playwright.close', params: {}, id: kBrowserCloseMessageId });
|
||||
}
|
||||
|
||||
override defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): string[] {
|
||||
override async defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): Promise<string[]> {
|
||||
const { args = [], headless } = options;
|
||||
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));
|
||||
if (userDataDirArg)
|
||||
|
|
@ -65,12 +68,21 @@ 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 (options.channel === 'webkit-wsl') {
|
||||
if (options.executablePath)
|
||||
throw new Error('Cannot specify executablePath when using the "webkit-wsl" channel.');
|
||||
webkitArguments.unshift(
|
||||
path.join(__dirname, 'wsl/webkit-wsl-transport-server.js'),
|
||||
);
|
||||
}
|
||||
|
||||
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' ? await translatePathToWSL(userDataDir) : userDataDir}`);
|
||||
else
|
||||
webkitArguments.push(`--no-startup-window`);
|
||||
const proxy = options.proxyOverride || options.proxy;
|
||||
|
|
@ -79,7 +91,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}`));
|
||||
|
|
@ -97,3 +109,8 @@ export class WebKit extends BrowserType {
|
|||
return webkitArguments;
|
||||
}
|
||||
}
|
||||
|
||||
export async function translatePathToWSL(path: string): Promise<string> {
|
||||
const { stdout } = await spawnAsync('wsl.exe', ['-d', 'playwright', '--cd', '/home/pwuser', 'wslpath', path.replace(/\\/g, '\\\\')]);
|
||||
return stdout.toString().trim();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import * as network from '../network';
|
|||
import { WKConnection, WKSession, kPageProxyMessageReceived } from './wkConnection';
|
||||
import { WKPage } from './wkPage';
|
||||
import { TargetClosedError } from '../errors';
|
||||
import { translatePathToWSL } from './webkit';
|
||||
|
||||
import type { BrowserOptions } from '../browser';
|
||||
import type { SdkObject } from '../instrumentation';
|
||||
|
|
@ -87,7 +88,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);
|
||||
|
|
@ -227,7 +228,7 @@ export class WKBrowserContext extends BrowserContext {
|
|||
const promises: Promise<any>[] = [super._initialize()];
|
||||
promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', {
|
||||
behavior: this._options.acceptDownloads === 'accept' ? 'allow' : 'deny',
|
||||
downloadPath: this._browser.options.downloadsPath,
|
||||
downloadPath: this._browser.options.channel === 'webkit-wsl' ? await translatePathToWSL(this._browser.options.downloadsPath) : this._browser.options.downloadsPath,
|
||||
browserContextId
|
||||
}));
|
||||
if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest';
|
|||
import { WKProvisionalPage } from './wkProvisionalPage';
|
||||
import { WKWorkers } from './wkWorkers';
|
||||
import { debugLogger } from '../utils/debugLogger';
|
||||
import { translatePathToWSL } from './webkit';
|
||||
|
||||
import type { Protocol } from './protocol';
|
||||
import type { WKBrowserContext } from './wkBrowser';
|
||||
|
|
@ -842,7 +843,7 @@ export class WKPage implements PageDelegate {
|
|||
private async _startVideo(options: types.PageScreencastOptions): Promise<void> {
|
||||
assert(!this._recordingVideoFile);
|
||||
const { screencastId } = await this._pageProxySession.send('Screencast.startVideo', {
|
||||
file: options.outputFile,
|
||||
file: this._browserContext._browser.options.channel === 'webkit-wsl' ? await translatePathToWSL(options.outputFile) : options.outputFile,
|
||||
width: options.width,
|
||||
height: options.height,
|
||||
toolbarHeight: this._toolbarHeight()
|
||||
|
|
@ -976,6 +977,8 @@ export class WKPage implements PageDelegate {
|
|||
async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> {
|
||||
const pageProxyId = this._pageProxySession.sessionId;
|
||||
const objectId = handle._objectId;
|
||||
if (this._browserContext._browser?.options.channel === 'webkit-wsl')
|
||||
paths = await Promise.all(paths.map(path => translatePathToWSL(path)));
|
||||
await Promise.all([
|
||||
this._pageProxySession.connection.browserSession.send('Playwright.grantFileReadAccess', { pageProxyId, paths }),
|
||||
this._session.send('DOM.setInputFiles', { objectId, paths })
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
// @ts-check
|
||||
/* eslint-disable no-restricted-properties */
|
||||
/* eslint-disable no-console */
|
||||
|
||||
// WebKit WSL Transport Client - runs inside WSL/Linux
|
||||
// See webkit-wsl-transport-server.ts for the complete architecture diagram.
|
||||
// This client connects to the TCP server and bridges it to WebKit via fd3/fd4 pipes.
|
||||
|
||||
import net from 'net';
|
||||
import fs from 'fs';
|
||||
import { spawn, spawnSync } from 'child_process';
|
||||
|
||||
(async () => {
|
||||
const { PW_WSL_BRIDGE_PORT: socketPort, ...childEnv } = process.env;
|
||||
if (!socketPort)
|
||||
throw new Error('PW_WSL_BRIDGE_PORT env var is not set');
|
||||
|
||||
const [executable, ...args] = process.argv.slice(2);
|
||||
|
||||
if (!(await fs.promises.stat(executable)).isFile())
|
||||
throw new Error(`Executable does not exist. Did you update Playwright recently? Make sure to run npx playwright install webkit-wsl`);
|
||||
|
||||
const address = (() => {
|
||||
const res = spawnSync('/usr/bin/wslinfo', ['--networking-mode'], { encoding: 'utf8' });
|
||||
if (res.error || res.status !== 0)
|
||||
throw new Error(`Failed to run /usr/bin/wslinfo --networking-mode: ${res.error?.message || res.stderr || res.status}`);
|
||||
if (res.stdout.trim() === 'nat') {
|
||||
const ipRes = spawnSync('/usr/sbin/ip', ['route', 'show'], { encoding: 'utf8' });
|
||||
if (ipRes.error || ipRes.status !== 0)
|
||||
throw new Error(`Failed to run ip route show: ${ipRes.error?.message || ipRes.stderr || ipRes.status}`);
|
||||
const ip = ipRes.stdout.trim().split('\n').find(line => line.includes('default'))?.split(' ')[2];
|
||||
if (!ip)
|
||||
throw new Error('Could not determine WSL IP address (NAT mode).');
|
||||
return ip;
|
||||
}
|
||||
return '127.0.0.1';
|
||||
})();
|
||||
|
||||
const socket = net.createConnection(parseInt(socketPort, 10), address);
|
||||
// Disable Nagle's algorithm to reduce latency for small, frequent messages.
|
||||
socket.setNoDelay(true);
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
socket.on('connect', resolve);
|
||||
socket.on('error', reject);
|
||||
});
|
||||
|
||||
const child = spawn(executable, args, {
|
||||
stdio: ['inherit', 'inherit', 'inherit', 'pipe', 'pipe'],
|
||||
env: childEnv,
|
||||
});
|
||||
|
||||
const [childOutput, childInput] = [child.stdio[3] as NodeJS.WritableStream, child.stdio[4] as NodeJS.ReadableStream];
|
||||
socket.pipe(childOutput);
|
||||
childInput.pipe(socket);
|
||||
|
||||
socket.on('end', () => child.kill());
|
||||
|
||||
child.on('exit', exitCode => {
|
||||
socket.end();
|
||||
process.exit(exitCode || 0);
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
child.on('exit', resolve);
|
||||
child.on('error', reject);
|
||||
});
|
||||
})().catch(error => {
|
||||
console.error('Error occurred:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/* eslint-disable no-restricted-properties */
|
||||
/* eslint-disable no-console */
|
||||
import net from 'net';
|
||||
import path from 'path';
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
// WebKit WSL Transport Architecture Diagram:
|
||||
//
|
||||
// ┌─────────────────┐ fd3/fd4 ┌──────────────────────┐
|
||||
// │ Playwright │◄──────────────►│ webkit-wsl-transport │
|
||||
// │ │ (pipes) │ server.ts │
|
||||
// └─────────────────┘ │ (Windows/Host) │
|
||||
// └──────────┬───────────┘
|
||||
// │ spawns
|
||||
// ▼
|
||||
// ┌──────────────────────┐
|
||||
// │ wsl.exe │
|
||||
// │ -d playwright │
|
||||
// └──────────┬───────────┘
|
||||
// │ starts
|
||||
// ▼
|
||||
// ┌─────────────────┐ TCP socket ┌──────────────────────┐ fd3/fd4 ┌─────────────┐
|
||||
// │ TCP Server │◄───────────────►│ webkit-wsl-transport │◄──────────────►│ WebKit │
|
||||
// │ (port forwarded │ over WSL │ client.ts │ (pipes) │ Browser │
|
||||
// │ via env var) │ boundary │ (WSL/Linux) │ │ Process │
|
||||
// └─────────────────┘ └──────────────────────┘ └─────────────┘
|
||||
//
|
||||
// The TCP server bridges fd3/fd4 pipes across the WSL boundary because wsl.exe
|
||||
// only supports forwarding up to 3 file descriptors (stdin/stdout/stderr).
|
||||
//
|
||||
// Data flow: Playwright ↔ fd3/fd4 ↔ TCP socket ↔ WSL network ↔ TCP socket ↔ fd3/fd4 ↔ WebKit
|
||||
//
|
||||
// Start a TCP server to bridge between parent (fd3/fd4) and the WSL child process.
|
||||
// This is needed because wsl.exe only supports up to 3 forwarded fds, so we can't
|
||||
// pass extra pipes directly and must tunnel them over a socket instead.
|
||||
|
||||
(async () => {
|
||||
const argv = process.argv.slice(2);
|
||||
if (!argv.length) {
|
||||
console.error(`Usage: node ${path.basename(__filename)} <executable> [args...]`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Use net.Socket instead of fs.createReadStream/WriteStream to avoid hanging at shutdown.
|
||||
// fs streams use libuv's async fs API which spawns FSReqCallbacks in the threadpool.
|
||||
// If fs operations are pending (e.g. waiting for EOF), Node's event loop stays referenced
|
||||
// and the process never exits. net.Socket integrates with libuv's event loop directly,
|
||||
// making reads/writes non-blocking and allowing clean shutdown via destroy().
|
||||
const parentIn = new net.Socket({ fd: 3, readable: true, writable: false }); // parent -> us
|
||||
const parentOut = new net.Socket({ fd: 4, readable: false, writable: true }); // us -> parent
|
||||
|
||||
const server = net.createServer();
|
||||
|
||||
let socket: net.Socket | null = null;
|
||||
server.on('connection', s => {
|
||||
if (socket) {
|
||||
log('Extra connection received, destroying.');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
socket = s;
|
||||
// Disable Nagle's algorithm to reduce latency for small, frequent messages.
|
||||
socket.setNoDelay(true);
|
||||
log('Client connected, wiring pipes.');
|
||||
|
||||
socket.pipe(parentOut);
|
||||
parentIn.pipe(socket);
|
||||
|
||||
socket.on('close', () => {
|
||||
log('Socket closed');
|
||||
socket = null;
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
server.once('error', reject);
|
||||
server.listen(0, () => resolve(null));
|
||||
});
|
||||
const address = server.address();
|
||||
if (!address || typeof address === 'string') {
|
||||
console.error('Failed to obtain listening address');
|
||||
process.exit(1);
|
||||
}
|
||||
const port = address.port;
|
||||
log('Server listening on', port);
|
||||
|
||||
// Spawn child process with augmented env. PW_WSL_BRIDGE_PORT is added to WSLENV
|
||||
// so this environment variable is propagated across Windows <-> WSL boundaries.
|
||||
// This does not forward the TCP port itself, only the env var containing it.
|
||||
const env = {
|
||||
...process.env,
|
||||
// WSLENV is a colon-delimited list of environment variables that should be included when launching WSL processes from Win32 or Win32 processes from WSL
|
||||
WSLENV: 'PW_WSL_BRIDGE_PORT',
|
||||
PW_WSL_BRIDGE_PORT: String(port),
|
||||
};
|
||||
|
||||
let shuttingDown = false;
|
||||
|
||||
const child = spawn('wsl.exe', [
|
||||
'-d',
|
||||
'playwright',
|
||||
'--cd',
|
||||
'/home/pwuser',
|
||||
'/home/pwuser/node/bin/node',
|
||||
'/home/pwuser/webkit-wsl-transport-client.js',
|
||||
process.env.WEBKIT_EXECUTABLE || '',
|
||||
...argv,
|
||||
], {
|
||||
env,
|
||||
stdio: ['inherit', 'inherit', 'inherit'], // no fd3/fd4 here; they stay only in this wrapper
|
||||
});
|
||||
|
||||
log('Spawned child pid', child.pid);
|
||||
|
||||
child.on('close', (code, signal) => {
|
||||
log('Child exit', { code, signal });
|
||||
// Use actual exit code, or 128, or fallback to 1 for unknown signals
|
||||
const exitCode = code ?? (signal ? 128 : 0);
|
||||
shutdown(exitCode);
|
||||
});
|
||||
child.on('error', err => {
|
||||
console.error('Child process failed to start:', err);
|
||||
shutdown(1);
|
||||
});
|
||||
|
||||
await new Promise(resolve => child.once('close', resolve));
|
||||
|
||||
async function shutdown(code = 0) {
|
||||
if (shuttingDown)
|
||||
return;
|
||||
shuttingDown = true;
|
||||
|
||||
server.close();
|
||||
|
||||
parentIn.destroy();
|
||||
parentOut.destroy();
|
||||
socket?.destroy();
|
||||
|
||||
await new Promise(resolve => server.once('close', resolve));
|
||||
|
||||
process.exit(code);
|
||||
}
|
||||
|
||||
function log(...args: any[]) {
|
||||
console.error(new Date(), `[${path.basename(__filename)}]`, ...args);
|
||||
}
|
||||
})().catch(error => {
|
||||
console.error('Error occurred:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
|
|
@ -73,10 +73,10 @@ const test = baseTest.extend<BrowserTestTestFixtures, BrowserTestWorkerFixtures>
|
|||
await run(false);
|
||||
}, { scope: 'worker' }],
|
||||
|
||||
defaultSameSiteCookieValue: [async ({ browserName, platform, macVersion }, run) => {
|
||||
defaultSameSiteCookieValue: [async ({ browserName, platform, channel }, run) => {
|
||||
if (browserName === 'chromium' || browserName as any === '_bidiChromium' || browserName as any === '_bidiFirefox')
|
||||
await run('Lax');
|
||||
else if (browserName === 'webkit' && platform === 'linux')
|
||||
else if (browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl'))
|
||||
await run('Lax');
|
||||
else if (browserName === 'webkit')
|
||||
await run('None'); // Windows + older macOS
|
||||
|
|
|
|||
|
|
@ -53,13 +53,13 @@ export async function verifyViewport(page: Page, width: number, height: number)
|
|||
expect(await page.evaluate('window.innerHeight')).toBe(height);
|
||||
}
|
||||
|
||||
export function expectedSSLError(browserName: string, platform: string): RegExp {
|
||||
export function expectedSSLError(browserName: string, platform: string, channel: string): RegExp {
|
||||
if (browserName === 'chromium')
|
||||
return /net::(ERR_CERT_AUTHORITY_INVALID|ERR_CERT_INVALID)/;
|
||||
if (browserName === 'webkit') {
|
||||
if (platform === 'darwin')
|
||||
return /The certificate for this server is invalid/;
|
||||
else if (platform === 'win32')
|
||||
else if (platform === 'win32' && channel !== 'webkit-wsl')
|
||||
return /SSL peer certificate or SSH remote key was not OK/;
|
||||
else
|
||||
return /Unacceptable TLS certificate|Operation was cancelled/;
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ it('should add cookies with empty value', async ({ context, page, server }) => {
|
|||
expect(await page.evaluate(() => document.cookie)).toEqual('marker=');
|
||||
});
|
||||
|
||||
it('should set cookies with SameSite attribute and no secure attribute', async ({ context, browserName, isWindows, isLinux, defaultSameSiteCookieValue }) => {
|
||||
it('should set cookies with SameSite attribute and no secure attribute', async ({ context, browserName, isWindows, isLinux, defaultSameSiteCookieValue, channel }) => {
|
||||
// Use domain instead of URL to ensure that the `secure` attribute is not set.
|
||||
await context.addCookies([{
|
||||
domain: 'foo.com',
|
||||
|
|
@ -101,7 +101,7 @@ it('should set cookies with SameSite attribute and no secure attribute', async (
|
|||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: defaultSameSiteCookieValue,
|
||||
}, ...(browserName === 'chromium' || (browserName === 'webkit' && isLinux) ? [] : [{
|
||||
}, ...(browserName === 'chromium' || (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl')) ? [] : [{
|
||||
name: 'same-site-none',
|
||||
value: '1',
|
||||
domain: 'foo.com',
|
||||
|
|
@ -118,7 +118,7 @@ it('should set cookies with SameSite attribute and no secure attribute', async (
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}, {
|
||||
name: 'same-site-strict',
|
||||
value: '1',
|
||||
|
|
@ -127,7 +127,7 @@ it('should set cookies with SameSite attribute and no secure attribute', async (
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Strict',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Strict',
|
||||
}]));
|
||||
});
|
||||
|
||||
|
|
@ -309,7 +309,7 @@ it('should set cookie with reasonable defaults', async ({ context, server, defau
|
|||
}]);
|
||||
});
|
||||
|
||||
it('should set a cookie with a path', async ({ context, page, server, browserName, isWindows }) => {
|
||||
it('should set a cookie with a path', async ({ context, page, server, browserName, isWindows, channel }) => {
|
||||
await page.goto(server.PREFIX + '/grid.html');
|
||||
await context.addCookies([{
|
||||
domain: server.HOSTNAME,
|
||||
|
|
@ -326,7 +326,7 @@ it('should set a cookie with a path', async ({ context, page, server, browserNam
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]);
|
||||
expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID');
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
|
|
@ -384,7 +384,7 @@ it('should be able to set unsecure cookie for HTTP website', async ({ context, p
|
|||
expect(cookie.secure).toBe(false);
|
||||
});
|
||||
|
||||
it('should set a cookie on a different domain', async ({ context, page, server, browserName, isWindows }) => {
|
||||
it('should set a cookie on a different domain', async ({ context, page, server, browserName, isWindows, channel }) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await context.addCookies([{
|
||||
url: 'https://www.example.com',
|
||||
|
|
@ -401,7 +401,7 @@ it('should set a cookie on a different domain', async ({ context, page, server,
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ function expectPartitionKey(cookies: Cookie[], name: string, partitionKey: strin
|
|||
throw new Error(`Cookie ${name} has partitionKey ${cookie.partitionKey} but expected ${partitionKey}.`);
|
||||
}
|
||||
|
||||
async function runNonPartitionedTest(page: Page, httpsServer: TestServer, browserName: string, isMac: boolean, isLinux: boolean, urls: TestUrls) {
|
||||
async function runNonPartitionedTest(page: Page, httpsServer: TestServer, browserName: string, isMac: boolean, isLinux: boolean, channel: string, urls: TestUrls) {
|
||||
addCommonCookieHandlers(httpsServer, urls);
|
||||
httpsServer.setRoute('/set-cookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', `${req.headers.referer ? 'frame' : 'top-level'}=value; SameSite=None; Path=/; Secure;`);
|
||||
|
|
@ -136,14 +136,14 @@ async function runNonPartitionedTest(page: Page, httpsServer: TestServer, browse
|
|||
await page.goto(urls.set_origin2_origin1);
|
||||
await page.goto(urls.read_origin2_origin1);
|
||||
const expectedThirdParty = browserName === 'webkit' && isMac ?
|
||||
'Received cookie: undefined' : browserName === 'webkit' && isLinux ?
|
||||
'Received cookie: undefined' : browserName === 'webkit' && (isLinux || channel === 'webkit-wsl') ?
|
||||
'Received cookie: top-level=value' :
|
||||
'Received cookie: frame=value; top-level=value';
|
||||
await expect(frameBody).toHaveText(expectedThirdParty, { timeout: 1000 });
|
||||
|
||||
// Check again the top-level cookie.
|
||||
await page.goto(urls.read_origin1);
|
||||
const expectedTopLevel = browserName === 'webkit' && (isMac || isLinux) ?
|
||||
const expectedTopLevel = browserName === 'webkit' && (isMac || isLinux || channel === 'webkit-wsl') ?
|
||||
'Received cookie: top-level=value' :
|
||||
'Received cookie: frame=value; top-level=value';
|
||||
expect(await page.locator('body').textContent()).toBe(expectedTopLevel);
|
||||
|
|
@ -154,13 +154,13 @@ async function runNonPartitionedTest(page: Page, httpsServer: TestServer, browse
|
|||
};
|
||||
}
|
||||
|
||||
test(`third party non-partitioned cookies`, async ({ page, browserName, httpsServer, isMac, isLinux, urls }) => {
|
||||
await runNonPartitionedTest(page, httpsServer, browserName, isMac, isLinux, urls);
|
||||
test(`third party non-partitioned cookies`, async ({ page, browserName, httpsServer, isMac, isLinux, urls, channel }) => {
|
||||
await runNonPartitionedTest(page, httpsServer, browserName, isMac, isLinux, channel, urls);
|
||||
});
|
||||
|
||||
test(`save/load third party non-partitioned cookies`, async ({ page, browserName, httpsServer, isMac, isLinux, browser, urls }) => {
|
||||
test(`save/load third party non-partitioned cookies`, async ({ page, browserName, httpsServer, isMac, isLinux, browser, urls, channel }) => {
|
||||
// Run the test to populate the cookies.
|
||||
const { expectedTopLevel, expectedThirdParty } = await runNonPartitionedTest(page, httpsServer, browserName, isMac, isLinux, urls);
|
||||
const { expectedTopLevel, expectedThirdParty } = await runNonPartitionedTest(page, httpsServer, browserName, isMac, isLinux, channel, urls);
|
||||
|
||||
async function checkCookies(page: Page) {
|
||||
// Check top-level cookie first.
|
||||
|
|
|
|||
|
|
@ -83,8 +83,8 @@ it('should properly report httpOnly cookie', async ({ context, page, server }) =
|
|||
expect(cookies[0].httpOnly).toBe(true);
|
||||
});
|
||||
|
||||
it('should properly report "Strict" sameSite cookie', async ({ context, page, server, browserName, platform }) => {
|
||||
it.fail(browserName === 'webkit' && platform === 'win32');
|
||||
it('should properly report "Strict" sameSite cookie', async ({ context, page, server, browserName, platform, channel }) => {
|
||||
it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl');
|
||||
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', 'name=value;SameSite=Strict');
|
||||
|
|
@ -96,8 +96,8 @@ it('should properly report "Strict" sameSite cookie', async ({ context, page, se
|
|||
expect(cookies[0].sameSite).toBe('Strict');
|
||||
});
|
||||
|
||||
it('should properly report "Lax" sameSite cookie', async ({ context, page, server, browserName, platform }) => {
|
||||
it.fail(browserName === 'webkit' && platform === 'win32');
|
||||
it('should properly report "Lax" sameSite cookie', async ({ context, page, server, browserName, platform, channel }) => {
|
||||
it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl');
|
||||
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', 'name=value;SameSite=Lax');
|
||||
|
|
@ -142,7 +142,7 @@ it('should get multiple cookies', async ({ context, page, server, defaultSameSit
|
|||
]));
|
||||
});
|
||||
|
||||
it('should get cookies from multiple urls', async ({ context, browserName, isWindows }) => {
|
||||
it('should get cookies from multiple urls', async ({ context, browserName, isWindows, channel }) => {
|
||||
await context.addCookies([{
|
||||
url: 'https://foo.com',
|
||||
name: 'doggo',
|
||||
|
|
@ -168,7 +168,7 @@ it('should get cookies from multiple urls', async ({ context, browserName, isWin
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}, {
|
||||
name: 'doggo',
|
||||
value: 'woofs',
|
||||
|
|
@ -181,7 +181,7 @@ it('should get cookies from multiple urls', async ({ context, browserName, isWin
|
|||
}]));
|
||||
});
|
||||
|
||||
it('should work with subdomain cookie', async ({ context, browserName, isWindows }) => {
|
||||
it('should work with subdomain cookie', async ({ context, browserName, isWindows, channel }) => {
|
||||
await context.addCookies([{
|
||||
domain: '.foo.com',
|
||||
path: '/',
|
||||
|
|
@ -198,7 +198,7 @@ it('should work with subdomain cookie', async ({ context, browserName, isWindows
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]);
|
||||
expect(await context.cookies('https://sub.foo.com')).toEqual([{
|
||||
name: 'doggo',
|
||||
|
|
@ -208,7 +208,7 @@ it('should work with subdomain cookie', async ({ context, browserName, isWindows
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]);
|
||||
});
|
||||
|
||||
|
|
@ -227,7 +227,7 @@ it('should return cookies with empty value', async ({ context, page, server }) =
|
|||
]);
|
||||
});
|
||||
|
||||
it('should return secure cookies based on HTTP(S) protocol', async ({ context, browserName, isWindows }) => {
|
||||
it('should return secure cookies based on HTTP(S) protocol', async ({ context, browserName, isWindows, channel }) => {
|
||||
await context.addCookies([{
|
||||
url: 'https://foo.com',
|
||||
name: 'doggo',
|
||||
|
|
@ -250,7 +250,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}, {
|
||||
name: 'doggo',
|
||||
value: 'woofs',
|
||||
|
|
@ -259,7 +259,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: true,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]));
|
||||
expect(await context.cookies('http://foo.com/')).toEqual([{
|
||||
name: 'catto',
|
||||
|
|
@ -269,7 +269,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]);
|
||||
});
|
||||
|
||||
|
|
@ -346,7 +346,7 @@ it('should support requestStorageAccess', async ({ page, server, channel, browse
|
|||
expect(serverRequest.headers.cookie).toBe('name=value');
|
||||
}
|
||||
} else {
|
||||
if (isLinux && browserName === 'webkit')
|
||||
if (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl'))
|
||||
expect(await frame.evaluate(() => document.hasStorageAccess())).toBeTruthy();
|
||||
else
|
||||
expect(await frame.evaluate(() => document.hasStorageAccess())).toBeFalsy();
|
||||
|
|
@ -355,7 +355,7 @@ it('should support requestStorageAccess', async ({ page, server, channel, browse
|
|||
server.waitForRequest('/title.html'),
|
||||
frame.evaluate(() => fetch('/title.html'))
|
||||
]);
|
||||
if (isWindows && browserName === 'webkit')
|
||||
if (isWindows && browserName === 'webkit' && channel !== 'webkit-wsl')
|
||||
expect(serverRequest.headers.cookie).toBe('name=value');
|
||||
else
|
||||
expect(serverRequest.headers.cookie).toBeFalsy();
|
||||
|
|
@ -367,7 +367,7 @@ it('should support requestStorageAccess', async ({ page, server, channel, browse
|
|||
server.waitForRequest('/title.html'),
|
||||
frame.evaluate(() => fetch('/title.html'))
|
||||
]);
|
||||
if (isLinux && browserName === 'webkit')
|
||||
if (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl'))
|
||||
expect(serverRequest.headers.cookie).toBe(undefined);
|
||||
else
|
||||
expect(serverRequest.headers.cookie).toBe('name=value');
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ it('should remove cookie with expires far in the past', async ({ page, server })
|
|||
expect(serverRequest.headers.cookie).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should handle cookies on redirects', async ({ context, server, browserName, isWindows }) => {
|
||||
it('should handle cookies on redirects', async ({ context, server, browserName, isWindows, channel }) => {
|
||||
server.setRoute('/redirect1', (req, res) => {
|
||||
res.setHeader('Set-Cookie', 'r1=v1;SameSite=Lax');
|
||||
res.writeHead(301, { location: '/a/b/redirect2' });
|
||||
|
|
@ -436,7 +436,7 @@ it('should handle cookies on redirects', async ({ context, server, browserName,
|
|||
const cookies = await context.cookies();
|
||||
expect(new Set(cookies)).toEqual(new Set([
|
||||
{
|
||||
'sameSite': (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
'sameSite': (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
'name': 'r2',
|
||||
'value': 'v2',
|
||||
'domain': server.HOSTNAME,
|
||||
|
|
@ -446,7 +446,7 @@ it('should handle cookies on redirects', async ({ context, server, browserName,
|
|||
'secure': false
|
||||
},
|
||||
{
|
||||
'sameSite': (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
'sameSite': (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
'name': 'r1',
|
||||
'value': 'v1',
|
||||
'domain': server.HOSTNAME,
|
||||
|
|
@ -1201,7 +1201,8 @@ it('context request should export same storage state as context', async ({ conte
|
|||
expect(pageState).toEqual(contextState);
|
||||
});
|
||||
|
||||
it('should send secure cookie over http for localhost', async ({ page, server }) => {
|
||||
it('should send secure cookie over http for localhost', async ({ page, server, channel }) => {
|
||||
it.skip(channel === 'webkit-wsl');
|
||||
server.setRoute('/setcookie.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', ['a=v; secure']);
|
||||
res.end();
|
||||
|
|
@ -1277,7 +1278,7 @@ it('should work with connectOverCDP', async ({ browserName, browserType, server
|
|||
}
|
||||
});
|
||||
|
||||
it('should support SameSite cookie attribute over https', async ({ contextFactory, httpsServer, browserName, isWindows }) => {
|
||||
it('should support SameSite cookie attribute over https', async ({ contextFactory, httpsServer, browserName, isWindows, channel }) => {
|
||||
// Cookies with SameSite=None must also specify the Secure attribute. WebKit navigation
|
||||
// to HTTP url will fail if the response contains a cookie with Secure attribute, so
|
||||
// we do HTTPS navigation.
|
||||
|
|
@ -1291,7 +1292,7 @@ it('should support SameSite cookie attribute over https', async ({ contextFactor
|
|||
});
|
||||
await page.request.get(httpsServer.EMPTY_PAGE);
|
||||
const [cookie] = await page.context().cookies();
|
||||
if (browserName === 'webkit' && isWindows)
|
||||
if (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl')
|
||||
expect(cookie.sameSite).toBe('None');
|
||||
else
|
||||
expect(cookie.sameSite).toBe(value);
|
||||
|
|
@ -1301,7 +1302,7 @@ it('should support SameSite cookie attribute over https', async ({ contextFactor
|
|||
|
||||
it('should set domain=localhost cookie', async ({ context, server, browserName, isWindows }) => {
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.setHeader('Set-Cookie', `name=val; Domain=localhost; Path=/;`);
|
||||
res.setHeader('Set-Cookie', `name=val; Domain=${server.HOSTNAME}; Path=/;`);
|
||||
res.end();
|
||||
});
|
||||
await context.request.get(server.EMPTY_PAGE);
|
||||
|
|
@ -1322,7 +1323,7 @@ it('fetch should not throw on long set-cookie value', async ({ context, server }
|
|||
expect(cookies.map(c => c.name)).toContain('bar');
|
||||
});
|
||||
|
||||
it('should support set-cookie with SameSite and without Secure attribute over HTTP', async ({ page, server, browserName, isWindows, isLinux }) => {
|
||||
it('should support set-cookie with SameSite and without Secure attribute over HTTP', async ({ page, server, browserName, isWindows, isLinux, channel }) => {
|
||||
for (const value of ['None', 'Lax', 'Strict']) {
|
||||
await it.step(`SameSite=${value}`, async () => {
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
|
|
@ -1333,9 +1334,9 @@ it('should support set-cookie with SameSite and without Secure attribute over HT
|
|||
const [cookie] = await page.context().cookies();
|
||||
if (browserName === 'chromium' && value === 'None')
|
||||
expect(cookie).toBeFalsy();
|
||||
else if (browserName === 'webkit' && isLinux && value === 'None')
|
||||
else if (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl') && value === 'None')
|
||||
expect(cookie).toBeFalsy();
|
||||
else if (browserName === 'webkit' && isWindows)
|
||||
else if (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl')
|
||||
expect(cookie.sameSite).toBe('None');
|
||||
else
|
||||
expect(cookie.sameSite).toBe(value);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ it('should use proxy', async ({ contextFactory, server, proxyServer }) => {
|
|||
await context.close();
|
||||
});
|
||||
|
||||
it('should send secure cookies to subdomain.localhost', async ({ contextFactory, browserName, server, isWindows, proxyServer }) => {
|
||||
it('should send secure cookies to subdomain.localhost', async ({ contextFactory, browserName, server, isWindows, proxyServer, channel }) => {
|
||||
proxyServer.forwardTo(server.PORT);
|
||||
const context = await contextFactory({
|
||||
proxy: { server: proxyServer.HOST },
|
||||
|
|
@ -88,7 +88,7 @@ it('should send secure cookies to subdomain.localhost', async ({ contextFactory,
|
|||
name: 'non-secure',
|
||||
domain: 'subdomain.localhost',
|
||||
},
|
||||
...((browserName === 'webkit') && !isWindows ? [] : [{
|
||||
...((browserName === 'webkit' && (!isWindows || channel === 'webkit-wsl')) ? [] : [{
|
||||
name: 'secure',
|
||||
domain: 'subdomain.localhost',
|
||||
}]),
|
||||
|
|
@ -102,8 +102,8 @@ it('should send secure cookies to subdomain.localhost', async ({ contextFactory,
|
|||
|
||||
it('should set cookie for top-level domain', {
|
||||
annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/18362' }
|
||||
}, async ({ contextFactory, server, proxyServer, browserName, isLinux }) => {
|
||||
it.fixme(browserName === 'webkit' && isLinux);
|
||||
}, async ({ contextFactory, server, proxyServer, browserName, isLinux, channel }) => {
|
||||
it.fixme(browserName === 'webkit' && (isLinux || channel === 'webkit-wsl'));
|
||||
|
||||
proxyServer.forwardTo(server.PORT, { allowConnectRequests: true });
|
||||
const context = await contextFactory({
|
||||
|
|
@ -172,8 +172,9 @@ it.describe('should proxy local network requests', () => {
|
|||
});
|
||||
|
||||
|
||||
it('should use ipv6 proxy', async ({ contextFactory, server, proxyServer, browserName }) => {
|
||||
it('should use ipv6 proxy', async ({ contextFactory, server, proxyServer, browserName, channel }) => {
|
||||
it.fail(browserName === 'firefox', 'page.goto: NS_ERROR_UNKNOWN_HOST');
|
||||
it.fail(channel === 'webkit-wsl', 'WebKit on WSL does not support IPv6: https://github.com/microsoft/WSL/issues/10803');
|
||||
proxyServer.forwardTo(server.PORT);
|
||||
const context = await contextFactory({
|
||||
proxy: { server: `[0:0:0:0:0:0:0:1]:${proxyServer.PORT}` }
|
||||
|
|
|
|||
|
|
@ -159,8 +159,9 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
}
|
||||
});
|
||||
|
||||
test('should be able to visit ipv6', async ({ connect, startRemoteServer, ipV6ServerPort }) => {
|
||||
test('should be able to visit ipv6', async ({ connect, startRemoteServer, ipV6ServerPort, channel }) => {
|
||||
test.fail(!!process.env.INSIDE_DOCKER, 'docker does not support IPv6 by default');
|
||||
test.fail(channel === 'webkit-wsl', 'WebKit on WSL does not support IPv6: https://github.com/microsoft/WSL/issues/10803');
|
||||
const remoteServer = await startRemoteServer(kind);
|
||||
const browser = await connect(remoteServer.wsEndpoint());
|
||||
const page = await browser.newPage();
|
||||
|
|
@ -185,8 +186,9 @@ for (const kind of ['launchServer', 'run-server'] as const) {
|
|||
(browserType as any)._playwright._defaultLaunchOptions.headless = headless;
|
||||
});
|
||||
|
||||
test('should be able to visit ipv6 through localhost', async ({ connect, startRemoteServer, ipV6ServerPort }) => {
|
||||
test('should be able to visit ipv6 through localhost', async ({ connect, startRemoteServer, ipV6ServerPort, channel }) => {
|
||||
test.fail(!!process.env.INSIDE_DOCKER, 'docker does not support IPv6 by default');
|
||||
test.skip(channel === 'webkit-wsl', 'WebKit on WSL does not support IPv6: https://github.com/microsoft/WSL/issues/10803');
|
||||
const remoteServer = await startRemoteServer(kind);
|
||||
const browser = await connect(remoteServer.wsEndpoint());
|
||||
const page = await browser.newPage();
|
||||
|
|
|
|||
|
|
@ -61,12 +61,15 @@ it('should throw if page argument is passed', async ({ browserType, browserName
|
|||
expect(waitError!.message).toContain('can not specify page');
|
||||
});
|
||||
|
||||
it('should reject if launched browser fails immediately', async ({ mode, browserType, asset, isWindows }) => {
|
||||
it('should reject if launched browser fails immediately', async ({ mode, browserType, asset, isWindows, channel }) => {
|
||||
it.skip(mode.startsWith('service'));
|
||||
|
||||
let waitError: Error | undefined;
|
||||
await browserType.launch({ executablePath: asset('dummy_bad_browser_executable.js') }).catch(e => waitError = e);
|
||||
expect(waitError!.message).toContain(isWindows ? 'browserType.launch: spawn UNKNOWN' : 'Browser logs:');
|
||||
if (channel === 'webkit-wsl')
|
||||
expect(waitError!.message).toContain('Cannot specify executablePath when using the \"webkit-wsl\" channel.');
|
||||
else
|
||||
expect(waitError!.message).toContain(isWindows ? 'browserType.launch: spawn UNKNOWN' : 'Browser logs:');
|
||||
});
|
||||
|
||||
it('should reject if executable path is invalid', async ({ browserType, mode }) => {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ it('context.addCookies() should work', async ({ server, launchPersistent, browse
|
|||
expires: -1,
|
||||
httpOnly: false,
|
||||
secure: false,
|
||||
sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax',
|
||||
sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax',
|
||||
}]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ it('should have passed URL when launching with ignoreDefaultArgs: true', async (
|
|||
it.skip(mode !== 'default');
|
||||
|
||||
const userDataDir = await createUserDataDir();
|
||||
const args = toImpl(browserType).defaultArgs((browserType as any)._playwright._defaultLaunchOptions, 'persistent', userDataDir, 0).filter(a => a !== 'about:blank');
|
||||
const args = (await toImpl(browserType).defaultArgs((browserType as any)._playwright._defaultLaunchOptions, 'persistent', userDataDir, 0)).filter(a => a !== 'about:blank');
|
||||
const options = {
|
||||
args: browserName === 'firefox' ? [...args, '-new-tab', server.EMPTY_PAGE] : [...args, server.EMPTY_PAGE],
|
||||
ignoreDefaultArgs: true,
|
||||
|
|
|
|||
|
|
@ -420,8 +420,8 @@ it.describe('download event', () => {
|
|||
]).toContain(saveError.message);
|
||||
});
|
||||
|
||||
it('should close the context without awaiting the download', async ({ browser, server, browserName, platform }, testInfo) => {
|
||||
it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers');
|
||||
it('should close the context without awaiting the download', async ({ browser, server, browserName, platform, channel }, testInfo) => {
|
||||
it.skip(browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl'), 'WebKit on linux does not convert to the download immediately upon receiving headers');
|
||||
|
||||
server.setRoute('/downloadStall', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
|
|
@ -455,8 +455,8 @@ it.describe('download event', () => {
|
|||
]).toContain(saveError.message);
|
||||
});
|
||||
|
||||
it('should throw if browser dies', async ({ server, browserType, browserName, platform }, testInfo) => {
|
||||
it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers');
|
||||
it('should throw if browser dies', async ({ server, browserType, browserName, platform, channel }, testInfo) => {
|
||||
it.skip(browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl'), 'WebKit on linux does not convert to the download immediately upon receiving headers');
|
||||
server.setRoute('/downloadStall', (req, res) => {
|
||||
res.setHeader('Content-Type', 'application/octet-stream');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=file.txt');
|
||||
|
|
|
|||
|
|
@ -660,8 +660,8 @@ it('should return server address directly from response', async ({ page, server,
|
|||
}
|
||||
});
|
||||
|
||||
it('should return security details directly from response', async ({ contextFactory, httpsServer, browserName, platform }) => {
|
||||
it.fail(browserName === 'webkit' && platform === 'linux', 'https://github.com/microsoft/playwright/issues/6759');
|
||||
it('should return security details directly from response', async ({ contextFactory, httpsServer, browserName, platform, channel }) => {
|
||||
it.fail(browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl'), 'https://github.com/microsoft/playwright/issues/6759');
|
||||
|
||||
const context = await contextFactory({ ignoreHTTPSErrors: true });
|
||||
const page = await context.newPage();
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function normalizeCode(code: string): string {
|
|||
return code.replace(/\s+/g, ' ').trim();
|
||||
}
|
||||
|
||||
test('should click', async ({ context, browserName, platform }) => {
|
||||
test('should click', async ({ context, browserName, platform, channel }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button onclick="console.log('click')">Submit</button>`);
|
||||
|
|
@ -60,7 +60,7 @@ test('should click', async ({ context, browserName, platform }) => {
|
|||
selector: 'internal:role=button[name="Submit"i]',
|
||||
ref: 'e2',
|
||||
// Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || platform === 'win32')) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || (platform === 'win32' && channel !== 'webkit-wsl'))) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
|
|
@ -69,7 +69,7 @@ test('should click', async ({ context, browserName, platform }) => {
|
|||
expect(normalizeCode(clickActions[0].code)).toEqual(`await page.getByRole('button', { name: 'Submit' }).click();`);
|
||||
});
|
||||
|
||||
test('should double click', async ({ context, browserName, platform }) => {
|
||||
test('should double click', async ({ context, browserName, platform, channel }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button onclick="console.log('click')" ondblclick="console.log('dblclick')">Submit</button>`);
|
||||
|
|
@ -84,7 +84,7 @@ test('should double click', async ({ context, browserName, platform }) => {
|
|||
selector: 'internal:role=button[name="Submit"i]',
|
||||
ref: 'e2',
|
||||
// Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || platform === 'win32')) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || (platform === 'win32' && channel !== 'webkit-wsl'))) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
|
|
@ -93,7 +93,7 @@ test('should double click', async ({ context, browserName, platform }) => {
|
|||
expect(normalizeCode(clickActions[0].code)).toEqual(`await page.getByRole('button', { name: 'Submit' }).dblclick();`);
|
||||
});
|
||||
|
||||
test('should right click', async ({ context, browserName, platform }) => {
|
||||
test('should right click', async ({ context, browserName, platform, channel }) => {
|
||||
const log = await startRecording(context);
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<button oncontextmenu="console.log('contextmenu')">Submit</button>`);
|
||||
|
|
@ -108,7 +108,7 @@ test('should right click', async ({ context, browserName, platform }) => {
|
|||
selector: 'internal:role=button[name="Submit"i]',
|
||||
ref: 'e2',
|
||||
// Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || platform === 'win32')) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
ariaSnapshot: (browserName === 'webkit' && (platform === 'darwin' || (platform === 'win32' && channel !== 'webkit-wsl'))) ? '- button "Submit" [ref=e2]' : '- button "Submit" [active] [ref=e2]',
|
||||
}),
|
||||
startTime: expect.any(Number),
|
||||
})
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ async function checkFeatures(name: string, context: BrowserContext, server: Test
|
|||
}
|
||||
}
|
||||
|
||||
it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headless }) => {
|
||||
it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headless, channel }) => {
|
||||
it.skip(browserName !== 'webkit');
|
||||
it.skip(browserName === 'webkit' && platform === 'darwin' && os.arch() === 'x64', 'Modernizr uses WebGL which is not available on Intel macOS - https://bugs.webkit.org/show_bug.cgi?id=278277');
|
||||
it.skip(browserName === 'webkit' && hostPlatform.startsWith('ubuntu20.04'), 'Ubuntu 20.04 is frozen');
|
||||
|
|
@ -56,7 +56,7 @@ it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headl
|
|||
expected.video = !!expected.video;
|
||||
actual.video = !!actual.video;
|
||||
|
||||
if (platform === 'linux') {
|
||||
if (platform === 'linux' || channel === 'webkit-wsl') {
|
||||
expected.speechrecognition = false;
|
||||
expected.mediastream = false;
|
||||
if (headless)
|
||||
|
|
@ -67,7 +67,7 @@ it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headl
|
|||
delete expected.variablefonts;
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
if (platform === 'win32' && channel !== 'webkit-wsl') {
|
||||
expected.getusermedia = false;
|
||||
expected.peerconnection = false;
|
||||
expected.speechrecognition = false;
|
||||
|
|
@ -91,7 +91,7 @@ it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headl
|
|||
expect(actual).toEqual(expected);
|
||||
});
|
||||
|
||||
it('Mobile Safari', async ({ playwright, browser, browserName, platform, httpsServer, headless }) => {
|
||||
it('Mobile Safari', async ({ playwright, browser, browserName, platform, httpsServer, headless, channel }) => {
|
||||
it.skip(browserName !== 'webkit');
|
||||
it.skip(browserName === 'webkit' && platform === 'darwin' && os.arch() === 'x64', 'Modernizr uses WebGL which is not available on Intel macOS - https://bugs.webkit.org/show_bug.cgi?id=278277');
|
||||
it.skip(browserName === 'webkit' && hostPlatform.startsWith('ubuntu20.04'), 'Ubuntu 20.04 is frozen');
|
||||
|
|
@ -120,7 +120,7 @@ it('Mobile Safari', async ({ playwright, browser, browserName, platform, httpsSe
|
|||
actual.video = !!actual.video;
|
||||
}
|
||||
|
||||
if (platform === 'linux') {
|
||||
if (platform === 'linux' || channel === 'webkit-wsl') {
|
||||
expected.speechrecognition = false;
|
||||
expected.mediastream = false;
|
||||
if (headless)
|
||||
|
|
@ -131,7 +131,7 @@ it('Mobile Safari', async ({ playwright, browser, browserName, platform, httpsSe
|
|||
delete expected.variablefonts;
|
||||
}
|
||||
|
||||
if (platform === 'win32') {
|
||||
if (platform === 'win32' && channel !== 'webkit-wsl') {
|
||||
expected.getusermedia = false;
|
||||
expected.peerconnection = false;
|
||||
expected.speechrecognition = false;
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ const test = testBase.extend<{ crash: () => void }, { dummy: string }>({
|
|||
dummy: ['', { scope: 'worker' }],
|
||||
});
|
||||
|
||||
test.beforeEach(({ platform, browserName }) => {
|
||||
test.slow(platform === 'linux' && browserName === 'webkit', 'WebKit/Linux tests are consistently slower on some Linux environments. Most likely WebContent process is not getting terminated properly and is causing the slowdown.');
|
||||
test.beforeEach(({ platform, browserName, channel }) => {
|
||||
test.slow(platform === 'linux' && (browserName === 'webkit'), 'WebKit/Linux tests are consistently slower on some Linux environments. Most likely WebContent process is not getting terminated properly and is causing the slowdown.');
|
||||
test.fixme(channel === 'webkit-wsl', 'WebKit on WSL is even slower than above ^^ - skipping for now');
|
||||
});
|
||||
|
||||
test('should emit crash event when page crashes', async ({ page, crash }) => {
|
||||
|
|
|
|||
|
|
@ -323,7 +323,7 @@ it('should use SOCKS proxy for websocket requests', async ({ browserType, server
|
|||
await closeProxyServer();
|
||||
});
|
||||
|
||||
it('should use http proxy for websocket requests', async ({ browserName, browserType, server, proxyServer, isWindows, isMac, macVersion }) => {
|
||||
it('should use http proxy for websocket requests', async ({ browserName, browserType, server, proxyServer, isWindows, isMac, macVersion, channel }) => {
|
||||
it.skip(isMac && macVersion === 13, 'Times out on Mac 13');
|
||||
|
||||
proxyServer.forwardTo(server.PORT, { allowConnectRequests: true });
|
||||
|
|
@ -352,7 +352,7 @@ it('should use http proxy for websocket requests', async ({ browserName, browser
|
|||
|
||||
// WebKit does not use CONNECT for websockets, but other browsers do.
|
||||
if (browserName === 'webkit')
|
||||
expect(proxyServer.wsUrls).toContain(isWindows ? '/ws' : 'ws://fake-localhost-127-0-0-1.nip.io:1337/ws');
|
||||
expect(proxyServer.wsUrls).toContain((isWindows && !channel) ? '/ws' : 'ws://fake-localhost-127-0-0-1.nip.io:1337/ws');
|
||||
else
|
||||
expect(proxyServer.connectHosts).toContain('fake-localhost-127-0-0-1.nip.io:1337');
|
||||
|
||||
|
|
|
|||
|
|
@ -1463,7 +1463,8 @@ test('should remove noscript when javaScriptEnabled is set to true', async ({ br
|
|||
await expect(frame.getByText('Enable JavaScript to run this app.')).toBeHidden();
|
||||
});
|
||||
|
||||
test('should open snapshot in new browser context', async ({ browser, page, runAndTrace, server }) => {
|
||||
test('should open snapshot in new browser context', async ({ browser, page, runAndTrace, server, channel }) => {
|
||||
test.skip(channel === 'webkit-wsl', 'Trace Viewer opens via ipv6 address which is not supported in WSL');
|
||||
const traceViewer = await runAndTrace(async () => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.setContent('hello');
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ it('Page.Events.Response @smoke', async ({ page, server }) => {
|
|||
expect(responses[0].request()).toBeTruthy();
|
||||
});
|
||||
|
||||
it('Page.Events.RequestFailed @smoke', async ({ page, server, browserName, platform }) => {
|
||||
it('Page.Events.RequestFailed @smoke', async ({ page, server, browserName, platform, channel }) => {
|
||||
server.setRoute('/one-style.css', (req, res) => {
|
||||
res.setHeader('Content-Type', 'text/css');
|
||||
res.connection.destroy();
|
||||
|
|
@ -57,7 +57,7 @@ it('Page.Events.RequestFailed @smoke', async ({ page, server, browserName, platf
|
|||
if (browserName === 'chromium' || browserName === '_bidiChromium') {
|
||||
expect(failedRequests[0].failure().errorText).toBe('net::ERR_EMPTY_RESPONSE');
|
||||
} else if (browserName === 'webkit') {
|
||||
if (platform === 'linux')
|
||||
if (platform === 'linux' || channel === 'webkit-wsl')
|
||||
expect(failedRequests[0].failure().errorText).toMatch(/(Message Corrupt)|(Connection terminated unexpectedly)/i);
|
||||
else if (platform === 'darwin')
|
||||
expect(failedRequests[0].failure().errorText).toBe('The network connection was lost.');
|
||||
|
|
|
|||
|
|
@ -24,9 +24,10 @@ it('should work @smoke', async ({ page, server }) => {
|
|||
expect(page.url()).toBe(server.EMPTY_PAGE);
|
||||
});
|
||||
|
||||
it('should work with file URL', async ({ page, asset, isAndroid, mode }) => {
|
||||
it('should work with file URL', async ({ page, asset, isAndroid, mode, channel }) => {
|
||||
it.skip(isAndroid, 'No files on Android');
|
||||
it.skip(mode.startsWith('service'));
|
||||
it.skip(channel === 'webkit-wsl', 'separate filesystem on wsl');
|
||||
|
||||
const fileurl = url.pathToFileURL(asset('empty.html')).href;
|
||||
await page.goto(fileurl);
|
||||
|
|
@ -34,9 +35,10 @@ it('should work with file URL', async ({ page, asset, isAndroid, mode }) => {
|
|||
expect(page.frames().length).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with file URL with subframes', async ({ page, asset, isAndroid, mode }) => {
|
||||
it('should work with file URL with subframes', async ({ page, asset, isAndroid, mode, channel }) => {
|
||||
it.skip(isAndroid, 'No files on Android');
|
||||
it.skip(mode.startsWith('service'));
|
||||
it.skip(channel === 'webkit-wsl', 'separate filesystem on wsl');
|
||||
|
||||
const fileurl = url.pathToFileURL(asset('frames/two-frames.html')).href;
|
||||
await page.goto(fileurl);
|
||||
|
|
@ -301,7 +303,7 @@ it('should fail when navigating to bad url', async ({ mode, page, browserName })
|
|||
expect(error.message).toContain('Invalid url');
|
||||
});
|
||||
|
||||
it('should fail when navigating to bad SSL', async ({ page, browserName, httpsServer, platform }) => {
|
||||
it('should fail when navigating to bad SSL', async ({ page, browserName, httpsServer, platform, channel }) => {
|
||||
// Make sure that network events do not emit 'undefined'.
|
||||
// @see https://crbug.com/750469
|
||||
page.on('request', request => expect(request).toBeTruthy());
|
||||
|
|
@ -309,15 +311,15 @@ it('should fail when navigating to bad SSL', async ({ page, browserName, httpsSe
|
|||
page.on('requestfailed', request => expect(request).toBeTruthy());
|
||||
let error = null;
|
||||
await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e);
|
||||
expect(error.message).toMatch(expectedSSLError(browserName, platform));
|
||||
expect(error.message).toMatch(expectedSSLError(browserName, platform, channel));
|
||||
});
|
||||
|
||||
it('should fail when navigating to bad SSL after redirects', async ({ page, browserName, server, httpsServer, platform }) => {
|
||||
it('should fail when navigating to bad SSL after redirects', async ({ page, browserName, server, httpsServer, platform, channel }) => {
|
||||
server.setRedirect('/redirect/1.html', '/redirect/2.html');
|
||||
server.setRedirect('/redirect/2.html', '/empty.html');
|
||||
let error = null;
|
||||
await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e);
|
||||
expect(error.message).toMatch(expectedSSLError(browserName, platform));
|
||||
expect(error.message).toMatch(expectedSSLError(browserName, platform, channel));
|
||||
});
|
||||
|
||||
it('should not crash when navigating to bad SSL after a cross origin navigation', async ({ page, server, httpsServer }) => {
|
||||
|
|
@ -337,7 +339,7 @@ it('should throw if networkidle2 is passed as an option', async ({ page, server
|
|||
expect(error.message).toContain(`waitUntil: expected one of (load|domcontentloaded|networkidle|commit)`);
|
||||
});
|
||||
|
||||
it('should fail when main resources failed to load', async ({ page, browserName, isWindows, mode }) => {
|
||||
it('should fail when main resources failed to load', async ({ page, browserName, isWindows, mode, channel }) => {
|
||||
let error = null;
|
||||
await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e);
|
||||
if (browserName === 'chromium') {
|
||||
|
|
@ -347,7 +349,7 @@ it('should fail when main resources failed to load', async ({ page, browserName,
|
|||
expect(error.message).toContain('net::ERR_CONNECTION_REFUSED');
|
||||
} else if (browserName === 'webkit' && isWindows && mode === 'service2') {
|
||||
expect(error.message).toContain(`proxy handshake error`);
|
||||
} else if (browserName === 'webkit' && isWindows) {
|
||||
} else if (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') {
|
||||
expect(error.message).toContain(`Could not connect to server`);
|
||||
} else if (browserName === 'webkit') {
|
||||
if (mode === 'service2')
|
||||
|
|
@ -730,9 +732,9 @@ it('should work with lazy loading iframes', async ({ page, server, isAndroid })
|
|||
expect(page.frames().length).toBe(2);
|
||||
});
|
||||
|
||||
it('should report raw buffer for main resource', async ({ page, server, browserName, platform }) => {
|
||||
it('should report raw buffer for main resource', async ({ page, server, browserName, platform, channel }) => {
|
||||
it.fail(browserName === 'chromium', 'Chromium sends main resource as text');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Same here');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl', 'Same here');
|
||||
|
||||
server.setRoute('/empty.html', (req, res) => {
|
||||
res.statusCode = 200;
|
||||
|
|
|
|||
|
|
@ -52,9 +52,10 @@ it('page.goBack should work with HistoryAPI', async ({ page, server }) => {
|
|||
expect(page.url()).toBe(server.PREFIX + '/first.html');
|
||||
});
|
||||
|
||||
it('page.goBack should work for file urls', async ({ page, server, asset, browserName, platform, isAndroid, mode }) => {
|
||||
it('page.goBack should work for file urls', async ({ page, server, asset, channel, isAndroid, mode }) => {
|
||||
it.skip(isAndroid, 'No files on Android');
|
||||
it.skip(mode.startsWith('service'));
|
||||
it.skip(channel === 'webkit-wsl');
|
||||
|
||||
const url1 = url.pathToFileURL(asset('consolelog.html')).href;
|
||||
const url2 = server.PREFIX + '/consolelog.html';
|
||||
|
|
|
|||
|
|
@ -496,8 +496,8 @@ it('should support simple cut-pasting', async ({ page }) => {
|
|||
expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('123123');
|
||||
});
|
||||
|
||||
it('should support undo-redo', async ({ page, browserName, isLinux }) => {
|
||||
it.fixme(browserName === 'webkit' && isLinux, 'https://github.com/microsoft/playwright/issues/12000');
|
||||
it('should support undo-redo', async ({ page, browserName, isLinux, channel }) => {
|
||||
it.fixme(browserName === 'webkit' && isLinux || channel === 'webkit-wsl', 'https://github.com/microsoft/playwright/issues/12000');
|
||||
await page.setContent(`<div contenteditable></div>`);
|
||||
const div = page.locator('div');
|
||||
await expect(div).toHaveText('');
|
||||
|
|
|
|||
|
|
@ -88,9 +88,9 @@ it('should return headers', async ({ page, server, browserName }) => {
|
|||
expect(response.request().headers()['user-agent']).toContain('WebKit');
|
||||
});
|
||||
|
||||
it('should get the same headers as the server', async ({ page, server, browserName, platform, isElectron, browserMajorVersion }) => {
|
||||
it('should get the same headers as the server', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, channel }) => {
|
||||
it.skip(isElectron && browserMajorVersion < 99, 'This needs Chromium >= 99');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl', 'Curl does not show accept-encoding and accept-language');
|
||||
let serverRequest;
|
||||
server.setRoute('/empty.html', (request, response) => {
|
||||
serverRequest = request;
|
||||
|
|
@ -101,9 +101,9 @@ it('should get the same headers as the server', async ({ page, server, browserNa
|
|||
expect(headers).toEqual(adjustServerHeaders(serverRequest.headers, browserName));
|
||||
});
|
||||
|
||||
it('should not return allHeaders() until they are available', async ({ page, server, browserName, platform, isElectron, browserMajorVersion }) => {
|
||||
it('should not return allHeaders() until they are available', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, channel }) => {
|
||||
it.skip(isElectron && browserMajorVersion < 99, 'This needs Chromium >= 99');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl', 'Curl does not show accept-encoding and accept-language');
|
||||
|
||||
let requestHeadersPromise;
|
||||
page.on('request', request => requestHeadersPromise = request.allHeaders());
|
||||
|
|
@ -126,9 +126,9 @@ it('should not return allHeaders() until they are available', async ({ page, ser
|
|||
expect(responseHeaders['foo']).toBe('bar');
|
||||
});
|
||||
|
||||
it('should get the same headers as the server CORS', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, }) => {
|
||||
it('should get the same headers as the server CORS', async ({ page, server, browserName, platform, isElectron, browserMajorVersion, channel }) => {
|
||||
it.skip(isElectron && browserMajorVersion < 99, 'This needs Chromium >= 99');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32', 'Curl does not show accept-encoding and accept-language');
|
||||
it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl', 'Curl does not show accept-encoding and accept-language');
|
||||
|
||||
await page.goto(server.PREFIX + '/empty.html');
|
||||
let serverRequest;
|
||||
|
|
@ -392,7 +392,7 @@ it('should report raw headers', async ({ page, server, browserName, platform, is
|
|||
expectedHeaders = [];
|
||||
for (let i = 0; i < req.rawHeaders.length; i += 2)
|
||||
expectedHeaders.push({ name: req.rawHeaders[i], value: req.rawHeaders[i + 1] });
|
||||
if (browserName === 'webkit' && platform === 'win32') {
|
||||
if (browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl') {
|
||||
expectedHeaders = expectedHeaders.filter(({ name }) => name.toLowerCase() !== 'accept-encoding');
|
||||
// Convert "value": "en-US, en-US" => "en-US"
|
||||
expectedHeaders = expectedHeaders.map(e => {
|
||||
|
|
|
|||
|
|
@ -230,10 +230,11 @@ it('should behave the same way for headers and allHeaders', async ({ page, serve
|
|||
expect(allHeaders['name-b']).toEqual('v4');
|
||||
});
|
||||
|
||||
it('should provide a Response with a file URL', async ({ page, asset, isAndroid, isElectron, isWindows, browserName, mode }) => {
|
||||
it('should provide a Response with a file URL', async ({ page, asset, isAndroid, isElectron, isWindows, browserName, mode, channel }) => {
|
||||
it.skip(isAndroid, 'No files on Android');
|
||||
it.skip(browserName === 'firefox', 'Firefox does return null for file:// URLs');
|
||||
it.skip(mode.startsWith('service'));
|
||||
it.skip(channel === 'webkit-wsl');
|
||||
|
||||
const fileurl = url.pathToFileURL(asset('frames/two-frames.html')).href;
|
||||
const response = await page.goto(fileurl);
|
||||
|
|
|
|||
|
|
@ -280,14 +280,14 @@ it.describe('page screenshot', () => {
|
|||
expect(screenshot).toMatchSnapshot('screenshot-clip-odd-size.png');
|
||||
});
|
||||
|
||||
it('should work for canvas', async ({ page, server, isElectron, isMac, isLinux, macVersion, browserName, isHeadlessShell, headless }) => {
|
||||
it('should work for canvas', async ({ page, server, isElectron, isMac, isLinux, macVersion, browserName, isHeadlessShell, headless, channel }) => {
|
||||
it.fixme(isElectron && isMac, 'Fails on the bots');
|
||||
it.fixme(browserName === 'webkit' && isLinux && !headless, 'WebKit has slightly different corners on gtk4.');
|
||||
await page.setViewportSize({ width: 500, height: 500 });
|
||||
await page.goto(server.PREFIX + '/screenshots/canvas.html');
|
||||
const screenshot = await page.screenshot();
|
||||
if ((!isHeadlessShell && browserName === 'chromium' && isMac && os.arch() === 'arm64' && macVersion >= 14) ||
|
||||
(browserName === 'webkit' && isLinux && os.arch() === 'x64'))
|
||||
(browserName === 'webkit' && isLinux && os.arch() === 'x64') || channel === 'webkit-wsl')
|
||||
expect(screenshot).toMatchSnapshot('screenshot-canvas-with-accurate-corners.png');
|
||||
else
|
||||
expect(screenshot).toMatchSnapshot('screenshot-canvas.png');
|
||||
|
|
|
|||
|
|
@ -82,14 +82,14 @@ it('should work with clicking on anchor links', async ({ page, server }) => {
|
|||
expect(page.url()).toBe(server.EMPTY_PAGE + '#foobar');
|
||||
});
|
||||
|
||||
it('should work with clicking on links which do not commit navigation', async ({ page, server, httpsServer, browserName, platform }) => {
|
||||
it('should work with clicking on links which do not commit navigation', async ({ page, server, httpsServer, browserName, platform, channel }) => {
|
||||
await page.goto(server.EMPTY_PAGE);
|
||||
await page.setContent(`<a href='${httpsServer.EMPTY_PAGE}'>foobar</a>`);
|
||||
const [error] = await Promise.all([
|
||||
page.waitForNavigation().catch(e => e),
|
||||
page.click('a'),
|
||||
]);
|
||||
expect(error.message).toMatch(expectedSSLError(browserName, platform));
|
||||
expect(error.message).toMatch(expectedSSLError(browserName, platform, channel));
|
||||
});
|
||||
|
||||
it('should work with history.pushState()', async ({ page, server }) => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue