From e736fddbe4b3588a24bf0a965d98092ba8cfcaf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Tue, 15 Sep 2026 16:27:49 +0200 Subject: [PATCH] fix(android): warn about the 180s record limit only when the last chunk ended early stopOwnedChunks OR-ed every chunk's already-exited result, and rotation always ends every earlier chunk before record stop, so every chunked recording got the 'stopped before record stop' truncation warning. Only the active (last) chunk exiting before stop means the video ends early; recovery already judged that from chunks.at(-1). --- CHANGELOG.md | 3 ++ .../src/recording/chunks.test.ts | 46 +++++++++++++++++++ .../platform-android/src/recording/chunks.ts | 12 +++-- 3 files changed, 58 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ccd1888b73..9b74095056 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Fixed (android): a chunked `record stop` (recordings over 170 s) no longer warns that screenrecord + stopped before record stop at the 180 s limit. Rotation always ends every earlier chunk before + stop, so the warning now fires only when the last chunk's recorder had already exited. - Fixed: an iOS snapshot whose XCTest query-sweep tier cannot read the screen no longer ends the runner process. On a live React Native feed (Bluesky Home, images re-rendering) the AX server rejects each of the sweep's 19 element-type queries with `kAXErrorIllegalArgument`, and XCTest diff --git a/packages/platform-android/src/recording/chunks.test.ts b/packages/platform-android/src/recording/chunks.test.ts index e65c3476cf..f924a9b128 100644 --- a/packages/platform-android/src/recording/chunks.test.ts +++ b/packages/platform-android/src/recording/chunks.test.ts @@ -66,6 +66,52 @@ test('returns secondary client paths, split/180s warnings, and skips chunked tou } }); +async function finishedWarning(params: { + rotate: boolean; + exitedBeforeStop: readonly string[]; +}): Promise { + vi.useFakeTimers(); + try { + let nextPid = 41; + const exited = new Set(params.exitedBeforeStop); + const runtime = await start({ + start: async () => recordingProcess(String(++nextPid)), + inspect: async ({ pid }: { pid: string }) => (exited.has(pid) ? 'missing' : 'owned-alive'), + stop: async ({ pid }: { pid: string }) => { + if (exited.has(pid)) return 'already-missing' as const; + exited.add(pid); + return 'stopped' as const; + }, + }); + const started = await runtime.screenRecordingStart(recordingInput()); + const handle = started.pendingHandle.transfer(); + if (params.rotate) await vi.advanceTimersByTimeAsync(170_000); + const finishing = handle.finish(); + await vi.advanceTimersByTimeAsync(1_000); + const outcome = await finishing; + expect(outcome.status).toBe('completed'); + return outcome.status === 'completed' ? outcome.result.warning : undefined; + } finally { + vi.useRealTimers(); + } +} + +test('omits the 180s limit warning when only a rotated-out chunk exited before record stop', async () => { + // Rotation stops chunk 42, so it is already missing at record stop; chunk 43 is still recording. + const warning = await finishedWarning({ rotate: true, exitedBeforeStop: [] }); + expect(warning).toContain('split into multiple MP4 chunks'); + expect(warning).not.toContain('likely after reaching the 180s platform limit'); +}); + +test('warns about the 180s limit when the last chunk exited before record stop', async () => { + await expect(finishedWarning({ rotate: false, exitedBeforeStop: ['42'] })).resolves.toContain( + 'likely after reaching the 180s platform limit', + ); + await expect(finishedWarning({ rotate: true, exitedBeforeStop: ['43'] })).resolves.toContain( + 'likely after reaching the 180s platform limit', + ); +}); + test('continues through every owned chunk after a stop or removal failure', async () => { const chunks = [ { diff --git a/packages/platform-android/src/recording/chunks.ts b/packages/platform-android/src/recording/chunks.ts index 83d470b417..d7288c11db 100644 --- a/packages/platform-android/src/recording/chunks.ts +++ b/packages/platform-android/src/recording/chunks.ts @@ -80,21 +80,27 @@ function validProcessIdentity( ); } +/** + * Resolves whether the active (last) chunk's recorder had already exited, i.e. the video ends before + * record stop. Earlier chunks always end before stop because rotation replaced them. + */ export async function stopOwnedChunks( transport: Transport, chunks: readonly NativeChunk[], ): Promise { - let reachedLimit = false; + const active = chunks.at(-1); + let activeAlreadyExited = false; let failure: unknown; for (const chunk of [...chunks].reverse()) { try { - reachedLimit = (await stopChunk(transport, chunk)) || reachedLimit; + const alreadyExited = await stopChunk(transport, chunk); + if (chunk === active) activeAlreadyExited = alreadyExited; } catch (error) { failure ??= error; } } if (failure) throw failure; - return reachedLimit; + return activeAlreadyExited; } export async function waitForStableArtifacts(