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
97 changes: 32 additions & 65 deletions src/platforms/android/__tests__/multitouch-helper.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,10 @@ import { ANDROID_EMULATOR } from '../../../__tests__/test-utils/index.ts';
import {
ensureAndroidMultiTouchHelper,
parseAndroidMultiTouchHelperOutput,
pinchAndroid,
resetAndroidMultiTouchHelperInstallCache,
rotateGestureAndroid,
runAndroidMultiTouchHelperGesture,
swipeGestureAndroid,
transformGestureAndroid,
} from '../multitouch-helper.ts';
import {
withAndroidAdbProvider,
Expand Down Expand Up @@ -109,9 +107,7 @@ test('runAndroidMultiTouchHelperGesture encodes one-finger swipe payloads', asyn
capturedPayload = JSON.parse(Buffer.from(args[6]!, 'base64').toString('utf8'));
return {
exitCode: 0,
stdout: [resultRecord({ ok: 'true', kind: 'swipe' }), 'INSTRUMENTATION_CODE: 0'].join(
'\n',
),
stdout: [resultRecord({ ok: 'true', kind: 'swipe' }), 'INSTRUMENTATION_CODE: 0'].join('\n'),
stderr: '',
};
},
Expand Down Expand Up @@ -166,66 +162,6 @@ test('runAndroidMultiTouchHelperGesture preserves helper failure messages', asyn
);
});

test('swipeGestureAndroid and multi-touch gestures prefer provider-native touch injection', async () => {
const calls: unknown[] = [];
await withAndroidAdbProvider(
{
exec: async () => {
throw new Error('adb should not run when native touch is available');
},
touch: async (request) => {
calls.push(request);
return { backendDetail: 'native' };
},
},
{ serial: ANDROID_EMULATOR.id },
async () => {
const swipe = await swipeGestureAndroid(ANDROID_EMULATOR, {
x1: 340,
y1: 400,
x2: 60,
y2: 400,
durationMs: 300,
});
const pinch = await pinchAndroid(ANDROID_EMULATOR, { scale: 2, x: 100, y: 200 });
const rotate = await rotateGestureAndroid(ANDROID_EMULATOR, {
degrees: -215,
x: 100,
y: 200,
});
const transform = await transformGestureAndroid(ANDROID_EMULATOR, {
x: 100,
y: 200,
dx: 30,
dy: -20,
scale: 1.5,
degrees: 35,
});

assert.equal(swipe?.backend, 'provider-native-touch');
assert.equal(pinch.backend, 'provider-native-touch');
assert.equal(rotate.backend, 'provider-native-touch');
assert.equal(transform.backend, 'provider-native-touch');
},
);

assert.deepEqual(calls, [
{ kind: 'swipe', x1: 340, y1: 400, x2: 60, y2: 400, durationMs: 300 },
{ kind: 'pinch', x: 100, y: 200, scale: 2, durationMs: undefined },
{ kind: 'rotate', x: 100, y: 200, degrees: -215, durationMs: undefined },
{
kind: 'transform',
x: 100,
y: 200,
dx: 30,
dy: -20,
scale: 1.5,
degrees: 35,
durationMs: undefined,
},
]);
});

test('swipeGestureAndroid falls back to adb input swipe when helper path is unavailable', async () => {
const adbCalls: string[][] = [];
const result = await withAndroidAdbProvider(
Expand Down Expand Up @@ -274,6 +210,37 @@ test('swipeGestureAndroid falls back to adb input swipe when helper path is unav
assert.ok(adbCalls.some((args) => args.join(' ') === 'shell input swipe 340 400 60 400 300'));
});

test('swipeGestureAndroid propagates provider-native failures without adb fallback', async () => {
const adbCalls: string[][] = [];
await withAndroidAdbProvider(
{
exec: async (args) => {
adbCalls.push(args);
return { exitCode: 0, stdout: '', stderr: '' };
},
touch: async () => {
throw new Error('native touch failed');
},
},
{ serial: ANDROID_EMULATOR.id },
async () => {
await assert.rejects(
() =>
swipeGestureAndroid(ANDROID_EMULATOR, {
x1: 340,
y1: 400,
x2: 60,
y2: 400,
durationMs: 300,
}),
/native touch failed/,
);
},
);

assert.deepEqual(adbCalls, []);
});

test('rotateGestureAndroid rejects zero velocity before provider dispatch', async () => {
await withAndroidAdbProvider(
{
Expand Down
44 changes: 28 additions & 16 deletions src/platforms/android/multitouch-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,16 +120,14 @@ export async function swipeGestureAndroid(
device: DeviceInfo,
options: AndroidSwipeGestureOptions,
): Promise<Record<string, unknown> | void> {
const providerTouch = resolveAndroidTouchInjector(device);
if (providerTouch) {
return {
backend: 'provider-native-touch',
...((await providerTouch({ kind: 'swipe', ...options })) ?? {}),
};
}
const providerResult = await runAndroidTouchProviderGesture(device, {
kind: 'swipe',
...options,
});
if (providerResult) return providerResult;

try {
return await runAndroidMultiTouchGesture(device, { kind: 'swipe', ...options });
return await runAndroidMultiTouchHelperGestureForDevice(device, { kind: 'swipe', ...options });
} catch (error) {
emitDiagnostic({
level: 'warn',
Expand All @@ -151,7 +149,7 @@ export async function pinchAndroid(
throw new AppError('INVALID_ARGS', 'gesture pinch requires scale > 0');
}
const center = await resolveGestureCenter(device, options.x, options.y);
return await runAndroidMultiTouchGesture(device, {
return await performAndroidTouchGesture(device, {
kind: 'pinch',
x: center.x,
y: center.y,
Expand All @@ -175,7 +173,7 @@ export async function rotateGestureAndroid(
}
const center = await resolveGestureCenter(device, options.x, options.y);
const degrees = options.degrees;
return await runAndroidMultiTouchGesture(device, {
return await performAndroidTouchGesture(device, {
kind: 'rotate',
x: center.x,
y: center.y,
Expand All @@ -197,7 +195,7 @@ export async function transformGestureAndroid(
if (![options.x, options.y, options.dx, options.dy].every(Number.isFinite)) {
throw new AppError('INVALID_ARGS', 'gesture transform requires finite x y dx dy');
}
return await runAndroidMultiTouchGesture(device, {
return await performAndroidTouchGesture(device, {
kind: 'transform',
x: options.x,
y: options.y,
Expand All @@ -219,16 +217,30 @@ async function resolveGestureCenter(
return { x: Math.round(size.width / 2), y: Math.round(size.height / 2) };
}

async function runAndroidMultiTouchGesture(
async function performAndroidTouchGesture(
device: DeviceInfo,
request: AndroidTouchGestureRequest,
): Promise<Record<string, unknown>> {
const providerResult = await runAndroidTouchProviderGesture(device, request);
if (providerResult) return providerResult;

return await runAndroidMultiTouchHelperGestureForDevice(device, request);
}

async function runAndroidTouchProviderGesture(
device: DeviceInfo,
request: AndroidTouchGestureRequest,
): Promise<Record<string, unknown> | undefined> {
const providerTouch = resolveAndroidTouchInjector(device);
if (providerTouch) {
const result = (await providerTouch(request)) ?? {};
return { backend: 'provider-native-touch', ...result };
}
if (!providerTouch) return undefined;
const result = (await providerTouch(request)) ?? {};
return { backend: 'provider-native-touch', ...result };
}

async function runAndroidMultiTouchHelperGestureForDevice(
device: DeviceInfo,
request: AndroidTouchGestureRequest,
): Promise<Record<string, unknown>> {
const adb = resolveAndroidAdbExecutor(device);
const artifact = await resolveAndroidMultiTouchHelperArtifact();
const adbProvider = resolveAndroidAdbProvider(device);
Expand Down
8 changes: 8 additions & 0 deletions test/integration/provider-scenarios/android-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ test('Provider-backed integration Android touch provider handles multi-touch ges
const client = world.daemon.client();
await client.apps.open({ app: 'settings', ...world.selection });

await client.interactions.swipe({
from: { x: 340, y: 400 },
to: { x: 60, y: 400 },
durationMs: 300,
...world.selection,
});

const pinch = await client.interactions.pinch({
scale: 2,
x: 195,
Expand Down Expand Up @@ -118,6 +125,7 @@ test('Provider-backed integration Android touch provider handles multi-touch ges
assert.equal(transform.backend, 'provider-native-touch');

assert.deepEqual(world.touchInjectionCalls, [
{ kind: 'swipe', x1: 340, y1: 400, x2: 60, y2: 400, durationMs: 300 },
{ kind: 'pinch', x: 195, y: 320, scale: 2, durationMs: undefined },
{ kind: 'rotate', x: 195, y: 320, degrees: 145, durationMs: undefined },
{
Expand Down
Loading