diff --git a/.github/workflows/driver-function.yml b/.github/workflows/driver-function.yml index 97495613..eded9086 100644 --- a/.github/workflows/driver-function.yml +++ b/.github/workflows/driver-function.yml @@ -48,6 +48,7 @@ jobs: - run: npm install -g appium - run: | npm install + appium driver install --source=local ./ nohup appium --log-timestamp --log-no-colors > appium.log & working-directory: driver diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 4521f431..aa486601 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -39,4 +39,5 @@ jobs: - run: | npm install npm run lint + npm run format:check working-directory: driver diff --git a/driver/lib/commands/assertions.ts b/driver/lib/commands/assertions.ts index c27c22f6..cfb91782 100644 --- a/driver/lib/commands/assertions.ts +++ b/driver/lib/commands/assertions.ts @@ -1,14 +1,14 @@ -import { FlutterDriver } from '../driver'; -import { byValueKey, byText, byTooltip } from 'appium-flutter-finder'; -import type { SerializableFinder } from 'appium-flutter-finder'; +import {FlutterDriver} from '../driver'; +import {byValueKey, byText, byTooltip} from 'appium-flutter-finder'; +import type {SerializableFinder} from 'appium-flutter-finder'; export type FinderInput = - | { key: string } - | { text: string } - | { label: string } + | {key: string} + | {text: string} + | {label: string} | SerializableFinder | string - | { getRawFinder: () => SerializableFinder }; // FlutterElement-like input + | {getRawFinder: () => SerializableFinder}; // FlutterElement-like input // Serialize a finder to base64 const serializeFinder = (finder: SerializableFinder): string => @@ -18,7 +18,7 @@ const serializeFinder = (finder: SerializableFinder): string => const isRawFinder = (input: any): input is SerializableFinder => input && typeof input === 'object' && typeof input.finderType === 'string'; -const isFlutterElementLike = (input: any): input is { getRawFinder: () => SerializableFinder } => +const isFlutterElementLike = (input: any): input is {getRawFinder: () => SerializableFinder} => input && typeof input === 'object' && typeof input.getRawFinder === 'function'; // Convert FinderInput to base64 string @@ -47,7 +47,9 @@ function getFinderBase64(input: FinderInput): string { return byTooltip(input.label); } - throw new Error('Invalid finder input: must provide key, text, label, raw finder, or FlutterElement'); + throw new Error( + 'Invalid finder input: must provide key, text, label, raw finder, or FlutterElement', + ); } // Generic helper to wrap assert commands @@ -56,11 +58,14 @@ async function executeAssertion( command: string, input: FinderInput, timeout = 5000, - extraArgs: object = {} + extraArgs: object = {}, ): Promise { const base64 = getFinderBase64(input); try { - await driver.executeElementCommand(command, base64, { timeout, ...extraArgs }); + await driver.executeElementCommand(command, base64, { + timeout, + ...extraArgs, + }); } catch (err) { throw new Error(`Assertion failed on command "${command}" within ${timeout}ms\n${err}`); } @@ -70,21 +75,17 @@ async function executeAssertion( export const assertVisible = async ( driver: FlutterDriver, input: FinderInput, - timeout = 5000 -): Promise => - await executeAssertion(driver, 'waitFor', input, timeout, { visible: true }); + timeout = 5000, +): Promise => await executeAssertion(driver, 'waitFor', input, timeout, {visible: true}); export const assertNotVisible = async ( driver: FlutterDriver, input: FinderInput, - timeout = 5000 -): Promise => - await executeAssertion(driver, 'waitForAbsent', input, timeout); + timeout = 5000, +): Promise => await executeAssertion(driver, 'waitForAbsent', input, timeout); export const assertTappable = async ( driver: FlutterDriver, input: FinderInput, - timeout = 5000 -): Promise => - await executeAssertion(driver, 'waitForTappable', input, timeout); - + timeout = 5000, +): Promise => await executeAssertion(driver, 'waitForTappable', input, timeout); diff --git a/driver/lib/commands/clipboard.ts b/driver/lib/commands/clipboard.ts index 165b3dc3..9b771b9a 100644 --- a/driver/lib/commands/clipboard.ts +++ b/driver/lib/commands/clipboard.ts @@ -1,4 +1,4 @@ -import type { FlutterDriver } from '../driver'; +import type {FlutterDriver} from '../driver'; /** * Set clipboard content via each native app driver @@ -6,18 +6,21 @@ import type { FlutterDriver } from '../driver'; * @param content the content to get the clipboard * @param contentType the contentType to set the data type */ -export const setClipboard = async function(this: FlutterDriver, content: string, contentType: string) { +export const setClipboard = async function ( + this: FlutterDriver, + content: string, + contentType: string, +) { // @ts-expect-error this exist in xcuitestdriver or uia2 driver await this.proxydriver?.setClipboard(content, contentType); }; - /** * Get the clipboard content via each native app driver * @param this the FlutterDriver * @param contentType the contentType to set the data type */ -export const getClipboard = async function(this: FlutterDriver, contentType: string) { +export const getClipboard = async function (this: FlutterDriver, contentType: string) { // @ts-expect-error this exist in xcuitestdriver or uia2 driver await this.proxydriver?.getClipboard(contentType); }; diff --git a/driver/lib/commands/context.ts b/driver/lib/commands/context.ts index db81f7fd..b01209db 100644 --- a/driver/lib/commands/context.ts +++ b/driver/lib/commands/context.ts @@ -1,19 +1,21 @@ -import type { FlutterDriver } from '../driver'; -import { log } from '../logger'; +import type {FlutterDriver} from '../driver'; +import {log} from '../logger'; export const FLUTTER_CONTEXT_NAME = `FLUTTER`; export const NATIVE_CONTEXT_NAME = `NATIVE_APP`; -export const getCurrentContext = async function(this: FlutterDriver): Promise { +export const getCurrentContext = async function (this: FlutterDriver): Promise { return this.currentContext; }; -export const setContext = async function(this: FlutterDriver, context: string) { +export const setContext = async function (this: FlutterDriver, context: string) { if ([FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(context)) { this.proxyWebViewActive = false; // Set 'native context' when flutter driver sets the context to FLUTTER_CONTEXT_NAME if (this.proxydriver) { - log.debug(`Setting downstream drier context to '${NATIVE_CONTEXT_NAME}' in context '${context}'.`); + log.debug( + `Setting downstream drier context to '${NATIVE_CONTEXT_NAME}' in context '${context}'.`, + ); // @ts-expect-error this exist in xcuitestdriver or uia2 driver await this.proxydriver.setContext(NATIVE_CONTEXT_NAME); } @@ -28,21 +30,23 @@ export const setContext = async function(this: FlutterDriver, context: string) { } this.currentContext = context; if (context === FLUTTER_CONTEXT_NAME) { - log.debug(`Downstream driver context is set as 'NATIVE_APP' in 'FLUTTER' context to handle the native app.`); + log.debug( + `Downstream driver context is set as 'NATIVE_APP' in 'FLUTTER' context to handle the native app.`, + ); } }; -export const getContexts = async function(this: FlutterDriver): Promise { +export const getContexts = async function (this: FlutterDriver): Promise { // @ts-expect-error this exist in xcuitestdriver or uia2 driver const nativeContext = await this.proxydriver?.getContexts(); if (nativeContext) { - return [...nativeContext as string[], FLUTTER_CONTEXT_NAME]; + return [...(nativeContext as string[]), FLUTTER_CONTEXT_NAME]; } else { return [FLUTTER_CONTEXT_NAME]; } }; -export const driverShouldDoProxyCmd = function(this: FlutterDriver, command: string): boolean { +export const driverShouldDoProxyCmd = function (this: FlutterDriver, command: string): boolean { if (!this.proxydriver) { return false; } diff --git a/driver/lib/commands/element.ts b/driver/lib/commands/element.ts index 2d7dd7f7..144b6bcc 100755 --- a/driver/lib/commands/element.ts +++ b/driver/lib/commands/element.ts @@ -1,11 +1,15 @@ -import { FlutterDriver } from '../driver'; +import {FlutterDriver} from '../driver'; -export const getText = async function(this: FlutterDriver, el: string): Promise { +export const getText = async function (this: FlutterDriver, el: string): Promise { const response = await this.executeElementCommand(`get_text`, el); return response.text; }; -export const setValue = async function(this: FlutterDriver, textInput: string | [string], el: string) { +export const setValue = async function ( + this: FlutterDriver, + textInput: string | [string], + el: string, +) { const clickPromise = this.click(el); // acquire focus let text = ``; if (textInput instanceof Array) { @@ -19,6 +23,6 @@ export const setValue = async function(this: FlutterDriver, textInput: string | await this.execute(`flutter:enterText`, [text]); }; -export const clear = async function(this: FlutterDriver, el: string) { +export const clear = async function (this: FlutterDriver, el: string) { await this.setValue([``], el); }; diff --git a/driver/lib/commands/execute.ts b/driver/lib/commands/execute.ts index d290163c..50dfefdf 100644 --- a/driver/lib/commands/execute.ts +++ b/driver/lib/commands/execute.ts @@ -1,32 +1,21 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { FlutterDriver } from '../driver'; -import { reConnectFlutterDriver } from '../sessions/session'; +import type {FlutterDriver} from '../driver'; +import {reConnectFlutterDriver} from '../sessions/session'; import { longTap, scroll, scrollIntoView, scrollUntilVisible, - scrollUntilTapable + scrollUntilTapable, } from './execute/scroll'; -import { - waitFor, - waitForAbsent, - waitForTappable -} from './execute/wait'; -import { - assertVisible, - assertNotVisible, - assertTappable, - type FinderInput -} from './assertions'; +import {waitFor, waitForAbsent, waitForTappable} from './execute/wait'; +import {assertVisible, assertNotVisible, assertTappable, type FinderInput} from './assertions'; -import { launchApp } from './../ios/app'; +import {launchApp} from './../ios/app'; import B from 'bluebird'; const flutterCommandRegex = /^[\s]*flutter[\s]*:(.+)/; - - // Define types for better type safety type CommandHandler = (driver: FlutterDriver, ...args: any[]) => Promise; type CommandMap = Record; @@ -44,8 +33,6 @@ interface DiagnosticsOptions { includeProperties?: boolean; } - - interface LongTapOptions { durationMilliseconds: number; frequency?: number; @@ -58,7 +45,7 @@ interface OffsetOptions { // Extract command handlers into a separate object for better organization const commandHandlers: CommandMap = { launchApp: async (driver, appId: string, opts = {}) => { - const { arguments: args = [], environment: env = {} } = opts; + const {arguments: args = [], environment: env = {}} = opts; await launchApp(driver.internalCaps.udid!, appId, args, env); await reConnectFlutterDriver.bind(driver)(driver.internalCaps); }, @@ -71,18 +58,20 @@ const commandHandlers: CommandMap = { getOffset: async (driver, elementBase64: string, options: OffsetOptions) => await driver.executeElementCommand('get_offset', elementBase64, options), waitForCondition: async (driver, conditionName: string) => - await driver.executeElementCommand('waitForCondition', '', { conditionName }), + await driver.executeElementCommand('waitForCondition', '', { + conditionName, + }), forceGC: async (driver) => { - const response = await driver.socket!.call('_collectAllGarbage', { + const response = (await driver.socket!.call('_collectAllGarbage', { isolateId: driver.socket!.isolateId, - }) as { type: string }; + })) as {type: string}; if (response.type !== 'Success') { throw new Error(`Could not forceGC, response was ${JSON.stringify(response)}`); } }, setIsolateId: async (driver, isolateId: string) => { driver.socket!.isolateId = isolateId; - return await driver.socket!.call('getIsolate', { isolateId }); + return await driver.socket!.call('getIsolate', {isolateId}); }, getIsolate: async (driver, isolateId?: string) => await driver.executeGetIsolateCommand(isolateId || driver.socket!.isolateId!), @@ -94,8 +83,12 @@ const commandHandlers: CommandMap = { throw new Error(`Could not clear timeline, response was ${JSON.stringify(response)}`); } }, - getRenderObjectDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => { - const { subtreeDepth = 0, includeProperties = true } = opts; + getRenderObjectDiagnostics: async ( + driver, + elementBase64: string, + opts: DiagnosticsOptions = {}, + ) => { + const {subtreeDepth = 0, includeProperties = true} = opts; return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, { diagnosticsType: 'renderObject', includeProperties, @@ -103,7 +96,7 @@ const commandHandlers: CommandMap = { }); }, getWidgetDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => { - const { subtreeDepth = 0, includeProperties = true } = opts; + const {subtreeDepth = 0, includeProperties = true} = opts; return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, { diagnosticsType: 'widget', includeProperties, @@ -118,8 +111,7 @@ const commandHandlers: CommandMap = { await waitFor(driver, finder, timeout), waitForTappable: async (driver, finder: string, timeout?: number) => await waitForTappable(driver, finder, timeout), - scroll: async (driver, finder: string, opts: any) => - await scroll(driver, finder, opts), + scroll: async (driver, finder: string, opts: any) => await scroll(driver, finder, opts), scrollUntilVisible: async (driver, finder: string, opts: any) => await scrollUntilVisible(driver, finder, opts), scrollUntilTapable: async (driver, finder: string, opts: any) => @@ -127,24 +119,32 @@ const commandHandlers: CommandMap = { scrollIntoView: async (driver, finder: string, opts: any) => await scrollIntoView(driver, finder, opts), setTextEntryEmulation: async (driver, enabled: boolean) => - await driver.socket!.executeSocketCommand({ command: 'set_text_entry_emulation', enabled }), + await driver.socket!.executeSocketCommand({ + command: 'set_text_entry_emulation', + enabled, + }), enterText: async (driver, text: string) => - await driver.socket!.executeSocketCommand({ command: 'enter_text', text }), + await driver.socket!.executeSocketCommand({command: 'enter_text', text}), requestData: async (driver, message: string) => - await driver.socket!.executeSocketCommand({ command: 'request_data', message }), + await driver.socket!.executeSocketCommand({ + command: 'request_data', + message, + }), longTap: async (driver, finder: string, durationOrOptions: LongTapOptions) => await longTap(driver, finder, durationOrOptions), waitForFirstFrame: async (driver) => - await driver.executeElementCommand('waitForCondition', '', { conditionName: 'FirstFrameRasterizedCondition' }), + await driver.executeElementCommand('waitForCondition', '', { + conditionName: 'FirstFrameRasterizedCondition', + }), setFrameSync: async (driver, enabled: boolean, durationMilliseconds: number) => await driver.socket!.executeSocketCommand({ command: 'set_frame_sync', enabled, timeout: durationMilliseconds, }), - clickElement: async (driver, finder: string, opts: { timeout?: number } = {}) => { - const { timeout = 1000 } = opts; - return await driver.executeElementCommand('tap', finder, { timeout }); + clickElement: async (driver, finder: string, opts: {timeout?: number} = {}) => { + const {timeout = 1000} = opts; + return await driver.executeElementCommand('tap', finder, {timeout}); }, dragAndDropWithCommandExtension: async (driver, params: DragAndDropParams) => await driver.socket!.executeSocketCommand({ @@ -157,17 +157,13 @@ const commandHandlers: CommandMap = { await assertNotVisible(driver, input, timeout), assertTappable: async (driver, input: FinderInput, timeout = 5000) => await assertTappable(driver, input, timeout), - getTextWithCommandExtension: async (driver, params: { findBy: string }) => + getTextWithCommandExtension: async (driver, params: {findBy: string}) => await driver.socket!.executeSocketCommand({ command: 'getTextWithCommandExtension', findBy: params.findBy, }), }; -export const execute = async function ( - this: FlutterDriver, - rawCommand: string, - args: any[], -) { +export const execute = async function (this: FlutterDriver, rawCommand: string, args: any[]) { const matching = rawCommand.match(flutterCommandRegex); if (!matching) { throw new Error(`Command not supported: "${rawCommand}"`); @@ -182,4 +178,3 @@ export const execute = async function ( return await handler(this, ...args); }; - diff --git a/driver/lib/commands/execute/scroll.ts b/driver/lib/commands/execute/scroll.ts index 29cdb169..10bd79c5 100644 --- a/driver/lib/commands/execute/scroll.ts +++ b/driver/lib/commands/execute/scroll.ts @@ -1,7 +1,6 @@ - import _ from 'lodash'; -import { FlutterDriver } from '../../driver'; -import { waitFor, waitForTappable } from './wait'; +import {FlutterDriver} from '../../driver'; +import {waitFor, waitForTappable} from './wait'; export const scroll = async ( self: FlutterDriver, @@ -13,7 +12,7 @@ export const scroll = async ( frequency?: number; }, ) => { - const { dx, dy, durationMilliseconds, frequency = 60 } = opts; + const {dx, dy, durationMilliseconds, frequency = 60} = opts; if ( typeof dx !== `number` || @@ -48,7 +47,7 @@ export const longTap = async ( frequency?: number; }, ) => { - const { durationMilliseconds = 1000, frequency = 60 } = options; + const {durationMilliseconds = 1000, frequency = 60} = options; if (typeof durationMilliseconds !== 'number' || typeof frequency !== 'number') { throw new Error(`Invalid longTap options: ${JSON.stringify(options)}`); @@ -100,7 +99,15 @@ export const scrollUntilVisible = async ( waitTimeoutMilliseconds?: number; }, ) => { - const { item, alignment = 0.0, dxScroll = 0, dyScroll = 0, durationMilliseconds = 100, frequency, waitTimeoutMilliseconds } = opts; + const { + item, + alignment = 0.0, + dxScroll = 0, + dyScroll = 0, + durationMilliseconds = 100, + frequency, + waitTimeoutMilliseconds, + } = opts; if (!validateOps(alignment, dxScroll, dyScroll)) { throw new Error(`${opts} is not a valid options`); @@ -121,16 +128,18 @@ export const scrollUntilVisible = async ( dx: dxScroll, dy: dyScroll, durationMilliseconds, - frequency + frequency, }); - } catch { /* go to the next scroll */ } + } catch { + /* go to the next scroll */ + } } if (!isVisible) { throw new Error(`Stop scrolling as timeout ${waitTimeoutMilliseconds}`); } - return scrollIntoView(self, item, { alignment }); + return scrollIntoView(self, item, {alignment}); }; export const scrollUntilTapable = async ( @@ -146,7 +155,15 @@ export const scrollUntilTapable = async ( waitTimeoutMilliseconds?: number; }, ) => { - const { item, alignment = 0.0, dxScroll = 0, dyScroll = 0, durationMilliseconds = 100, frequency, waitTimeoutMilliseconds } = opts; + const { + item, + alignment = 0.0, + dxScroll = 0, + dyScroll = 0, + durationMilliseconds = 100, + frequency, + waitTimeoutMilliseconds, + } = opts; if (!validateOps(alignment, dxScroll, dyScroll)) { throw new Error(`${opts} is not a valid options`); @@ -170,16 +187,18 @@ export const scrollUntilTapable = async ( dx: dxScroll, dy: dyScroll, durationMilliseconds, - frequency + frequency, }); - } catch { /* go to the next scroll */ } + } catch { + /* go to the next scroll */ + } } if (!isVisible) { throw new Error(`Stop scrolling as timeout ${waitTimeoutMilliseconds}`); } - return scrollIntoView(self, item, { alignment }); + return scrollIntoView(self, item, {alignment}); }; export const scrollIntoView = async ( @@ -190,13 +209,16 @@ export const scrollIntoView = async ( timeout?: number; }, ) => { - const { alignment = 0.0, timeout } = opts; - if (typeof alignment !== `number` || (typeof timeout !== `undefined` && typeof timeout !== `number`)) { + const {alignment = 0.0, timeout} = opts; + if ( + typeof alignment !== `number` || + (typeof timeout !== `undefined` && typeof timeout !== `number`) + ) { // @todo BaseDriver's errors.InvalidArgumentError(); throw new Error(`${opts} is not a valid options`); } - const args = typeof timeout === `number` ? { alignment, timeout } : { alignment }; + const args = typeof timeout === `number` ? {alignment, timeout} : {alignment}; return await self.executeElementCommand(`scrollIntoView`, elementBase64, args); }; diff --git a/driver/lib/commands/execute/wait.ts b/driver/lib/commands/execute/wait.ts index 17a8c9db..e22c4dca 100644 --- a/driver/lib/commands/execute/wait.ts +++ b/driver/lib/commands/execute/wait.ts @@ -1,25 +1,26 @@ -import { FlutterDriver } from '../../driver'; +import {FlutterDriver} from '../../driver'; -const waitForConstructor = (command: `waitForAbsent` | `waitFor` | `waitForTappable`) => async ( - self: FlutterDriver, - elementBase64: string, - durationMilliseconds?: number, -): Promise => { +const waitForConstructor = + (command: `waitForAbsent` | `waitFor` | `waitForTappable`) => + async ( + self: FlutterDriver, + elementBase64: string, + durationMilliseconds?: number, + ): Promise => { + let args = {}; - let args = {}; + if (typeof durationMilliseconds === `number`) { + args = { + timeout: durationMilliseconds, + }; + } else if (typeof durationMilliseconds !== `undefined`) { + // @todo BaseDriver's errors.InvalidArgumentError(); + throw new Error(`durationMilliseconds is not a valid options`); + } - if (typeof durationMilliseconds === `number`) { - args = { - timeout: durationMilliseconds, - }; - } else if (typeof durationMilliseconds !== `undefined`) { - // @todo BaseDriver's errors.InvalidArgumentError(); - throw new Error(`durationMilliseconds is not a valid options`); - } - - await self.executeElementCommand(command, elementBase64, args); - return elementBase64; -}; + await self.executeElementCommand(command, elementBase64, args); + return elementBase64; + }; export const waitForAbsent = waitForConstructor(`waitForAbsent`); diff --git a/driver/lib/commands/gesture.ts b/driver/lib/commands/gesture.ts index 57baddc6..043e6c7a 100755 --- a/driver/lib/commands/gesture.ts +++ b/driver/lib/commands/gesture.ts @@ -1,23 +1,19 @@ -import type { FlutterDriver } from '../driver'; -import { longTap as longClick} from './execute/scroll'; +import type {FlutterDriver} from '../driver'; +import {longTap as longClick} from './execute/scroll'; -export const click = async function(this: FlutterDriver, el: string) { +export const click = async function (this: FlutterDriver, el: string) { const retVal = await this.tapEl(el, false); return retVal; }; -export const tapEl = async function( - this: FlutterDriver, - el: string, - longPress: boolean, -) { +export const tapEl = async function (this: FlutterDriver, el: string, longPress: boolean) { // perform a tap on the given element // if longPress is true, the tap becomes a longPress action const commandName = longPress ? `longPress` : `tap`; return await this.executeElementCommand(commandName, el); }; -export const tap = async function( +export const tap = async function ( this: FlutterDriver, gestures: Record[], longPress: boolean, @@ -28,7 +24,7 @@ export const tap = async function( await this.tapEl(elementId, longPress); }; -export const longTap = async function( +export const longTap = async function ( this: FlutterDriver, gestures: Record[], ms: number, @@ -36,13 +32,17 @@ export const longTap = async function( // pass duration if the wait action given by user. // If wait action is missing taking 10000 ms default const elementId = gestures[0].options.element; - return await longClick(this, elementId, {durationMilliseconds: ms, frequency: 30}); + return await longClick(this, elementId, { + durationMilliseconds: ms, + frequency: 30, + }); }; -export const performTouch = async function(this: FlutterDriver, gestures: Record[]) { +export const performTouch = async function (this: FlutterDriver, gestures: Record[]) { if (gestures.length === 3) { if ( - gestures[0].action === `longPress` && gestures[1].action === `wait` && + gestures[0].action === `longPress` && + gestures[1].action === `wait` && gestures[2].action === `release` ) { return await this.longTap(gestures, gestures[1].options.ms); @@ -50,10 +50,7 @@ export const performTouch = async function(this: FlutterDriver, gestures: Record } else if (gestures.length === 2) { if (gestures[0].action === `press` && gestures[1].action === `release`) { return await this.tap(gestures, false); - } else if ( - gestures[0].action === `longPress` && - gestures[1].action === `release` - ) { + } else if (gestures[0].action === `longPress` && gestures[1].action === `release`) { return await this.longTap(gestures, 10 * 1000); } } else if (gestures.length === 1) { diff --git a/driver/lib/commands/screen.ts b/driver/lib/commands/screen.ts index bc7a5e82..4725052a 100644 --- a/driver/lib/commands/screen.ts +++ b/driver/lib/commands/screen.ts @@ -1,7 +1,7 @@ -import type { FlutterDriver } from '../driver'; -import { IsolateSocket } from '../sessions/isolate_socket'; +import type {FlutterDriver} from '../driver'; +import {IsolateSocket} from '../sessions/isolate_socket'; -export const getScreenshot = async function(this: FlutterDriver) { - const response = await (this.socket as IsolateSocket).call(`_flutter.screenshot`) as any; +export const getScreenshot = async function (this: FlutterDriver) { + const response = (await (this.socket as IsolateSocket).call(`_flutter.screenshot`)) as any; return response.screenshot; }; diff --git a/driver/lib/desired-caps.ts b/driver/lib/desired-caps.ts index ae1bdb0c..3bdf572e 100755 --- a/driver/lib/desired-caps.ts +++ b/driver/lib/desired-caps.ts @@ -9,10 +9,7 @@ export const desiredCapConstraints = { isNumber: true, }, platformName: { - inclusionCaseInsensitive: [ - 'iOS', - 'Android', - ], + inclusionCaseInsensitive: ['iOS', 'Android'], isString: true, presence: true, }, @@ -26,15 +23,15 @@ export const desiredCapConstraints = { isString: true, }, skipPortForward: { - isBoolean: true + isBoolean: true, }, adbPort: { - isNumber: true + isNumber: true, }, remoteAdbHost: { - isString: true + isString: true, }, forwardingPort: { - isNumber: true + isNumber: true, }, } as const; diff --git a/driver/lib/doctor/checks.js b/driver/lib/doctor/checks.js index dbe7d61a..c44f76bc 100644 --- a/driver/lib/doctor/checks.js +++ b/driver/lib/doctor/checks.js @@ -1,5 +1,5 @@ -import { doctor as androidDoctor } from 'appium-android-driver'; -import { doctor as iosDoctor } from 'appium-xcuitest-driver'; +import {doctor as androidDoctor} from 'appium-android-driver'; +import {doctor as iosDoctor} from 'appium-xcuitest-driver'; // shared export const homeEnvVarCheck = /** @type {any} */ (iosDoctor.required.homeEnvVarCheck); @@ -34,6 +34,14 @@ if (!process.env.SKIP_IOS) { export const optionalFfmpegCheck = androidDoctor.optionalFfmpegCheck; export { - androidHomeCheck, javaHomeCheck, javaHomeValueCheck, androidSdkCheck, optionalBundletoolCheck, optionalGstreamerCheck, - xcodeCheck, xcodeToolsCheck, envVarAndPathCheck, optionalApplesimutilsCommandCheck + androidHomeCheck, + javaHomeCheck, + javaHomeValueCheck, + androidSdkCheck, + optionalBundletoolCheck, + optionalGstreamerCheck, + xcodeCheck, + xcodeToolsCheck, + envVarAndPathCheck, + optionalApplesimutilsCommandCheck, }; diff --git a/driver/lib/driver.ts b/driver/lib/driver.ts index dab50115..688b1ea8 100644 --- a/driver/lib/driver.ts +++ b/driver/lib/driver.ts @@ -1,36 +1,41 @@ // @ts-ignore: no 'errors' export module import _ from 'lodash'; -import { BaseDriver } from 'appium/driver'; -import { log as logger } from './logger'; +import {BaseDriver} from 'appium/driver'; +import {log as logger} from './logger'; import { - executeElementCommand, executeGetVMCommand, executeGetIsolateCommand + executeElementCommand, + executeGetVMCommand, + executeGetIsolateCommand, } from './sessions/observatory'; -import { PLATFORM } from './platform'; -import { createSession, reConnectFlutterDriver } from './sessions/session'; +import {PLATFORM} from './platform'; +import {createSession, reConnectFlutterDriver} from './sessions/session'; import { - driverShouldDoProxyCmd, FLUTTER_CONTEXT_NAME, - getContexts, getCurrentContext, NATIVE_CONTEXT_NAME, setContext + driverShouldDoProxyCmd, + FLUTTER_CONTEXT_NAME, + getContexts, + getCurrentContext, + NATIVE_CONTEXT_NAME, + setContext, } from './commands/context'; -import { clear, getText, setValue } from './commands/element'; -import { execute } from './commands/execute'; -import { click, longTap, performTouch, tap, tapEl } from './commands/gesture'; -import { getScreenshot } from './commands/screen'; -import { getClipboard, setClipboard } from './commands/clipboard'; -import { desiredCapConstraints } from './desired-caps'; -import { XCUITestDriver } from 'appium-xcuitest-driver'; -import { AndroidUiautomator2Driver } from 'appium-uiautomator2-driver'; +import {clear, getText, setValue} from './commands/element'; +import {execute} from './commands/execute'; +import {click, longTap, performTouch, tap, tapEl} from './commands/gesture'; +import {getScreenshot} from './commands/screen'; +import {getClipboard, setClipboard} from './commands/clipboard'; +import {desiredCapConstraints} from './desired-caps'; +import {XCUITestDriver} from 'appium-xcuitest-driver'; +import {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver'; import type { DefaultCreateSessionResult, DriverCaps, DriverData, - W3CDriverCaps, + W3CDriverCaps, RouteMatcher, - Orientation + Orientation, } from '@appium/types'; -import type { IsolateSocket } from './sessions/isolate_socket'; -import type { Server } from 'node:net'; -import type { LogMonitor } from './sessions/log-monitor'; - +import type {IsolateSocket} from './sessions/isolate_socket'; +import type {Server} from 'node:net'; +import type {LogMonitor} from './sessions/log-monitor'; type FluttertDriverConstraints = typeof desiredCapConstraints; // Need to not proxy in WebView context @@ -112,10 +117,17 @@ class FlutterDriver extends BaseDriver { this.localServer = null; } - public async createSession(...args): Promise> { - const [sessionId, caps] = await super.createSession(...JSON.parse(JSON.stringify(args)) as [ - W3CDriverCaps, W3CDriverCaps, W3CDriverCaps, DriverData[] - ]); + public async createSession( + ...args + ): Promise> { + const [sessionId, caps] = await super.createSession( + ...(JSON.parse(JSON.stringify(args)) as [ + W3CDriverCaps, + W3CDriverCaps, + W3CDriverCaps, + DriverData[], + ]), + ); this.internalCaps = caps; return createSession.bind(this)(sessionId, caps, ...JSON.parse(JSON.stringify(args))); } @@ -136,11 +148,13 @@ class FlutterDriver extends BaseDriver { case PLATFORM.ANDROID: if (this.portForwardLocalPort) { if (this.proxydriver) { - await (this.proxydriver as AndroidUiautomator2Driver).adb?.removePortForward(this.portForwardLocalPort); + await (this.proxydriver as AndroidUiautomator2Driver).adb?.removePortForward( + this.portForwardLocalPort, + ); } } break; - } + } if (this.proxydriver) { this.log.info('Deleting the proxy driver session.'); @@ -185,16 +199,20 @@ class FlutterDriver extends BaseDriver { return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'GET'); default: return await (this.proxydriver as AndroidUiautomator2Driver).getOrientation(); - } + } } public async setOrientation(orientation: string) { switch (_.toLower(this.internalCaps.platformName)) { case PLATFORM.IOS: - return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'POST', {orientation}); + return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'POST', { + orientation, + }); default: - return await (this.proxydriver as AndroidUiautomator2Driver).setOrientation(orientation as Orientation); - } + return await (this.proxydriver as AndroidUiautomator2Driver).setOrientation( + orientation as Orientation, + ); + } } public validateLocatorStrategy(strategy: string) { @@ -205,7 +223,9 @@ class FlutterDriver extends BaseDriver { super.validateLocatorStrategy(strategy, false); } - validateDesiredCaps(caps: DriverCaps): caps is DriverCaps { + validateDesiredCaps( + caps: DriverCaps, + ): caps is DriverCaps { // check with the base class, and return if it fails const res = super.validateDesiredCaps(caps); if (!res) { @@ -216,17 +236,22 @@ class FlutterDriver extends BaseDriver { return true; } - public async proxyCommand (url: string, method: string, body = null) { + public async proxyCommand(url: string, method: string, body = null) { // @ts-expect-error this exist in xcuitestdriver or uia2 driver const result = await this.proxydriver?.proxyCommand(url, method, body); return result; } - public async executeCommand(cmd: string, ...args: [string, [{skipAttachObservatoryUrl: string, any: any}]]) { + public async executeCommand( + cmd: string, + ...args: [string, [{skipAttachObservatoryUrl: string; any: any}]] + ) { if (new RegExp(/^[\s]*mobile:[\s]*activateApp$/).test(args[0])) { - const { skipAttachObservatoryUrl = false } = args[1][0]; + const {skipAttachObservatoryUrl = false} = args[1][0]; await this.proxydriver?.executeCommand(cmd, ...args); - if (skipAttachObservatoryUrl) { return; } + if (skipAttachObservatoryUrl) { + return; + } await reConnectFlutterDriver.bind(this)(this.internalCaps); return; } else if (new RegExp(/^[\s]*mobile:[\s]*terminateApp$/).test(args[0])) { @@ -285,4 +310,4 @@ class FlutterDriver extends BaseDriver { } } -export { FlutterDriver }; +export {FlutterDriver}; diff --git a/driver/lib/ios/app.ts b/driver/lib/ios/app.ts index 6fa15b25..db018f91 100644 --- a/driver/lib/ios/app.ts +++ b/driver/lib/ios/app.ts @@ -1,14 +1,21 @@ import {services, INSTRUMENT_CHANNEL} from 'appium-ios-device'; -import { log } from './../logger'; +import {log} from './../logger'; /** * Launch the given bundle id via instrument service. */ -export const launchApp = async (udid: string, bundleId: string, args = [], env = {}): Promise => { +export const launchApp = async ( + udid: string, + bundleId: string, + args = [], + env = {}, +): Promise => { let instrumentService; try { instrumentService = await services.startInstrumentService(udid); - log.info(`Launching app ${bundleId} with arguments ${JSON.stringify(args)} and env ${JSON.stringify(env)} on device ${udid}`); + log.info( + `Launching app ${bundleId} with arguments ${JSON.stringify(args)} and env ${JSON.stringify(env)} on device ${udid}`, + ); await instrumentService.callChannel( INSTRUMENT_CHANNEL.PROCESS_CONTROL, 'launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:', @@ -16,7 +23,7 @@ export const launchApp = async (udid: string, bundleId: string, args = [], env = bundleId, env, args, - {'StartSuspendedKey': 0, 'KillExisting': 1} + {StartSuspendedKey: 0, KillExisting: 1}, ); return true; } catch (err) { diff --git a/driver/lib/logger.ts b/driver/lib/logger.ts index 1c17ef3f..fe7925e3 100755 --- a/driver/lib/logger.ts +++ b/driver/lib/logger.ts @@ -1,4 +1,4 @@ -import { logger } from '@appium/support'; -import { AppiumLogger } from '@appium/types'; +import {logger} from '@appium/support'; +import {AppiumLogger} from '@appium/types'; export const log: AppiumLogger = logger.getLogger(`FlutterDriver`); diff --git a/driver/lib/platform.ts b/driver/lib/platform.ts index 3f70c784..5cddef18 100644 --- a/driver/lib/platform.ts +++ b/driver/lib/platform.ts @@ -2,4 +2,3 @@ export const PLATFORM = { IOS: 'ios', ANDROID: 'android', } as const; - diff --git a/driver/lib/sessions/android.ts b/driver/lib/sessions/android.ts index 3b611261..6f5101db 100644 --- a/driver/lib/sessions/android.ts +++ b/driver/lib/sessions/android.ts @@ -1,10 +1,10 @@ -import { AndroidUiautomator2Driver } from 'appium-uiautomator2-driver'; -import { connectSocket, extractObservatoryUrl, OBSERVATORY_URL_PATTERN } from './observatory'; -import type { InitialOpts, StringRecord } from '@appium/types'; -import type { IsolateSocket } from './isolate_socket'; -import { FlutterDriver } from '../driver'; -import { LogMonitor } from './log-monitor'; -import type { LogEntry } from './log-monitor'; +import {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver'; +import {connectSocket, extractObservatoryUrl, OBSERVATORY_URL_PATTERN} from './observatory'; +import type {InitialOpts, StringRecord} from '@appium/types'; +import type {IsolateSocket} from './isolate_socket'; +import {FlutterDriver} from '../driver'; +import {LogMonitor} from './log-monitor'; +import type {LogEntry} from './log-monitor'; export async function startAndroidSession( this: FlutterDriver, @@ -33,27 +33,24 @@ export async function startAndroidSession( return [androiddriver, null]; } - return [ - androiddriver, - await connectAndroidSession.bind(this)(androiddriver, caps), - ]; + return [androiddriver, await connectAndroidSession.bind(this)(androiddriver, caps)]; } -export async function connectAndroidSession ( +export async function connectAndroidSession( this: FlutterDriver, androiddriver: AndroidUiautomator2Driver, caps: Record, - clearLog: boolean = false + clearLog: boolean = false, ): Promise { const observatoryWsUri = await getObservatoryWsUri.bind(this)(androiddriver, caps, clearLog); return await connectSocket.bind(this)(observatoryWsUri, caps); } -export async function getObservatoryWsUri ( +export async function getObservatoryWsUri( this: FlutterDriver, proxydriver: AndroidUiautomator2Driver, caps: StringRecord, - clearLog: boolean = false + clearLog: boolean = false, ): Promise { if (clearLog) { this._logmon?.clearlastMatch(); @@ -74,15 +71,15 @@ export async function getObservatoryWsUri ( if (!this._logmon) { throw new Error( `The mandatory logcat service must be running in order to initialize the Flutter driver. ` + - `Have you disabled it in capabilities?` + `Have you disabled it in capabilities?`, ); } - let lastMatch : LogEntry | null = null; + let lastMatch: LogEntry | null = null; try { lastMatch = await this._logmon.waitForLastMatchExist( caps.maxRetryCount, - caps.retryBackoffTime + caps.retryBackoffTime, ); } catch (e) { this.log.error(e); @@ -90,8 +87,8 @@ export async function getObservatoryWsUri ( if (!lastMatch) { throw new Error( `No observatory URL matching to '${OBSERVATORY_URL_PATTERN}' was found in the device log. ` + - `Please make sure the application under test is configured properly according to ` + - `https://github.com/appium/appium-flutter-driver#usage and that it does not crash on startup.` + `Please make sure the application under test is configured properly according to ` + + `https://github.com/appium/appium-flutter-driver#usage and that it does not crash on startup.`, ); } urlObject = extractObservatoryUrl(lastMatch) as URL; diff --git a/driver/lib/sessions/base64url.ts b/driver/lib/sessions/base64url.ts index 9bc083ad..fd09b6b1 100644 --- a/driver/lib/sessions/base64url.ts +++ b/driver/lib/sessions/base64url.ts @@ -1,7 +1,9 @@ import _ from 'lodash'; -import { util } from '@appium/support'; +import {util} from '@appium/support'; -export const decode = (input: string | {ELEMENT: string} | {[util.W3C_WEB_ELEMENT_IDENTIFIER]: string}): string => { +export const decode = ( + input: string | {ELEMENT: string} | {[util.W3C_WEB_ELEMENT_IDENTIFIER]: string}, +): string => { let base64String: string = ``; if (_.isString(input)) { base64String = input as string; @@ -13,7 +15,8 @@ export const decode = (input: string | {ELEMENT: string} | {[util.W3C_WEB_ELEMEN } else { throw new Error( `Input is expected to be a base64-encoded string or a valid element object. ` + - `${JSON.stringify(input)} has been provided instead`); + `${JSON.stringify(input)} has been provided instead`, + ); } return Buffer.from(base64String, `base64`).toString(); }; diff --git a/driver/lib/sessions/ios.ts b/driver/lib/sessions/ios.ts index 6522db7a..22b6847b 100644 --- a/driver/lib/sessions/ios.ts +++ b/driver/lib/sessions/ios.ts @@ -1,24 +1,21 @@ -import { utilities } from 'appium-ios-device'; -import { XCUITestDriver } from 'appium-xcuitest-driver'; +import {utilities} from 'appium-ios-device'; +import {XCUITestDriver} from 'appium-xcuitest-driver'; import B from 'bluebird'; import net from 'node:net'; -import { checkPortStatus } from 'portscanner'; -import { - connectSocket, - extractObservatoryUrl, - OBSERVATORY_URL_PATTERN -} from './observatory'; -import type { IsolateSocket } from './isolate_socket'; -import { LogMonitor } from './log-monitor'; -import type { LogEntry } from './log-monitor'; -import type { FlutterDriver } from '../driver'; -import type { XCUITestDriverOpts } from 'appium-xcuitest-driver/build/lib/driver'; +import {checkPortStatus} from 'portscanner'; +import {connectSocket, extractObservatoryUrl, OBSERVATORY_URL_PATTERN} from './observatory'; +import type {IsolateSocket} from './isolate_socket'; +import {LogMonitor} from './log-monitor'; +import type {LogEntry} from './log-monitor'; +import type {FlutterDriver} from '../driver'; +import type {XCUITestDriverOpts} from 'appium-xcuitest-driver/build/lib/driver'; const LOCALHOST = `127.0.0.1`; export async function startIOSSession( this: FlutterDriver, - caps: Record, ...args: any[] + caps: Record, + ...args: any[] ): Promise<[XCUITestDriver, IsolateSocket | null]> { this.log.info(`Starting an IOS proxy session`); const iosdriver = new XCUITestDriver({} as XCUITestDriverOpts); @@ -42,52 +39,49 @@ export async function startIOSSession( return [iosdriver, null]; } - return [ - iosdriver, - await connectIOSSession.bind(this)(iosdriver, caps), - ]; + return [iosdriver, await connectIOSSession.bind(this)(iosdriver, caps)]; } export async function connectIOSSession( this: FlutterDriver, iosdriver: XCUITestDriver, caps: Record, - clearLog: boolean = false + clearLog: boolean = false, ): Promise { const observatoryWsUri = await getObservatoryWsUri.bind(this)(iosdriver, caps, clearLog); return await connectSocket.bind(this)(observatoryWsUri, iosdriver, caps); } -async function requireFreePort( - this: FlutterDriver, - port: number -) { +async function requireFreePort(this: FlutterDriver, port: number) { // Try to close existing local server if it exists if (this.localServer) { this.log.info(`Closing existing local server on port ${port}`); await new Promise((resolve) => { - this.localServer?.close((err) => { - if (err) { - this.log.error(`Error occurred while closing the local server: ${err.message}`); - return resolve(); // Resolve even if there's an error to avoid hanging - } - this.log.info(`Previous local server closed`); - resolve(); - }); + this.localServer?.close((err) => { + if (err) { + this.log.error(`Error occurred while closing the local server: ${err.message}`); + return resolve(); // Resolve even if there's an error to avoid hanging + } + this.log.info(`Previous local server closed`); + resolve(); + }); }); } if ((await checkPortStatus(port, LOCALHOST)) !== `open`) { return; } this.log.warn(`Port #${port} is busy. Did you quit the previous driver session(s) properly?`); - throw new Error(`The port :${port} is occupied by an other process. ` + - `You can either quit that process or select another free port.`); + throw new Error( + `The port :${port} is occupied by an other process. ` + + `You can either quit that process or select another free port.`, + ); } -export async function getObservatoryWsUri ( +export async function getObservatoryWsUri( this: FlutterDriver, - proxydriver: XCUITestDriver, caps: Record, - clearLog: boolean = false + proxydriver: XCUITestDriver, + caps: Record, + clearLog: boolean = false, ): Promise { if (clearLog) { this._logmon?.clearlastMatch(); @@ -108,15 +102,15 @@ export async function getObservatoryWsUri ( if (!this._logmon) { throw new Error( `The mandatory syslog service must be running in order to initialize the Flutter driver. ` + - `Have you disabled it in capabilities?` + `Have you disabled it in capabilities?`, ); } - let lastMatch : LogEntry | null = null; + let lastMatch: LogEntry | null = null; try { lastMatch = await this._logmon.waitForLastMatchExist( caps.maxRetryCount, - caps.retryBackoffTime + caps.retryBackoffTime, ); } catch (e) { this.log.error(e); @@ -124,8 +118,8 @@ export async function getObservatoryWsUri ( if (!lastMatch) { throw new Error( `No observatory URL matching to '${OBSERVATORY_URL_PATTERN}' was found in the device log. ` + - `Please make sure the application under test is configured properly according to ` + - `https://github.com/appium/appium-flutter-driver#usage and that it does not crash on startup.` + `Please make sure the application under test is configured properly according to ` + + `https://github.com/appium/appium-flutter-driver#usage and that it does not crash on startup.`, ); } urlObject = extractObservatoryUrl(lastMatch) as URL; @@ -140,7 +134,7 @@ export async function getObservatoryWsUri ( urlObject.port = localPort; this.log.info(`Running on iOS real device`); - const { udid } = proxydriver.opts; + const {udid} = proxydriver.opts; await requireFreePort.bind(this)(localPort); this.localServer = net.createServer(async (localSocket) => { let remoteSocket; diff --git a/driver/lib/sessions/isolate_socket.ts b/driver/lib/sessions/isolate_socket.ts index 4fad28a2..4337dbca 100644 --- a/driver/lib/sessions/isolate_socket.ts +++ b/driver/lib/sessions/isolate_socket.ts @@ -1,20 +1,20 @@ -import { Client } from 'rpc-websockets'; +import {Client} from 'rpc-websockets'; interface ExecuteArgs { - command: string, - [key: string]: any; + command: string; + [key: string]: any; } export class IsolateSocket extends Client { - public isolateId: number | string = 0; - public async executeSocketCommand(args: ExecuteArgs) { - // call an RPC method with parameters - return this.call(`ext.flutter.driver`, { - ...args, - isolateId: this.isolateId, - }) as Promise<{ - isError: boolean, - response: any, - }>; - } + public isolateId: number | string = 0; + public async executeSocketCommand(args: ExecuteArgs) { + // call an RPC method with parameters + return this.call(`ext.flutter.driver`, { + ...args, + isolateId: this.isolateId, + }) as Promise<{ + isError: boolean; + response: any; + }>; + } } diff --git a/driver/lib/sessions/log-monitor.ts b/driver/lib/sessions/log-monitor.ts index 6f2406d9..e48c1702 100644 --- a/driver/lib/sessions/log-monitor.ts +++ b/driver/lib/sessions/log-monitor.ts @@ -1,8 +1,8 @@ import type {EventEmitter} from 'node:events'; -import { retryInterval } from 'asyncbox'; +import {retryInterval} from 'asyncbox'; export interface LogEntry { timestamp: number; - level: string, + level: string; message: string; } @@ -40,21 +40,17 @@ export class LogMonitor { maxRetryCount: number = DEFAULT_MAX_RETRY_COUNT, retryBackoffTime: number = DEFAULT_BACKOFF_TIME_MS, ): Promise { - return await retryInterval( - maxRetryCount, - retryBackoffTime, - async () => { - if (this._lastMatch !== null) { - return this._lastMatch; - } - throw new Error( - `No matched log found with ${retryBackoffTime} ms interval ` + + return await retryInterval(maxRetryCount, retryBackoffTime, async () => { + if (this._lastMatch !== null) { + return this._lastMatch; + } + throw new Error( + `No matched log found with ${retryBackoffTime} ms interval ` + `up to ${maxRetryCount} times. Increasing appium:retryBackoffTime ` + - `and appium:maxRetryCount would help.` - ); - }, - ); - }; + `and appium:maxRetryCount would help.`, + ); + }); + } start(): this { if (this.started) { diff --git a/driver/lib/sessions/observatory.ts b/driver/lib/sessions/observatory.ts index b760b568..efc14c56 100644 --- a/driver/lib/sessions/observatory.ts +++ b/driver/lib/sessions/observatory.ts @@ -1,10 +1,10 @@ -import { URL } from 'node:url'; +import {URL} from 'node:url'; import _ from 'lodash'; -import type { FlutterDriver } from '../driver'; -import { IsolateSocket } from './isolate_socket'; -import { decode } from './base64url'; -import type { LogEntry } from './log-monitor'; -import { retryInterval } from 'asyncbox'; +import type {FlutterDriver} from '../driver'; +import {IsolateSocket} from './isolate_socket'; +import {decode} from './base64url'; +import type {LogEntry} from './log-monitor'; +import {retryInterval} from 'asyncbox'; const truncateLength = 500; // https://github.com/flutter/flutter/blob/f90b019c68edf4541a4c8273865a2b40c2c01eb3/dev/devicelab/lib/framework/runner.dart#L183 @@ -13,9 +13,9 @@ const truncateLength = 500; // e.g. 'An Observatory debugger and profiler on ${device.device.name} is available at: http://127.0.0.1:52817/_w_SwaKs9-g=/' export const OBSERVATORY_URL_PATTERN = new RegExp( `(Observatory listening on |` + - `An Observatory debugger and profiler on\\s.+\\sis available at: |` + - `The Dart VM service is listening on )` + - `((http|//)[a-zA-Z0-9:/=_\\-.\\[\\]]+)`, + `An Observatory debugger and profiler on\\s.+\\sis available at: |` + + `The Dart VM service is listening on )` + + `((http|//)[a-zA-Z0-9:/=_\\-.\\[\\]]+)`, ); const moduleCheckIntervalCount = 30; @@ -25,7 +25,7 @@ const moduleCheckIntervalMs = 500; export async function connectSocket( this: FlutterDriver, dartObservatoryURL: string, - caps: Record + caps: Record, ): Promise { const isolateId = caps.isolateId; @@ -74,11 +74,13 @@ export async function connectSocket( this.log.info(`Listing the given isolate id: ${isolateId}`); socket.isolateId = isolateId; } else { - const vm = await socket.call(`getVM`) as { - isolates: [{ - name: string, - id: number, - }], + const vm = (await socket.call(`getVM`)) as { + isolates: [ + { + name: string; + id: number; + }, + ]; }; this.log.info(`Listing all isolates: ${JSON.stringify(vm.isolates)}`); // To accept 'main.dart:main()' and 'main' @@ -95,26 +97,26 @@ export async function connectSocket( // It could take time to load the expected module. try { - await retryInterval( - moduleCheckIntervalCount, - moduleCheckIntervalMs, - async () => { - const isolate = await socket.call(`getIsolate`, { - isolateId: `${socket.isolateId}`, - }) as { - extensionRPCs: [string] | null, - } | null; - if (!isolate) { - throw new Error(`Cannot get main Dart Isolate`); - } - if (!Array.isArray(isolate.extensionRPCs)) { - throw new Error(`Cannot get Dart extensionRPCs from isolate ${JSON.stringify(isolate)}`); - } - if (isolate.extensionRPCs.indexOf(`ext.flutter.driver`) < 0) { - throw new Error(`"ext.flutter.driver" is not found in "extensionRPCs" ${JSON.stringify(isolate.extensionRPCs)}`); - } + await retryInterval(moduleCheckIntervalCount, moduleCheckIntervalMs, async () => { + const isolate = (await socket.call(`getIsolate`, { + isolateId: `${socket.isolateId}`, + })) as { + extensionRPCs: [string] | null; + } | null; + if (!isolate) { + throw new Error(`Cannot get main Dart Isolate`); } - ); + if (!Array.isArray(isolate.extensionRPCs)) { + throw new Error( + `Cannot get Dart extensionRPCs from isolate ${JSON.stringify(isolate)}`, + ); + } + if (isolate.extensionRPCs.indexOf(`ext.flutter.driver`) < 0) { + throw new Error( + `"ext.flutter.driver" is not found in "extensionRPCs" ${JSON.stringify(isolate.extensionRPCs)}`, + ); + } + }); } catch (e) { this.log.error(e.message); removeListenerAndResolve(null); @@ -132,29 +134,30 @@ export async function connectSocket( throw new Error( `Cannot connect to the Dart Observatory URL ${dartObservatoryURL}. ` + - `Check the server log for more details` + `Check the server log for more details`, ); } -export async function executeGetIsolateCommand( - this: FlutterDriver, - isolateId: string | number -) { +export async function executeGetIsolateCommand(this: FlutterDriver, isolateId: string | number) { this.log.debug(`>>> getIsolate`); - const isolate = await (this.socket as IsolateSocket).call(`getIsolate`, { isolateId: `${isolateId}` }); - this.log.debug(`<<< ${_.truncate(JSON.stringify(isolate), {'length': truncateLength})}`); + const isolate = await (this.socket as IsolateSocket).call(`getIsolate`, { + isolateId: `${isolateId}`, + }); + this.log.debug(`<<< ${_.truncate(JSON.stringify(isolate), {length: truncateLength})}`); return isolate; } export async function executeGetVMCommand(this: FlutterDriver) { this.log.debug(`>>> getVM`); - const vm = await (this.socket as IsolateSocket).call(`getVM`) as { - isolates: [{ - name: string, - id: number, - }], + const vm = (await (this.socket as IsolateSocket).call(`getVM`)) as { + isolates: [ + { + name: string; + id: number; + }, + ]; }; - this.log.debug(`<<< ${_.truncate(JSON.stringify(vm), {'length': truncateLength})}`); + this.log.debug(`<<< ${_.truncate(JSON.stringify(vm), {length: truncateLength})}`); return vm; } @@ -162,10 +165,10 @@ export async function executeElementCommand( this: FlutterDriver, command: string, elementBase64?: string, - extraArgs = {} + extraArgs = {}, ) { const elementObject = elementBase64 ? JSON.parse(decode(elementBase64)) : {}; - const serializedCommand = { command, ...elementObject, ...extraArgs }; + const serializedCommand = {command, ...elementObject, ...extraArgs}; this.log.debug(`>>> ${JSON.stringify(serializedCommand)}`); const data = await (this.socket as IsolateSocket).executeSocketCommand(serializedCommand); this.log.debug(`<<< ${JSON.stringify(data)} | previous command ${command}`); diff --git a/driver/lib/sessions/session.ts b/driver/lib/sessions/session.ts index 242f203b..6b2951f3 100644 --- a/driver/lib/sessions/session.ts +++ b/driver/lib/sessions/session.ts @@ -1,17 +1,15 @@ -import type { FlutterDriver } from '../driver'; +import type {FlutterDriver} from '../driver'; import _ from 'lodash'; -import { - startAndroidSession, connectAndroidSession -} from './android'; -import { - startIOSSession, connectIOSSession -} from './ios'; -import { PLATFORM } from '../platform'; -import type { XCUITestDriver } from 'appium-xcuitest-driver'; +import {startAndroidSession, connectAndroidSession} from './android'; +import {startIOSSession, connectIOSSession} from './ios'; +import {PLATFORM} from '../platform'; +import type {XCUITestDriver} from 'appium-xcuitest-driver'; import type {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver'; - -export const reConnectFlutterDriver = async function(this: FlutterDriver, caps: Record) { +export const reConnectFlutterDriver = async function ( + this: FlutterDriver, + caps: Record, +) { // setup proxies - if platformName is not empty, make it less case sensitive if (!caps.platformName) { this.log.errorWithException(new Error(`No platformName was given`)); @@ -28,13 +26,18 @@ export const reConnectFlutterDriver = async function(this: FlutterDriver, caps: this.log.errorWithException( new Error( `Unsupported platformName: ${caps.platformName}. ` + - `Only the following platforms are supported: ${_.keys(PLATFORM)}` - ) + `Only the following platforms are supported: ${_.keys(PLATFORM)}`, + ), ); } }; -export const createSession: any = async function(this: FlutterDriver, sessionId: string, caps, ...args) { +export const createSession: any = async function ( + this: FlutterDriver, + sessionId: string, + caps, + ...args +) { try { // setup proxies - if platformName is not empty, make it less case sensitive switch (_.toLower(caps.platformName)) { @@ -47,7 +50,8 @@ export const createSession: any = async function(this: FlutterDriver, sessionId: break; case PLATFORM.ANDROID: [this.proxydriver, this.socket] = await startAndroidSession.bind(this)(caps, ...args); - (this.proxydriver as AndroidUiautomator2Driver).relaxedSecurityEnabled = this.relaxedSecurityEnabled; + (this.proxydriver as AndroidUiautomator2Driver).relaxedSecurityEnabled = + this.relaxedSecurityEnabled; (this.proxydriver as AndroidUiautomator2Driver).denyInsecure = this.denyInsecure; (this.proxydriver as AndroidUiautomator2Driver).allowInsecure = this.allowInsecure; break; @@ -55,8 +59,8 @@ export const createSession: any = async function(this: FlutterDriver, sessionId: this.log.errorWithException( new Error( `Unsupported platformName: ${caps.platformName}. ` + - `Only the following platforms are supported: ${_.keys(PLATFORM)}` - ) + `Only the following platforms are supported: ${_.keys(PLATFORM)}`, + ), ); } diff --git a/driver/package.json b/driver/package.json index 1ba737d2..d22806f5 100644 --- a/driver/package.json +++ b/driver/package.json @@ -53,6 +53,8 @@ "clean": "npm run build -- --clean", "lint": "eslint .", "lint:fix": "npm run lint -- --fix", + "format": "prettier -w ./lib", + "format:check": "prettier --check ./lib", "prepublishOnly": "cp ../README.md ../LICENSE ./", "prepare": "npm run clean && npm run build", "test": "echo no test", @@ -68,6 +70,7 @@ "@semantic-release/changelog": "^6.0.3", "@semantic-release/git": "^10.0.1", "eslint": "^9.17.0", + "prettier": "^3.0.0", "semantic-release": "^25.0.2", "ts-node": "^10.9.1", "typescript": "~5.9" @@ -83,5 +86,10 @@ "lodash": "^4.0.0", "portscanner": "^2.2.0", "rpc-websockets": "^10.0.0" + }, + "prettier": { + "bracketSpacing": false, + "printWidth": 100, + "singleQuote": true } }