diff --git a/.circleci/config.yml b/.circleci/config.yml index 3bd4e428ffea0..259804b77a4f7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -15,9 +15,7 @@ jobs: - save_cache: key: dependency-cache-{{ checksum "package.json" }} paths: - - ./.local-chromium - - ./.local-firefox - - ./.local-webkit + - ./.local-browsers - run: command: | diff --git a/.gitignore b/.gitignore index 589960ef12092..a0d4e623c07fd 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/docs/api.md b/docs/api.md index 0206885e41b52..ecaf12cafd179 100644 --- a/docs/api.md +++ b/docs/api.md @@ -3602,7 +3602,6 @@ const { chromium } = require('playwright'); // Or 'firefox' or 'webkit'. - [browserType.connect(options)](#browsertypeconnectoptions) -- [browserType.downloadBrowserIfNeeded([progress])](#browsertypedownloadbrowserifneededprogress) - [browserType.executablePath()](#browsertypeexecutablepath) - [browserType.launch([options])](#browsertypelaunchoptions) - [browserType.launchPersistentContext(userDataDir, [options])](#browsertypelaunchpersistentcontextuserdatadir-options) @@ -3618,14 +3617,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: diff --git a/docs/troubleshooting.md b/docs/troubleshooting.md index 15d85d029a8e8..395cdcf4bbfe9 100644 --- a/docs/troubleshooting.md +++ b/docs/troubleshooting.md @@ -164,7 +164,7 @@ done only once per host environment: ```bash # cd to the downloaded instance -cd /node_modules/playwright/.local-chromium/linux-/chrome-linux/ +cd /node_modules/playwright/.local-browsers/chromium-/ sudo chown root:root chrome_sandbox sudo chmod 4755 chrome_sandbox # copy sandbox executable to a shared location diff --git a/download-browser.js b/download-browser.js index 11a6cc2b057f4..05514579a9d3e 100644 --- a/download-browser.js +++ b/download-browser.js @@ -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}; diff --git a/index.js b/index.js index 4dd03dabdf75f..22381bfca2fe9 100644 --- a/index.js +++ b/index.js @@ -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; + diff --git a/install-from-github.js b/install-from-github.js index a74a56c59af2f..f51d19f98b66c 100644 --- a/install-from-github.js +++ b/install-from-github.js @@ -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 { - const firefoxRevision = await downloadAndCleanup(playwright.firefox); - await protocolGenerator.generateFirefoxProtocol(firefoxRevision); + 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 { - const webkitRevision = await downloadAndCleanup(playwright.webkit); - await protocolGenerator.generateWebKitProtocol(webkitRevision); + 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); } -})(); -async function downloadAndCleanup(browserType) { - const revisionInfo = await downloadBrowser(browserType); + // 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))); - // 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]); + async function readdirAsync(dirpath) { + return fs.promises.readdir(dirpath).then(dirs => dirs.map(dir => path.join(dirpath, dir))); + } +})(); - return revisionInfo; -} diff --git a/packages/playwright-chromium/index.js b/packages/playwright-chromium/index.js index 4901c679e72d1..1ff40f5174dd9 100644 --- a/packages/playwright-chromium/index.js +++ b/packages/playwright-chromium/index.js @@ -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.'); +} diff --git a/packages/playwright-chromium/install.js b/packages/playwright-chromium/install.js index 8955679e2e763..782cfb4598993 100644 --- a/packages/playwright-chromium/install.js +++ b/packages/playwright-chromium/install.js @@ -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})); +})(); diff --git a/packages/playwright-firefox/index.js b/packages/playwright-firefox/index.js index 7dbf16eac6101..0236851b65dcd 100644 --- a/packages/playwright-firefox/index.js +++ b/packages/playwright-firefox/index.js @@ -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.'); +} diff --git a/packages/playwright-firefox/install.js b/packages/playwright-firefox/install.js index ea6ba3f9644d4..2c90c29437fab 100644 --- a/packages/playwright-firefox/install.js +++ b/packages/playwright-firefox/install.js @@ -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, })); +})(); diff --git a/packages/playwright-webkit/index.js b/packages/playwright-webkit/index.js index 0e15ee1b52063..b5a4f5738f157 100644 --- a/packages/playwright-webkit/index.js +++ b/packages/playwright-webkit/index.js @@ -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.'); +} diff --git a/packages/playwright-webkit/install.js b/packages/playwright-webkit/install.js index 0a7b2b2f6aa8c..14546c13612ba 100644 --- a/packages/playwright-webkit/install.js +++ b/packages/playwright-webkit/install.js @@ -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, })); +})(); diff --git a/packages/playwright/index.js b/packages/playwright/index.js index 5866f0899d9ab..db84bb9d034cf 100644 --- a/packages/playwright/index.js +++ b/packages/playwright/index.js @@ -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'); +} + diff --git a/packages/playwright/install.js b/packages/playwright/install.js index daaee729565de..bf628efc9c357 100644 --- a/packages/playwright/install.js +++ b/packages/playwright/install.js @@ -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, })); })(); diff --git a/src/server/browserFetcher.ts b/src/server/browserFetcher.ts index 9d049190fb9e2..466c813b12089 100644 --- a/src/server/browserFetcher.ts +++ b/src/server/browserFetcher.ts @@ -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 => new Promise(resolve => fs.stat(path, err => resolve(!err))); -function existsAsync(filePath: string): Promise { - let fulfill: (exists: boolean) => void; - const promise = new Promise(x => fulfill = x); - fs.access(filePath, err => fulfill(!err)); - return promise; -} - -type ParamsGetter = (platform: string, revision: string) => { downloadUrl: string, executablePath: string }; - -export type OnProgressCallback = (downloadedBytes: number, totalBytes: number) => void; - -export class BrowserFetcher { - private _downloadsFolder: string; - private _platform: string; - private _preferredRevision: string; - private _params: ParamsGetter; - - constructor(downloadsFolder: string, platform: string, preferredRevision: string, params: ParamsGetter) { - this._downloadsFolder = downloadsFolder; - this._platform = platform; - this._preferredRevision = preferredRevision; - this._params = params; - } +const DEFAULT_DOWNLOAD_HOSTS = { + chromium: 'https://storage.googleapis.com', + firefox: 'https://playwright.azureedge.net', + webkit: 'https://playwright.azureedge.net', +}; - canDownload(revision: string = this._preferredRevision): Promise { - const url = this._params(this._platform, revision).downloadUrl; - let resolve: (result: boolean) => void = () => {}; - const promise = new Promise(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; - } +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', + }, +}; - async download(revision: string = this._preferredRevision, progressCallback?: OnProgressCallback): Promise { - 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; - } +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'], + }, +}; - async localRevisions(): Promise { - 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); - } +export type OnProgressCallback = (downloadedBytes: number, totalBytes: number) => void; +export type BrowserName = ('chromium'|'webkit'|'firefox'); +export type BrowserPlatform = ('win32'|'win64'|'mac10.14'|'mac10.15'|'linux'); - 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)); - } +export type DownloadOptions = { + browser: BrowserName, + revision: string, + downloadPath: string, + platform?: BrowserPlatform, + host?: string, + progress?: OnProgressCallback, +}; - 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}; +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; +})(); + +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); +} - _getFolderPath(revision: string): string { - return path.join(this._downloadsFolder, this._platform + '-' + revision); +export async function downloadBrowser(options: DownloadOptions): Promise { + 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; } -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 canDownload(options: DownloadOptions): Promise { + const url = revisionURL(options); + let resolve: (result: boolean) => void = () => {}; + const promise = new Promise(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 { @@ -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, -}; diff --git a/src/server/browserType.ts b/src/server/browserType.ts index ff4dc21ceb214..eafb4a0b7f454 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -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; launchPersistentContext(userDataDir: string, options?: LaunchOptions): Promise; connect(options: ConnectOptions): Promise; - downloadBrowserIfNeeded(progress?: OnProgressCallback): Promise; } diff --git a/src/server/chromium.ts b/src/server/chromium.ts index 2cb977334d2b6..8e38d429f7358 100644 --- a/src/server/chromium.ts +++ b/src/server/chromium.ts @@ -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); diff --git a/src/server/firefox.ts b/src/server/firefox.ts index ea70e9111d05f..b6b55cb8aeb1e 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -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 }; - } } diff --git a/src/server/playwright.ts b/src/server/playwright.ts index 3a3d96d73ccef..6141b97891b57 100644 --- a/src/server/playwright.ts +++ b/src/server/playwright.ts @@ -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; -} diff --git a/src/server/webkit.ts b/src/server/webkit.ts index b6997db3d36c2..0fb6dfa40677b 100644 --- a/src/server/webkit.ts +++ b/src/server/webkit.ts @@ -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'; @@ -37,29 +34,18 @@ import { Events } from '../events'; import { BrowserContext } from '../browserContext'; export class WebKit implements BrowserType { - private _downloadPath: string; - private _downloadHost: string; - readonly _revision: string; - - constructor(downloadPath: string, downloadHost: (string|undefined), preferredRevision: string) { - this._downloadPath = downloadPath; - this._downloadHost = downloadHost || 'https://playwright.azureedge.net'; - this._revision = preferredRevision; + private _executablePath: (string|undefined); + + 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 { if (options && (options as any).userDataDir) throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead'); @@ -107,18 +93,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, @@ -156,10 +138,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, @@ -183,67 +161,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 { static _lastSequenceNumber = 1; private _values = new Map(); diff --git a/test/chromium/launcher.spec.js b/test/chromium/launcher.spec.js index cebb683d91cbd..12b15971bc1e9 100644 --- a/test/chromium/launcher.spec.js +++ b/test/chromium/launcher.spec.js @@ -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(); diff --git a/test/utils.js b/test/utils.js index 4ac2595875c18..4f15bfb3ba766 100644 --- a/test/utils.js +++ b/test/utils.js @@ -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 => {}); } - }; diff --git a/utils/check_availability.js b/utils/check_availability.js index 553888e9b6c5b..340d5527caaeb 100755 --- a/utils/check_availability.js +++ b/utils/check_availability.js @@ -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)]; diff --git a/utils/protocol-types-generator/index.js b/utils/protocol-types-generator/index.js index c51f2833d123e..d205cfa1511bc 100644 --- a/utils/protocol-types-generator/index.js +++ b/utils/protocol-types-generator/index.js @@ -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};