From 5a4b4428688fa87dd321cecd00358da47115fa7a Mon Sep 17 00:00:00 2001 From: Mohammad Saddam Hossain Date: Fri, 23 Oct 2026 08:33:04 +0530 Subject: [PATCH] feat(ci): link the GitHub Actions failure summary to the run's artifacts (#130) Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 2 + docs-site/docs/changelog.md | 2 + docs-site/docs/ci/github-actions.md | 2 +- docs-site/docs/guides/trace-viewer.md | 10 +++++ .../seleniumboot/ci/GitHubActionsSummary.java | 39 ++++++++++++++++++- .../unit/GitHubActionsSummaryTest.java | 29 ++++++++++++++ 6 files changed, 82 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b1aca7..e72c6d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ### v3.9.0 — 2026-10-21 +- **GitHub Actions job summary: link to the run's artifacts** — the failed-tests summary now adds a line linking to the workflow run page (built from `GITHUB_SERVER_URL`, `GITHUB_REPOSITORY` and `GITHUB_RUN_ID`, no config) where uploaded `target/traces/` artifacts live, so a reviewer can get from a red PR to the trace in one click. Omitted when those variables are missing. (#130) + - **`Locator` keyboard and pointer actions: `press(Keys...)`, `focus()`, `doubleClick()`, `rightClick()`** — send keys without clearing the value, focus without clicking, or perform native double/context clicks directly on a locator. Each action re-resolves on every poll and retries stale or not-yet-present elements until `timeouts.explicit` elapses. (#134, #188) - **HTML report: flaky-test trend line** — the dashboard gains a third "Flaky Test Trend" line chart (flaky tests per run) next to the pass-rate and duration charts. (#185) diff --git a/docs-site/docs/changelog.md b/docs-site/docs/changelog.md index ccd5f6a..140b4d2 100644 --- a/docs-site/docs/changelog.md +++ b/docs-site/docs/changelog.md @@ -11,6 +11,8 @@ All notable changes to Selenium Boot are documented here. ## [3.9.0] — 2026-10-21 +- **GitHub Actions job summary: link to the run's artifacts** — the failed-tests summary now adds a line linking to the workflow run page (built from `GITHUB_SERVER_URL`, `GITHUB_REPOSITORY` and `GITHUB_RUN_ID`, no config) where uploaded `target/traces/` artifacts live, so a reviewer can get from a red PR to the trace in one click. Omitted when those variables are missing. (#130) + - **`Locator` keyboard and pointer actions: `press(Keys...)`, `focus()`, `doubleClick()`, `rightClick()`** — send keys without clearing the value, focus without clicking, or perform native double/context clicks directly on a locator. Each action re-resolves on every poll and retries stale or not-yet-present elements until `timeouts.explicit` elapses. (#134, #188) - **HTML report: flaky-test trend line** — the dashboard gains a third "Flaky Test Trend" line chart (flaky tests per run) next to the pass-rate and duration charts. (#185) diff --git a/docs-site/docs/ci/github-actions.md b/docs-site/docs/ci/github-actions.md index 020dc5f..8b90018 100644 --- a/docs-site/docs/ci/github-actions.md +++ b/docs-site/docs/ci/github-actions.md @@ -73,7 +73,7 @@ When `tracing.enabled: true` is enabled in `selenium-boot.yml` and tests fail, S | --- | --- | --- | | `com.example.LoginTest#testInvalidLogin` | Expected [Dashboard] but found [401 Unauthorized] | target/traces/LoginTest/testInvalidLogin-trace.html | -This lists every failing test, its error message, and the path to its trace file directly in the GitHub Actions run summary page. If no tests fail or outside GitHub Actions, nothing is written. +This lists every failing test, its error message, and the path to its trace file directly in the GitHub Actions run summary page. It also adds a link to the workflow run page, where artifacts are listed; upload `target/traces/` with `actions/upload-artifact` (see the [Trace Viewer guide](../guides/trace-viewer.md#in-github-actions)). If no tests fail or outside GitHub Actions, nothing is written. --- diff --git a/docs-site/docs/guides/trace-viewer.md b/docs-site/docs/guides/trace-viewer.md index cdd76e8..aa82b89 100644 --- a/docs-site/docs/guides/trace-viewer.md +++ b/docs-site/docs/guides/trace-viewer.md @@ -61,3 +61,13 @@ When running in GitHub Actions and `tracing.enabled: true` is on, failed tests a This lets teams triage failures and locate the corresponding HTML trace file directly on the Actions job summary page without downloading and extracting artifact archives. +The summary also links to the workflow run page, where artifacts are listed. Upload the traces in your workflow so the link leads somewhere: + +```yaml + - uses: actions/upload-artifact@v4 + if: always() + with: + name: traces + path: target/traces/ +``` + diff --git a/src/main/java/com/seleniumboot/ci/GitHubActionsSummary.java b/src/main/java/com/seleniumboot/ci/GitHubActionsSummary.java index 3934cb7..a9741a5 100644 --- a/src/main/java/com/seleniumboot/ci/GitHubActionsSummary.java +++ b/src/main/java/com/seleniumboot/ci/GitHubActionsSummary.java @@ -28,6 +28,14 @@ private GitHubActionsSummary() {} * Returns an empty string if {@code timings} is null or contains no failed tests. */ public static String buildMarkdown(Collection timings) { + return buildMarkdown(timings, null); + } + + /** + * Same as {@link #buildMarkdown(Collection)}, and when {@code runUrl} is set, adds a line + * under the table linking to the workflow run page where uploaded trace artifacts live. + */ + public static String buildMarkdown(Collection timings, String runUrl) { if (timings == null || timings.isEmpty()) { return ""; } @@ -58,9 +66,37 @@ public static String buildMarkdown(Collection timings) { .append(" |\n"); } + if (runUrl != null && !runUrl.isBlank()) { + sb.append("\nTraces are in this run's [artifacts](").append(runUrl.trim()) + .append(") if `").append(base(baseDir)).append("/traces/` is uploaded with `actions/upload-artifact`.\n"); + } + return sb.toString(); } + /** + * Builds the workflow run page URL from the GitHub Actions environment, or {@code null} + * when any of the three values is missing. + */ + public static String runUrl(String serverUrl, String repository, String runId) { + if (isBlank(serverUrl) || isBlank(repository) || isBlank(runId)) { + return null; + } + String server = serverUrl.trim(); + if (server.endsWith("/")) { + server = server.substring(0, server.length() - 1); + } + return server + "/" + repository.trim() + "/actions/runs/" + runId.trim(); + } + + private static boolean isBlank(String s) { + return s == null || s.isBlank(); + } + + private static String base(String baseDir) { + return isBlank(baseDir) ? "target" : baseDir.replace('\\', '/').trim().replaceAll("/+$", ""); + } + /** * Appends failed tests with trace links to {@code summaryFile} when tracing is enabled * and at least one test failed. @@ -87,7 +123,8 @@ public static boolean appendSummary(SeleniumBootConfig config, return false; } - String markdown = buildMarkdown(timings); + String markdown = buildMarkdown(timings, + runUrl(System.getenv("GITHUB_SERVER_URL"), System.getenv("GITHUB_REPOSITORY"), System.getenv("GITHUB_RUN_ID"))); if (markdown.isEmpty()) { return false; } diff --git a/src/test/java/com/seleniumboot/unit/GitHubActionsSummaryTest.java b/src/test/java/com/seleniumboot/unit/GitHubActionsSummaryTest.java index 47b998b..3af2cb5 100644 --- a/src/test/java/com/seleniumboot/unit/GitHubActionsSummaryTest.java +++ b/src/test/java/com/seleniumboot/unit/GitHubActionsSummaryTest.java @@ -95,6 +95,35 @@ public void buildMarkdown_singleFailedTest_generatesTable() { "Trace path must include base dir and relative trace file path"); } + @Test + public void buildMarkdown_withRunUrl_linksToRunArtifacts() { + TestTiming failed = timing("t1", "LoginTest", "FAILED", "boom", "traces/LoginTest/t1-trace.html"); + + String markdown = GitHubActionsSummary.buildMarkdown(List.of(failed), + "https://github.com/acme/app/actions/runs/42"); + + assertTrue(markdown.contains("[artifacts](https://github.com/acme/app/actions/runs/42)"), + "Must link to the workflow run page"); + assertTrue(markdown.contains("actions/upload-artifact"), "Must hint how traces get there"); + } + + @Test + public void buildMarkdown_withoutRunUrl_hasNoArtifactsLine() { + TestTiming failed = timing("t1", "LoginTest", "FAILED", "boom", "traces/LoginTest/t1-trace.html"); + + assertFalse(GitHubActionsSummary.buildMarkdown(List.of(failed)).contains("artifacts")); + assertFalse(GitHubActionsSummary.buildMarkdown(List.of(failed), " ").contains("artifacts")); + } + + @Test + public void runUrl_buildsFromEnvironmentValues() { + assertEquals(GitHubActionsSummary.runUrl("https://github.com/", "acme/app", "42"), + "https://github.com/acme/app/actions/runs/42"); + assertNull(GitHubActionsSummary.runUrl(null, "acme/app", "42")); + assertNull(GitHubActionsSummary.runUrl("https://github.com", "", "42")); + assertNull(GitHubActionsSummary.runUrl("https://github.com", "acme/app", null)); + } + @Test public void buildMarkdown_multipleFailedTests_generatesMultipleRows() { List timings = List.of(