From 1f0217986e2c1eb9f3b114f5e76b97039741dd88 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 4 May 2020 09:34:59 -0700 Subject: [PATCH] feat(firefox): cache firefox pre-compiled scripts (#2087) --- src/install/installer.ts | 21 ++++++++++++++++++--- src/server/browserType.ts | 5 +++-- src/server/firefox.ts | 26 +++++++++++++++++++++++++- 3 files changed, 46 insertions(+), 6 deletions(-) diff --git a/src/install/installer.ts b/src/install/installer.ts index 6fdf30ef5e..e18db5d28c 100644 --- a/src/install/installer.ts +++ b/src/install/installer.ts @@ -22,6 +22,7 @@ import * as util from 'util'; import * as removeFolder from 'rimraf'; import * as browserPaths from '../install/browserPaths'; import * as browserFetcher from '../install/browserFetcher'; +import { Playwright } from '../server/playwright'; const fsMkdirAsync = util.promisify(fs.mkdir.bind(fs)); const fsReaddirAsync = util.promisify(fs.readdir.bind(fs)); @@ -40,10 +41,10 @@ export async function installBrowsersWithProgressBar(packagePath: string) { } await fsMkdirAsync(linksDir, { recursive: true }); await fsWriteFileAsync(path.join(linksDir, sha1(packagePath)), packagePath); - await validateCache(browsersPath, linksDir); + await validateCache(packagePath, browsersPath, linksDir); } -async function validateCache(browsersPath: string, linksDir: string) { +async function validateCache(packagePath: string, browsersPath: string, linksDir: string) { // 1. Collect unused downloads and package descriptors. const allBrowsers: browserPaths.BrowserDescriptor[] = []; for (const fileName of await fsReaddirAsync(linksDir)) { @@ -73,10 +74,24 @@ async function validateCache(browsersPath: string, linksDir: string) { // 3. Install missing browsers. for (const browser of allBrowsers) { const browserPath = browserPaths.browserDirectory(browsersPath, browser); - await browserFetcher.downloadBrowserWithProgressBar(browserPath, browser); + if (await browserFetcher.downloadBrowserWithProgressBar(browserPath, browser)) + await installBrowser(packagePath, browserPath, browser); } } +async function installBrowser(packagePath: string, browserPath: string, browser: browserPaths.BrowserDescriptor) { + if (browser.name !== 'firefox') + return; + const firefox = new Playwright(packagePath, [browser]).firefox!; + const userDataDir = path.join(browserPath, 'cached-profile'); + await fsMkdirAsync(userDataDir, { recursive: true }); + logPolitely('Pre-compiling Firefox scripts at ' + userDataDir); + const browserContext = await firefox.launchPersistentContext(userDataDir); + const page = await browserContext.newPage(); + await page.goto('data:text/html,Hello world'); + await browserContext.close(); +} + function sha1(data: string): string { const sum = crypto.createHash('sha1'); sum.update(data); diff --git a/src/server/browserType.ts b/src/server/browserType.ts index 5f897b5e7c..de9c794708 100644 --- a/src/server/browserType.ts +++ b/src/server/browserType.ts @@ -54,13 +54,14 @@ export interface BrowserType { export abstract class AbstractBrowserType implements BrowserType { private _name: string; + readonly _browserPath: string; private _executablePath: string | undefined; constructor(packagePath: string, browser: browserPaths.BrowserDescriptor) { this._name = browser.name; const browsersPath = browserPaths.browsersPath(packagePath); - const browserPath = browserPaths.browserDirectory(browsersPath, browser); - this._executablePath = browserPaths.executablePath(browserPath, browser); + this._browserPath = browserPaths.browserDirectory(browsersPath, browser); + this._executablePath = browserPaths.executablePath(this._browserPath, browser); } executablePath(): string { diff --git a/src/server/firefox.ts b/src/server/firefox.ts index 81d8021f67..d1d430ee97 100644 --- a/src/server/firefox.ts +++ b/src/server/firefox.ts @@ -91,7 +91,7 @@ export class Firefox extends AbstractBrowserType { let temporaryProfileDir = null; if (!userDataDir) { - userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); + userDataDir = await this._createTemporaryProfile(); temporaryProfileDir = userDataDir; } @@ -189,6 +189,15 @@ export class Firefox extends AbstractBrowserType { return firefoxArguments; } + + async _createTemporaryProfile(): Promise { + const userDataDir = await mkdtempAsync(path.join(os.tmpdir(), 'playwright_dev_firefox_profile-')); + const cachedProfileDir = path.join(this._browserPath, 'cached-profile'); + const files = fs.readdirSync(cachedProfileDir); + for (const file of files) + copyRecursiveSync(path.join(cachedProfileDir, file), userDataDir); + return userDataDir; + } } function wrapTransportWithWebSocket(transport: ConnectionTransport, logger: InnerLogger, port: number): WebSocketWrapper { @@ -319,3 +328,18 @@ function wrapTransportWithWebSocket(transport: ConnectionTransport, logger: Inne return new WebSocketWrapper(wsEndpoint, [pendingBrowserContextCreations, pendingBrowserContextDeletions, browserContextIds, sessionToSocket, sockets]); } + +function copyRecursiveSync(source: string, targetFolder: string) { + const target = path.join(targetFolder, path.basename(source)); + const stat = fs.lstatSync(source); + if (stat.isFile()) { + fs.copyFileSync(source, target); + return; + } + if (stat.isDirectory()) { + fs.mkdirSync(target, { recursive: true }); + const files = fs.readdirSync(source); + for (const file of files) + copyRecursiveSync(path.join(source, file), target); + } +}