How to integrate Percy with Selenium tests
Add Percy snapshots to Python or Java Selenium tests, run them through Percy CLI, and capture stable visual checkpoints for review.
Percy integrates with Selenium by adding a language-specific SDK snapshot call to your existing test, then running the test command through Percy CLI with PERCY_TOKEN set. Selenium still opens pages and performs interactions; the Percy call marks the browser state to capture and upload for visual review.
This guide covers Python and Java setup, snapshot placement, reliable runs, troubleshooting, and alternatives. Use the SDK that matches your existing test language; their package names and APIs are different.
1. How the integration works
- Your Selenium test navigates to the application and performs its usual actions.
- After the page reaches the state you want to compare, the Percy SDK records a named snapshot.
- Percy CLI runs around the test command. With the project token configured, Percy creates a build and uploads the snapshots.
- Review the visual results against the project baseline.
Snapshot names should describe the page and state and be unique within the snapshot set. For example, use Account settings – saved state rather than a generic name such as Home. See the official [Python Selenium SDK](https://github.com/percy/percy-selenium-python) and [Java Selenium SDK](https://github.com/percy/percy-selenium-java) instructions.
2. Python: add Percy snapshots to Selenium
Install the CLI and SDK
Add Percy CLI as a development dependency using the package manager and lockfile conventions already used by your project. Install the Python SDK:
pip install percy-selenium
Install the CLI with your project’s chosen Node package manager. For example, in a project that uses npm:
npm install --save-dev @percy/cli
Keep the CLI in the project so local and CI runs use the same dependency definition. Consult the [official Python SDK repository](https://github.com/percy/percy-selenium-python) for current installation requirements.
Add a snapshot after the desired UI state
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
from percy import percy_snapshot
browser = webdriver.Chrome()
try:
browser.set_window_size(1440, 1000)
browser.get("https://example.com/account")
WebDriverWait(browser, 15).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='account-title']"))
)
# Perform any interactions needed to reach the state under test here.
percy_snapshot(browser, "Account settings - initial state")
finally:
browser.quit()
The example assumes your environment has Selenium and a compatible browser driver configured. Replace the URL and selector with elements from your application. The SDK call takes the Selenium driver and a descriptive unique snapshot name.
Set the token and run through Percy CLI
Set the Percy project’s token as PERCY_TOKEN in the environment that runs the test. Do not commit the token to source control. Then invoke the test command through Percy CLI:
export PERCY_TOKEN="your-project-token"
npx percy exec -- python -m pytest
Replace python -m pytest with the command your project uses to run the tests. In CI, store the token in the CI platform’s secret store and expose it to the job environment.
3. Java: add Percy snapshots to Selenium
Add dependencies
Add @percy/cli as a development dependency and add the Percy Selenium Java SDK to your Maven project. The official repository shows 1.2.0 as an example version; check the [repository](https://github.com/percy/percy-selenium-java) and package registry for the current release and compatibility before pinning a version.
<dependency>
<groupId>io.percy</groupId>
<artifactId>percy-java-selenium</artifactId>
<version>1.2.0</version>
</dependency>
Install the CLI using the package manager used by your project, for example:
npm install --save-dev @percy/cli
Call Percy at a stable checkpoint
import io.percy.selenium.Percy;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class AccountVisualTest {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
try {
driver.manage().window().setSize(new org.openqa.selenium.Dimension(1440, 1000));
driver.get("https://example.com/account");
new WebDriverWait(driver, Duration.ofSeconds(15)).until(
ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid='account-title']"))
);
Percy percy = new Percy(driver);
percy.snapshot("Account settings - initial state");
} finally {
driver.quit();
}
}
}
This is a compact runnable entry point when the project dependencies and browser driver are configured. In a test framework, place the same navigation, wait, and snapshot operations in the appropriate test and teardown hooks.
Run the Java test command through Percy
export PERCY_TOKEN="your-project-token"
npx percy exec -- mvn test
Use your normal test command in place of mvn test. The CLI wraps that command so snapshots from the run can be collected and uploaded.
4. Place snapshots for useful comparisons
Capture after navigation, relevant interactions, and the loading condition that defines the state under test. If a snapshot runs too early, it can record a spinner, incomplete content, or a transient layout rather than the intended result. Percy’s [Selenium guidance](https://percy.io/blog/visual-tests-with-selenium) recommends consistent viewport conditions and waiting for key content.
- Wait for meaningful content: use an explicit Selenium wait for a key element or state. Avoid relying only on a fixed sleep when a specific condition can be observed.
- Set a consistent viewport: keep window dimensions stable across runs. A changed viewport can alter wrapping and responsive breakpoints.
- Name states clearly: include the page and state, such as
Checkout - validation errororAccount settings - saved state. - Capture deliberate states: take separate snapshots after distinct interactions when each resulting state matters.
- Keep test data predictable: use stable content where possible so changing timestamps or generated values do not dominate visual differences.
5. Run locally and in CI
- Install project dependencies, browser binaries, and the matching browser driver as your Selenium setup requires.
- Set
PERCY_TOKENin the process environment. In CI, use a secret variable rather than a checked-in file. - Run the same test command through
percy exec --locally and in CI. - Check that the run creates a Percy build and that expected named snapshots appear.
- Review visual changes and accept a new baseline only when the rendered change is intentional.
For repeatability, pin dependencies according to your project policy and keep browser, driver, viewport, and test data conditions consistent between runs. Avoid running the Percy-wrapped command without the project token when you expect snapshots to upload.
6. cURL, Python, and Node.js alternatives for the screenshot step
Percy snapshots are made through its Selenium SDK in the language-specific examples above; the cited SDK instructions do not describe a cURL snapshot upload flow. If you need a direct website screenshot API call instead of adding capture code to a Selenium suite, ScreenshotNeo accepts a URL and returns an image or PDF. The [ScreenshotNeo documentation](https://screenshotneo.com/docs/) lists its request options.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await require('node:fs/promises').writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));
These calls capture a page by URL; they do not replace Percy’s named snapshots and baseline review inside a Selenium visual-testing workflow. ScreenshotNeo is a website screenshot API and MCP server from [ScreenshotNeo](https://screenshotneo.com), with options for full-page capture, CSS selectors, device viewports, custom CSS and JavaScript, waits, request blocking, caching, and more.
7. Troubleshooting
| Symptom | Likely cause | What to check |
|---|---|---|
| No Percy build or uploaded snapshots | The test was run directly, the CLI did not wrap the command, or PERCY_TOKEN is missing from that process. |
Run through percy exec --; confirm the token is present in the local shell or CI job environment. |
| Snapshot call raises an import or class error | The wrong language SDK or package is installed, or the import does not match that SDK. | For Python, install percy-selenium and import percy_snapshot from percy. For Java, use io.percy:percy-java-selenium and io.percy.selenium.Percy. |
| Snapshot is blank or shows a loading state | The checkpoint occurs before the intended content is visible, or navigation failed. | Wait for a key element or application state before calling the snapshot; inspect the Selenium page and browser logs. |
| Unexpected diffs across otherwise unchanged runs | Viewport, timing, test data, or rendered content varies between runs. | Set a consistent viewport, wait on stable conditions, and remove unpredictable test inputs where practical. |
| Browser session fails before the snapshot | Selenium cannot start the configured browser or driver. | Verify the browser and driver are installed and compatible in the execution environment; this is a Selenium setup issue before Percy capture. |
| Snapshots are missing or names collide | The snapshot call was skipped or names are not unique within the set. | Ensure the test reaches the call and use distinct page-and-state names as required by the SDK. |
| Java dependency resolution fails | The example version may not match the current package release or repository configuration. | Check the official Java SDK repository and your Maven repositories for current coordinates and compatibility. |
8. Performance, reliability, and cost considerations
Every snapshot adds capture and upload work to a test run, so place checkpoints where they provide review value rather than after every low-level action. Use explicit waits for the page condition you care about; this avoids capturing incomplete states while avoiding arbitrary long pauses. Large suites can be split using the test runner’s normal grouping mechanisms, while preserving meaningful snapshot names and a consistent environment.
Reliability depends on the whole chain: Selenium must reach the intended state, the Percy SDK must be present, the CLI must wrap the test command, and the token must be available. Keep credentials out of source control and make the CI environment resemble local runs where feasible.
Percy usage, plan limits, and pricing are not specified in the research references used for this implementation guide. Check Percy’s current product documentation for those details before planning a budget. ScreenshotNeo pricing is 1,000 shots per month free with no card; paid plans are Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. ScreenshotNeo bills only clean shots: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses identify the verdict and billing status in headers.
9. Or skip the browser setup
Use ScreenshotNeo for a one-call URL screenshot when you do not need to drive an interactive Selenium flow:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
See the [ScreenshotNeo API documentation](https://screenshotneo.com/docs/) for the other supported options. Cookie banners are accepted like a visitor and more than 60 known consent platforms, newsletter popups, and chat widgets are removed before the shot; each step can be turned off. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents, including Claude and Cursor, take screenshots. 1,000 screenshots a month are free with no card, and paid plans start at $5 for 3,000. These URL captures complement a Percy workflow; they do not create Percy baselines.
Create a free ScreenshotNeo account and get 1,000 screenshots a month with no card.
10. FAQ
Does Percy replace Selenium?
No. Selenium continues to control the browser. Percy adds named visual capture checkpoints to that test flow.
Can I use the Python call in a Java suite?
No. Use the SDK and API documented for the language of the suite: percy_snapshot for Python or Percy.snapshot for Java.
Do I need Percy CLI if I already call the SDK?
The documented workflow runs the test command through Percy CLI so the run can create a build and upload snapshots using the configured project token.
Can a ScreenshotNeo screenshot become a Percy baseline?
The ScreenshotNeo URL capture is a separate screenshot workflow. The supplied product facts do not describe importing those captures as Percy baselines.


