diff --git a/.github/workflows/tests_secondary.yml b/.github/workflows/tests_secondary.yml index b9804c6eb9..cd59416d39 100644 --- a/.github/workflows/tests_secondary.yml +++ b/.github/workflows/tests_secondary.yml @@ -274,7 +274,7 @@ jobs: strategy: fail-fast: false matrix: - runs-on: [ubuntu-latest, macos-latest, windows-latest] + runs-on: [ubuntu-latest] runs-on: ${{ matrix.runs-on }} steps: - uses: actions/checkout@v4 @@ -288,3 +288,24 @@ jobs: flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} env: PWTEST_CHANNEL: chromium-headless-shell + + test_chromium_next: + name: Test chromium-next channel + environment: ${{ github.event_name == 'push' && 'allow-uploading-flakiness-results' || null }} + strategy: + fail-fast: false + matrix: + runs-on: [ubuntu-latest, windows-latest, macos-latest] + runs-on: ${{ matrix.runs-on }} + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/run-test + with: + browsers-to-install: chromium + command: npm run ctest + bot-name: "chromium-next-${{ matrix.runs-on }}" + flakiness-client-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_CLIENT_ID }} + flakiness-tenant-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_TENANT_ID }} + flakiness-subscription-id: ${{ secrets.AZURE_FLAKINESS_DASHBOARD_SUBSCRIPTION_ID }} + env: + PWTEST_CHANNEL: chromium-next diff --git a/docs/src/browsers.md b/docs/src/browsers.md index 52b3272096..829bf0ccdb 100644 --- a/docs/src/browsers.md +++ b/docs/src/browsers.md @@ -338,6 +338,32 @@ dotnet test --settings:webkit.runsettings For Google Chrome, Microsoft Edge and other Chromium-based browsers, by default, Playwright uses open source Chromium builds. Since the Chromium project is ahead of the branded browsers, when the world is on Google Chrome N, Playwright already supports Chromium N+1 that will be released in Google Chrome and Microsoft Edge a few weeks later. +Playwright ships a regular Chromium build for headed operations and a separate [Chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) for headless mode. These two behave differently in some edge cases, but the majority of testing scenarios are not affected. Note this behavior has changed in Playwright version 1.49, see [issue #33566](https://github.com/microsoft/playwright/issues/33566) for details. + +#### Save on download size + +If you are only running tests in headless, for example on CI, you can avoid downloading a headed version of Chromium by specifying `chromium-headless-shell` during installation. + +```bash js +# When only running tests headlessly +npx playwright install chromium-headless-shell firefox webkit +``` + +```bash java +# When only running tests headlessly +mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="install chromium-headless-shell firefox webkit" +``` + +```bash python +# When only running tests headlessly +playwright install chromium-headless-shell firefox webkit +``` + +```bash csharp +# When only running tests headlessly +pwsh bin/Debug/netX/playwright.ps1 install chromium-headless-shell firefox webkit +``` + ### Google Chrome & Microsoft Edge While Playwright can download and use the recent Chromium build, it can operate against the branded Google Chrome and Microsoft Edge browsers available on the machine (note that Playwright doesn't install them by default). In particular, the current Playwright version will support Stable and Beta channels of these browsers. @@ -348,6 +374,10 @@ Available channels are `chrome`, `msedge`, `chrome-beta`, `msedge-beta` or `msed Certain Enterprise Browser Policies may impact Playwright's ability to launch and control Google Chrome and Microsoft Edge. Running in an environment with browser policies is outside of the Playwright project's scope. ::: +:::warning +Google Chrome and Microsoft Edge have switched to a [new headless mode](https://developer.chrome.com/docs/chromium/headless) implementation that is closer to a regular headed mode. This differs from [chromium headless shell](https://developer.chrome.com/blog/chrome-headless-shell) that is used in Playwright by default when running headless, so expect different behavior in some cases. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) fore details. +::: + ```js import { defineConfig, devices } from '@playwright/test'; @@ -472,12 +502,6 @@ rarely the case), you will also want to use the official channel. Google Chrome and Microsoft Edge respect enterprise policies, which include limitations to the capabilities, network proxy, mandatory extensions that stand in the way of testing. So if you are part of the organization that uses such policies, it is easiest to use bundled Chromium for your local testing, you can still opt into stable channels on the bots that are typically free of such restrictions. -### Chromium Headless Shell - -Playwright runs a regular Chromium build in headed and headless modes. Note that headless mode has changed in Playwright version 1.49 when Chromium entirely switched to the [new headless implementation](https://developer.chrome.com/docs/chromium/headless). - -Playwright also provides [`'chromium-headless-shell'` channel](https://developer.chrome.com/blog/chrome-headless-shell) that differs from the regular Chromium browser in features, performance and behavior. If you would like to optimize your CI performance and can tolerate different behavior in some cases, install and use this channel similarly to [Google Chrome & Microsoft Edge](#google-chrome--microsoft-edge). - ### Firefox Playwright's Firefox version matches the recent [Firefox Stable](https://www.mozilla.org/en-US/firefox/new/) build. Playwright doesn't work with the branded version of Firefox since it relies on patches. diff --git a/docs/src/chrome-extensions-js-python.md b/docs/src/chrome-extensions-js-python.md index e34236ab5b..f34ecf8e9d 100644 --- a/docs/src/chrome-extensions-js-python.md +++ b/docs/src/chrome-extensions-js-python.md @@ -214,10 +214,6 @@ def test_popup_page(page: Page, extension_id: str) -> None: ## Headless mode -:::danger -`headless=new` mode is not officially supported by Playwright and might result in unexpected behavior. -::: - By default, Chrome's headless mode in Playwright does not support Chrome extensions. To overcome this limitation, you can run Chrome's persistent context with a new headless mode by using the following code: ```js title="fixtures.ts" diff --git a/docs/src/release-notes-js.md b/docs/src/release-notes-js.md index 6919b661a3..7a2669bc6f 100644 --- a/docs/src/release-notes-js.md +++ b/docs/src/release-notes-js.md @@ -8,57 +8,57 @@ import LiteYouTube from '@site/src/components/LiteYouTube'; ## Version 1.49 -### New Chromium Headless +### Breaking: channels `chrome`, `msedge` and similar switch to new headless -Prior to this release, Playwright was running the old established implementation of Chromium headless. However, Chromium had entirely switched to the [new headless mode](https://developer.chrome.com/docs/chromium/headless) implementation, so Playwright had to switch as well. +Prior to this release, Playwright was running the old established implementation of [Chromium headless mode](https://developer.chrome.com/docs/chromium/headless). However, Chromium had entirely **switched to the new headless mode**, and **removed the old one**. -Most likely, this change should go unnoticed for you. However, the new headless implementation differs in a number of ways, for example when handling pdf documents, so please file an issue if you encounter a problem. +![Chromium Headless](https://github.com/user-attachments/assets/2829e86a-dfe2-4743-a6d4-2aa65beea890) -### Chromium Headless Shell +If you are using a browser channel, for example `'chrome'` or `'msedge'`, the headless mode switch **will affect you**. Most likely, you will have to update some of your tests and all of your screenshot expectations. See [issue #33566](https://github.com/microsoft/playwright/issues/33566) for more details. -Playwright now also ships `chromium-headless-shell` channel that is a separate build that closely follows the old headless implementation. If you would like to keep the old behavior before you are ready to switch to the new headless, please fallback to this channel: +#### Chromium headless shell -1. First, install this channel prior to running tests. Make sure to list all browsers that you use. +Starting with this release, Playwright downloads and runs two different browser builds - one is a regular headed chromium and the other is a chromium headless shell. This should be transparent to you, **no action is needed**. You can learn more in [issue #33566](https://github.com/microsoft/playwright/issues/33566). - ```bash - # running tests in all three browsers, headless and headed - npx playwright install chromium chromium-headless-shell firefox webkit +If you are only running tests in headless, for example on CI, you can avoid downloading a headed version of Chromium by specifying `chromium-headless-shell` during installation. - # running tests in all three browsers on CI, headless only - npx playwright install chromium-headless-shell firefox webkit - ``` +```bash +# only running tests headlessly +npx playwright install chromium-headless-shell firefox webkit +``` -1. Update your config file to specify `'chromium-headless-shell'` channel. +Playwright will skip downloading headed chromium build, and will use `chromium-headless-shell` when running headless. - ```js - import { defineConfig, devices } from '@playwright/test'; +#### Opt-in to new headless - export default defineConfig({ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - channel: 'chromium-headless-shell', - }, +We encourage everyone to try and switch to the new headless by using the `chromium-next` channel. + +First, install this channel prior to running tests. Make sure to list all the browsers that you use. + +```bash +npx playwright install chromium-next firefox webkit +``` + +Then update your config file to specify `'chromium-next'` channel. + +```js +import { defineConfig, devices } from '@playwright/test'; +export default defineConfig({ + projects: [ + { + name: 'chromium', + use: { + ...devices['Desktop Chrome'], + channel: 'chromium-next', }, - { - name: 'firefox', - use: { ...devices['Desktop Firefox'] }, - }, - { - name: 'webkit', - use: { ...devices['Desktop Safari'] }, - }, - ], - }); - ``` - -1. Note that `chromium-headless-shell` channel only supports headless operations. If you try to run tests in headed mode, it will automatically fallback to regular `chromium`. + }, + ], +}); +``` ### Browser Versions -- Chromium 131.0.6778.24 +- Chromium 131.0.6778.33 - Mozilla Firefox 132.0 - WebKit 18.0 diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 2f04b76916..a657f4ad10 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -10,7 +10,7 @@ { "name": "chromium-headless-shell", "revision": "1148", - "installByDefault": false, + "installByDefault": true, "browserVersion": "131.0.6778.33" }, { diff --git a/packages/playwright-core/src/server/chromium/chromium.ts b/packages/playwright-core/src/server/chromium/chromium.ts index 07ce3211fd..8fce8f51ca 100644 --- a/packages/playwright-core/src/server/chromium/chromium.ts +++ b/packages/playwright-core/src/server/chromium/chromium.ts @@ -300,7 +300,7 @@ export class Chromium extends BrowserType { // See https://github.com/microsoft/playwright/issues/7362 chromeArguments.push('--enable-use-zoom-for-dsf=false'); // See https://bugs.chromium.org/p/chromium/issues/detail?id=1407025. - if (options.headless) + if (options.headless && (!options.channel || options.channel === 'chromium-headless-shell')) chromeArguments.push('--use-angle'); } @@ -349,9 +349,9 @@ export class Chromium extends BrowserType { } override getExecutableName(options: types.LaunchOptions): string { - if (options.channel === 'chromium-headless-shell' && !options.headless) - return 'chromium'; - return options.channel || 'chromium'; + if (options.channel) + return options.channel; + return options.headless ? 'chromium-headless-shell' : 'chromium'; } } diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index bf871e63c8..d80900de9f 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -79,7 +79,7 @@ const EXECUTABLE_PATHS = { }; type DownloadPaths = Record; -const DOWNLOAD_PATHS: Record = { +const DOWNLOAD_PATHS: Record = { 'chromium': { '': undefined, 'ubuntu18.04-x64': undefined, @@ -403,9 +403,9 @@ function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { } export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi'; -type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' |'android'; +type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'android'; type BidiChannel = 'bidi-firefox-stable' | 'bidi-firefox-beta' | 'bidi-firefox-nightly' | 'bidi-chrome-canary' | 'bidi-chrome-stable' | 'bidi-chromium'; -type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary'; +type ChromiumChannel = 'chrome' | 'chrome-beta' | 'chrome-dev' | 'chrome-canary' | 'chromium-headless-shell' | 'chromium-next' | 'msedge' | 'msedge-beta' | 'msedge-dev' | 'msedge-canary'; const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell']; export interface Executable { @@ -488,11 +488,26 @@ export class Registry { _dependencyGroup: 'chromium', _isHermeticInstallation: true, }); + this._executables.push({ + type: 'channel', + name: 'chromium-next', + browserName: 'chromium', + directory: chromium.dir, + executablePath: () => chromiumExecutable, + executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('chromium-next', chromiumExecutable, chromium.installByDefault, sdkLanguage), + installType: 'download-on-demand', + _validateHostRequirements: (sdkLanguage: string) => this._validateHostRequirements(sdkLanguage, chromium.dir, ['chrome-linux'], [], ['chrome-win']), + downloadURLs: this._downloadURLs(chromium), + browserVersion: chromium.browserVersion, + _install: () => this._downloadExecutable(chromium, chromiumExecutable), + _dependencyGroup: 'chromium', + _isHermeticInstallation: true, + }); const chromiumHeadlessShell = descriptors.find(d => d.name === 'chromium-headless-shell')!; const chromiumHeadlessShellExecutable = findExecutablePath(chromiumHeadlessShell.dir, 'chromium-headless-shell'); this._executables.push({ - type: 'tool', + type: 'channel', name: 'chromium-headless-shell', browserName: 'chromium', directory: chromiumHeadlessShell.dir, @@ -885,6 +900,8 @@ export class Registry { set.add(executable); if (executable.browserName === 'chromium') set.add(this.findExecutable('ffmpeg')!); + if (executable.name === 'chromium') + set.add(this.findExecutable('chromium-headless-shell')!); } return Array.from(set); } diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index 01110d3c38..ab2b9b2a88 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -31,13 +31,19 @@ export const TMP_WORKSPACES = path.join(os.platform() === 'darwin' ? '/tmp' : os const debug = debugLogger('itest'); const expect = _expect.extend({ - toHaveLoggedSoftwareDownload(received: any, browsers: ('chromium' | 'firefox' | 'webkit' | 'ffmpeg')[]) { + toHaveLoggedSoftwareDownload(received: any, browsers: ('chromium' | 'chromium-headless-shell' | 'firefox' | 'webkit' | 'ffmpeg')[]) { if (typeof received !== 'string') throw new Error(`Expected argument to be a string.`); const downloaded = new Set(); - for (const [, browser] of received.matchAll(/^.*(chromium|firefox|webkit|ffmpeg).*playwright build v\d+\)? downloaded.*$/img)) - downloaded.add(browser.toLowerCase()); + let index = 0; + while (true) { + const match = received.substring(index).match(/(chromium|chromium headless shell|firefox|webkit|ffmpeg)[\s\d\.]+\(?playwright build v\d+\)? downloaded/im); + if (!match) + break; + downloaded.add(match[1].replace(/\s/g, '-').toLowerCase()); + index += match.index + 1; + } const expected = browsers; if (expected.length === downloaded.size && expected.every(browser => downloaded.has(browser))) { @@ -143,7 +149,7 @@ export const test = _test installedSoftwareOnDisk: async ({ isolateBrowsers, _browsersPath }, use) => { if (!isolateBrowsers) throw new Error(`Test that checks browser installation must set "isolateBrowsers" to true`); - await use(async () => fs.promises.readdir(_browsersPath).catch(() => []).then(files => files.map(f => f.split('-')[0]).filter(f => !f.startsWith('.')))); + await use(async () => fs.promises.readdir(_browsersPath).catch(() => []).then(files => files.map(f => f.split('-')[0].replace(/_/g, '-')).filter(f => !f.startsWith('.')))); }, exec: async ({ tmpWorkspace, _browsersPath, isolateBrowsers }, use, testInfo) => { await use(async (cmd: string, ...argsAndOrOptions: [] | [...string[]] | [...string[], ExecOptions] | [ExecOptions]) => { diff --git a/tests/installation/npx-global.spec.ts b/tests/installation/npx-global.spec.ts index 4a7a5b47f5..6a3da572ef 100755 --- a/tests/installation/npx-global.spec.ts +++ b/tests/installation/npx-global.spec.ts @@ -35,8 +35,8 @@ test('npx playwright install global', async ({ exec, installedSoftwareOnDisk }) test.skip(process.platform === 'win32', 'isLikelyNpxGlobal() does not work in this setup on our bots'); const result = await exec('npx playwright install'); - expect(result).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg', 'firefox', 'webkit']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); expect(result).not.toContain(`Please run the following command to download new browsers`); expect(result).toContain(`To avoid unexpected behavior, please install your dependencies first`); }); diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index e1e438ecdc..924dbb4c55 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -41,8 +41,8 @@ for (const cdn of CDNS) { test(`playwright cdn failover should work (${cdn})`, async ({ exec, installedSoftwareOnDisk }) => { await exec('npm i playwright'); const result = await exec('npx playwright install', { env: { PW_TEST_CDN_THAT_SHOULD_WORK: cdn, DEBUG: 'pw:install' } }); - expect(result).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg', 'firefox', 'webkit']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); const dls = parsedDownloads(result); for (const software of ['chromium', 'ffmpeg', 'firefox', 'webkit']) expect(dls).toContainEqual({ status: 200, name: software, url: expect.stringContaining(cdn) }); diff --git a/tests/installation/playwright-cli-install-should-work.spec.ts b/tests/installation/playwright-cli-install-should-work.spec.ts index e056e61138..1c0cea6277 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -23,14 +23,14 @@ test('install command should work', async ({ exec, installedSoftwareOnDisk }) => await test.step('playwright install chromium', async () => { const result = await exec('npx playwright install chromium'); - expect(result).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg']); }); await test.step('playwright install', async () => { const result = await exec('npx playwright install'); expect(result).toHaveLoggedSoftwareDownload(['firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg', 'firefox', 'webkit']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); }); await exec('node sanity.js playwright', { env: { PLAYWRIGHT_BROWSERS_PATH: '0' } }); @@ -50,7 +50,7 @@ test('install command should work', async ({ exec, installedSoftwareOnDisk }) => test('should be able to remove browsers', async ({ exec, installedSoftwareOnDisk }) => { await exec('npm i playwright'); await exec('npx playwright install chromium'); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg']); await exec('npx playwright uninstall'); expect(await installedSoftwareOnDisk()).toEqual([]); }); diff --git a/tests/installation/playwright-packages-install-behavior.spec.ts b/tests/installation/playwright-packages-install-behavior.spec.ts index 24d8c058a5..e03ea94e1a 100755 --- a/tests/installation/playwright-packages-install-behavior.spec.ts +++ b/tests/installation/playwright-packages-install-behavior.spec.ts @@ -25,7 +25,7 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { const browserName = pkg.split('-')[1]; const expectedSoftware = [browserName]; if (browserName === 'chromium') - expectedSoftware.push('ffmpeg'); + expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); expect(result).toHaveLoggedSoftwareDownload(expectedSoftware as any); expect(await installedSoftwareOnDisk()).toEqual(expectedSoftware); expect(result).not.toContain(`To avoid unexpected behavior, please install your dependencies first`); @@ -39,7 +39,7 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { const pkg = `@playwright/browser-${browser}`; const expectedSoftware = [browser]; if (browser === 'chromium') - expectedSoftware.push('ffmpeg'); + expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); const result1 = await exec('npm i --foreground-scripts', pkg); expect(result1).toHaveLoggedSoftwareDownload(expectedSoftware as any); @@ -69,13 +69,22 @@ test(`playwright should work`, async ({ exec, installedSoftwareOnDisk }) => { expect(await installedSoftwareOnDisk()).toEqual([]); const result2 = await exec('npx playwright install'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); await exec('node sanity.js playwright chromium firefox webkit'); await exec('node esm-playwright.mjs'); }); +test(`playwright should work with chromium-next`, async ({ exec, installedSoftwareOnDisk }) => { + const result1 = await exec('npm i --foreground-scripts playwright'); + expect(result1).toHaveLoggedSoftwareDownload([]); + expect(await installedSoftwareOnDisk()).toEqual([]); + const result2 = await exec('npx playwright install chromium-next'); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg']); +}); + test('@playwright/test should work', async ({ exec, installedSoftwareOnDisk }) => { const result1 = await exec('npm i --foreground-scripts @playwright/test'); expect(result1).toHaveLoggedSoftwareDownload([]); @@ -84,8 +93,8 @@ test('@playwright/test should work', async ({ exec, installedSoftwareOnDisk }) = await exec('npx playwright test -c . sample.spec.js', { expectToExitWithError: true, message: 'should not be able to run tests without installing browsers' }); const result2 = await exec('npx playwright install'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg', 'firefox', 'webkit']); - expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(await installedSoftwareOnDisk()).toEqual(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); await exec('node sanity.js @playwright/test chromium firefox webkit'); await exec('node', 'esm-playwright-test.mjs'); diff --git a/tests/installation/validate-dependencies.spec.ts b/tests/installation/validate-dependencies.spec.ts index 26b27c76e3..ae9ff15590 100644 --- a/tests/installation/validate-dependencies.spec.ts +++ b/tests/installation/validate-dependencies.spec.ts @@ -58,7 +58,7 @@ test('should validate dependencies correctly if skipped during install', async ( DEBUG: 'pw:install', }, }); - expect(result).toContain(`validating host requirements for "chromium"`); + expect(result).toContain(`validating host requirements for "chromium-headless-shell"`); expect(result).not.toContain(`validating host requirements for "firefox"`); expect(result).not.toContain(`validating host requirements for "webkit"`); }); diff --git a/tests/library/browsercontext-credentials.spec.ts b/tests/library/browsercontext-credentials.spec.ts index c061ffb067..beb4595aa9 100644 --- a/tests/library/browsercontext-credentials.spec.ts +++ b/tests/library/browsercontext-credentials.spec.ts @@ -15,26 +15,33 @@ * limitations under the License. */ -import { browserTest as it, expect } from '../config/browserTest'; +import { browserTest as base, expect } from '../config/browserTest'; -it('should fail without credentials', async ({ browser, server, browserName, channel }) => { +const it = base.extend<{ failsOn401: boolean }>({ + failsOn401: async ({ browserName, headless, channel }, use) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + await use(browserName === 'chromium' && !isHeadlessShell); + }, +}); + +it('should fail without credentials', async ({ browser, server, failsOn401 }) => { server.setAuth('/empty.html', 'user', 'pass'); const context = await browser.newContext(); const page = await context.newPage(); const responseOrError = await page.goto(server.EMPTY_PAGE).catch(e => e); - if (browserName === 'chromium' && channel !== 'chromium-headless-shell') + if (failsOn401) expect(responseOrError.message).toContain('net::ERR_INVALID_AUTH_CREDENTIALS'); else expect(responseOrError.status()).toBe(401); }); -it('should work with setHTTPCredentials', async ({ browser, server, browserName, channel }) => { +it('should work with setHTTPCredentials', async ({ browser, server, failsOn401 }) => { server.setAuth('/empty.html', 'user', 'pass'); const context = await browser.newContext(); const page = await context.newPage(); let responseOrError = await page.goto(server.EMPTY_PAGE).catch(e => e); - if (browserName === 'chromium' && channel !== 'chromium-headless-shell') + if (failsOn401) expect(responseOrError.message).toContain('net::ERR_INVALID_AUTH_CREDENTIALS'); else expect(responseOrError.status()).toBe(401); @@ -102,21 +109,21 @@ it('should work with correct credentials and matching origin case insensitive', await context.close(); }); -it('should fail with correct credentials and mismatching scheme', async ({ browser, server, browserName, channel }) => { +it('should fail with correct credentials and mismatching scheme', async ({ browser, server, failsOn401 }) => { server.setAuth('/empty.html', 'user', 'pass'); const context = await browser.newContext({ httpCredentials: { username: 'user', password: 'pass', origin: server.PREFIX.replace('http://', 'https://') } }); const page = await context.newPage(); const responseOrError = await page.goto(server.EMPTY_PAGE).catch(e => e); - if (browserName === 'chromium' && channel !== 'chromium-headless-shell') + if (failsOn401) expect(responseOrError.message).toContain('net::ERR_INVALID_AUTH_CREDENTIALS'); else expect(responseOrError.status()).toBe(401); await context.close(); }); -it('should fail with correct credentials and mismatching hostname', async ({ browser, server, browserName, channel }) => { +it('should fail with correct credentials and mismatching hostname', async ({ browser, server, failsOn401 }) => { server.setAuth('/empty.html', 'user', 'pass'); const hostname = new URL(server.PREFIX).hostname; const origin = server.PREFIX.replace(hostname, 'mismatching-hostname'); @@ -125,14 +132,14 @@ it('should fail with correct credentials and mismatching hostname', async ({ bro }); const page = await context.newPage(); const responseOrError = await page.goto(server.EMPTY_PAGE).catch(e => e); - if (browserName === 'chromium' && channel !== 'chromium-headless-shell') + if (failsOn401) expect(responseOrError.message).toContain('net::ERR_INVALID_AUTH_CREDENTIALS'); else expect(responseOrError.status()).toBe(401); await context.close(); }); -it('should fail with correct credentials and mismatching port', async ({ browser, server, browserName, channel }) => { +it('should fail with correct credentials and mismatching port', async ({ browser, server, failsOn401 }) => { server.setAuth('/empty.html', 'user', 'pass'); const origin = server.PREFIX.replace(server.PORT.toString(), (server.PORT + 1).toString()); const context = await browser.newContext({ @@ -140,7 +147,7 @@ it('should fail with correct credentials and mismatching port', async ({ browser }); const page = await context.newPage(); const responseOrError = await page.goto(server.EMPTY_PAGE).catch(e => e); - if (browserName === 'chromium' && channel !== 'chromium-headless-shell') + if (failsOn401) expect(responseOrError.message).toContain('net::ERR_INVALID_AUTH_CREDENTIALS'); else expect(responseOrError.status()).toBe(401); diff --git a/tests/library/browsertype-connect.spec.ts b/tests/library/browsertype-connect.spec.ts index 77bcd5647c..77efb65e9b 100644 --- a/tests/library/browsertype-connect.spec.ts +++ b/tests/library/browsertype-connect.spec.ts @@ -170,6 +170,8 @@ for (const kind of ['launchServer', 'run-server'] as const) { }); test('should ignore page.pause when headed', async ({ connect, startRemoteServer, browserType, channel }) => { + test.skip(channel === 'chromium-headless-shell', 'shell is never headed'); + const headless = (browserType as any)._defaultLaunchOptions.headless; (browserType as any)._defaultLaunchOptions.headless = false; const remoteServer = await startRemoteServer(kind); diff --git a/tests/library/chromium/launcher.spec.ts b/tests/library/chromium/launcher.spec.ts index 09e6673642..d00ac63185 100644 --- a/tests/library/chromium/launcher.spec.ts +++ b/tests/library/chromium/launcher.spec.ts @@ -52,8 +52,10 @@ it('should open devtools when "devtools: true" option is given', async ({ browse await browser.close(); }); -it('should return background pages', async ({ browserType, createUserDataDir, asset, channel }) => { - it.skip(channel === 'chromium-headless-shell', 'Headless Shell has no support for extensions'); +it('should return background pages', async ({ browserType, createUserDataDir, asset, headless, channel }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.skip(isHeadlessShell, 'Headless Shell has no support for extensions'); + const userDataDir = await createUserDataDir(); const extensionPath = asset('simple-extension'); const extensionOptions = { @@ -76,8 +78,10 @@ it('should return background pages', async ({ browserType, createUserDataDir, as expect(context.backgroundPages().length).toBe(0); }); -it('should return background pages when recording video', async ({ browserType, createUserDataDir, asset, channel }, testInfo) => { - it.skip(channel === 'chromium-headless-shell', 'Headless Shell has no support for extensions'); +it('should return background pages when recording video', async ({ browserType, createUserDataDir, asset, headless, channel }, testInfo) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.skip(isHeadlessShell, 'Headless Shell has no support for extensions'); + const userDataDir = await createUserDataDir(); const extensionPath = asset('simple-extension'); const extensionOptions = { @@ -101,8 +105,10 @@ it('should return background pages when recording video', async ({ browserType, await context.close(); }); -it('should support request/response events when using backgroundPage()', async ({ browserType, createUserDataDir, asset, server, channel }) => { - it.skip(channel === 'chromium-headless-shell', 'Headless Shell has no support for extensions'); +it('should support request/response events when using backgroundPage()', async ({ browserType, createUserDataDir, asset, server, headless, channel }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.skip(isHeadlessShell, 'Headless Shell has no support for extensions'); + server.setRoute('/empty.html', (req, res) => { res.writeHead(200, { 'Content-Type': 'text/html', 'x-response-foobar': 'BarFoo' }); res.end(`hello world!`); @@ -151,8 +157,10 @@ it('should support request/response events when using backgroundPage()', async ( it('should report console messages from content script', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32762' } -}, async ({ browserType, createUserDataDir, asset, server, channel }) => { - it.skip(channel === 'chromium-headless-shell', 'Headless Shell has no support for extensions'); +}, async ({ browserType, createUserDataDir, asset, server, headless, channel }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.skip(isHeadlessShell, 'Headless Shell has no support for extensions'); + const userDataDir = await createUserDataDir(); const extensionPath = asset('extension-with-logging'); const extensionOptions = { @@ -184,13 +192,3 @@ it('should not create pages automatically', async ({ browserType }) => { await browser.close(); expect(targets.length).toBe(0); }); - -it('should fallback to regular chromium when running chromium-headless-shell channel as headed', async ({ browserType, channel }) => { - it.skip(channel !== 'chromium-headless-shell'); - - const browser = await browserType.launch({ channel: 'chromium-headless-shell', headless: false }); - const page = await browser.newPage(); - const ua = await page.evaluate(() => navigator.userAgent); - await browser.close(); - expect(ua).not.toContain('Headless'); -}); diff --git a/tests/library/download.spec.ts b/tests/library/download.spec.ts index 74b008c51b..644519118c 100644 --- a/tests/library/download.spec.ts +++ b/tests/library/download.spec.ts @@ -636,8 +636,10 @@ it('should be able to download a inline PDF file via response interception', asy await page.close(); }); -it('should be able to download a inline PDF file via navigation', async ({ browser, server, asset, browserName, channel }) => { - it.skip(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'We expect PDF Viewer to open up in Chromium'); +it('should be able to download a inline PDF file via navigation', async ({ browser, server, asset, browserName, channel, headless }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.skip(browserName === 'chromium' && !isHeadlessShell, 'We expect PDF Viewer to open up in headed Chromium'); + const page = await browser.newPage(); await page.goto(server.EMPTY_PAGE); await page.setContent(` diff --git a/tests/library/emulation-focus.spec.ts b/tests/library/emulation-focus.spec.ts index 5bbef6b164..2e1e08163a 100644 --- a/tests/library/emulation-focus.spec.ts +++ b/tests/library/emulation-focus.spec.ts @@ -104,7 +104,8 @@ it('should change document.activeElement', async ({ page, server }) => { it('should not affect screenshots', async ({ page, server, browserName, headless, isWindows, channel }) => { it.skip(browserName === 'webkit' && isWindows && !headless, 'WebKit/Windows/headed has a larger minimal viewport. See https://github.com/microsoft/playwright/issues/22616'); it.skip(browserName === 'firefox' && !headless, 'Firefox headed produces a different image'); - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'https://github.com/microsoft/playwright/issues/33330'); + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'https://github.com/microsoft/playwright/issues/33330'); const page2 = await page.context().newPage(); await Promise.all([ diff --git a/tests/library/headful.spec.ts b/tests/library/headful.spec.ts index 5832ef44da..d0c9f99450 100644 --- a/tests/library/headful.spec.ts +++ b/tests/library/headful.spec.ts @@ -19,6 +19,7 @@ import { PNG } from 'playwright-core/lib/utilsBundle'; import { expect, playwrightTest as it } from '../config/browserTest'; it.use({ headless: false }); +it.skip(({ channel }) => channel === 'chromium-headless-shell', 'shell is never headed'); it('should have default url when launching browser @smoke', async ({ launchPersistent }) => { const { context } = await launchPersistent(); diff --git a/tests/library/launcher.spec.ts b/tests/library/launcher.spec.ts index 4104c9e747..bb244c0b60 100644 --- a/tests/library/launcher.spec.ts +++ b/tests/library/launcher.spec.ts @@ -44,6 +44,7 @@ it('should kill browser process on timeout after close', async ({ browserType, m it('should throw a friendly error if its headed and there is no xserver on linux running', async ({ mode, browserType, platform, channel }) => { it.skip(platform !== 'linux'); it.skip(mode.startsWith('service')); + it.skip(channel === 'chromium-headless-shell', 'shell is never headed'); const error: Error = await browserType.launch({ headless: false, diff --git a/tests/library/permissions.spec.ts b/tests/library/permissions.spec.ts index 451149878c..4654d70ff2 100644 --- a/tests/library/permissions.spec.ts +++ b/tests/library/permissions.spec.ts @@ -156,8 +156,9 @@ it('should support clipboard read', async ({ page, context, server, browserName, if (browserName !== 'webkit') expect(await getPermission(page, 'clipboard-read')).toBe('prompt'); - if (browserName === 'chromium' && channel === 'chromium-headless-shell') { - // Chromium shows a dialog and does not resolve the promise. + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + if (browserName === 'chromium' && isHeadlessShell) { + // Chromium (but not headless-shell) shows a dialog and does not resolve the promise. const error = await page.evaluate(() => navigator.clipboard.readText()).catch(e => e); expect(error.toString()).toContain('denied'); } diff --git a/tests/library/screenshot.spec.ts b/tests/library/screenshot.spec.ts index 2f8e516963..bdefca6095 100644 --- a/tests/library/screenshot.spec.ts +++ b/tests/library/screenshot.spec.ts @@ -22,8 +22,10 @@ import { verifyViewport } from '../config/utils'; browserTest.describe('page screenshot', () => { browserTest.skip(({ browserName, headless }) => browserName === 'firefox' && !headless, 'Firefox headed produces a different image.'); - browserTest('should run in parallel in multiple pages', async ({ server, contextFactory, browserName, channel }) => { - browserTest.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'https://github.com/microsoft/playwright/issues/33330'); + browserTest('should run in parallel in multiple pages', async ({ server, contextFactory, browserName, headless, channel }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + browserTest.fixme(browserName === 'chromium' && !isHeadlessShell, 'https://github.com/microsoft/playwright/issues/33330'); + const context = await contextFactory(); const N = 5; const pages = await Promise.all(Array(N).fill(0).map(async () => { diff --git a/tests/library/tracing.spec.ts b/tests/library/tracing.spec.ts index 0b3671b575..aecef80a59 100644 --- a/tests/library/tracing.spec.ts +++ b/tests/library/tracing.spec.ts @@ -430,8 +430,9 @@ for (const params of [ } ]) { browserTest(`should produce screencast frames ${params.id}`, async ({ video, contextFactory, browserName, platform, headless, channel }, testInfo) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); browserTest.skip(browserName === 'chromium' && video === 'on', 'Same screencast resolution conflicts'); - browserTest.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'Chromium screencast has a min width issue'); + browserTest.fixme(browserName === 'chromium' && !isHeadlessShell, 'Chromium (but not headless-shell) screencast has a min width issue'); browserTest.fixme(params.id === 'fit' && browserName === 'chromium' && platform === 'darwin', 'High DPI maxes image at 600x600'); browserTest.fixme(params.id === 'fit' && browserName === 'webkit' && platform === 'linux', 'Image size is flaky'); browserTest.fixme(browserName === 'firefox' && !headless, 'Image size is different'); diff --git a/tests/library/video.spec.ts b/tests/library/video.spec.ts index c6b8de24fb..7a21ec3adf 100644 --- a/tests/library/video.spec.ts +++ b/tests/library/video.spec.ts @@ -475,7 +475,8 @@ it.describe('screencast', () => { it('should scale frames down to the requested size ', async ({ browser, browserName, server, headless, channel }, testInfo) => { it.fixme(!headless, 'Fails on headed'); - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'Fails on Chromiums'); + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'Chromium (but not headless shell) has a min width issue'); const context = await browser.newContext({ recordVideo: { @@ -722,9 +723,10 @@ it.describe('screencast', () => { expect(files.length).toBe(1); }); - it('should capture full viewport', async ({ browserType, browserName, isWindows, channel }, testInfo) => { + it('should capture full viewport', async ({ browserType, browserName, isWindows, headless, channel }, testInfo) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/22411' }); - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'The square is not on the video'); + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'The square is not on the video'); it.fixme(browserName === 'firefox' && isWindows, 'https://github.com/microsoft/playwright/issues/14405'); const size = { width: 600, height: 400 }; const browser = await browserType.launch(); @@ -759,7 +761,8 @@ it.describe('screencast', () => { it('should capture full viewport on hidpi', async ({ browserType, browserName, headless, isWindows, isLinux, channel }, testInfo) => { it.info().annotations.push({ type: 'issue', description: 'https://github.com/microsoft/playwright/issues/22411' }); - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'The square is not on the video'); + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'The square is not on the video'); it.fixme(browserName === 'firefox' && isWindows, 'https://github.com/microsoft/playwright/issues/14405'); it.fixme(browserName === 'webkit' && isLinux && !headless, 'https://github.com/microsoft/playwright/issues/22617'); const size = { width: 600, height: 400 }; @@ -797,7 +800,8 @@ it.describe('screencast', () => { it('should work with video+trace', async ({ browser, trace, headless, browserName, channel }, testInfo) => { it.skip(trace === 'on'); it.fixme(!headless, 'different trace screencast image size on all browsers'); - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'different trace screencast image size on Chromium'); + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'different trace screencast image size'); const size = { width: 500, height: 400 }; const traceFile = testInfo.outputPath('trace.zip'); diff --git a/tests/page/page-focus.spec.ts b/tests/page/page-focus.spec.ts index 67e3e883b4..d2db5cf6b9 100644 --- a/tests/page/page-focus.spec.ts +++ b/tests/page/page-focus.spec.ts @@ -119,8 +119,9 @@ it('clicking checkbox should activate it', async ({ page, browserName, headless, it('tab should cycle between single input and browser', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32339' } -}, async ({ page, browserName, channel }) => { - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'Chromium keeps input focused.'); +}, async ({ page, browserName, headless, channel }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'Chromium keeps input focused.'); it.fixme(browserName !== 'chromium'); await page.setContent(` @@ -146,8 +147,9 @@ it('tab should cycle between single input and browser', { it('tab should cycle between document elements and browser', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/32339' } -}, async ({ page, browserName, channel }) => { - it.fixme(browserName === 'chromium' && channel !== 'chromium-headless-shell', 'Chromium keeps last input focused.'); +}, async ({ page, browserName, headless, channel }) => { + const isHeadlessShell = channel === 'chromium-headless-shell' || (!channel && headless); + it.fixme(browserName === 'chromium' && !isHeadlessShell, 'Chromium keeps last input focused.'); it.fixme(browserName !== 'chromium'); await page.setContent(`