Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions packages/playwright-core/src/server/registry/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import path from 'path';

import { wrapInASCIIBox } from '@utils/ascii';
import { hostPlatform, isOfficiallySupportedPlatform } from '@utils/hostPlatform';
import { getLinuxDistributionInfoSync, isDebianBasedDistro } from '@utils/linuxUtils';
import { spawnAsync } from '@utils/spawnAsync';
import { getPlaywrightVersion } from '../userAgent';
import { deps } from './nativeDeps';
Expand Down Expand Up @@ -90,6 +91,19 @@ export async function installDependenciesWindows(targets: Set<DependencyGroup>,
}

export async function installDependenciesLinux(targets: Set<DependencyGroup>, dryRun: boolean) {
if (!isDebianBasedDistro()) {
const distroId = getLinuxDistributionInfoSync()?.id || 'this distribution';
const message = [
`'install-deps' can only install system dependencies on Debian and Ubuntu.`,
`On ${distroId}, install the libraries Playwright's browsers need using your`,
`distribution's package manager (e.g. zypper, dnf or pacman).`,
].join('\n');
if (dryRun) {
console.log(message); // eslint-disable-line no-console
return;
}
throw new Error(message);
}
const libraries: string[] = [];
const platform = hostPlatform;
if (!isOfficiallySupportedPlatform)
Expand Down Expand Up @@ -229,7 +243,10 @@ export async function validateDependenciesLinux(sdkLanguage: string, linuxLddDir
// Check Ubuntu version.
const missingPackages = new Set();

const libraryToPackageNameMapping = deps[hostPlatform] ? {
// Only translate libraries to apt package names on Debian/Ubuntu; on other distributions the
// names would be wrong, so we report the missing libraries instead (see the `else` branch below).
const isDebianBased = isDebianBasedDistro();
const libraryToPackageNameMapping = isDebianBased && deps[hostPlatform] ? {
...(deps[hostPlatform]?.lib2package || {}),
...MANUAL_LIBRARY_TO_PACKAGE_NAME_UBUNTU,
} : {};
Expand Down Expand Up @@ -285,13 +302,24 @@ export async function validateDependenciesLinux(sdkLanguage: string, linuxLddDir
`<3 Playwright Team`,
]);
} else {
// Unhappy path: we either run on unknown distribution, or we failed to resolve all missing
// libraries to package names.
// Unhappy path: we either run on a non-Debian distribution, or we failed to resolve all
// missing libraries to package names.
// Print missing libraries only:
errorLines.push(...[
`Missing libraries:`,
...[...allMissingDeps].map(dep => ' ' + dep),
]);
if (!isDebianBased) {
const distroId = getLinuxDistributionInfoSync()?.id || 'this distribution';
errorLines.push(...[
``,
`Playwright's automated dependency installer ('install-deps') supports only Debian and`,
`Ubuntu. On ${distroId}, install the packages that provide the libraries listed above`,
`using your distribution's package manager (e.g. zypper, dnf or pacman).`,
``,
`<3 Playwright Team`,
]);
}
}

throw new Error('\n' + wrapInASCIIBox(errorLines.join('\n'), 1));
Expand All @@ -313,7 +341,7 @@ async function executablesOrSharedLibraries(directoryPath: string): Promise<stri
return [];
const allPaths = (await fs.promises.readdir(directoryPath)).map(file => path.resolve(directoryPath, file));
const allStats = await Promise.all(allPaths.map(aPath => fs.promises.stat(aPath)));
const filePaths = allPaths.filter((aPath, index) => (allStats[index] as any).isFile());
const filePaths = allPaths.filter((aPath, index) => allStats[index].isFile());

const executablersOrLibraries = (await Promise.all(filePaths.map(async filePath => {
const basename = path.basename(filePath).toLowerCase();
Expand Down
21 changes: 13 additions & 8 deletions packages/playwright-core/src/tools/mcp/browserFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import crypto from 'crypto';
import fs from 'fs';
import path from 'path';

import { getLinuxDistributionInfoSync, isDebianBasedDistro } from '@utils/linuxUtils';

import { playwright } from '../../inprocess';
import { defaultCacheDirectory } from '../../server/registry/index';
import { testDebug } from './log';
Expand Down Expand Up @@ -78,8 +80,7 @@ export interface BrowserContextFactory {

function browserInfo(browser: playwrightTypes.Browser, config: FullConfig): BrowserInfo {
return {
// eslint-disable-next-line no-restricted-syntax
guid: (browser as any)._guid,
guid: (browser as unknown as { _guid: string })._guid,
browserName: config.browser.browserName,
launchOptions: config.browser.launchOptions,
userDataDir: config.browser.userDataDir
Expand Down Expand Up @@ -121,8 +122,7 @@ async function createRemoteBrowser(config: FullConfig): Promise<BrowserWithInfo>
// shape.
const remote = config.browser.remoteEndpoint!;
// `remoteHeaders` is for back-compat, `remoteEndpoint.headers` takes precedence.
// eslint-disable-next-line no-restricted-syntax
const remoteHeaders = (config.browser as any).remoteHeaders as Record<string, string> | undefined;
const remoteHeaders = (config.browser as { remoteHeaders?: Record<string, string> }).remoteHeaders;
const remoteOptions = typeof remote === 'string'
? { endpoint: remote, headers: remoteHeaders }
: { ...remote, headers: { ...remoteHeaders, ...remote.headers } };
Expand Down Expand Up @@ -181,11 +181,16 @@ async function createPersistentBrowser(config: FullConfig, clientInfo: ClientInf
const browserContext = await browserType.launchPersistentContext(userDataDir, launchOptions);
const browser = browserContext.browser()!;
return browser;
} catch (error: any) {
} catch (error: unknown) {
if (!(error instanceof Error))
throw error;
throwIfExecutableMissing(error, config);
if (error.message.includes('cannot open shared object file: No such file or directory')) {
const browserName = launchOptions.channel ?? config.browser.browserName;
throw new Error(`Missing system dependencies required to run browser ${browserName}. Install them with: sudo npx playwright install-deps ${browserName}`);
if (isDebianBasedDistro())
throw new Error(`Missing system dependencies required to run browser ${browserName}. Install them with: sudo npx playwright install-deps ${browserName}`);
const distroId = getLinuxDistributionInfoSync()?.id || 'this distribution';
throw new Error(`Missing system dependencies required to run browser ${browserName}. On ${distroId}, install the libraries it needs using your distribution's package manager (e.g. zypper, dnf or pacman).`);
}
if (error.message.includes('ProcessSingleton') || error.message.includes('exitCode=21'))
throw new Error(`Browser is already in use for ${userDataDir}, use --isolated to run multiple instances of the same browser`);
Expand Down Expand Up @@ -229,8 +234,8 @@ export function isProfileLocked(userDataDir: string): boolean {
const fd = fs.openSync(lockPath, 'r+');
fs.closeSync(fd);
return false;
} catch (e: any) {
return e.code !== 'ENOENT';
} catch (e: unknown) {
return (e as NodeJS.ErrnoException).code !== 'ENOENT';
}
}

Expand Down
17 changes: 16 additions & 1 deletion packages/utils/linuxUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,10 @@ let didFailToReadOSRelease = false;
let osRelease: {
id: string,
version: string,
idLike: string,
} | undefined;

export function getLinuxDistributionInfoSync(): { id: string, version: string } | undefined {
export function getLinuxDistributionInfoSync(): { id: string, version: string, idLike: string } | undefined {
if (process.platform !== 'linux')
return undefined;
if (!osRelease && !didFailToReadOSRelease) {
Expand All @@ -35,6 +36,7 @@ export function getLinuxDistributionInfoSync(): { id: string, version: string }
osRelease = {
id: fields.get('id') ?? '',
version: fields.get('version_id') ?? '',
idLike: fields.get('id_like') ?? '',
};
} catch (e) {
didFailToReadOSRelease = true;
Expand All @@ -43,6 +45,19 @@ export function getLinuxDistributionInfoSync(): { id: string, version: string }
return osRelease;
}

// Distributions whose system dependencies Playwright can install or describe with apt.
// ID_LIKE covers derivatives (e.g. Pop!_OS, Mint, Kali) that set it to "debian"/"ubuntu".
const DEBIAN_BASED_DISTRO_IDS = new Set(['ubuntu', 'debian', 'pop', 'neon', 'tuxedo', 'linuxmint', 'raspbian']);

export function isDebianBasedDistro(): boolean {
const info = getLinuxDistributionInfoSync();
if (!info)
return false;
if (DEBIAN_BASED_DISTRO_IDS.has(info.id))
return true;
return info.idLike.split(' ').some(like => like === 'debian' || like === 'ubuntu');
}

function parseOSReleaseText(osReleaseText: string): Map<string, string> {
const fields = new Map();
for (const line of osReleaseText.split('\n')) {
Expand Down