From bd87d8e8771f5d7459af2e84253408a90647409b Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Thu, 29 Oct 2026 21:02:36 +1100 Subject: [PATCH] fix(ci): read the suite file of Flutter widget tests in flake check The Flutter flake-check lane marked every iteration FAILED on PR #8601 even though `flutter test` passed all 65 selected tests. The Dart JSON reporter gives a test declared through a helper, which includes every `testWidgets`, the helper's `package:flutter_test/...` url and puts the suite file in `root_url`. parseDartReport passed that `package:` url to fileURLToPath, which throws ERR_INVALID_URL_SCHEME. repeatDart's catch then turned the throw into an empty status map, so any selection that contained a widget test could never pass. parseDartReport now prefers `root_url` and falls back to `url`. Parsing the CI run's iter-1.json report now gives 65 passed and 1 skipped, which matches flutter's own summary. The parser table gains a widget-test event, and that row fails with ERR_INVALID_URL_SCHEME without the fix. Co-Authored-By: Claude Opus 5.5 Signed-off-by: Matt Toohey --- scripts/flake-check.mjs | 7 ++++++- scripts/flake-check.test.mjs | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/scripts/flake-check.mjs b/scripts/flake-check.mjs index 3dd4fe570d..ee23cd76cc 100644 --- a/scripts/flake-check.mjs +++ b/scripts/flake-check.mjs @@ -1115,6 +1115,8 @@ function repeatNode(files, iterations) { // Per-test statuses of a `dart test` JSON report (one event per line), keyed // ` › `. Hidden entries are the runner's own file-loading steps. +// A test declared through a helper (every `testWidgets`) has the helper's +// `package:` url; `root_url` then names the suite file. export function parseDartReport(text, mobileRoot = `${root}mobile/`) { const started = new Map(); const statuses = new Map(); @@ -1129,7 +1131,10 @@ export function parseDartReport(text, mobileRoot = `${root}mobile/`) { if (event.type !== "testDone" || event.hidden) continue; const test = started.get(event.testID); if (!test) continue; - const file = fileURLToPath(test.url ?? "file:///").replace(mobileRoot, ""); + const file = fileURLToPath(test.root_url ?? test.url ?? "file:///").replace( + mobileRoot, + "", + ); statuses.set( `${file} › ${test.name}`, event.skipped diff --git a/scripts/flake-check.test.mjs b/scripts/flake-check.test.mjs index 5450e484a1..555142059c 100644 --- a/scripts/flake-check.test.mjs +++ b/scripts/flake-check.test.mjs @@ -951,6 +951,15 @@ test("reports give per-test statuses per iteration", () => { type: "testStart", test: { id: 4, name: "later", url: "file:///m/test/b_test.dart" }, }), + event({ + type: "testStart", + test: { + id: 5, + name: "widget", + url: "package:flutter_test/src/widget_tester.dart", + root_url: "file:///m/test/b_test.dart", + }, + }), "not json", event({ type: "testDone", @@ -973,6 +982,13 @@ test("reports give per-test statuses per iteration", () => { hidden: false, skipped: true, }), + event({ + type: "testDone", + testID: 5, + result: "success", + hidden: false, + skipped: false, + }), event({ type: "done", success: false }), ].join("\n"), "/m/", @@ -981,6 +997,7 @@ test("reports give per-test statuses per iteration", () => { ["test/a_test.dart › group renders", "passed"], ["test/a_test.dart › group fails", "failed"], ["test/b_test.dart › later", "skipped"], + ["test/b_test.dart › widget", "passed"], ]), ); // Playwright lists a --repeat-each journey once per repetition, in order.