appium-flutter-driver
Version:
Appium Flutter driver
186 lines (169 loc) • 7.19 kB
text/typescript
/* eslint-disable @typescript-eslint/no-non-null-assertion */
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';
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>;
interface DragAndDropParams {
startX: string;
startY: string;
endX: string;
endY: string;
duration: string;
}
interface DiagnosticsOptions {
subtreeDepth?: number;
includeProperties?: boolean;
}
interface LongTapOptions {
durationMilliseconds: number;
frequency?: number;
}
interface OffsetOptions {
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;
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,
getVMInfo: async (driver) => await driver.executeGetVMCommand(),
getRenderTree: async (driver) => (await driver.executeElementCommand('get_render_tree')).tree,
getOffset: async (driver, elementBase64: string, options: OffsetOptions) =>
await driver.executeElementCommand('get_offset', elementBase64, options),
waitForCondition: async (driver, conditionName: string) =>
await driver.executeElementCommand('waitForCondition', '', { conditionName }),
forceGC: async (driver) => {
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)}`);
}
},
setIsolateId: async (driver, isolateId: string) => {
driver.socket!.isolateId = isolateId;
return await driver.socket!.call('getIsolate', { isolateId });
},
getIsolate: async (driver, isolateId?: string) =>
await driver.executeGetIsolateCommand(isolateId || driver.socket!.isolateId!),
clearTimeline: async (driver) => {
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)}`);
}
},
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;
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,
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),
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 }),
enterText: async (driver, text: string) =>
await driver.socket!.executeSocketCommand({ command: 'enter_text', text }),
requestData: async (driver, message: string) =>
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.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 });
},
dragAndDropWithCommandExtension: async (driver, params: DragAndDropParams) =>
await driver.socket!.executeSocketCommand({
command: 'dragAndDropWithCommandExtension',
...params,
}),
assertVisible: async (driver, input: FinderInput, timeout = 5000) =>
await assertVisible(driver, input, timeout),
assertNotVisible: async (driver, input: FinderInput, timeout = 5000) =>
await assertNotVisible(driver, input, timeout),
assertTappable: async (driver, input: FinderInput, timeout = 5000) =>
await assertTappable(driver, input, timeout),
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[],
) {
const matching = rawCommand.match(flutterCommandRegex);
if (!matching) {
throw new Error(`Command not supported: "${rawCommand}"`);
}
const command = matching[1].trim();
const handler = commandHandlers[command];
if (!handler) {
throw new Error(`Command not supported: "${rawCommand}"`);
}
return await handler(this, ...args);
};