playwright-core
Version:
A high-level API to automate web browsers
187 lines • 8 kB
JavaScript
;
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications 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.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.logPolitely = exports.downloadBrowserWithProgressBar = void 0;
const extract_zip_1 = __importDefault(require("extract-zip"));
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const progress_1 = __importDefault(require("progress"));
const proxy_from_env_1 = require("proxy-from-env");
const URL = __importStar(require("url"));
const util = __importStar(require("util"));
const registry_1 = require("../utils/registry");
// `https-proxy-agent` v5 is written in Typescript and exposes generated types.
// However, as of June 2020, its types are generated with tsconfig that enables
// `esModuleInterop` option.
//
// As a result, we can't depend on the package unless we enable the option
// for our codebase. Instead of doing this, we abuse "require" to import module
// without types.
const ProxyAgent = require('https-proxy-agent');
const unlinkAsync = util.promisify(fs_1.default.unlink.bind(fs_1.default));
const chmodAsync = util.promisify(fs_1.default.chmod.bind(fs_1.default));
const existsAsync = (path) => new Promise(resolve => fs_1.default.stat(path, err => resolve(!err)));
async function downloadBrowserWithProgressBar(registry, browserName) {
const browserDirectory = registry.browserDirectory(browserName);
const progressBarName = `${browserName} v${registry.revision(browserName)}`;
if (await existsAsync(browserDirectory)) {
// Already downloaded.
return false;
}
let progressBar;
let lastDownloadedBytes = 0;
function progress(downloadedBytes, totalBytes) {
if (!progressBar) {
progressBar = new progress_1.default(`Downloading ${progressBarName} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
complete: '=',
incomplete: ' ',
width: 20,
total: totalBytes,
});
}
const delta = downloadedBytes - lastDownloadedBytes;
lastDownloadedBytes = downloadedBytes;
progressBar.tick(delta);
}
const url = registry.downloadURL(browserName);
const zipPath = path_1.default.join(os_1.default.tmpdir(), `playwright-download-${browserName}-${registry_1.hostPlatform}-${registry.revision(browserName)}.zip`);
try {
for (let attempt = 1, N = 3; attempt <= N; ++attempt) {
const { error } = await downloadFile(url, zipPath, progress);
if (!error)
break;
const errorMessage = typeof error === 'object' && typeof error.message === 'string' ? error.message : '';
if (attempt < N && (errorMessage.includes('ECONNRESET') || errorMessage.includes('ETIMEDOUT'))) {
// Maximum delay is 3rd retry: 1337.5ms
const millis = (Math.random() * 200) + (250 * Math.pow(1.5, attempt));
await new Promise(c => setTimeout(c, millis));
}
else {
throw error;
}
}
await extract_zip_1.default(zipPath, { dir: browserDirectory });
await chmodAsync(registry.executablePath(browserName), 0o755);
}
catch (e) {
process.exitCode = 1;
throw e;
}
finally {
if (await existsAsync(zipPath))
await unlinkAsync(zipPath);
}
logPolitely(`${progressBarName} downloaded to ${browserDirectory}`);
return true;
}
exports.downloadBrowserWithProgressBar = downloadBrowserWithProgressBar;
function toMegabytes(bytes) {
const mb = bytes / 1024 / 1024;
return `${Math.round(mb * 10) / 10} Mb`;
}
function downloadFile(url, destinationPath, progressCallback) {
let fulfill = ({ error }) => { };
let downloadedBytes = 0;
let totalBytes = 0;
const promise = new Promise(x => { fulfill = x; });
const request = httpRequest(url, 'GET', response => {
if (response.statusCode !== 200) {
const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`);
// consume response data to free up memory
response.resume();
fulfill({ error });
return;
}
const file = fs_1.default.createWriteStream(destinationPath);
file.on('finish', () => fulfill({ error: null }));
file.on('error', error => fulfill({ error }));
response.pipe(file);
totalBytes = parseInt(response.headers['content-length'], 10);
if (progressCallback)
response.on('data', onData);
});
request.on('error', (error) => fulfill({ error }));
return promise;
function onData(chunk) {
downloadedBytes += chunk.length;
progressCallback(downloadedBytes, totalBytes);
}
}
function httpRequest(url, method, response) {
let options = URL.parse(url);
options.method = method;
const proxyURL = proxy_from_env_1.getProxyForUrl(url);
if (proxyURL) {
if (url.startsWith('http:')) {
const proxy = URL.parse(proxyURL);
options = {
path: options.href,
host: proxy.hostname,
port: proxy.port,
};
}
else {
const parsedProxyURL = URL.parse(proxyURL);
parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:';
options.agent = new ProxyAgent(parsedProxyURL);
options.rejectUnauthorized = false;
}
}
const requestCallback = (res) => {
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location)
httpRequest(res.headers.location, method, response);
else
response(res);
};
const request = options.protocol === 'https:' ?
require('https').request(options, requestCallback) :
require('http').request(options, requestCallback);
request.end();
return request;
}
function logPolitely(toBeLogged) {
const logLevel = process.env.npm_config_loglevel;
const logLevelDisplay = ['silent', 'error', 'warn'].indexOf(logLevel || '') > -1;
if (!logLevelDisplay)
console.log(toBeLogged); // eslint-disable-line no-console
}
exports.logPolitely = logPolitely;
//# sourceMappingURL=browserFetcher.js.map