Skip to content

Commit 68af6a1

Browse files
DavertMikclaude
andcommitted
refactor(browser plugin): optional configure import + cleaner arg parser
* `@codeceptjs/configure` is now imported dynamically. If it's not installed (e.g. user pinned a stripped-down dep set), the plugin prints a one-line hint and skips the override instead of crashing. * Arg parsing rewritten as small composable functions: `parseArgs` → `parseArg` → `parseValue`. Uses `String.split('=')` instead of manual `indexOf`/`slice`. The hot loop is now a `reduce`. * Drop number coercion — values stay as strings; helpers (and setBrowserConfig's regex parsers) coerce as needed. `true`/`false` still become real booleans for boolean-typed helper options. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f0440fc commit 68af6a1

2 files changed

Lines changed: 72 additions & 61 deletions

File tree

lib/plugin/browser.js

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { setBrowserConfig } from '@codeceptjs/configure'
21
import output from '../output.js'
32

43
/**
@@ -26,42 +25,52 @@ import output from '../output.js'
2625
* * `show=true|false` — toggles `show` on Playwright/Puppeteer; injects/strips
2726
* `--headless` in WebDriver chrome/firefox capability args
2827
*
29-
* Values are coerced: `true`/`false` → boolean, numbers → Number, otherwise string.
28+
* Values stay as strings. `true` / `false` are coerced to booleans.
29+
*
30+
* Requires `@codeceptjs/configure` to be installed; if missing, the plugin
31+
* logs a hint and skips the override.
3032
*/
31-
export default function (config = {}) {
32-
const args = config._args || []
33-
if (!args.length) return
33+
export default async function (config = {}) {
34+
const opts = parseArgs(config._args || [])
35+
if (Object.keys(opts).length === 0) return
3436

35-
const opts = {}
36-
for (const arg of args) {
37-
if (!arg) continue
38-
if (arg === 'show') {
39-
opts.show = true
40-
continue
41-
}
42-
if (arg === 'hide') {
43-
opts.show = false
44-
continue
45-
}
46-
const eq = arg.indexOf('=')
47-
if (eq < 0) {
48-
output.error(`browser plugin: unknown arg "${arg}"`)
49-
continue
50-
}
51-
opts[arg.slice(0, eq)] = coerce(arg.slice(eq + 1))
52-
}
37+
const configure = await tryImportConfigure()
38+
if (!configure) return
5339

54-
if (Object.keys(opts).length === 0) return
40+
configure.setBrowserConfig(opts)
41+
output.debug(`browser plugin: applied ${formatOpts(opts)}`)
42+
}
43+
44+
async function tryImportConfigure() {
45+
try {
46+
return await import('@codeceptjs/configure')
47+
} catch (err) {
48+
output.error("browser plugin: '@codeceptjs/configure' is not installed; CLI overrides are skipped. Run `npm i @codeceptjs/configure` to enable.")
49+
return null
50+
}
51+
}
5552

56-
setBrowserConfig(opts)
53+
function parseArgs(args) {
54+
return args.filter(Boolean).reduce((acc, arg) => Object.assign(acc, parseArg(arg)), {})
55+
}
5756

58-
const summary = Object.entries(opts).map(([k, v]) => `${k}=${v}`).join(', ')
59-
output.debug(`browser plugin: applied ${summary}`)
57+
function parseArg(arg) {
58+
if (arg === 'show') return { show: true }
59+
if (arg === 'hide') return { show: false }
60+
if (arg.includes('=')) {
61+
const [key, ...rest] = arg.split('=')
62+
return { [key]: parseValue(rest.join('=')) }
63+
}
64+
output.error(`browser plugin: unknown arg "${arg}"`)
65+
return {}
6066
}
6167

62-
function coerce(v) {
68+
function parseValue(v) {
6369
if (v === 'true') return true
6470
if (v === 'false') return false
65-
if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v)
6671
return v
6772
}
73+
74+
function formatOpts(opts) {
75+
return Object.entries(opts).map(([k, v]) => `${k}=${v}`).join(', ')
76+
}

test/unit/plugin/browser_test.js

Lines changed: 34 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,47 @@ import { expect } from 'chai'
22
import browser from '../../../lib/plugin/browser.js'
33
import Config from '../../../lib/config.js'
44

5-
function applyAndCreate(args, base = {}) {
5+
async function applyAndCreate(args, base = {}) {
66
Config.reset()
7-
browser({ _args: args })
7+
await browser({ _args: args })
88
return Config.create(base)
99
}
1010

1111
describe('browser plugin', () => {
1212
beforeEach(() => Config.reset())
1313

14-
it('does nothing when no args passed', () => {
15-
const cfg = applyAndCreate([], { helpers: { Playwright: { show: true } } })
14+
it('does nothing when no args passed', async () => {
15+
const cfg = await applyAndCreate([], { helpers: { Playwright: { show: true } } })
1616
expect(cfg.helpers.Playwright.show).to.equal(true)
1717
})
1818

1919
describe('show / hide flags', () => {
20-
it('show forces headed for Playwright + Puppeteer', () => {
21-
const cfg = applyAndCreate(['show'], {
20+
it('show forces headed for Playwright + Puppeteer', async () => {
21+
const cfg = await applyAndCreate(['show'], {
2222
helpers: { Playwright: { show: false }, Puppeteer: { show: false } },
2323
})
2424
expect(cfg.helpers.Playwright.show).to.equal(true)
2525
expect(cfg.helpers.Puppeteer.show).to.equal(true)
2626
})
2727

28-
it('hide forces headless for Playwright + Puppeteer', () => {
29-
const cfg = applyAndCreate(['hide'], {
28+
it('hide forces headless for Playwright + Puppeteer', async () => {
29+
const cfg = await applyAndCreate(['hide'], {
3030
helpers: { Playwright: { show: true }, Puppeteer: { show: true } },
3131
})
3232
expect(cfg.helpers.Playwright.show).to.equal(false)
3333
expect(cfg.helpers.Puppeteer.show).to.equal(false)
3434
})
3535

36-
it('hide injects --headless into WebDriver chrome capability args', () => {
37-
const cfg = applyAndCreate(['hide'], {
36+
it('hide injects --headless into WebDriver chrome capability args', async () => {
37+
const cfg = await applyAndCreate(['hide'], {
3838
helpers: { WebDriver: { browser: 'chrome' } },
3939
})
4040
const args = cfg.helpers.WebDriver.desiredCapabilities.chromeOptions.args
4141
expect(args).to.include('--headless')
4242
})
4343

44-
it('show strips --headless from WebDriver chrome capability args', () => {
45-
const cfg = applyAndCreate(['show'], {
44+
it('show strips --headless from WebDriver chrome capability args', async () => {
45+
const cfg = await applyAndCreate(['show'], {
4646
helpers: {
4747
WebDriver: { browser: 'chrome', desiredCapabilities: { chromeOptions: { args: ['--headless', '--disable-gpu'] } } },
4848
},
@@ -54,8 +54,8 @@ describe('browser plugin', () => {
5454
})
5555

5656
describe('windowSize', () => {
57-
it('windowSize=WxH sets windowSize across browser helpers and chrome args', () => {
58-
const cfg = applyAndCreate(['windowSize=800x600'], {
57+
it('windowSize=WxH sets windowSize across browser helpers and chrome args', async () => {
58+
const cfg = await applyAndCreate(['windowSize=800x600'], {
5959
helpers: { Playwright: {}, Puppeteer: {}, WebDriver: {} },
6060
})
6161
expect(cfg.helpers.Playwright.windowSize).to.equal('800x600')
@@ -66,8 +66,8 @@ describe('browser plugin', () => {
6666
})
6767

6868
describe('generic key=value passthrough', () => {
69-
it('coerces booleans and applies to every browser helper present', () => {
70-
const cfg = applyAndCreate(['video=false'], {
69+
it('coerces booleans and applies to every browser helper present', async () => {
70+
const cfg = await applyAndCreate(['video=false'], {
7171
helpers: { Playwright: {}, Puppeteer: {}, WebDriver: {}, Appium: {} },
7272
})
7373
expect(cfg.helpers.Playwright.video).to.equal(false)
@@ -76,41 +76,41 @@ describe('browser plugin', () => {
7676
expect(cfg.helpers.Appium.video).to.equal(false)
7777
})
7878

79-
it('coerces numbers', () => {
80-
const cfg = applyAndCreate(['waitForTimeout=5000'], {
79+
it('keeps numbers as strings (helpers coerce as needed)', async () => {
80+
const cfg = await applyAndCreate(['waitForTimeout=5000'], {
8181
helpers: { Playwright: {} },
8282
})
83-
expect(cfg.helpers.Playwright.waitForTimeout).to.equal(5000)
83+
expect(cfg.helpers.Playwright.waitForTimeout).to.equal('5000')
8484
})
8585

86-
it('keeps strings as strings', () => {
87-
const cfg = applyAndCreate(['url=http://staging.test'], {
86+
it('keeps strings as strings', async () => {
87+
const cfg = await applyAndCreate(['url=http://staging.test'], {
8888
helpers: { Playwright: {} },
8989
})
9090
expect(cfg.helpers.Playwright.url).to.equal('http://staging.test')
9191
})
9292

93-
it('skips helpers not present in config without errors', () => {
94-
const cfg = applyAndCreate(['video=true'], {
95-
helpers: { Playwright: {} }, // Puppeteer/WebDriver absent
93+
it('skips helpers not present in config without errors', async () => {
94+
const cfg = await applyAndCreate(['video=true'], {
95+
helpers: { Playwright: {} },
9696
})
9797
expect(cfg.helpers.Playwright.video).to.equal(true)
9898
expect(cfg.helpers.Puppeteer).to.equal(undefined)
9999
})
100100
})
101101

102102
describe('browser engine selection', () => {
103-
it('browser=firefox routes through setBrowser, Puppeteer gets product', () => {
104-
const cfg = applyAndCreate(['browser=firefox'], {
103+
it('browser=firefox routes through setBrowser, Puppeteer gets product', async () => {
104+
const cfg = await applyAndCreate(['browser=firefox'], {
105105
helpers: { Puppeteer: {}, Playwright: {} },
106106
})
107107
expect(cfg.helpers.Puppeteer.product).to.equal('firefox')
108108
expect(cfg.helpers.Puppeteer.browser).to.equal(undefined)
109109
expect(cfg.helpers.Playwright.browser).to.equal('firefox')
110110
})
111111

112-
it('browser=webkit + show=false combine cleanly', () => {
113-
const cfg = applyAndCreate(['hide', 'browser=webkit'], {
112+
it('browser=webkit + show=false combine cleanly', async () => {
113+
const cfg = await applyAndCreate(['hide', 'browser=webkit'], {
114114
helpers: { Playwright: { show: true } },
115115
})
116116
expect(cfg.helpers.Playwright.browser).to.equal('webkit')
@@ -119,8 +119,8 @@ describe('browser plugin', () => {
119119
})
120120

121121
describe('combined args', () => {
122-
it('applies show + windowSize + key=value in a single call', () => {
123-
const cfg = applyAndCreate(['show', 'windowSize=1024x768', 'video=false'], {
122+
it('applies show + windowSize + key=value in a single call', async () => {
123+
const cfg = await applyAndCreate(['show', 'windowSize=1024x768', 'video=false'], {
124124
helpers: { Playwright: { show: false }, Puppeteer: { show: false }, WebDriver: { browser: 'chrome' } },
125125
})
126126
expect(cfg.helpers.Playwright.show).to.equal(true)
@@ -133,8 +133,10 @@ describe('browser plugin', () => {
133133
})
134134

135135
describe('unknown arg', () => {
136-
it('does not throw when an arg has no value and is not a flag', () => {
137-
expect(() => applyAndCreate(['weirdtoken'], { helpers: { Playwright: {} } })).not.to.throw()
136+
it('does not throw when an arg has no value and is not a flag', async () => {
137+
let threw = false
138+
try { await applyAndCreate(['weirdtoken'], { helpers: { Playwright: {} } }) } catch { threw = true }
139+
expect(threw).to.equal(false)
138140
})
139141
})
140142
})

0 commit comments

Comments
 (0)