ScreenshotNeo

BlogHow-to

How to Take Screenshots of Multiple URLs with Cypress

Capture a named screenshot for every URL in a Cypress test. Learn how to handle readiness, cross-origin pages, output files, CI, and common failures.

By the ScreenshotNeo team4 October 20268 min read

Use a list of URLs, visit each one in sequence, wait for the page state you need, and call cy.screenshot() with a distinct name. Cypress has no special multi-URL screenshot command; this pattern combines its regular visit and screenshot commands.

1. Set up Cypress and choose your URLs

For routes in one application, configure baseUrl and list relative paths. For unrelated sites, use full URLs. Keep an explicit name beside each URL so output filenames stay stable when routes or query strings change.

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    screenshotsFolder: 'cypress/screenshots',
  },
})

Save the following as cypress/e2e/page-screenshots.cy.js:

const pages = [
  { path: '/', name: 'home' },
  { path: '/about', name: 'about' },
  { path: '/pricing', name: 'pricing' },
]

describe('page screenshots', () => {
  for (const page of pages) {
    it(`captures ${page.name}`, () => {
      cy.visit(page.path)
      // Replace this with an assertion for the page's actual ready state.
      cy.get('main').should('be.visible')
      cy.screenshot(page.name)
    })
  }
})

Each URL gets its own test, which isolates failures and makes it easy to rerun one page. To capture the whole list in one test instead, use a loop inside the test:

describe('page screenshots', () => {
  it('captures every listed page', () => {
    for (const page of pages) {
      cy.visit(page.path)
      cy.get('main').should('be.visible')
      cy.screenshot(page.name)
    }
  })
})

Cypress queues commands, so keep navigation and capture inside Cypress’s command flow. Avoid wrapping cy.visit() calls in ordinary asynchronous JavaScript such as Promise.all(); Cypress visits pages sequentially in a test.

2. Wait for the page you intend to capture

cy.visit() resolves when the page fires its load event. A client-rendered app may still be loading data or updating its layout at that point. Assert a meaningful, visible element or application state before taking the screenshot.

cy.visit('/reports')
cy.get('[data-testid="report-ready"]').should('be.visible')
cy.get('[data-testid="loading-indicator"]').should('not.exist')
cy.screenshot('reports')

Prefer an observable condition over an arbitrary delay such as cy.wait(3000). A fixed wait can be too short on a slow run and waste time on a fast one. If a specific request determines readiness, wait for that request and then assert the rendered result:

cy.intercept('GET', '/api/reports').as('reports')
cy.visit('/reports')
cy.wait('@reports')
cy.get('[data-testid="report-ready"]').should('be.visible')
cy.screenshot('reports')

Make the capture state deterministic too: seed test data, dismiss consent dialogs if appropriate for the test, and wait for animations or transient banners to finish when they affect the image. Cypress notes that the page can change around an asynchronous screenshot, so establish the state you want before capture.

3. Choose screenshot names and output behavior

Cypress saves screenshots under cypress/screenshots by default, organized with the spec path. Set screenshotsFolder in configuration if your CI artifact collection expects a different directory.

  • Use stable labels: names such as home and pricing are easier to maintain than names generated from URLs.
  • Sanitize generated names: if names must come from URLs, remove or replace slashes, query strings, fragments, and characters unsuitable for filenames.
  • Avoid collisions: Cypress automatically adds numeric suffixes to repeated screenshot names. Use { overwrite: true } only when replacing the earlier image is intentional.
  • Keep captures organized: use a predictable directory and artifact naming scheme if screenshots are consumed by another CI step.
cy.screenshot('pricing-desktop', { overwrite: true })

Use overwrite: true only when one stable output per name is wanted. Otherwise, unique labels preserve separate captures.

4. Viewport, full-page, and element captures

Choose the capture size to match the question the screenshot should answer. A viewport capture gives each URL the same visible browser dimensions, which is useful for consistent page comparisons. A full-page capture includes content beyond the viewport; Cypress scrolls and stitches the page, so fixed or sticky elements can appear more than once.

// Configure a consistent viewport before visiting each page.
cy.viewport(1440, 900)
cy.visit('/pricing')
cy.get('main').should('be.visible')
cy.screenshot('pricing-viewport')

// Capture the full document instead.
cy.screenshot('pricing-full-page', { capture: 'fullPage' })

For one component, capture the element rather than the entire page:

cy.get('[data-testid="pricing-table"]').screenshot('pricing-table')

Cypress also supports clipping a specific region with screenshot options. See the cy.screenshot() API for the current option names and behavior.

5. Same-site routes and cross-origin URLs

With baseUrl set, cy.visit('/about') visits that path on the configured host. Pass a full URL for a different host, such as https://example.com/. Visiting another superdomain causes Cypress to reload the window. Interactions with a cross-origin page require cy.origin(); a simple visit followed by a screenshot may not need cross-origin interactions.

const pages = [
  { url: 'https://example.com/', name: 'example-home' },
  { url: 'https://www.cypress.io/', name: 'cypress-home' },
]

describe('external page captures', () => {
  for (const page of pages) {
    it(`captures ${page.name}`, () => {
      cy.visit(page.url)
      cy.get('body').should('be.visible')
      cy.screenshot(page.name)
    })
  }
})

External websites can block automation, redirect, require authentication, or change without notice. For repeatable application tests, prefer controlled test environments and data. Follow the target site’s terms and access requirements.

6. Run locally and in CI

Run interactively to inspect and debug the captures, or run headlessly to create CI artifacts:

# Interactive Cypress runner
npx cypress open

# Headless run
npx cypress run --spec 'cypress/e2e/page-screenshots.cy.js'

Manual cy.screenshot() calls work in both modes. In cypress run, Cypress also captures screenshots automatically when a test fails; it does not do this automatically in cypress open. By default, Cypress clears the screenshots folder and its nested contents before a run. If your workflow deliberately retains prior assets, set trashAssetsBeforeRuns: false and manage cleanup yourself.

// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    screenshotsFolder: 'cypress/screenshots',
    trashAssetsBeforeRuns: false,
  },
})

Ensure the application is running before the Cypress command, and configure CI to collect the screenshots folder as an artifact. Cypress documents that CI screenshots can also be viewed in Cypress Cloud; availability and workflow depend on your Cypress setup.

7. Troubleshooting

Symptom Likely cause Fix
The screenshot shows a loading state or incomplete page The browser’s load event fired before client-side rendering or data loading finished. Wait for a meaningful app element or the relevant network request, then assert the rendered state before capture.
A later URL is not captured An earlier visit or assertion failed, stopping the test. Use one test per URL to isolate failures, or inspect the first failed command in the Cypress runner and fix its readiness condition.
Navigation to another host fails or interaction is blocked The destination is a different superdomain, or an interaction crosses origins. Use a full URL for the visit and cy.origin() for cross-origin interactions. Check redirects and access restrictions on the destination.
Screenshot files disappear on the next run cypress run clears the screenshots folder before execution by default. Collect artifacts after each run, or disable cleanup with trashAssetsBeforeRuns: false and clean files explicitly.
Files have unexpected numeric suffixes Names were reused and Cypress avoided silently replacing an existing image. Give each page and capture mode a unique name, or set overwrite: true when replacement is intended.
Full-page screenshots repeat a header or sticky control Full-page capture stitches the page across scroll positions. Use a viewport or element capture, or account for sticky positioning in the test page.
The output folder is empty The test may not have reached the screenshot command, the configured folder differs, or the run cleaned prior files. Check the command log, verify screenshotsFolder, and inspect artifacts after the current run.

8. Performance, reliability, and cost

Cypress visits and captures the pages sequentially in this pattern. The total run time therefore grows with page load and readiness time for each URL. Keep the list focused, use a small stable readiness assertion, and avoid unconditional sleeps. Splitting pages into separate tests improves failure isolation, while running those tests in parallel requires a CI setup that distributes specs or jobs deliberately.

These screenshots are local files produced by your Cypress run; the workflow itself does not require a per-screenshot API charge. CI still uses compute time and artifact storage, and external pages add network variability. For repeatability, capture a controlled app environment and preserve only the artifacts your workflow needs.

9. Or skip the browser setup

If you need image files for many URLs without maintaining a Cypress browser run, ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request returns a PNG, JPEG, WebP, or PDF. The API documentation describes the 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,
)
r.raise_for_status()
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);

Replace the example target with the page you want. For multiple URLs, make one request per URL and save each response under a distinct filename. ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before capture; bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Sign up for ScreenshotNeo’s free plan to get 1,000 screenshots a month with no card.

10. FAQ

Can one Cypress test take screenshots of multiple URLs?

Yes. Visit each URL and capture it in sequence inside the test. Use separate tests when you want failures isolated by page.

Does Cypress have a batch screenshot command?

No. The multi-page pattern is a loop around the standard cy.visit() and cy.screenshot() commands.

Can I save screenshots outside the project?

Configure screenshotsFolder for the output location that fits your project or CI artifact workflow.

Why can the same page look different between runs?

Dynamic content, external dependencies, animations, responsive viewport dimensions, and data can change the rendered page. Control those inputs and assert the intended state before capture.

References: Cypress visit(), screenshot(), Screenshots and Videos guide, and configuration reference.