Skip to content

Commit 5a4b442

Browse files
shossain786claude
andcommitted
feat(ci): link the GitHub Actions failure summary to the run's artifacts (#130)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
1 parent b0e283a commit 5a4b442

6 files changed

Lines changed: 82 additions & 2 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
99

1010
### v3.9.0 — 2026-10-21
1111

12+
- **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)
13+
1214
- **`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)
1315

1416
- **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)

‎docs-site/docs/changelog.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ All notable changes to Selenium Boot are documented here.
1111

1212
## [3.9.0] — 2026-10-21
1313

14+
- **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)
15+
1416
- **`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)
1517

1618
- **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)

‎docs-site/docs/ci/github-actions.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ When `tracing.enabled: true` is enabled in `selenium-boot.yml` and tests fail, S
7373
| --- | --- | --- |
7474
| `com.example.LoginTest#testInvalidLogin` | Expected [Dashboard] but found [401 Unauthorized] | target/traces/LoginTest/testInvalidLogin-trace.html |
7575

76-
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.
76+
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.
7777

7878
---
7979

‎docs-site/docs/guides/trace-viewer.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,13 @@ When running in GitHub Actions and `tracing.enabled: true` is on, failed tests a
6161

6262
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.
6363

64+
The summary also links to the workflow run page, where artifacts are listed. Upload the traces in your workflow so the link leads somewhere:
65+
66+
```yaml
67+
- uses: actions/upload-artifact@v4
68+
if: always()
69+
with:
70+
name: traces
71+
path: target/traces/
72+
```
73+

‎src/main/java/com/seleniumboot/ci/GitHubActionsSummary.java‎

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ private GitHubActionsSummary() {}
2828
* Returns an empty string if {@code timings} is null or contains no failed tests.
2929
*/
3030
public static String buildMarkdown(Collection<TestTiming> timings) {
31+
return buildMarkdown(timings, null);
32+
}
33+
34+
/**
35+
* Same as {@link #buildMarkdown(Collection)}, and when {@code runUrl} is set, adds a line
36+
* under the table linking to the workflow run page where uploaded trace artifacts live.
37+
*/
38+
public static String buildMarkdown(Collection<TestTiming> timings, String runUrl) {
3139
if (timings == null || timings.isEmpty()) {
3240
return "";
3341
}
@@ -58,9 +66,37 @@ public static String buildMarkdown(Collection<TestTiming> timings) {
5866
.append(" |\n");
5967
}
6068

69+
if (runUrl != null && !runUrl.isBlank()) {
70+
sb.append("\nTraces are in this run's [artifacts](").append(runUrl.trim())
71+
.append(") if `").append(base(baseDir)).append("/traces/` is uploaded with `actions/upload-artifact`.\n");
72+
}
73+
6174
return sb.toString();
6275
}
6376

77+
/**
78+
* Builds the workflow run page URL from the GitHub Actions environment, or {@code null}
79+
* when any of the three values is missing.
80+
*/
81+
public static String runUrl(String serverUrl, String repository, String runId) {
82+
if (isBlank(serverUrl) || isBlank(repository) || isBlank(runId)) {
83+
return null;
84+
}
85+
String server = serverUrl.trim();
86+
if (server.endsWith("/")) {
87+
server = server.substring(0, server.length() - 1);
88+
}
89+
return server + "/" + repository.trim() + "/actions/runs/" + runId.trim();
90+
}
91+
92+
private static boolean isBlank(String s) {
93+
return s == null || s.isBlank();
94+
}
95+
96+
private static String base(String baseDir) {
97+
return isBlank(baseDir) ? "target" : baseDir.replace('\\', '/').trim().replaceAll("/+$", "");
98+
}
99+
64100
/**
65101
* Appends failed tests with trace links to {@code summaryFile} when tracing is enabled
66102
* and at least one test failed.
@@ -87,7 +123,8 @@ public static boolean appendSummary(SeleniumBootConfig config,
87123
return false;
88124
}
89125

90-
String markdown = buildMarkdown(timings);
126+
String markdown = buildMarkdown(timings,
127+
runUrl(System.getenv("GITHUB_SERVER_URL"), System.getenv("GITHUB_REPOSITORY"), System.getenv("GITHUB_RUN_ID")));
91128
if (markdown.isEmpty()) {
92129
return false;
93130
}

‎src/test/java/com/seleniumboot/unit/GitHubActionsSummaryTest.java‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,35 @@ public void buildMarkdown_singleFailedTest_generatesTable() {
9595
"Trace path must include base dir and relative trace file path");
9696
}
9797

98+
@Test
99+
public void buildMarkdown_withRunUrl_linksToRunArtifacts() {
100+
TestTiming failed = timing("t1", "LoginTest", "FAILED", "boom", "traces/LoginTest/t1-trace.html");
101+
102+
String markdown = GitHubActionsSummary.buildMarkdown(List.of(failed),
103+
"https://github.com/acme/app/actions/runs/42");
104+
105+
assertTrue(markdown.contains("[artifacts](https://github.com/acme/app/actions/runs/42)"),
106+
"Must link to the workflow run page");
107+
assertTrue(markdown.contains("actions/upload-artifact"), "Must hint how traces get there");
108+
}
109+
110+
@Test
111+
public void buildMarkdown_withoutRunUrl_hasNoArtifactsLine() {
112+
TestTiming failed = timing("t1", "LoginTest", "FAILED", "boom", "traces/LoginTest/t1-trace.html");
113+
114+
assertFalse(GitHubActionsSummary.buildMarkdown(List.of(failed)).contains("artifacts"));
115+
assertFalse(GitHubActionsSummary.buildMarkdown(List.of(failed), " ").contains("artifacts"));
116+
}
117+
118+
@Test
119+
public void runUrl_buildsFromEnvironmentValues() {
120+
assertEquals(GitHubActionsSummary.runUrl("https://github.com/", "acme/app", "42"),
121+
"https://github.com/acme/app/actions/runs/42");
122+
assertNull(GitHubActionsSummary.runUrl(null, "acme/app", "42"));
123+
assertNull(GitHubActionsSummary.runUrl("https://github.com", "", "42"));
124+
assertNull(GitHubActionsSummary.runUrl("https://github.com", "acme/app", null));
125+
}
126+
98127
@Test
99128
public void buildMarkdown_multipleFailedTests_generatesMultipleRows() {
100129
List<TestTiming> timings = List.of(

0 commit comments

Comments
 (0)