[build-tools] Add eas/run_maestro_tests step with smart retry#3635
[build-tools] Add eas/run_maestro_tests step with smart retry#3635
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3635 +/- ##
==========================================
+ Coverage 55.97% 56.08% +0.12%
==========================================
Files 866 866
Lines 37312 37439 +127
Branches 7759 7785 +26
==========================================
+ Hits 20882 20995 +113
- Misses 16334 16348 +14
Partials 96 96 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
13e1d4c to
c0d875b
Compare
This stack of pull requests is managed by Graphite. Learn more about stacking. |
c0d875b to
0c794e5
Compare
a89b35a to
9806cfe
Compare
0c794e5 to
64b5fa4
Compare
|
Subscribed to pull request
Generated by CodeMention |
sjchmiela
left a comment
There was a problem hiding this comment.
let's make this opt-in?
| if (isFilesystemError(mergeErr)) { | ||
| throw new SystemError('Failed to write final_report_path', { cause: mergeErr }); | ||
| } | ||
| logger.warn({ err: mergeErr }, 'Smart merge failed; falling back to copy-latest.'); |
There was a problem hiding this comment.
let's make it more user-understandable
There was a problem hiding this comment.
I think this is covered in the next PR: https://github.com/expo/eas-cli/pull/3639/changes#diff-fa4d0db2be5715542edd5cf992cc4aaedbc5349a1663bd70164767b85707f2f7R262-R267
| try { | ||
| resolvedRoot = await fs.realpath(projectRoot); | ||
| } catch {} |
There was a problem hiding this comment.
i like asyncResult(fs.realpath(projectRoot)) for stuff like this
There was a problem hiding this comment.
This is removed in the next PR as well. I'll skip it here.
| ); | ||
| const fileGroups: FileGroup[] = []; | ||
| const skippedFiles: string[] = []; | ||
| for (const { filename, content } of contents) { |
There was a problem hiding this comment.
what would your parser do if instead of merging in this smart way you did
<testsuites>
<testsuite>
{...testsuitesfromfirstfile}
{...testsuitesfromsecondfile}
{...testsuitesfromthirdfile}
?
There was a problem hiding this comment.
One thing to clarify first — this merge isn’t for our own parsers. All of our internal parsers read the per-attempt attempt-*.xml files directly from junit_report_directory. The merged final_report_path only exists as a human-facing artifact for customers who download it and run their own analysis.
Given that, two reasons I’d prefer to keep the smart merge:
-
JUnit shape — Concatenating attempt-N
<testsuite>s under one<testsuites>creates an ambiguous shape (e.g.flow-a, flow-b, flow-c, flow-b, flow-bfor retries). It’s not a shape that Maestro emits. -
Backwards compatibility — Changing this to concat would likely break existing customer aggregation logic that depends on the current
final_report_pathshape.
64b5fa4 to
08a9151
Compare
9806cfe to
9d16d62
Compare
08a9151 to
11f2a04
Compare
9d16d62 to
b3f6380
Compare
aa0cf30 to
e222a62
Compare
In `reuse_devices` mode, narrow the flow_paths to only the failing flows on retry. After a failed attempt, parse the per-attempt JUnit XML to identify the flows that failed and pass just those to the next maestro invocation. Falls back to retrying everything when the JUnit cannot be trusted (missing testcases, conflicting names, malformed XML). Replaces copy-latest with mergeJUnitReports — picks the latest attempt's result per flow across retries — so the merged final report reflects the best outcome each flow achieved. Falls back to copy-latest on data errors. Signed-off-by: Ash Wu <hsatac@gmail.com>
Signed-off-by: Ash Wu <hsatac@gmail.com>
e222a62 to
14e6591
Compare
|
⏩ The changelog entry check has been skipped since the "no changelog" label is present. |

Why
After porting the Maestro step to TS (#3641), we want to stop re-running the entire flow set on every retry. With dozens of flows a single flaky one masks other failures and inflates the whole run. Smart retry re-runs only the flows that actually failed.
Linear: ENG-19927. Stacked on #3641.
How
After each failed attempt, parse the per-attempt JUnit XML, find the failing testcase names, map them back to flow file paths, and pass only those paths to the next
maestro testinvocation.parseFailedFlowsFromJUnitreturns the failed-flow subset, ornullwhen the result can't be trusted (malformed XML, duplicate testcase names, unknown flow). Onnullthe loop falls back to dumb retry, preserving the previous behavior.mergeJUnitReports, which keeps the latest result per flow across attempts. The merged final report reflects each flow's best outcome (a flow that flaked once and passed on retry shows as passed).The
flow_name → flow_file_pathmap is built from Maestro'sai-*.jsondebug output. PR #3639 (next in stack) replaces this with a self-built map after Maestro 2.5.0 broke this assumption.Test Plan
maestroResultParser.test.tsforparseFailedFlowsFromJUnit(happy path, all-passing, collisions, malformed XML, partial parse) andmergeJUnitReports(per-flow latest, fallback on data errors).runMaestroTests.test.tsexercising the smart-retry loop.