ScreenshotNeo

BlogHow-to

How to Take a Screenshot of a Logged-In Page After SAML Authentication Completes

Wait for the application’s authenticated state after the SAML redirect, then capture the page with Playwright. Includes reusable auth state, troubleshooting, and a ScreenshotNeo option.

By the ScreenshotNeo team4 October 20269 min read

To capture a page after SAML authentication, wait until the identity-provider redirect has returned to your application and the application shows a reliable authenticated state. Then take the screenshot. With Playwright, use the final application URL, a visible authenticated UI element, or both as the completion condition; do not rely on a fixed sleep alone.

import { test, expect } from '@playwright/test';

test('capture an authenticated page after SAML login', async ({ page }) => {
  await page.goto('https://app.example.com/login');

  // Replace these steps with your application's login flow.
  await page.getByRole('button', { name: 'Sign in with SSO' }).click();
  // Complete the identity-provider steps here, if they are part of this test.

  // Use the actual stable post-login destination for your application.
  await page.waitForURL('https://app.example.com/');

  // Prefer a positive signal that the application rendered an authenticated state.
  await expect(page.getByRole('button', { name: 'Account' })).toBeVisible();

  await page.screenshot({ path: 'logged-in.png', fullPage: true });
});

The URL and Account button are examples; replace them with the destination and an element that only appears after your app has finished its own session setup. Playwright’s authentication guidance demonstrates waiting for the final URL or checking an authenticated UI element before saving authentication state, and its Page API provides the screenshot method. Playwright authentication · Page screenshot API

1. Decide what proves SAML login is complete

SAML commonly sends the browser through one or more identity-provider and application redirects. Reaching an identity-provider callback or seeing a brief intermediate page does not necessarily mean the application has completed session setup. Define a condition for the final application state before capturing.

Signal Use it when Watch for
Final application URL The destination is stable and deterministic after login. Query parameters, trailing slashes, or a later redirect may make an exact URL too strict. Match the stable destination your app actually uses.
Authenticated UI element A reliable element appears only for signed-in users, such as an account menu or dashboard heading. Choose an element tied to the authenticated view, not a generic page shell visible before login.
Both URL and UI You want to confirm both the redirect destination and rendered authenticated state. Ensure the URL check does not prevent the UI from rendering; set the checks in the order appropriate to your flow.

A positive UI assertion is often useful even when you wait for the URL: the redirect can finish before the application has rendered the content you intend to capture. Conversely, a UI locator alone may match a stale or shared layout. Combine signals when both are meaningful in your application.

2. Run the login and capture in Playwright

Install Playwright and its browser if your project does not already include them:

npm install -D @playwright/test
npx playwright install chromium

Save this as tests/saml-screenshot.spec.ts in a Playwright project. Adapt the sign-in steps to your identity provider, including any test account or second-factor handling your environment requires. Avoid placing credentials directly in source code; load them from your environment or your CI secret store.

import { test, expect } from '@playwright/test';

test('save a screenshot after the SAML session is ready', async ({ page }) => {
  await page.goto('https://app.example.com/login');

  await page.getByRole('button', { name: 'Sign in with SSO' }).click();

  // Complete your IdP interaction here. The exact controls vary by provider.
  await page.getByLabel('Email').fill(process.env.SSO_TEST_USER!);
  await page.getByRole('button', { name: 'Next' }).click();
  await page.getByLabel('Password').fill(process.env.SSO_TEST_PASSWORD!);
  await page.getByRole('button', { name: 'Sign in' }).click();

  // Wait for the application, not just an intermediate identity-provider page.
  await page.waitForURL('https://app.example.com/');
  await expect(page.getByRole('button', { name: 'Account' })).toBeVisible();

  await page.screenshot({ path: 'logged-in.png', fullPage: true });
});

Run the test with your credentials set in the environment:

SSO_TEST_USER='test-user@example.com' SSO_TEST_PASSWORD='replace-me' npx playwright test tests/saml-screenshot.spec.ts

The sample login fields and buttons are illustrative. Use locators that match your IdP’s accessible labels and your app’s actual sign-in flow. If your organization uses a flow that cannot be completed unattended, authenticate through an approved test setup, then reuse the resulting Playwright storage state as described below.

Use a tolerant URL condition when the destination varies

If the app has variable query strings or a dynamic path segment, use a predicate or regular expression that checks the stable part of the URL, then assert the authenticated UI. Avoid matching a broad hostname alone if login callbacks and unauthenticated routes share it.

await page.waitForURL(url =>
  url.origin === 'https://app.example.com' && url.pathname === '/dashboard'
);
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();

3. Reuse authenticated state carefully

For repeated captures, authenticate once and save browser storage state, then create later contexts from that state. This avoids repeating the interactive SAML flow when the session remains valid. Playwright warns that storage-state files can contain cookies and headers that may let someone impersonate the account. Treat them like credentials: keep them out of source control, restrict access, and use a least-privileged test account.

Create the state after the authenticated condition succeeds:

import { test, expect } from '@playwright/test';

test('authenticate and save browser state', async ({ page }) => {
  await page.goto('https://app.example.com/login');
  await page.getByRole('button', { name: 'Sign in with SSO' }).click();
  // Complete the SAML login steps for your environment.
  await page.waitForURL('https://app.example.com/');
  await expect(page.getByRole('button', { name: 'Account' })).toBeVisible();

  await page.context().storageState({ path: 'playwright/.auth/user.json' });
});

Configure a later test to use that state, or create a context explicitly:

import { chromium, expect } from '@playwright/test';

const browser = await chromium.launch();
const context = await browser.newContext({
  storageState: 'playwright/.auth/user.json',
});
const page = await context.newPage();
await page.goto('https://app.example.com/dashboard');
await expect(page.getByRole('button', { name: 'Account' })).toBeVisible();
await page.screenshot({ path: 'dashboard.png', fullPage: true });
await browser.close();

State can expire, be revoked, or be bound to conditions your test environment does not preserve. If the authenticated UI check fails, refresh the state by running the login setup again rather than assuming that the stored file still represents a valid session.

4. Choose screenshot scope and protect sensitive content

By default, a screenshot captures the current viewport. Set fullPage: true to capture the full scrollable page. For pages with sensitive account details, mask selected locator regions in the screenshot call:

await page.screenshot({
  path: 'account-page.png',
  fullPage: true,
  mask: [page.locator('[data-private]')],
});

Make sure the mask selector identifies every sensitive region that should be hidden. A screenshot file can contain personal or session-related information even when the login itself is successful; store, share, and retain the image according to your application’s data-handling rules.

For visual regression assertions, Playwright’s toHaveScreenshot behavior waits for two consecutive screenshots to match and disables animations by default for that assertion path. That behavior is specific to the visual assertion API; a one-off page.screenshot() does not automatically use the same stability check. Playwright visual comparisons

5. Troubleshoot screenshots that show the login page

Symptom Likely cause Fix
Screenshot shows the IdP or login form The script captured during a redirect, or the chosen wait only observed an intermediate URL. Wait for the final application destination and assert an authenticated-only element before capture.
URL wait passes, but dashboard content is missing The app redirected before client-side session initialization or data rendering finished. Add a locator assertion for the actual authenticated content, such as a dashboard heading or account control.
URL wait times out The expected URL differs due to a trailing slash, callback route, query string, or failed login. Inspect the actual final URL and login state; change the wait to match the stable destination and investigate the auth flow if it never arrives.
Authenticated locator times out The selector is wrong, the element is not unique, the session is invalid, or the app uses a different authenticated layout. Verify the locator against the signed-in page, ensure it only represents the desired state, and confirm the stored or newly created session is valid.
Stored state works once and then fails The SSO session expired, was revoked, or depends on state not present in the reused context. Regenerate state through the login setup and avoid treating auth state as permanent.
Intermittent browser or CDP capture error during SAML Some issue reports describe version- and environment-specific failures in SAML/CDP workflows. Reproduce with the exact Playwright and browser versions, isolate the login and capture steps, and consult the relevant issue report. These reports do not establish a universal failure or fix: Playwright issue tracker.

A fixed delay can help accommodate a known application delay, but it should follow a state-based wait rather than replace one. A delay by itself cannot establish that authentication completed or that the content is ready.

6. Performance, reliability, and cost

Browser startup, SAML redirects, identity-provider response time, and application rendering all contribute to capture time. Reusing authenticated state can avoid repeating the interactive login on every capture, but it introduces session-expiry and secret-storage responsibilities. Keep the completion condition specific so the test fails with a useful timeout instead of producing a misleading login screenshot.

For a single capture, Playwright’s browser workflow has no per-screenshot service charge, but you operate the browser runtime and maintain the login flow. CI environments also need the browser installed and access to the application and identity provider. Protect screenshots and auth state as sensitive artifacts where appropriate.

Or skip the browser setup

If the page is publicly reachable, ScreenshotNeo can capture it with one request. It is a website screenshot API and MCP server from Yorker Media. For pages behind SAML, a screenshot API request does not perform your interactive SAML login; use the browser workflow above unless you can provide an authorized authenticated request context supported by the service’s options. See the ScreenshotNeo documentation for request configuration.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

Equivalent Python and Node.js requests:

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 and consent banners, newsletter popups, and chat widgets are removed before capture; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status.
  • An MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account for 1,000 screenshots a month with no card.

Frequently asked questions

Can I take the screenshot directly from the SAML identity-provider page?

You can capture whatever page the browser is showing, but that may be the identity provider or a callback screen rather than the signed-in application. Wait until your application’s authenticated state is visible when that is the page you need.

Should I use a fixed timeout after the SAML redirect?

Not as the only readiness check. Prefer the final destination or an authenticated UI assertion. A delay can supplement these checks when your app has a known post-login settling period.

Does saving Playwright storage state save the screenshot?

No. Storage state preserves browser authentication data for a later context. Call the Page screenshot API separately to save an image.

Will ScreenshotNeo complete an interactive SAML login for me?

The documented ScreenshotNeo facts here describe screenshot requests and options, not an interactive SAML sign-in flow. Use Playwright for the interactive login process described in this guide.