Skip to content

Commit 1a6fea4

Browse files
feat: Allow user to set browser preferences
1 parent 12b2c8e commit 1a6fea4

File tree

3 files changed

+71
-2
lines changed

3 files changed

+71
-2
lines changed

README.md

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ npm install chrome-launcher
4040
// Do note, many flags are set by default: https://github.com/GoogleChrome/chrome-launcher/blob/master/src/flags.ts
4141
chromeFlags: Array<string>;
4242

43+
// (optional) Additional preferences to be set in Chrome, for example: {'download.default_directory': __dirname}
44+
// See: https://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/pref_names.cc?view=markup
45+
// Do note, if you set preferences when using your default profile it will overwrite these
46+
prefs: {[key: string]: Object};
47+
4348
// (optional) Close the Chrome process on `Ctrl-C`
4449
// Default: true
4550
handleSIGINT: boolean;

src/chrome-launcher.ts

+35-2
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ type SupportedPlatforms = 'darwin'|'linux'|'win32'|'wsl';
2626

2727
const instances = new Set<Launcher>();
2828

29+
type JSONLike =|{[property: string]: JSONLike}|readonly JSONLike[]|string|number|boolean|null;
30+
2931
export interface Options {
3032
startingUrl?: string;
3133
chromeFlags?: Array<string>;
34+
prefs?: Record<string, JSONLike>;
3235
port?: number;
3336
handleSIGINT?: boolean;
3437
chromePath?: string;
@@ -105,6 +108,7 @@ class Launcher {
105108
private chromePath?: string;
106109
private ignoreDefaultFlags?: boolean;
107110
private chromeFlags: string[];
111+
private prefs: Record<string, JSONLike>;
108112
private requestedPort?: number;
109113
private connectionPollInterval: number;
110114
private maxConnectionRetries: number;
@@ -127,6 +131,7 @@ class Launcher {
127131
// choose the first one (default)
128132
this.startingUrl = defaults(this.opts.startingUrl, 'about:blank');
129133
this.chromeFlags = defaults(this.opts.chromeFlags, []);
134+
this.prefs = defaults(this.opts.prefs, {});
130135
this.requestedPort = defaults(this.opts.port, 0);
131136
this.chromePath = this.opts.chromePath;
132137
this.ignoreDefaultFlags = defaults(this.opts.ignoreDefaultFlags, false);
@@ -197,6 +202,8 @@ class Launcher {
197202
this.outFile = this.fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a');
198203
this.errFile = this.fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a');
199204

205+
this.setBrowserPrefs();
206+
200207
// fix for Node4
201208
// you can't pass a fd to fs.writeFileSync
202209
this.pidFile = `${this.userDataDir}/chrome.pid`;
@@ -206,6 +213,28 @@ class Launcher {
206213
this.tmpDirandPidFileReady = true;
207214
}
208215

216+
private setBrowserPrefs() {
217+
// don't set prefs if not defined
218+
if (Object.keys(this.prefs).length) {
219+
return
220+
}
221+
222+
const preferenceFile = `${this.userDataDir}/Preferences`;
223+
try {
224+
if (this.fs.existsSync(preferenceFile)) {
225+
// overwrite existing file
226+
const file = this.fs.readFileSync(preferenceFile, 'utf-8');
227+
const content = JSON.parse(file);
228+
this.fs.writeFileSync(preferenceFile, JSON.stringify({...content, ...this.prefs}), 'utf-8');
229+
} else {
230+
// create new Preference file
231+
this.fs.writeFileSync(preferenceFile, JSON.stringify({...this.prefs}), 'utf-8');
232+
}
233+
} catch (err) {
234+
log.log('ChromeLauncher', `Failed to set browser prefs: ${err.message}`);
235+
}
236+
}
237+
209238
async launch() {
210239
if (this.requestedPort !== 0) {
211240
this.port = this.requestedPort;
@@ -259,7 +288,9 @@ class Launcher {
259288
{detached: true, stdio: ['ignore', this.outFile, this.errFile], env: this.envVars});
260289
this.chrome = chrome;
261290

262-
this.fs.writeFileSync(this.pidFile, chrome.pid.toString());
291+
if (chrome.pid) {
292+
this.fs.writeFileSync(this.pidFile, chrome.pid.toString());
293+
}
263294

264295
log.verbose('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
265296
return chrome.pid;
@@ -347,7 +378,9 @@ class Launcher {
347378
// if you don't explicitly set `stdio`
348379
execSync(`taskkill /pid ${this.chrome.pid} /T /F`, {stdio: 'pipe'});
349380
} else {
350-
process.kill(-this.chrome.pid);
381+
if (this.chrome.pid) {
382+
process.kill(-this.chrome.pid);
383+
}
351384
}
352385
} catch (err) {
353386
const message = `Chrome could not be killed ${err.message}`;

test/chrome-launcher-test.ts

+31
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,37 @@ describe('Launcher', () => {
6464
assert.strictEqual(fs.rmdir.callCount, 0);
6565
});
6666

67+
it('allows to overwrite browser prefs', async () => {
68+
const existStub = stub().returns(true)
69+
const readFileStub = stub().returns(Buffer.from(JSON.stringify({ some: 'prefs' })))
70+
const writeFileStub = stub()
71+
const fs = {...fsMock, rmdir: spy(), readFileSync: readFileStub, writeFileSync: writeFileStub, existsSync: existStub };
72+
const chromeInstance =
73+
new Launcher({prefs: {'download.default_directory': '/some/dir'}}, {fs: fs as any});
74+
75+
chromeInstance.prepare();
76+
assert.equal(
77+
writeFileStub.getCall(0).args[1],
78+
'{"some":"prefs","download.default_directory":"/some/dir"}'
79+
)
80+
});
81+
82+
it('allows to set browser prefs', async () => {
83+
const existStub = stub().returns(false)
84+
const readFileStub = stub().returns(Buffer.from(JSON.stringify({ some: 'prefs' })))
85+
const writeFileStub = stub()
86+
const fs = {...fsMock, rmdir: spy(), readFileSync: readFileStub, writeFileSync: writeFileStub, existsSync: existStub };
87+
const chromeInstance =
88+
new Launcher({prefs: {'download.default_directory': '/some/dir'}}, {fs: fs as any});
89+
90+
chromeInstance.prepare();
91+
assert.equal(readFileStub.getCalls().length, 0)
92+
assert.equal(
93+
writeFileStub.getCall(0).args[1],
94+
'{"download.default_directory":"/some/dir"}'
95+
)
96+
});
97+
6798
it('cleans up the tmp dir after closing', async () => {
6899
const rmdirMock = stub().callsFake((_path, _options, done) => done());
69100
const fs = {...fsMock, rmdir: rmdirMock};

0 commit comments

Comments
 (0)