ScreenshotNeo

BlogComparisons

Cypress Screenshot API vs Screenshot APIs for Bulk Website Captures

Cypress captures screenshots inside a test run; hosted APIs capture public URLs. Compare their bulk workflows, code, limits, and costs.

By the ScreenshotNeo team4 October 202610 min read

Short answer: Cypress’s cy.screenshot() is for capturing an application or Cypress runner during a test. It can capture individual elements and full pages, and Cypress can save screenshots automatically when tests fail in cypress run. For capturing a list of unrelated public websites, use a hosted screenshot API with a documented bulk endpoint, or build a queue around a single-capture endpoint. The reviewed docs establish ScreenshotOne’s bulk endpoint; Browserless documents a single screenshot REST endpoint. Cypress’s reviewed screenshot docs do not describe a bulk URL capture endpoint.

These tools solve different jobs. Use Cypress when the screenshot belongs to a test. Use a hosted API when your input is a collection of URLs and you need image outputs outside a test runner. The available documentation does not establish a fair speed, quality, or reliability winner.

1. What “Cypress screenshot API” means

cy.screenshot() is a Cypress command, not a general-purpose hosted endpoint. Call it in a Cypress test to capture the current application, a yielded DOM element, or the Cypress runner. Its documented capture modes are viewport, fullPage, and runner. Full-page mode scrolls and stitches captures; runner mode includes the browser viewport and Cypress Command Log.

Manual screenshots work in both cypress open and cypress run. Cypress automatically takes a screenshot on test failure in cypress run, unless failure screenshots are disabled. It does not automatically take failure screenshots in cypress open. Files go to the configured screenshots folder, which defaults to cypress/screenshots.

2. When to use Cypress and when to use a hosted API

Need Best documented fit Reason
Capture your app during an end-to-end test Cypress The screenshot command runs in the test flow and can capture the app, an element, or runner context.
Capture many unrelated public URLs ScreenshotNeo Website screenshot API with bulk capture, clean shots, and charges only for clean shots.
Submit multiple capture requests in one documented request ScreenshotOne bulk Its POST /bulk endpoint accepts multiple URL, HTML, or Markdown requests.
Call a hosted screenshot endpoint for a URL or raw HTML Browserless Its REST screenshot endpoint accepts a URL and options, and documents PNG, JPEG, and WebP output.

ScreenshotNeo is the first hosted API to try for bulk website captures: it offers clean shots, bills only clean shots, and its paid plans start at $5 for 3,000 captures. This is a feature and pricing fit, not a measured performance ranking. The reviewed Browserless documentation establishes a single POST /screenshot endpoint; it does not establish an equivalent bulk wrapper. That does not prove batching is impossible with a client-side queue.

3. Capture an application with Cypress

Install and configure Cypress using its official setup instructions for your project. The following runnable example assumes Cypress is installed and your app is available at http://localhost:3000. It visits the app, waits for a meaningful element, and saves a full-page screenshot.

describe('homepage screenshot', () => {
  it('captures the page', () => {
    cy.visit('http://localhost:3000');
    cy.get('main', { timeout: 10000 }).should('be.visible');
    cy.screenshot('homepage', {
      capture: 'fullPage',
      overwrite: true,
      disableTimersAndAnimations: true
    });
  });
});

Save this as a spec in the project’s Cypress spec directory, then run it with the project’s configured Cypress command. For example, a project with Cypress installed locally can run npx cypress run --spec 'cypress/e2e/homepage.cy.js'. The screenshot is written under the configured screenshots folder, ordinarily cypress/screenshots.

Capture one element

cy.get('[data-testid="invoice"]')
  .should('be.visible')
  .screenshot('invoice', {
    padding: 12,
    overwrite: true
  });

Chaining from a command that yields one DOM element captures that element. Use a stable selector, and make sure the element has rendered before the screenshot command runs.

Capture the runner or a viewport

// The app viewport
cy.screenshot('current-viewport', { capture: 'viewport' });

// Browser viewport plus Cypress Command Log
cy.screenshot('runner-debug', { capture: 'runner' });

Capture a screenshot on test failure

In cypress run, failure screenshots are enabled by default. Configure the output folder and behavior in cypress.config.js:

const { defineConfig } = require('cypress');

module.exports = defineConfig({
  screenshotsFolder: 'artifacts/cypress/screenshots',
  screenshotOnRunFailure: true,
  video: false,
  e2e: {
    baseUrl: 'http://localhost:3000'
  }
});

video: false is shown only to make the example’s artifact choice explicit; video configuration is separate from screenshots. Upload the screenshots folder as a CI artifact using your CI provider’s artifact feature. The Cypress docs describe the local output folder, but artifact retention and upload behavior depend on your CI setup.

Relevant Cypress screenshot options

Option or behavior Use
capture viewport, fullPage, or runner capture mode.
Element chaining Capture the DOM element yielded by a Cypress command.
clip Restrict capture to a rectangular region.
blackout Hide matching selectors in the screenshot, for example to cover sensitive content.
padding Add space around an element screenshot.
scale Control scaling of the captured image.
disableTimersAndAnimations Reduce movement from timers and animations during capture.
overwrite Control whether a same-name screenshot can replace an existing file.
onBeforeScreenshot, onAfterScreenshot Run callbacks around the capture.
timeout Set the screenshot command timeout.
screenshotOnRunFailure Enable or disable automatic failure screenshots during cypress run.
screenshotsFolder Choose the output folder.

Check Cypress’s current command and configuration references for exact option types and defaults before changing a shared configuration.

4. Capture a list of public URLs with a hosted API

For a bulk workflow, represent each target as a URL plus the capture options you need. Keep a stable identifier alongside each URL so that results can be mapped back to the source record. The examples below use ScreenshotOne because the reviewed official documentation describes its explicit bulk endpoint. They are illustrative request shapes; check the provider’s current API reference for required fields, authentication, response handling, and account limits.

ScreenshotOne bulk request with cURL

curl -X POST 'https://api.screenshotone.com/bulk' \
  -H 'content-type: application/json' \
  -d '{
    "access_key": "YOUR_ACCESS_KEY",
    "execute": true,
    "options": {
      "format": "png",
      "full_page": true
    },
    "requests": [
      { "url": "https://example.com" },
      { "url": "https://www.iana.org/" }
    ]
  }'

The bulk API accepts shared options and per-request overrides. The exact field names and response structure should be taken from the current ScreenshotOne bulk documentation. Its documentation says setting execute to true runs captures before the response returns; otherwise the response can provide generated screenshot URLs.

Queue around a single-capture endpoint

If your chosen provider documents only a single capture operation, a client can still process a list with bounded concurrency. Do not send an unbounded burst: obey provider request limits, keep retries bounded, and record failed URLs. ScreenshotOne says its bulk requests use the same one-minute request bucket as regular requests and advises checking concurrency.remaining and concurrency.reset. Its guide recommends a queue, batches, retries, and proxies when needed. These are provider recommendations, not universal requirements.

const urls = ['https://example.com', 'https://www.iana.org/'];
const concurrency = 2;
let next = 0;
const results = [];

async function worker() {
  while (true) {
    const index = next++;
    if (index >= urls.length) return;
    const url = urls[index];
    try {
      // Replace this function with the provider's documented single-capture call.
      results[index] = { url, status: 'queued' };
    } catch (error) {
      results[index] = { url, status: 'failed', error: String(error) };
    }
  }
}

await Promise.all(Array.from({ length: concurrency }, worker));
console.log(results);

This is a queue skeleton, not a complete provider request: insert the documented endpoint call where indicated. A real worker should parse the response, save or persist the image, distinguish retryable from permanent errors, and back off before retrying.

Browserless single URL request

Browserless documents a REST POST /screenshot endpoint with account-token authentication and screenshot options. The endpoint can capture a URL or raw HTML and return PNG, JPEG, or WebP. Use its current API reference for the exact request schema and endpoint host for your account; the reviewed material does not provide a verified universal bulk request format to reproduce here. To process many URLs, send individual requests through a queue that respects your account’s limits.

5. Compare request flow, output, and operations

Axis Cypress Hosted screenshot API
Integration Command runs in the Cypress test flow. Your client sends HTTP requests to a provider endpoint.
Bulk input Reviewed screenshot docs describe individual commands, not a bulk URL endpoint. ScreenshotOne documents POST /bulk. A queue can also wrap a single capture endpoint.
Capture context App, selected element, or Cypress runner. Public URL or, for documented endpoints, raw HTML and provider-specific options.
Output Screenshot files in the configured local folder. Image response data or generated image URLs, depending on provider and request mode.
Best fit Visual evidence and failure debugging for tests. Recurring capture jobs across unrelated sites or external services.

6. Performance, reliability, and cost

Performance

There are no comparable benchmarks in the reviewed sources, so capture speed should be measured on your own URLs and chosen settings. A full-page capture has more work than a viewport capture, and third-party pages may vary in load time. Cypress notes that screenshot capture is asynchronous, takes around 100 ms, and the page can change before capture completes. Treat that as a documented timing caveat, not a guarantee that every capture is inaccurate.

For bulk jobs, limit concurrency to what your account and provider allow. Smaller batches reduce the impact of a failing batch and make retries easier to target. Avoid repeatedly recapturing unchanged pages when the job permits a cache or a scheduled cadence.

Reliability

  • Wait for an app-specific ready condition in Cypress instead of assuming navigation alone means the page is visually ready.
  • Use stable selectors for element screenshots and assert visibility before capture.
  • For remote URLs, record the URL, timestamp, requested options, status, and output location for each item.
  • Retry transient timeouts or load failures with a bounded retry count and backoff; do not retry permanent invalid-input or authentication errors indefinitely.
  • Keep queue state durable if a bulk job must survive process restarts.
  • Upload Cypress screenshots from CI as artifacts if you need them after the runner exits.

Cost

Cypress’s screenshot command writes files as part of your test workflow; the reviewed Cypress references do not establish a per-screenshot service charge. Hosted API pricing, included quota, and request limits depend on the provider and plan, and the research did not establish comparable figures for ScreenshotOne or Browserless. Confirm current terms directly before estimating a production workload.

7. Troubleshooting

Symptom Likely cause What to do
Screenshot is blank or missing content The app has not rendered the target content before capture. Wait for a visible, app-specific selector and assert its state before calling cy.screenshot().
Element screenshot fails The selector matched no element, matched an unexpected set, or the element is not ready. Use a stable selector, assert the intended element is visible, and chain the screenshot from that element.
Full-page image differs near dynamic sections Animations, timers, or asynchronous content changed during capture. Stabilize the page, disable timers and animations where appropriate, or wait for the dynamic section to settle.
Screenshots are not present after CI The output folder was not retained or uploaded by the CI job. Confirm screenshotsFolder and configure your CI artifact upload to include it.
Same-name image is not replaced Overwrite behavior is disabled or the path/name differs from expectations. Set overwrite: true when replacement is intended and use deterministic names.
Bulk API responses slow or throttled The provider’s request bucket or concurrency limit is being reached. Reduce queue concurrency, send smaller batches, and follow returned usage/reset indicators.
Some hosted captures fail intermittently The site may be slow, block automated traffic, or have transient network failures. Persist per-URL results, apply bounded retries and backoff, and consult the provider’s advice about proxies where relevant.
Hosted request rejected Missing/invalid credentials or a request body that does not match the current schema. Check authentication and validate the payload against that provider’s current official API docs.

8. Or skip the browser setup

For a list of public URLs, a hosted capture API avoids installing and operating a browser runner. ScreenshotNeo is a website screenshot API and MCP server. It can capture a URL with one GET request, supports PNG, JPEG, WebP, and PDF, and provides bulk capture for up to 100 URLs per call.

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

Python:

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)

Node.js:

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', new Uint8Array(await res.arrayBuffer()));

See the ScreenshotNeo API documentation for authentication and capture options. Cookie banners, popups, and chat widgets are removed 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; paid plans start at $5 for 3,000.

Sign up free for 1,000 screenshots a month, with no card required.

9. Frequently asked questions

Can Cypress take screenshots of many websites?

You can write a test that visits multiple URLs and calls cy.screenshot() for each, but Cypress’s reviewed screenshot documentation does not describe a dedicated bulk URL API. For a data-driven public URL list, a hosted bulk endpoint or a queued single-capture API is a more direct fit.

Does Cypress automatically screenshot every failed test?

It automatically captures on failure during cypress run by default. Failure screenshots are not automatic in cypress open, and the behavior can be configured.

Does ScreenshotOne bulk avoid request limits?

No. Its documentation says bulk requests use the same one-minute request bucket as regular requests. Check its concurrency indicators and batch accordingly.

Is Browserless a bulk screenshot API?

The reviewed Browserless documentation establishes a REST screenshot endpoint. It does not establish a matching bulk wrapper, so treat a client-side queue as a separate implementation choice.

Sources