diff --git a/driver/lib/sessions/ios.ts b/driver/lib/sessions/ios.ts index feedb04c..3f638393 100644 --- a/driver/lib/sessions/ios.ts +++ b/driver/lib/sessions/ios.ts @@ -26,7 +26,7 @@ export async function startIOSSession( ...args: any[] ): Promise<[XCUITestDriver, IsolateSocket | null]> { this.log.info(`Starting an IOS proxy session`); - injectDartVmServicePortFlags(caps); + injectDartVmServicePortFlags(caps, args); const iosdriver = new XCUITestDriver({} as XCUITestDriverOpts); if (!caps.observatoryWsUri) { iosdriver.eventEmitter.once('syslogStarted', (syslog) => { @@ -249,10 +249,17 @@ async function ensureDeviceLogCaptureStarted( * Other entries (including a user-supplied `--observatory-port=*`) are left * untouched. * + * `caps` is the driver's own processed copy, while XCUITest is started from the + * raw W3C capabilities in `w3cCapsList`, so the resulting `processArguments` are + * mirrored into each of their `alwaysMatch` (and dropped from `firstMatch` to + * avoid a duplicate-key validation error). Otherwise the flags never reach the + * app under test. + * * If `dartVmServicePort` is not set, the caps are left untouched. - * @param caps The W3C capabilities passed to the driver session. + * @param caps The processed capabilities of the driver session. + * @param w3cCapsList The raw W3C capabilities forwarded to XCUITest. */ -function injectDartVmServicePortFlags(caps: Record): void { +function injectDartVmServicePortFlags(caps: Record, w3cCapsList: any[] = []): void { const port = caps.dartVmServicePort; if (typeof port !== 'number') { return; @@ -265,6 +272,16 @@ function injectDartVmServicePortFlags(caps: Record): void { filtered.push(DISABLE_SERVICE_AUTH_CODES_FLAG); } caps.processArguments.args = filtered; + + for (const w3cCaps of w3cCapsList) { + if (!w3cCaps?.alwaysMatch || typeof w3cCaps.alwaysMatch !== 'object') { + continue; + } + w3cCaps.alwaysMatch['appium:processArguments'] = caps.processArguments; + for (const firstMatch of Array.isArray(w3cCaps.firstMatch) ? w3cCaps.firstMatch : []) { + delete firstMatch?.['appium:processArguments']; + } + } } async function requireFreePort(this: FlutterDriver, port: number) { diff --git a/driver/test/ios-dart-vm-service-port.test.mjs b/driver/test/ios-dart-vm-service-port.test.mjs new file mode 100644 index 00000000..95a6ff7c --- /dev/null +++ b/driver/test/ios-dart-vm-service-port.test.mjs @@ -0,0 +1,60 @@ +import assert from 'node:assert/strict'; +import {afterEach, beforeEach, it, mock} from 'node:test'; + +import {XCUITestDriver} from 'appium-xcuitest-driver'; + +import {startIOSSession} from '../build/lib/sessions/ios.js'; + +const noopLog = {info() {}, debug() {}, warn() {}, error() {}}; +const w3cCaps = (alwaysMatch = {}, firstMatch = [{}]) => ({alwaysMatch, firstMatch}); + +let forwardedArgs; + +beforeEach(() => { + forwardedArgs = undefined; + mock.method(XCUITestDriver.prototype, 'createSession', async (...args) => { + forwardedArgs = args; + }); +}); + +afterEach(() => mock.restoreAll()); + +// Without `app`/`bundleId`, startIOSSession returns right after creating the XCUITest session, +// so these tests only exercise what gets forwarded to XCUITest. +const start = (caps, ...args) => startIOSSession.call({log: noopLog}, caps, ...args); + +it('forwards the injected VM service flags to XCUITest', async () => { + const caps = {dartVmServicePort: 9123, processArguments: {args: ['--foo'], env: {A: '1'}}}; + const forwarded = w3cCaps({'appium:dartVmServicePort': 9123}, [ + {'appium:processArguments': {args: ['--foo'], env: {A: '1'}}}, + ]); + + await start(caps, forwarded, undefined, {}); + + assert.equal(forwardedArgs[0], forwarded); + assert.deepEqual(forwarded.alwaysMatch['appium:processArguments'], { + args: ['--foo', '--vm-service-port=9123', '--disable-service-auth-codes'], + env: {A: '1'}, + }); + assert.deepEqual(forwarded.firstMatch, [{}]); +}); + +it('replaces a user-supplied --vm-service-port with the capability value', async () => { + const caps = {dartVmServicePort: 9123, processArguments: {args: ['--vm-service-port=1']}}; + const forwarded = w3cCaps(); + + await start(caps, forwarded); + + assert.deepEqual(forwardedArgs[0].alwaysMatch['appium:processArguments'].args, [ + '--vm-service-port=9123', + '--disable-service-auth-codes', + ]); +}); + +it('forwards the W3C caps unchanged when dartVmServicePort is not set', async () => { + const forwarded = w3cCaps({'appium:app': 'x'}); + + await start({}, forwarded); + + assert.deepEqual(forwardedArgs[0], w3cCaps({'appium:app': 'x'})); +});