mirror of
https://github.com/microsoft/playwright.git
synced 2024-12-14 21:53:35 +03:00
4ab4c0bda1
This patch prints a friendly instructions in case Docker image version mismatches Playwright version and there are missing browser dependencies. With this patch, Playwright will yield the following error: ``` root@f0774d2b2097:~# node a.mjs node:internal/process/promises:279 triggerUncaughtException(err, true /* fromPromise */); ^ browserType.launch: ╔════════════════════════════════════════════════════════════════════════════════════════════╗ ║ Host system is missing dependencies to run browsers. ║ ║ This is most likely due to docker image version not matching Playwright version: ║ ║ - Playwright: 1.22.0 ║ ║ - Docker: 1.21.0 ║ ║ ║ ║ Either: ║ ║ - (recommended) use docker image "mcr.microsoft.com/playwright:v1.22.0-focal" ║ ║ - (alternative 1) run the following command inside docker to install missing dependencies: ║ ║ ║ ║ npx playwright install-deps ║ ║ ║ ║ - (alternative 2) use Aptitude inside docker: ║ ║ ║ ║ apt-get install libgbm1 ║ ║ ║ ║ <3 Playwright Team ║ ╚════════════════════════════════════════════════════════════════════════════════════════════╝ at file:///root/a.mjs:3:10 { name: 'Error' }``` Fixes #12796 Co-authored-by: Dmitry Gozman <dgozman@gmail.com>
238 lines
7.8 KiB
JavaScript
Executable File
238 lines
7.8 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
/**
|
|
* 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
|
|
|
|
/**
|
|
* Use the following command to typescheck this file:
|
|
* npx tsc --target es2020 --watch --checkjs --noemit --moduleResolution node workspace.js
|
|
*/
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const readJSON = async (filePath) => JSON.parse(await fs.promises.readFile(filePath, 'utf8'));
|
|
const writeJSON = async (filePath, json) => {
|
|
await fs.promises.writeFile(filePath, JSON.stringify(json, null, 2) + '\n');
|
|
}
|
|
|
|
class PWPackage {
|
|
constructor(descriptor) {
|
|
this.name = descriptor.name;
|
|
this.path = descriptor.path;
|
|
this.files = descriptor.files;
|
|
this.packageJSONPath = path.join(this.path, 'package.json');
|
|
this.packageJSON = JSON.parse(fs.readFileSync(this.packageJSONPath, 'utf8'));
|
|
this.isPrivate = !!this.packageJSON.private;
|
|
}
|
|
}
|
|
|
|
class Workspace {
|
|
/**
|
|
* @param {string} rootDir
|
|
* @param {PWPackage[]} packages
|
|
*/
|
|
constructor(rootDir, packages) {
|
|
this._rootDir = rootDir;
|
|
this._packages = packages;
|
|
}
|
|
|
|
/**
|
|
* @returns {PWPackage[]}
|
|
*/
|
|
packages() {
|
|
return this._packages;
|
|
}
|
|
|
|
async version() {
|
|
const workspacePackageJSON = await readJSON(path.join(this._rootDir, 'package.json'));
|
|
return workspacePackageJSON.version;
|
|
}
|
|
|
|
/**
|
|
* @param {string} version
|
|
*/
|
|
async setVersion(version) {
|
|
if (version.startsWith('v'))
|
|
throw new Error('version must not start with "v"');
|
|
|
|
// 1. update workspace's package.json (playwright-internal) with the new version
|
|
const workspacePackageJSON = await readJSON(path.join(this._rootDir, 'package.json'));
|
|
workspacePackageJSON.version = version;
|
|
await writeJSON(path.join(this._rootDir, 'package.json'), workspacePackageJSON);
|
|
// 2. make workspace consistent.
|
|
await this.ensureConsistent();
|
|
}
|
|
|
|
async ensureConsistent() {
|
|
let hasChanges = false;
|
|
|
|
const maybeWriteJSON = async (jsonPath, json) => {
|
|
const oldJson = await readJSON(jsonPath);
|
|
if (JSON.stringify(json) === JSON.stringify(oldJson))
|
|
return;
|
|
hasChanges = true;
|
|
console.warn('Updated', jsonPath);
|
|
await writeJSON(jsonPath, json);
|
|
};
|
|
|
|
const workspacePackageJSON = await readJSON(path.join(this._rootDir, 'package.json'));
|
|
const packageLockPath = path.join(this._rootDir, 'package-lock.json');
|
|
const packageLock = JSON.parse(await fs.promises.readFile(packageLockPath, 'utf8'));
|
|
const version = workspacePackageJSON.version;
|
|
|
|
// Make sure package-lock version is consistent with root package.json version.
|
|
packageLock.version = version;
|
|
packageLock.packages[""].version = version;
|
|
|
|
for (const pkg of this._packages) {
|
|
// 1. Copy package files.
|
|
for (const file of pkg.files) {
|
|
const fromPath = path.join(this._rootDir, file);
|
|
const toPath = path.join(pkg.path, file);
|
|
await fs.promises.mkdir(path.dirname(pkg.path), { recursive: true });
|
|
await fs.promises.copyFile(fromPath, toPath);
|
|
}
|
|
|
|
// 2. Make sure package-lock and package's package.json are consistent.
|
|
// All manual package-lock management is a workaround for
|
|
// https://github.com/npm/cli/issues/3940
|
|
const pkgLockEntry = packageLock['packages']['packages/' + path.basename(pkg.path)];
|
|
const depLockEntry = packageLock['dependencies'][pkg.name];
|
|
if (!pkg.isPrivate) {
|
|
pkgLockEntry.version = version;
|
|
pkg.packageJSON.version = version;
|
|
pkg.packageJSON.repository = workspacePackageJSON.repository;
|
|
pkg.packageJSON.engines = workspacePackageJSON.engines;
|
|
pkg.packageJSON.homepage = workspacePackageJSON.homepage;
|
|
pkg.packageJSON.author = workspacePackageJSON.author;
|
|
pkg.packageJSON.license = workspacePackageJSON.license;
|
|
}
|
|
for (const otherPackage of this._packages) {
|
|
if (pkgLockEntry.dependencies && pkgLockEntry.dependencies[otherPackage.name])
|
|
pkgLockEntry.dependencies[otherPackage.name] = version;
|
|
if (depLockEntry.requires && depLockEntry.requires[otherPackage.name])
|
|
depLockEntry.requires[otherPackage.name] = version;
|
|
if (pkg.packageJSON.dependencies && pkg.packageJSON.dependencies[otherPackage.name])
|
|
pkg.packageJSON.dependencies[otherPackage.name] = version;
|
|
}
|
|
await maybeWriteJSON(pkg.packageJSONPath, pkg.packageJSON);
|
|
}
|
|
await maybeWriteJSON(packageLockPath, packageLock);
|
|
return hasChanges;
|
|
}
|
|
}
|
|
|
|
const ROOT_PATH = path.join(__dirname, '..');
|
|
const LICENCE_FILES = ['NOTICE', 'LICENSE'];
|
|
const workspace = new Workspace(ROOT_PATH, [
|
|
new PWPackage({
|
|
name: 'playwright',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright'),
|
|
// We copy README.md additionally for Playwright so that it looks nice on NPM.
|
|
files: [...LICENCE_FILES, 'README.md'],
|
|
}),
|
|
new PWPackage({
|
|
name: 'playwright-core',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-core'),
|
|
files: LICENCE_FILES,
|
|
}),
|
|
new PWPackage({
|
|
name: '@playwright/test',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-test'),
|
|
files: LICENCE_FILES,
|
|
}),
|
|
new PWPackage({
|
|
name: 'playwright-webkit',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-webkit'),
|
|
files: LICENCE_FILES,
|
|
}),
|
|
new PWPackage({
|
|
name: 'playwright-firefox',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-firefox'),
|
|
files: LICENCE_FILES,
|
|
}),
|
|
new PWPackage({
|
|
name: 'playwright-chromium',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-chromium'),
|
|
files: LICENCE_FILES,
|
|
}),
|
|
new PWPackage({
|
|
name: 'html-reporter',
|
|
path: path.join(ROOT_PATH, 'packages', 'html-reporter'),
|
|
files: [],
|
|
}),
|
|
new PWPackage({
|
|
name: '@playwright/experimental-ct-react',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-react'),
|
|
files: ['LICENSE'],
|
|
}),
|
|
new PWPackage({
|
|
name: '@playwright/experimental-ct-svelte',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-svelte'),
|
|
files: ['LICENSE'],
|
|
}),
|
|
new PWPackage({
|
|
name: '@playwright/experimental-ct-vue',
|
|
path: path.join(ROOT_PATH, 'packages', 'playwright-ct-vue'),
|
|
files: ['LICENSE'],
|
|
}),
|
|
]);
|
|
|
|
if (require.main === module) {
|
|
parseCLI();
|
|
} else {
|
|
module.exports = {workspace};
|
|
}
|
|
|
|
function die(message, exitCode = 1) {
|
|
console.error(message);
|
|
process.exit(exitCode);
|
|
}
|
|
|
|
async function parseCLI() {
|
|
const commands = {
|
|
'--ensure-consistent': async () => {
|
|
const hasChanges = await workspace.ensureConsistent();
|
|
if (hasChanges)
|
|
die(`\n ERROR: workspace is inconsistent! Run '//utils/workspace.js --ensure-consistent' and commit changes!`);
|
|
},
|
|
'--list-public-package-paths': () => {
|
|
for (const pkg of workspace.packages()) {
|
|
if (!pkg.isPrivate)
|
|
console.log(pkg.path);
|
|
}
|
|
},
|
|
'--get-version': async (version) => {
|
|
console.log(await workspace.version());
|
|
},
|
|
'--set-version': async (version) => {
|
|
if (!version)
|
|
die('ERROR: Please specify version! e.g. --set-version 1.99.2');
|
|
await workspace.setVersion(version);
|
|
},
|
|
'--help': () => {
|
|
console.log([
|
|
`Available commands:`,
|
|
...Object.keys(commands).map(cmd => ' ' + cmd),
|
|
].join('\n'));
|
|
},
|
|
};
|
|
const handler = commands[process.argv[2]];
|
|
if (!handler)
|
|
die('ERROR: wrong usage! Run with --help to list commands');
|
|
await handler(process.argv[3]);
|
|
}
|