Fix Playwright Screenshots of an Indian Portal That Redirects to the Login Page
If a Playwright screenshot shows a login page, first verify that the capturing browser context has current, complete authentication state. Follow this checklist to trace redirects and reuse state safely.
If a Playwright screenshot of a protected portal shows its login page, the browser context that took the screenshot probably was unauthenticated, had expired state, or was missing a state component the portal requires. The exact cause depends on the portal. First confirm that login completes in the same browser engine and environment, then save the resulting browser state and use it in the context that navigates to the protected page.
A click on a sign-in button is not proof of authentication. Check the final URL and a page-specific signed-in signal before saving state or capturing. Do not try to bypass the portal’s security controls; use an authorized account and follow the portal owner’s automation policy.
1. Confirm where the redirect happens
- Run the flow in the same Playwright browser engine, machine or CI environment, and browser context configuration used by the screenshot job.
- Record the URL before navigation, the URL after navigation settles, and whether the expected authenticated page element appears.
- If the page lands on login, inspect the redirect chain and the browser’s cookies. A browser follows redirects as part of navigation, so the final URL is more useful than assuming the first request’s URL or status describes the outcome. [Playwright network documentation]
- Check whether a state file is current and whether the page is opened in the context configured to use it.
For a local trace, enable Playwright tracing for the relevant run and inspect the navigation, requests, and final page. A trace helps establish what happened; it does not establish why a particular portal rejected a session. Portal-specific causes such as session expiry, additional identity checks, or browser-bound state must be verified with the portal owner’s supported flow.
2. Save authentication state after login succeeds
Playwright’s recommended pattern is to create an authentication setup project, complete login, wait for authentication to finish, and save the browser state for later tests. The example below uses a visible authenticated-page marker and a final URL check. Replace the sample URL, selectors, and credentials handling with values for the portal and authorized test account.
// tests/auth.setup.ts
import { test as setup, expect } from '@playwright/test';
import path from 'node:path';
const authFile = path.join(__dirname, '../playwright/.auth/user.json');
setup('authenticate', async ({ page }) => {
await page.goto('https://portal.example.in/login');
await page.getByLabel('Username').fill(process.env.PORTAL_USERNAME!);
await page.getByLabel('Password').fill(process.env.PORTAL_PASSWORD!);
await page.getByRole('button', { name: 'Sign in' }).click();
// Confirm the completed flow, rather than merely waiting after a click.
await expect(page).toHaveURL(/\/dashboard(?:\?|$)/, { timeout: 30_000 });
await expect(page.getByRole('navigation', { name: 'Account' })).toBeVisible();
// Save only after redirects and authentication state have settled.
await page.context().storageState({ path: authFile });
});
The labels, URL pattern, and authenticated marker are illustrative: use selectors and a completion condition that actually identify the portal’s signed-in state. If its flow takes longer, use a suitable timeout and a real completion signal instead of adding an arbitrary fixed sleep. Playwright documents both waiting for the final URL and checking an authenticated UI element in its setup examples. [Playwright authentication documentation]
3. Reuse the state in the screenshot project
Configure a setup project as a dependency so the state is created before the project that captures screenshots. The capture test must use the project context that loads the state.
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium-authenticated',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
dependencies: ['setup'],
},
],
});
// tests/portal-screenshot.spec.ts
import { test, expect } from '@playwright/test';
test('captures the protected portal page', async ({ page }) => {
await page.goto('https://portal.example.in/reports');
await expect(page).toHaveURL(/\/reports(?:\?|$)/, { timeout: 30_000 });
await expect(page.getByRole('heading', { name: 'Reports' })).toBeVisible();
await page.screenshot({ path: 'artifacts/portal-reports.png', fullPage: true });
});
Use the expected page URL and a stable, page-specific element where possible. The screenshot should happen only after both conditions pass. If the state has expired, rerun the setup flow to regenerate it. Browser contexts are isolated, so state used by a different context or a separately launched browser does not automatically authenticate this capture.
4. Check the state the portal actually uses
Cookies, local storage, IndexedDB, and passkeys
Playwright’s standard storage state supports cookies, local storage, IndexedDB, and passkey authentication data. Save state only after login and any redirect sequence have completed. Keep the browser engine consistent when investigating: do not assume authentication saved from one browser will work in another if the portal or identity provider binds state to a browser.
Session storage
Session storage is a less common exception: it is scoped to a domain and is not persisted by the standard storage-state mechanism across page loads. If the portal relies on it, explicitly capture and restore the required values with an initialization script scoped to the portal origin. Do not copy session data indiscriminately to unrelated origins.
// Example pattern: capture the portal's sessionStorage after successful login.
const sessionData = await page.evaluate(() => ({
origin: location.origin,
entries: Object.fromEntries(
Array.from({ length: sessionStorage.length }, (_, i) => {
const key = sessionStorage.key(i)!;
return [key, sessionStorage.getItem(key)];
}),
),
}));
// Write sessionData to a protected test-only file using your project's
// secret-safe file handling. Before navigation, restore only for that origin:
await context.addInitScript(({ origin, entries }) => {
if (location.origin !== origin) return;
for (const [key, value] of Object.entries(entries)) {
if (value !== null) sessionStorage.setItem(key, value);
}
}, sessionData);
This is a pattern, not a universal portal workaround: identify the specific keys the authorized application needs, protect the captured values, and validate the flow. Playwright’s authentication guidance describes session storage as requiring separate handling when an application depends on it. [Playwright authentication documentation]
Redirects and cookies
Inspect the actual navigation and redirect chain. The browser obtains cookies from its cookie store; passing a Cookie header through route continuation is not a substitute for correctly establishing browser cookie state. Use the supported login flow, then inspect what cookies and storage exist in the capturing context. [Playwright network documentation]
5. Choose the right authentication setup for parallel tests
- Shared state: Logging in once and reusing state can suit tests that do not modify shared server-side data and can safely use the same account.
- Separate accounts per worker: When parallel tests mutate shared server-side data, Playwright recommends separate accounts per worker to avoid interference.
- API login: Use an API-based login only when the portal supports an authentication API appropriate for the test. It is an advanced option, not a universal way around an interactive flow.
- Browser-specific state: Verify state in each browser engine you run. A successful login in one engine does not prove its state is usable in another.
These choices affect account contention, state lifetime, and which browser context owns the authenticated session. Avoid sharing a single mutable account across workers when their tests can change the same server-side records.
6. Keep saved state secret
Authentication state can contain cookies and headers that allow someone to impersonate the test account. Playwright explicitly warns that the saved state file may contain sensitive credentials. Store it in a protected location, exclude the auth directory from source control, avoid attaching it to bug reports, and rotate or invalidate the account session if it is exposed. [Playwright authentication documentation]
# .gitignore
/playwright/.auth/
7. Troubleshooting checklist
| Symptom | Likely cause | What to check or fix |
|---|---|---|
| Screenshot is the login page | The capture context is unauthenticated, the state expired, or a required state component is missing. | Check final URL and signed-in marker; confirm the configured state path and regenerate state through the authorized login flow. |
| Setup clicks Sign in but saves login state | The click completed, but authentication did not; the flow may still be redirecting or displaying an error. | Wait for the expected authenticated URL and visible signed-in element before calling storageState(). |
| State works locally but fails in CI | The CI run may use a different browser, stale state, different environment, or a different login outcome. | Run setup in the same CI job and browser engine; capture the final URL and trace; do not assume a local state file is portable. |
storageState exists but login still redirects |
The portal may rely on sessionStorage, state may have expired, or the capture test may use another context. | Verify the context configuration; inspect supported storage; separately restore sessionStorage only if the application requires it. |
| Manually adding a Cookie header changes nothing | The browser’s cookie store supplies navigation cookies; a route continuation header is not the same as establishing that store. | Use supported browser login/state setup and inspect cookies in the context. |
| Tests intermittently affect each other | Parallel workers may share an account and mutate common server data. | Use separate accounts per worker when tests modify shared data, or serialize the affected tests. |
| State works in one browser but not another | Authentication may be browser-specific or tied to browser-side state. | Run the setup flow in each target engine and save state for that engine’s capture project. |
The dossier does not identify the portal, its identity provider, Playwright version, account flow, or trace. Therefore, no particular portal behavior or root cause can be concluded from the redirect alone.
8. Performance, reliability, and cost
Reusing saved state avoids repeating the interactive login flow before every capture, but the state can expire and still needs a safe refresh process. A setup project makes that dependency explicit; a page URL and authenticated UI assertion make failures easier to diagnose before producing a misleading screenshot. Parallel workers need account isolation if they change shared data. The main operational cost is maintaining authorized test accounts and a secure, current state file. No portal-specific timing or reliability figures are available, so measure the flow in the actual CI environment rather than relying on a generic benchmark.
Or skip the browser setup
If your task is to capture a public page, ScreenshotNeo can return an image with one API request. It cannot authenticate to a protected portal by magic, so it is not a replacement for establishing an authorized session for this login-protected page. Its API and options are documented at ScreenshotNeo documentation.
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}`);
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed. Its 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. Learn about ScreenshotNeo, then sign up for 1,000 free screenshots a month, with no card.
FAQ
Why does Playwright redirect to login?
The context may lack current authentication state or a state component required by that portal. Verify the final URL, the signed-in UI, and the storage used by the authorized flow.
How do I reuse Playwright login state for screenshots?
Save state after login has reached a confirmed authenticated page, configure the screenshot project with that state, and capture from its configured context.
Why does storageState still send me to login?
Check expiry, context wiring, browser differences, and whether the portal depends on sessionStorage, which needs separate persistence and restoration.
Can I use an API request to log in instead?
Only if the portal provides a supported authentication API suitable for the test. Otherwise follow the portal’s authorized UI flow.


