diff --git a/packages/playwright-core/bin/PrintDeps.exe b/packages/playwright-core/bin/PrintDeps.exe deleted file mode 100644 index eb8ddf4d7b..0000000000 Binary files a/packages/playwright-core/bin/PrintDeps.exe and /dev/null differ diff --git a/packages/playwright-core/bin/README.md b/packages/playwright-core/bin/README.md deleted file mode 100644 index 2426643de5..0000000000 --- a/packages/playwright-core/bin/README.md +++ /dev/null @@ -1,2 +0,0 @@ -See building instructions at [`/browser_patches/winldd/README.md`](../../../browser_patches/winldd/README.md) - diff --git a/packages/playwright-core/browsers.json b/packages/playwright-core/browsers.json index 0b673fef08..a163c7a607 100644 --- a/packages/playwright-core/browsers.json +++ b/packages/playwright-core/browsers.json @@ -52,6 +52,11 @@ "mac12-arm64": "1010" } }, + { + "name": "winldd", + "revision": "1007", + "installByDefault": false + }, { "name": "android", "revision": "1001", diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index 7ce1c4f928..5cd941d5d4 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -116,6 +116,9 @@ function checkBrowsersToInstall(args: string[], options: { noShell?: boolean, on } } + if (process.platform === 'win32') + executables.push(registry.findExecutable('winldd')!); + if (faultyArguments.length) throw new Error(`Invalid installation targets: ${faultyArguments.map(name => `'${name}'`).join(', ')}. Expecting one of: ${suggestedBrowsersToInstall()}`); return executables; diff --git a/packages/playwright-core/src/server/registry/dependencies.ts b/packages/playwright-core/src/server/registry/dependencies.ts index 44f56f3d2f..60ef80d846 100644 --- a/packages/playwright-core/src/server/registry/dependencies.ts +++ b/packages/playwright-core/src/server/registry/dependencies.ts @@ -21,7 +21,7 @@ import childProcess from 'child_process'; import * as utils from '../../utils'; import { spawnAsync } from '../../utils/spawnAsync'; import { hostPlatform, isOfficiallySupportedPlatform } from '../../utils/hostPlatform'; -import { buildPlaywrightCLICommand } from '.'; +import { buildPlaywrightCLICommand, registry } from '.'; import { deps } from './nativeDeps'; import { getPlaywrightVersion } from '../../utils/userAgent'; @@ -122,12 +122,12 @@ export async function installDependenciesLinux(targets: Set, dr }); } -export async function validateDependenciesWindows(windowsExeAndDllDirectories: string[]) { +export async function validateDependenciesWindows(sdkLanguage: string, windowsExeAndDllDirectories: string[]) { const directoryPaths = windowsExeAndDllDirectories; const lddPaths: string[] = []; for (const directoryPath of directoryPaths) lddPaths.push(...(await executablesOrSharedLibraries(directoryPath))); - const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(lddPath))); + const allMissingDeps = await Promise.all(lddPaths.map(lddPath => missingFileDependenciesWindows(sdkLanguage, lddPath))); const missingDeps: Set = new Set(); for (const deps of allMissingDeps) { for (const dep of deps) @@ -302,8 +302,8 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise> { - const executable = path.join(__dirname, '..', '..', '..', 'bin', 'PrintDeps.exe'); +async function missingFileDependenciesWindows(sdkLanguage: string, filePath: string): Promise> { + const executable = registry.findExecutable('winldd')!.executablePathOrDie(sdkLanguage); const dirname = path.dirname(filePath); const { stdout, code } = await spawnAsync(executable, [filePath], { cwd: dirname, diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index f12d5ceb4c..c4d7f2ffbb 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -79,6 +79,11 @@ const EXECUTABLE_PATHS = { 'mac': ['ffmpeg-mac'], 'win': ['ffmpeg-win64.exe'], }, + 'winldd': { + 'linux': undefined, + 'mac': undefined, + 'win': ['PrintDeps.exe'], + }, }; type DownloadPaths = Record; @@ -315,6 +320,35 @@ const DOWNLOAD_PATHS: Record = { 'mac15-arm64': 'builds/ffmpeg/%s/ffmpeg-mac-arm64.zip', 'win64': 'builds/ffmpeg/%s/ffmpeg-win64.zip', }, + 'winldd': { + '': undefined, + 'ubuntu18.04-x64': undefined, + 'ubuntu20.04-x64': undefined, + 'ubuntu22.04-x64': undefined, + 'ubuntu24.04-x64': undefined, + 'ubuntu18.04-arm64': undefined, + 'ubuntu20.04-arm64': undefined, + 'ubuntu22.04-arm64': undefined, + 'ubuntu24.04-arm64': undefined, + 'debian11-x64': undefined, + 'debian11-arm64': undefined, + 'debian12-x64': undefined, + 'debian12-arm64': undefined, + 'mac10.13': undefined, + 'mac10.14': undefined, + 'mac10.15': undefined, + 'mac11': undefined, + 'mac11-arm64': undefined, + 'mac12': undefined, + 'mac12-arm64': undefined, + 'mac13': undefined, + 'mac13-arm64': undefined, + 'mac14': undefined, + 'mac14-arm64': undefined, + 'mac15': undefined, + 'mac15-arm64': undefined, + 'win64': 'builds/winldd/%s/winldd-win64.zip', + }, 'android': { '': 'builds/android/%s/android.zip', 'ubuntu18.04-x64': undefined, @@ -442,7 +476,7 @@ function readDescriptors(browsersJSON: BrowsersJSON): BrowsersJSONDescriptor[] { } export type BrowserName = 'chromium' | 'firefox' | 'webkit' | 'bidi'; -type InternalTool = 'ffmpeg' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | 'android'; +type InternalTool = 'ffmpeg' | 'winldd' | 'firefox-beta' | 'chromium-tip-of-tree' | 'chromium-headless-shell' | 'chromium-tip-of-tree-headless-shell' | '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'; const allDownloadable = ['android', 'chromium', 'firefox', 'webkit', 'ffmpeg', 'firefox-beta', 'chromium-tip-of-tree', 'chromium-headless-shell', 'chromium-tip-of-tree-headless-shell']; @@ -772,6 +806,22 @@ export class Registry { _dependencyGroup: 'tools', _isHermeticInstallation: true, }); + const winldd = descriptors.find(d => d.name === 'winldd')!; + const winlddExecutable = findExecutablePath(winldd.dir, 'winldd'); + this._executables.push({ + type: 'tool', + name: 'winldd', + browserName: undefined, + directory: winldd.dir, + executablePath: () => winlddExecutable, + executablePathOrDie: (sdkLanguage: string) => executablePathOrDie('winldd', winlddExecutable, winldd.installByDefault, sdkLanguage), + installType: process.platform === 'win32' ? 'download-by-default' : 'none', + _validateHostRequirements: () => Promise.resolve(), + downloadURLs: this._downloadURLs(winldd), + _install: () => this._downloadExecutable(winldd, winlddExecutable), + _dependencyGroup: 'tools', + _isHermeticInstallation: true, + }); const android = descriptors.find(d => d.name === 'android')!; this._executables.push({ type: 'tool', @@ -944,7 +994,7 @@ export class Registry { if (os.platform() === 'linux') return await validateDependenciesLinux(sdkLanguage, linuxLddDirectories.map(d => path.join(browserDirectory, d)), dlOpenLibraries); if (os.platform() === 'win32' && os.arch() === 'x64') - return await validateDependenciesWindows(windowsExeAndDllDirectories.map(d => path.join(browserDirectory, d))); + return await validateDependenciesWindows(sdkLanguage, windowsExeAndDllDirectories.map(d => path.join(browserDirectory, d))); } async installDeps(executablesToInstallDeps: Executable[], dryRun: boolean) { @@ -1265,6 +1315,8 @@ export async function installBrowsersForNpmInstall(browsers: string[]) { return false; } const executables: Executable[] = []; + if (process.platform === 'win32') + executables.push(registry.findExecutable('winldd')!); for (const browserName of browsers) { const executable = registry.findExecutable(browserName); if (!executable || executable.installType === 'none') diff --git a/tests/installation/npmTest.ts b/tests/installation/npmTest.ts index 4801f967e8..4e39fb8c98 100644 --- a/tests/installation/npmTest.ts +++ b/tests/installation/npmTest.ts @@ -31,22 +31,22 @@ export const TMP_WORKSPACES = path.join(os.platform() === 'darwin' ? '/tmp' : os const debug = debugLogger('itest'); const expect = _expect.extend({ - toHaveLoggedSoftwareDownload(received: any, browsers: ('chromium' | 'chromium-headless-shell' | 'firefox' | 'webkit' | 'ffmpeg')[]) { + toHaveLoggedSoftwareDownload(received: string, browsers: ('chromium' | 'chromium-headless-shell' | 'firefox' | 'webkit' | 'winldd' |'ffmpeg')[]) { if (typeof received !== 'string') throw new Error(`Expected argument to be a string.`); const downloaded = new Set(); 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); + const match = received.substring(index).match(/(chromium|chromium headless shell|firefox|webkit|winldd|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))) { + const expected = new Set(browsers); + if (expected.size === downloaded.size && [...expected].every(browser => downloaded.has(browser))) { return { pass: true, message: () => 'Expected not to download browsers, but did.' diff --git a/tests/installation/playwright-cdn.spec.ts b/tests/installation/playwright-cdn.spec.ts index 9b74345025..776aec42ad 100644 --- a/tests/installation/playwright-cdn.spec.ts +++ b/tests/installation/playwright-cdn.spec.ts @@ -37,12 +37,14 @@ const parsedDownloads = (rawLogs: string) => { test.use({ isolateBrowsers: true }); +const extraInstalledSoftware = process.platform === 'win32' ? ['winldd' as const] : []; + for (const cdn of CDNS) { test(`playwright cdn failover should work (${cdn})`, async ({ exec, checkInstalledSoftwareOnDisk }) => { 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', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk((['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware])); 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 064568f3e2..2825a9a44f 100755 --- a/tests/installation/playwright-cli-install-should-work.spec.ts +++ b/tests/installation/playwright-cli-install-should-work.spec.ts @@ -19,19 +19,21 @@ import path from 'path'; test.use({ isolateBrowsers: true }); +const extraInstalledSoftware = process.platform === 'win32' ? ['winldd' as const] : []; + test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); await test.step('playwright install chromium', async () => { const result = await exec('npx playwright install chromium'); - expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg']); + expect(result).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); }); await test.step('playwright install', async () => { const result = await exec('npx playwright install'); expect(result).toHaveLoggedSoftwareDownload(['firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); }); await exec('node sanity.js playwright', { env: { PLAYWRIGHT_BROWSERS_PATH: '0' } }); @@ -51,9 +53,9 @@ test('install command should work', async ({ exec, checkInstalledSoftwareOnDisk test('should be able to remove browsers', async ({ exec, checkInstalledSoftwareOnDisk }) => { await exec('npm i playwright'); await exec('npx playwright install chromium'); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg']); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', ...extraInstalledSoftware]); await exec('npx playwright uninstall'); - await checkInstalledSoftwareOnDisk([]); + await checkInstalledSoftwareOnDisk([...extraInstalledSoftware]); }); test('should print the right install command without browsers', async ({ exec }) => { diff --git a/tests/installation/playwright-packages-install-behavior.spec.ts b/tests/installation/playwright-packages-install-behavior.spec.ts index 9ace1eaa67..2474d889eb 100755 --- a/tests/installation/playwright-packages-install-behavior.spec.ts +++ b/tests/installation/playwright-packages-install-behavior.spec.ts @@ -18,12 +18,14 @@ import { test, expect } from './npmTest'; test.use({ isolateBrowsers: true }); +const extraInstalledSoftware = process.platform === 'win32' ? ['winldd' as const] : []; + for (const browser of ['chromium', 'firefox', 'webkit']) { test(`playwright-${browser} should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const pkg = `playwright-${browser}`; const result = await exec('npm i --foreground-scripts', pkg); const browserName = pkg.split('-')[1]; - const expectedSoftware = [browserName]; + const expectedSoftware = [browserName, ...extraInstalledSoftware]; if (browserName === 'chromium') expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); expect(result).toHaveLoggedSoftwareDownload(expectedSoftware as any); @@ -37,7 +39,7 @@ for (const browser of ['chromium', 'firefox', 'webkit']) { for (const browser of ['chromium', 'firefox', 'webkit']) { test(`@playwright/browser-${browser} should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => { const pkg = `@playwright/browser-${browser}`; - const expectedSoftware = [browser]; + const expectedSoftware = [browser, ...extraInstalledSoftware]; if (browser === 'chromium') expectedSoftware.push('chromium-headless-shell', 'ffmpeg'); @@ -69,8 +71,8 @@ test(`playwright should work`, async ({ exec, checkInstalledSoftwareOnDisk }) => await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); await exec('node sanity.js playwright chromium firefox webkit'); await exec('node esm-playwright.mjs'); @@ -81,8 +83,8 @@ test(`playwright should work with chromium --no-shell`, async ({ exec, checkInst expect(result1).toHaveLoggedSoftwareDownload([]); await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install chromium --no-shell'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg']); - await checkInstalledSoftwareOnDisk(['chromium', 'ffmpeg']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'ffmpeg', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'ffmpeg', ...extraInstalledSoftware]); }); test(`playwright should work with chromium --only-shell`, async ({ exec, checkInstalledSoftwareOnDisk }) => { @@ -90,8 +92,8 @@ test(`playwright should work with chromium --only-shell`, async ({ exec, checkIn expect(result1).toHaveLoggedSoftwareDownload([]); await checkInstalledSoftwareOnDisk([]); const result2 = await exec('npx playwright install --only-shell'); - expect(result2).toHaveLoggedSoftwareDownload(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); }); test('@playwright/test should work', async ({ exec, checkInstalledSoftwareOnDisk }) => { @@ -102,8 +104,8 @@ test('@playwright/test should work', async ({ exec, checkInstalledSoftwareOnDisk 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', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); - await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit']); + expect(result2).toHaveLoggedSoftwareDownload(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); + await checkInstalledSoftwareOnDisk(['chromium', 'chromium-headless-shell', 'ffmpeg', 'firefox', 'webkit', ...extraInstalledSoftware]); await exec('node sanity.js @playwright/test chromium firefox webkit'); await exec('node', 'esm-playwright-test.mjs'); diff --git a/tests/installation/playwright-test-plugin.spec.ts b/tests/installation/playwright-test-plugin.spec.ts index 5762b49900..fe4d24cd93 100755 --- a/tests/installation/playwright-test-plugin.spec.ts +++ b/tests/installation/playwright-test-plugin.spec.ts @@ -26,7 +26,6 @@ function patchPackageJsonForPreReleaseIfNeeded(tmpWorkspace: string) { // Workaround per https://stackoverflow.com/questions/71479750/npm-install-pre-release-versions-for-peer-dependency. const pkg = JSON.parse(fs.readFileSync(path.resolve(tmpWorkspace, 'package.json'), 'utf-8')); if (pkg.dependencies['@playwright/test'].match(/\d+\.\d+-\w+/)) { - console.log(`Setting overrides in package.json to make pre-release version of peer dependency work.`); pkg.overrides = { '@playwright/test': '$@playwright/test' }; fs.writeFileSync(path.resolve(tmpWorkspace, 'package.json'), JSON.stringify(pkg, null, 2)); }