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
44 changes: 44 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ name: Tests

on: [push]

permissions:
contents: read
Comment thread
Garbee marked this conversation as resolved.

jobs:
lint:
name: Lint
Expand Down Expand Up @@ -81,3 +84,44 @@ jobs:
SERVER_URL: https://axe.dequelabs.com
CHROME_BIN: ${{ steps.install-chrome.outputs.chrome-path }}
CHROMEDRIVER_BIN: ${{ steps.install-chrome.outputs.chromedriver-path }}

java-smoke-tests:
name: Java Smoke Tests
needs: lint
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
# We don't want to fail the whole matrix if one job fails
# Makes it harder to debug what went wrong if it ends early
fail-fast: false
matrix:
directory:
- java/playwright/basic
- java/playwright/manual-mode
- java/playwright/context-wrapping
- java/playwright/multi-page
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-java@ad2b38190b15e4d6bdf0c97fb4fca8412226d287 # v5.3.0
with:
distribution: temurin
java-version: 17
cache: maven
- run: mkdir -p ./tmp/browsers
- uses: ./.github/actions/install-chrome
id: install-chrome
with:
working-directory: ./tmp/browsers
chrome-version: ${{ vars.CHROME_VERSION || 'stable' }}
- name: Run tests
run: |
cd ${{ matrix.directory }} &&
mvn -B test
env:
API_KEY: ${{ secrets.AXE_DEVHUB_API_KEY }}
PROJECT_ID: ${{ secrets.PROJECT_ID }}
SERVER_URL: https://axe.dequelabs.com
# Point Playwright Java at the runner's Chrome; the bundled-browser download is skipped
# since axe Watcher loads its extension into this binary via the `chrome` channel.
CHROME_BIN: ${{ steps.install-chrome.outputs.chrome-path }}
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@
```sh
SERVER_URL="https://axe.yourcompany.com" API_KEY="YOUR API KEY" PROJECT_ID="YOUR PROJECT ID" npm test
```

## Java examples

The [`java`](./java) directory holds Maven projects built and run with `mvn test` instead of npm.
See [`java/playwright`](./java/playwright) for the Playwright Java examples (auto analysis, manual
mode, and context wrapping) and its [README](./java/playwright/README.md) for prerequisites and run
instructions.
80 changes: 80 additions & 0 deletions java/playwright/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# axe Watcher — Playwright Java examples

Runnable [JUnit 5](https://junit.org/junit5/) projects showing how to add accessibility analysis to
a [Playwright Java](https://playwright.dev/java/) test suite with
[`@axe-core/watcher`](https://central.sonatype.com/artifact/com.deque.axe_core/watcher). axe Watcher
wraps your Playwright `Page` (or `BrowserContext`) so scans run automatically as your existing
end-to-end tests drive the browser, then uploads the results to
[axe Developer Hub](https://docs.deque.com/developer-hub).

| Example | Scenario | What it shows |
| ---------------------------------------- | ---------------- | --------------------------------------------------------------------------------- |
| [`basic`](./basic) | Auto analysis | The default mode — wrap the page and every interaction is analyzed automatically. |
| [`manual-mode`](./manual-mode) | Manual mode | `setAutoAnalyze(false)` plus `analyze()` / `start()` / `stop()` to control scans. |
| [`context-wrapping`](./context-wrapping) | Context wrapping | `wrapContext()` so every page opened from one `BrowserContext` is instrumented. |
| [`multi-page`](./multi-page) | Multi-page | Reuse one wrapped page across several pages, flushing after each test. |

## Prerequisites

- **Java 11 or newer.** The axe Watcher integration itself supports Java 8+; these example projects
target Java 11 source compatibility and are tested on Java 17 in CI.
- **[Maven](https://maven.apache.org/).** Each example is a standalone Maven project.
- **A Chromium-based browser.** axe Watcher loads a browser extension, which Chromium only supports
when launched via a persistent context using [Chrome for
Testing](https://developer.chrome.com/blog/chrome-for-testing), Playwright's bundled Chromium, or
a Microsoft Edge channel. Recent branded Google Chrome releases (139+) restrict the
`--load-extension` flag axe Watcher relies on, so prefer Chrome for Testing or Chromium.
- **An axe Developer Hub API key and project ID.**
- [Create an API key](https://axe.deque.com/settings)
- [Create a project ID](https://axe.deque.com/axe-watcher/projects)

## Running an example

1. Provide your credentials as environment variables. `SERVER_URL` is optional and defaults to
`https://axe.deque.com`; set it to target a different axe Developer Hub instance.

```sh
export API_KEY="YOUR API KEY"
export PROJECT_ID="YOUR PROJECT ID"
# export SERVER_URL="https://axe.yourcompany.com"
```

2. Change into the example you want to run:

```sh
cd basic
```

3. (Local runs only.) Install Playwright's bundled Chromium once. Skip this if you instead set
`CHROME_BIN` to a Chrome for Testing (or Chromium) binary — the examples will launch that, which
is how they run in CI.

```sh
mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install chromium"
```

4. Run the tests:

```sh
mvn test
```

5. Open your project in [axe Developer Hub](https://docs.deque.com/developer-hub) to review the
captured page states and issues.

## Notes

- **Headless mode.** The examples run in Chromium's "new" headless mode (`setHeadless(true)` with a
Chromium-based channel) so they work in CI. axe Watcher's extension cannot load in Chromium's
classic/default headless mode, so `configure()` rejects a plain `--headless` argument, an
`--incognito` argument, or `setHeadless(true)` without a supported channel. Call
`setHeadless(false)` to watch a run in a visible window.
- **Always `flush()`.** Results are only uploaded when you flush. The examples call
`page.axeWatcher().flush()` in an `@AfterEach` hook (the `context-wrapping` example relies on
`BrowserContext.close()`, which flushes every tracked page).
- **Attributing scans to a test.** To trace a violation back to the test that produced it, call
`page.axeWatcher().setTestContext(testFilePath, testLocation)` — typically from a `@BeforeEach`
hook using JUnit 5's `TestInfo`.

For the full API and configuration reference, see the
[`@axe-core/watcher` Java documentation](https://central.sonatype.com/artifact/com.deque.axe_core/watcher).
50 changes: 50 additions & 0 deletions java/playwright/basic/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.deque.watcher_examples.playwright</groupId>
<artifactId>basic</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!--
axe Watcher declares Playwright as a `compileOnly` dependency, so you must declare
`com.microsoft.playwright:playwright` yourself alongside the watcher package.
-->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.60.0</version>
</dependency>
<dependency>
<groupId>com.deque.axe_core</groupId>
<artifactId>watcher</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.deque.watcher_examples.playwright;

import static org.junit.jupiter.api.Assertions.assertNotNull;

import com.deque.axe_core.commons.AxeWatcherOptions;
import com.deque.axe_core.playwright.AxeWatcherPage;
import com.deque.axe_core.playwright.AxeWatcherPlaywright;
import com.microsoft.playwright.BrowserContext;
import com.microsoft.playwright.BrowserType;
import com.microsoft.playwright.Playwright;
import java.nio.file.Paths;
import java.util.Arrays;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

/*
Auto analysis (the default mode).

Once the Playwright `Page` is wrapped with `wrapPage()`, axe Watcher analyzes each page state
automatically: every wrapped interaction (navigation, click, fill, ...) triggers a scan, and the
page is re-analyzed whenever the DOM changes. The rest of the test is plain Playwright Java.
*/
@DisplayName("My Login Application")
class BasicTest {

Playwright playwright;
BrowserContext context;
AxeWatcherPage page;

@BeforeEach
void setUp() {
AxeWatcherPlaywright axeWatcher =
new AxeWatcherPlaywright(
new AxeWatcherOptions()
.setApiKey(System.getenv("API_KEY"))
.setProjectId(System.getenv("PROJECT_ID"))
.setServerUrl(serverUrl()))
.enableDebugLogger();

// configure() merges the axe Watcher extension flags into the launch options. It must be
// called before launching the persistent context — Chromium only loads extensions when
// launched via a persistent context.
BrowserType.LaunchPersistentContextOptions launchOptions =
axeWatcher.configure(browserOptions());

playwright = Playwright.create();
// An empty path tells Playwright to use a temporary profile directory.
context = playwright.chromium().launchPersistentContext(Paths.get(""), launchOptions);

// Interactions on a wrapped page are analyzed automatically.
page = axeWatcher.wrapPage(context.newPage());
}

@AfterEach
void tearDown() {
// Send the collected results to axe Developer Hub after each test.
page.axeWatcher().flush();
context.close();
playwright.close();
}
Comment thread
Garbee marked this conversation as resolved.

@Nested
@DisplayName("Login")
class LoginTests {
@Nested
@DisplayName("with valid credentials")
class ShouldLoginTests {
@Test
@DisplayName("should login")
void shouldLoginTest() {
page.navigate("https://the-internet.herokuapp.com/login");

page.locator("#username").fill("tomsmith");
page.locator("#password").fill("SuperSecretPassword!");

page.locator("button[type='submit']").click();

assertNotNull(page.waitForSelector("#flash"));
}
}
}

private static String serverUrl() {
String serverUrl = System.getenv("SERVER_URL");
return serverUrl != null ? serverUrl : "https://axe.deque.com";
}

private static BrowserType.LaunchPersistentContextOptions browserOptions() {
/*
axe Watcher's browser extension loads only in a headed browser or Chromium's "new"
headless mode — never Chromium's classic/default headless mode. We run headless here
(with a Chromium-based channel, selected below, which new-headless requires) so the
example works in CI; call setHeadless(false) to watch the run in a visible window.
"--no-sandbox" lets Chromium run as root in CI and isn't needed for local headed runs.
*/
BrowserType.LaunchPersistentContextOptions options =
new BrowserType.LaunchPersistentContextOptions()
.setHeadless(true)
.setArgs(Arrays.asList("--no-sandbox"));

// In CI we point Playwright at the Chrome installed on the runner (CHROME_BIN). Locally,
// without CHROME_BIN, Playwright falls back to its bundled Chromium — install it with
// `mvn exec:java -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install chromium"`.
String chromeBin = System.getenv("CHROME_BIN");
if (chromeBin != null) {
options.setChannel("chrome").setExecutablePath(Paths.get(chromeBin));
} else {
options.setChannel("chromium");
}

return options;
}
}
50 changes: 50 additions & 0 deletions java/playwright/context-wrapping/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.deque.watcher_examples.playwright</groupId>
<artifactId>context-wrapping</artifactId>
<version>1.0-SNAPSHOT</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<!--
axe Watcher declares Playwright as a `compileOnly` dependency, so you must declare
`com.microsoft.playwright:playwright` yourself alongside the watcher package.
-->
<dependency>
<groupId>com.microsoft.playwright</groupId>
<artifactId>playwright</artifactId>
<version>1.60.0</version>
</dependency>
<dependency>
<groupId>com.deque.axe_core</groupId>
<artifactId>watcher</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.11.3</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.2.5</version>
</plugin>
</plugins>
</build>

</project>
Loading