playwright/src/install/installer.ts
Dmitry Gozman fd2e65b73c
api: export all browsers from every package (#3128)
This makes it easier to reason about our packages.
The only difference is what each package downloads.
When the browser is not downloaded, it will fail to launch.

Each browser gets a 'download' attribute in the browser.json file.
2020-07-24 16:36:00 -07:00

104 lines
4.7 KiB
TypeScript

/**
* Copyright Microsoft Corporation. All rights reserved.
*
* 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.
*/
import * as crypto from 'crypto';
import { getFromENV, logPolitely } from '../helper';
import * as fs from 'fs';
import * as path from 'path';
import * as util from 'util';
import * as removeFolder from 'rimraf';
import * as browserPaths from './browserPaths';
import * as browserFetcher from './browserFetcher';
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
const fsReaddirAsync = util.promisify(fs.readdir.bind(fs));
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
const fsExistsAsync = (filePath: string) => fsReadFileAsync(filePath).then(() => true).catch(e => false);
const fsUnlinkAsync = util.promisify(fs.unlink.bind(fs));
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
const removeFolderAsync = util.promisify(removeFolder);
export async function installBrowsersWithProgressBar(packagePath: string) {
const browsersPath = browserPaths.browsersPath(packagePath);
const linksDir = path.join(browsersPath, '.links');
if (getFromENV('PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD')) {
logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
return false;
}
await fsMkdirAsync(linksDir, { recursive: true });
await fsWriteFileAsync(path.join(linksDir, sha1(packagePath)), packagePath);
await validateCache(packagePath, browsersPath, linksDir);
}
async function validateCache(packagePath: string, browsersPath: string, linksDir: string) {
// 1. Collect used downloads and package descriptors.
const usedBrowserPaths: Set<string> = new Set();
for (const fileName of await fsReaddirAsync(linksDir)) {
const linkPath = path.join(linksDir, fileName);
let linkTarget = '';
try {
linkTarget = (await fsReadFileAsync(linkPath)).toString();
const browsersToDownload = await readBrowsersToDownload(linkTarget);
for (const browser of browsersToDownload) {
const usedBrowserPath = browserPaths.browserDirectory(browsersPath, browser);
const browserRevision = parseInt(browser.revision, 10);
// Old browser installations don't have marker file.
const shouldHaveMarkerFile = (browser.name === 'chromium' && browserRevision >= 786218) ||
(browser.name === 'firefox' && browserRevision >= 1128) ||
(browser.name === 'webkit' && browserRevision >= 1307);
if (!shouldHaveMarkerFile || (await fsExistsAsync(browserPaths.markerFilePath(browsersPath, browser))))
usedBrowserPaths.add(usedBrowserPath);
}
} catch (e) {
if (linkTarget)
logPolitely('Failed to process descriptor at ' + linkTarget);
await fsUnlinkAsync(linkPath).catch(e => {});
}
}
// 2. Delete all unused browsers.
let downloadedBrowsers = (await fsReaddirAsync(browsersPath)).map(file => path.join(browsersPath, file));
downloadedBrowsers = downloadedBrowsers.filter(file => browserPaths.isBrowserDirectory(file));
const directories = new Set<string>(downloadedBrowsers);
for (const browserPath of usedBrowserPaths)
directories.delete(browserPath);
for (const directory of directories) {
logPolitely('Removing unused browser at ' + directory);
await removeFolderAsync(directory).catch(e => {});
}
// 3. Install missing browsers for this package.
const myBrowsersToDownload = await readBrowsersToDownload(packagePath);
for (const browser of myBrowsersToDownload) {
await browserFetcher.downloadBrowserWithProgressBar(browsersPath, browser);
await fsWriteFileAsync(browserPaths.markerFilePath(browsersPath, browser), '');
}
}
async function readBrowsersToDownload(packagePath: string) {
const browsers = JSON.parse((await fsReadFileAsync(path.join(packagePath, 'browsers.json'))).toString())['browsers'] as browserPaths.BrowserDescriptor[];
// Older versions do not have "download" field. We assume they need all browsers
// from the list. So we want to skip all browsers that are explicitly marked as "download: false".
return browsers.filter(browser => browser.download !== false);
}
function sha1(data: string): string {
const sum = crypto.createHash('sha1');
sum.update(data);
return sum.digest('hex');
}