Skip to content

Commit 9fc2663

Browse files
authored
fix: stabilize Replay Nightly fixture boundaries (#1610)
* fix: stabilize replay nightly fixture boundaries * test: stabilize exit flush integration coverage * chore: drop the deleted exit-naive fixture from fallow's entry list (#1610 review P3)
1 parent a13a683 commit 9fc2663

9 files changed

Lines changed: 31 additions & 37 deletions

.fallowrc.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,9 @@
2323
"scripts/vitest-runner-timeout-setup.ts",
2424
"test/contention-retry-fixtures/vitest.fixture.config.ts",
2525
"test/contention-retry-fixtures/timeout-provenance.fixture.ts",
26-
// #1596 regression fixtures: run as real `node --experimental-strip-types`
27-
// subprocesses (test/integration/daemon-replace-exit-flush.test.ts), so
28-
// dependency analysis cannot follow the runCmdSync string path to either.
29-
"test/integration/support/exit-naive.ts",
26+
// #1596 regression fixture: runs as a real `node --experimental-strip-types`
27+
// subprocess (test/integration/daemon-replace-exit-flush.test.ts), so
28+
// dependency analysis cannot follow the runCmdSync string path to it.
3029
"test/integration/support/exit-after-flush.ts",
3130
"src/utils/update-check-entry.ts",
3231
"examples/sdk/client-session.ts",

test/integration/android-emulator-e2e/live-lifecycle-scenario.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { type LiveContext, runStep, verifyBehavior, verifyCommand } from './live
77
const C = PUBLIC_COMMANDS;
88
const AUTOMATION_DEEP_LINK =
99
'agent-device-test-app:///automation?event=full.lifecycle&payload=%7B%22source%22%3A%22android-nightly%22%7D';
10+
export const ANDROID_PERMISSION_PROMPT_COMMAND = ['alert', 'wait', '10000'] as const;
1011

1112
export async function assertLifecycleAndSystem(context: LiveContext): Promise<void> {
1213
await runStep(context, 'open Android fixture lifecycle route', [
@@ -73,8 +74,7 @@ async function resetAndRequestMicrophonePermission(
7374
'id="automation-request-microphone"',
7475
]);
7576
const prompt = await runStep(context, `inspect Android microphone prompt for ${action}`, [
76-
'alert',
77-
'get',
77+
...ANDROID_PERMISSION_PROMPT_COMMAND,
7878
]);
7979
assert.equal(prompt.json?.data?.alert?.source, 'permission', JSON.stringify(prompt.json));
8080
const alertAction = action === 'deny' ? 'dismiss' : 'accept';

test/integration/daemon-replace-exit-flush.test.ts

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -86,22 +86,10 @@ test('daemon replace mid-command returns a structured, parseable error and exits
8686
}
8787
});
8888

89-
// Isolates the exact mechanism from the end-to-end test above: Node flushes
90-
// stdout/stderr synchronously only to a file or TTY, so `process.exit()`
91-
// called right after a write can drop that write when the stream is a pipe
92-
// (this CLI's normal condition, driven as a subprocess). Runs the write+exit
93-
// sequence directly as a real piped child process, independent of any
94-
// daemon/device setup, so the mechanism itself is proven deterministically.
95-
test('a bare process.exit() after a large write truncates it on a piped stream', () => {
96-
const { exitCode, stderr } = runFixture('exit-naive.ts');
97-
assert.equal(exitCode, 1);
98-
assert.ok(
99-
!stderr.includes(PAYLOAD_MARKER),
100-
'expected the naive exit to truncate before the trailing marker; the pipe-buffer ' +
101-
'reproduction this test depends on may not hold on this platform',
102-
);
103-
});
104-
89+
// Isolates the delivery guarantee from the end-to-end test above. The fixture
90+
// writes a large payload through a real piped subprocess without asserting
91+
// that the unsafe alternative must truncate under a particular OS, Node
92+
// version, pipe configuration, or scheduler interleaving.
10593
test('exitAfterFlush (the #1596 fix) delivers the full write before the process exits', () => {
10694
const { exitCode, stderr } = runFixture('exit-after-flush.ts');
10795
assert.equal(exitCode, 1);

test/integration/replays/ios/fixture/01-navigation-scroll.ad

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ context platform=ios kind=simulator timeout=120000
33

44
env APP_TARGET="Agent Device Tester"
55

6-
open "${APP_TARGET}" --relaunch
6+
open "${APP_TARGET}" --relaunch --launch-url "agent-device-test-app:///"
77
wait "Agent Device Tester" 30000
88
click "label=\"Catalog\""
99
wait "Search, filter, scroll, favorite, and drill into detail without extra dependencies." 5000

test/integration/smoke-android-emulator-coverage.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import test from 'node:test';
44
import { PUBLIC_COMMANDS } from '../../src/command-catalog.ts';
55
import { isCommandSupportedOnDevice } from '../../src/core/capabilities.ts';
66
import { ANDROID_EMULATOR_BEHAVIOR_COVERAGE } from './android-emulator-e2e/behavior-coverage.ts';
7+
import { ANDROID_PERMISSION_PROMPT_COMMAND } from './android-emulator-e2e/live-lifecycle-scenario.ts';
78
import {
89
ANDROID_EMULATOR_COVERAGE_CLASSIFICATION_SUMMARY,
910
ANDROID_EMULATOR_E2E_COVERAGE,
@@ -136,6 +137,10 @@ test('Android app scenarios declare deterministic starting surfaces and IME mode
136137
);
137138
});
138139

140+
test('Android permission recovery waits for the asynchronous native prompt', () => {
141+
assert.deepEqual(ANDROID_PERMISSION_PROMPT_COMMAND, ['alert', 'wait', '10000']);
142+
});
143+
139144
test('Android emulator scenarios can be selected as an ordered subset', () => {
140145
assert.deepEqual(
141146
selectAndroidEmulatorScenarios(['smoke:keyboard-ime', 'smoke:automation-system']).map(

test/integration/smoke-ios-simulator-coverage.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,18 @@ test('fixture navigation uses edge-aware traversal without losing direct swipe e
251251
);
252252
});
253253

254+
test('fixture navigation establishes the home route before selecting Catalog', () => {
255+
const actions = parseReplayScriptDetailed(
256+
fs.readFileSync('test/integration/replays/ios/fixture/01-navigation-scroll.ad', 'utf8'),
257+
).actions;
258+
const open = actions[0];
259+
assert.equal(open?.command, 'open');
260+
assert.equal(open?.flags.relaunch, true);
261+
assert.equal(open?.runtime?.launchUrl, 'agent-device-test-app:///');
262+
assert.equal(actions[2]?.command, 'click');
263+
assert.equal(actions[2]?.positionals?.[0], 'label="Catalog"');
264+
});
265+
254266
test('event timeline coverage follows cursors beyond the first page', async () => {
255267
const requestedCursors: Array<string | undefined> = [];
256268
const timeline = await collectPagedEventTimeline(async (cursor) => {

test/integration/support/exit-after-flush.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// Counterpart to exit-naive.ts using the #1596 fix: same oversized write,
2-
// exited through `exitAfterFlush` instead of a bare `process.exit()`.
1+
// #1596 fixture: writes an oversized payload to piped stderr, then exits
2+
// through `exitAfterFlush` so the parent must receive the trailing marker.
33
import { exitAfterFlush } from '../../../src/utils/process-exit.ts';
44
import { buildPayload } from './exit-payload.ts';
55

test/integration/support/exit-naive.ts

Lines changed: 0 additions & 9 deletions
This file was deleted.

test/integration/support/exit-payload.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
// Shared payload for the #1596 exit-flush fixtures: sized past a pipe's
2-
// kernel buffer (64 KiB on macOS/Linux) so a truncated write is observable,
3-
// and ends with a marker that only survives the write if it wasn't cut off.
1+
// Large payload for the #1596 exit-flush fixture, ending with a marker that
2+
// proves the whole write reached the parent process.
43
export const PAYLOAD_MARKER = 'EXIT_PAYLOAD_END_MARKER';
54
const PAYLOAD_BYTES = 200_000;
65

0 commit comments

Comments
 (0)