Skip to content

Commit 4553f7a

Browse files
authored
refactor: split platform interactors (#526)
1 parent 204a320 commit 4553f7a

8 files changed

Lines changed: 319 additions & 277 deletions

File tree

src/core/__tests__/dispatch-interactions.test.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { test, vi } from 'vitest';
22
import assert from 'node:assert/strict';
33
import { handlePressCommand } from '../dispatch-interactions.ts';
4-
import type { Interactor } from '../interactors.ts';
4+
import type { Interactor } from '../interactor-types.ts';
55
import { MACOS_DEVICE } from '../../__tests__/test-utils/device-fixtures.ts';
66

77
vi.mock('../../platforms/ios/macos-helper.ts', async (importOriginal) => {
@@ -31,6 +31,7 @@ function makeUnusedInteractor(): Interactor {
3131
fill: fail,
3232
scroll: fail,
3333
screenshot: fail,
34+
snapshot: fail,
3435
back: fail,
3536
home: fail,
3637
rotate: fail,
@@ -45,16 +46,10 @@ test('handlePressCommand routes macOS menubar press through the helper', async (
4546
const mockRunMacOsPressAction = vi.mocked(runMacOsPressAction);
4647
mockRunMacOsPressAction.mockClear();
4748

48-
const result = await handlePressCommand(
49-
MACOS_DEVICE,
50-
makeUnusedInteractor(),
51-
['100', '200'],
52-
{
53-
surface: 'menubar',
54-
appBundleId: 'com.example.menubarapp',
55-
},
56-
{},
57-
);
49+
const result = await handlePressCommand(MACOS_DEVICE, makeUnusedInteractor(), ['100', '200'], {
50+
surface: 'menubar',
51+
appBundleId: 'com.example.menubarapp',
52+
});
5853

5954
assert.deepEqual(result, {
6055
x: 100,

src/core/dispatch-interactions.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
runRepeatedSeries,
2222
} from './dispatch-series.ts';
2323
import type { DispatchContext } from './dispatch-context.ts';
24-
import type { Interactor, RunnerContext } from './interactors.ts';
24+
import type { Interactor } from './interactor-types.ts';
2525

2626
export async function handleLongPressCommand(
2727
interactor: Interactor,
@@ -92,7 +92,6 @@ export async function handlePressCommand(
9292
interactor: Interactor,
9393
positionals: string[],
9494
context: DispatchContext | undefined,
95-
_runnerCtx: RunnerContext,
9695
): Promise<Record<string, unknown>> {
9796
const { x, y } = readPoint(positionals, 'press requires x y');
9897

@@ -338,7 +337,6 @@ export async function handleSwipeCommand(
338337
interactor: Interactor,
339338
positionals: string[],
340339
context: DispatchContext | undefined,
341-
_runnerCtx: RunnerContext,
342340
): Promise<Record<string, unknown>> {
343341
const x1 = Number(positionals[0]);
344342
const y1 = Number(positionals[1]);
@@ -459,7 +457,6 @@ export async function handlePinchCommand(
459457
device: DeviceInfo,
460458
positionals: string[],
461459
context: DispatchContext | undefined,
462-
_runnerCtx: RunnerContext,
463460
): Promise<Record<string, unknown>> {
464461
if (device.platform === 'android') {
465462
throw new AppError(
@@ -499,7 +496,6 @@ export async function handleReadCommand(
499496
device: DeviceInfo,
500497
positionals: string[],
501498
context: DispatchContext | undefined,
502-
_runnerCtx: RunnerContext,
503499
): Promise<Record<string, unknown>> {
504500
const [x, y] = positionals.map(Number);
505501
if (Number.isNaN(x) || Number.isNaN(y)) {

src/core/dispatch.ts

Lines changed: 18 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,17 @@ import {
66
dismissAndroidKeyboard,
77
getAndroidKeyboardState,
88
pushAndroidNotification,
9-
snapshotAndroid,
109
} from '../platforms/android/index.ts';
11-
import { getInteractor, type Interactor, type RunnerContext } from './interactors.ts';
10+
import { getInteractor } from './interactors.ts';
11+
import type { Interactor, RunnerContext } from './interactor-types.ts';
1212
import { runIosRunnerCommand } from '../platforms/ios/runner-client.ts';
1313
import { pushIosNotification } from '../platforms/ios/index.ts';
14-
import { snapshotLinux } from '../platforms/linux/snapshot.ts';
1514
import { isDeepLinkTarget } from './open-target.ts';
1615
import { parseTriggerAppEventArgs, resolveAppEventUrl } from './app-events.ts';
17-
import type { RawSnapshotNode } from '../utils/snapshot.ts';
1816
import { emitDiagnostic, withDiagnosticTimer } from '../utils/diagnostics.ts';
1917
import { readLocationCoordinate } from '../utils/location-coordinates.ts';
2018
import { successText, withSuccessText } from '../utils/success-text.ts';
2119
import type { DispatchContext } from './dispatch-context.ts';
22-
import { shouldUseIosTapSeries, shouldUseIosDragSeries } from './dispatch-series.ts';
2320
import {
2421
handleFillCommand,
2522
handleFocusCommand,
@@ -35,7 +32,6 @@ import { readNotificationPayload } from './dispatch-payload.ts';
3532
import { parseDeviceRotation } from './device-rotation.ts';
3633

3734
export { resolveTargetDevice } from './dispatch-resolve.ts';
38-
export { shouldUseIosTapSeries, shouldUseIosDragSeries };
3935
export type { BatchStep, CommandFlags, DispatchContext } from './dispatch-context.ts';
4036

4137
export async function dispatchCommand(
@@ -77,9 +73,9 @@ export async function dispatchCommand(
7773
return { app, ...successText(`Closed: ${app}`) };
7874
}
7975
case 'press':
80-
return handlePressCommand(device, interactor, positionals, context, runnerCtx);
76+
return handlePressCommand(device, interactor, positionals, context);
8177
case 'swipe':
82-
return handleSwipeCommand(device, interactor, positionals, context, runnerCtx);
78+
return handleSwipeCommand(device, interactor, positionals, context);
8379
case 'longpress':
8480
return handleLongPressCommand(interactor, positionals);
8581
case 'focus':
@@ -91,7 +87,7 @@ export async function dispatchCommand(
9187
case 'scroll':
9288
return handleScrollCommand(interactor, positionals, context);
9389
case 'pinch':
94-
return handlePinchCommand(device, positionals, context, runnerCtx);
90+
return handlePinchCommand(device, positionals, context);
9591
case 'trigger-app-event': {
9692
const { eventName, payload } = parseTriggerAppEventArgs(positionals);
9793
const eventUrl = resolveAppEventUrl(device.platform, eventName, payload);
@@ -135,15 +131,15 @@ export async function dispatchCommand(
135131
case 'clipboard':
136132
return handleClipboardCommand(interactor, positionals);
137133
case 'keyboard':
138-
return handleKeyboardCommand(device, interactor, positionals, context, runnerCtx);
134+
return handleKeyboardCommand(device, positionals, context, runnerCtx);
139135
case 'settings':
140136
return handleSettingsCommand(device, interactor, positionals, context);
141137
case 'push':
142138
return handlePushCommand(device, positionals, context);
143139
case 'snapshot':
144-
return handleSnapshotCommand(device, positionals, context, runnerCtx);
140+
return await handleSnapshotCommand(interactor, context);
145141
case 'read':
146-
return handleReadCommand(device, positionals, context, runnerCtx);
142+
return handleReadCommand(device, positionals, context);
147143
default:
148144
throw new AppError('INVALID_ARGS', `Unknown command: ${command}`);
149145
}
@@ -230,7 +226,6 @@ async function handleClipboardCommand(
230226

231227
async function handleKeyboardCommand(
232228
device: DeviceInfo,
233-
_interactor: Interactor,
234229
positionals: string[],
235230
context: DispatchContext | undefined,
236231
runnerCtx: RunnerContext,
@@ -367,76 +362,18 @@ async function handlePushCommand(
367362
}
368363

369364
async function handleSnapshotCommand(
370-
device: DeviceInfo,
371-
_positionals: string[],
365+
interactor: Interactor,
372366
context: DispatchContext | undefined,
373-
_runnerCtx: RunnerContext,
374367
): Promise<Record<string, unknown>> {
375-
if (device.platform === 'linux') {
376-
const linuxResult = await withDiagnosticTimer(
377-
'snapshot_capture',
378-
async () => await snapshotLinux(context?.surface),
379-
{ backend: 'linux-atspi' },
380-
);
381-
return {
382-
nodes: linuxResult.nodes ?? [],
383-
truncated: linuxResult.truncated ?? false,
384-
backend: 'linux-atspi',
385-
};
386-
}
387-
if (device.platform !== 'android') {
388-
const result = (await withDiagnosticTimer(
389-
'snapshot_capture',
390-
async () =>
391-
await runIosRunnerCommand(
392-
device,
393-
{
394-
command: 'snapshot',
395-
appBundleId: context?.appBundleId,
396-
interactiveOnly: context?.snapshotInteractiveOnly,
397-
compact: context?.snapshotCompact,
398-
depth: context?.snapshotDepth,
399-
scope: context?.snapshotScope,
400-
raw: context?.snapshotRaw,
401-
},
402-
{
403-
verbose: context?.verbose,
404-
logPath: context?.logPath,
405-
traceLogPath: context?.traceLogPath,
406-
requestId: context?.requestId,
407-
},
408-
),
409-
{
410-
backend: 'xctest',
411-
},
412-
)) as { nodes?: RawSnapshotNode[]; truncated?: boolean };
413-
const nodes = result.nodes ?? [];
414-
if (nodes.length === 0 && device.kind === 'simulator') {
415-
throw new AppError('COMMAND_FAILED', 'XCTest snapshot returned 0 nodes on iOS simulator.');
416-
}
417-
return { nodes, truncated: result.truncated ?? false, backend: 'xctest' };
418-
}
419-
const androidResult = await withDiagnosticTimer(
420-
'snapshot_capture',
421-
async () =>
422-
await snapshotAndroid(device, {
423-
interactiveOnly: context?.snapshotInteractiveOnly,
424-
compact: context?.snapshotCompact,
425-
depth: context?.snapshotDepth,
426-
scope: context?.snapshotScope,
427-
raw: context?.snapshotRaw,
428-
}),
429-
{
430-
backend: 'android',
431-
},
432-
);
433-
return {
434-
nodes: androidResult.nodes ?? [],
435-
truncated: androidResult.truncated ?? false,
436-
backend: 'android',
437-
analysis: androidResult.analysis,
438-
androidSnapshot: androidResult.androidSnapshot,
439-
};
368+
return await interactor.snapshot({
369+
appBundleId: context?.appBundleId,
370+
interactiveOnly: context?.snapshotInteractiveOnly,
371+
compact: context?.snapshotCompact,
372+
depth: context?.snapshotDepth,
373+
scope: context?.snapshotScope,
374+
raw: context?.snapshotRaw,
375+
surface: context?.surface,
376+
});
440377
}
441378

442379
function readResultMessage(result: Record<string, unknown>): string | undefined {

src/core/interactor-types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@ import type { DeviceRotation } from './device-rotation.ts';
22
import type { ScrollDirection } from './scroll-gesture.ts';
33
import type { SettingOptions } from '../platforms/permission-utils.ts';
44
import type { SessionSurface } from './session-surface.ts';
5+
import type { BackendSnapshotResult } from '../backend.ts';
6+
import type {
7+
RawSnapshotNode,
8+
SnapshotBackend,
9+
SnapshotOptions as BaseSnapshotOptions,
10+
} from '../utils/snapshot.ts';
511

612
export type RunnerContext = {
713
requestId?: string;
@@ -19,6 +25,16 @@ export type ScreenshotOptions = {
1925
surface?: SessionSurface;
2026
};
2127

28+
export type SnapshotOptions = BaseSnapshotOptions & {
29+
appBundleId?: string;
30+
surface?: SessionSurface;
31+
};
32+
33+
export type SnapshotResult = Omit<BackendSnapshotResult, 'backend' | 'nodes'> & {
34+
nodes?: RawSnapshotNode[];
35+
backend: Extract<SnapshotBackend, 'android' | 'xctest' | 'linux-atspi'>;
36+
};
37+
2238
export type Interactor = {
2339
open(
2440
app: string,
@@ -49,6 +65,7 @@ export type Interactor = {
4965
options?: { amount?: number; pixels?: number },
5066
): Promise<Record<string, unknown> | void>;
5167
screenshot(outPath: string, options?: ScreenshotOptions): Promise<void>;
68+
snapshot(options?: SnapshotOptions): Promise<SnapshotResult>;
5269
back(mode?: BackMode): Promise<void>;
5370
home(): Promise<void>;
5471
rotate(orientation: DeviceRotation): Promise<void>;

0 commit comments

Comments
 (0)