diff --git a/.changeset/fix-devtools-launch-option.md b/.changeset/fix-devtools-launch-option.md new file mode 100644 index 0000000..09d8bf4 --- /dev/null +++ b/.changeset/fix-devtools-launch-option.md @@ -0,0 +1,5 @@ +--- +"@heymp/scratchpad": patch +--- + +Fix devtools launch option for newer versions of Playwright by using --auto-open-devtools-for-tabs arg instead of the removed devtools property on LaunchOptions. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6bcf66e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + pull_request: + branches: + - main + +jobs: + build: + name: Build & Test + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + + - name: Setup Node.js 20.x + uses: actions/setup-node@v4 + with: + node-version: 20.x + + - name: Install Dependencies + run: yarn setup + + - name: Build + run: yarn build + + - name: Test + run: yarn test diff --git a/docs/proposal.md b/docs/proposal.md new file mode 100644 index 0000000..ce335b0 --- /dev/null +++ b/docs/proposal.md @@ -0,0 +1,64 @@ +Proposal: Support custom Playwright chromium.launch() options + +Problem + +Currently, @heymp/scratchpad hardcodes the chromium.launch() call to only accept headless and devtools: + +const browser = await playwright['chromium'].launch({ + headless: !!processor.opts.headless, + devtools: !!processor.opts.devtools +}); + +There's no way to pass additional launch arguments (e.g. --remote-debugging-port, --remote-allow-origins=*) which are needed for use cases like connecting external tools via CDP. + +Use Case + +I want to open a CDP port so an external agent (e.g. an AI browser automation tool) can connect to the scratchpad-launched browser: + +export default Scratchpad.defineConfig({ + url: 'https://www.redhat.com/en/dashboard', + headless: false, + launchOptions: { + args: [ + '--remote-debugging-port=9222', + '--remote-allow-origins=*' + ] + } +}); + +Suggested Change + +Add a launchOptions field to the Config type that passes through to Playwright's chromium.launch(). The existing headless and devtools fields would continue to work as top-level shortcuts but launchOptions +would allow full control. + +Config type change: + +import type { LaunchOptions } from 'playwright'; +export type Config = { + headless?: boolean; + devtools?: boolean; + tsWrite?: boolean; + url?: string; + login?: boolean; + rerouteDir?: string; + bypassCSP?: boolean; + launchOptions?: LaunchOptions; + playwright?: (page: PlaywrightConfig) => Promise; +}; + +browser.ts change: + +const browser = await playwright['chromium'].launch({ + headless: !!processor.opts.headless, + devtools: !!processor.opts.devtools, + ...processor.opts.launchOptions, +}); + +Top-level headless/devtools would take precedence unless you'd prefer the spread to win (just swap the order). Using Playwright's own LaunchOptions type keeps the API future-proof without scratchpad needing to +track new Playwright features. + +Scope + +• Add launchOptions?: LaunchOptions to the Config type in src/config.ts +• Spread it into the chromium.launch() call in src/browser.ts +• No breaking changes to existing configs diff --git a/package.json b/package.json index c86bf36..6c40319 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "preinstall": "echo 'Do not run yarn install directly. Use: yarn setup' && exit 1", "build": "tsc", "build:watch": "tsc -w", - "test": "yarn build && node --test src/**/*.test.js", + "test": "yarn build && node --test src/*.test.js", "release": "yarn changeset publish" }, "devDependencies": { diff --git a/src/browser.test.ts b/src/browser.test.ts index fbd112f..55d8d23 100644 --- a/src/browser.test.ts +++ b/src/browser.test.ts @@ -7,7 +7,6 @@ describe('buildLaunchOptions', () => { const result = buildLaunchOptions({}); assert.deepStrictEqual(result.args, []); assert.strictEqual(result.headless, undefined); - assert.strictEqual(result.devtools, undefined); }); test('respects launchOptions.headless when top-level headless is not set', () => { @@ -30,24 +29,15 @@ describe('buildLaunchOptions', () => { assert.strictEqual(result.headless, false); }); - test('respects launchOptions.devtools when top-level devtools is not set', () => { - const result = buildLaunchOptions({ - launchOptions: { devtools: true }, - }); - assert.strictEqual(result.devtools, true); - }); - - test('respects top-level devtools when launchOptions.devtools is not set', () => { - const result = buildLaunchOptions({ devtools: true }); - assert.strictEqual(result.devtools, true); + test('devtools adds --auto-open-devtools-for-tabs and forces headless false', () => { + const result = buildLaunchOptions({ devtools: true, headless: true }); + assert.ok(result.args!.includes('--auto-open-devtools-for-tabs')); + assert.strictEqual(result.headless, false); }); - test('top-level devtools wins over launchOptions.devtools', () => { - const result = buildLaunchOptions({ - devtools: false, - launchOptions: { devtools: true }, - }); - assert.strictEqual(result.devtools, false); + test('devtools false does not add --auto-open-devtools-for-tabs', () => { + const result = buildLaunchOptions({ devtools: false }); + assert.ok(!result.args!.includes('--auto-open-devtools-for-tabs')); }); test('bypassCSP adds --disable-web-security to args', () => { diff --git a/src/browser.ts b/src/browser.ts index 43a0ed4..f5e29c3 100644 --- a/src/browser.ts +++ b/src/browser.ts @@ -28,11 +28,16 @@ function readFile(...args: Parameters) { export function buildLaunchOptions(opts: ProcessorOpts): LaunchOptions { const bypassCSPArgs = opts.bypassCSP ? ['--disable-web-security'] : []; + const devtoolsArgs = opts.devtools ? ['--auto-open-devtools-for-tabs'] : []; + const headless = opts.devtools + ? false + : opts.headless !== undefined + ? !!opts.headless + : opts.launchOptions?.headless; return { ...opts.launchOptions, - ...(opts.headless !== undefined && { headless: !!opts.headless }), - ...(opts.devtools !== undefined && { devtools: !!opts.devtools }), - args: [...bypassCSPArgs, ...(opts.launchOptions?.args ?? [])], + ...(headless !== undefined && { headless }), + args: [...bypassCSPArgs, ...devtoolsArgs, ...(opts.launchOptions?.args ?? [])], }; } diff --git a/src/login.ts b/src/login.ts index 8985bc1..98c23e5 100644 --- a/src/login.ts +++ b/src/login.ts @@ -10,7 +10,7 @@ util.inspect.defaultOptions.depth = null; export async function login(config: Config) { const browser = await playwright['chromium'].launch({ headless: false, - devtools: !!config.devtools + args: config.devtools ? ['--auto-open-devtools-for-tabs'] : [], }); const context = await browser.newContext(); const page = await context.newPage();