Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions docs-site/docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion docs-site/docs/ci/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

---

Expand Down
10 changes: 10 additions & 0 deletions docs-site/docs/guides/trace-viewer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/
```

39 changes: 38 additions & 1 deletion src/main/java/com/seleniumboot/ci/GitHubActionsSummary.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestTiming> 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<TestTiming> timings, String runUrl) {
if (timings == null || timings.isEmpty()) {
return "";
}
Expand Down Expand Up @@ -58,9 +66,37 @@ public static String buildMarkdown(Collection<TestTiming> 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.
Expand All @@ -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;
}
Expand Down
29 changes: 29 additions & 0 deletions src/test/java/com/seleniumboot/unit/GitHubActionsSummaryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<TestTiming> timings = List.of(
Expand Down
Loading