How to Capture Authenticated Pages in Percy
Capture a logged-in page in Percy by setting the browser’s authenticated state, then provide required request headers for protected assets.
To capture an authenticated page in Percy, first make your browser test reach the page in its logged-in state. If Percy’s asset discovery also needs to fetch protected images, stylesheets, or other resources, pass the required request headers on the snapshot call or configure them globally in .percy.yml. These are separate steps: logging in establishes the page state; request headers let protected network requests succeed.
Percy’s official changelog documents both header configuration methods and shows an Authorization header in a Cypress example. The entry is dated October 8, 2019, so its @percy/agent v0.18.0 prerequisite is a historical minimum, not current compatibility advice. Check your installed package and current Percy documentation before changing versions. Percy authentication changelog
1. Reach the authenticated page in your test
Set up the browser session using the authentication method your application supports, then navigate to the exact page and state you want Percy to capture. For example, your test may log in through the application’s sign-in form, use a test-specific session setup, or visit a route after the test environment has established a session. The available Percy sources do not prescribe a login method, so use the approach already supported by your application and test suite.
Keep the Percy project token distinct from application credentials. The project token associates a test upload with a Percy project; application authentication lets the browser or network request access protected content. Percy’s Cypress guide describes the project token setup, while the authentication changelog covers protected request headers. Percy Cypress visual testing guide
2. Pass headers on one Cypress snapshot
When a particular capture needs protected resource requests, add requestHeaders to that snapshot call:
cy.percySnapshot('Account dashboard', {
requestHeaders: {
Authorization: `Bearer ${Cypress.env('APP_ACCESS_TOKEN')}`
}
})
This is a Cypress example using a bearer token sourced from the test environment. The Percy changelog’s documented example uses Basic authentication instead; use the scheme and header value your server requires. Do not copy the changelog’s illustrative username and password as a real credential.
A minimal runnable test shape, assuming your project already has Cypress and Percy configured, is:
describe('authenticated dashboard', () => {
it('captures the dashboard', () => {
cy.visit('/login')
cy.get('[name=email]').type(Cypress.env('TEST_USER'))
cy.get('[name=password]').type(Cypress.env('TEST_PASSWORD'), { log: false })
cy.get('button[type=submit]').click()
cy.url().should('include', '/dashboard')
cy.percySnapshot('Account dashboard', {
requestHeaders: {
Authorization: `Bearer ${Cypress.env('APP_ACCESS_TOKEN')}`
}
})
})
})
Adapt selectors, routes, and authentication to your app. If the browser is already authenticated through a cookie or session, the additional header may be unnecessary for the page itself; it is useful when Percy needs that header to retrieve protected assets during discovery.
3. Configure headers globally
If the same request header applies across Percy asset discovery in the project, the changelog documents this configuration in .percy.yml:
version: 1
agent:
asset-discovery:
request-headers:
Authorization: 'Bearer YOUR_TEST_TOKEN'
The key spelling differs from the per-snapshot option: use requestHeaders in the Cypress call and request-headers under agent.asset-discovery in YAML. The official example shows global placement under the shared Percy configuration. The reviewed documentation does not specify precedence when both local and global headers are configured, so choose one clear source unless you have confirmed the behavior in current Percy documentation.
4. Choose per-snapshot or global configuration
| Placement | Use it when | What to check |
|---|---|---|
Per snapshot: requestHeaders |
Only one capture, route, or test needs the header. | Confirm the header value is available in that test’s runtime environment. |
Global: agent.asset-discovery.request-headers |
The same header is needed for asset discovery across the project. | Confirm the configured header is appropriate for every relevant snapshot. |
Percy’s changelog says this capability requires @percy/agent v0.18.0 or newer. That is the minimum stated in the 2019 entry; it is not a recommendation to install that version today. Inspect the dependency currently in use with:
npm ls @percy/agent
Then consult current official Percy documentation for compatibility with your installed SDK and agent. The reviewed source does not establish that every SDK or authentication scheme uses the same syntax.
5. Verify the capture
- Run the browser test against the intended test environment.
- Confirm the browser reaches the logged-in route before the snapshot call.
- Review the Percy result for missing or failed protected assets, as well as the rendered page state.
- If assets are missing, verify that the server accepts the configured header for those asset requests and that the header is being applied at the scope you selected.
Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| The snapshot shows a sign-in page. | The browser did not reach an authenticated state before capture, or the session expired. | Assert the expected route or an authenticated page element before calling cy.percySnapshot; refresh the test session setup. |
| The page loads but protected images or styles are missing. | Asset discovery requests lack a header required by the asset server. | Set the required request header per snapshot or in the global asset-discovery configuration, then check the snapshot again. |
| Requests still return unauthorized. | The value, authentication scheme, or header name does not match what the protected server expects, or the credential is expired. | Use the exact header expected by your server and a valid test credential. The Percy example demonstrates a header mechanism; it does not promise support for every server-specific scheme. |
| The configuration appears to have no effect. | The installed agent may not meet the documented historical prerequisite, or the option may be placed under the wrong key. | Run npm ls @percy/agent, check current Percy guidance, and verify the distinct JavaScript and YAML key spellings. |
| The upload is associated with the wrong project or fails project setup. | The Percy project token is missing or belongs to another project. This is separate from application authentication. | Check the project token and test-run setup described in Percy’s Cypress guide; keep it distinct from application credentials. |
Security, reliability, and cost notes
- Use test credentials with only the access required for the pages being captured. Keep secrets in your test environment or secret store rather than committing live credentials into source control.
- Use a stable test account and deterministic page data so visual changes reflect application changes rather than changing account content.
- Expired sessions, rotating tokens, redirects, and protected third-party assets can make captures inconsistent. Check both the browser state and the server’s response to asset requests.
- The reviewed Percy sources do not provide pricing, timing benchmarks, or a complete matrix of supported auth schemes and SDKs. Consult current official Percy resources for those details.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. If your task is to capture a URL directly, a single request returns a PNG, JPEG, WebP, or PDF. For an authenticated page, provide the required cookies or headers; the API supports custom headers, cookies, user agents, and Authorization. See the ScreenshotNeo API 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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
These examples use the supplied Stripe target as a placeholder; replace it with your page URL. Add your actual authentication header or cookie as needed. ScreenshotNeo removes cookie banners, popups, and chat widgets before the shot; bot checks, blank pages, and failed loads are never billed; an MCP server lets AI agents take screenshots; and 1,000 screenshots a month are free with no card, with paid plans starting at $5 for 3,000. Sign up for free and get 1,000 screenshots a month with no card.
FAQ
Does a Percy project token log the browser into my application?
No. The project token associates the test upload with a Percy project. Application login and request headers serve access to protected app content.
Can I use the documented Basic authentication example?
Only if your protected server expects that Basic Authorization value. The changelog’s encoded example is illustrative, not a credential to reuse.
Does the 2019 changelog establish support for every Percy SDK?
No. It documents a Cypress snapshot example and global agent configuration. Confirm syntax and compatibility for your current SDK in Percy’s current documentation.


