How to Detect Visual Changes on Pages That Require Clicking a Button
Click the control, verify the resulting state, then compare a stable screenshot baseline with Playwright Test. Includes complete setup, troubleshooting, and CI guidance.
To detect a visual change in content that appears only after a button click, reproduce that interaction in a browser test, wait until the intended state is visible, then compare a screenshot of that state with a reviewed baseline. With Playwright Test, use a stable button locator and await expect(page).toHaveScreenshot('details-expanded.png').
This catches both new content and layout changes caused by the interaction. A screenshot taken before the click cannot cover a state the page has not reached yet.
1. Set up a repeatable Playwright visual test
Screenshot assertions are part of the Playwright test runner. If Playwright is already installed in your project, use its existing configuration and browser project. Otherwise, add the test runner and install its browser binaries:
npm init playwright@latest
Follow the prompts to choose TypeScript or JavaScript and install browsers. Keep the same browser project and rendering environment when creating and comparing baselines. Browser, operating system, rendering settings, and headless mode can change pixels even when the page code has not changed. See Playwright’s visual comparison guide.
Create tests/expanded-details.spec.ts (or use .js in a JavaScript project):
import { test, expect } from '@playwright/test';
test('captures the expanded details state', async ({ page }) => {
await page.goto('/example');
const showDetails = page.getByRole('button', { name: 'Show details' });
await showDetails.click();
// Synchronize on an observable result of the click.
await expect(page.getByText('Additional details')).toBeVisible();
// Compare the current page with its stored screenshot baseline.
await expect(page).toHaveScreenshot('details-expanded.png');
});
Replace /example, the accessible button name, and the visible state assertion with those from your application. The text assertion illustrates a synchronization point; choose a locator that is actually present and meaningful in your page.
2. Drive the exact state you want to monitor
Prefer a user-facing role locator and accessible name for interactive controls. For example, getByRole('button', { name: 'Show details' }) locates a button as users and assistive technology perceive it. If your project deliberately defines test IDs as its testing contract, a test ID locator is also reasonable. Playwright explains locator choices in its locator documentation.
Click the control that exposes the target area, then assert the result before taking the screenshot. Depending on the UI, useful checks include:
- The newly revealed heading or content is visible.
- The button’s expanded state changes, if the interface exposes that state accessibly.
- A dialog, menu, or panel appears.
- A navigation or popup reaches the expected destination.
For content that loads asynchronously, wait for a visible result of the click rather than adding an arbitrary delay. A state assertion documents what the test expects and avoids taking the screenshot before the new content is ready.
When the click opens a popup or navigates
If the button opens a new page, wait for that page and capture it. If it navigates the current page, assert the destination or resulting content before capturing. The following example waits for a popup, then takes a screenshot of the popup page:
import { test, expect } from '@playwright/test';
test('captures the page opened by the button', async ({ page }) => {
await page.goto('/example');
const popupPromise = page.waitForEvent('popup');
await page.getByRole('button', { name: 'Open report' }).click();
const popup = await popupPromise;
await expect(popup.getByRole('heading', { name: 'Report' })).toBeVisible();
await expect(popup).toHaveScreenshot('report-popup.png');
});
Adapt the expected heading to the real page. If the interaction stays in the current page, keep using page and wait for the current page’s resulting state.
3. Create and review the baseline
Run the test with your project’s usual command, commonly:
npx playwright test
On the first run, Playwright creates the expected screenshot baseline. Review the generated image and commit it with the test. Later runs compare the captured state to that baseline and report a visual difference when they do not match.
When a screenshot changes, inspect the diff and decide whether it represents an intended UI change or a regression. Update the baseline only after reviewing and accepting the new appearance. Playwright documents baseline creation and updates in Visual comparisons.
4. Keep comparisons useful and reduce noise
Screenshot assertions wait for two consecutive screenshots to match before comparing the last one with the expectation. Playwright also disables animations for screenshot assertions by default. These behaviors help with transient rendering, but your test still needs to reach the correct application state before capture. See PageAssertions.
Use a consistent browser project and environment for baseline creation and later runs. Keep viewport, browser, operating system, and relevant rendering settings consistent where your setup allows. This reduces environment-driven differences.
For genuinely volatile content, Playwright supports screenshot options such as a stylesheet to filter content and thresholds for comparison. Apply them narrowly. Hiding an area means the test cannot detect a meaningful change inside it; a permissive threshold can also conceal small but important differences. Consult the current screenshot assertion options and visual comparison guidance for the options your installed Playwright version supports.
5. Run the test in development and CI
Use the same Playwright project for baseline creation and CI where possible. The test itself is an ordinary Playwright Test test, so it can run with the rest of your suite using npx playwright test. If CI uses a different operating system or browser configuration from local development, pixel-level differences may result; create and review baselines in a consistent environment.
Keep the screenshot assertion near the interaction and state assertion that explain what the image represents. A descriptive file name such as details-expanded.png makes the baseline easier to identify when a test fails.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The screenshot shows the collapsed state | The click did not target the intended control, or capture happened before the state appeared. | Use a stable role/name locator, and assert that the revealed content or other expected result is visible before the screenshot. |
| Strict mode reports multiple matching buttons | The locator matches more than one button with that role and name. | Use a more specific accessible name or scope the locator to the relevant dialog or region. Avoid selecting an arbitrary match when the page has ambiguous controls. |
| The assertion says the screenshot does not match | The UI changed, the state differs, or rendering conditions vary from the baseline environment. | Inspect the diff first. If the visual change is intended, review it and update the baseline. Otherwise, fix the UI or stabilize the test and rendering environment. |
| The image contains an animation or transient content | The page has motion or content that changes while being captured. | Playwright disables animations for screenshot assertions by default. For other volatile content, use a narrowly scoped stylesheet or supported screenshot option, preserving the area whose changes matter. |
toHaveScreenshot is unavailable or errors outside a test |
Screenshot assertions require the Playwright test runner. | Run the code under @playwright/test as a Playwright Test test, rather than calling the assertion from a standalone browser script. |
| A popup screenshot is missing or times out | The test may start waiting after the click, or the button may navigate in the same page instead of opening a popup. | Register the popup wait before clicking, as in the example. If the interaction navigates the current page, assert and capture that page instead. |
7. Performance, reliability, and cost
A visual assertion adds browser rendering and image comparison work to the test. Keep the test focused on the interaction and state that matter, and avoid taking multiple equivalent screenshots in the same flow. The exact runtime depends on the page and environment; the cited documentation does not establish a universal benchmark.
Reliable results depend on reaching a deterministic state and comparing under consistent rendering conditions. A stable locator, a state assertion, a reviewed baseline, and narrowly applied noise controls make a failure easier to interpret. Baseline changes should be reviewed rather than accepted automatically.
Playwright Test is the direct fit when your team wants screenshots and baselines alongside browser tests. For a hosted review workflow, Percy documents Playwright capture, visual diffs, approvals, and CI integration; evaluate its current terms and integration requirements in the Percy visual testing overview and Percy Playwright integration.
Or skip the browser setup
If you need a screenshot of a page URL without managing a browser capture stack, ScreenshotNeo provides a website screenshot API and MCP server. One request captures the page; see the API documentation for options and response details.
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}`);
Cookie banners, newsletter popups, and chat widgets are removed before capture, and each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers say the page verdict and whether it was billed. The MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Every feature is available on every plan. This API captures a URL as rendered by its browser; for a state that appears only after a particular click, use the Playwright interaction above to reproduce that state.
Sign up for 1,000 free screenshots a month, with no card required.
FAQ
Does the first Playwright run pass?
The first run creates the reference screenshot. Review it as the expected appearance; later runs compare against it.
Can I compare only the revealed section?
This pattern captures the page. Playwright also has locator screenshot assertions for targeting an element; check the API documentation for the method and options in your installed version.
Should I update a baseline whenever CI reports a diff?
No. Review the visual change and update the expected image only when the difference is intentional.
Does Percy replace the click in the test?
The page still needs to reach the state you intend to review. Percy’s Playwright integration supports capturing application states for hosted visual review.


