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; |     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); |     const chromeArguments = this._innerDefaultArgs(options); | ||||||
|     chromeArguments.push(`--user-data-dir=${userDataDir}`); |     chromeArguments.push(`--user-data-dir=${userDataDir}`); | ||||||
|     chromeArguments.push('--remote-debugging-port=0'); |     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 { args = [], headless } = options; | ||||||
|     const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); |     const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); | ||||||
|     if (userDataDirArg) |     if (userDataDirArg) | ||||||
|  |  | ||||||
|  | @ -174,9 +174,9 @@ export abstract class BrowserType extends SdkObject { | ||||||
|     if (ignoreAllDefaultArgs) |     if (ignoreAllDefaultArgs) | ||||||
|       browserArguments.push(...args); |       browserArguments.push(...args); | ||||||
|     else if (ignoreDefaultArgs) |     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 |     else | ||||||
|       browserArguments.push(...this.defaultArgs(options, isPersistent, userDataDir)); |       browserArguments.push(...await this.defaultArgs(options, isPersistent, userDataDir)); | ||||||
| 
 | 
 | ||||||
|     let executable: string; |     let executable: string; | ||||||
|     if (executablePath) { |     if (executablePath) { | ||||||
|  | @ -212,7 +212,7 @@ export abstract class BrowserType extends SdkObject { | ||||||
|     const { launchedProcess, gracefullyClose, kill } = await launchProcess({ |     const { launchedProcess, gracefullyClose, kill } = await launchProcess({ | ||||||
|       command: prepared.executable, |       command: prepared.executable, | ||||||
|       args: prepared.browserArguments, |       args: prepared.browserArguments, | ||||||
|       env: this.amendEnvironment(env, prepared.userDataDir, isPersistent), |       env: this.amendEnvironment(env, prepared.userDataDir, isPersistent, options), | ||||||
|       handleSIGINT, |       handleSIGINT, | ||||||
|       handleSIGTERM, |       handleSIGTERM, | ||||||
|       handleSIGHUP, |       handleSIGHUP, | ||||||
|  | @ -338,9 +338,9 @@ export abstract class BrowserType extends SdkObject { | ||||||
|     return options.channel || this._name; |     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 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 doRewriteStartupLog(error: ProtocolError): ProtocolError; | ||||||
|   abstract attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void; |   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); |     const chromeArguments = this._innerDefaultArgs(options); | ||||||
|     chromeArguments.push(`--user-data-dir=${userDataDir}`); |     chromeArguments.push(`--user-data-dir=${userDataDir}`); | ||||||
|     if (options.cdpPort !== undefined) |     if (options.cdpPort !== undefined) | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ export class Firefox extends BrowserType { | ||||||
|     transport.send(message); |     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 { args = [], headless } = options; | ||||||
|     const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); |     const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile')); | ||||||
|     if (userDataDirArg) |     if (userDataDirArg) | ||||||
|  |  | ||||||
|  | @ -510,7 +510,7 @@ const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', ' | ||||||
| 
 | 
 | ||||||
| export interface Executable { | export interface Executable { | ||||||
|   type: 'browser' | 'tool' | 'channel'; |   type: 'browser' | 'tool' | 'channel'; | ||||||
|   name: BrowserName | InternalTool | ChromiumChannel | BidiChannel; |   name: BrowserName | InternalTool | ChromiumChannel | BidiChannel | 'webkit-wsl'; | ||||||
|   browserName: BrowserName | undefined; |   browserName: BrowserName | undefined; | ||||||
|   installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none'; |   installType: 'download-by-default' | 'download-on-demand' | 'install-script' | 'none'; | ||||||
|   directory: string | undefined; |   directory: string | undefined; | ||||||
|  | @ -519,6 +519,7 @@ export interface Executable { | ||||||
|   executablePathOrDie(sdkLanguage: string): string; |   executablePathOrDie(sdkLanguage: string): string; | ||||||
|   executablePath(sdkLanguage: string): string | undefined; |   executablePath(sdkLanguage: string): string | undefined; | ||||||
|   _validateHostRequirements(sdkLanguage: string): Promise<void>; |   _validateHostRequirements(sdkLanguage: string): Promise<void>; | ||||||
|  |   wslExecutablePath?: string | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| interface ExecutableImpl extends Executable { | interface ExecutableImpl extends Executable { | ||||||
|  | @ -816,6 +817,31 @@ export class Registry { | ||||||
|       _dependencyGroup: 'webkit', |       _dependencyGroup: 'webkit', | ||||||
|       _isHermeticInstallation: true, |       _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 ffmpeg = descriptors.find(d => d.name === 'ffmpeg')!; | ||||||
|     const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg'); |     const ffmpegExecutable = findExecutablePath(ffmpeg.dir, 'ffmpeg'); | ||||||
|  |  | ||||||
|  | @ -21,6 +21,8 @@ import { kBrowserCloseMessageId } from './wkConnection'; | ||||||
| import { wrapInASCIIBox } from '../utils/ascii'; | import { wrapInASCIIBox } from '../utils/ascii'; | ||||||
| import { BrowserType, kNoXServerRunningError } from '../browserType'; | import { BrowserType, kNoXServerRunningError } from '../browserType'; | ||||||
| import { WKBrowser } from '../webkit/wkBrowser'; | import { WKBrowser } from '../webkit/wkBrowser'; | ||||||
|  | import { spawnAsync } from '../utils/spawnAsync'; | ||||||
|  | import { registry } from '../registry'; | ||||||
| 
 | 
 | ||||||
| import type { BrowserOptions } from '../browser'; | import type { BrowserOptions } from '../browser'; | ||||||
| import type { SdkObject } from '../instrumentation'; | import type { SdkObject } from '../instrumentation'; | ||||||
|  | @ -37,10 +39,11 @@ export class WebKit extends BrowserType { | ||||||
|     return WKBrowser.connect(this.attribution.playwright, transport, options); |     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 { |     return { | ||||||
|       ...env, |       ...env, | ||||||
|       CURL_COOKIE_JAR_PATH: process.platform === 'win32' && isPersistent ? path.join(userDataDir, 'cookiejar.db') : undefined, |       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 }); |     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 { args = [], headless } = options; | ||||||
|     const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); |     const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir')); | ||||||
|     if (userDataDirArg) |     if (userDataDirArg) | ||||||
|  | @ -65,12 +68,21 @@ export class WebKit extends BrowserType { | ||||||
|     if (args.find(arg => !arg.startsWith('-'))) |     if (args.find(arg => !arg.startsWith('-'))) | ||||||
|       throw new Error('Arguments can not specify page to be opened'); |       throw new Error('Arguments can not specify page to be opened'); | ||||||
|     const webkitArguments = ['--inspector-pipe']; |     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'); |       webkitArguments.push('--disable-accelerated-compositing'); | ||||||
|     if (headless) |     if (headless) | ||||||
|       webkitArguments.push('--headless'); |       webkitArguments.push('--headless'); | ||||||
|     if (isPersistent) |     if (isPersistent) | ||||||
|       webkitArguments.push(`--user-data-dir=${userDataDir}`); |       webkitArguments.push(`--user-data-dir=${options.channel === 'webkit-wsl' ? await translatePathToWSL(userDataDir) : userDataDir}`); | ||||||
|     else |     else | ||||||
|       webkitArguments.push(`--no-startup-window`); |       webkitArguments.push(`--no-startup-window`); | ||||||
|     const proxy = options.proxyOverride || options.proxy; |     const proxy = options.proxyOverride || options.proxy; | ||||||
|  | @ -79,7 +91,7 @@ export class WebKit extends BrowserType { | ||||||
|         webkitArguments.push(`--proxy=${proxy.server}`); |         webkitArguments.push(`--proxy=${proxy.server}`); | ||||||
|         if (proxy.bypass) |         if (proxy.bypass) | ||||||
|           webkitArguments.push(`--proxy-bypass-list=${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}`); |         webkitArguments.push(`--proxy=${proxy.server}`); | ||||||
|         if (proxy.bypass) |         if (proxy.bypass) | ||||||
|           webkitArguments.push(...proxy.bypass.split(',').map(t => `--ignore-host=${t}`)); |           webkitArguments.push(...proxy.bypass.split(',').map(t => `--ignore-host=${t}`)); | ||||||
|  | @ -97,3 +109,8 @@ export class WebKit extends BrowserType { | ||||||
|     return webkitArguments; |     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 { WKConnection, WKSession, kPageProxyMessageReceived } from './wkConnection'; | ||||||
| import { WKPage } from './wkPage'; | import { WKPage } from './wkPage'; | ||||||
| import { TargetClosedError } from '../errors'; | import { TargetClosedError } from '../errors'; | ||||||
|  | import { translatePathToWSL } from './webkit'; | ||||||
| 
 | 
 | ||||||
| import type { BrowserOptions } from '../browser'; | import type { BrowserOptions } from '../browser'; | ||||||
| import type { SdkObject } from '../instrumentation'; | import type { SdkObject } from '../instrumentation'; | ||||||
|  | @ -87,7 +88,7 @@ export class WKBrowser extends Browser { | ||||||
|     const createOptions = proxy ? { |     const createOptions = proxy ? { | ||||||
|       // Enable socks5 hostname resolution on Windows.
 |       // Enable socks5 hostname resolution on Windows.
 | ||||||
|       // See https://github.com/microsoft/playwright/issues/20451
 |       // 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 |       proxyBypassList: proxy.bypass | ||||||
|     } : undefined; |     } : undefined; | ||||||
|     const { browserContextId } = await this._browserSession.send('Playwright.createContext', createOptions); |     const { browserContextId } = await this._browserSession.send('Playwright.createContext', createOptions); | ||||||
|  | @ -227,7 +228,7 @@ export class WKBrowserContext extends BrowserContext { | ||||||
|     const promises: Promise<any>[] = [super._initialize()]; |     const promises: Promise<any>[] = [super._initialize()]; | ||||||
|     promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', { |     promises.push(this._browser._browserSession.send('Playwright.setDownloadBehavior', { | ||||||
|       behavior: this._options.acceptDownloads === 'accept' ? 'allow' : 'deny', |       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 |       browserContextId | ||||||
|     })); |     })); | ||||||
|     if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors) |     if (this._options.ignoreHTTPSErrors || this._options.internalIgnoreHTTPSErrors) | ||||||
|  |  | ||||||
|  | @ -39,6 +39,7 @@ import { WKInterceptableRequest, WKRouteImpl } from './wkInterceptableRequest'; | ||||||
| import { WKProvisionalPage } from './wkProvisionalPage'; | import { WKProvisionalPage } from './wkProvisionalPage'; | ||||||
| import { WKWorkers } from './wkWorkers'; | import { WKWorkers } from './wkWorkers'; | ||||||
| import { debugLogger } from '../utils/debugLogger'; | import { debugLogger } from '../utils/debugLogger'; | ||||||
|  | import { translatePathToWSL } from './webkit'; | ||||||
| 
 | 
 | ||||||
| import type { Protocol } from './protocol'; | import type { Protocol } from './protocol'; | ||||||
| import type { WKBrowserContext } from './wkBrowser'; | import type { WKBrowserContext } from './wkBrowser'; | ||||||
|  | @ -842,7 +843,7 @@ export class WKPage implements PageDelegate { | ||||||
|   private async _startVideo(options: types.PageScreencastOptions): Promise<void> { |   private async _startVideo(options: types.PageScreencastOptions): Promise<void> { | ||||||
|     assert(!this._recordingVideoFile); |     assert(!this._recordingVideoFile); | ||||||
|     const { screencastId } = await this._pageProxySession.send('Screencast.startVideo', { |     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, |       width: options.width, | ||||||
|       height: options.height, |       height: options.height, | ||||||
|       toolbarHeight: this._toolbarHeight() |       toolbarHeight: this._toolbarHeight() | ||||||
|  | @ -976,6 +977,8 @@ export class WKPage implements PageDelegate { | ||||||
|   async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> { |   async setInputFilePaths(handle: dom.ElementHandle<HTMLInputElement>, paths: string[]): Promise<void> { | ||||||
|     const pageProxyId = this._pageProxySession.sessionId; |     const pageProxyId = this._pageProxySession.sessionId; | ||||||
|     const objectId = handle._objectId; |     const objectId = handle._objectId; | ||||||
|  |     if (this._browserContext._browser?.options.channel === 'webkit-wsl') | ||||||
|  |       paths = await Promise.all(paths.map(path => translatePathToWSL(path))); | ||||||
|     await Promise.all([ |     await Promise.all([ | ||||||
|       this._pageProxySession.connection.browserSession.send('Playwright.grantFileReadAccess', { pageProxyId, paths }), |       this._pageProxySession.connection.browserSession.send('Playwright.grantFileReadAccess', { pageProxyId, paths }), | ||||||
|       this._session.send('DOM.setInputFiles', { objectId, 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); |       await run(false); | ||||||
|   }, { scope: 'worker' }], |   }, { 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') |     if (browserName === 'chromium' || browserName as any === '_bidiChromium' || browserName as any === '_bidiFirefox') | ||||||
|       await run('Lax'); |       await run('Lax'); | ||||||
|     else if (browserName === 'webkit' && platform === 'linux') |     else if (browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl')) | ||||||
|       await run('Lax'); |       await run('Lax'); | ||||||
|     else if (browserName === 'webkit') |     else if (browserName === 'webkit') | ||||||
|       await run('None'); // Windows + older macOS
 |       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); |   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') |   if (browserName === 'chromium') | ||||||
|     return /net::(ERR_CERT_AUTHORITY_INVALID|ERR_CERT_INVALID)/; |     return /net::(ERR_CERT_AUTHORITY_INVALID|ERR_CERT_INVALID)/; | ||||||
|   if (browserName === 'webkit') { |   if (browserName === 'webkit') { | ||||||
|     if (platform === 'darwin') |     if (platform === 'darwin') | ||||||
|       return /The certificate for this server is invalid/; |       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/; |       return /SSL peer certificate or SSH remote key was not OK/; | ||||||
|     else |     else | ||||||
|       return /Unacceptable TLS certificate|Operation was cancelled/; |       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='); |   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.
 |   // Use domain instead of URL to ensure that the `secure` attribute is not set.
 | ||||||
|   await context.addCookies([{ |   await context.addCookies([{ | ||||||
|     domain: 'foo.com', |     domain: 'foo.com', | ||||||
|  | @ -101,7 +101,7 @@ it('should set cookies with SameSite attribute and no secure attribute', async ( | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: false, |     secure: false, | ||||||
|     sameSite: defaultSameSiteCookieValue, |     sameSite: defaultSameSiteCookieValue, | ||||||
|   }, ...(browserName === 'chromium' || (browserName === 'webkit' && isLinux) ? [] : [{ |   }, ...(browserName === 'chromium' || (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl')) ? [] : [{ | ||||||
|     name: 'same-site-none', |     name: 'same-site-none', | ||||||
|     value: '1', |     value: '1', | ||||||
|     domain: 'foo.com', |     domain: 'foo.com', | ||||||
|  | @ -118,7 +118,7 @@ it('should set cookies with SameSite attribute and no secure attribute', async ( | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: false, |     secure: false, | ||||||
|     sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax', |     sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax', | ||||||
|   }, { |   }, { | ||||||
|     name: 'same-site-strict', |     name: 'same-site-strict', | ||||||
|     value: '1', |     value: '1', | ||||||
|  | @ -127,7 +127,7 @@ it('should set cookies with SameSite attribute and no secure attribute', async ( | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: 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 page.goto(server.PREFIX + '/grid.html'); | ||||||
|   await context.addCookies([{ |   await context.addCookies([{ | ||||||
|     domain: server.HOSTNAME, |     domain: server.HOSTNAME, | ||||||
|  | @ -326,7 +326,7 @@ it('should set a cookie with a path', async ({ context, page, server, browserNam | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: 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'); |   expect(await page.evaluate('document.cookie')).toBe('gridcookie=GRID'); | ||||||
|   await page.goto(server.EMPTY_PAGE); |   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); |   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 page.goto(server.EMPTY_PAGE); | ||||||
|   await context.addCookies([{ |   await context.addCookies([{ | ||||||
|     url: 'https://www.example.com', |     url: 'https://www.example.com', | ||||||
|  | @ -401,7 +401,7 @@ it('should set a cookie on a different domain', async ({ context, page, server, | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: true, |     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}.`); |     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); |   addCommonCookieHandlers(httpsServer, urls); | ||||||
|   httpsServer.setRoute('/set-cookie.html', (req, res) => { |   httpsServer.setRoute('/set-cookie.html', (req, res) => { | ||||||
|     res.setHeader('Set-Cookie', `${req.headers.referer ? 'frame' : 'top-level'}=value; SameSite=None; Path=/; Secure;`); |     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.set_origin2_origin1); | ||||||
|   await page.goto(urls.read_origin2_origin1); |   await page.goto(urls.read_origin2_origin1); | ||||||
|   const expectedThirdParty = browserName === 'webkit' && isMac ? |   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: top-level=value' : | ||||||
|       'Received cookie: frame=value; top-level=value'; |       'Received cookie: frame=value; top-level=value'; | ||||||
|   await expect(frameBody).toHaveText(expectedThirdParty, { timeout: 1000 }); |   await expect(frameBody).toHaveText(expectedThirdParty, { timeout: 1000 }); | ||||||
| 
 | 
 | ||||||
|   // Check again the top-level cookie.
 |   // Check again the top-level cookie.
 | ||||||
|   await page.goto(urls.read_origin1); |   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: top-level=value' : | ||||||
|     'Received cookie: frame=value; top-level=value'; |     'Received cookie: frame=value; top-level=value'; | ||||||
|   expect(await page.locator('body').textContent()).toBe(expectedTopLevel); |   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 }) => { | test(`third party non-partitioned cookies`, async ({ page, browserName, httpsServer, isMac, isLinux, urls, channel }) => { | ||||||
|   await runNonPartitionedTest(page, httpsServer, browserName, isMac, isLinux, urls); |   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.
 |   // 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) { |   async function checkCookies(page: Page) { | ||||||
|     // Check top-level cookie first.
 |     // 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); |   expect(cookies[0].httpOnly).toBe(true); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should properly report "Strict" sameSite cookie', async ({ context, page, server, browserName, platform }) => { | it('should properly report "Strict" sameSite cookie', async ({ context, page, server, browserName, platform, channel }) => { | ||||||
|   it.fail(browserName === 'webkit' && platform === 'win32'); |   it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl'); | ||||||
| 
 | 
 | ||||||
|   server.setRoute('/empty.html', (req, res) => { |   server.setRoute('/empty.html', (req, res) => { | ||||||
|     res.setHeader('Set-Cookie', 'name=value;SameSite=Strict'); |     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'); |   expect(cookies[0].sameSite).toBe('Strict'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should properly report "Lax" sameSite cookie', async ({ context, page, server, browserName, platform }) => { | it('should properly report "Lax" sameSite cookie', async ({ context, page, server, browserName, platform, channel }) => { | ||||||
|   it.fail(browserName === 'webkit' && platform === 'win32'); |   it.fail(browserName === 'webkit' && platform === 'win32' && channel !== 'webkit-wsl'); | ||||||
| 
 | 
 | ||||||
|   server.setRoute('/empty.html', (req, res) => { |   server.setRoute('/empty.html', (req, res) => { | ||||||
|     res.setHeader('Set-Cookie', 'name=value;SameSite=Lax'); |     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([{ |   await context.addCookies([{ | ||||||
|     url: 'https://foo.com', |     url: 'https://foo.com', | ||||||
|     name: 'doggo', |     name: 'doggo', | ||||||
|  | @ -168,7 +168,7 @@ it('should get cookies from multiple urls', async ({ context, browserName, isWin | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: true, |     secure: true, | ||||||
|     sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax', |     sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax', | ||||||
|   }, { |   }, { | ||||||
|     name: 'doggo', |     name: 'doggo', | ||||||
|     value: 'woofs', |     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([{ |   await context.addCookies([{ | ||||||
|     domain: '.foo.com', |     domain: '.foo.com', | ||||||
|     path: '/', |     path: '/', | ||||||
|  | @ -198,7 +198,7 @@ it('should work with subdomain cookie', async ({ context, browserName, isWindows | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: true, |     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([{ |   expect(await context.cookies('https://sub.foo.com')).toEqual([{ | ||||||
|     name: 'doggo', |     name: 'doggo', | ||||||
|  | @ -208,7 +208,7 @@ it('should work with subdomain cookie', async ({ context, browserName, isWindows | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: true, |     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([{ |   await context.addCookies([{ | ||||||
|     url: 'https://foo.com', |     url: 'https://foo.com', | ||||||
|     name: 'doggo', |     name: 'doggo', | ||||||
|  | @ -250,7 +250,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: false, |     secure: false, | ||||||
|     sameSite: (browserName === 'webkit' && isWindows) ? 'None' : 'Lax', |     sameSite: (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax', | ||||||
|   }, { |   }, { | ||||||
|     name: 'doggo', |     name: 'doggo', | ||||||
|     value: 'woofs', |     value: 'woofs', | ||||||
|  | @ -259,7 +259,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: true, |     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([{ |   expect(await context.cookies('http://foo.com/')).toEqual([{ | ||||||
|     name: 'catto', |     name: 'catto', | ||||||
|  | @ -269,7 +269,7 @@ it('should return secure cookies based on HTTP(S) protocol', async ({ context, b | ||||||
|     expires: -1, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: 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'); |       expect(serverRequest.headers.cookie).toBe('name=value'); | ||||||
|     } |     } | ||||||
|   } else { |   } else { | ||||||
|     if (isLinux && browserName === 'webkit') |     if (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl')) | ||||||
|       expect(await frame.evaluate(() => document.hasStorageAccess())).toBeTruthy(); |       expect(await frame.evaluate(() => document.hasStorageAccess())).toBeTruthy(); | ||||||
|     else |     else | ||||||
|       expect(await frame.evaluate(() => document.hasStorageAccess())).toBeFalsy(); |       expect(await frame.evaluate(() => document.hasStorageAccess())).toBeFalsy(); | ||||||
|  | @ -355,7 +355,7 @@ it('should support requestStorageAccess', async ({ page, server, channel, browse | ||||||
|         server.waitForRequest('/title.html'), |         server.waitForRequest('/title.html'), | ||||||
|         frame.evaluate(() => fetch('/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'); |         expect(serverRequest.headers.cookie).toBe('name=value'); | ||||||
|       else |       else | ||||||
|         expect(serverRequest.headers.cookie).toBeFalsy(); |         expect(serverRequest.headers.cookie).toBeFalsy(); | ||||||
|  | @ -367,7 +367,7 @@ it('should support requestStorageAccess', async ({ page, server, channel, browse | ||||||
|         server.waitForRequest('/title.html'), |         server.waitForRequest('/title.html'), | ||||||
|         frame.evaluate(() => fetch('/title.html')) |         frame.evaluate(() => fetch('/title.html')) | ||||||
|       ]); |       ]); | ||||||
|       if (isLinux && browserName === 'webkit') |       if (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl')) | ||||||
|         expect(serverRequest.headers.cookie).toBe(undefined); |         expect(serverRequest.headers.cookie).toBe(undefined); | ||||||
|       else |       else | ||||||
|         expect(serverRequest.headers.cookie).toBe('name=value'); |         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(); |   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) => { |   server.setRoute('/redirect1', (req, res) => { | ||||||
|     res.setHeader('Set-Cookie', 'r1=v1;SameSite=Lax'); |     res.setHeader('Set-Cookie', 'r1=v1;SameSite=Lax'); | ||||||
|     res.writeHead(301, { location: '/a/b/redirect2' }); |     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(); |   const cookies = await context.cookies(); | ||||||
|   expect(new Set(cookies)).toEqual(new Set([ |   expect(new Set(cookies)).toEqual(new Set([ | ||||||
|     { |     { | ||||||
|       'sameSite': (browserName === 'webkit' && isWindows) ? 'None' : 'Lax', |       'sameSite': (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax', | ||||||
|       'name': 'r2', |       'name': 'r2', | ||||||
|       'value': 'v2', |       'value': 'v2', | ||||||
|       'domain': server.HOSTNAME, |       'domain': server.HOSTNAME, | ||||||
|  | @ -446,7 +446,7 @@ it('should handle cookies on redirects', async ({ context, server, browserName, | ||||||
|       'secure': false |       'secure': false | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|       'sameSite': (browserName === 'webkit' && isWindows) ? 'None' : 'Lax', |       'sameSite': (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') ? 'None' : 'Lax', | ||||||
|       'name': 'r1', |       'name': 'r1', | ||||||
|       'value': 'v1', |       'value': 'v1', | ||||||
|       'domain': server.HOSTNAME, |       'domain': server.HOSTNAME, | ||||||
|  | @ -1201,7 +1201,8 @@ it('context request should export same storage state as context', async ({ conte | ||||||
|   expect(pageState).toEqual(contextState); |   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) => { |   server.setRoute('/setcookie.html', (req, res) => { | ||||||
|     res.setHeader('Set-Cookie', ['a=v; secure']); |     res.setHeader('Set-Cookie', ['a=v; secure']); | ||||||
|     res.end(); |     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
 |   // 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
 |   // to HTTP url will fail if the response contains a cookie with Secure attribute, so
 | ||||||
|   // we do HTTPS navigation.
 |   // 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); |       await page.request.get(httpsServer.EMPTY_PAGE); | ||||||
|       const [cookie] = await page.context().cookies(); |       const [cookie] = await page.context().cookies(); | ||||||
|       if (browserName === 'webkit' && isWindows) |       if (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') | ||||||
|         expect(cookie.sameSite).toBe('None'); |         expect(cookie.sameSite).toBe('None'); | ||||||
|       else |       else | ||||||
|         expect(cookie.sameSite).toBe(value); |         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 }) => { | it('should set domain=localhost cookie', async ({ context, server, browserName, isWindows }) => { | ||||||
|   server.setRoute('/empty.html', (req, res) => { |   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(); |     res.end(); | ||||||
|   }); |   }); | ||||||
|   await context.request.get(server.EMPTY_PAGE); |   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'); |   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']) { |   for (const value of ['None', 'Lax', 'Strict']) { | ||||||
|     await it.step(`SameSite=${value}`, async () => { |     await it.step(`SameSite=${value}`, async () => { | ||||||
|       server.setRoute('/empty.html', (req, res) => { |       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(); |       const [cookie] = await page.context().cookies(); | ||||||
|       if (browserName === 'chromium' && value === 'None') |       if (browserName === 'chromium' && value === 'None') | ||||||
|         expect(cookie).toBeFalsy(); |         expect(cookie).toBeFalsy(); | ||||||
|       else if (browserName === 'webkit' && isLinux && value === 'None') |       else if (browserName === 'webkit' && (isLinux || channel === 'webkit-wsl') && value === 'None') | ||||||
|         expect(cookie).toBeFalsy(); |         expect(cookie).toBeFalsy(); | ||||||
|       else if (browserName === 'webkit' && isWindows) |       else if (browserName === 'webkit' && isWindows && channel !== 'webkit-wsl') | ||||||
|         expect(cookie.sameSite).toBe('None'); |         expect(cookie.sameSite).toBe('None'); | ||||||
|       else |       else | ||||||
|         expect(cookie.sameSite).toBe(value); |         expect(cookie.sameSite).toBe(value); | ||||||
|  |  | ||||||
|  | @ -64,7 +64,7 @@ it('should use proxy', async ({ contextFactory, server, proxyServer }) => { | ||||||
|   await context.close(); |   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); |   proxyServer.forwardTo(server.PORT); | ||||||
|   const context = await contextFactory({ |   const context = await contextFactory({ | ||||||
|     proxy: { server: proxyServer.HOST }, |     proxy: { server: proxyServer.HOST }, | ||||||
|  | @ -88,7 +88,7 @@ it('should send secure cookies to subdomain.localhost', async ({ contextFactory, | ||||||
|       name: 'non-secure', |       name: 'non-secure', | ||||||
|       domain: 'subdomain.localhost', |       domain: 'subdomain.localhost', | ||||||
|     }, |     }, | ||||||
|     ...((browserName === 'webkit') && !isWindows ? [] : [{ |     ...((browserName === 'webkit' && (!isWindows || channel === 'webkit-wsl')) ? [] : [{ | ||||||
|       name: 'secure', |       name: 'secure', | ||||||
|       domain: 'subdomain.localhost', |       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', { | it('should set cookie for top-level domain', { | ||||||
|   annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/18362' } |   annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/18362' } | ||||||
| }, async ({ contextFactory, server, proxyServer, browserName, isLinux }) => { | }, async ({ contextFactory, server, proxyServer, browserName, isLinux, channel }) => { | ||||||
|   it.fixme(browserName === 'webkit' && isLinux); |   it.fixme(browserName === 'webkit' && (isLinux || channel === 'webkit-wsl')); | ||||||
| 
 | 
 | ||||||
|   proxyServer.forwardTo(server.PORT, { allowConnectRequests: true }); |   proxyServer.forwardTo(server.PORT, { allowConnectRequests: true }); | ||||||
|   const context = await contextFactory({ |   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(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); |   proxyServer.forwardTo(server.PORT); | ||||||
|   const context = await contextFactory({ |   const context = await contextFactory({ | ||||||
|     proxy: { server: `[0:0:0:0:0:0:0:1]:${proxyServer.PORT}` } |     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(!!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 remoteServer = await startRemoteServer(kind); | ||||||
|       const browser = await connect(remoteServer.wsEndpoint()); |       const browser = await connect(remoteServer.wsEndpoint()); | ||||||
|       const page = await browser.newPage(); |       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; |       (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.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 remoteServer = await startRemoteServer(kind); | ||||||
|       const browser = await connect(remoteServer.wsEndpoint()); |       const browser = await connect(remoteServer.wsEndpoint()); | ||||||
|       const page = await browser.newPage(); |       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'); |   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')); |   it.skip(mode.startsWith('service')); | ||||||
| 
 | 
 | ||||||
|   let waitError: Error | undefined; |   let waitError: Error | undefined; | ||||||
|   await browserType.launch({ executablePath: asset('dummy_bad_browser_executable.js') }).catch(e => waitError = e); |   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 }) => { | 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, |     expires: -1, | ||||||
|     httpOnly: false, |     httpOnly: false, | ||||||
|     secure: 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'); |   it.skip(mode !== 'default'); | ||||||
| 
 | 
 | ||||||
|   const userDataDir = await createUserDataDir(); |   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 = { |   const options = { | ||||||
|     args: browserName === 'firefox' ? [...args, '-new-tab', server.EMPTY_PAGE] : [...args, server.EMPTY_PAGE], |     args: browserName === 'firefox' ? [...args, '-new-tab', server.EMPTY_PAGE] : [...args, server.EMPTY_PAGE], | ||||||
|     ignoreDefaultArgs: true, |     ignoreDefaultArgs: true, | ||||||
|  |  | ||||||
|  | @ -420,8 +420,8 @@ it.describe('download event', () => { | ||||||
|     ]).toContain(saveError.message); |     ]).toContain(saveError.message); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should close the context without awaiting the download', async ({ browser, server, browserName, platform }, testInfo) => { |   it('should close the context without awaiting the download', async ({ browser, server, browserName, platform, channel }, testInfo) => { | ||||||
|     it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers'); |     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) => { |     server.setRoute('/downloadStall', (req, res) => { | ||||||
|       res.setHeader('Content-Type', 'application/octet-stream'); |       res.setHeader('Content-Type', 'application/octet-stream'); | ||||||
|  | @ -455,8 +455,8 @@ it.describe('download event', () => { | ||||||
|     ]).toContain(saveError.message); |     ]).toContain(saveError.message); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should throw if browser dies', async ({ server, browserType, browserName, platform }, testInfo) => { |   it('should throw if browser dies', async ({ server, browserType, browserName, platform, channel }, testInfo) => { | ||||||
|     it.skip(browserName === 'webkit' && platform === 'linux', 'WebKit on linux does not convert to the download immediately upon receiving headers'); |     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) => { |     server.setRoute('/downloadStall', (req, res) => { | ||||||
|       res.setHeader('Content-Type', 'application/octet-stream'); |       res.setHeader('Content-Type', 'application/octet-stream'); | ||||||
|       res.setHeader('Content-Disposition', 'attachment; filename=file.txt'); |       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('should return security details directly from response', async ({ contextFactory, httpsServer, browserName, platform, channel }) => { | ||||||
|   it.fail(browserName === 'webkit' && platform === 'linux', 'https://github.com/microsoft/playwright/issues/6759'); |   it.fail(browserName === 'webkit' && (platform === 'linux' || channel === 'webkit-wsl'), 'https://github.com/microsoft/playwright/issues/6759'); | ||||||
| 
 | 
 | ||||||
|   const context = await contextFactory({ ignoreHTTPSErrors: true }); |   const context = await contextFactory({ ignoreHTTPSErrors: true }); | ||||||
|   const page = await context.newPage(); |   const page = await context.newPage(); | ||||||
|  |  | ||||||
|  | @ -46,7 +46,7 @@ function normalizeCode(code: string): string { | ||||||
|   return code.replace(/\s+/g, ' ').trim(); |   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 log = await startRecording(context); | ||||||
|   const page = await context.newPage(); |   const page = await context.newPage(); | ||||||
|   await page.setContent(`<button onclick="console.log('click')">Submit</button>`); |   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]', |         selector: 'internal:role=button[name="Submit"i]', | ||||||
|         ref: 'e2', |         ref: 'e2', | ||||||
|         // Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
 |         // 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), |       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();`); |   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 log = await startRecording(context); | ||||||
|   const page = await context.newPage(); |   const page = await context.newPage(); | ||||||
|   await page.setContent(`<button onclick="console.log('click')" ondblclick="console.log('dblclick')">Submit</button>`); |   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]', |         selector: 'internal:role=button[name="Submit"i]', | ||||||
|         ref: 'e2', |         ref: 'e2', | ||||||
|         // Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
 |         // 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), |       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();`); |   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 log = await startRecording(context); | ||||||
|   const page = await context.newPage(); |   const page = await context.newPage(); | ||||||
|   await page.setContent(`<button oncontextmenu="console.log('contextmenu')">Submit</button>`); |   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]', |         selector: 'internal:role=button[name="Submit"i]', | ||||||
|         ref: 'e2', |         ref: 'e2', | ||||||
|         // Safari does not focus after a click: https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#clicking_and_focus
 |         // 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), |       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'); | ||||||
|   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' && 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'); |   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; |   expected.video = !!expected.video; | ||||||
|   actual.video = !!actual.video; |   actual.video = !!actual.video; | ||||||
| 
 | 
 | ||||||
|   if (platform === 'linux') { |   if (platform === 'linux' || channel === 'webkit-wsl') { | ||||||
|     expected.speechrecognition = false; |     expected.speechrecognition = false; | ||||||
|     expected.mediastream = false; |     expected.mediastream = false; | ||||||
|     if (headless) |     if (headless) | ||||||
|  | @ -67,7 +67,7 @@ it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headl | ||||||
|     delete expected.variablefonts; |     delete expected.variablefonts; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (platform === 'win32') { |   if (platform === 'win32' && channel !== 'webkit-wsl') { | ||||||
|     expected.getusermedia = false; |     expected.getusermedia = false; | ||||||
|     expected.peerconnection = false; |     expected.peerconnection = false; | ||||||
|     expected.speechrecognition = false; |     expected.speechrecognition = false; | ||||||
|  | @ -91,7 +91,7 @@ it('Safari Desktop', async ({ browser, browserName, platform, httpsServer, headl | ||||||
|   expect(actual).toEqual(expected); |   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'); | ||||||
|   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' && 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'); |   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; |     actual.video = !!actual.video; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (platform === 'linux') { |   if (platform === 'linux' || channel === 'webkit-wsl') { | ||||||
|     expected.speechrecognition = false; |     expected.speechrecognition = false; | ||||||
|     expected.mediastream = false; |     expected.mediastream = false; | ||||||
|     if (headless) |     if (headless) | ||||||
|  | @ -131,7 +131,7 @@ it('Mobile Safari', async ({ playwright, browser, browserName, platform, httpsSe | ||||||
|     delete expected.variablefonts; |     delete expected.variablefonts; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   if (platform === 'win32') { |   if (platform === 'win32' && channel !== 'webkit-wsl') { | ||||||
|     expected.getusermedia = false; |     expected.getusermedia = false; | ||||||
|     expected.peerconnection = false; |     expected.peerconnection = false; | ||||||
|     expected.speechrecognition = false; |     expected.speechrecognition = false; | ||||||
|  |  | ||||||
|  | @ -32,8 +32,9 @@ const test = testBase.extend<{ crash: () => void }, { dummy: string }>({ | ||||||
|   dummy: ['', { scope: 'worker' }], |   dummy: ['', { scope: 'worker' }], | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| test.beforeEach(({ platform, browserName }) => { | 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.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 }) => { | 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(); |   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'); |   it.skip(isMac && macVersion === 13, 'Times out on Mac 13'); | ||||||
| 
 | 
 | ||||||
|   proxyServer.forwardTo(server.PORT, { allowConnectRequests: true }); |   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.
 |   // WebKit does not use CONNECT for websockets, but other browsers do.
 | ||||||
|   if (browserName === 'webkit') |   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 |   else | ||||||
|     expect(proxyServer.connectHosts).toContain('fake-localhost-127-0-0-1.nip.io:1337'); |     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(); |   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 () => { |   const traceViewer = await runAndTrace(async () => { | ||||||
|     await page.goto(server.EMPTY_PAGE); |     await page.goto(server.EMPTY_PAGE); | ||||||
|     await page.setContent('hello'); |     await page.setContent('hello'); | ||||||
|  |  | ||||||
|  | @ -42,7 +42,7 @@ it('Page.Events.Response @smoke', async ({ page, server }) => { | ||||||
|   expect(responses[0].request()).toBeTruthy(); |   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) => { |   server.setRoute('/one-style.css', (req, res) => { | ||||||
|     res.setHeader('Content-Type', 'text/css'); |     res.setHeader('Content-Type', 'text/css'); | ||||||
|     res.connection.destroy(); |     res.connection.destroy(); | ||||||
|  | @ -57,7 +57,7 @@ it('Page.Events.RequestFailed @smoke', async ({ page, server, browserName, platf | ||||||
|   if (browserName === 'chromium' || browserName === '_bidiChromium') { |   if (browserName === 'chromium' || browserName === '_bidiChromium') { | ||||||
|     expect(failedRequests[0].failure().errorText).toBe('net::ERR_EMPTY_RESPONSE'); |     expect(failedRequests[0].failure().errorText).toBe('net::ERR_EMPTY_RESPONSE'); | ||||||
|   } else if (browserName === 'webkit') { |   } 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); |       expect(failedRequests[0].failure().errorText).toMatch(/(Message Corrupt)|(Connection terminated unexpectedly)/i); | ||||||
|     else if (platform === 'darwin') |     else if (platform === 'darwin') | ||||||
|       expect(failedRequests[0].failure().errorText).toBe('The network connection was lost.'); |       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); |   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(isAndroid, 'No files on Android'); | ||||||
|   it.skip(mode.startsWith('service')); |   it.skip(mode.startsWith('service')); | ||||||
|  |   it.skip(channel === 'webkit-wsl', 'separate filesystem on wsl'); | ||||||
| 
 | 
 | ||||||
|   const fileurl = url.pathToFileURL(asset('empty.html')).href; |   const fileurl = url.pathToFileURL(asset('empty.html')).href; | ||||||
|   await page.goto(fileurl); |   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); |   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(isAndroid, 'No files on Android'); | ||||||
|   it.skip(mode.startsWith('service')); |   it.skip(mode.startsWith('service')); | ||||||
|  |   it.skip(channel === 'webkit-wsl', 'separate filesystem on wsl'); | ||||||
| 
 | 
 | ||||||
|   const fileurl = url.pathToFileURL(asset('frames/two-frames.html')).href; |   const fileurl = url.pathToFileURL(asset('frames/two-frames.html')).href; | ||||||
|   await page.goto(fileurl); |   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'); |     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'.
 |   // Make sure that network events do not emit 'undefined'.
 | ||||||
|   // @see https://crbug.com/750469
 |   // @see https://crbug.com/750469
 | ||||||
|   page.on('request', request => expect(request).toBeTruthy()); |   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()); |   page.on('requestfailed', request => expect(request).toBeTruthy()); | ||||||
|   let error = null; |   let error = null; | ||||||
|   await page.goto(httpsServer.EMPTY_PAGE).catch(e => error = e); |   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/1.html', '/redirect/2.html'); | ||||||
|   server.setRedirect('/redirect/2.html', '/empty.html'); |   server.setRedirect('/redirect/2.html', '/empty.html'); | ||||||
|   let error = null; |   let error = null; | ||||||
|   await page.goto(httpsServer.PREFIX + '/redirect/1.html').catch(e => error = e); |   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 }) => { | 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)`); |   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; |   let error = null; | ||||||
|   await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); |   await page.goto('http://localhost:44123/non-existing-url').catch(e => error = e); | ||||||
|   if (browserName === 'chromium') { |   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'); |       expect(error.message).toContain('net::ERR_CONNECTION_REFUSED'); | ||||||
|   } else if (browserName === 'webkit' && isWindows && mode === 'service2') { |   } else if (browserName === 'webkit' && isWindows && mode === 'service2') { | ||||||
|     expect(error.message).toContain(`proxy handshake error`); |     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`); |     expect(error.message).toContain(`Could not connect to server`); | ||||||
|   } else if (browserName === 'webkit') { |   } else if (browserName === 'webkit') { | ||||||
|     if (mode === 'service2') |     if (mode === 'service2') | ||||||
|  | @ -730,9 +732,9 @@ it('should work with lazy loading iframes', async ({ page, server, isAndroid }) | ||||||
|   expect(page.frames().length).toBe(2); |   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 === '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) => { |   server.setRoute('/empty.html', (req, res) => { | ||||||
|     res.statusCode = 200; |     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'); |   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(isAndroid, 'No files on Android'); | ||||||
|   it.skip(mode.startsWith('service')); |   it.skip(mode.startsWith('service')); | ||||||
|  |   it.skip(channel === 'webkit-wsl'); | ||||||
| 
 | 
 | ||||||
|   const url1 = url.pathToFileURL(asset('consolelog.html')).href; |   const url1 = url.pathToFileURL(asset('consolelog.html')).href; | ||||||
|   const url2 = server.PREFIX + '/consolelog.html'; |   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'); |   expect(await page.evaluate(() => document.querySelector('div').textContent)).toBe('123123'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| it('should support undo-redo', async ({ page, browserName, isLinux }) => { | it('should support undo-redo', async ({ page, browserName, isLinux, channel }) => { | ||||||
|   it.fixme(browserName === 'webkit' && isLinux, 'https://github.com/microsoft/playwright/issues/12000'); |   it.fixme(browserName === 'webkit' && isLinux || channel === 'webkit-wsl', 'https://github.com/microsoft/playwright/issues/12000'); | ||||||
|   await page.setContent(`<div contenteditable></div>`); |   await page.setContent(`<div contenteditable></div>`); | ||||||
|   const div = page.locator('div'); |   const div = page.locator('div'); | ||||||
|   await expect(div).toHaveText(''); |   await expect(div).toHaveText(''); | ||||||
|  |  | ||||||
|  | @ -88,9 +88,9 @@ it('should return headers', async ({ page, server, browserName }) => { | ||||||
|     expect(response.request().headers()['user-agent']).toContain('WebKit'); |     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.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; |   let serverRequest; | ||||||
|   server.setRoute('/empty.html', (request, response) => { |   server.setRoute('/empty.html', (request, response) => { | ||||||
|     serverRequest = request; |     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)); |   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.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; |   let requestHeadersPromise; | ||||||
|   page.on('request', request => requestHeadersPromise = request.allHeaders()); |   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'); |   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.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'); |   await page.goto(server.PREFIX + '/empty.html'); | ||||||
|   let serverRequest; |   let serverRequest; | ||||||
|  | @ -392,7 +392,7 @@ it('should report raw headers', async ({ page, server, browserName, platform, is | ||||||
|     expectedHeaders = []; |     expectedHeaders = []; | ||||||
|     for (let i = 0; i < req.rawHeaders.length; i += 2) |     for (let i = 0; i < req.rawHeaders.length; i += 2) | ||||||
|       expectedHeaders.push({ name: req.rawHeaders[i], value: req.rawHeaders[i + 1] }); |       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'); |       expectedHeaders = expectedHeaders.filter(({ name }) => name.toLowerCase() !== 'accept-encoding'); | ||||||
|       // Convert "value": "en-US, en-US" => "en-US"
 |       // Convert "value": "en-US, en-US" => "en-US"
 | ||||||
|       expectedHeaders = expectedHeaders.map(e => { |       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'); |   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(isAndroid, 'No files on Android'); | ||||||
|   it.skip(browserName === 'firefox', 'Firefox does return null for file:// URLs'); |   it.skip(browserName === 'firefox', 'Firefox does return null for file:// URLs'); | ||||||
|   it.skip(mode.startsWith('service')); |   it.skip(mode.startsWith('service')); | ||||||
|  |   it.skip(channel === 'webkit-wsl'); | ||||||
| 
 | 
 | ||||||
|   const fileurl = url.pathToFileURL(asset('frames/two-frames.html')).href; |   const fileurl = url.pathToFileURL(asset('frames/two-frames.html')).href; | ||||||
|   const response = await page.goto(fileurl); |   const response = await page.goto(fileurl); | ||||||
|  |  | ||||||
|  | @ -280,14 +280,14 @@ it.describe('page screenshot', () => { | ||||||
|     expect(screenshot).toMatchSnapshot('screenshot-clip-odd-size.png'); |     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(isElectron && isMac, 'Fails on the bots'); | ||||||
|     it.fixme(browserName === 'webkit' && isLinux && !headless, 'WebKit has slightly different corners on gtk4.'); |     it.fixme(browserName === 'webkit' && isLinux && !headless, 'WebKit has slightly different corners on gtk4.'); | ||||||
|     await page.setViewportSize({ width: 500, height: 500 }); |     await page.setViewportSize({ width: 500, height: 500 }); | ||||||
|     await page.goto(server.PREFIX + '/screenshots/canvas.html'); |     await page.goto(server.PREFIX + '/screenshots/canvas.html'); | ||||||
|     const screenshot = await page.screenshot(); |     const screenshot = await page.screenshot(); | ||||||
|     if ((!isHeadlessShell && browserName === 'chromium' && isMac && os.arch() === 'arm64' && macVersion >= 14) || |     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'); |       expect(screenshot).toMatchSnapshot('screenshot-canvas-with-accurate-corners.png'); | ||||||
|     else |     else | ||||||
|       expect(screenshot).toMatchSnapshot('screenshot-canvas.png'); |       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'); |   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.goto(server.EMPTY_PAGE); | ||||||
|   await page.setContent(`<a href='${httpsServer.EMPTY_PAGE}'>foobar</a>`); |   await page.setContent(`<a href='${httpsServer.EMPTY_PAGE}'>foobar</a>`); | ||||||
|   const [error] = await Promise.all([ |   const [error] = await Promise.all([ | ||||||
|     page.waitForNavigation().catch(e => e), |     page.waitForNavigation().catch(e => e), | ||||||
|     page.click('a'), |     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 }) => { | it('should work with history.pushState()', async ({ page, server }) => { | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue