Skip to content

Commit

Permalink
Merge pull request #2357 from GoogleChrome/allow-custom-profile
Browse files Browse the repository at this point in the history
Enable passing of a custom userDataDir to launcher
  • Loading branch information
paulirish authored Jun 5, 2017
2 parents 387731a + a05e85d commit a9f5785
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 86 deletions.
58 changes: 35 additions & 23 deletions chrome-launcher/chrome-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export interface Options {
port?: number;
handleSIGINT?: boolean;
chromePath?: string;
userDataDir?: string;
}

export interface LaunchedChrome {
Expand All @@ -49,6 +50,8 @@ export interface LaunchedChrome {
kill: () => Promise<{}>;
}

export interface ModuleOverrides { fs?: typeof fs, rimraf?: typeof rimraf, }

export async function launch(opts: Options = {}): Promise<LaunchedChrome> {
opts.handleSIGINT = defaults(opts.handleSIGINT, true);

Expand All @@ -72,29 +75,35 @@ export class Launcher {
private pollInterval: number = 500;
private pidFile: string;
private startingUrl: string;
private TMP_PROFILE_DIR: string;
private outFile?: number;
private errFile?: number;
private chromePath?: string;
private chromeFlags: string[];
private chrome?: childProcess.ChildProcess;
private requestedPort?: number;
private chrome?: childProcess.ChildProcess;
private fs: typeof fs;
private rimraf: typeof rimraf;

userDataDir?: string;
port?: number;
pid?: number;

constructor(opts: Options = {}) {
constructor(private opts: Options = {}, moduleOverrides: ModuleOverrides = {}) {
this.fs = moduleOverrides.fs || fs;
this.rimraf = moduleOverrides.rimraf || rimraf;

// choose the first one (default)
this.startingUrl = defaults(opts.startingUrl, 'about:blank');
this.chromeFlags = defaults(opts.chromeFlags, []);
this.requestedPort = defaults(opts.port, 0);
this.chromePath = opts.chromePath;
this.startingUrl = defaults(this.opts.startingUrl, 'about:blank');
this.chromeFlags = defaults(this.opts.chromeFlags, []);
this.requestedPort = defaults(this.opts.port, 0);
this.chromePath = this.opts.chromePath;
}

private get flags() {
const flags = DEFAULT_FLAGS.concat([
`--remote-debugging-port=${this.port}`,
// Place Chrome profile in a custom location we'll rm -rf later
`--user-data-dir=${this.TMP_PROFILE_DIR}`
`--user-data-dir=${this.userDataDir}`
]);

if (process.platform === 'linux') {
Expand All @@ -107,22 +116,26 @@ export class Launcher {
return flags;
}

private prepare() {
// Wrapper function to enable easy testing.
makeTmpDir() {
return makeTmpDir();
}

prepare() {
const platform = process.platform as SupportedPlatforms;
if (!_SUPPORTED_PLATFORMS.has(platform)) {
throw new Error(`Platform ${platform} is not supported`);
}

this.TMP_PROFILE_DIR = makeTmpDir();

this.outFile = fs.openSync(`${this.TMP_PROFILE_DIR}/chrome-out.log`, 'a');
this.errFile = fs.openSync(`${this.TMP_PROFILE_DIR}/chrome-err.log`, 'a');
this.userDataDir = this.opts.userDataDir || this.makeTmpDir();
this.outFile = this.fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a');
this.errFile = this.fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a');

// fix for Node4
// you can't pass a fd to fs.writeFileSync
this.pidFile = `${this.TMP_PROFILE_DIR}/chrome.pid`;
this.pidFile = `${this.userDataDir}/chrome.pid`;

log.verbose('ChromeLauncher', `created ${this.TMP_PROFILE_DIR}`);
log.verbose('ChromeLauncher', `created ${this.userDataDir}`);

this.tmpDirandPidFileReady = true;
}
Expand Down Expand Up @@ -179,7 +192,7 @@ export class Launcher {
execPath, this.flags, {detached: true, stdio: ['ignore', this.outFile, this.errFile]});
this.chrome = chrome;

fs.writeFileSync(this.pidFile, chrome.pid.toString());
this.fs.writeFileSync(this.pidFile, chrome.pid.toString());

log.verbose('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
resolve(chrome.pid);
Expand Down Expand Up @@ -270,25 +283,24 @@ export class Launcher {
});
}

private destroyTmp() {
destroyTmp() {
return new Promise(resolve => {
if (!this.TMP_PROFILE_DIR) {
// Only clean up the tmp dir if we created it.
if (this.userDataDir === undefined || this.opts.userDataDir !== undefined) {
return resolve();
}

log.verbose('ChromeLauncher', `Removing ${this.TMP_PROFILE_DIR}`);

if (this.outFile) {
fs.closeSync(this.outFile);
this.fs.closeSync(this.outFile);
delete this.outFile;
}

if (this.errFile) {
fs.closeSync(this.errFile);
this.fs.closeSync(this.errFile);
delete this.errFile;
}

rimraf(this.TMP_PROFILE_DIR, () => resolve());
this.rimraf(this.userDataDir, () => resolve());
});
}
};
7 changes: 5 additions & 2 deletions chrome-launcher/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,19 @@
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"test": "mocha --reporter dot test/**/*-test.js",
"test": "mocha --require ts-node/register --reporter dot test/**/*-test.ts",
"test-formatting": "test/check-formatting.sh",
"format": "clang-format -i -style=file *.ts"
"format": "clang-format -i -style=file **/*.ts *.ts"
},
"devDependencies": {
"@types/mocha": "^2.2.41",
"clang-format": "^1.0.50",
"mocha": "^3.2.0",
"ts-node": "^3.0.4",
"typescript": "2.2.1"
},
"dependencies": {
"@types/core-js": "^0.9.41",
"@types/mkdirp": "^0.3.29",
"@types/node": "6.0.66"
}
Expand Down
1 change: 1 addition & 0 deletions chrome-launcher/test/check-formatting.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ check_formatting ()
}

check_formatting "*.ts"
check_formatting "**/*.ts"
52 changes: 0 additions & 52 deletions chrome-launcher/test/chrome-launcher-test.js

This file was deleted.

102 changes: 102 additions & 0 deletions chrome-launcher/test/chrome-launcher-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* Copyright 2016 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

import {Launcher} from '../chrome-launcher';

import * as assert from 'assert';

const log = require('../../lighthouse-core/lib/log');
const fsMock = {
openSync: () => {},
closeSync: () => {}
};

describe('Launcher', () => {
it('accepts and uses a custom path', async () => {
log.setLevel('error');
let callCount = 0;
const rimrafMock = (_: string, done: () => void) => {
callCount++;
done();
};

const chromeInstance =
new Launcher({userDataDir: 'some_path'}, {fs: fsMock as any, rimraf: rimrafMock as any});

chromeInstance.prepare();

await chromeInstance.destroyTmp();
assert.strictEqual(callCount, 0);
});

it('cleans up the tmp dir after closing', async () => {
log.setLevel('error');
let callCount = 0;
const rimrafMock = (_: string, done: () => void) => {
callCount++;
done();
};

const chromeInstance = new Launcher({}, {fs: fsMock as any, rimraf: rimrafMock as any});

chromeInstance.prepare();
await chromeInstance.destroyTmp();
assert.strictEqual(callCount, 1);
});

it('does not delete created directory when custom path passed', () => {
log.setLevel('error');

const chromeInstance = new Launcher({userDataDir: 'some_path'}, {fs: fsMock as any});

chromeInstance.prepare();
assert.equal(chromeInstance.userDataDir, 'some_path');
});

it('defaults to genering a tmp dir when no data dir is passed', () => {
log.setLevel('error');
const chromeInstance = new Launcher({}, {fs: fsMock as any});
const originalMakeTmp = chromeInstance.makeTmpDir;
chromeInstance.makeTmpDir = () => 'tmp_dir'
chromeInstance.prepare()
assert.equal(chromeInstance.userDataDir, 'tmp_dir');

// Restore the original fn.
chromeInstance.makeTmpDir = originalMakeTmp;
});

it('doesn\'t fail when killed twice', async () => {
log.setLevel('error');
const chromeInstance = new Launcher();
await chromeInstance.launch();
log.setLevel();
await chromeInstance.kill();
await chromeInstance.kill();
});

it('doesn\'t launch multiple chrome processes', async () => {
log.setLevel('error');
const chromeInstance = new Launcher();
await chromeInstance.launch();
let pid = chromeInstance.pid!;
await chromeInstance.launch();
log.setLevel();
assert.strictEqual(pid, chromeInstance.pid);
await chromeInstance.kill();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
*/
'use strict';

/* eslint-env mocha */

const assert = require('assert');
const getRandomPort = require('../random-port').getRandomPort;
import * as assert from 'assert';
import {getRandomPort} from '../random-port';

describe('Random port generation', () => {
it('generates a valid random port number', () => {
Expand Down
Loading

0 comments on commit a9f5785

Please sign in to comment.