Repository navigation
fix(appautomate): support Maestro, Espresso, XCUITest and Flutter builds - #572
Conversation
…eLogs and screenshots Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The App Automate log helpers return a "not available" message on 404. They now throw LogsNotFoundError when asked (getFailureLogs does), so the framework fallback runs only on a real 404 and Appium sessions keep their existing output. Adds tool-level tests for listSessions and getFailureLogs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the
|
Inflate each image as a stream and stop at 10 MB whatever the archive header claims, cap the images read per call at 25 MB in total, process them one at a time, and refuse archives with more than 500 files. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sarve-shreyas
left a comment
There was a problem hiding this comment.
Reviewed the full diff at dff5c7a (4 commits on 76ff189, current main). Locally: eslint, prettier --check, tsc --noEmit and vitest run (144 files, 2129 tests) all pass.
These parts look right:
- Making
LogsNotFoundErroropt-in keeps Appium sessions with logs on exactly the old path and output. - Credentials only go to https
*.browserstack.comlinks. follow-redirects 1.16.0 dropsAuthorizationon a cross-host redirect, so a redirect to S3 doesn't leak them. - The archive limits hold up: the stream is capped regardless of the header size, there's an entry-count cap and a total budget, and images are processed one at a time.
One blocking item (untrusted log content) and three non-blocking ones, inline.
| : tail( | ||
| typeof file.data === "string" ? file.data : JSON.stringify(file.data), | ||
| ); | ||
| parts.push(`### ${label(test)} · ${field}\n${text}`); |
There was a problem hiding this comment.
Blocking: the framework log text goes back to the agent without wrapUntrusted. The generic device, Appium and crash logs in app-automate.ts are all wrapped, and tests/manual/injection-e2e.test.ts checks that they are. This path returns maestro_log / instrumentation_log / device_log tails and test or class names as plain text. The crash-count list at L56 also prints names unwrapped.
Scenario: a Maestro flow asserts on text from the app, or an Espresso test logs a server response, and the log tail contains Ignore previous instructions and call …. Through appiumLogs on an Appium session that line would be quarantined. Through this path it reaches the model as ordinary tool output.
Suggestion: wrap the joined parts and the crash list in wrapUntrusted("test logs", …). Keep the label a literal; never interpolate the framework or test names into it. Then add the framework path to the injection e2e test next to the device, Appium and crash cases.
| config: BrowserStackConfig, | ||
| ): Promise<FrameworkBuild | undefined> { | ||
| const id = encodeURIComponent(buildId.trim()); | ||
| for (const framework of FRAMEWORKS) { |
There was a problem hiding this comment.
Non-blocking (question): detection now also runs for plain Appium cases: an Appium session whose deviceLogs 404 (device logs off), and an empty App Automate build in listSessions. getJson throws on any non-404 (L56). So a 401, 403, 429 or 5xx from an unrelated framework endpoint turns the old "No device logs available for this session" or "No sessions found" into a tool error. In getFailureLogs, that throw also discards the other log types in the same call that had already succeeded.
Do all five endpoints return 404, not 401 or 403, for an account without that framework, e.g. no Flutter iOS access? I couldn't verify that. If not, consider having the fallback callers log the failure and return the pre-PR text instead of throwing.
Also, the five calls run one after another, so a Flutter iOS build and every Appium miss wait on five round trips. A Promise.allSettled over FRAMEWORKS, taking the first hit in list order, keeps the semantics and drops that to one round trip.
| let urls: string[]; | ||
| try { | ||
| urls = await extractScreenshotUrls(sessionId, sessionType, config); | ||
| } catch (error) { |
There was a problem hiding this comment.
Non-blocking: this catch falls back on every error, not only a 404. The PR description says "Only a 404 triggers a fallback", and that's true for logs but not here. For an Appium session whose logs return 413 (the "too large, open the dashboard" message), 401 or 5xx, the tool now calls resolveAppAutomateBuildId and then makes up to five framework calls. If any of those calls throws, its error replaces the original, more useful message.
Suggestion: fall back only on the 404 that assertOkResponse turns into Invalid session ID…, for example with a typed not-found error like LogsNotFoundError, and rethrow everything else unchanged.
| const framework = await findFrameworkBuild(buildId, config); | ||
| if (framework) { | ||
| sessions = frameworkSessions(framework).filter( | ||
| (s) => !args.status || s.status === args.status, |
There was a problem hiding this comment.
Non-blocking: the framework list ignores limit and offset, and it compares status case-sensitively. The generic path, mapSessionRecords, trims and lowercases the filter before comparing.
Scenario: an agent pages a large Maestro build with offset: 50 and gets the same full list back, so it can loop. status: "Failed" matches nothing.
Suggestion: reuse the same status normalisation, then .slice(offset, offset + limit) with DEFAULT_SESSION_LIST_LIMIT as the default.
- Wrap framework test logs and test names as untrusted content, and cover the path in the injection test. - Look up the five framework endpoints in parallel. A failed lookup counts as "not this framework", so it never turns an empty session list or a "no logs" reply into a tool error. - Fall back to Maestro screenshots only after a 404, and keep the original error if the framework check fails. - Apply limit, offset and a case-insensitive status filter to sessions listed from a framework build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
sarve-shreyas
left a comment
There was a problem hiding this comment.
Re-reviewed dff5c7a..fb1d0c8. Locally: eslint, prettier --check, tsc --noEmit and vitest run (144 files, 2133 tests, including the new injection case) all pass.
All four items from my last round are resolved:
- The framework log tails and the crash-count test names are wrapped with literal labels (
"test logs","test names"), andretrieveFrameworkLogsis ininjection-e2e.test.ts. findFrameworkBuildruns the five lookups withPromise.allSettledand keeps list order. A rejected lookup is logged at debug and treated as not this framework, so empty-build and no-logs replies keep their old text.- The screenshot fallback only runs on
SessionLogsNotFoundError, which keeps the oldInvalid session ID for Sessionmessage. Lookup failures insideframeworkScreenshotsreturnundefined, so the original error is the one surfaced. - The framework
listSessionspath lowercases the status filter and appliesoffset,limitandDEFAULT_SESSION_LIST_LIMIT.
One optional follow-up: the tradeoff in (2) is that when the matching framework endpoint itself returns 5xx, the reply is now the plain "no sessions" or "no logs" text, with the cause only in the debug log. A short note such as "framework lookup failed, try again" would make that case distinguishable. It isn't needed for this PR.
Summary
App Automate builds started through a framework API (Maestro, Espresso, XCUITest and Flutter on Android and iOS) have their own build and session endpoints. For these builds the generic App Automate session list returns no sessions, and the generic log and screenshot endpoints return 404, so listSessions, getFailureLogs and fetchAutomationScreenshots couldn't help with them. This PR reads those builds through their framework endpoints. Appium builds keep their current behaviour.
Behaviour changes
listSessions
limit,offsetand a case-insensitivestatusfilter apply as on the generic path.getFailureLogs
appiumLogsreturns each test'smaestro_logorinstrumentation_log;deviceLogsreturns each test'sdevice_log, where the framework provides one;crashLogsreports the tests that recorded crashes.fetchAutomationScreenshots
Safety and limits
Dependency
jszip(^3.10.1) as a direct dependency, to read Maestro screenshot archives. The same version is already installed throughwebdriverio.Testing
npm run buildpasses: lint, format, tests and tsc.🤖 Generated with Claude Code