devops: remake downloading logic (#1419)

This patch:
- removes `browserType.downloadBrowserIfNeeded()` method. The method
  turned out to be ill-behaving and cannot not be used as we'd like to (see #1085)
- adds a `browserType.setExecutablePath` method to set a browser
  exectuable.

With this patch, we take the following approach towards managing browser downloads:
- `playwright-core` doesn't download any browsers. In `playwright-core`, `playwright.chromium.executablePath()` returns `null` (same for firefox and webkit).
- clients of `playwright-core` (e.g. `playwright` and others) download browsers one way or another.
They can then configure `playwright` with executable paths and re-export the `playwright` object to their clients.
- `playwright`, `playwright-firefox`, `playwright-chromium` and `playwright-webkit` download 
browsers. Once browsers are downloaded, their executable paths are saved to a `.downloaded-browsers.json` file. This file is read in `playwright/index.js` to configure browser executable paths and re-export the API.
- special case is `install-from-github.js` that also cleans up old browsers.
This commit is contained in:
Andrey Lushnikov 2020-03-19 11:43:35 -07:00 committed by GitHub
parent 2af07ce475
commit f5ecbff16e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 345 additions and 515 deletions

View File

@ -15,9 +15,7 @@ jobs:
- save_cache:
key: dependency-cache-{{ checksum "package.json" }}
paths:
- ./.local-chromium
- ./.local-firefox
- ./.local-webkit
- ./.local-browsers
- run:
command: |

5
.gitignore vendored
View File

@ -3,11 +3,10 @@
/test/output-firefox
/test/output-webkit
/test/test-user-data-dir*
/.local-chromium/
/.local-firefox/
/.local-webkit/
/.local-browsers/
/.dev_profile*
.DS_Store
.downloaded-browsers.json
*.swp
*.pyc
.vscode

View File

@ -3645,7 +3645,6 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
<!-- GEN:toc -->
- [browserType.connect(options)](#browsertypeconnectoptions)
- [browserType.downloadBrowserIfNeeded([progress])](#browsertypedownloadbrowserifneededprogress)
- [browserType.executablePath()](#browsertypeexecutablepath)
- [browserType.launch([options])](#browsertypelaunchoptions)
- [browserType.launchPersistentContext(userDataDir, [options])](#browsertypelaunchpersistentcontextuserdatadir-options)
@ -3661,14 +3660,8 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'.
This methods attaches Playwright to an existing browser instance.
#### browserType.downloadBrowserIfNeeded([progress])
- `progress` <[function]> If download is initiated, this function is called with two parameters: `downloadedBytes` and `totalBytes`.
- returns: <[Promise]> promise that resolves when browser is successfully downloaded.
Download browser binary if it is missing.
#### browserType.executablePath()
- returns: <[string]> A path where Playwright expects to find a bundled browser.
- returns: <[string]> A path where Playwright expects to find a bundled browser executable.
#### browserType.launch([options])
- `options` <[Object]> Set of configurable options to set on the browser. Can have the following fields:

View File

@ -164,7 +164,7 @@ done only once per host environment:
```bash
# cd to the downloaded instance
cd <project-dir-path>/node_modules/playwright/.local-chromium/linux-<revision>/chrome-linux/
cd <project-dir-path>/node_modules/playwright/.local-browsers/chromium-<revision>/
sudo chown root:root chrome_sandbox
sudo chmod 4755 chrome_sandbox
# copy sandbox executable to a shared location

View File

@ -13,37 +13,39 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const fs = require('fs');
const browserFetcher = require('./lib/server/browserFetcher.js');
const packageJSON = require('./package.json');
async function downloadBrowser(browserType) {
const browser = browserType.name();
async function downloadBrowserWithProgressBar(downloadPath, browser, version = '') {
let progressBar = null;
let lastDownloadedBytes = 0;
function onProgress(downloadedBytes, totalBytes) {
const revision = packageJSON.playwright[`${browser}_revision`];
function progress(downloadedBytes, totalBytes) {
if (!progressBar) {
const ProgressBar = require('progress');
progressBar = new ProgressBar(`Downloading ${browser} ${browserType._revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
progressBar = new ProgressBar(`Downloading ${browser} r${revision} - ${toMegabytes(totalBytes)} [:bar] :percent :etas `, {
complete: '=',
incomplete: ' ',
width: 20,
total: totalBytes,
host: getFromENV('PLAYWRIGHT_DOWNLOAD_HOST'),
});
}
const delta = downloadedBytes - lastDownloadedBytes;
lastDownloadedBytes = downloadedBytes;
progressBar.tick(delta);
}
const fetcher = browserType._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return revisionInfo;
await browserType.downloadBrowserIfNeeded(onProgress);
logPolitely(`${browser} downloaded to ${revisionInfo.folderPath}`);
return revisionInfo;
const executablePath = await browserFetcher.downloadBrowser({
downloadPath,
browser,
revision,
progress,
});
logPolitely(`${browser} downloaded to ${downloadPath}`);
return executablePath;
}
function toMegabytes(bytes) {
const mb = bytes / 1024 / 1024;
return `${Math.round(mb * 10) / 10} Mb`;
@ -57,4 +59,11 @@ function logPolitely(toBeLogged) {
console.log(toBeLogged);
}
module.exports = {downloadBrowser};
function getFromENV(name) {
let value = process.env[name];
value = value || process.env[`npm_config_${name.toLowerCase()}`];
value = value || process.env[`npm_package_config_${name.toLowerCase()}`];
return value;
}
module.exports = {downloadBrowserWithProgressBar};

View File

@ -15,9 +15,17 @@
*/
const {Playwright} = require('./lib/server/playwright.js');
module.exports = new Playwright({
downloadPath: __dirname,
const playwright = new Playwright({
browsers: ['webkit', 'chromium', 'firefox'],
respectEnvironmentVariables: false,
});
try {
const downloadedBrowsers = require('./.downloaded-browsers.json');
playwright.chromium._executablePath = downloadedBrowsers.crExecutablePath;
playwright.firefox._executablePath = downloadedBrowsers.ffExecutablePath;
playwright.webkit._executablePath = downloadedBrowsers.wkExecutablePath;
} catch (e) {
}
module.exports = playwright;

View File

@ -24,41 +24,69 @@ try {
});
} catch (e) {
}
const {downloadBrowser} = require('./download-browser');
const playwright = require('.');
const path = require('path');
const fs = require('fs');
const util = require('util');
const rmAsync = util.promisify(require('rimraf'));
const existsAsync = path => fs.promises.access(path).then(() => true, e => false);
const {downloadBrowserWithProgressBar} = require('./download-browser');
const protocolGenerator = require('./utils/protocol-types-generator');
const packageJSON = require('./package.json');
const DOWNLOADED_BROWSERS_JSON_PATH = path.join(__dirname, '.downloaded-browsers.json');
const DOWNLOAD_PATHS = {
chromium: path.join(__dirname, '.local-browsers', `chromium-${packageJSON.playwright.chromium_revision}`),
firefox: path.join(__dirname, '.local-browsers', `firefox-${packageJSON.playwright.firefox_revision}`),
webkit: path.join(__dirname, '.local-browsers', `webkit-${packageJSON.playwright.webkit_revision}`),
};
(async function() {
const protocolGenerator = require('./utils/protocol-types-generator');
const downloadedBrowsersJSON = await fs.promises.readFile(DOWNLOADED_BROWSERS_JSON_PATH, 'utf8').then(json => JSON.parse(json)).catch(() => ({}));
try {
const chromeRevision = await downloadAndCleanup(playwright.chromium);
await protocolGenerator.generateChromiunProtocol(chromeRevision);
if (!(await existsAsync(DOWNLOAD_PATHS.chromium))) {
const crExecutablePath = await downloadBrowserWithProgressBar(DOWNLOAD_PATHS.chromium, 'chromium');
downloadedBrowsersJSON.crExecutablePath = crExecutablePath;
await protocolGenerator.generateChromiumProtocol(crExecutablePath);
await fs.promises.writeFile(DOWNLOADED_BROWSERS_JSON_PATH, JSON.stringify(downloadedBrowsersJSON));
}
} catch (e) {
console.warn(e.message);
}
try {
if (!(await existsAsync(DOWNLOAD_PATHS.firefox))) {
const ffExecutablePath = await downloadBrowserWithProgressBar(DOWNLOAD_PATHS.firefox, 'firefox');
downloadedBrowsersJSON.ffExecutablePath = ffExecutablePath;
await protocolGenerator.generateFirefoxProtocol(ffExecutablePath);
await fs.promises.writeFile(DOWNLOADED_BROWSERS_JSON_PATH, JSON.stringify(downloadedBrowsersJSON));
}
} catch (e) {
console.warn(e.message);
}
try {
if (!(await existsAsync(DOWNLOAD_PATHS.webkit))) {
const wkExecutablePath = await downloadBrowserWithProgressBar(DOWNLOAD_PATHS.webkit, 'webkit');
downloadedBrowsersJSON.wkExecutablePath = wkExecutablePath;
await protocolGenerator.generateWebKitProtocol(path.dirname(wkExecutablePath));
await fs.promises.writeFile(DOWNLOADED_BROWSERS_JSON_PATH, JSON.stringify(downloadedBrowsersJSON));
}
} catch (e) {
console.warn(e.message);
}
try {
const firefoxRevision = await downloadAndCleanup(playwright.firefox);
await protocolGenerator.generateFirefoxProtocol(firefoxRevision);
} catch (e) {
console.warn(e.message);
}
// Cleanup stale revisions.
const directories = new Set(await readdirAsync(path.join(__dirname, '.local-browsers')));
directories.delete(DOWNLOAD_PATHS.chromium);
directories.delete(DOWNLOAD_PATHS.firefox);
directories.delete(DOWNLOAD_PATHS.webkit);
// cleanup old browser directories.
directories.add(path.join(__dirname, '.local-chromium'));
directories.add(path.join(__dirname, '.local-firefox'));
directories.add(path.join(__dirname, '.local-webkit'));
await Promise.all([...directories].map(directory => rmAsync(directory)));
try {
const webkitRevision = await downloadAndCleanup(playwright.webkit);
await protocolGenerator.generateWebKitProtocol(webkitRevision);
} catch (e) {
console.warn(e.message);
async function readdirAsync(dirpath) {
return fs.promises.readdir(dirpath).then(dirs => dirs.map(dir => path.join(dirpath, dir)));
}
})();
async function downloadAndCleanup(browserType) {
const revisionInfo = await downloadBrowser(browserType);
// Remove previous revisions.
const fetcher = browserType._createBrowserFetcher();
const localRevisions = await fetcher.localRevisions();
const cleanupOldVersions = localRevisions.filter(revision => revision !== revisionInfo.revision).map(revision => fetcher.remove(revision));
await Promise.all([...cleanupOldVersions]);
return revisionInfo;
}

View File

@ -13,12 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const {Playwright} = require('playwright-core/lib/server/playwright.js');
module.exports = new Playwright({
downloadPath: __dirname,
const playwright = new Playwright({
browsers: ['chromium'],
respectEnvironmentVariables: true,
});
module.exports = playwright;
try {
const downloadedBrowsers = require('./.downloaded-browsers.json');
playwright.chromium._executablePath = downloadedBrowsers.crExecutablePath;
} catch (e) {
throw new Error('playwright-chromium has not downloaded Chromium.');
}

View File

@ -13,6 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const {downloadBrowser} = require('playwright-core/download-browser');
const playwright = require('.');
downloadBrowser(playwright.chromium);
const path = require('path');
const fs = require('fs');
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
(async function() {
const crExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'chromium'), 'chromium');
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({crExecutablePath}));
})();

View File

@ -13,12 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const {Playwright} = require('playwright-core/lib/server/playwright.js');
module.exports = new Playwright({
downloadPath: __dirname,
const playwright = new Playwright({
browsers: ['firefox'],
respectEnvironmentVariables: true,
});
module.exports = playwright;
try {
const downloadedBrowsers = require(path.join(__dirname, '.downloaded-browsers.json'));
playwright.firefox._executablePath = downloadedBrowsers.ffExecutablePath;
} catch (e) {
throw new Error('playwright-firefox has not downloaded Firefox.');
}

View File

@ -13,6 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const {downloadBrowser} = require('playwright-core/download-browser');
const playwright = require('.');
downloadBrowser(playwright.firefox);
const path = require('path');
const fs = require('fs');
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
(async function() {
const ffExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'firefox'), 'firefox');
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({ffExecutablePath, }));
})();

View File

@ -13,12 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const {Playwright} = require('playwright-core/lib/server/playwright.js');
module.exports = new Playwright({
downloadPath: __dirname,
const playwright = new Playwright({
browsers: ['webkit'],
respectEnvironmentVariables: true,
});
module.exports = playwright;
try {
const downloadedBrowsers = require(path.join(__dirname, '.downloaded-browsers.json'));
playwright.webkit._executablePath = downloadedBrowsers.wkExecutablePath;
} catch (e) {
throw new Error('playwright-webkit has not downloaded WebKit.');
}

View File

@ -13,6 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const {downloadBrowser} = require('playwright-core/download-browser');
const playwright = require('.');
downloadBrowser(playwright.webkit);
const path = require('path');
const fs = require('fs');
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
(async function() {
const wkExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'webkit'), 'webkit');
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({wkExecutablePath, }));
})();

View File

@ -13,11 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const path = require('path');
const {Playwright} = require('playwright-core/lib/server/playwright.js');
module.exports = new Playwright({
downloadPath: __dirname,
const playwright = new Playwright({
browsers: ['webkit', 'chromium', 'firefox'],
respectEnvironmentVariables: true,
});
module.exports = playwright;
try {
const downloadedBrowsers = require(path.join(__dirname, '.downloaded-browsers.json'));
playwright.chromium._executablePath = downloadedBrowsers.crExecutablePath;
playwright.firefox._executablePath = downloadedBrowsers.ffExecutablePath;
playwright.webkit._executablePath = downloadedBrowsers.wkExecutablePath;
} catch (e) {
throw new Error('ERROR: Playwright did not download browsers');
}

View File

@ -13,10 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const {downloadBrowser} = require('playwright-core/download-browser');
const playwright = require('.');
const path = require('path');
const fs = require('fs');
const {downloadBrowserWithProgressBar} = require('playwright-core/download-browser');
(async function() {
await downloadBrowser(playwright.chromium);
await downloadBrowser(playwright.firefox);
await downloadBrowser(playwright.webkit);
const crExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'chromium'), 'chromium');
const ffExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'firefox'), 'firefox');
const wkExecutablePath = await downloadBrowserWithProgressBar(path.join(__dirname, '.local-browsers', 'webkit'), 'webkit');
await fs.promises.writeFile(path.join(__dirname, '.downloaded-browsers.json'), JSON.stringify({crExecutablePath, ffExecutablePath, wkExecutablePath, }));
})();

View File

@ -17,110 +17,152 @@
import * as extract from 'extract-zip';
import * as fs from 'fs';
import * as os from 'os';
import * as util from 'util';
import { execSync } from 'child_process';
import * as ProxyAgent from 'https-proxy-agent';
import * as path from 'path';
import * as platform from '../platform';
import { getProxyForUrl } from 'proxy-from-env';
import * as removeRecursive from 'rimraf';
import * as URL from 'url';
import { assert } from '../helper';
import * as platform from '../platform';
const readdirAsync = platform.promisify(fs.readdir.bind(fs));
const mkdirAsync = platform.promisify(fs.mkdir.bind(fs));
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
const chmodAsync = platform.promisify(fs.chmod.bind(fs));
const existsAsync = (path: string): Promise<boolean> => new Promise(resolve => fs.stat(path, err => resolve(!err)));
function existsAsync(filePath: string): Promise<boolean> {
let fulfill: (exists: boolean) => void;
const promise = new Promise<boolean>(x => fulfill = x);
fs.access(filePath, err => fulfill(!err));
return promise;
}
const DEFAULT_DOWNLOAD_HOSTS = {
chromium: 'https://storage.googleapis.com',
firefox: 'https://playwright.azureedge.net',
webkit: 'https://playwright.azureedge.net',
};
type ParamsGetter = (platform: string, revision: string) => { downloadUrl: string, executablePath: string };
const DOWNLOAD_URLS = {
chromium: {
'linux': '%s/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
'mac10.14': '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
'mac10.15': '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
'win32': '%s/chromium-browser-snapshots/Win/%d/chrome-win.zip',
'win64': '%s/chromium-browser-snapshots/Win_x64/%d/chrome-win.zip',
},
firefox: {
'linux': '%s/builds/firefox/%s/firefox-linux.zip',
'mac10.14': '%s/builds/firefox/%s/firefox-mac.zip',
'mac10.15': '%s/builds/firefox/%s/firefox-mac.zip',
'win32': '%s/builds/firefox/%s/firefox-win32.zip',
'win64': '%s/builds/firefox/%s/firefox-win64.zip',
},
webkit: {
'linux': '%s/builds/webkit/%s/minibrowser-gtk-wpe.zip',
'mac10.14': '%s/builds/webkit/%s/minibrowser-mac-10.14.zip',
'mac10.15': '%s/builds/webkit/%s/minibrowser-mac-10.15.zip',
'win32': '%s/builds/webkit/%s/minibrowser-win64.zip',
'win64': '%s/builds/webkit/%s/minibrowser-win64.zip',
},
};
const RELATIVE_EXECUTABLE_PATHS = {
chromium: {
'linux': ['chrome-linux', 'chrome'],
'mac10.14': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'mac10.15': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'win32': ['chrome-win', 'chrome.exe'],
'win64': ['chrome-win', 'chrome.exe'],
},
firefox: {
'linux': ['firefox', 'firefox'],
'mac10.14': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'mac10.15': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'win32': ['firefox', 'firefox.exe'],
'win64': ['firefox', 'firefox.exe'],
},
webkit: {
'linux': ['pw_run.sh'],
'mac10.14': ['pw_run.sh'],
'mac10.15': ['pw_run.sh'],
'win32': ['MiniBrowser.exe'],
'win64': ['MiniBrowser.exe'],
},
};
export type OnProgressCallback = (downloadedBytes: number, totalBytes: number) => void;
export type BrowserName = ('chromium'|'webkit'|'firefox');
export type BrowserPlatform = ('win32'|'win64'|'mac10.14'|'mac10.15'|'linux');
export class BrowserFetcher {
private _downloadsFolder: string;
private _platform: string;
private _preferredRevision: string;
private _params: ParamsGetter;
export type DownloadOptions = {
browser: BrowserName,
revision: string,
downloadPath: string,
platform?: BrowserPlatform,
host?: string,
progress?: OnProgressCallback,
};
constructor(downloadsFolder: string, platform: string, preferredRevision: string, params: ParamsGetter) {
this._downloadsFolder = downloadsFolder;
this._platform = platform;
this._preferredRevision = preferredRevision;
this._params = params;
const CURRENT_HOST_PLATFORM = ((): string => {
const platform = os.platform();
if (platform === 'darwin') {
const macVersion = execSync('sw_vers -productVersion').toString('utf8').trim().split('.').slice(0, 2).join('.');
return `mac${macVersion}`;
}
if (platform === 'linux')
return 'linux';
if (platform === 'win32')
return os.arch() === 'x64' ? 'win64' : 'win32';
return platform;
})();
canDownload(revision: string = this._preferredRevision): Promise<boolean> {
const url = this._params(this._platform, revision).downloadUrl;
let resolve: (result: boolean) => void = () => {};
const promise = new Promise<boolean>(x => resolve = x);
const request = httpRequest(url, 'HEAD', response => {
resolve(response.statusCode === 200);
});
request.on('error', (error: any) => {
console.error(error);
resolve(false);
});
return promise;
}
async download(revision: string = this._preferredRevision, progressCallback?: OnProgressCallback): Promise<BrowserFetcherRevisionInfo> {
const url = this._params(this._platform, revision).downloadUrl;
const zipPath = path.join(this._downloadsFolder, `download-${this._platform}-${revision}.zip`);
const folderPath = this._getFolderPath(revision);
if (await existsAsync(folderPath))
return this.revisionInfo(revision);
if (!(await existsAsync(this._downloadsFolder)))
await mkdirAsync(this._downloadsFolder);
try {
await downloadFile(url, zipPath, progressCallback);
await extractZip(zipPath, folderPath);
} finally {
if (await existsAsync(zipPath))
await unlinkAsync(zipPath);
}
const revisionInfo = this.revisionInfo(revision);
if (revisionInfo)
await chmodAsync(revisionInfo.executablePath, 0o755);
return revisionInfo;
}
async localRevisions(): Promise<string[]> {
if (!await existsAsync(this._downloadsFolder))
return [];
const fileNames: string[] = await readdirAsync(this._downloadsFolder);
return fileNames.map(fileName => parseFolderPath(fileName)).filter(entry => entry && entry.platform === this._platform).map(entry => entry!.revision);
}
async remove(revision: string = this._preferredRevision) {
const folderPath = this._getFolderPath(revision);
assert(await existsAsync(folderPath), `Failed to remove: revision ${revision} is not downloaded`);
await new Promise(fulfill => removeRecursive(folderPath, fulfill));
}
revisionInfo(revision: string = this._preferredRevision): BrowserFetcherRevisionInfo {
const folderPath = this._getFolderPath(revision);
const params = this._params(this._platform, revision);
const local = fs.existsSync(folderPath);
return {revision, executablePath: path.join(folderPath, params.executablePath), folderPath, local, url: params.downloadUrl};
}
_getFolderPath(revision: string): string {
return path.join(this._downloadsFolder, this._platform + '-' + revision);
}
function revisionURL(options: DownloadOptions): string {
const {
browser,
revision,
platform = CURRENT_HOST_PLATFORM,
host = DEFAULT_DOWNLOAD_HOSTS[browser],
} = options;
assert(revision, `'revision' must be specified`);
assert(DOWNLOAD_URLS[browser], 'Unsupported browser: ' + browser);
const urlTemplate = (DOWNLOAD_URLS[browser] as any)[platform];
assert(urlTemplate, `ERROR: Playwright does not support ${browser} on ${platform}`);
return util.format(urlTemplate, host, revision);
}
function parseFolderPath(folderPath: string): { platform: string; revision: string; } | null {
const name = path.basename(folderPath);
const splits = name.split('-');
if (splits.length !== 2)
return null;
const [platform, revision] = splits;
return {platform, revision};
export async function downloadBrowser(options: DownloadOptions): Promise<string> {
const {
browser,
revision,
downloadPath,
platform = CURRENT_HOST_PLATFORM,
progress,
} = options;
assert(downloadPath, '`downloadPath` must be provided');
const url = revisionURL(options);
const zipPath = path.join(os.tmpdir(), `playwright-download-${browser}-${platform}-${revision}.zip`);
if (await existsAsync(downloadPath))
throw new Error('ERROR: downloadPath folder already exists!');
try {
await downloadFile(url, zipPath, progress);
// await mkdirAsync(downloadPath, {recursive: true});
await extractZip(zipPath, downloadPath);
} finally {
if (await existsAsync(zipPath))
await unlinkAsync(zipPath);
}
const executablePath = path.join(downloadPath, ...RELATIVE_EXECUTABLE_PATHS[browser][platform as BrowserPlatform]);
await chmodAsync(executablePath, 0o755);
return executablePath;
}
export async function canDownload(options: DownloadOptions): Promise<boolean> {
const url = revisionURL(options);
let resolve: (result: boolean) => void = () => {};
const promise = new Promise<boolean>(x => resolve = x);
const request = httpRequest(url, 'HEAD', response => {
resolve(response.statusCode === 200);
});
request.on('error', (error: any) => {
console.error(error);
resolve(false);
});
return promise;
}
function downloadFile(url: string, destinationPath: string, progressCallback: OnProgressCallback | undefined): Promise<any> {
@ -199,17 +241,3 @@ function httpRequest(url: string, method: string, response: (r: any) => void) {
request.end();
return request;
}
export type BrowserFetcherOptions = {
platform?: string,
path?: string,
host?: string,
};
export type BrowserFetcherRevisionInfo = {
folderPath: string,
executablePath: string,
url: string,
local: boolean,
revision: string,
};

View File

@ -17,7 +17,6 @@
import { Browser, ConnectOptions } from '../browser';
import { BrowserContext } from '../browserContext';
import { BrowserServer } from './browserServer';
import { OnProgressCallback } from './browserFetcher';
export type BrowserArgOptions = {
headless?: boolean,
@ -47,5 +46,4 @@ export interface BrowserType {
launchServer(options?: LaunchOptions & { port?: number }): Promise<BrowserServer>;
launchPersistentContext(userDataDir: string, options?: LaunchOptions): Promise<BrowserContext>;
connect(options: ConnectOptions): Promise<Browser>;
downloadBrowserIfNeeded(progress?: OnProgressCallback): Promise<void>;
}

View File

@ -18,9 +18,7 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from '../server/browserFetcher';
import { assert, helper } from '../helper';
import { helper } from '../helper';
import { CRBrowser } from '../chromium/crBrowser';
import * as platform from '../platform';
import { TimeoutError } from '../errors';
@ -35,14 +33,12 @@ import { ConnectionTransport } from '../transport';
import { BrowserContext } from '../browserContext';
export class Chromium implements BrowserType {
private _downloadPath: string;
private _downloadHost: string;
readonly _revision: string;
private _executablePath: (string|undefined);
constructor(downloadPath: string, downloadHost: (string|undefined), preferredRevision: string) {
this._downloadPath = downloadPath;
this._downloadHost = downloadHost || 'https://storage.googleapis.com';
this._revision = preferredRevision;
executablePath(): string {
if (!this._executablePath)
throw new Error('No executable path!');
return this._executablePath;
}
name() {
@ -97,16 +93,12 @@ export class Chromium implements BrowserType {
else
chromeArguments.push(...args);
let chromeExecutable = executablePath;
if (!executablePath) {
const {missingText, executablePath} = this._resolveExecutablePath();
if (missingText)
throw new Error(missingText);
chromeExecutable = executablePath;
}
const chromeExecutable = executablePath || this._executablePath;
if (!chromeExecutable)
throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
let browserServer: BrowserServer | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: chromeExecutable!,
executablePath: chromeExecutable,
args: chromeArguments,
env,
handleSIGINT,
@ -134,7 +126,7 @@ export class Chromium implements BrowserType {
let transport: PipeTransport | undefined;
let browserWSEndpoint: string | undefined;
if (launchType === 'server') {
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chromium! The only Chromium revision guaranteed to work is r${this._revision}`);
const timeoutError = new TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chromium!`);
const match = await waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, timeout, timeoutError);
browserWSEndpoint = match[1];
browserServer = new BrowserServer(launchedProcess, gracefullyClose, browserWSEndpoint);
@ -153,10 +145,6 @@ export class Chromium implements BrowserType {
});
}
executablePath(): string {
return this._resolveExecutablePath().executablePath;
}
private _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string, port: number): string[] {
const {
devtools = false,
@ -193,71 +181,6 @@ export class Chromium implements BrowserType {
return chromeArguments;
}
async downloadBrowserIfNeeded(onProgress?: OnProgressCallback) {
const fetcher = this._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return;
await fetcher.download(revisionInfo.revision, onProgress);
}
_createBrowserFetcher(options: BrowserFetcherOptions = {}): BrowserFetcher {
const downloadURLs = {
linux: '%s/chromium-browser-snapshots/Linux_x64/%d/%s.zip',
mac: '%s/chromium-browser-snapshots/Mac/%d/%s.zip',
win32: '%s/chromium-browser-snapshots/Win/%d/%s.zip',
win64: '%s/chromium-browser-snapshots/Win_x64/%d/%s.zip',
};
const defaultOptions = {
path: path.join(this._downloadPath, '.local-chromium'),
host: this._downloadHost,
platform: (() => {
const platform = os.platform();
if (platform === 'darwin')
return 'mac';
if (platform === 'linux')
return 'linux';
if (platform === 'win32')
return os.arch() === 'x64' ? 'win64' : 'win32';
return platform;
})()
};
options = {
...defaultOptions,
...options,
};
assert(!!(downloadURLs as any)[options.platform!], 'Unsupported platform: ' + options.platform);
return new BrowserFetcher(options.path!, options.platform!, this._revision, (platform: string, revision: string) => {
let archiveName = '';
let executablePath = '';
if (platform === 'linux') {
archiveName = 'chrome-linux';
executablePath = path.join(archiveName, 'chrome');
} else if (platform === 'mac') {
archiveName = 'chrome-mac';
executablePath = path.join(archiveName, 'Chromium.app', 'Contents', 'MacOS', 'Chromium');
} else if (platform === 'win32' || platform === 'win64') {
// Windows archive name changed at r591479.
archiveName = parseInt(revision, 10) > 591479 ? 'chrome-win' : 'chrome-win32';
executablePath = path.join(archiveName, 'chrome.exe');
}
return {
downloadUrl: util.format((downloadURLs as any)[platform], options.host, revision, archiveName),
executablePath
};
});
}
_resolveExecutablePath(): { executablePath: string; missingText: string | null; } {
const browserFetcher = this._createBrowserFetcher();
const revisionInfo = browserFetcher.revisionInfo();
const missingText = !revisionInfo.local ? `Chromium revision is not downloaded. Run "npm install"` : null;
return { executablePath: revisionInfo.executablePath, missingText };
}
}
const mkdtempAsync = platform.promisify(fs.mkdtemp);

View File

@ -18,16 +18,14 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import * as util from 'util';
import { ConnectOptions, LaunchType } from '../browser';
import { BrowserContext } from '../browserContext';
import { TimeoutError } from '../errors';
import { Events } from '../events';
import { FFBrowser } from '../firefox/ffBrowser';
import { kBrowserCloseMessageId } from '../firefox/ffConnection';
import { assert, helper } from '../helper';
import { helper } from '../helper';
import * as platform from '../platform';
import { BrowserFetcher, BrowserFetcherOptions, OnProgressCallback } from './browserFetcher';
import { BrowserServer } from './browserServer';
import { BrowserArgOptions, BrowserType, LaunchOptions } from './browserType';
import { launchProcess, waitForLine } from './processLauncher';
@ -35,23 +33,12 @@ import { launchProcess, waitForLine } from './processLauncher';
const mkdtempAsync = platform.promisify(fs.mkdtemp);
export class Firefox implements BrowserType {
private _downloadPath: string;
private _downloadHost: string;
readonly _revision: string;
private _executablePath: (string|undefined);
constructor(downloadPath: string, downloadHost: (string|undefined), preferredRevision: string) {
this._downloadPath = downloadPath;
this._downloadHost = downloadHost || 'https://playwright.azureedge.net';
this._revision = preferredRevision;
}
async downloadBrowserIfNeeded(onProgress?: OnProgressCallback) {
const fetcher = this._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return;
await fetcher.download(revisionInfo.revision, onProgress);
executablePath(): string {
if (!this._executablePath)
throw new Error('No executable path!');
return this._executablePath;
}
name() {
@ -116,13 +103,9 @@ export class Firefox implements BrowserType {
else
firefoxArguments.push(...args);
let firefoxExecutable = executablePath;
if (!firefoxExecutable) {
const {missingText, executablePath} = this._resolveExecutablePath();
if (missingText)
throw new Error(missingText);
firefoxExecutable = executablePath;
}
const firefoxExecutable = executablePath || this._executablePath;
if (!firefoxExecutable)
throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
let browserServer: BrowserServer | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
@ -168,10 +151,6 @@ export class Firefox implements BrowserType {
});
}
executablePath(): string {
return this._resolveExecutablePath().executablePath;
}
private _defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string, port: number): string[] {
const {
devtools = false,
@ -204,55 +183,5 @@ export class Firefox implements BrowserType {
firefoxArguments.push('about:blank');
return firefoxArguments;
}
_createBrowserFetcher(options: BrowserFetcherOptions = {}): BrowserFetcher {
const downloadURLs = {
linux: '%s/builds/firefox/%s/firefox-linux.zip',
mac: '%s/builds/firefox/%s/firefox-mac.zip',
win32: '%s/builds/firefox/%s/firefox-win32.zip',
win64: '%s/builds/firefox/%s/firefox-win64.zip',
};
const defaultOptions = {
path: path.join(this._downloadPath, '.local-firefox'),
host: this._downloadHost,
platform: (() => {
const platform = os.platform();
if (platform === 'darwin')
return 'mac';
if (platform === 'linux')
return 'linux';
if (platform === 'win32')
return os.arch() === 'x64' ? 'win64' : 'win32';
return platform;
})()
};
options = {
...defaultOptions,
...options,
};
assert(!!(downloadURLs as any)[options.platform!], 'Unsupported platform: ' + options.platform);
return new BrowserFetcher(options.path!, options.platform!, this._revision, (platform: string, revision: string) => {
let executablePath = '';
if (platform === 'linux')
executablePath = path.join('firefox', 'firefox');
else if (platform === 'mac')
executablePath = path.join('firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox');
else if (platform === 'win32' || platform === 'win64')
executablePath = path.join('firefox', 'firefox.exe');
return {
downloadUrl: util.format((downloadURLs as any)[platform], options.host, revision),
executablePath
};
});
}
_resolveExecutablePath() {
const browserFetcher = this._createBrowserFetcher();
const revisionInfo = browserFetcher.revisionInfo();
const missingText = !revisionInfo.local ? `Firefox revision is not downloaded. Run "npm install"` : null;
return { executablePath: revisionInfo.executablePath, missingText };
}
}

View File

@ -23,17 +23,13 @@ import { Chromium } from './chromium';
import { WebKit } from './webkit';
import { Firefox } from './firefox';
const packageJSON = require('../../package.json');
for (const className in api) {
if (typeof (api as any)[className] === 'function')
helper.installApiHooks(className[0].toLowerCase() + className.substring(1), (api as any)[className]);
}
type PlaywrightOptions = {
downloadPath: string,
browsers: Array<('firefox'|'webkit'|'chromium')>,
respectEnvironmentVariables: boolean,
};
export class Playwright {
@ -46,25 +42,15 @@ export class Playwright {
constructor(options: PlaywrightOptions) {
const {
downloadPath,
browsers,
respectEnvironmentVariables,
} = options;
this.devices = DeviceDescriptors;
this.errors = { TimeoutError };
const downloadHost = respectEnvironmentVariables ? getFromENV('PLAYWRIGHT_DOWNLOAD_HOST') : undefined;
if (browsers.includes('chromium'))
this.chromium = new Chromium(downloadPath, downloadHost, packageJSON.playwright.chromium_revision);
this.chromium = new Chromium();
if (browsers.includes('webkit'))
this.webkit = new WebKit(downloadPath, downloadHost, packageJSON.playwright.webkit_revision);
this.webkit = new WebKit();
if (browsers.includes('firefox'))
this.firefox = new Firefox(downloadPath, downloadHost, packageJSON.playwright.firefox_revision);
this.firefox = new Firefox();
}
}
function getFromENV(name: string): (string|undefined) {
let value = process.env[name];
value = value || process.env[`npm_config_${name.toLowerCase()}`];
value = value || process.env[`npm_package_config_${name.toLowerCase()}`];
return value;
}

View File

@ -15,17 +15,14 @@
* limitations under the License.
*/
import { BrowserFetcher, OnProgressCallback, BrowserFetcherOptions } from './browserFetcher';
import { WKBrowser } from '../webkit/wkBrowser';
import { execSync } from 'child_process';
import { PipeTransport } from './pipeTransport';
import { launchProcess } from './processLauncher';
import * as fs from 'fs';
import * as path from 'path';
import * as platform from '../platform';
import * as util from 'util';
import * as os from 'os';
import { assert, helper } from '../helper';
import { helper } from '../helper';
import { kBrowserCloseMessageId } from '../webkit/wkConnection';
import { LaunchOptions, BrowserArgOptions, BrowserType } from './browserType';
import { ConnectionTransport } from '../transport';
@ -36,29 +33,18 @@ import { Events } from '../events';
import { BrowserContext } from '../browserContext';
export class WebKit implements BrowserType {
private _downloadPath: string;
private _downloadHost: string;
readonly _revision: string;
private _executablePath: (string|undefined);
constructor(downloadPath: string, downloadHost: (string|undefined), preferredRevision: string) {
this._downloadPath = downloadPath;
this._downloadHost = downloadHost || 'https://playwright.azureedge.net';
this._revision = preferredRevision;
executablePath(): string {
if (!this._executablePath)
throw new Error('No executable path!');
return this._executablePath;
}
name() {
return 'webkit';
}
async downloadBrowserIfNeeded(onProgress?: OnProgressCallback) {
const fetcher = this._createBrowserFetcher();
const revisionInfo = fetcher.revisionInfo();
// Do nothing if the revision is already downloaded.
if (revisionInfo.local)
return;
await fetcher.download(revisionInfo.revision, onProgress);
}
async launch(options?: LaunchOptions & { slowMo?: number }): Promise<WKBrowser> {
if (options && (options as any).userDataDir)
throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
@ -106,18 +92,14 @@ export class WebKit implements BrowserType {
else
webkitArguments.push(...args);
let webkitExecutable = executablePath;
if (!executablePath) {
const {missingText, executablePath} = this._resolveExecutablePath();
if (missingText)
throw new Error(missingText);
webkitExecutable = executablePath;
}
const webkitExecutable = executablePath || this._executablePath;
if (!webkitExecutable)
throw new Error(`No executable path is specified.`);
let transport: ConnectionTransport | undefined = undefined;
let browserServer: BrowserServer | undefined = undefined;
const { launchedProcess, gracefullyClose } = await launchProcess({
executablePath: webkitExecutable!,
executablePath: webkitExecutable,
args: webkitArguments,
env: { ...env, CURL_COOKIE_JAR_PATH: path.join(userDataDir!, 'cookiejar.db') },
handleSIGINT,
@ -155,10 +137,6 @@ export class WebKit implements BrowserType {
});
}
executablePath(): string {
return this._resolveExecutablePath().executablePath;
}
_defaultArgs(options: BrowserArgOptions = {}, launchType: LaunchType, userDataDir: string, port: number): string[] {
const {
devtools = false,
@ -182,67 +160,12 @@ export class WebKit implements BrowserType {
webkitArguments.push(...args);
return webkitArguments;
}
_createBrowserFetcher(options?: BrowserFetcherOptions): BrowserFetcher {
const downloadURLs = {
linux: '%s/builds/webkit/%s/minibrowser-gtk-wpe.zip',
mac: '%s/builds/webkit/%s/minibrowser-mac-%s.zip',
win64: '%s/builds/webkit/%s/minibrowser-win64.zip',
};
const defaultOptions = {
path: path.join(this._downloadPath, '.local-webkit'),
host: this._downloadHost,
platform: (() => {
const platform = os.platform();
if (platform === 'darwin')
return 'mac';
if (platform === 'linux')
return 'linux';
if (platform === 'win32')
return 'win64';
return platform;
})()
};
options = {
...defaultOptions,
...options,
};
assert(!!(downloadURLs as any)[options.platform!], 'Unsupported platform: ' + options.platform);
return new BrowserFetcher(options.path!, options.platform!, this._revision, (platform: string, revision: string) => {
return {
downloadUrl: (platform === 'mac') ?
util.format(downloadURLs[platform], options!.host, revision, getMacVersion()) :
util.format((downloadURLs as any)[platform], options!.host, revision),
executablePath: platform.startsWith('win') ? 'MiniBrowser.exe' : 'pw_run.sh',
};
});
}
_resolveExecutablePath(): { executablePath: string; missingText: string | null; } {
const browserFetcher = this._createBrowserFetcher();
const revisionInfo = browserFetcher.revisionInfo();
const missingText = !revisionInfo.local ? `WebKit revision is not downloaded. Run "npm install"` : null;
return { executablePath: revisionInfo.executablePath, missingText };
}
}
const mkdtempAsync = platform.promisify(fs.mkdtemp);
const WEBKIT_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
let cachedMacVersion: string | undefined = undefined;
function getMacVersion(): string {
if (!cachedMacVersion) {
const [major, minor] = execSync('sw_vers -productVersion').toString('utf8').trim().split('.');
assert(+major === 10 && +minor >= 14, 'Error: unsupported macOS version, macOS 10.14 and newer are supported');
cachedMacVersion = major + '.' + minor;
}
return cachedMacVersion;
}
class SequenceNumberMixer<V> {
static _lastSequenceNumber = 1;
private _values = new Map<number, V>();

View File

@ -91,36 +91,6 @@ module.exports.describe = function({testRunner, expect, defaultBrowserOptions, b
});
});
describe('BrowserFetcher', function() {
it('should download and extract linux binary', async({server}) => {
const downloadsFolder = await mkdtempAsync(TMP_FOLDER);
const browserFetcher = browserType._createBrowserFetcher({
platform: 'linux',
path: downloadsFolder,
host: server.PREFIX
});
let revisionInfo = browserFetcher.revisionInfo('123456');
server.setRoute(revisionInfo.url.substring(server.PREFIX.length), (req, res) => {
server.serveFile(req, res, '/chromium-linux.zip');
});
expect(revisionInfo.local).toBe(false);
expect(browserFetcher._platform).toBe('linux');
expect(await browserFetcher.canDownload('100000')).toBe(false);
expect(await browserFetcher.canDownload('123456')).toBe(true);
revisionInfo = await browserFetcher.download('123456');
expect(revisionInfo.local).toBe(true);
expect(await readFileAsync(revisionInfo.executablePath, 'utf8')).toBe('LINUX BINARY\n');
const expectedPermissions = WIN ? 0666 : 0755;
expect((await statAsync(revisionInfo.executablePath)).mode & 0777).toBe(expectedPermissions);
expect(await browserFetcher.localRevisions()).toEqual(['123456']);
await browserFetcher.remove('123456');
expect(await browserFetcher.localRevisions()).toEqual([]);
await rmAsync(downloadsFolder);
});
});
describe('BrowserContext', function() {
it('should not create pages automatically', async function() {
const browser = await browserType.launch();

View File

@ -285,9 +285,8 @@ const utils = module.exports = {
makeUserDataDir: async function() {
return await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_profile-'));
},
removeUserDataDir: async function(dir) {
await removeFolderAsync(dir).catch(e => {});
}
};

View File

@ -16,11 +16,11 @@
*/
const assert = require('assert');
const playwright = require('..').chromium;
const browserFetcher = require('../lib/server/browserFetcher.js');
const https = require('https');
const SUPPORTER_PLATFORMS = ['linux', 'mac', 'win32', 'win64'];
const fetchers = SUPPORTER_PLATFORMS.map(platform => playwright._createBrowserFetcher({platform}));
const fetcherOptions = SUPPORTER_PLATFORMS.map(platform => ({platform: platform === 'mac' ? 'mac10.15' : platform, browser: 'chromium'}));
const colors = {
reset: '\x1b[0m',
@ -100,7 +100,7 @@ async function checkRangeAvailability(fromRevision, toRevision, stopWhenAllAvail
* @return {boolean}
*/
async function checkAndDrawRevisionAvailability(table, name, revision) {
const promises = fetchers.map(fetcher => fetcher.canDownload(revision));
const promises = fetcherOptions.map(options => browserFetcher.canDownload({...options, revision}));
const availability = await Promise.all(promises);
const allAvailable = availability.every(e => !!e);
const values = [name + ' ' + (allAvailable ? colors.green + revision + colors.reset : revision)];

View File

@ -4,29 +4,26 @@ const fs = require('fs');
const StreamZip = require('node-stream-zip');
const vm = require('vm');
const os = require('os');
const util = require('util');
async function generateChromiunProtocol(revision) {
async function generateChromiumProtocol(executablePath) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'chromium', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const playwright = await require('../../index').chromium;
const browserServer = await playwright.launchServer({ executablePath: revision.executablePath, args: ['--no-sandbox'] });
const browserServer = await playwright.launchServer({ executablePath, args: ['--no-sandbox'] });
const origin = browserServer.wsEndpoint().match(/ws:\/\/([0-9A-Za-z:\.]*)\//)[1];
const browser = await playwright.connect({ wsEndpoint: browserServer.wsEndpoint() });
const page = await browser.newPage();
await page.goto(`http://${origin}/json/protocol`);
const json = JSON.parse(await page.evaluate(() => document.documentElement.innerText));
await browserServer.close();
fs.writeFileSync(outputPath, jsonToTS(json));
await fs.promises.writeFile(outputPath, jsonToTS(json));
console.log(`Wrote protocol.ts to ${path.relative(process.cwd(), outputPath)}`);
}
async function generateWebKitProtocol(revision) {
async function generateWebKitProtocol(folderPath) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'webkit', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const json = JSON.parse(fs.readFileSync(path.join(revision.folderPath, 'protocol.json'), 'utf8'));
fs.writeFileSync(outputPath, jsonToTS({domains: json}));
const json = JSON.parse(await fs.promises.readFile(path.join(folderPath, 'protocol.json'), 'utf8'));
await fs.promises.writeFile(outputPath, jsonToTS({domains: json}));
console.log(`Wrote protocol.ts for WebKit to ${path.relative(process.cwd(), outputPath)}`);
}
@ -118,13 +115,11 @@ function typeOfProperty(property, domain) {
return property.type;
}
async function generateFirefoxProtocol(revision) {
async function generateFirefoxProtocol(executablePath) {
const outputPath = path.join(__dirname, '..', '..', 'src', 'firefox', 'protocol.ts');
if (revision.local && fs.existsSync(outputPath))
return;
const omnija = os.platform() === 'darwin' ?
path.join(revision.executablePath, '..', '..', 'Resources', 'omni.ja') :
path.join(revision.executablePath, '..', 'omni.ja');
path.join(executablePath, '..', '..', 'Resources', 'omni.ja') :
path.join(executablePath, '..', 'omni.ja');
const zip = new StreamZip({file: omnija, storeEntries: true});
// @ts-ignore
await new Promise(x => zip.on('ready', x));
@ -164,7 +159,7 @@ async function generateFirefoxProtocol(revision) {
}
}
const json = vm.runInContext(`(${inject})();${protocolJSCode}; this.protocol;`, ctx);
fs.writeFileSync(outputPath, firefoxJSONToTS(json));
await fs.promises.writeFile(outputPath, firefoxJSONToTS(json));
console.log(`Wrote protocol.ts for Firefox to ${path.relative(process.cwd(), outputPath)}`);
}
@ -216,4 +211,4 @@ function firefoxTypeToString(type, indent=' ') {
return type['$type'];
}
module.exports = {generateChromiunProtocol, generateFirefoxProtocol, generateWebKitProtocol};
module.exports = {generateChromiumProtocol, generateFirefoxProtocol, generateWebKitProtocol};