2020-04-30 02:19:21 +02:00
|
|
|
/**
|
|
|
|
|
* 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 * as fs from 'fs';
|
|
|
|
|
import * as path from 'path';
|
|
|
|
|
import * as util from 'util';
|
|
|
|
|
import * as removeFolder from 'rimraf';
|
2020-08-23 06:15:03 +02:00
|
|
|
import * as browserPaths from '../utils/browserPaths';
|
2020-07-02 00:22:29 +02:00
|
|
|
import * as browserFetcher from './browserFetcher';
|
2020-08-22 16:07:13 +02:00
|
|
|
import { getFromENV } from '../utils/utils';
|
2020-04-30 02:19:21 +02:00
|
|
|
|
|
|
|
|
const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs));
|
|
|
|
|
const fsReaddirAsync = util.promisify(fs.readdir.bind(fs));
|
|
|
|
|
const fsReadFileAsync = util.promisify(fs.readFile.bind(fs));
|
2020-07-18 01:39:27 +02:00
|
|
|
const fsExistsAsync = (filePath: string) => fsReadFileAsync(filePath).then(() => true).catch(e => false);
|
2020-04-30 02:19:21 +02:00
|
|
|
const fsUnlinkAsync = util.promisify(fs.unlink.bind(fs));
|
|
|
|
|
const fsWriteFileAsync = util.promisify(fs.writeFile.bind(fs));
|
2020-06-11 03:49:03 +02:00
|
|
|
const removeFolderAsync = util.promisify(removeFolder);
|
2020-04-30 02:19:21 +02:00
|
|
|
|
|
|
|
|
export async function installBrowsersWithProgressBar(packagePath: string) {
|
|
|
|
|
const browsersPath = browserPaths.browsersPath(packagePath);
|
|
|
|
|
const linksDir = path.join(browsersPath, '.links');
|
|
|
|
|
|
|
|
|
|
if (getFromENV('PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD')) {
|
2020-08-18 01:19:21 +02:00
|
|
|
browserFetcher.logPolitely('Skipping browsers download because `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` env variable is set');
|
2020-04-30 02:19:21 +02:00
|
|
|
return false;
|
|
|
|
|
}
|
2020-04-30 03:59:20 +02:00
|
|
|
await fsMkdirAsync(linksDir, { recursive: true });
|
2020-04-30 02:19:21 +02:00
|
|
|
await fsWriteFileAsync(path.join(linksDir, sha1(packagePath)), packagePath);
|
2020-05-04 18:34:59 +02:00
|
|
|
await validateCache(packagePath, browsersPath, linksDir);
|
2020-04-30 02:19:21 +02:00
|
|
|
}
|
|
|
|
|
|
2020-05-04 18:34:59 +02:00
|
|
|
async function validateCache(packagePath: string, browsersPath: string, linksDir: string) {
|
2020-07-18 01:39:27 +02:00
|
|
|
// 1. Collect used downloads and package descriptors.
|
|
|
|
|
const usedBrowserPaths: Set<string> = new Set();
|
2020-04-30 02:19:21 +02:00
|
|
|
for (const fileName of await fsReaddirAsync(linksDir)) {
|
|
|
|
|
const linkPath = path.join(linksDir, fileName);
|
2020-05-29 07:36:08 +02:00
|
|
|
let linkTarget = '';
|
2020-04-30 02:19:21 +02:00
|
|
|
try {
|
2020-05-29 07:36:08 +02:00
|
|
|
linkTarget = (await fsReadFileAsync(linkPath)).toString();
|
2020-07-25 01:36:00 +02:00
|
|
|
const browsersToDownload = await readBrowsersToDownload(linkTarget);
|
|
|
|
|
for (const browser of browsersToDownload) {
|
2020-07-18 01:39:27 +02:00
|
|
|
const usedBrowserPath = browserPaths.browserDirectory(browsersPath, browser);
|
devops: encode build number together with Chromium revision (#3769)
This is an alternative approach to #3698 that was setting up a custom
mapping between chromium revisions and our mirrored builds. For example, we were
taking chromium `792639` and re-packaging it to our CDN as Chromium 1000.
One big downside of this opaque mapping was inability to quickly
understand which Chromium is mirrored to CDN.
To solve this, this patch starts treating browser revision as a fractional number,
with and integer part being a chromium revision, and fractional
part being our build number. For example, we can generate builds `792639`, `792639.1`,
`792639.2` etc, all of which will pick Chromium `792639` and re-package it to our CDN.
In the Playwright code itself, there are a handful of places that treat
browser revision as integer, exclusively to compare revision with some particular
revision numbers. This code would still work as-is, but I changed these places
to use `parseFloat` instead of `parseInt` for correctness.
2020-09-04 12:12:30 +02:00
|
|
|
const browserRevision = parseFloat(browser.revision);
|
2020-07-18 01:39:27 +02:00
|
|
|
// 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);
|
|
|
|
|
}
|
2020-04-30 02:19:21 +02:00
|
|
|
} catch (e) {
|
2020-05-29 07:36:08 +02:00
|
|
|
if (linkTarget)
|
2020-08-18 01:19:21 +02:00
|
|
|
browserFetcher.logPolitely('Failed to process descriptor at ' + linkTarget);
|
2020-04-30 02:19:21 +02:00
|
|
|
await fsUnlinkAsync(linkPath).catch(e => {});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-06-11 03:49:03 +02:00
|
|
|
// 2. Delete all unused browsers.
|
2020-04-30 02:19:21 +02:00
|
|
|
let downloadedBrowsers = (await fsReaddirAsync(browsersPath)).map(file => path.join(browsersPath, file));
|
|
|
|
|
downloadedBrowsers = downloadedBrowsers.filter(file => browserPaths.isBrowserDirectory(file));
|
|
|
|
|
const directories = new Set<string>(downloadedBrowsers);
|
2020-07-18 01:39:27 +02:00
|
|
|
for (const browserPath of usedBrowserPaths)
|
|
|
|
|
directories.delete(browserPath);
|
2020-04-30 02:19:21 +02:00
|
|
|
for (const directory of directories) {
|
2020-08-18 01:19:21 +02:00
|
|
|
browserFetcher.logPolitely('Removing unused browser at ' + directory);
|
2020-06-11 03:49:03 +02:00
|
|
|
await removeFolderAsync(directory).catch(e => {});
|
2020-04-30 02:19:21 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-11 03:49:03 +02:00
|
|
|
// 3. Install missing browsers for this package.
|
2020-07-25 01:36:00 +02:00
|
|
|
const myBrowsersToDownload = await readBrowsersToDownload(packagePath);
|
|
|
|
|
for (const browser of myBrowsersToDownload) {
|
|
|
|
|
await browserFetcher.downloadBrowserWithProgressBar(browsersPath, browser);
|
2020-07-18 01:39:27 +02:00
|
|
|
await fsWriteFileAsync(browserPaths.markerFilePath(browsersPath, browser), '');
|
2020-04-30 02:19:21 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-25 01:36:00 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-30 02:19:21 +02:00
|
|
|
function sha1(data: string): string {
|
|
|
|
|
const sum = crypto.createHash('sha1');
|
|
|
|
|
sum.update(data);
|
|
|
|
|
return sum.digest('hex');
|
|
|
|
|
}
|