Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Allow user to set browser preferences #247

Merged
merged 2 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ npm install chrome-launcher
// Do note, many flags are set by default: https://github.com/GoogleChrome/chrome-launcher/blob/master/src/flags.ts
chromeFlags: Array<string>;

// (optional) Additional preferences to be set in Chrome, for example: {'download.default_directory': __dirname}
// See: https://chromium.googlesource.com/chromium/src/+/main/chrome/common/pref_names.cc
// Do note, if you set preferences when using your default profile it will overwrite these
prefs: {[key: string]: Object};

// (optional) Close the Chrome process on `Ctrl-C`
// Default: true
handleSIGINT: boolean;
Expand Down
37 changes: 35 additions & 2 deletions src/chrome-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ type SupportedPlatforms = 'darwin'|'linux'|'win32'|'wsl';

const instances = new Set<Launcher>();

type JSONLike =|{[property: string]: JSONLike}|readonly JSONLike[]|string|number|boolean|null;

export interface Options {
startingUrl?: string;
chromeFlags?: Array<string>;
prefs?: Record<string, JSONLike>;
port?: number;
handleSIGINT?: boolean;
chromePath?: string;
Expand Down Expand Up @@ -105,6 +108,7 @@ class Launcher {
private chromePath?: string;
private ignoreDefaultFlags?: boolean;
private chromeFlags: string[];
private prefs: Record<string, JSONLike>;
private requestedPort?: number;
private connectionPollInterval: number;
private maxConnectionRetries: number;
Expand All @@ -127,6 +131,7 @@ class Launcher {
// choose the first one (default)
this.startingUrl = defaults(this.opts.startingUrl, 'about:blank');
this.chromeFlags = defaults(this.opts.chromeFlags, []);
this.prefs = defaults(this.opts.prefs, {});
this.requestedPort = defaults(this.opts.port, 0);
this.chromePath = this.opts.chromePath;
this.ignoreDefaultFlags = defaults(this.opts.ignoreDefaultFlags, false);
Expand Down Expand Up @@ -197,6 +202,8 @@ class Launcher {
this.outFile = this.fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a');
this.errFile = this.fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a');

this.setBrowserPrefs();

// fix for Node4
// you can't pass a fd to fs.writeFileSync
this.pidFile = `${this.userDataDir}/chrome.pid`;
Expand All @@ -206,6 +213,28 @@ class Launcher {
this.tmpDirandPidFileReady = true;
}

private setBrowserPrefs() {
// don't set prefs if not defined
if (Object.keys(this.prefs).length === 0) {
return;
}

const preferenceFile = `${this.userDataDir}/Preferences`;
try {
if (this.fs.existsSync(preferenceFile)) {
// overwrite existing file
const file = this.fs.readFileSync(preferenceFile, 'utf-8');
const content = JSON.parse(file);
this.fs.writeFileSync(preferenceFile, JSON.stringify({...content, ...this.prefs}), 'utf-8');
} else {
// create new Preference file
this.fs.writeFileSync(preferenceFile, JSON.stringify({...this.prefs}), 'utf-8');
}
} catch (err) {
log.log('ChromeLauncher', `Failed to set browser prefs: ${err.message}`);
}
}

async launch() {
if (this.requestedPort !== 0) {
this.port = this.requestedPort;
Expand Down Expand Up @@ -259,7 +288,9 @@ class Launcher {
{detached: true, stdio: ['ignore', this.outFile, this.errFile], env: this.envVars});
this.chrome = chrome;

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

log.verbose('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
return chrome.pid;
Expand Down Expand Up @@ -347,7 +378,9 @@ class Launcher {
// if you don't explicitly set `stdio`
execSync(`taskkill /pid ${this.chrome.pid} /T /F`, {stdio: 'pipe'});
} else {
process.kill(-this.chrome.pid);
if (this.chrome.pid) {
process.kill(-this.chrome.pid);
}
}
} catch (err) {
const message = `Chrome could not be killed ${err.message}`;
Expand Down
31 changes: 31 additions & 0 deletions test/chrome-launcher-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,37 @@ describe('Launcher', () => {
assert.strictEqual(fs.rmdir.callCount, 0);
});

it('allows to overwrite browser prefs', async () => {
const existStub = stub().returns(true)
const readFileStub = stub().returns(JSON.stringify({ some: 'prefs' }))
const writeFileStub = stub()
const fs = {...fsMock, rmdir: spy(), readFileSync: readFileStub, writeFileSync: writeFileStub, existsSync: existStub };
const chromeInstance =
new Launcher({prefs: {'download.default_directory': '/some/dir'}}, {fs: fs as any});

chromeInstance.prepare();
assert.equal(
writeFileStub.getCall(0).args[1],
'{"some":"prefs","download.default_directory":"/some/dir"}'
)
});

it('allows to set browser prefs', async () => {
const existStub = stub().returns(false)
const readFileStub = stub().returns(Buffer.from(JSON.stringify({ some: 'prefs' })))
const writeFileStub = stub()
const fs = {...fsMock, rmdir: spy(), readFileSync: readFileStub, writeFileSync: writeFileStub, existsSync: existStub };
const chromeInstance =
new Launcher({prefs: {'download.default_directory': '/some/dir'}}, {fs: fs as any});

chromeInstance.prepare();
assert.equal(readFileStub.getCalls().length, 0)
assert.equal(
writeFileStub.getCall(0).args[1],
'{"download.default_directory":"/some/dir"}'
)
});

it('cleans up the tmp dir after closing', async () => {
const rmdirMock = stub().callsFake((_path, _options, done) => done());
const fs = {...fsMock, rmdir: rmdirMock};
Expand Down