Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/platforms/ios/__tests__/runner-client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -720,6 +720,64 @@ Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
);
});

test('parseRunnerResponse classifies explicit target app crashes from runner log tail', async () => {
const logPath = writeRunnerLogTail(`
AGENT_DEVICE_RUNNER_COMMAND_FAILED command=snapshot
The application under test terminated unexpectedly.
`);
const response = new Response(
JSON.stringify({
ok: false,
error: {
code: 'COMMAND_FAILED',
message: 'Runner error',
},
}),
);
const session = { ready: true };

await assert.rejects(
() => parseRunnerResponse(response, session, logPath),
(error: unknown) => {
assert.ok(error instanceof AppError);
assert.equal(error.code, 'IOS_TARGET_APP_CRASH');
assert.equal(error.details?.runnerFailureReason, 'target_app_crash');
assert.match(String(error.details?.hint), /target iOS app appears to have crashed/);
assert.equal(isRetryableRunnerError(error), false);
return true;
},
);
});

test('parseRunnerResponse does not classify incidental XCTest crash text as target app crash', async () => {
const logPath = writeRunnerLogTail(`
XCTest runner recovered from a previous test note: the word crashed appeared in debug output.
AGENT_DEVICE_RUNNER_COMMAND_FAILED command=snapshot error=fetch failed
`);
const response = new Response(
JSON.stringify({
ok: false,
error: {
code: 'COMMAND_FAILED',
message: 'fetch failed',
},
}),
);
const session = { ready: true };

await assert.rejects(
() => parseRunnerResponse(response, session, logPath),
(error: unknown) => {
assert.ok(error instanceof AppError);
assert.equal(error.code, 'COMMAND_FAILED');
assert.equal(error.details?.runnerFailureReason, undefined);
assert.equal(error.details?.hint, undefined);
assert.equal(isRetryableRunnerError(error), true);
return true;
},
);
});

test('parseRunnerResponse keeps ordinary runner failures generic without crash log evidence', async () => {
const logPath = writeRunnerLogTail(
'AGENT_DEVICE_RUNNER_COMMAND_FAILED command=type error=main thread execution timed out',
Expand Down
3 changes: 1 addition & 2 deletions src/platforms/ios/runner-failure-diagnostics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,7 @@ function isTargetAppCrash(normalized: string): boolean {
normalized.includes('process crashed') ||
normalized.includes('the application under test') ||
normalized.includes('terminated unexpectedly') ||
(normalized.includes('exception type:') && normalized.includes('thread 0 crashed')) ||
(normalized.includes('crashed') && normalized.includes('xctest'))
(normalized.includes('exception type:') && normalized.includes('thread 0 crashed'))
);
}

Expand Down
Loading