ScreenshotNeo

BlogHow-to

How to capture a Playwright screenshot after clicking a button

Click a button, wait for the resulting page state, and capture a reliable Playwright screenshot—with patterns for navigation, popups, and full-page images.

By the ScreenshotNeo team4 October 20267 min read

To capture a Playwright screenshot after clicking a button, click it with a locator, wait for the specific result you want to show, then call page.screenshot():

await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved')).toBeVisible();
await page.screenshot({ path: 'after-click.png' });

The click waits for Playwright’s actionability checks, such as the button being visible and enabled. It does not necessarily wait for the application’s asynchronous update. Synchronize the capture with an assertion, URL, or event that represents the state you intend to document. See the Playwright locator guide and Page API.

1. Set up a runnable Playwright Test example

This example assumes your app is already running locally. Install the Playwright Test package, save the test as tests/after-click.spec.js, and run it with the command below.

npm install --save-dev @playwright/test
npx playwright install chromium
// tests/after-click.spec.js
const { test, expect } = require('@playwright/test');

test('captures the page after saving', async ({ page }) => {
  await page.goto('http://127.0.0.1:3000');
  await page.getByRole('button', { name: 'Save' }).click();
  await expect(page.getByText('Saved')).toBeVisible();
  await page.screenshot({ path: 'artifacts/after-click.png' });
});
npx playwright test tests/after-click.spec.js

Change the URL, button’s accessible name, and expected result to match your application. In Playwright Test, the page fixture is provided for each test. If you use the Playwright library directly instead, create and close a browser and page yourself; examples follow below.

2. Choose the right readiness signal

The screenshot should be taken after the condition that makes the intended image true. A fixed delay can work around a known animation, but a visible result or destination is usually a better signal because it ties the capture to application state.

In-page update

await page.getByRole('button', { name: 'Show details' }).click();
await expect(page.getByRole('region', { name: 'Details' })).toBeVisible();
await page.screenshot({ path: 'details.png' });

Use an assertion that matches the actual user-visible result: a confirmation message, updated heading, expanded region, or changed value. Playwright’s web-first assertions retry until the condition is met or the assertion timeout expires.

await page.getByRole('button', { name: 'Continue' }).click();
await page.waitForURL('**/next-step');
await expect(page.getByRole('heading', { name: 'Next step' })).toBeVisible();
await page.screenshot({ path: 'next-step.png' });

Wait for the known destination with waitForURL(); then assert destination content if the screenshot depends on it. The Page API marks waitForNavigation() deprecated and describes it as inherently racy. Use waitForURL() instead.

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 popup.screenshot({ path: 'report.png' });

Register the popup wait before clicking so you do not miss the event. The returned popup is a page; wait for the content you need before capturing it.

Download

const downloadPromise = page.waitForEvent('download');
await page.getByRole('button', { name: 'Download report' }).click();
const download = await downloadPromise;
console.log(download.suggestedFilename());
await page.screenshot({ path: 'after-download-started.png' });

Wait for the download event before clicking. A download does not by itself define what the page should look like, so decide whether to capture the page before the download, after it starts, or after any visible completion state, and synchronize accordingly.

3. Select the screenshot output

Playwright captures the current viewport by default. Choose a viewport image, full-page image, locator image, or returned bytes depending on what the image is for.

Viewport, full page, and bytes

await page.screenshot({ path: 'viewport.png' });
await page.screenshot({ path: 'full-page.png', fullPage: true });
const imageBytes = await page.screenshot();
  • path writes the screenshot to a file. Ensure the parent directory exists if you choose a nested path.
  • Without path, page.screenshot() returns image bytes, which you can pass to another API or write with Node’s file system module.
  • fullPage: true captures the full scrollable page rather than only the viewport. For extremely long pages, consider whether a full-page image is necessary because it can take longer and use more memory.

Capture one element

await page.getByRole('main').screenshot({ path: 'main.png' });

A locator screenshot is useful when the desired output is a component rather than the entire page. The locator must resolve to the intended element, and it should be visible after the click and readiness wait.

Use the screenshot as a visual regression assertion

await expect(page).toHaveScreenshot('after-click.png');

Use toHaveScreenshot() from Playwright Test when your goal is to create or compare a visual baseline. Rendering can differ across operating systems, browser versions, browser settings, hardware, power source, and headless mode. Keep the capture and comparison environments consistent and review intentional baseline changes.

4. Use the Playwright library directly

For a standalone Node.js script instead of a Playwright Test, install Playwright, install a browser, and manage the browser lifecycle explicitly:

npm install playwright
npx playwright install chromium
// capture-after-click.js
const { chromium } = require('playwright');

(async () => {
  const browser = await chromium.launch({ headless: true });
  try {
    const page = await browser.newPage();
    await page.goto('http://127.0.0.1:3000');
    await page.getByRole('button', { name: 'Save' }).click();
    await page.getByText('Saved').waitFor({ state: 'visible' });
    await page.screenshot({ path: 'after-click.png', fullPage: true });
  } finally {
    await browser.close();
  }
})();

Run it with node capture-after-click.js. The script uses a locator wait for the result; when the result is a navigation, popup, or another event, use the corresponding pattern above. Closing the browser in finally also releases it if navigation, assertion, or capture fails.

5. Make the capture repeatable

  • Use a user-facing locator. Prefer getByRole('button', { name: 'Save' }) over a long CSS or XPath chain. Role and accessible name describe the control the user interacts with and are less tied to DOM implementation details.
  • Wait for the right thing. Click actionability checks ensure the action can be performed; they do not establish that a server response, animation, or app update has finished.
  • Keep the browser setup consistent. For visual regression, use a consistent browser, operating system, viewport, and rendering configuration so environment changes do not create unrelated image differences.
  • Capture only what you need. A viewport or element screenshot is typically a smaller output than a full-page capture. Full-page images can be useful for documentation but grow with page length.
  • Save useful artifacts. Use a predictable file name and directory in scripts and CI so the screenshot can be found and reviewed after a failure.

These practices improve reliability by making the trigger, expected state, and output explicit. They do not make a changing application deterministic by themselves: test data, third-party content, fonts, animations, and environment differences can still affect pixels.

6. Troubleshooting

Symptom Likely cause Fix
The screenshot shows the old state The click finished, but the asynchronous UI update had not appeared. Wait for a visible confirmation, updated value, or other assertion tied to the intended state before calling screenshot().
The click times out The locator did not match, matched multiple controls, or the button was not actionable. Check the accessible name and role, make the locator specific, and inspect whether the control is visible and enabled at click time.
The screenshot is from the original tab The click opened a popup, but the original page was captured. Register page.waitForEvent('popup') before clicking, then capture the returned popup page.
The destination page is incomplete The capture followed a URL change before the destination content was ready. Wait with waitForURL() and assert the heading or other content required in the image.
The screenshot file is missing The path is not where expected, or its parent directory does not exist. Use a known path and create the output directory before capture; alternatively omit path and handle the returned bytes.
A visual test fails only on another machine Browser, OS, fonts, headless mode, hardware, or other rendering conditions differ. Capture and compare in a consistent environment, and inspect whether the change is a genuine UI regression before updating a baseline.
A popup or download wait hangs The click did not trigger the expected event, or the wait was registered incorrectly. Confirm the control’s behavior and register the event wait before clicking. Use an appropriate event for the actual result.

7. Or skip the browser setup

If you need a screenshot of a public URL rather than the state of your own interactive Playwright session, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF, and the API accepts common screenshot parameter names to make switching straightforward. The Playwright examples above remain the right approach when the screenshot depends on clicking a button in a live session; a URL capture cannot reproduce that session’s interaction state unless the target URL itself renders that state.

See the ScreenshotNeo API documentation for 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}`);
const image = Buffer.from(await res.arrayBuffer());
require('node:fs').writeFileSync('shot.webp', image);

Replace YOUR_API_KEY with your key and change the target URL. Cookie banners, newsletter popups, and chat widgets are removed before the shot; bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 screenshots. Sign up for free and get 1,000 screenshots a month with no card.

8. FAQ

Does a click automatically wait until the screenshot is ready?

No. The locator click waits for actionability. Add a wait for the app-specific result you want the screenshot to show.

Can I capture the image without writing a file?

Yes. Call page.screenshot() without a path; it returns image bytes.

Should I use a fixed timeout?

Prefer a visible result, URL, or event wait that expresses what must happen. Use a delay only when the condition you need is genuinely time-based, such as allowing a visual animation to finish.

Can I screenshot a page after a button opens another tab?

Yes. Wait for the popup before clicking, then take the screenshot from the returned popup page after its relevant content is ready.