From f96754df3382ca10d4f3163d78f74a91bfadcb49 Mon Sep 17 00:00:00 2001 From: Vishnu Vardhan Date: Tue, 13 Oct 2026 12:18:33 +0530 Subject: [PATCH 1/2] fix(selenium-devtools): apply port, hostname and capture options in configure() DevTools.configure() accepted only eleven keys, so port, hostname, captureScreenshots, filmstrip and emitArtifactsManifest were declared on DevToolsOptions and documented, yet silently ignored. The plugin is built with no options, so configure() is the only way to set anything and the defaults always applied. configure() now takes DevToolsOptions and copies every option through a Record keyed on that type, so an option added later fails to compile until it is handled. A rerun keeps its parent's backend whatever port the test file asks for, since a rerun loads the file again; port and hostname passed after the backend start has begun are ignored with a warning, and null values or an invalid port are refused. Co-Authored-By: Claude Opus 5.5 --- .changeset/selenium-configure-options.md | 5 + packages/selenium-devtools/README.md | 2 + .../src/helpers/configure-options.ts | 86 +++++++++++++ packages/selenium-devtools/src/index.ts | 76 ++--------- packages/selenium-devtools/src/types.ts | 3 + .../tests/configure-options.test.ts | 118 ++++++++++++++++++ 6 files changed, 227 insertions(+), 63 deletions(-) create mode 100644 .changeset/selenium-configure-options.md create mode 100644 packages/selenium-devtools/src/helpers/configure-options.ts create mode 100644 packages/selenium-devtools/tests/configure-options.test.ts diff --git a/.changeset/selenium-configure-options.md b/.changeset/selenium-configure-options.md new file mode 100644 index 00000000..fd103061 --- /dev/null +++ b/.changeset/selenium-configure-options.md @@ -0,0 +1,5 @@ +--- +'@wdio/selenium-devtools': patch +--- + +`DevTools.configure()` now applies `port`, `hostname`, `captureScreenshots`, `filmstrip` and `emitArtifactsManifest`. They were documented as configure options but silently ignored, so the defaults always applied. `port`/`hostname` passed after the backend start has begun are ignored with a warning, and an invalid port or a `null` value is refused rather than applied. A rerun keeps its parent's backend whatever they say. diff --git a/packages/selenium-devtools/README.md b/packages/selenium-devtools/README.md index 30829026..801b5239 100644 --- a/packages/selenium-devtools/README.md +++ b/packages/selenium-devtools/README.md @@ -259,6 +259,8 @@ DevTools.configure({ Here's what every option does, in plain language. **All are optional** — the plugin runs fine with `DevTools.configure({})` or no configure call at all. +> `port` and `hostname` only take effect before the backend starts, which happens when the first driver is built, so call `DevTools.configure(...)` before that; a later call ignores them with a warning. A rerun keeps its parent's backend whatever they say. + > **For CI**, set both `headless: true` (hide the test browser) and `openUi: false` (don't try to open the dashboard window). The backend stays running on the configured port so you can still open the UI later. #### `screencast` — record a video of the browser diff --git a/packages/selenium-devtools/src/helpers/configure-options.ts b/packages/selenium-devtools/src/helpers/configure-options.ts new file mode 100644 index 00000000..d7a70f08 --- /dev/null +++ b/packages/selenium-devtools/src/helpers/configure-options.ts @@ -0,0 +1,86 @@ +import type { DevToolsOptions, ServerLock } from '../types.js' + +// A Record over every option, so a key added to DevToolsOptions fails to compile here instead of being silently ignored. +const OPTION_KIND = { + openUi: 'runtime', + headless: 'runtime', + captureScreenshots: 'runtime', + captureAssertions: 'runtime', + filmstrip: 'runtime', + emitArtifactsManifest: 'runtime', + mode: 'runtime', + traceFormat: 'runtime', + traceGranularity: 'runtime', + tracePolicy: 'runtime', + screenshot: 'runtime', + video: 'runtime', + port: 'server', + hostname: 'server' +} as const satisfies Record< + Exclude, + 'runtime' | 'server' +> + +type ConfigurableKey = keyof typeof OPTION_KIND + +export type ConfigurableOptions = Pick + +export function serverLock(state: { + isReuse: boolean + backendStarting: boolean +}): ServerLock | undefined { + if (state.isReuse) { + return 'reuse' + } + return state.backendStarting ? 'started' : undefined +} + +const CONFIGURABLE_KEYS = Object.keys(OPTION_KIND) as ConfigurableKey[] + +function isSet(value: T | null | undefined): value is T { + return value !== undefined && value !== null +} + +function copyDefined( + target: ConfigurableOptions, + opts: DevToolsOptions, + key: K +): void { + const value = opts[key] + if (isSet(value)) { + target[key] = value + } +} + +function isValidPort(port: number): boolean { + return Number.isInteger(port) && port > 0 && port < 65536 +} + +/** Returns a warning when a requested option was refused; a reused backend's port/hostname are skipped silently, as that is the rerun working. */ +export function applyConfigureOptions( + target: ConfigurableOptions, + opts: DevToolsOptions, + lock?: ServerLock +): string | undefined { + const requestedServer: ConfigurableKey[] = [] + for (const key of CONFIGURABLE_KEYS) { + if (OPTION_KIND[key] === 'runtime') { + copyDefined(target, opts, key) + } else if (isSet(opts[key])) { + requestedServer.push(key) + } + } + if (requestedServer.length === 0 || lock === 'reuse') { + return undefined + } + if (lock === 'started') { + return `configure: ignoring ${requestedServer.join('/')}: the backend start has already begun` + } + if (isSet(opts.port) && !isValidPort(opts.port)) { + return `configure: ignoring invalid port ${opts.port}` + } + for (const key of requestedServer) { + copyDefined(target, opts, key) + } + return undefined +} diff --git a/packages/selenium-devtools/src/index.ts b/packages/selenium-devtools/src/index.ts index 69b49c0b..c260814e 100644 --- a/packages/selenium-devtools/src/index.ts +++ b/packages/selenium-devtools/src/index.ts @@ -43,6 +43,10 @@ import { detectSeleniumVersion } from './helpers/runtime.js' import { findFreePort } from './helpers/utils.js' +import { + applyConfigureOptions, + serverLock +} from './helpers/configure-options.js' import { RetryTracker, errorMessage, @@ -60,15 +64,9 @@ import { import { type ActionSnapshot, type CapturedCommand, - type DevToolsMode, type DevToolsOptions, type ScreencastFrame, type ScreencastOptions, - type TraceFormat, - type TraceGranularity, - type TraceRetentionPolicy, - type TraceScreenshotPolicy, - type TraceVideoPolicy, type SeleniumDriverLike, type TestStats } from './types.js' @@ -299,54 +297,18 @@ class SeleniumDevToolsPlugin { return this.#uiReadyPromise } - // Declarative option-application mapper — one guarded assignment per option; - // splitting it purely to satisfy the line rule hurts readability. - // eslint-disable-next-line max-lines-per-function - configure( - opts: { - rerunCommand?: string - screencast?: ScreencastOptions - headless?: boolean - openUi?: boolean - captureAssertions?: boolean - mode?: DevToolsMode - traceFormat?: TraceFormat - traceGranularity?: TraceGranularity - tracePolicy?: TraceRetentionPolicy - screenshot?: TraceScreenshotPolicy - video?: TraceVideoPolicy - } = {} - ) { + configure(opts: DevToolsOptions = {}) { if ('rerunCommand' in opts) { this.#rerunManager.configure(opts.rerunCommand) this.#options.rerunCommand = opts.rerunCommand } - if (typeof opts.headless === 'boolean') { - this.#options.headless = opts.headless - } - if (typeof opts.captureAssertions === 'boolean') { - this.#options.captureAssertions = opts.captureAssertions - } - if (typeof opts.openUi === 'boolean') { - this.#options.openUi = opts.openUi - } - if (opts.mode) { - this.#options.mode = opts.mode - } - if (opts.traceFormat) { - this.#options.traceFormat = opts.traceFormat - } - if (opts.traceGranularity) { - this.#options.traceGranularity = opts.traceGranularity - } - if (opts.tracePolicy) { - this.#options.tracePolicy = opts.tracePolicy - } - if (opts.screenshot) { - this.#options.screenshot = opts.screenshot - } - if (opts.video) { - this.#options.video = opts.video + const lock = serverLock({ + isReuse: this.#isReuse, + backendStarting: this.#backendStartPromise !== undefined + }) + const warning = applyConfigureOptions(this.#options, opts, lock) + if (warning) { + log.warn(warning) } if (opts.screencast) { if (this.#options.mode === 'trace' && opts.screencast.enabled) { @@ -734,19 +696,7 @@ if (!registerHooks()) { registerProcessHooks(plugin) export const DevTools = { - configure: (opts: { - rerunCommand?: string - screencast?: ScreencastOptions - headless?: boolean - openUi?: boolean - captureAssertions?: boolean - mode?: DevToolsMode - traceFormat?: TraceFormat - traceGranularity?: TraceGranularity - tracePolicy?: TraceRetentionPolicy - screenshot?: TraceScreenshotPolicy - video?: TraceVideoPolicy - }) => plugin.configure(opts), + configure: (opts: DevToolsOptions) => plugin.configure(opts), startTest: (name: string, meta?: { file?: string }) => plugin.startTest(name, meta), endTest: (state: TestStats['state'] = 'passed') => plugin.endTest(state) diff --git a/packages/selenium-devtools/src/types.ts b/packages/selenium-devtools/src/types.ts index 8804a772..09bb33bf 100644 --- a/packages/selenium-devtools/src/types.ts +++ b/packages/selenium-devtools/src/types.ts @@ -22,6 +22,9 @@ export { type TraceVideoPolicy } from '@wdio/devtools-shared' +/** Why `configure()` can no longer change port/hostname: a rerun reusing its parent's backend, or a backend start already under way. */ +export type ServerLock = 'reuse' | 'started' + export interface DevToolsOptions extends BaseDevToolsOptions { /** Open a Chrome window pointing at the UI. Default true. */ openUi?: boolean diff --git a/packages/selenium-devtools/tests/configure-options.test.ts b/packages/selenium-devtools/tests/configure-options.test.ts new file mode 100644 index 00000000..5a818dfd --- /dev/null +++ b/packages/selenium-devtools/tests/configure-options.test.ts @@ -0,0 +1,118 @@ +import { describe, it, expect } from 'vitest' +import { + applyConfigureOptions, + serverLock, + type ConfigurableOptions +} from '../src/helpers/configure-options.js' +import type { DevToolsOptions } from '../src/types.js' + +function defaults(): ConfigurableOptions { + return { + port: 3000, + hostname: 'localhost', + openUi: true, + captureScreenshots: true, + filmstrip: true, + mode: 'live' + } +} + +describe('applyConfigureOptions', () => { + it('applies the options configure() used to drop', () => { + const target = defaults() + const warning = applyConfigureOptions(target, { + port: 4123, + hostname: '127.0.0.1', + captureScreenshots: false, + filmstrip: false, + emitArtifactsManifest: true + }) + expect(warning).toBeUndefined() + expect(target).toMatchObject({ + port: 4123, + hostname: '127.0.0.1', + captureScreenshots: false, + filmstrip: false, + emitArtifactsManifest: true + }) + }) + + it('applies false booleans and leaves unspecified options untouched', () => { + const target = defaults() + applyConfigureOptions(target, { openUi: false, mode: 'trace' }) + expect(target).toEqual({ ...defaults(), openUi: false, mode: 'trace' }) + }) + + it('treats an explicit undefined or null as "not specified"', () => { + const target = defaults() + // A JS caller can hand configure() null even though the type forbids it. + const loose = { + emitArtifactsManifest: undefined, + traceFormat: null, + port: null + } as unknown as DevToolsOptions + expect(applyConfigureOptions(target, loose)).toBeUndefined() + expect(target).toEqual(defaults()) + expect('emitArtifactsManifest' in target).toBe(false) + }) + + it('refuses an invalid port and keeps the current one', () => { + const target = defaults() + const warning = applyConfigureOptions(target, { + port: Number('not-a-port'), + hostname: '0.0.0.0' + }) + expect(warning).toBe('configure: ignoring invalid port NaN') + expect(target.port).toBe(3000) + expect(target.hostname).toBe('localhost') + }) + + it('refuses port/hostname once the backend start has begun, and says why', () => { + const target = defaults() + const warning = applyConfigureOptions( + target, + { port: 4123, hostname: 'example.test', filmstrip: false }, + 'started' + ) + expect(target.port).toBe(3000) + expect(target.hostname).toBe('localhost') + expect(target.filmstrip).toBe(false) + expect(warning).toBe( + 'configure: ignoring port/hostname: the backend start has already begun' + ) + }) + + it("keeps a reused parent backend's port/hostname without warning", () => { + const target = defaults() + const warning = applyConfigureOptions( + target, + { port: 4123, openUi: false }, + 'reuse' + ) + expect(warning).toBeUndefined() + expect(target.port).toBe(3000) + expect(target.openUi).toBe(false) + }) + + it('does not warn under a lock when port/hostname were not asked for', () => { + expect( + applyConfigureOptions(defaults(), { openUi: false }, 'started') + ).toBeUndefined() + }) +}) + +describe('serverLock', () => { + it('prefers reuse over a started backend, since a rerun child never binds its own', () => { + expect(serverLock({ isReuse: true, backendStarting: true })).toBe('reuse') + expect(serverLock({ isReuse: true, backendStarting: false })).toBe('reuse') + }) + + it('locks once the backend start has begun, and not before', () => { + expect(serverLock({ isReuse: false, backendStarting: true })).toBe( + 'started' + ) + expect( + serverLock({ isReuse: false, backendStarting: false }) + ).toBeUndefined() + }) +}) From 87c77ca8cf7244f5e8777e8960a7311310b39776 Mon Sep 17 00:00:00 2001 From: Vishnu Vardhan Date: Tue, 13 Oct 2026 12:46:15 +0530 Subject: [PATCH 2/2] fix(selenium-devtools): refuse configure() values of the wrong type JS callers bypass the option types, and configure() copied any defined value straight through. The string 'false' is truthy, so DevTools.configure({ headless: process.env.HEADLESS }) with HEADLESS=false turned headless on. The old per-option boolean checks are restored as per-kind shape checks covering every option (boolean, non-empty string choice, port, host). A value of the wrong shape is refused with a warning naming the option, the expected shape and the value, instead of being applied or silently dropped. Co-Authored-By: Claude Opus 5.5 --- .changeset/selenium-configure-options.md | 2 +- .../src/helpers/configure-options.ts | 120 +++++++++++------- packages/selenium-devtools/src/index.ts | 3 +- .../tests/configure-options.test.ts | 78 +++++++++--- 4 files changed, 137 insertions(+), 66 deletions(-) diff --git a/.changeset/selenium-configure-options.md b/.changeset/selenium-configure-options.md index fd103061..fa7b6d07 100644 --- a/.changeset/selenium-configure-options.md +++ b/.changeset/selenium-configure-options.md @@ -2,4 +2,4 @@ '@wdio/selenium-devtools': patch --- -`DevTools.configure()` now applies `port`, `hostname`, `captureScreenshots`, `filmstrip` and `emitArtifactsManifest`. They were documented as configure options but silently ignored, so the defaults always applied. `port`/`hostname` passed after the backend start has begun are ignored with a warning, and an invalid port or a `null` value is refused rather than applied. A rerun keeps its parent's backend whatever they say. +`DevTools.configure()` now applies `port`, `hostname`, `captureScreenshots`, `filmstrip` and `emitArtifactsManifest`. They were documented as configure options but silently ignored, so the defaults always applied. `port`/`hostname` passed after the backend start has begun are ignored with a warning, and an invalid port, a `null` value or a value of the wrong type (for example the string `'false'` for a boolean option) is refused with a warning rather than applied. A rerun keeps its parent's backend whatever they say. diff --git a/packages/selenium-devtools/src/helpers/configure-options.ts b/packages/selenium-devtools/src/helpers/configure-options.ts index d7a70f08..6026472f 100644 --- a/packages/selenium-devtools/src/helpers/configure-options.ts +++ b/packages/selenium-devtools/src/helpers/configure-options.ts @@ -1,26 +1,35 @@ import type { DevToolsOptions, ServerLock } from '../types.js' +type OptionKind = 'boolean' | 'choice' | 'port' | 'host' + // A Record over every option, so a key added to DevToolsOptions fails to compile here instead of being silently ignored. const OPTION_KIND = { - openUi: 'runtime', - headless: 'runtime', - captureScreenshots: 'runtime', - captureAssertions: 'runtime', - filmstrip: 'runtime', - emitArtifactsManifest: 'runtime', - mode: 'runtime', - traceFormat: 'runtime', - traceGranularity: 'runtime', - tracePolicy: 'runtime', - screenshot: 'runtime', - video: 'runtime', - port: 'server', - hostname: 'server' + openUi: 'boolean', + headless: 'boolean', + captureScreenshots: 'boolean', + captureAssertions: 'boolean', + filmstrip: 'boolean', + emitArtifactsManifest: 'boolean', + mode: 'choice', + traceFormat: 'choice', + traceGranularity: 'choice', + tracePolicy: 'choice', + screenshot: 'choice', + video: 'choice', + port: 'port', + hostname: 'host' } as const satisfies Record< Exclude, - 'runtime' | 'server' + OptionKind > +const EXPECTED: Record = { + boolean: 'a boolean', + choice: 'a non-empty string', + port: 'an integer between 1 and 65535', + host: 'a non-empty string' +} + type ConfigurableKey = keyof typeof OPTION_KIND export type ConfigurableOptions = Pick @@ -37,50 +46,73 @@ export function serverLock(state: { const CONFIGURABLE_KEYS = Object.keys(OPTION_KIND) as ConfigurableKey[] -function isSet(value: T | null | undefined): value is T { - return value !== undefined && value !== null +function isServerKey(key: ConfigurableKey): boolean { + const kind: OptionKind = OPTION_KIND[key] + return kind === 'port' || kind === 'host' +} + +// JS callers bypass the types (e.g. `headless: process.env.HEADLESS`), and the string 'false' is truthy. +function hasExpectedShape(kind: OptionKind, value: unknown): boolean { + switch (kind) { + case 'boolean': + return typeof value === 'boolean' + case 'port': + return ( + typeof value === 'number' && + Number.isInteger(value) && + value > 0 && + value < 65536 + ) + case 'choice': + case 'host': + return typeof value === 'string' && value !== '' + } +} + +function describeValue(value: unknown): string { + return typeof value === 'string' ? `'${value}'` : String(value) } -function copyDefined( +function copyOption( target: ConfigurableOptions, opts: DevToolsOptions, key: K ): void { - const value = opts[key] - if (isSet(value)) { - target[key] = value - } -} - -function isValidPort(port: number): boolean { - return Number.isInteger(port) && port > 0 && port < 65536 + target[key] = opts[key] } -/** Returns a warning when a requested option was refused; a reused backend's port/hostname are skipped silently, as that is the rerun working. */ +/** Returns one warning per refused option; a reused backend's port/hostname are skipped silently, as that is the rerun working. */ export function applyConfigureOptions( target: ConfigurableOptions, opts: DevToolsOptions, lock?: ServerLock -): string | undefined { - const requestedServer: ConfigurableKey[] = [] +): string[] { + const warnings: string[] = [] + const accepted: ConfigurableKey[] = [] for (const key of CONFIGURABLE_KEYS) { - if (OPTION_KIND[key] === 'runtime') { - copyDefined(target, opts, key) - } else if (isSet(opts[key])) { - requestedServer.push(key) + const value: unknown = opts[key] + if (value === undefined || value === null) { + continue + } + const kind: OptionKind = OPTION_KIND[key] + if (hasExpectedShape(kind, value)) { + accepted.push(key) + } else { + warnings.push( + `configure: ignoring ${key}: expected ${EXPECTED[kind]}, got ${describeValue(value)}` + ) } } - if (requestedServer.length === 0 || lock === 'reuse') { - return undefined - } - if (lock === 'started') { - return `configure: ignoring ${requestedServer.join('/')}: the backend start has already begun` - } - if (isSet(opts.port) && !isValidPort(opts.port)) { - return `configure: ignoring invalid port ${opts.port}` + const server = accepted.filter(isServerKey) + if (server.length > 0 && lock === 'started') { + warnings.push( + `configure: ignoring ${server.join('/')}: the backend start has already begun` + ) } - for (const key of requestedServer) { - copyDefined(target, opts, key) + for (const key of accepted) { + if (!(lock && isServerKey(key))) { + copyOption(target, opts, key) + } } - return undefined + return warnings } diff --git a/packages/selenium-devtools/src/index.ts b/packages/selenium-devtools/src/index.ts index c260814e..f2a071dd 100644 --- a/packages/selenium-devtools/src/index.ts +++ b/packages/selenium-devtools/src/index.ts @@ -306,8 +306,7 @@ class SeleniumDevToolsPlugin { isReuse: this.#isReuse, backendStarting: this.#backendStartPromise !== undefined }) - const warning = applyConfigureOptions(this.#options, opts, lock) - if (warning) { + for (const warning of applyConfigureOptions(this.#options, opts, lock)) { log.warn(warning) } if (opts.screencast) { diff --git a/packages/selenium-devtools/tests/configure-options.test.ts b/packages/selenium-devtools/tests/configure-options.test.ts index 5a818dfd..136c5288 100644 --- a/packages/selenium-devtools/tests/configure-options.test.ts +++ b/packages/selenium-devtools/tests/configure-options.test.ts @@ -11,23 +11,29 @@ function defaults(): ConfigurableOptions { port: 3000, hostname: 'localhost', openUi: true, + headless: false, captureScreenshots: true, filmstrip: true, mode: 'live' } } +// A JS caller can hand configure() anything, whatever the type says. +function untyped(opts: Record): DevToolsOptions { + return opts as DevToolsOptions +} + describe('applyConfigureOptions', () => { it('applies the options configure() used to drop', () => { const target = defaults() - const warning = applyConfigureOptions(target, { + const warnings = applyConfigureOptions(target, { port: 4123, hostname: '127.0.0.1', captureScreenshots: false, filmstrip: false, emitArtifactsManifest: true }) - expect(warning).toBeUndefined() + expect(warnings).toEqual([]) expect(target).toMatchObject({ port: 4123, hostname: '127.0.0.1', @@ -43,33 +49,67 @@ describe('applyConfigureOptions', () => { expect(target).toEqual({ ...defaults(), openUi: false, mode: 'trace' }) }) + it('refuses a string where a boolean belongs, so "false" cannot turn headless on', () => { + const target = defaults() + const warnings = applyConfigureOptions( + target, + untyped({ headless: 'false', openUi: 'true', captureAssertions: 'yes' }) + ) + expect(target.headless).toBe(false) + expect(target.openUi).toBe(true) + expect('captureAssertions' in target).toBe(false) + expect(warnings).toEqual([ + "configure: ignoring openUi: expected a boolean, got 'true'", + "configure: ignoring headless: expected a boolean, got 'false'", + "configure: ignoring captureAssertions: expected a boolean, got 'yes'" + ]) + }) + + it('refuses an empty or non-string choice', () => { + const target = defaults() + const warnings = applyConfigureOptions( + target, + untyped({ mode: '', traceFormat: 1 }) + ) + expect(target.mode).toBe('live') + expect('traceFormat' in target).toBe(false) + expect(warnings).toEqual([ + "configure: ignoring mode: expected a non-empty string, got ''", + 'configure: ignoring traceFormat: expected a non-empty string, got 1' + ]) + }) + it('treats an explicit undefined or null as "not specified"', () => { const target = defaults() - // A JS caller can hand configure() null even though the type forbids it. - const loose = { - emitArtifactsManifest: undefined, - traceFormat: null, - port: null - } as unknown as DevToolsOptions - expect(applyConfigureOptions(target, loose)).toBeUndefined() + const warnings = applyConfigureOptions( + target, + untyped({ + emitArtifactsManifest: undefined, + traceFormat: null, + port: null + }) + ) + expect(warnings).toEqual([]) expect(target).toEqual(defaults()) expect('emitArtifactsManifest' in target).toBe(false) }) - it('refuses an invalid port and keeps the current one', () => { + it('refuses an invalid port but still applies a valid hostname', () => { const target = defaults() - const warning = applyConfigureOptions(target, { + const warnings = applyConfigureOptions(target, { port: Number('not-a-port'), hostname: '0.0.0.0' }) - expect(warning).toBe('configure: ignoring invalid port NaN') + expect(warnings).toEqual([ + 'configure: ignoring port: expected an integer between 1 and 65535, got NaN' + ]) expect(target.port).toBe(3000) - expect(target.hostname).toBe('localhost') + expect(target.hostname).toBe('0.0.0.0') }) it('refuses port/hostname once the backend start has begun, and says why', () => { const target = defaults() - const warning = applyConfigureOptions( + const warnings = applyConfigureOptions( target, { port: 4123, hostname: 'example.test', filmstrip: false }, 'started' @@ -77,19 +117,19 @@ describe('applyConfigureOptions', () => { expect(target.port).toBe(3000) expect(target.hostname).toBe('localhost') expect(target.filmstrip).toBe(false) - expect(warning).toBe( + expect(warnings).toEqual([ 'configure: ignoring port/hostname: the backend start has already begun' - ) + ]) }) it("keeps a reused parent backend's port/hostname without warning", () => { const target = defaults() - const warning = applyConfigureOptions( + const warnings = applyConfigureOptions( target, { port: 4123, openUi: false }, 'reuse' ) - expect(warning).toBeUndefined() + expect(warnings).toEqual([]) expect(target.port).toBe(3000) expect(target.openUi).toBe(false) }) @@ -97,7 +137,7 @@ describe('applyConfigureOptions', () => { it('does not warn under a lock when port/hostname were not asked for', () => { expect( applyConfigureOptions(defaults(), { openUi: false }, 'started') - ).toBeUndefined() + ).toEqual([]) }) })