Skip to content
Open
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
44 changes: 44 additions & 0 deletions .github/workflows/tests_rhel.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: RHEL / Rocky Linux 9 (community)

on:
push:
branches:
- main
- release-*
pull_request:
paths:
- packages/utils/hostPlatform.ts
- packages/utils/linuxUtils.ts
- packages/playwright-core/src/server/registry/nativeDeps.ts
- packages/playwright-core/src/server/registry/dependencies.ts
- packages/playwright-core/src/server/registry/index.ts

jobs:
rhel9-chromium:
name: Rocky Linux 9 — Chromium smoke test
runs-on: ubuntu-latest
container:
image: rockylinux:9

steps:
- uses: actions/checkout@v6

- name: Enable Node.js 20 module stream
run: |
dnf module enable -y nodejs:20
dnf install -y nodejs

- name: Install npm dependencies
run: npm ci

- name: Build
run: npm run build

- name: Install OS-level Chromium dependencies
run: npx playwright install-deps chromium

- name: Install Chromium browser
run: npx playwright install chromium

- name: Chromium headless smoke test
run: node utils/linux-browser-dependencies/inside_docker/rhel9-smoke-test.js
79 changes: 64 additions & 15 deletions packages/playwright-core/src/server/registry/dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ import os from 'os';
import path from 'path';

import { wrapInASCIIBox } from '@utils/ascii';
import { hostPlatform, isOfficiallySupportedPlatform } from '@utils/hostPlatform';
import { hostPlatform, isOfficiallySupportedPlatform, isRhelFamilyDistro } from '@utils/hostPlatform';
import { getLinuxDistributionInfoSync } from '@utils/linuxUtils';
import { spawnAsync } from '@utils/spawnAsync';
import { getPlaywrightVersion } from '../userAgent';
import { deps } from './nativeDeps';
Expand Down Expand Up @@ -92,8 +93,18 @@ export async function installDependenciesWindows(targets: Set<DependencyGroup>,
export async function installDependenciesLinux(targets: Set<DependencyGroup>, dryRun: boolean) {
const libraries: string[] = [];
const platform = hostPlatform;
if (!isOfficiallySupportedPlatform)
console.warn(`BEWARE: your OS is not officially supported by Playwright; installing dependencies for ${platform} as a fallback.`); // eslint-disable-line no-console
if (!isOfficiallySupportedPlatform) {
if (platform === '<unknown>') {
// Provide an actionable message for RHEL 8 users who fell through to <unknown>.
const distroInfo = getLinuxDistributionInfoSync();
if (distroInfo && isRhelFamilyDistro(distroInfo) && parseInt(distroInfo.version, 10) < 9)
throw new Error('RHEL/Rocky Linux 8 and earlier are not supported by Playwright. Please upgrade to RHEL 9+ or use a supported platform.');
}
if (platform.startsWith('rhel'))
console.warn(`BEWARE: your OS is not officially supported by Playwright; installing dependencies for ${platform} on a best-effort basis.`); // eslint-disable-line no-console
else
console.warn(`BEWARE: your OS is not officially supported by Playwright; installing dependencies for ${platform} as a fallback.`); // eslint-disable-line no-console
}
for (const target of targets) {
const info = deps[platform];
if (!info) {
Expand All @@ -105,12 +116,37 @@ export async function installDependenciesLinux(targets: Set<DependencyGroup>, dr
const uniqueLibraries = Array.from(new Set(libraries));
if (!dryRun)
console.log(`Installing dependencies...`); // eslint-disable-line no-console
const commands: string[] = [];
commands.push('apt-get update');
commands.push(['apt-get', 'install', '-y', '--no-install-recommends',
...uniqueLibraries,
].join(' '));
const { command, args, elevatedPermissions } = await transformCommandsForRoot(commands);
if (platform.startsWith('rhel')) {
await installDependenciesLinuxRhel(uniqueLibraries, dryRun);
} else {
const commands: string[] = [];
commands.push('apt-get update');
commands.push(['apt-get', 'install', '-y', '--no-install-recommends',
...uniqueLibraries,
].join(' '));
const { command, args, elevatedPermissions } = await transformCommandsForRoot(commands);
if (dryRun) {
console.log(`${command} ${quoteProcessArgs(args).join(' ')}`); // eslint-disable-line no-console
return;
}
if (elevatedPermissions)
console.log('Switching to root user to install dependencies...'); // eslint-disable-line no-console
const child = childProcess.spawn(command, args, { stdio: 'inherit' });
await new Promise<void>((resolve, reject) => {
child.on('exit', (code: number) => code === 0 ? resolve() : reject(new Error(`Installation process exited with code: ${code}`)));
child.on('error', reject);
});
}
}

async function installDependenciesLinuxRhel(libraries: string[], dryRun: boolean) {
const dnfOrYum = await findDnfOrYum();
const weakDepsFlag = dnfOrYum === 'dnf' ? ' --setopt=install_weak_deps=False' : '';
const commands = [
`${dnfOrYum} install -y epel-release`,
`${dnfOrYum} install -y${weakDepsFlag} ${libraries.join(' ')}`,
];
const { command, args, elevatedPermissions } = await transformCommandsForRoot(commands);
if (dryRun) {
console.log(`${command} ${quoteProcessArgs(args).join(' ')}`); // eslint-disable-line no-console
return;
Expand All @@ -119,11 +155,21 @@ export async function installDependenciesLinux(targets: Set<DependencyGroup>, dr
console.log('Switching to root user to install dependencies...'); // eslint-disable-line no-console
const child = childProcess.spawn(command, args, { stdio: 'inherit' });
await new Promise<void>((resolve, reject) => {
child.on('exit', (code: number) => code === 0 ? resolve() : reject(new Error(`Installation process exited with code: ${code}`)));
child.on('exit', (code: number) => code === 0 ? resolve() : reject(new Error(`Installation process exited with code: ${code}. Check that EPEL is accessible and that all required packages are available for your architecture.`)));
child.on('error', reject);
});
}

async function findDnfOrYum(): Promise<string> {
const dnfResult = await spawnAsync('which', ['dnf'], {});
if (dnfResult.code === 0)
return 'dnf';
const yumResult = await spawnAsync('which', ['yum'], {});
if (yumResult.code === 0)
return 'yum';
throw new Error("Neither 'dnf' nor 'yum' found on PATH. Please install one and retry.");
}

export async function validateDependenciesWindows(sdkLanguage: string, windowsExeAndDllDirectories: string[]) {
const directoryPaths = windowsExeAndDllDirectories;
const lddPaths: string[] = [];
Expand Down Expand Up @@ -208,7 +254,7 @@ export async function validateDependenciesLinux(sdkLanguage: string, linuxLddDir

const libraryToPackageNameMapping = deps[hostPlatform] ? {
...(deps[hostPlatform]?.lib2package || {}),
...MANUAL_LIBRARY_TO_PACKAGE_NAME_UBUNTU,
...(hostPlatform.startsWith('rhel') ? {} : MANUAL_LIBRARY_TO_PACKAGE_NAME_UBUNTU),
} : {};
// Translate missing dependencies to package names to install with apt.
for (const missingDep of missingDeps) {
Expand All @@ -220,6 +266,9 @@ export async function validateDependenciesLinux(sdkLanguage: string, linuxLddDir
}

const maybeSudo = process.getuid?.() && os.platform() !== 'win32' ? 'sudo ' : '';
const isRhel = hostPlatform.startsWith('rhel');
const pkgMgr = isRhel ? 'dnf' : 'apt';
const pkgInstallCmd = isRhel ? 'dnf install' : 'apt-get install';
const dockerInfo = readDockerVersionSync();
const errorLines = [
`Host system is missing dependencies to run browsers.`,
Expand All @@ -242,9 +291,9 @@ export async function validateDependenciesLinux(sdkLanguage: string, linuxLddDir
``,
` ${maybeSudo}${buildPlaywrightCLICommand(sdkLanguage, 'install-deps')}`,
``,
`- (alternative 2) use apt inside Docker:`,
`- (alternative 2) use ${pkgMgr} inside Docker:`,
``,
` ${maybeSudo}apt-get install ${[...missingPackages].join('\\\n ')}`,
` ${maybeSudo}${pkgInstallCmd} ${[...missingPackages].join('\\\n ')}`,
``,
`<3 Playwright Team`,
]);
Expand All @@ -256,8 +305,8 @@ export async function validateDependenciesLinux(sdkLanguage: string, linuxLddDir
``,
` ${maybeSudo}${buildPlaywrightCLICommand(sdkLanguage, 'install-deps')}`,
``,
`Alternatively, use apt:`,
` ${maybeSudo}apt-get install ${[...missingPackages].join('\\\n ')}`,
`Alternatively, use ${pkgMgr}:`,
` ${maybeSudo}${pkgInstallCmd} ${[...missingPackages].join('\\\n ')}`,
``,
`<3 Playwright Team`,
]);
Expand Down
20 changes: 20 additions & 0 deletions packages/playwright-core/src/server/registry/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'debian13-x64': cftUrl('linux64/chrome-linux64.zip'),
'debian13-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'rhel9-x64': cftUrl('linux64/chrome-linux64.zip'),
'rhel9-arm64': 'builds/chromium/%s/chromium-linux-arm64.zip',
'mac10.13': cftUrl('mac-x64/chrome-mac-x64.zip'),
'mac10.14': cftUrl('mac-x64/chrome-mac-x64.zip'),
'mac10.15': cftUrl('mac-x64/chrome-mac-x64.zip'),
Expand Down Expand Up @@ -192,6 +194,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'debian13-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
'debian13-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'rhel9-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
'rhel9-arm64': 'builds/chromium/%s/chromium-headless-shell-linux-arm64.zip',
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
Expand Down Expand Up @@ -225,6 +229,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'debian13-x64': cftUrl('linux64/chrome-linux64.zip'),
'debian13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'rhel9-x64': cftUrl('linux64/chrome-linux64.zip'),
'rhel9-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-linux-arm64.zip',
'mac10.13': cftUrl('mac-x64/chrome-mac-x64.zip'),
'mac10.14': cftUrl('mac-x64/chrome-mac-x64.zip'),
'mac10.15': cftUrl('mac-x64/chrome-mac-x64.zip'),
Expand Down Expand Up @@ -258,6 +264,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'debian13-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
'debian13-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'rhel9-x64': cftUrl('linux64/chrome-headless-shell-linux64.zip'),
'rhel9-arm64': 'builds/chromium-tip-of-tree/%s/chromium-tip-of-tree-headless-shell-linux-arm64.zip',
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
Expand Down Expand Up @@ -291,6 +299,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/firefox/%s/firefox-debian-12-arm64.zip',
'debian13-x64': 'builds/firefox/%s/firefox-debian-13.zip',
'debian13-arm64': 'builds/firefox/%s/firefox-debian-13-arm64.zip',
'rhel9-x64': undefined,
'rhel9-arm64': undefined,
'mac10.13': 'builds/firefox/%s/firefox-mac.zip',
'mac10.14': 'builds/firefox/%s/firefox-mac.zip',
'mac10.15': 'builds/firefox/%s/firefox-mac.zip',
Expand Down Expand Up @@ -324,6 +334,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip',
'debian13-x64': 'builds/firefox-beta/%s/firefox-beta-debian-12.zip',
'debian13-arm64': 'builds/firefox-beta/%s/firefox-beta-debian-12-arm64.zip',
'rhel9-x64': undefined,
'rhel9-arm64': undefined,
'mac10.13': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac10.14': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
'mac10.15': 'builds/firefox-beta/%s/firefox-beta-mac.zip',
Expand Down Expand Up @@ -357,6 +369,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/webkit/%s/webkit-debian-12-arm64.zip',
'debian13-x64': 'builds/webkit/%s/webkit-debian-13.zip',
'debian13-arm64': 'builds/webkit/%s/webkit-debian-13-arm64.zip',
'rhel9-x64': undefined,
'rhel9-arm64': undefined,
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
Expand Down Expand Up @@ -390,6 +404,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'debian13-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'debian13-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'rhel9-x64': 'builds/ffmpeg/%s/ffmpeg-linux.zip',
'rhel9-arm64': 'builds/ffmpeg/%s/ffmpeg-linux-arm64.zip',
'mac10.13': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.14': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
'mac10.15': 'builds/ffmpeg/%s/ffmpeg-mac.zip',
Expand Down Expand Up @@ -423,6 +439,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': undefined,
'debian13-x64': undefined,
'debian13-arm64': undefined,
'rhel9-x64': undefined,
'rhel9-arm64': undefined,
'mac10.13': undefined,
'mac10.14': undefined,
'mac10.15': undefined,
Expand Down Expand Up @@ -456,6 +474,8 @@ const DOWNLOAD_PATHS: Record<string, DownloadPaths> = {
'debian12-arm64': 'builds/android/%s/android.zip',
'debian13-x64': 'builds/android/%s/android.zip',
'debian13-arm64': 'builds/android/%s/android.zip',
'rhel9-x64': undefined,
'rhel9-arm64': undefined,
'mac10.13': 'builds/android/%s/android.zip',
'mac10.14': 'builds/android/%s/android.zip',
'mac10.15': 'builds/android/%s/android.zip',
Expand Down
60 changes: 60 additions & 0 deletions packages/playwright-core/src/server/registry/nativeDeps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1281,3 +1281,63 @@ deps['debian13-arm64'] = {
...deps['debian13-x64'].lib2package,
},
};

// Generated by: utils/linux-browser-dependencies/run.sh rockylinux:9
deps['rhel9-x64'] = {
tools: [],
firefox: [],
webkit: [],
chromium: [
'alsa-lib',
'at-spi2-atk',
'at-spi2-core',
'atk',
'cairo',
'cups-libs',
'dbus-libs',
'libX11',
'libXcomposite',
'libXdamage',
'libXext',
'libXfixes',
'libXrandr',
'libxcb',
'libxkbcommon',
'mesa-libgbm',
'nspr',
'nss',
'nss-util',
'pango',
],
lib2package: {
'libasound.so.2': 'alsa-lib',
'libatk-1.0.so.0': 'atk',
'libatk-bridge-2.0.so.0': 'at-spi2-atk',
'libatspi.so.0': 'at-spi2-core',
'libcairo.so.2': 'cairo',
'libcups.so.2': 'cups-libs',
'libdbus-1.so.3': 'dbus-libs',
'libgbm.so.1': 'mesa-libgbm',
'libnspr4.so': 'nspr',
'libnss3.so': 'nss',
'libnssutil3.so': 'nss-util',
'libpango-1.0.so.0': 'pango',
'libsmime3.so': 'nss',
'libX11.so.6': 'libX11',
'libxcb.so.1': 'libxcb',
'libXcomposite.so.1': 'libXcomposite',
'libXdamage.so.1': 'libXdamage',
'libXext.so.6': 'libXext',
'libXfixes.so.3': 'libXfixes',
'libxkbcommon.so.0': 'libxkbcommon',
'libXrandr.so.2': 'libXrandr',
},
};

deps['rhel9-arm64'] = {
tools: [],
firefox: [],
webkit: [],
chromium: [...deps['rhel9-x64'].chromium],
lib2package: { ...deps['rhel9-x64'].lib2package },
};
Loading