diff --git a/driver/lib/driver.ts b/driver/lib/driver.ts index 1a869e05..b4939e29 100644 --- a/driver/lib/driver.ts +++ b/driver/lib/driver.ts @@ -238,12 +238,19 @@ class FlutterDriver extends BaseDriver { public async executeCommand(cmd: string, ...args: [string, [{skipAttachObservatoryUrl: string; any: any}]]) { if (new RegExp(/^[\s]*mobile:[\s]*(activateApp|launchApp)$/).test(args[0])) { const {skipAttachObservatoryUrl = false} = args[1][0]; - await this.proxydriver?.executeCommand(cmd, ...args); - if (skipAttachObservatoryUrl) { + // This bypasses BaseDriver.executeCommand, so pause the FlutterDriver CommandTimeout here: + // the reconnect can wait for the observatory URL much longer than newCommandTimeout. + await this.clearNewCommandTimeout(); + try { + await this.proxydriver?.executeCommand(cmd, ...args); + if (skipAttachObservatoryUrl) { + return; + } + await reConnectFlutterDriver.bind(this)(this.internalCaps); return; + } finally { + await this.startNewCommandTimeout(); } - await reConnectFlutterDriver.bind(this)(this.internalCaps); - return; } else if (new RegExp(/^[\s]*mobile:[\s]*terminateApp$/).test(args[0])) { // to make the behavior as same as this.terminateApp return await this.proxydriver?.executeCommand(cmd, ...args); diff --git a/driver/test/mobile-launch-app.test.mjs b/driver/test/mobile-launch-app.test.mjs index 5e3e0a7e..f0368ebd 100644 --- a/driver/test/mobile-launch-app.test.mjs +++ b/driver/test/mobile-launch-app.test.mjs @@ -5,34 +5,59 @@ import {FlutterDriver} from '../build/lib/driver.js'; const noopLog = {info() {}, debug() {}, warn() {}, error() {}, errorWithException() {}}; +// Records the order of proxy calls and newCommandTimeout changes. +function fakeDriver() { + const events = []; + return { + events, + log: noopLog, + currentContext: 'FLUTTER', + internalCaps: {platformName: 'iOS'}, + proxydriver: {executeCommand: mock.fn(async () => events.push('proxy'))}, + clearNewCommandTimeout: async () => events.push('clearTimeout'), + startNewCommandTimeout: async () => events.push('startTimeout'), + }; +} + // The reconnect that follows the launch fails right away here because no device-log monitor // was started, which shows that the driver tried to re-attach to the Dart VM. it('forwards mobile: launchApp to XCUITest from the FLUTTER context and reconnects', async () => { - const proxydriver = {executeCommand: mock.fn(async () => {})}; + const driver = fakeDriver(); const params = {bundleId: 'com.example.app', arguments: ['--vm-service-port=9123']}; await assert.rejects( - FlutterDriver.prototype.executeCommand.call( - {log: noopLog, currentContext: 'FLUTTER', internalCaps: {platformName: 'iOS'}, proxydriver}, - 'execute', - 'mobile: launchApp', - [params], - ), + FlutterDriver.prototype.executeCommand.call(driver, 'execute', 'mobile: launchApp', [params]), /mandatory syslog service/, ); - assert.deepEqual(proxydriver.executeCommand.mock.calls[0].arguments, ['execute', 'mobile: launchApp', [params]]); + assert.deepEqual(driver.proxydriver.executeCommand.mock.calls[0].arguments, [ + 'execute', + 'mobile: launchApp', + [params], + ]); }); it('skips the reconnect for mobile: launchApp when skipAttachObservatoryUrl is set', async () => { - const proxydriver = {executeCommand: mock.fn(async () => {})}; + const driver = fakeDriver(); - await FlutterDriver.prototype.executeCommand.call( - {log: noopLog, currentContext: 'FLUTTER', internalCaps: {platformName: 'iOS'}, proxydriver}, - 'execute', - 'mobile: launchApp', - [{bundleId: 'com.example.app', skipAttachObservatoryUrl: true}], + await FlutterDriver.prototype.executeCommand.call(driver, 'execute', 'mobile: launchApp', [ + {bundleId: 'com.example.app', skipAttachObservatoryUrl: true}, + ]); + + assert.equal(driver.proxydriver.executeCommand.mock.callCount(), 1); +}); + +// The reconnect can wait for the observatory URL far longer than newCommandTimeout, so the +// timeout must be paused for the whole command and restarted even when the reconnect fails. +it('pauses newCommandTimeout while launching and reconnecting', async () => { + const driver = fakeDriver(); + + await assert.rejects( + FlutterDriver.prototype.executeCommand.call(driver, 'execute', 'mobile: activateApp', [ + {bundleId: 'com.example.app'}, + ]), + /mandatory syslog service/, ); - assert.equal(proxydriver.executeCommand.mock.callCount(), 1); + assert.deepEqual(driver.events, ['clearTimeout', 'proxy', 'startTimeout']); });