Skip to content

Commit 68576d5

Browse files
committed
fix: improve maestro test output
1 parent 37618c9 commit 68576d5

7 files changed

Lines changed: 84 additions & 29 deletions

File tree

src/__tests__/cli-network.test.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ test('test command prints suite summary and exits non-zero on failures', async (
106106
assert.equal(result.calls.length, 1);
107107
assert.equal(result.calls[0]?.meta?.requestProgress, 'replay-test');
108108
assert.match(result.stderr, /Running replay suite\.\.\./);
109-
assert.doesNotMatch(result.stdout, /PASS \/tmp\/01-pass\.ad/);
109+
assert.match(result.stdout, /PASS 01-pass\.ad \(0\.01s\)/);
110110
assert.match(
111111
result.stdout,
112112
/FAIL "Checkout failure" in 02-fail\.ad after 2 attempts \(total 0\.005s\)/,
@@ -204,9 +204,9 @@ test('test command --verbose prints step telemetry for passing tests without deb
204204
assert.equal(result.code, null);
205205
assert.equal(result.calls[0]?.meta?.debug, false);
206206
assert.match(result.stdout, /PASS "Authentication flow" \(0\.5s\)/);
207-
assert.match(result.stdout, /steps \(attempt 1\):/);
208-
assert.match(result.stdout, /\[ok\] tapOn "text=\\"Log in\\"" \(line 3, 0\.25s\)/);
209-
assert.match(result.stdout, /\[ok\] assertVisible "text=\\"Home\\"" \(line 4, 0\.075s\)/);
207+
assert.match(result.stdout, /steps:/);
208+
assert.match(result.stdout, /tapOn "text=\\"Log in\\"" \(line 3, 0\.25s\)/);
209+
assert.match(result.stdout, /assertVisible "text=\\"Home\\"" \(line 4, 0\.075s\)/);
210210
} finally {
211211
await fs.rm(tmpDir, { recursive: true, force: true });
212212
}
@@ -248,6 +248,10 @@ test('test command reports flaky passed-on-retry cases in the default summary',
248248
assert.equal(result.code, null);
249249
assert.match(result.stderr, /Running replay suite\.\.\./);
250250
assert.doesNotMatch(result.stdout, /FLAKY/);
251+
assert.match(
252+
result.stdout,
253+
/PASS "Authentication flow" after 2 attempts \(passed attempt 17\.5s, total 112\.2s\)/,
254+
);
251255
assert.match(result.stdout, /Test summary: 1 passed, 0 failed, 1 flaky in 0\.025s/);
252256
assert.match(result.stdout, /Flaky tests:/);
253257
assert.match(
@@ -336,10 +340,7 @@ test('test command prints failed attempt step telemetry when timing trace exists
336340

337341
assert.equal(result.code, 1);
338342
assert.match(result.stdout, /steps \(attempt 2\):/);
339-
assert.match(
340-
result.stdout,
341-
/\[ok\] open "Demo" \(line 3, 0\.125s, timing \{"launchMs":100\}\)/,
342-
);
343+
assert.match(result.stdout, /open "Demo" \(line 3, 0\.125s, timing \{"launchMs":100\}\)/);
343344
assert.match(
344345
result.stdout,
345346
/\[FAIL\] tapOn "text=\\"Pay\\"" \(line 4, 1\.50s, ASSERTION_FAILED\)/,
@@ -381,6 +382,7 @@ test('test --maestro forwards Maestro backend and platform for directory suites'
381382
assert.deepEqual(result.calls[0]?.positionals, [tmpDir]);
382383
assert.equal(result.calls[0]?.flags?.replayBackend, 'maestro');
383384
assert.equal(result.calls[0]?.flags?.platform, 'android');
385+
assert.equal(result.calls[0]?.meta?.requestProgress, 'replay-test');
384386
assert.match(result.stderr, /Running replay suite\.\.\./);
385387
} finally {
386388
await fs.rm(tmpDir, { recursive: true, force: true });

src/cli-test.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ function renderReplayTestSummary(
3737
renderVerboseTestResult(entry);
3838
}
3939
} else {
40-
for (const entry of data.failures) {
41-
renderFailedTestResult(entry);
40+
for (const entry of data.tests) {
41+
renderDefaultTestResult(entry);
4242
}
4343
}
4444

@@ -52,6 +52,18 @@ function renderReplayTestSummary(
5252
return getReplayTestExitCode(data);
5353
}
5454

55+
function renderDefaultTestResult(result: ReplaySuiteTestResult): void {
56+
if (result.status === 'failed') {
57+
renderFailedTestResult(result);
58+
return;
59+
}
60+
if (result.status !== 'passed') return;
61+
62+
process.stdout.write(
63+
`PASS ${replayTestDisplayName(result)}${formatReplayTestDurationSuffix(result)}\n`,
64+
);
65+
}
66+
5567
function renderVerboseTestResult(result: ReplaySuiteTestResult): void {
5668
if (result.status === 'failed') {
5769
renderFailedTestResult(result);
@@ -139,7 +151,7 @@ function replayTestStepLines(result: ReplaySuiteTestResult): string[] {
139151
if (stops.length === 0) return [];
140152

141153
return [
142-
`steps (attempt ${result.attempts}):`,
154+
result.attempts > 1 ? `steps (attempt ${result.attempts}):` : 'steps:',
143155
...stops.map((stop) => renderReplayStepTrace(stop, starts.get(stop.step))),
144156
];
145157
}
@@ -211,8 +223,8 @@ function renderReplayStepTrace(
211223
start: ReplayActionStartTrace | undefined,
212224
): string {
213225
const failed = stop.ok === false;
214-
const status = failed ? '[FAIL]' : stop.ok === true ? '[ok]' : '[info]';
215-
return ` ${status} ${formatReplayStepCommand(start, stop)}${formatReplayStepDetails(stop, start)}`;
226+
const status = failed ? '[FAIL] ' : stop.ok === true ? '' : '[info] ';
227+
return ` ${status}${formatReplayStepCommand(start, stop)}${formatReplayStepDetails(stop, start)}`;
216228
}
217229

218230
function formatReplayStepDetails(

src/daemon/handlers/__tests__/session-test-suite.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,16 +141,19 @@ test('test emits progress when attempts retry and pass', async () => {
141141
expect(events.map((event) => event.status)).toEqual(['fail', 'pass']);
142142
expect(events[0]).toMatchObject({
143143
type: 'replay-test',
144+
title: undefined,
144145
status: 'fail',
145146
index: 1,
146147
total: 1,
147148
attempt: 1,
148149
maxAttempts: 2,
150+
durationMs: expect.any(Number),
149151
retrying: true,
150152
message: 'Replay failed at step 1 (open "Demo"): first attempt failed',
151153
});
152154
expect(events[1]).toMatchObject({
153155
type: 'replay-test',
156+
title: undefined,
154157
status: 'pass',
155158
index: 1,
156159
total: 1,

src/daemon/handlers/session-test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,11 +208,13 @@ async function runReplayTestCase(
208208
emitRequestProgress({
209209
type: 'replay-test',
210210
file: entry.path,
211+
title: entry.title,
211212
status: 'fail',
212213
index: suiteIndex,
213214
total: suiteTotal,
214215
attempt: attempts,
215216
maxAttempts: retries + 1,
217+
durationMs: finalAttemptDurationMs,
216218
retrying: true,
217219
message: response.error.message,
218220
});
@@ -223,6 +225,7 @@ async function runReplayTestCase(
223225
emitRequestProgress({
224226
type: 'replay-test',
225227
file: entry.path,
228+
title: entry.title,
226229
status: 'pass',
227230
index: suiteIndex,
228231
total: suiteTotal,
@@ -255,6 +258,7 @@ async function runReplayTestCase(
255258
emitRequestProgress({
256259
type: 'replay-test',
257260
file: entry.path,
261+
title: entry.title,
258262
status: 'fail',
259263
index: suiteIndex,
260264
total: suiteTotal,

src/daemon/request-progress-protocol.ts

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import path from 'node:path';
12
import type { DaemonRequest, DaemonResponse } from './types.ts';
23
import type { RequestProgressEvent } from './request-progress.ts';
34

@@ -49,19 +50,51 @@ export function serializeDaemonRpcResponseEnvelope(response: unknown): string {
4950

5051
export function formatRequestProgressEvent(event: RequestProgressEvent): string | undefined {
5152
if (event.type !== 'replay-test') return undefined;
52-
const parts = [event.status, `${event.index}/${event.total}`, event.file];
53-
if (event.attempt !== undefined && event.maxAttempts !== undefined) {
54-
parts.push(`attempt=${event.attempt}/${event.maxAttempts}`);
55-
}
56-
if (event.retrying) parts.push('retry=true');
57-
if (event.durationMs !== undefined)
58-
parts.push(`duration=${formatDurationSeconds(event.durationMs)}`);
59-
if (event.artifactsDir && event.status === 'fail') parts.push(`artifacts=${event.artifactsDir}`);
53+
const name = formatReplayTestProgressName(event);
54+
const durationSuffix =
55+
event.durationMs !== undefined ? ` (${formatReplayProgressDuration(event)})` : '';
56+
const attemptSuffix = formatReplayProgressAttemptSuffix(event);
6057
const message = event.message?.replace(/\s+/g, ' ').trim();
61-
if (message) parts.push(message);
62-
return parts.join(' ');
58+
59+
if (event.status === 'pass') {
60+
return `PASS ${name}${attemptSuffix}${durationSuffix}`;
61+
}
62+
if (event.status === 'skip') {
63+
return [`SKIP ${name}`, message ? ` ${message}` : ''].filter(Boolean).join('\n');
64+
}
65+
66+
return [
67+
`FAIL ${name}${attemptSuffix}${durationSuffix}`,
68+
message ? ` ${message}` : '',
69+
event.artifactsDir ? ` artifacts: ${event.artifactsDir}` : '',
70+
]
71+
.filter(Boolean)
72+
.join('\n');
73+
}
74+
75+
function formatReplayTestProgressName(event: RequestProgressEvent): string {
76+
const title = event.title?.trim();
77+
if (title) return JSON.stringify(title);
78+
return path.basename(event.file);
79+
}
80+
81+
function formatReplayProgressAttemptSuffix(event: RequestProgressEvent): string {
82+
if (event.attempt === undefined) return '';
83+
if (event.status === 'fail' && event.retrying && event.maxAttempts !== undefined) {
84+
return ` attempt ${event.attempt}/${event.maxAttempts} retrying`;
85+
}
86+
if (event.attempt > 1) return ` after ${event.attempt} attempts`;
87+
return '';
88+
}
89+
90+
function formatReplayProgressDuration(event: RequestProgressEvent): string {
91+
const duration = formatDurationSeconds(event.durationMs ?? 0);
92+
return event.attempt && event.attempt > 1 && !event.retrying ? `total ${duration}` : duration;
6393
}
6494

6595
function formatDurationSeconds(durationMs: number): string {
66-
return `${(Math.max(0, durationMs) / 1000).toFixed(2)}s`;
96+
const seconds = Math.max(0, durationMs) / 1000;
97+
if (seconds >= 10) return `${seconds.toFixed(1)}s`;
98+
if (seconds >= 1) return `${seconds.toFixed(2)}s`;
99+
return `${seconds.toFixed(3).replace(/0+$/, '').replace(/\.$/, '')}s`;
67100
}

src/daemon/request-progress.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { AsyncLocalStorage } from 'node:async_hooks';
33
export type ReplayTestProgressEvent = {
44
type: 'replay-test';
55
file: string;
6+
title?: string;
67
status: 'pass' | 'fail' | 'skip';
78
index: number;
89
total: number;

src/utils/__tests__/daemon-client.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ test('sendToDaemon prints replay test progress before the socket response', asyn
397397
event: {
398398
type: 'replay-test',
399399
file: '/tmp/01-login.ad',
400+
title: 'Login flow',
400401
status: 'fail',
401402
index: 1,
402403
total: 2,
@@ -440,10 +441,8 @@ test('sendToDaemon prints replay test progress before the socket response', asyn
440441
});
441442

442443
assert.deepEqual(response, { ok: true, data: { via: 'socket' } });
443-
assert.match(
444-
stderr,
445-
/fail 1\/2 \/tmp\/01-login\.ad attempt=1\/2 retry=true duration=1\.23s first attempt failed/,
446-
);
444+
assert.match(stderr, /FAIL "Login flow" attempt 1\/2 retrying \(1\.23s\)/);
445+
assert.match(stderr, / first attempt failed/);
447446
} finally {
448447
(net as unknown as { createConnection: typeof net.createConnection }).createConnection =
449448
originalCreateConnection;
@@ -498,6 +497,7 @@ test('sendToDaemon prints replay test progress before the HTTP NDJSON response',
498497
event: {
499498
type: 'replay-test',
500499
file: '/tmp/02-payments.ad',
500+
title: 'Payments flow',
501501
status: 'pass',
502502
index: 2,
503503
total: 3,
@@ -543,7 +543,7 @@ test('sendToDaemon prints replay test progress before the HTTP NDJSON response',
543543
assert.deepEqual(response, { ok: true, data: { via: 'http-progress' } });
544544
});
545545
assert.deepEqual(seenPaths, ['GET /agent-device/health', 'POST /agent-device/rpc']);
546-
assert.match(stderr, /pass 2\/3 \/tmp\/02-payments\.ad attempt=1\/1 duration=2\.50s/);
546+
assert.match(stderr, /PASS "Payments flow" \(2\.50s\)/);
547547
} finally {
548548
(http as unknown as { request: typeof http.request }).request = originalHttpRequest;
549549
process.stderr.write = originalStderrWrite;

0 commit comments

Comments
 (0)