From dff3f6b4c0eafec33aef8dbc4286614ec2faacf8 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 15 Feb 2026 23:37:57 -0800 Subject: [PATCH 1/3] fix: format --- .github/workflows/nodejs.yml | 1 + driver/lib/commands/assertions.ts | 54 ++++++---- driver/lib/commands/clipboard.ts | 14 ++- driver/lib/commands/context.ts | 32 ++++-- driver/lib/commands/element.ts | 19 +++- driver/lib/commands/execute.ts | 145 ++++++++++++++++---------- driver/lib/commands/execute/scroll.ts | 65 +++++++++--- driver/lib/commands/execute/wait.ts | 39 +++---- driver/lib/commands/gesture.ts | 25 +++-- driver/lib/commands/screen.ts | 10 +- driver/lib/desired-caps.ts | 13 +-- driver/lib/doctor/checks.js | 23 ++-- driver/lib/driver.ts | 137 +++++++++++++++--------- driver/lib/ios/app.ts | 25 +++-- driver/lib/logger.ts | 4 +- driver/lib/platform.ts | 5 +- driver/lib/sessions/android.ts | 49 +++++---- driver/lib/sessions/base64url.ts | 16 ++- driver/lib/sessions/ios.ts | 104 +++++++++--------- driver/lib/sessions/isolate_socket.ts | 28 ++--- driver/lib/sessions/log-monitor.ts | 34 +++--- driver/lib/sessions/observatory.ts | 92 +++++++++------- driver/lib/sessions/session.ts | 73 ++++++++----- driver/package.json | 3 + 24 files changed, 625 insertions(+), 385 deletions(-) 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..80de0247 100644 --- a/driver/lib/commands/assertions.ts +++ b/driver/lib/commands/assertions.ts @@ -1,6 +1,6 @@ -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 } @@ -12,18 +12,22 @@ export type FinderInput = // Serialize a finder to base64 const serializeFinder = (finder: SerializableFinder): string => - Buffer.from(JSON.stringify(finder)).toString('base64'); + Buffer.from(JSON.stringify(finder)).toString("base64"); // Type guards const isRawFinder = (input: any): input is SerializableFinder => - input && typeof input === 'object' && typeof input.finderType === 'string'; + input && typeof input === "object" && typeof input.finderType === "string"; -const isFlutterElementLike = (input: any): input is { getRawFinder: () => SerializableFinder } => - input && typeof input === 'object' && typeof input.getRawFinder === 'function'; +const isFlutterElementLike = ( + input: any, +): input is { getRawFinder: () => SerializableFinder } => + input && + typeof input === "object" && + typeof input.getRawFinder === "function"; // Convert FinderInput to base64 string function getFinderBase64(input: FinderInput): string { - if (typeof input === 'string') { + if (typeof input === "string") { return input; // already base64 } @@ -35,19 +39,21 @@ function getFinderBase64(input: FinderInput): string { return serializeFinder(input); } - if ('key' in input) { + if ("key" in input) { return byValueKey(input.key); } - if ('text' in input) { + if ("text" in input) { return byText(input.text); } - if ('label' in input) { + if ("label" in input) { 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,13 +62,18 @@ 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}`); + throw new Error( + `Assertion failed on command "${command}" within ${timeout}ms\n${err}`, + ); } } @@ -70,21 +81,20 @@ async function executeAssertion( export const assertVisible = async ( driver: FlutterDriver, input: FinderInput, - timeout = 5000 + timeout = 5000, ): Promise => - await executeAssertion(driver, 'waitFor', input, timeout, { visible: true }); + await executeAssertion(driver, "waitFor", input, timeout, { visible: true }); export const assertNotVisible = async ( driver: FlutterDriver, input: FinderInput, - timeout = 5000 + timeout = 5000, ): Promise => - await executeAssertion(driver, 'waitForAbsent', input, timeout); + await executeAssertion(driver, "waitForAbsent", input, timeout); export const assertTappable = async ( driver: FlutterDriver, input: FinderInput, - timeout = 5000 + timeout = 5000, ): Promise => - await executeAssertion(driver, 'waitForTappable', input, timeout); - + await executeAssertion(driver, "waitForTappable", input, timeout); diff --git a/driver/lib/commands/clipboard.ts b/driver/lib/commands/clipboard.ts index 165b3dc3..03e08929 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,24 @@ 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..4a3d870a 100644 --- a/driver/lib/commands/context.ts +++ b/driver/lib/commands/context.ts @@ -1,19 +1,26 @@ -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 +35,28 @@ 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..9e326c09 100755 --- a/driver/lib/commands/element.ts +++ b/driver/lib/commands/element.ts @@ -1,15 +1,24 @@ -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) { - text = textInput.reduce((previousValue, currentValue) => `${previousValue}${currentValue}`); + text = textInput.reduce( + (previousValue, currentValue) => `${previousValue}${currentValue}`, + ); } else if (typeof textInput === `string`) { text = textInput; } else { @@ -19,6 +28,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..93291d66 100644 --- a/driver/lib/commands/execute.ts +++ b/driver/lib/commands/execute.ts @@ -1,32 +1,26 @@ /* 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 -} from './execute/scroll'; -import { - waitFor, - waitForAbsent, - waitForTappable -} from './execute/wait'; + scrollUntilTapable, +} from "./execute/scroll"; +import { waitFor, waitForAbsent, waitForTappable } from "./execute/wait"; import { assertVisible, assertNotVisible, assertTappable, - type FinderInput -} from './assertions'; + type FinderInput, +} from "./assertions"; -import { launchApp } from './../ios/app'; -import B from 'bluebird'; +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,15 +38,13 @@ interface DiagnosticsOptions { includeProperties?: boolean; } - - interface LongTapOptions { durationMilliseconds: number; frequency?: number; } interface OffsetOptions { - offsetType: 'bottomLeft' | 'bottomRight' | 'center' | 'topLeft' | 'topRight'; + offsetType: "bottomLeft" | "bottomRight" | "center" | "topLeft" | "topRight"; } // Extract command handlers into a separate object for better organization @@ -65,53 +57,79 @@ const commandHandlers: CommandMap = { connectObservatoryWsUrl: async (driver) => { await reConnectFlutterDriver.bind(driver)(driver.internalCaps); }, - checkHealth: async (driver) => (await driver.executeElementCommand('get_health')).status, + checkHealth: async (driver) => + (await driver.executeElementCommand("get_health")).status, getVMInfo: async (driver) => await driver.executeGetVMCommand(), - getRenderTree: async (driver) => (await driver.executeElementCommand('get_render_tree')).tree, + getRenderTree: async (driver) => + (await driver.executeElementCommand("get_render_tree")).tree, getOffset: async (driver, elementBase64: string, options: OffsetOptions) => - await driver.executeElementCommand('get_offset', elementBase64, options), + 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 }; - if (response.type !== 'Success') { - throw new Error(`Could not forceGC, response was ${JSON.stringify(response)}`); + })) 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!), + await driver.executeGetIsolateCommand( + isolateId || driver.socket!.isolateId!, + ), clearTimeline: async (driver) => { - const call1 = driver.socket!.call('_clearVMTimeline'); - const call2 = driver.socket!.call('clearVMTimeline'); + const call1 = driver.socket!.call("_clearVMTimeline"); + const call2 = driver.socket!.call("clearVMTimeline"); const response = await B.any([call1, call2]); - if (response.type !== 'Success') { - throw new Error(`Could not clear timeline, response was ${JSON.stringify(response)}`); + if (response.type !== "Success") { + throw new Error( + `Could not clear timeline, response was ${JSON.stringify(response)}`, + ); } }, - getRenderObjectDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => { + getRenderObjectDiagnostics: async ( + driver, + elementBase64: string, + opts: DiagnosticsOptions = {}, + ) => { const { subtreeDepth = 0, includeProperties = true } = opts; - return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, { - diagnosticsType: 'renderObject', - includeProperties, - subtreeDepth, - }); + return await driver.executeElementCommand( + "get_diagnostics_tree", + elementBase64, + { + diagnosticsType: "renderObject", + includeProperties, + subtreeDepth, + }, + ); }, - getWidgetDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => { + getWidgetDiagnostics: async ( + driver, + elementBase64: string, + opts: DiagnosticsOptions = {}, + ) => { const { subtreeDepth = 0, includeProperties = true } = opts; - return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, { - diagnosticsType: 'widget', - includeProperties, - subtreeDepth, - }); + return await driver.executeElementCommand( + "get_diagnostics_tree", + elementBase64, + { + diagnosticsType: "widget", + includeProperties, + subtreeDepth, + }, + ); }, getSemanticsId: async (driver, elementBase64: string) => - (await driver.executeElementCommand('get_semantics_id', elementBase64)).id, + (await driver.executeElementCommand("get_semantics_id", elementBase64)).id, waitForAbsent: async (driver, finder: string, timeout?: number) => await waitForAbsent(driver, finder, timeout), waitFor: async (driver, finder: string, timeout?: number) => @@ -127,28 +145,44 @@ 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' }), - setFrameSync: async (driver, enabled: boolean, durationMilliseconds: number) => + await driver.executeElementCommand("waitForCondition", "", { + conditionName: "FirstFrameRasterizedCondition", + }), + setFrameSync: async ( + driver, + enabled: boolean, + durationMilliseconds: number, + ) => await driver.socket!.executeSocketCommand({ - command: 'set_frame_sync', + command: "set_frame_sync", enabled, timeout: durationMilliseconds, }), - clickElement: async (driver, finder: string, opts: { timeout?: number } = {}) => { + clickElement: async ( + driver, + finder: string, + opts: { timeout?: number } = {}, + ) => { const { timeout = 1000 } = opts; - return await driver.executeElementCommand('tap', finder, { timeout }); + return await driver.executeElementCommand("tap", finder, { timeout }); }, dragAndDropWithCommandExtension: async (driver, params: DragAndDropParams) => await driver.socket!.executeSocketCommand({ - command: 'dragAndDropWithCommandExtension', + command: "dragAndDropWithCommandExtension", ...params, }), assertVisible: async (driver, input: FinderInput, timeout = 5000) => @@ -159,7 +193,7 @@ const commandHandlers: CommandMap = { await assertTappable(driver, input, timeout), getTextWithCommandExtension: async (driver, params: { findBy: string }) => await driver.socket!.executeSocketCommand({ - command: 'getTextWithCommandExtension', + command: "getTextWithCommandExtension", findBy: params.findBy, }), }; @@ -182,4 +216,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..208c8794 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 _ from "lodash"; +import { FlutterDriver } from "../../driver"; +import { waitFor, waitForTappable } from "./wait"; export const scroll = async ( self: FlutterDriver, @@ -50,11 +49,14 @@ export const longTap = async ( ) => { const { durationMilliseconds = 1000, frequency = 60 } = options; - if (typeof durationMilliseconds !== 'number' || typeof frequency !== 'number') { + if ( + typeof durationMilliseconds !== "number" || + typeof frequency !== "number" + ) { throw new Error(`Invalid longTap options: ${JSON.stringify(options)}`); } - return await self.executeElementCommand('scroll', elementBase64, { + return await self.executeElementCommand("scroll", elementBase64, { dx: 0, dy: 0, duration: durationMilliseconds * 1000, @@ -78,7 +80,10 @@ const validateOps = (alignment: any, dxScroll: any, dyScroll: any): boolean => { return true; }; -const shouldRetry = (startAt: number, waitTimeoutMilliseconds?: number): boolean => { +const shouldRetry = ( + startAt: number, + waitTimeoutMilliseconds?: number, +): boolean => { if (!waitTimeoutMilliseconds) { // Then, the scroll should continue infinitely return true; @@ -100,7 +105,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,9 +134,11 @@ 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) { @@ -146,7 +161,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,9 +193,11 @@ 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) { @@ -191,12 +216,20 @@ export const scrollIntoView = async ( }, ) => { const { alignment = 0.0, timeout } = opts; - if (typeof alignment !== `number` || (typeof timeout !== `undefined` && typeof timeout !== `number`)) { + 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); + 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..d1c17dda 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..a33692c1 100755 --- a/driver/lib/commands/gesture.ts +++ b/driver/lib/commands/gesture.ts @@ -1,12 +1,12 @@ -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( +export const tapEl = async function ( this: FlutterDriver, el: string, longPress: boolean, @@ -17,7 +17,7 @@ export const tapEl = async function( return await this.executeElementCommand(commandName, el); }; -export const tap = async function( +export const tap = async function ( this: FlutterDriver, gestures: Record[], longPress: boolean, @@ -28,7 +28,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 +36,20 @@ 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); diff --git a/driver/lib/commands/screen.ts b/driver/lib/commands/screen.ts index bc7a5e82..84d98437 100644 --- a/driver/lib/commands/screen.ts +++ b/driver/lib/commands/screen.ts @@ -1,7 +1,9 @@ -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..51f4449e 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..69e0b568 100644 --- a/driver/lib/doctor/checks.js +++ b/driver/lib/doctor/checks.js @@ -1,8 +1,10 @@ -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); +export const homeEnvVarCheck = /** @type {any} */ ( + iosDoctor.required.homeEnvVarCheck +); let androidHomeCheck; let javaHomeCheck; @@ -27,13 +29,22 @@ if (!process.env.SKIP_IOS) { xcodeCheck = iosDoctor.required.xcodeCheck; xcodeToolsCheck = iosDoctor.required.xcodeToolsCheck; envVarAndPathCheck = /** @type {any} */ (iosDoctor.required.homeEnvVarCheck); - optionalApplesimutilsCommandCheck = iosDoctor.optional.optionalApplesimutilsCheck; + optionalApplesimutilsCommandCheck = + iosDoctor.optional.optionalApplesimutilsCheck; } // shared 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..af2f504d 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 _ from "lodash"; +import { BaseDriver } from "appium/driver"; +import { log as logger } from "./logger"; import { - executeElementCommand, executeGetVMCommand, executeGetIsolateCommand -} from './sessions/observatory'; -import { PLATFORM } from './platform'; -import { createSession, reConnectFlutterDriver } from './sessions/session'; + executeElementCommand, + executeGetVMCommand, + executeGetIsolateCommand, +} from "./sessions/observatory"; +import { PLATFORM } from "./platform"; +import { createSession, reConnectFlutterDriver } from "./sessions/session"; import { - 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'; + 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 type { DefaultCreateSessionResult, DriverCaps, DriverData, - W3CDriverCaps, + W3CDriverCaps, RouteMatcher, - Orientation -} from '@appium/types'; -import type { IsolateSocket } from './sessions/isolate_socket'; -import type { Server } from 'node:net'; -import type { LogMonitor } from './sessions/log-monitor'; - + Orientation, +} from "@appium/types"; +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 @@ -46,7 +51,7 @@ const WEBVIEW_NO_PROXY = [ [`POST`, new RegExp(`^/session/[^/]+/orientation`)], [`POST`, new RegExp(`^/session/[^/]+/touch/multi/perform`)], [`POST`, new RegExp(`^/session/[^/]+/touch/perform`)], -] as import('@appium/types').RouteMatcher[]; +] as import("@appium/types").RouteMatcher[]; class FlutterDriver extends BaseDriver { public socket: IsolateSocket | null; @@ -112,12 +117,23 @@ 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))); + return createSession.bind(this)( + sessionId, + caps, + ...JSON.parse(JSON.stringify(args)), + ); } public async deleteSession() { @@ -125,9 +141,9 @@ class FlutterDriver extends BaseDriver { this._logmon?.stop(); this._logmon = null; - this.proxydriver?.eventEmitter?.removeAllListeners('syslogStarted'); + this.proxydriver?.eventEmitter?.removeAllListeners("syslogStarted"); - this.log.info('Cleanup the port forward'); + this.log.info("Cleanup the port forward"); switch (_.toLower(this.internalCaps.platformName)) { case PLATFORM.IOS: this.localServer?.close(); @@ -136,14 +152,16 @@ 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.'); + this.log.info("Deleting the proxy driver session."); try { await this.proxydriver.deleteSession(this.sessionId || undefined); } catch (e) { @@ -182,19 +200,30 @@ class FlutterDriver extends BaseDriver { } switch (_.toLower(this.internalCaps.platformName)) { case PLATFORM.IOS: - return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'GET'); + return await (this.proxydriver as XCUITestDriver).proxyCommand( + "/orientation", + "GET", + ); default: - return await (this.proxydriver as AndroidUiautomator2Driver).getOrientation(); - } + 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 +234,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 +247,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]; 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])) { @@ -262,7 +298,9 @@ class FlutterDriver extends BaseDriver { } public getProxyAvoidList(): RouteMatcher[] { - if ([FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(this.currentContext)) { + if ( + [FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(this.currentContext) + ) { return []; } @@ -275,7 +313,10 @@ class FlutterDriver extends BaseDriver { // On iOS, WebView context is handled by XCUITest driver while Android is by chromedriver. // It means XCUITest driver should keep the XCUITest driver as a proxy, // while UIAutomator2 driver should proxy to chromedriver instead of UIA2 proxy. - return this.proxyWebViewActive && this.proxydriver?.constructor.name !== XCUITestDriver.name; + return ( + this.proxyWebViewActive && + this.proxydriver?.constructor.name !== XCUITestDriver.name + ); } public canProxy(): boolean { diff --git a/driver/lib/ios/app.ts b/driver/lib/ios/app.ts index 6fa15b25..d5481d67 100644 --- a/driver/lib/ios/app.ts +++ b/driver/lib/ios/app.ts @@ -1,26 +1,35 @@ -import {services, INSTRUMENT_CHANNEL} from 'appium-ios-device'; -import { log } from './../logger'; +import { services, INSTRUMENT_CHANNEL } from "appium-ios-device"; +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:', - '', + "launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:", + "", bundleId, env, args, - {'StartSuspendedKey': 0, 'KillExisting': 1} + { StartSuspendedKey: 0, KillExisting: 1 }, ); return true; } catch (err) { - log.warn(`Failed to launch '${bundleId}'. Original error: ${err.stderr || err.message}`); + log.warn( + `Failed to launch '${bundleId}'. Original error: ${err.stderr || err.message}`, + ); return false; } finally { if (instrumentService) { diff --git a/driver/lib/logger.ts b/driver/lib/logger.ts index 1c17ef3f..421abb29 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..437fbd42 100644 --- a/driver/lib/platform.ts +++ b/driver/lib/platform.ts @@ -1,5 +1,4 @@ export const PLATFORM = { - IOS: 'ios', - ANDROID: 'android', + IOS: "ios", + ANDROID: "android", } as const; - diff --git a/driver/lib/sessions/android.ts b/driver/lib/sessions/android.ts index 3b611261..bd298ce9 100644 --- a/driver/lib/sessions/android.ts +++ b/driver/lib/sessions/android.ts @@ -1,10 +1,14 @@ -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, @@ -14,7 +18,7 @@ export async function startAndroidSession( this.log.info(`Starting an Android proxy session`); const androiddriver = new AndroidUiautomator2Driver({} as InitialOpts); if (!caps.observatoryWsUri) { - androiddriver.eventEmitter.once('syslogStarted', (syslog) => { + androiddriver.eventEmitter.once("syslogStarted", (syslog) => { this._logmon = new LogMonitor(syslog, async (entry: LogEntry) => { if (extractObservatoryUrl(entry)) { this.log.debug(`Matched the syslog line '${entry.message}'`); @@ -39,21 +43,25 @@ export async function startAndroidSession( ]; } -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); + 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 +82,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 +98,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; @@ -99,7 +107,10 @@ export async function getObservatoryWsUri ( const remotePort = urlObject.port; this.portForwardLocalPort = caps.forwardingPort ?? remotePort; urlObject.port = this.portForwardLocalPort as string; - await proxydriver.adb.forwardPort(this.portForwardLocalPort as string, remotePort); + await proxydriver.adb.forwardPort( + this.portForwardLocalPort as string, + remotePort, + ); if (!caps.observatoryWsUri && proxydriver.adb.adbHost) { urlObject.host = proxydriver.adb.adbHost; } diff --git a/driver/lib/sessions/base64url.ts b/driver/lib/sessions/base64url.ts index 9bc083ad..888914f5 100644 --- a/driver/lib/sessions/base64url.ts +++ b/driver/lib/sessions/base64url.ts @@ -1,19 +1,25 @@ -import _ from 'lodash'; -import { util } from '@appium/support'; +import _ from "lodash"; +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; } else if (_.has(input, util.W3C_WEB_ELEMENT_IDENTIFIER)) { base64String = input[util.W3C_WEB_ELEMENT_IDENTIFIER] as string; - } else if (_.has(input, 'ELEMENT')) { + } else if (_.has(input, "ELEMENT")) { // @ts-ignore TS2339 base64String = input.ELEMENT as string; } 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..d72bb785 100644 --- a/driver/lib/sessions/ios.ts +++ b/driver/lib/sessions/ios.ts @@ -1,29 +1,30 @@ -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 { 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'; + 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); if (!caps.observatoryWsUri) { - iosdriver.eventEmitter.once('syslogStarted', (syslog) => { + iosdriver.eventEmitter.once("syslogStarted", (syslog) => { this._logmon = new LogMonitor(syslog, async (entry: LogEntry) => { if (extractObservatoryUrl(entry)) { this.log.debug(`Matched the syslog line '${entry.message}'`); @@ -42,52 +43,57 @@ 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); + 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.`); + 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.`, + ); } -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 +114,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 +130,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; @@ -159,14 +165,14 @@ export async function getObservatoryWsUri ( destroyCommChannel(); localSocket.destroy(); }); - remoteSocket.on('error', (e) => this.log.debug(e)); + remoteSocket.on("error", (e) => this.log.debug(e)); localSocket.once(`end`, destroyCommChannel); localSocket.once(`close`, () => { destroyCommChannel(); remoteSocket.destroy(); }); - localSocket.on('error', (e) => this.log.warn(e.message)); + localSocket.on("error", (e) => this.log.warn(e.message)); localSocket.pipe(remoteSocket); remoteSocket.pipe(localSocket); }); @@ -179,10 +185,14 @@ export async function getObservatoryWsUri ( await listeningPromise; } catch (e) { this.localServer = null; - throw new Error(`Cannot listen on the local port ${localPort}. Original error: ${e.message}`); + throw new Error( + `Cannot listen on the local port ${localPort}. Original error: ${e.message}`, + ); } - this.log.info(`Forwarding the remote port ${remotePort} to the local port ${localPort}`); + this.log.info( + `Forwarding the remote port ${remotePort} to the local port ${localPort}`, + ); process.on(`beforeExit`, () => { this.localServer?.close(); diff --git a/driver/lib/sessions/isolate_socket.ts b/driver/lib/sessions/isolate_socket.ts index 4fad28a2..db1372d8 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..5e6c3f6a 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 type { EventEmitter } from "node:events"; +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) { @@ -63,7 +59,7 @@ export class LogMonitor { this._outputListener = this._onOutput.bind(this); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._logsEmitter.on('output', this._outputListener!); + this._logsEmitter.on("output", this._outputListener!); return this; } @@ -73,7 +69,7 @@ export class LogMonitor { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._logsEmitter.off('output', this._outputListener!); + this._logsEmitter.off("output", this._outputListener!); this._outputListener = null; return this; } diff --git a/driver/lib/sessions/observatory.ts b/driver/lib/sessions/observatory.ts index b760b568..3ea284b7 100644 --- a/driver/lib/sessions/observatory.ts +++ b/driver/lib/sessions/observatory.ts @@ -1,10 +1,10 @@ -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 { 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"; 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; @@ -43,7 +43,9 @@ export async function connectSocket( // Add an 'error' event handler for the client socket const onErrorListener = (ex: Error) => { - this.log.error(`Connection to ${dartObservatoryURL} got an error: ${ex.message}`); + this.log.error( + `Connection to ${dartObservatoryURL} got an error: ${ex.message}`, + ); removeListenerAndResolve(null); }; socket.on(`error`, onErrorListener); @@ -74,15 +76,19 @@ 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' - const mainIsolateData = vm.isolates.find((e) => e.name.includes(`main`)); + const mainIsolateData = vm.isolates.find((e) => + e.name.includes(`main`), + ); if (!mainIsolateData) { this.log.error(`Cannot get Dart main isolate info`); removeListenerAndResolve(null); @@ -99,21 +105,25 @@ export async function connectSocket( moduleCheckIntervalCount, moduleCheckIntervalMs, async () => { - const isolate = await socket.call(`getIsolate`, { + const isolate = (await socket.call(`getIsolate`, { isolateId: `${socket.isolateId}`, - }) as { - extensionRPCs: [string] | null, + })) 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)}`); + 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)}`); + throw new Error( + `"ext.flutter.driver" is not found in "extensionRPCs" ${JSON.stringify(isolate.extensionRPCs)}`, + ); } - } + }, ); } catch (e) { this.log.error(e.message); @@ -132,29 +142,37 @@ 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 + 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,12 +180,14 @@ export async function executeElementCommand( this: FlutterDriver, command: string, elementBase64?: string, - extraArgs = {} + extraArgs = {}, ) { const elementObject = elementBase64 ? JSON.parse(decode(elementBase64)) : {}; const serializedCommand = { command, ...elementObject, ...extraArgs }; this.log.debug(`>>> ${JSON.stringify(serializedCommand)}`); - const data = await (this.socket as IsolateSocket).executeSocketCommand(serializedCommand); + const data = await (this.socket as IsolateSocket).executeSocketCommand( + serializedCommand, + ); this.log.debug(`<<< ${JSON.stringify(data)} | previous command ${command}`); if (data.isError) { throw new Error( diff --git a/driver/lib/sessions/session.ts b/driver/lib/sessions/session.ts index 242f203b..916d372b 100644 --- a/driver/lib/sessions/session.ts +++ b/driver/lib/sessions/session.ts @@ -1,17 +1,15 @@ -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 type {AndroidUiautomator2Driver} from 'appium-uiautomator2-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 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`)); @@ -19,44 +17,67 @@ export const reConnectFlutterDriver = async function(this: FlutterDriver, caps: switch (_.toLower(caps.platformName)) { case PLATFORM.IOS: - this.socket = await connectIOSSession.bind(this)(this.proxydriver, caps, true); + this.socket = await connectIOSSession.bind(this)( + this.proxydriver, + caps, + true, + ); break; case PLATFORM.ANDROID: - this.socket = await connectAndroidSession.bind(this)(this.proxydriver, caps, true); + this.socket = await connectAndroidSession.bind(this)( + this.proxydriver, + caps, + true, + ); break; default: 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)) { case PLATFORM.IOS: - [this.proxydriver, this.socket] = await startIOSSession.bind(this)(caps, ...args); - (this.proxydriver as XCUITestDriver).relaxedSecurityEnabled = this.relaxedSecurityEnabled; + [this.proxydriver, this.socket] = await startIOSSession.bind(this)( + caps, + ...args, + ); + (this.proxydriver as XCUITestDriver).relaxedSecurityEnabled = + this.relaxedSecurityEnabled; (this.proxydriver as XCUITestDriver).denyInsecure = this.denyInsecure; (this.proxydriver as XCUITestDriver).allowInsecure = this.allowInsecure; 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).denyInsecure = this.denyInsecure; - (this.proxydriver as AndroidUiautomator2Driver).allowInsecure = this.allowInsecure; + [this.proxydriver, this.socket] = await startAndroidSession.bind(this)( + caps, + ...args, + ); + (this.proxydriver as AndroidUiautomator2Driver).relaxedSecurityEnabled = + this.relaxedSecurityEnabled; + (this.proxydriver as AndroidUiautomator2Driver).denyInsecure = + this.denyInsecure; + (this.proxydriver as AndroidUiautomator2Driver).allowInsecure = + this.allowInsecure; break; default: 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..75a53a26 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" From cd628347b17b0f2fa1af4d5de134a72648ba1dda Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Sun, 15 Feb 2026 23:38:48 -0800 Subject: [PATCH 2/3] modify again --- driver/lib/commands/assertions.ts | 49 ++++----- driver/lib/commands/clipboard.ts | 7 +- driver/lib/commands/context.ts | 22 ++-- driver/lib/commands/element.ts | 11 +- driver/lib/commands/execute.ts | 142 ++++++++++---------------- driver/lib/commands/execute/scroll.ts | 37 +++---- driver/lib/commands/execute/wait.ts | 2 +- driver/lib/commands/gesture.ts | 20 +--- driver/lib/commands/screen.ts | 8 +- driver/lib/desired-caps.ts | 2 +- driver/lib/doctor/checks.js | 11 +- driver/lib/driver.ts | 96 ++++++++--------- driver/lib/ios/app.ts | 14 ++- driver/lib/logger.ts | 4 +- driver/lib/platform.ts | 4 +- driver/lib/sessions/android.ts | 36 ++----- driver/lib/sessions/base64url.ts | 11 +- driver/lib/sessions/ios.ts | 56 ++++------ driver/lib/sessions/isolate_socket.ts | 2 +- driver/lib/sessions/log-monitor.ts | 8 +- driver/lib/sessions/observatory.ts | 85 ++++++--------- driver/lib/sessions/session.ts | 45 +++----- driver/package.json | 5 + 23 files changed, 253 insertions(+), 424 deletions(-) diff --git a/driver/lib/commands/assertions.ts b/driver/lib/commands/assertions.ts index 80de0247..cfb91782 100644 --- a/driver/lib/commands/assertions.ts +++ b/driver/lib/commands/assertions.ts @@ -1,33 +1,29 @@ -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 => - Buffer.from(JSON.stringify(finder)).toString("base64"); + Buffer.from(JSON.stringify(finder)).toString('base64'); // Type guards const isRawFinder = (input: any): input is SerializableFinder => - input && typeof input === "object" && typeof input.finderType === "string"; + input && typeof input === 'object' && typeof input.finderType === 'string'; -const isFlutterElementLike = ( - input: any, -): input is { getRawFinder: () => SerializableFinder } => - input && - typeof input === "object" && - typeof input.getRawFinder === "function"; +const isFlutterElementLike = (input: any): input is {getRawFinder: () => SerializableFinder} => + input && typeof input === 'object' && typeof input.getRawFinder === 'function'; // Convert FinderInput to base64 string function getFinderBase64(input: FinderInput): string { - if (typeof input === "string") { + if (typeof input === 'string') { return input; // already base64 } @@ -39,20 +35,20 @@ function getFinderBase64(input: FinderInput): string { return serializeFinder(input); } - if ("key" in input) { + if ('key' in input) { return byValueKey(input.key); } - if ("text" in input) { + if ('text' in input) { return byText(input.text); } - if ("label" in input) { + if ('label' in input) { return byTooltip(input.label); } throw new Error( - "Invalid finder input: must provide key, text, label, raw finder, or FlutterElement", + 'Invalid finder input: must provide key, text, label, raw finder, or FlutterElement', ); } @@ -71,9 +67,7 @@ async function executeAssertion( ...extraArgs, }); } catch (err) { - throw new Error( - `Assertion failed on command "${command}" within ${timeout}ms\n${err}`, - ); + throw new Error(`Assertion failed on command "${command}" within ${timeout}ms\n${err}`); } } @@ -82,19 +76,16 @@ export const assertVisible = async ( driver: FlutterDriver, input: FinderInput, timeout = 5000, -): Promise => - await executeAssertion(driver, "waitFor", input, timeout, { visible: true }); +): 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); +): Promise => await executeAssertion(driver, 'waitForAbsent', input, timeout); export const assertTappable = async ( driver: FlutterDriver, input: FinderInput, timeout = 5000, -): Promise => - await executeAssertion(driver, "waitForTappable", input, timeout); +): Promise => await executeAssertion(driver, 'waitForTappable', input, timeout); diff --git a/driver/lib/commands/clipboard.ts b/driver/lib/commands/clipboard.ts index 03e08929..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 @@ -20,10 +20,7 @@ export const setClipboard = async function ( * @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 4a3d870a..b01209db 100644 --- a/driver/lib/commands/context.ts +++ b/driver/lib/commands/context.ts @@ -1,19 +1,14 @@ -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 @@ -41,9 +36,7 @@ export const setContext = async function ( } }; -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) { @@ -53,10 +46,7 @@ export const getContexts = async function ( } }; -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 9e326c09..144b6bcc 100755 --- a/driver/lib/commands/element.ts +++ b/driver/lib/commands/element.ts @@ -1,9 +1,6 @@ -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; }; @@ -16,9 +13,7 @@ export const setValue = async function ( const clickPromise = this.click(el); // acquire focus let text = ``; if (textInput instanceof Array) { - text = textInput.reduce( - (previousValue, currentValue) => `${previousValue}${currentValue}`, - ); + text = textInput.reduce((previousValue, currentValue) => `${previousValue}${currentValue}`); } else if (typeof textInput === `string`) { text = textInput; } else { diff --git a/driver/lib/commands/execute.ts b/driver/lib/commands/execute.ts index 93291d66..50dfefdf 100644 --- a/driver/lib/commands/execute.ts +++ b/driver/lib/commands/execute.ts @@ -1,23 +1,18 @@ /* 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, -} from "./execute/scroll"; -import { waitFor, waitForAbsent, waitForTappable } from "./execute/wait"; -import { - assertVisible, - assertNotVisible, - assertTappable, - type FinderInput, -} from "./assertions"; +} from './execute/scroll'; +import {waitFor, waitForAbsent, waitForTappable} from './execute/wait'; +import {assertVisible, assertNotVisible, assertTappable, type FinderInput} from './assertions'; -import { launchApp } from "./../ios/app"; -import B from "bluebird"; +import {launchApp} from './../ios/app'; +import B from 'bluebird'; const flutterCommandRegex = /^[\s]*flutter[\s]*:(.+)/; @@ -44,56 +39,48 @@ interface LongTapOptions { } interface OffsetOptions { - offsetType: "bottomLeft" | "bottomRight" | "center" | "topLeft" | "topRight"; + offsetType: 'bottomLeft' | 'bottomRight' | 'center' | 'topLeft' | 'topRight'; } // 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); }, connectObservatoryWsUrl: async (driver) => { await reConnectFlutterDriver.bind(driver)(driver.internalCaps); }, - checkHealth: async (driver) => - (await driver.executeElementCommand("get_health")).status, + checkHealth: async (driver) => (await driver.executeElementCommand('get_health')).status, getVMInfo: async (driver) => await driver.executeGetVMCommand(), - getRenderTree: async (driver) => - (await driver.executeElementCommand("get_render_tree")).tree, + getRenderTree: async (driver) => (await driver.executeElementCommand('get_render_tree')).tree, getOffset: async (driver, elementBase64: string, options: OffsetOptions) => - await driver.executeElementCommand("get_offset", elementBase64, options), + await driver.executeElementCommand('get_offset', elementBase64, options), waitForCondition: async (driver, conditionName: string) => - await driver.executeElementCommand("waitForCondition", "", { + 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 }; - if (response.type !== "Success") { - throw new Error( - `Could not forceGC, response was ${JSON.stringify(response)}`, - ); + })) 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!, - ), + await driver.executeGetIsolateCommand(isolateId || driver.socket!.isolateId!), clearTimeline: async (driver) => { - const call1 = driver.socket!.call("_clearVMTimeline"); - const call2 = driver.socket!.call("clearVMTimeline"); + const call1 = driver.socket!.call('_clearVMTimeline'); + const call2 = driver.socket!.call('clearVMTimeline'); const response = await B.any([call1, call2]); - if (response.type !== "Success") { - throw new Error( - `Could not clear timeline, response was ${JSON.stringify(response)}`, - ); + if (response.type !== 'Success') { + throw new Error(`Could not clear timeline, response was ${JSON.stringify(response)}`); } }, getRenderObjectDiagnostics: async ( @@ -101,43 +88,30 @@ const commandHandlers: CommandMap = { elementBase64: string, opts: DiagnosticsOptions = {}, ) => { - const { subtreeDepth = 0, includeProperties = true } = opts; - return await driver.executeElementCommand( - "get_diagnostics_tree", - elementBase64, - { - diagnosticsType: "renderObject", - includeProperties, - subtreeDepth, - }, - ); + const {subtreeDepth = 0, includeProperties = true} = opts; + return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, { + diagnosticsType: 'renderObject', + includeProperties, + subtreeDepth, + }); }, - getWidgetDiagnostics: async ( - driver, - elementBase64: string, - opts: DiagnosticsOptions = {}, - ) => { - const { subtreeDepth = 0, includeProperties = true } = opts; - return await driver.executeElementCommand( - "get_diagnostics_tree", - elementBase64, - { - diagnosticsType: "widget", - includeProperties, - subtreeDepth, - }, - ); + getWidgetDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => { + const {subtreeDepth = 0, includeProperties = true} = opts; + return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, { + diagnosticsType: 'widget', + includeProperties, + subtreeDepth, + }); }, getSemanticsId: async (driver, elementBase64: string) => - (await driver.executeElementCommand("get_semantics_id", elementBase64)).id, + (await driver.executeElementCommand('get_semantics_id', elementBase64)).id, waitForAbsent: async (driver, finder: string, timeout?: number) => await waitForAbsent(driver, finder, timeout), waitFor: async (driver, finder: string, timeout?: number) => 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) => @@ -146,43 +120,35 @@ const commandHandlers: CommandMap = { await scrollIntoView(driver, finder, opts), setTextEntryEmulation: async (driver, enabled: boolean) => await driver.socket!.executeSocketCommand({ - command: "set_text_entry_emulation", + 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", + 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, - ) => + setFrameSync: async (driver, enabled: boolean, durationMilliseconds: number) => await driver.socket!.executeSocketCommand({ - command: "set_frame_sync", + 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({ - command: "dragAndDropWithCommandExtension", + command: 'dragAndDropWithCommandExtension', ...params, }), assertVisible: async (driver, input: FinderInput, timeout = 5000) => @@ -191,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", + 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}"`); diff --git a/driver/lib/commands/execute/scroll.ts b/driver/lib/commands/execute/scroll.ts index 208c8794..10bd79c5 100644 --- a/driver/lib/commands/execute/scroll.ts +++ b/driver/lib/commands/execute/scroll.ts @@ -1,6 +1,6 @@ -import _ from "lodash"; -import { FlutterDriver } from "../../driver"; -import { waitFor, waitForTappable } from "./wait"; +import _ from 'lodash'; +import {FlutterDriver} from '../../driver'; +import {waitFor, waitForTappable} from './wait'; export const scroll = async ( self: FlutterDriver, @@ -12,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` || @@ -47,16 +47,13 @@ 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" - ) { + if (typeof durationMilliseconds !== 'number' || typeof frequency !== 'number') { throw new Error(`Invalid longTap options: ${JSON.stringify(options)}`); } - return await self.executeElementCommand("scroll", elementBase64, { + return await self.executeElementCommand('scroll', elementBase64, { dx: 0, dy: 0, duration: durationMilliseconds * 1000, @@ -80,10 +77,7 @@ const validateOps = (alignment: any, dxScroll: any, dyScroll: any): boolean => { return true; }; -const shouldRetry = ( - startAt: number, - waitTimeoutMilliseconds?: number, -): boolean => { +const shouldRetry = (startAt: number, waitTimeoutMilliseconds?: number): boolean => { if (!waitTimeoutMilliseconds) { // Then, the scroll should continue infinitely return true; @@ -145,7 +139,7 @@ export const scrollUntilVisible = async ( throw new Error(`Stop scrolling as timeout ${waitTimeoutMilliseconds}`); } - return scrollIntoView(self, item, { alignment }); + return scrollIntoView(self, item, {alignment}); }; export const scrollUntilTapable = async ( @@ -204,7 +198,7 @@ export const scrollUntilTapable = async ( throw new Error(`Stop scrolling as timeout ${waitTimeoutMilliseconds}`); } - return scrollIntoView(self, item, { alignment }); + return scrollIntoView(self, item, {alignment}); }; export const scrollIntoView = async ( @@ -215,7 +209,7 @@ export const scrollIntoView = async ( timeout?: number; }, ) => { - const { alignment = 0.0, timeout } = opts; + const {alignment = 0.0, timeout} = opts; if ( typeof alignment !== `number` || (typeof timeout !== `undefined` && typeof timeout !== `number`) @@ -224,12 +218,7 @@ export const scrollIntoView = async ( 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, - ); + return await self.executeElementCommand(`scrollIntoView`, elementBase64, args); }; diff --git a/driver/lib/commands/execute/wait.ts b/driver/lib/commands/execute/wait.ts index d1c17dda..e22c4dca 100644 --- a/driver/lib/commands/execute/wait.ts +++ b/driver/lib/commands/execute/wait.ts @@ -1,4 +1,4 @@ -import { FlutterDriver } from "../../driver"; +import {FlutterDriver} from '../../driver'; const waitForConstructor = (command: `waitForAbsent` | `waitFor` | `waitForTappable`) => diff --git a/driver/lib/commands/gesture.ts b/driver/lib/commands/gesture.ts index a33692c1..043e6c7a 100755 --- a/driver/lib/commands/gesture.ts +++ b/driver/lib/commands/gesture.ts @@ -1,16 +1,12 @@ -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) { 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`; @@ -42,10 +38,7 @@ export const longTap = async function ( }); }; -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` && @@ -57,10 +50,7 @@ export const performTouch = async function ( } 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 84d98437..4725052a 100644 --- a/driver/lib/commands/screen.ts +++ b/driver/lib/commands/screen.ts @@ -1,9 +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; + 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 51f4449e..3bdf572e 100755 --- a/driver/lib/desired-caps.ts +++ b/driver/lib/desired-caps.ts @@ -9,7 +9,7 @@ export const desiredCapConstraints = { isNumber: true, }, platformName: { - inclusionCaseInsensitive: ["iOS", "Android"], + inclusionCaseInsensitive: ['iOS', 'Android'], isString: true, presence: true, }, diff --git a/driver/lib/doctor/checks.js b/driver/lib/doctor/checks.js index 69e0b568..c44f76bc 100644 --- a/driver/lib/doctor/checks.js +++ b/driver/lib/doctor/checks.js @@ -1,10 +1,8 @@ -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 -); +export const homeEnvVarCheck = /** @type {any} */ (iosDoctor.required.homeEnvVarCheck); let androidHomeCheck; let javaHomeCheck; @@ -29,8 +27,7 @@ if (!process.env.SKIP_IOS) { xcodeCheck = iosDoctor.required.xcodeCheck; xcodeToolsCheck = iosDoctor.required.xcodeToolsCheck; envVarAndPathCheck = /** @type {any} */ (iosDoctor.required.homeEnvVarCheck); - optionalApplesimutilsCommandCheck = - iosDoctor.optional.optionalApplesimutilsCheck; + optionalApplesimutilsCommandCheck = iosDoctor.optional.optionalApplesimutilsCheck; } // shared diff --git a/driver/lib/driver.ts b/driver/lib/driver.ts index af2f504d..688b1ea8 100644 --- a/driver/lib/driver.ts +++ b/driver/lib/driver.ts @@ -1,14 +1,14 @@ // @ts-ignore: no 'errors' export module -import _ from "lodash"; -import { BaseDriver } from "appium/driver"; -import { log as logger } from "./logger"; +import _ from 'lodash'; +import {BaseDriver} from 'appium/driver'; +import {log as logger} from './logger'; import { executeElementCommand, executeGetVMCommand, executeGetIsolateCommand, -} from "./sessions/observatory"; -import { PLATFORM } from "./platform"; -import { createSession, reConnectFlutterDriver } from "./sessions/session"; +} from './sessions/observatory'; +import {PLATFORM} from './platform'; +import {createSession, reConnectFlutterDriver} from './sessions/session'; import { driverShouldDoProxyCmd, FLUTTER_CONTEXT_NAME, @@ -16,15 +16,15 @@ import { 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"; +} 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 type { DefaultCreateSessionResult, DriverCaps, @@ -32,10 +32,10 @@ import type { W3CDriverCaps, RouteMatcher, Orientation, -} from "@appium/types"; -import type { IsolateSocket } from "./sessions/isolate_socket"; -import type { Server } from "node:net"; -import type { LogMonitor } from "./sessions/log-monitor"; +} from '@appium/types'; +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 @@ -51,7 +51,7 @@ const WEBVIEW_NO_PROXY = [ [`POST`, new RegExp(`^/session/[^/]+/orientation`)], [`POST`, new RegExp(`^/session/[^/]+/touch/multi/perform`)], [`POST`, new RegExp(`^/session/[^/]+/touch/perform`)], -] as import("@appium/types").RouteMatcher[]; +] as import('@appium/types').RouteMatcher[]; class FlutterDriver extends BaseDriver { public socket: IsolateSocket | null; @@ -129,11 +129,7 @@ class FlutterDriver extends BaseDriver { ]), ); this.internalCaps = caps; - return createSession.bind(this)( - sessionId, - caps, - ...JSON.parse(JSON.stringify(args)), - ); + return createSession.bind(this)(sessionId, caps, ...JSON.parse(JSON.stringify(args))); } public async deleteSession() { @@ -141,9 +137,9 @@ class FlutterDriver extends BaseDriver { this._logmon?.stop(); this._logmon = null; - this.proxydriver?.eventEmitter?.removeAllListeners("syslogStarted"); + this.proxydriver?.eventEmitter?.removeAllListeners('syslogStarted'); - this.log.info("Cleanup the port forward"); + this.log.info('Cleanup the port forward'); switch (_.toLower(this.internalCaps.platformName)) { case PLATFORM.IOS: this.localServer?.close(); @@ -152,16 +148,16 @@ 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."); + this.log.info('Deleting the proxy driver session.'); try { await this.proxydriver.deleteSession(this.sessionId || undefined); } catch (e) { @@ -200,29 +196,22 @@ class FlutterDriver extends BaseDriver { } switch (_.toLower(this.internalCaps.platformName)) { case PLATFORM.IOS: - return await (this.proxydriver as XCUITestDriver).proxyCommand( - "/orientation", - "GET", - ); + return await (this.proxydriver as XCUITestDriver).proxyCommand('/orientation', 'GET'); default: - return await ( - this.proxydriver as AndroidUiautomator2Driver - ).getOrientation(); + 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, + ); } } @@ -255,10 +244,10 @@ class FlutterDriver extends BaseDriver { public async executeCommand( cmd: string, - ...args: [string, [{ skipAttachObservatoryUrl: string; any: any }]] + ...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; @@ -298,9 +287,7 @@ class FlutterDriver extends BaseDriver { } public getProxyAvoidList(): RouteMatcher[] { - if ( - [FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(this.currentContext) - ) { + if ([FLUTTER_CONTEXT_NAME, NATIVE_CONTEXT_NAME].includes(this.currentContext)) { return []; } @@ -313,10 +300,7 @@ class FlutterDriver extends BaseDriver { // On iOS, WebView context is handled by XCUITest driver while Android is by chromedriver. // It means XCUITest driver should keep the XCUITest driver as a proxy, // while UIAutomator2 driver should proxy to chromedriver instead of UIA2 proxy. - return ( - this.proxyWebViewActive && - this.proxydriver?.constructor.name !== XCUITestDriver.name - ); + return this.proxyWebViewActive && this.proxydriver?.constructor.name !== XCUITestDriver.name; } public canProxy(): boolean { @@ -326,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 d5481d67..db018f91 100644 --- a/driver/lib/ios/app.ts +++ b/driver/lib/ios/app.ts @@ -1,5 +1,5 @@ -import { services, INSTRUMENT_CHANNEL } from "appium-ios-device"; -import { log } from "./../logger"; +import {services, INSTRUMENT_CHANNEL} from 'appium-ios-device'; +import {log} from './../logger'; /** * Launch the given bundle id via instrument service. @@ -18,18 +18,16 @@ export const launchApp = async ( ); await instrumentService.callChannel( INSTRUMENT_CHANNEL.PROCESS_CONTROL, - "launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:", - "", + 'launchSuspendedProcessWithDevicePath:bundleIdentifier:environment:arguments:options:', + '', bundleId, env, args, - { StartSuspendedKey: 0, KillExisting: 1 }, + {StartSuspendedKey: 0, KillExisting: 1}, ); return true; } catch (err) { - log.warn( - `Failed to launch '${bundleId}'. Original error: ${err.stderr || err.message}`, - ); + log.warn(`Failed to launch '${bundleId}'. Original error: ${err.stderr || err.message}`); return false; } finally { if (instrumentService) { diff --git a/driver/lib/logger.ts b/driver/lib/logger.ts index 421abb29..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 437fbd42..5cddef18 100644 --- a/driver/lib/platform.ts +++ b/driver/lib/platform.ts @@ -1,4 +1,4 @@ export const PLATFORM = { - IOS: "ios", - ANDROID: "android", + IOS: 'ios', + ANDROID: 'android', } as const; diff --git a/driver/lib/sessions/android.ts b/driver/lib/sessions/android.ts index bd298ce9..6f5101db 100644 --- a/driver/lib/sessions/android.ts +++ b/driver/lib/sessions/android.ts @@ -1,14 +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, @@ -18,7 +14,7 @@ export async function startAndroidSession( this.log.info(`Starting an Android proxy session`); const androiddriver = new AndroidUiautomator2Driver({} as InitialOpts); if (!caps.observatoryWsUri) { - androiddriver.eventEmitter.once("syslogStarted", (syslog) => { + androiddriver.eventEmitter.once('syslogStarted', (syslog) => { this._logmon = new LogMonitor(syslog, async (entry: LogEntry) => { if (extractObservatoryUrl(entry)) { this.log.debug(`Matched the syslog line '${entry.message}'`); @@ -37,10 +33,7 @@ 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( @@ -49,11 +42,7 @@ export async function connectAndroidSession( caps: Record, clearLog: boolean = false, ): Promise { - const observatoryWsUri = await getObservatoryWsUri.bind(this)( - androiddriver, - caps, - clearLog, - ); + const observatoryWsUri = await getObservatoryWsUri.bind(this)(androiddriver, caps, clearLog); return await connectSocket.bind(this)(observatoryWsUri, caps); } @@ -107,10 +96,7 @@ export async function getObservatoryWsUri( const remotePort = urlObject.port; this.portForwardLocalPort = caps.forwardingPort ?? remotePort; urlObject.port = this.portForwardLocalPort as string; - await proxydriver.adb.forwardPort( - this.portForwardLocalPort as string, - remotePort, - ); + await proxydriver.adb.forwardPort(this.portForwardLocalPort as string, remotePort); if (!caps.observatoryWsUri && proxydriver.adb.adbHost) { urlObject.host = proxydriver.adb.adbHost; } diff --git a/driver/lib/sessions/base64url.ts b/driver/lib/sessions/base64url.ts index 888914f5..fd09b6b1 100644 --- a/driver/lib/sessions/base64url.ts +++ b/driver/lib/sessions/base64url.ts @@ -1,18 +1,15 @@ -import _ from "lodash"; -import { util } from "@appium/support"; +import _ from 'lodash'; +import {util} from '@appium/support'; export const decode = ( - input: - | string - | { ELEMENT: string } - | { [util.W3C_WEB_ELEMENT_IDENTIFIER]: string }, + input: string | {ELEMENT: string} | {[util.W3C_WEB_ELEMENT_IDENTIFIER]: string}, ): string => { let base64String: string = ``; if (_.isString(input)) { base64String = input as string; } else if (_.has(input, util.W3C_WEB_ELEMENT_IDENTIFIER)) { base64String = input[util.W3C_WEB_ELEMENT_IDENTIFIER] as string; - } else if (_.has(input, "ELEMENT")) { + } else if (_.has(input, 'ELEMENT')) { // @ts-ignore TS2339 base64String = input.ELEMENT as string; } else { diff --git a/driver/lib/sessions/ios.ts b/driver/lib/sessions/ios.ts index d72bb785..22b6847b 100644 --- a/driver/lib/sessions/ios.ts +++ b/driver/lib/sessions/ios.ts @@ -1,18 +1,14 @@ -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 {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'; const LOCALHOST = `127.0.0.1`; @@ -24,7 +20,7 @@ export async function startIOSSession( this.log.info(`Starting an IOS proxy session`); const iosdriver = new XCUITestDriver({} as XCUITestDriverOpts); if (!caps.observatoryWsUri) { - iosdriver.eventEmitter.once("syslogStarted", (syslog) => { + iosdriver.eventEmitter.once('syslogStarted', (syslog) => { this._logmon = new LogMonitor(syslog, async (entry: LogEntry) => { if (extractObservatoryUrl(entry)) { this.log.debug(`Matched the syslog line '${entry.message}'`); @@ -52,11 +48,7 @@ export async function connectIOSSession( caps: Record, clearLog: boolean = false, ): Promise { - const observatoryWsUri = await getObservatoryWsUri.bind(this)( - iosdriver, - caps, - clearLog, - ); + const observatoryWsUri = await getObservatoryWsUri.bind(this)(iosdriver, caps, clearLog); return await connectSocket.bind(this)(observatoryWsUri, iosdriver, caps); } @@ -67,9 +59,7 @@ async function requireFreePort(this: FlutterDriver, port: number) { await new Promise((resolve) => { this.localServer?.close((err) => { if (err) { - this.log.error( - `Error occurred while closing the local server: ${err.message}`, - ); + 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`); @@ -80,9 +70,7 @@ async function requireFreePort(this: FlutterDriver, port: number) { if ((await checkPortStatus(port, LOCALHOST)) !== `open`) { return; } - this.log.warn( - `Port #${port} is busy. Did you quit the previous driver session(s) properly?`, - ); + 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.`, @@ -146,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; @@ -165,14 +153,14 @@ export async function getObservatoryWsUri( destroyCommChannel(); localSocket.destroy(); }); - remoteSocket.on("error", (e) => this.log.debug(e)); + remoteSocket.on('error', (e) => this.log.debug(e)); localSocket.once(`end`, destroyCommChannel); localSocket.once(`close`, () => { destroyCommChannel(); remoteSocket.destroy(); }); - localSocket.on("error", (e) => this.log.warn(e.message)); + localSocket.on('error', (e) => this.log.warn(e.message)); localSocket.pipe(remoteSocket); remoteSocket.pipe(localSocket); }); @@ -185,14 +173,10 @@ export async function getObservatoryWsUri( await listeningPromise; } catch (e) { this.localServer = null; - throw new Error( - `Cannot listen on the local port ${localPort}. Original error: ${e.message}`, - ); + throw new Error(`Cannot listen on the local port ${localPort}. Original error: ${e.message}`); } - this.log.info( - `Forwarding the remote port ${remotePort} to the local port ${localPort}`, - ); + this.log.info(`Forwarding the remote port ${remotePort} to the local port ${localPort}`); process.on(`beforeExit`, () => { this.localServer?.close(); diff --git a/driver/lib/sessions/isolate_socket.ts b/driver/lib/sessions/isolate_socket.ts index db1372d8..4337dbca 100644 --- a/driver/lib/sessions/isolate_socket.ts +++ b/driver/lib/sessions/isolate_socket.ts @@ -1,4 +1,4 @@ -import { Client } from "rpc-websockets"; +import {Client} from 'rpc-websockets'; interface ExecuteArgs { command: string; diff --git a/driver/lib/sessions/log-monitor.ts b/driver/lib/sessions/log-monitor.ts index 5e6c3f6a..e48c1702 100644 --- a/driver/lib/sessions/log-monitor.ts +++ b/driver/lib/sessions/log-monitor.ts @@ -1,5 +1,5 @@ -import type { EventEmitter } from "node:events"; -import { retryInterval } from "asyncbox"; +import type {EventEmitter} from 'node:events'; +import {retryInterval} from 'asyncbox'; export interface LogEntry { timestamp: number; level: string; @@ -59,7 +59,7 @@ export class LogMonitor { this._outputListener = this._onOutput.bind(this); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._logsEmitter.on("output", this._outputListener!); + this._logsEmitter.on('output', this._outputListener!); return this; } @@ -69,7 +69,7 @@ export class LogMonitor { } // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - this._logsEmitter.off("output", this._outputListener!); + this._logsEmitter.off('output', this._outputListener!); this._outputListener = null; return this; } diff --git a/driver/lib/sessions/observatory.ts b/driver/lib/sessions/observatory.ts index 3ea284b7..efc14c56 100644 --- a/driver/lib/sessions/observatory.ts +++ b/driver/lib/sessions/observatory.ts @@ -1,10 +1,10 @@ -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 {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'; const truncateLength = 500; // https://github.com/flutter/flutter/blob/f90b019c68edf4541a4c8273865a2b40c2c01eb3/dev/devicelab/lib/framework/runner.dart#L183 @@ -43,9 +43,7 @@ export async function connectSocket( // Add an 'error' event handler for the client socket const onErrorListener = (ex: Error) => { - this.log.error( - `Connection to ${dartObservatoryURL} got an error: ${ex.message}`, - ); + this.log.error(`Connection to ${dartObservatoryURL} got an error: ${ex.message}`); removeListenerAndResolve(null); }; socket.on(`error`, onErrorListener); @@ -86,9 +84,7 @@ export async function connectSocket( }; this.log.info(`Listing all isolates: ${JSON.stringify(vm.isolates)}`); // To accept 'main.dart:main()' and 'main' - const mainIsolateData = vm.isolates.find((e) => - e.name.includes(`main`), - ); + const mainIsolateData = vm.isolates.find((e) => e.name.includes(`main`)); if (!mainIsolateData) { this.log.error(`Cannot get Dart main isolate info`); removeListenerAndResolve(null); @@ -101,30 +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); @@ -146,17 +138,12 @@ export async function connectSocket( ); } -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 })}`, - ); + this.log.debug(`<<< ${_.truncate(JSON.stringify(isolate), {length: truncateLength})}`); return isolate; } @@ -170,9 +157,7 @@ export async function executeGetVMCommand(this: FlutterDriver) { }, ]; }; - this.log.debug( - `<<< ${_.truncate(JSON.stringify(vm), { length: truncateLength })}`, - ); + this.log.debug(`<<< ${_.truncate(JSON.stringify(vm), {length: truncateLength})}`); return vm; } @@ -183,11 +168,9 @@ export async function executeElementCommand( 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, - ); + const data = await (this.socket as IsolateSocket).executeSocketCommand(serializedCommand); this.log.debug(`<<< ${JSON.stringify(data)} | previous command ${command}`); if (data.isError) { throw new Error( diff --git a/driver/lib/sessions/session.ts b/driver/lib/sessions/session.ts index 916d372b..6b2951f3 100644 --- a/driver/lib/sessions/session.ts +++ b/driver/lib/sessions/session.ts @@ -1,10 +1,10 @@ -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 type { AndroidUiautomator2Driver } from "appium-uiautomator2-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 type {AndroidUiautomator2Driver} from 'appium-uiautomator2-driver'; export const reConnectFlutterDriver = async function ( this: FlutterDriver, @@ -17,18 +17,10 @@ export const reConnectFlutterDriver = async function ( switch (_.toLower(caps.platformName)) { case PLATFORM.IOS: - this.socket = await connectIOSSession.bind(this)( - this.proxydriver, - caps, - true, - ); + this.socket = await connectIOSSession.bind(this)(this.proxydriver, caps, true); break; case PLATFORM.ANDROID: - this.socket = await connectAndroidSession.bind(this)( - this.proxydriver, - caps, - true, - ); + this.socket = await connectAndroidSession.bind(this)(this.proxydriver, caps, true); break; default: this.log.errorWithException( @@ -50,27 +42,18 @@ export const createSession: any = async function ( // setup proxies - if platformName is not empty, make it less case sensitive switch (_.toLower(caps.platformName)) { case PLATFORM.IOS: - [this.proxydriver, this.socket] = await startIOSSession.bind(this)( - caps, - ...args, - ); - (this.proxydriver as XCUITestDriver).relaxedSecurityEnabled = - this.relaxedSecurityEnabled; + [this.proxydriver, this.socket] = await startIOSSession.bind(this)(caps, ...args); + (this.proxydriver as XCUITestDriver).relaxedSecurityEnabled = this.relaxedSecurityEnabled; (this.proxydriver as XCUITestDriver).denyInsecure = this.denyInsecure; (this.proxydriver as XCUITestDriver).allowInsecure = this.allowInsecure; break; case PLATFORM.ANDROID: - [this.proxydriver, this.socket] = await startAndroidSession.bind(this)( - caps, - ...args, - ); + [this.proxydriver, this.socket] = await startAndroidSession.bind(this)(caps, ...args); (this.proxydriver as AndroidUiautomator2Driver).relaxedSecurityEnabled = this.relaxedSecurityEnabled; - (this.proxydriver as AndroidUiautomator2Driver).denyInsecure = - this.denyInsecure; - (this.proxydriver as AndroidUiautomator2Driver).allowInsecure = - this.allowInsecure; + (this.proxydriver as AndroidUiautomator2Driver).denyInsecure = this.denyInsecure; + (this.proxydriver as AndroidUiautomator2Driver).allowInsecure = this.allowInsecure; break; default: this.log.errorWithException( diff --git a/driver/package.json b/driver/package.json index 75a53a26..d22806f5 100644 --- a/driver/package.json +++ b/driver/package.json @@ -86,5 +86,10 @@ "lodash": "^4.0.0", "portscanner": "^2.2.0", "rpc-websockets": "^10.0.0" + }, + "prettier": { + "bracketSpacing": false, + "printWidth": 100, + "singleQuote": true } } From bb5594f7cccfb06a12bf42bdc8776524f49a2c07 Mon Sep 17 00:00:00 2001 From: Kazuaki Matsuo Date: Mon, 16 Feb 2026 11:08:15 -0800 Subject: [PATCH 3/3] add local install --- .github/workflows/driver-function.yml | 1 + 1 file changed, 1 insertion(+) 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