diff --git a/test/integration/provider-scenarios/android-recording-fixtures.ts b/test/integration/provider-scenarios/android-recording-fixtures.ts index a8ea3d899c..b3a1e548be 100644 --- a/test/integration/provider-scenarios/android-recording-fixtures.ts +++ b/test/integration/provider-scenarios/android-recording-fixtures.ts @@ -75,7 +75,9 @@ export function seedAndroidRecordingResource( scope: manifest.scope, showTouches: manifest.showTouches, recordOnlySession: manifest.recordOnlySession, - exportQuality: manifest.exportQuality, + ...(manifest.exportQuality === undefined + ? {} + : { exportQuality: manifest.exportQuality }), transportMode: manifest.transportMode, }, }, diff --git a/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts b/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts index ccbad035da..c2c30ca956 100644 --- a/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts +++ b/test/integration/provider-scenarios/android-recording-manifest-fixtures.ts @@ -21,7 +21,7 @@ export type AndroidRecordingManifestFixture = { scope: 'device'; showTouches: boolean; recordOnlySession: boolean; - exportQuality: 'medium' | 'high'; + exportQuality?: 'medium' | 'high'; transportMode: 'transport-composed'; chunks: NativeChunk[]; pendingRemotePath?: string; @@ -126,7 +126,7 @@ function hasCaptureSettings(value: JsonObject): boolean { value.scope === 'device' && isBooleanProperty(value, 'showTouches') && isBooleanProperty(value, 'recordOnlySession') && - isExportQuality(value.exportQuality) && + isOptionalExportQuality(value.exportQuality) && value.transportMode === 'transport-composed' && hasChunkList(value.chunks) && hasOptionalString(value, 'pendingRemotePath') && @@ -174,6 +174,10 @@ function isExportQuality(value: JsonValue | undefined): boolean { return value === 'medium' || value === 'high'; } +function isOptionalExportQuality(value: JsonValue | undefined): boolean { + return value === undefined || isExportQuality(value); +} + function isJsonObject(value: unknown): value is JsonObject { return ( typeof value === 'object' && value !== null && !Array.isArray(value) && isJsonRecord(value) diff --git a/test/integration/provider-scenarios/android-recording-provider-fixtures.test.ts b/test/integration/provider-scenarios/android-recording-provider-fixtures.test.ts new file mode 100644 index 0000000000..b18dda31f4 --- /dev/null +++ b/test/integration/provider-scenarios/android-recording-provider-fixtures.test.ts @@ -0,0 +1,12 @@ +import assert from 'node:assert/strict'; +import { test } from 'vitest'; +import { createAndroidRecordingProvider } from './android-recording-provider-fixtures.ts'; + +test('Android recording provider fixtures reject unmodeled executable commands', async () => { + const provider = createAndroidRecordingProvider({ calls: [] }); + + await assert.rejects( + provider.exec(['shell', 'echo unmodeled-provider-command']), + /Unhandled Android recording provider command: shell echo unmodeled-provider-command/, + ); +}); diff --git a/test/integration/provider-scenarios/android-recording-provider-fixtures.ts b/test/integration/provider-scenarios/android-recording-provider-fixtures.ts index 31d4e39316..e914160ba5 100644 --- a/test/integration/provider-scenarios/android-recording-provider-fixtures.ts +++ b/test/integration/provider-scenarios/android-recording-provider-fixtures.ts @@ -50,7 +50,9 @@ function respondToCommand( const pull = respondToPull(args, params, state); if (pull) return pull; if (args.join(' ') === 'shell getprop sys.boot_completed') return ok('1\n'); - return respondToShellCommand(args[1] ?? '', state); + const response = args[0] === 'shell' ? respondToShellCommand(args[1] ?? '', state) : undefined; + if (response) return response; + throw new Error(`Unhandled Android recording provider command: ${args.join(' ')}`); } function respondToPull( @@ -108,12 +110,21 @@ function respondToManifestTarget( function respondToProcessCommand(command: string, processes: Map) { return ( + respondToProcessList(command, processes) ?? respondToProcessDirectory(command, processes) ?? respondToProcessMetadata(command, processes) ?? respondToProcessSignal(command, processes) ); } +function respondToProcessList(command: string, processes: Map) { + if (command !== 'ps -A -o pid=') return undefined; + const alivePids = [...processes.entries()] + .filter(([, nativeProcess]) => nativeProcess.alive) + .map(([pid]) => pid); + return ok(alivePids.length > 0 ? `${alivePids.join('\n')}\n` : ''); +} + function respondToProcessDirectory(command: string, processes: Map) { const directory = /^test -d \/proc\/(\d+)$/.exec(command); const [, directoryPid] = directory ?? []; @@ -148,8 +159,16 @@ function respondToScreenrecordCommand(command: string, processes: Map