Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/driver-function.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/nodejs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,5 @@ jobs:
- run: |
npm install
npm run lint
npm run format:check
working-directory: driver
43 changes: 22 additions & 21 deletions driver/lib/commands/assertions.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { FlutterDriver } from '../driver';
import { byValueKey, byText, byTooltip } from 'appium-flutter-finder';
import type { SerializableFinder } from 'appium-flutter-finder';
import {FlutterDriver} from '../driver';
import {byValueKey, byText, byTooltip} from 'appium-flutter-finder';
import type {SerializableFinder} from 'appium-flutter-finder';

export type FinderInput =
| { key: string }
| { text: string }
| { label: string }
| {key: string}
| {text: string}
| {label: string}
| SerializableFinder
| string
| { getRawFinder: () => SerializableFinder }; // FlutterElement-like input
| {getRawFinder: () => SerializableFinder}; // FlutterElement-like input

// Serialize a finder to base64
const serializeFinder = (finder: SerializableFinder): string =>
Expand All @@ -18,7 +18,7 @@ const serializeFinder = (finder: SerializableFinder): string =>
const isRawFinder = (input: any): input is SerializableFinder =>
input && typeof input === 'object' && typeof input.finderType === 'string';

const isFlutterElementLike = (input: any): input is { getRawFinder: () => SerializableFinder } =>
const isFlutterElementLike = (input: any): input is {getRawFinder: () => SerializableFinder} =>
input && typeof input === 'object' && typeof input.getRawFinder === 'function';

// Convert FinderInput to base64 string
Expand Down Expand Up @@ -47,7 +47,9 @@ function getFinderBase64(input: FinderInput): string {
return byTooltip(input.label);
}

throw new Error('Invalid finder input: must provide key, text, label, raw finder, or FlutterElement');
throw new Error(
'Invalid finder input: must provide key, text, label, raw finder, or FlutterElement',
);
}

// Generic helper to wrap assert commands
Expand All @@ -56,11 +58,14 @@ async function executeAssertion(
command: string,
input: FinderInput,
timeout = 5000,
extraArgs: object = {}
extraArgs: object = {},
): Promise<void> {
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}`);
}
Expand All @@ -70,21 +75,17 @@ async function executeAssertion(
export const assertVisible = async (
driver: FlutterDriver,
input: FinderInput,
timeout = 5000
): Promise<void> =>
await executeAssertion(driver, 'waitFor', input, timeout, { visible: true });
timeout = 5000,
): Promise<void> => await executeAssertion(driver, 'waitFor', input, timeout, {visible: true});

export const assertNotVisible = async (
driver: FlutterDriver,
input: FinderInput,
timeout = 5000
): Promise<void> =>
await executeAssertion(driver, 'waitForAbsent', input, timeout);
timeout = 5000,
): Promise<void> => await executeAssertion(driver, 'waitForAbsent', input, timeout);

export const assertTappable = async (
driver: FlutterDriver,
input: FinderInput,
timeout = 5000
): Promise<void> =>
await executeAssertion(driver, 'waitForTappable', input, timeout);

timeout = 5000,
): Promise<void> => await executeAssertion(driver, 'waitForTappable', input, timeout);
11 changes: 7 additions & 4 deletions driver/lib/commands/clipboard.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
import type { FlutterDriver } from '../driver';
import type {FlutterDriver} from '../driver';

/**
* Set clipboard content via each native app driver
* @param this the FlutterDriver
* @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);
};
22 changes: 13 additions & 9 deletions driver/lib/commands/context.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import type { FlutterDriver } from '../driver';
import { log } from '../logger';
import type {FlutterDriver} from '../driver';
import {log} from '../logger';

export const FLUTTER_CONTEXT_NAME = `FLUTTER`;
export const NATIVE_CONTEXT_NAME = `NATIVE_APP`;

export const getCurrentContext = async function(this: FlutterDriver): Promise<string> {
export const getCurrentContext = async function (this: FlutterDriver): Promise<string> {
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);
}
Expand All @@ -28,21 +30,23 @@ export const setContext = async function(this: FlutterDriver, context: string) {
}
this.currentContext = context;
if (context === FLUTTER_CONTEXT_NAME) {
log.debug(`Downstream driver context is set as 'NATIVE_APP' in 'FLUTTER' context to handle the native app.`);
log.debug(
`Downstream driver context is set as 'NATIVE_APP' in 'FLUTTER' context to handle the native app.`,
);
}
};

export const getContexts = async function(this: FlutterDriver): Promise<string[]> {
export const getContexts = async function (this: FlutterDriver): Promise<string[]> {
// @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;
}
Expand Down
12 changes: 8 additions & 4 deletions driver/lib/commands/element.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { FlutterDriver } from '../driver';
import {FlutterDriver} from '../driver';

export const getText = async function(this: FlutterDriver, el: string): Promise<string | null> {
export const getText = async function (this: FlutterDriver, el: string): Promise<string | null> {
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) {
Expand All @@ -19,6 +23,6 @@ export const setValue = async function(this: FlutterDriver, textInput: string |
await this.execute(`flutter:enterText`, [text]);
};

export const clear = async function(this: FlutterDriver, el: string) {
export const clear = async function (this: FlutterDriver, el: string) {
await this.setValue([``], el);
};
81 changes: 38 additions & 43 deletions driver/lib/commands/execute.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,21 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import type { FlutterDriver } from '../driver';
import { reConnectFlutterDriver } from '../sessions/session';
import type {FlutterDriver} from '../driver';
import {reConnectFlutterDriver} from '../sessions/session';
import {
longTap,
scroll,
scrollIntoView,
scrollUntilVisible,
scrollUntilTapable
scrollUntilTapable,
} from './execute/scroll';
import {
waitFor,
waitForAbsent,
waitForTappable
} from './execute/wait';
import {
assertVisible,
assertNotVisible,
assertTappable,
type FinderInput
} from './assertions';
import {waitFor, waitForAbsent, waitForTappable} from './execute/wait';
import {assertVisible, assertNotVisible, assertTappable, type FinderInput} from './assertions';

import { launchApp } from './../ios/app';
import {launchApp} from './../ios/app';
import B from 'bluebird';

const flutterCommandRegex = /^[\s]*flutter[\s]*:(.+)/;



// Define types for better type safety
type CommandHandler = (driver: FlutterDriver, ...args: any[]) => Promise<any>;
type CommandMap = Record<string, CommandHandler>;
Expand All @@ -44,8 +33,6 @@ interface DiagnosticsOptions {
includeProperties?: boolean;
}



interface LongTapOptions {
durationMilliseconds: number;
frequency?: number;
Expand All @@ -58,7 +45,7 @@ interface OffsetOptions {
// Extract command handlers into a separate object for better organization
const commandHandlers: CommandMap = {
launchApp: async (driver, appId: string, opts = {}) => {
const { arguments: args = [], environment: env = {} } = opts;
const {arguments: args = [], environment: env = {}} = opts;
await launchApp(driver.internalCaps.udid!, appId, args, env);
await reConnectFlutterDriver.bind(driver)(driver.internalCaps);
},
Expand All @@ -71,18 +58,20 @@ const commandHandlers: CommandMap = {
getOffset: async (driver, elementBase64: string, options: OffsetOptions) =>
await driver.executeElementCommand('get_offset', elementBase64, options),
waitForCondition: async (driver, conditionName: string) =>
await driver.executeElementCommand('waitForCondition', '', { conditionName }),
await driver.executeElementCommand('waitForCondition', '', {
conditionName,
}),
forceGC: async (driver) => {
const response = await driver.socket!.call('_collectAllGarbage', {
const response = (await driver.socket!.call('_collectAllGarbage', {
isolateId: driver.socket!.isolateId,
}) as { type: string };
})) as {type: string};
if (response.type !== 'Success') {
throw new Error(`Could not forceGC, response was ${JSON.stringify(response)}`);
}
},
setIsolateId: async (driver, isolateId: string) => {
driver.socket!.isolateId = isolateId;
return await driver.socket!.call('getIsolate', { isolateId });
return await driver.socket!.call('getIsolate', {isolateId});
},
getIsolate: async (driver, isolateId?: string) =>
await driver.executeGetIsolateCommand(isolateId || driver.socket!.isolateId!),
Expand All @@ -94,16 +83,20 @@ const commandHandlers: CommandMap = {
throw new Error(`Could not clear timeline, response was ${JSON.stringify(response)}`);
}
},
getRenderObjectDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => {
const { subtreeDepth = 0, includeProperties = true } = opts;
getRenderObjectDiagnostics: async (
driver,
elementBase64: string,
opts: DiagnosticsOptions = {},
) => {
const {subtreeDepth = 0, includeProperties = true} = opts;
return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, {
diagnosticsType: 'renderObject',
includeProperties,
subtreeDepth,
});
},
getWidgetDiagnostics: async (driver, elementBase64: string, opts: DiagnosticsOptions = {}) => {
const { subtreeDepth = 0, includeProperties = true } = opts;
const {subtreeDepth = 0, includeProperties = true} = opts;
return await driver.executeElementCommand('get_diagnostics_tree', elementBase64, {
diagnosticsType: 'widget',
includeProperties,
Expand All @@ -118,33 +111,40 @@ const commandHandlers: CommandMap = {
await waitFor(driver, finder, timeout),
waitForTappable: async (driver, finder: string, timeout?: number) =>
await waitForTappable(driver, finder, timeout),
scroll: async (driver, finder: string, opts: any) =>
await scroll(driver, finder, opts),
scroll: async (driver, finder: string, opts: any) => await scroll(driver, finder, opts),
scrollUntilVisible: async (driver, finder: string, opts: any) =>
await scrollUntilVisible(driver, finder, opts),
scrollUntilTapable: async (driver, finder: string, opts: any) =>
await scrollUntilTapable(driver, finder, opts),
scrollIntoView: async (driver, finder: string, opts: any) =>
await scrollIntoView(driver, finder, opts),
setTextEntryEmulation: async (driver, enabled: boolean) =>
await driver.socket!.executeSocketCommand({ command: 'set_text_entry_emulation', enabled }),
await driver.socket!.executeSocketCommand({
command: 'set_text_entry_emulation',
enabled,
}),
enterText: async (driver, text: string) =>
await driver.socket!.executeSocketCommand({ command: 'enter_text', text }),
await driver.socket!.executeSocketCommand({command: 'enter_text', text}),
requestData: async (driver, message: string) =>
await driver.socket!.executeSocketCommand({ command: 'request_data', message }),
await driver.socket!.executeSocketCommand({
command: 'request_data',
message,
}),
longTap: async (driver, finder: string, durationOrOptions: LongTapOptions) =>
await longTap(driver, finder, durationOrOptions),
waitForFirstFrame: async (driver) =>
await driver.executeElementCommand('waitForCondition', '', { conditionName: 'FirstFrameRasterizedCondition' }),
await driver.executeElementCommand('waitForCondition', '', {
conditionName: 'FirstFrameRasterizedCondition',
}),
setFrameSync: async (driver, enabled: boolean, durationMilliseconds: number) =>
await driver.socket!.executeSocketCommand({
command: 'set_frame_sync',
enabled,
timeout: durationMilliseconds,
}),
clickElement: async (driver, finder: string, opts: { timeout?: number } = {}) => {
const { timeout = 1000 } = opts;
return await driver.executeElementCommand('tap', finder, { timeout });
clickElement: async (driver, finder: string, opts: {timeout?: number} = {}) => {
const {timeout = 1000} = opts;
return await driver.executeElementCommand('tap', finder, {timeout});
},
dragAndDropWithCommandExtension: async (driver, params: DragAndDropParams) =>
await driver.socket!.executeSocketCommand({
Expand All @@ -157,17 +157,13 @@ const commandHandlers: CommandMap = {
await assertNotVisible(driver, input, timeout),
assertTappable: async (driver, input: FinderInput, timeout = 5000) =>
await assertTappable(driver, input, timeout),
getTextWithCommandExtension: async (driver, params: { findBy: string }) =>
getTextWithCommandExtension: async (driver, params: {findBy: string}) =>
await driver.socket!.executeSocketCommand({
command: 'getTextWithCommandExtension',
findBy: params.findBy,
}),
};
export const execute = async function (
this: FlutterDriver,
rawCommand: string,
args: any[],
) {
export const execute = async function (this: FlutterDriver, rawCommand: string, args: any[]) {
const matching = rawCommand.match(flutterCommandRegex);
if (!matching) {
throw new Error(`Command not supported: "${rawCommand}"`);
Expand All @@ -182,4 +178,3 @@ export const execute = async function (

return await handler(this, ...args);
};

Loading
Loading