|
| 1 | +import { afterEach, expect, test } from 'vitest'; |
| 2 | +import { once } from 'node:events'; |
| 3 | +import { openAndroidApp } from './app-lifecycle.ts'; |
| 4 | +import { withAndroidAdbProvider } from './adb-executor.ts'; |
| 5 | +import { readAndroidFillTargetBeforeMutation } from './fill-verification.ts'; |
| 6 | +import { resetAndroidSnapshotHelperSessions } from './snapshot-helper-session-lifecycle.ts'; |
| 7 | +import { |
| 8 | + createPersistentSnapshotHelperProvider, |
| 9 | + type FakeAndroidProcess, |
| 10 | +} from './__tests__/snapshot-helper-session.fixtures.ts'; |
| 11 | +import { ANDROID_SNAPSHOT_HELPER_FIXTURE_ARTIFACT as artifact } from './__tests__/test-utils/android-snapshot-helper.ts'; |
| 12 | +import { ANDROID_EMULATOR as device } from './__tests__/test-utils/device-fixtures.ts'; |
| 13 | +afterEach(resetAndroidSnapshotHelperSessions); |
| 14 | + |
| 15 | +test.each(['advertised', 'unsupported', 'ended'] as const)( |
| 16 | + 'app open through an %s session uses exactly one launch transport', |
| 17 | + async (mode) => { |
| 18 | + const calls: (readonly string[])[] = [], |
| 19 | + requests: string[] = [], |
| 20 | + processes: FakeAndroidProcess[] = []; |
| 21 | + const pkg = 'com.example.app'; |
| 22 | + const provider = createPersistentSnapshotHelperProvider({ |
| 23 | + calls, |
| 24 | + processes, |
| 25 | + spawnArgs: [], |
| 26 | + sessionRequests: requests, |
| 27 | + sessionCommands: |
| 28 | + mode === 'unsupported' ? ['snapshot-immediate'] : ['snapshot-immediate', 'app'], |
| 29 | + sessionCommand: (command) => |
| 30 | + command.startsWith('app ') |
| 31 | + ? { |
| 32 | + launchOutputBase64: Buffer.from( |
| 33 | + `Status: ok\nActivity: ${pkg}/.MainActivity\n`, |
| 34 | + ).toString('base64'), |
| 35 | + } |
| 36 | + : undefined, |
| 37 | + }); |
| 38 | + const exec = provider.exec; |
| 39 | + await withAndroidAdbProvider( |
| 40 | + { |
| 41 | + ...provider, |
| 42 | + snapshotHelperArtifact: artifact, |
| 43 | + exec: async (args, options) => { |
| 44 | + if (args[0] === 'shell' && args[1] === 'am' && args[2] === 'start') { |
| 45 | + calls.push(args); |
| 46 | + return { |
| 47 | + stdout: `Status: ok\nActivity: ${pkg}/.MainActivity\n`, |
| 48 | + stderr: '', |
| 49 | + exitCode: 0, |
| 50 | + }; |
| 51 | + } |
| 52 | + return await exec(args, options); |
| 53 | + }, |
| 54 | + }, |
| 55 | + { serial: device.id }, |
| 56 | + async () => { |
| 57 | + await readAndroidFillTargetBeforeMutation(device, 10, 10, { |
| 58 | + helperSessionScope: 'daemon-session', |
| 59 | + }); |
| 60 | + if (mode === 'ended') { |
| 61 | + const exited = once(processes[0]!, 'close'); |
| 62 | + processes[0]!.kill(); |
| 63 | + await exited; |
| 64 | + } |
| 65 | + await expect(openAndroidApp(device, pkg)).resolves.toBe(pkg); |
| 66 | + }, |
| 67 | + ); |
| 68 | + const appRequests = requests.filter((request) => request.startsWith('app ')); |
| 69 | + expect(appRequests).toHaveLength(mode === 'advertised' ? 1 : 0); |
| 70 | + expect( |
| 71 | + calls.filter((args) => args[0] === 'shell' && args[1] === 'am' && args[2] === 'start'), |
| 72 | + ).toHaveLength(mode === 'advertised' ? 0 : 1); |
| 73 | + if (mode === 'advertised') |
| 74 | + expect(JSON.parse(Buffer.from(appRequests[0]!.split(' ')[2]!, 'base64').toString())).toEqual({ |
| 75 | + kind: 'open', |
| 76 | + packageName: pkg, |
| 77 | + }); |
| 78 | + if (mode === 'ended') |
| 79 | + expect(calls.some((args) => args[0] === 'forward' && args[1] === '--remove')).toBe(true); |
| 80 | + }, |
| 81 | +); |
0 commit comments