Take a Screenshot of a Webpage After Submitting a Form with Playwright in Node.js
Submit a form with Playwright, wait for the result that matters, and save a viewport or full-page screenshot in Node.js.
Use Playwright to fill the form, click its submit button, wait for the outcome your application promises, and then call page.screenshot(). For an in-page confirmation, wait for that confirmation to become visible. If submission navigates to a known destination, wait for that URL with page.waitForURL(). The click alone does not prove that the application has finished processing the submission.
1. Install Playwright and prepare the project
In a new Node.js project, install Playwright and its browser binary:
npm init -y
npm install playwright
npx playwright install chromium
Save the script below as submit-and-screenshot.js. It uses Chromium and a success message as an example. Replace the URL, field label, button name, and confirmation text with values from your form.
2. Submit the form and wait for its success message
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch();
try {
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
await page.goto('https://example.com/form', { waitUntil: 'domcontentloaded' });
await page.getByLabel('Email').fill('reader@example.com');
await page.getByRole('button', { name: /submit/i }).click();
// Match this text to the confirmation shown by your application.
await page.getByText('Submitted', { exact: true }).waitFor({ state: 'visible' });
await page.screenshot({ path: 'submitted.png' });
} finally {
await browser.close();
}
})();
Run it with node submit-and-screenshot.js. The locators use the field’s accessible label and the button’s role and name. These are usually more robust than selectors tied to a page’s DOM structure. Playwright locator actions wait for actionability, and locator-based checks can wait for an expected state; a fixed sleep is not a reliable substitute for identifying the completion condition. See the official locators guide and assertions guide.
3. Choose the right completion condition
When the page updates in place
Wait for a success message, confirmation panel, changed form state, or another visible result that indicates the submission completed. If you use Playwright Test, its web-first assertions retry until the condition passes or times out:
const { test, expect } = require('@playwright/test');
test('captures the submitted state', async ({ page }) => {
await page.goto('https://example.com/form');
await page.getByLabel('Email').fill('reader@example.com');
await page.getByRole('button', { name: /submit/i }).click();
await expect(page.getByText('Submitted', { exact: true })).toBeVisible();
await page.screenshot({ path: 'submitted.png' });
});
Install the test runner separately with npm install -D @playwright/test and run the test with npx playwright test. For a standalone script without the test runner, the locator’s waitFor({ state: 'visible' }) works as shown above.
When submission navigates
Wait for the expected destination rather than waiting for a generic navigation event. Start waiting before clicking so a fast navigation is not missed:
const destination = page.waitForURL('**/thank-you');
await page.getByRole('button', { name: /submit/i }).click();
await destination;
await page.screenshot({ path: 'thank-you.png' });
Use a URL pattern that uniquely matches the expected destination. Playwright marks page.waitForNavigation() deprecated and describes it as inherently racy; its Page API recommends page.waitForURL() for URL-based waits. A form may submit without changing the URL, so use an application-specific state check in that case. See the Page API and Pages guide.
4. Capture the viewport, full page, or image buffer
By default, a screenshot captures the current viewport. Set fullPage: true to capture the full scrollable document:
await page.screenshot({ path: 'submitted-full.png', fullPage: true });
To keep the image in memory instead of writing it directly to disk, omit path. The returned buffer can be passed to an image-processing library or written elsewhere:
const image = await page.screenshot({ fullPage: true });
// Example: write the buffer to a file with Node's built-in filesystem API.
require('node:fs').writeFileSync('submitted.png', image);
Choose the viewport dimensions before navigating if layout depends on screen width. For long pages, full-page capture can use substantially more memory than a viewport capture.
5. Handle validation, delayed responses, and other form behavior
- Client-side validation: A click may leave the page in the form state because a required field is missing or invalid. Fill all required inputs with valid values and wait for the validation result or success state you expect.
- Server-side processing: If the page shows a spinner or pending state, wait for the final confirmation, not merely the spinner’s appearance or disappearance unless disappearance is a meaningful completion signal.
- Multiple matching controls: Make the locator specific enough to identify the intended form or button. Prefer a label, role and accessible name, or a locator scoped to the relevant form.
- Confirmation text changes: Match a stable status element or accessible role where possible. Avoid relying on transient copy that changes across environments.
- New tab or window: If submission opens a separate page, wait for the new page event and then wait for the destination or success state on that page before capturing it. Do not screenshot the original tab by accident.
- Authenticated forms: Provide the required test account or storage state through your automation setup. Avoid placing real credentials in source code or committing them to version control.
- Uploads and custom controls: Use the appropriate locator API for the control and wait for the application’s completion signal. A file being selected in the browser does not by itself mean the server accepted it.
6. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Timeout while locating the field or button | The accessible label or button name differs from the example, the form has not rendered, or the locator matches the wrong page state. | Inspect the page and use the actual label, role, and accessible name. Navigate to the correct page and wait for the form’s meaningful ready state. |
| Screenshot shows the form instead of confirmation | The script captured immediately after the click, or the chosen success condition does not match the application’s response. | Wait for the real confirmation message, changed state, or known destination URL before taking the screenshot. |
waitForURL() times out |
The submission updates the page in place, redirects somewhere else, or the URL pattern is too strict. | Check the actual post-submit behavior. Use a visible state condition for in-place updates, or adjust the pattern to the known destination. |
| Button click is intercepted or not actionable | An overlay, disabled state, animation, or another element covers the control. | Wait for the overlay to close or the button to become enabled and actionable. Use the locator for the actual submit control instead of forcing a click. |
| Browser executable is missing | The Playwright package is installed but its browser binary has not been downloaded. | Run npx playwright install chromium (or install the browser you selected). |
| Image is cropped or unexpectedly tall | The default capture is viewport-only, or full-page capture is being used on a long document. | Set fullPage: true when the entire document is needed; otherwise set an intentional viewport size and capture the viewport. |
7. Reliability, performance, and cost
For reliable captures, synchronize on the outcome that matters to the workflow. Locator auto-waiting handles ordinary actionability, but it cannot tell whether the server accepted a form; that requires an application-specific confirmation. Avoid arbitrary delays where a state or URL can be observed. Set a finite timeout appropriate to the application, and make test submissions safe to repeat or use isolated test data so retries do not create duplicate real-world actions.
Browser startup and page rendering are the main work in this approach. Reusing a browser process for multiple captures can avoid repeated startup overhead; close pages and the browser when finished. Full-page screenshots and high-resolution viewports use more memory and produce larger files than viewport captures. Locally, the direct costs are the machine and runtime resources used. A form submission itself can also trigger application-side effects, so use a staging environment or test data when appropriate.
8. Or skip the browser setup
If you already have the post-submit page URL and only need to capture that page, ScreenshotNeo provides a screenshot API and MCP server. Its one-request API returns an image or PDF; see the API documentation.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com/thank-you -o shot.webp
import requests
r = requests.get(
"https://api.screenshotneo.com/v1/shot",
params={"access_key": "YOUR_API_KEY", "url": "https://example.com/thank-you"},
timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
access_key: 'YOUR_API_KEY',
url: 'https://example.com/thank-you'
});
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('thank-you.webp', image);
Cookie banners, 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; paid plans start at $5 for 3,000. The API captures a URL; it does not submit your form, so use Playwright or another workflow to perform the submission when that is required.
Create a free ScreenshotNeo account to get 1,000 screenshots a month with no card.
9. Frequently asked questions
Can I capture a screenshot without saving a file?
Yes. Call page.screenshot() without a path; it returns an image buffer.
Does Playwright wait for the form’s server request when I click?
The click waits for the button to be actionable and performs the interaction. It does not establish that the application completed its server-side work. Wait for a visible result or the expected destination.
Should I use a fixed timeout after submitting?
Prefer a success state or URL condition. A fixed delay can be too short on a slow response and needlessly long on a fast one.
Can I use this for a form that does not navigate?
Yes. Wait for its in-page confirmation or another stable, visible state change, then capture the page.


