Skip to content

Commit c3e2687

Browse files
authored
Merge pull request #12 from link-foundation/issue-11-f0db41c23d6b
feat: add support for custom Chrome args in launchBrowser
2 parents a694671 + 60d357f commit c3e2687

File tree

4 files changed

+169
-2
lines changed

4 files changed

+169
-2
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'browser-commander': minor
3+
---
4+
5+
Add support for custom Chrome args in launchBrowser
6+
7+
Adds a new `args` option to the `launchBrowser` function that allows passing custom Chrome arguments to append to the default `CHROME_ARGS`. This is useful for headless server environments (Docker, CI/CD) that require additional flags like `--no-sandbox`, `--disable-setuid-sandbox`, or `--disable-dev-shm-usage`.
8+
9+
Usage example:
10+
11+
```javascript
12+
import { launchBrowser } from 'browser-commander';
13+
14+
const { browser, page } = await launchBrowser({
15+
engine: 'puppeteer',
16+
headless: true,
17+
args: ['--no-sandbox', '--disable-setuid-sandbox'],
18+
});
19+
```
20+
21+
Fixes #11

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,21 @@ commander.pageTrigger({
191191

192192
## API Reference
193193

194+
### launchBrowser(options)
195+
196+
```javascript
197+
const { browser, page } = await launchBrowser({
198+
engine: 'playwright', // 'playwright' or 'puppeteer'
199+
headless: false, // Run in headless mode
200+
userDataDir: '~/.hh-apply/playwright-data', // Browser profile directory
201+
slowMo: 150, // Slow down operations (ms)
202+
verbose: false, // Enable debug logging
203+
args: ['--no-sandbox', '--disable-setuid-sandbox'], // Custom Chrome args to append
204+
});
205+
```
206+
207+
The `args` option allows passing custom Chrome arguments, which is useful for headless server environments (Docker, CI/CD) that require flags like `--no-sandbox`.
208+
194209
### makeBrowserCommander(options)
195210

196211
```javascript

src/browser/launcher.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { disableTranslateInPreferences } from '../core/preferences.js';
1111
* @param {boolean} options.headless - Run in headless mode (default: false)
1212
* @param {number} options.slowMo - Slow down operations by ms (default: 150 for Playwright, 0 for Puppeteer)
1313
* @param {boolean} options.verbose - Enable verbose logging (default: false)
14+
* @param {string[]} options.args - Custom Chrome arguments to append to the default CHROME_ARGS
1415
* @returns {Promise<Object>} - Object with browser and page
1516
*/
1617
export async function launchBrowser(options = {}) {
@@ -20,8 +21,12 @@ export async function launchBrowser(options = {}) {
2021
headless = false,
2122
slowMo = engine === 'playwright' ? 150 : 0,
2223
verbose = false,
24+
args = [],
2325
} = options;
2426

27+
// Combine default CHROME_ARGS with custom args
28+
const chromeArgs = [...CHROME_ARGS, ...args];
29+
2530
if (!['playwright', 'puppeteer'].includes(engine)) {
2631
throw new Error(
2732
`Invalid engine: ${engine}. Expected 'playwright' or 'puppeteer'`
@@ -50,7 +55,7 @@ export async function launchBrowser(options = {}) {
5055
slowMo,
5156
chromiumSandbox: true,
5257
viewport: null,
53-
args: CHROME_ARGS,
58+
args: chromeArgs,
5459
ignoreDefaultArgs: ['--enable-automation'],
5560
});
5661
page = browser.pages()[0];
@@ -59,7 +64,7 @@ export async function launchBrowser(options = {}) {
5964
browser = await puppeteer.default.launch({
6065
headless,
6166
defaultViewport: null,
62-
args: ['--start-maximized', ...CHROME_ARGS],
67+
args: ['--start-maximized', ...chromeArgs],
6368
userDataDir,
6469
});
6570
const pages = await browser.pages();
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { describe, it, mock, beforeEach, afterEach } from 'node:test';
2+
import assert from 'node:assert';
3+
4+
describe('launcher', () => {
5+
describe('launchBrowser args option', () => {
6+
let originalEnv;
7+
let mockChromium;
8+
let mockPuppeteer;
9+
let launchBrowser;
10+
let capturedPlaywrightArgs;
11+
let capturedPuppeteerArgs;
12+
13+
beforeEach(async () => {
14+
// Save original environment
15+
originalEnv = { ...process.env };
16+
17+
// Reset captured args
18+
capturedPlaywrightArgs = null;
19+
capturedPuppeteerArgs = null;
20+
21+
// Create mock browser context for Playwright
22+
const mockBrowserContext = {
23+
pages: () => [
24+
{
25+
bringToFront: async () => {},
26+
},
27+
],
28+
close: async () => {},
29+
};
30+
31+
// Create mock browser for Puppeteer
32+
const mockBrowser = {
33+
pages: async () => [
34+
{
35+
bringToFront: async () => {},
36+
},
37+
],
38+
close: async () => {},
39+
};
40+
41+
// Mock Playwright's chromium
42+
mockChromium = {
43+
launchPersistentContext: async (userDataDir, options) => {
44+
capturedPlaywrightArgs = options.args;
45+
return mockBrowserContext;
46+
},
47+
};
48+
49+
// Mock Puppeteer
50+
mockPuppeteer = {
51+
default: {
52+
launch: async (options) => {
53+
capturedPuppeteerArgs = options.args;
54+
return mockBrowser;
55+
},
56+
},
57+
};
58+
59+
// Use mock.module to mock the dynamic imports
60+
// Since we can't easily mock dynamic imports in Node.js test runner,
61+
// we'll test the CHROME_ARGS constant usage directly
62+
});
63+
64+
afterEach(() => {
65+
// Restore original environment
66+
process.env = originalEnv;
67+
});
68+
69+
it('should export CHROME_ARGS constant', async () => {
70+
const { CHROME_ARGS } = await import('../../../src/core/constants.js');
71+
assert.ok(Array.isArray(CHROME_ARGS));
72+
assert.ok(CHROME_ARGS.length > 0);
73+
});
74+
75+
it('should include expected default Chrome args', async () => {
76+
const { CHROME_ARGS } = await import('../../../src/core/constants.js');
77+
assert.ok(CHROME_ARGS.includes('--disable-session-crashed-bubble'));
78+
assert.ok(CHROME_ARGS.includes('--no-first-run'));
79+
assert.ok(CHROME_ARGS.includes('--no-default-browser-check'));
80+
});
81+
82+
it('launchBrowser function should accept args option', async () => {
83+
// We can verify the function signature by checking that it
84+
// destructures args from options without throwing
85+
const { launchBrowser } =
86+
await import('../../../src/browser/launcher.js');
87+
assert.ok(typeof launchBrowser === 'function');
88+
89+
// The function should accept options object with args array
90+
// We can't fully test the launch without actual browsers,
91+
// but we can verify the function exists and is callable
92+
});
93+
94+
it('launchBrowser should throw for invalid engine', async () => {
95+
const { launchBrowser } =
96+
await import('../../../src/browser/launcher.js');
97+
98+
await assert.rejects(
99+
() => launchBrowser({ engine: 'invalid-engine' }),
100+
(error) => {
101+
assert.ok(error.message.includes('Invalid engine'));
102+
assert.ok(error.message.includes('invalid-engine'));
103+
return true;
104+
}
105+
);
106+
});
107+
108+
it('launchBrowser should accept args in options', async () => {
109+
const { launchBrowser } =
110+
await import('../../../src/browser/launcher.js');
111+
112+
// Verify the function signature accepts args
113+
// This test validates that the args parameter is correctly destructured
114+
// by attempting to call with an invalid engine (which fails before browser launch)
115+
// but proves args is accepted without error during options parsing
116+
const customArgs = ['--no-sandbox', '--disable-setuid-sandbox'];
117+
118+
await assert.rejects(
119+
() => launchBrowser({ engine: 'invalid', args: customArgs }),
120+
/Invalid engine/
121+
);
122+
123+
// If we got here, the args option was accepted without error
124+
});
125+
});
126+
});

0 commit comments

Comments
 (0)