ScreenshotNeo

BlogHow-to

How to Visually Test a Remix App with Cypress

Add repeatable visual regression checks to a Remix app with Cypress: configure a stable local server, capture meaningful states, and compare approved baselines.

By the ScreenshotNeo team4 October 20268 min read

To visually test a Remix app with Cypress, run the app at a stable local URL, configure Cypress E2E with that URL as baseUrl, visit a route, assert that it has reached the intended state, and pass a screenshot to an image-comparison plugin or hosted visual-testing service. Cypress can capture screenshots, but cy.screenshot() alone does not compare images or detect regressions.

Remix’s documented E2E guide uses Playwright with a locally served app. Cypress can still drive a Remix app as an external browser-testing workflow; treat this as a general Cypress setup, not a first-party Remix test integration. Remix testing guide · Cypress E2E guide

1. Understand what the visual check does

A functional assertion checks a condition such as whether text appears or a button is enabled. A visual assertion compares a rendered image with an approved baseline, helping catch changes to layout, spacing, colors, fonts, icons, and other rendered details. Those checks answer different questions, so a visual test usually complements functional tests.

Cypress captures the application. An image-comparison plugin or service supplies the comparison, baseline storage, and review workflow. Cypress documents both local or community plugins and hosted integrations; command names and setup vary by tool. Cypress visual testing documentation · cy.screenshot() API

2. Start the Remix app and configure Cypress

  1. Use the development or preview command defined by your Remix project to start the app. Keep the server running while Cypress runs. There is no single start command that applies to every Remix project or deployment setup.
  2. Set Cypress’s baseUrl to the local address and port where the app is listening. For example, if your app is available at http://localhost:3000, configure that address in your Cypress configuration.
  3. Put the browser test in your project’s E2E test directory and visit a route with cy.visit('/') or another route-specific path.
// cypress.config.js
const { defineConfig } = require('cypress');

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

Keep the server lifecycle outside the Cypress test process. Cypress recommends starting the web server separately rather than starting it from Cypress scripts. If your project already has a documented local or preview workflow, use that workflow and match baseUrl to its actual address.

3. Write a test that reaches a meaningful state

Choose a page state a reviewer can evaluate, such as an open navigation menu, a populated dashboard, or a form displaying validation feedback. Make the state explicit: visit the route, perform the interaction or provide deterministic data, and assert that the expected result appears before capturing it.

// cypress/e2e/dashboard.cy.js
// This example captures a screenshot. Add the snapshot command
// required by your selected visual comparison plugin or service.

describe('dashboard visual state', () => {
  it('shows the populated dashboard', () => {
    cy.visit('/dashboard');

    cy.get('[data-cy=dashboard-title]')
      .should('be.visible')
      .and('contain', 'Dashboard');

    cy.get('[data-cy=account-summary]').should('be.visible');

    // Capture only; this is not an image comparison by itself.
    cy.screenshot('dashboard-populated');
  });
});

For actual regression checking, replace or follow the capture with the snapshot command documented by your chosen tool. Cypress’s visual-testing guide shows the general sequence—set up a page state, then call a tool-specific snapshot command—but integrations do not all use the same command or configuration. See Cypress’s integration overview.

4. Add a comparison tool and manage baselines

Choose how your team wants to create, store, compare, and review images before wiring a snapshot command into the test:

Approach Useful when Tradeoffs to plan for
Local or community Cypress image-comparison plugin You want image comparison to run in your own environment. Your team manages baseline storage, CI artifacts, comparison setup, and reviewing changes.
Hosted visual-testing service You want a managed capture, comparison, storage, or review workflow. Check the service’s current features, browser support, pricing, data handling, and review flow; hosted services may have subscription costs.

Cypress lists integrations and services including Applitools Eyes, Argos, Chromatic, Happo, LambdaTest SmartUI, Percy, Sauce Labs Visual, SmartBear VisualTest, and Wopee.io. Its guide also names local or community options including Cypress Image Diff and Cypress Image Snapshot. Check each project’s current Cypress compatibility, maintenance, features, and terms before adopting it; this article does not assume a particular integration’s current command or plan. Cypress visual testing documentation

  1. Install and configure the selected tool according to its current Cypress instructions.
  2. Replace the capture-only line with its snapshot or comparison command. Keep the page setup and state assertions before that command.
  3. Run the test to generate or compare the baseline using the tool’s documented workflow.
  4. Review image differences. Approve a new baseline only when the visual change is intended.

Each snapshot creates review work. Start with a small set of important states and add checkpoints when they cover a meaningful page or component risk.

5. Make screenshots repeatable

A screenshot is a point-in-time capture. The page can change before the image is taken, so first wait for the expected state and assert it. Cypress screenshot API

  • Fix the viewport: set a consistent viewport for a given snapshot and keep it the same when generating and comparing baselines.
  • Control data: use fixtures or controlled network responses so the same test produces the same content. Avoid depending on live data that changes independently of the interface.
  • Control time: freeze or otherwise manage timestamps and time-dependent content when they affect the rendered page.
  • Wait for readiness: assert on the element or state that matters. Do not rely on a guessed delay when a meaningful state can be checked directly.
  • Handle motion: finish or disable CSS animations and transitions for the capture if they make the result vary.
  • Keep rendering consistent: use the same browser and rendering environment for baseline creation and comparison; pin browser versions where your workflow permits.
  • Mask narrowly: mask only genuinely uncontrollable regions, such as content that cannot be made deterministic. Avoid loosening a whole-page threshold to hide a small unstable area.
  • Pick the right scope: use an element snapshot when checking one component and a full-page capture when page-level layout is the concern.

Cypress describes component testing as a natural fit for visual checks because it isolates a component. Its component-testing setup guide lists supported frameworks and bundlers without listing Remix. Treat mounting Remix components in Cypress component testing as project-specific, and verify the actual bundler and runtime requirements before relying on a copy-paste setup. For routed and server-rendered behavior, the local-server E2E workflow above is the direct approach. Cypress component testing setup

6. Troubleshoot common failures

Symptom Likely cause What to do
cy.visit() cannot reach the app The Remix server is stopped, listening on another port, or Cypress has the wrong baseUrl. Start the app using the project’s command, confirm its local URL, and update baseUrl to match.
The test passes but no visual regression is reported The test only calls cy.screenshot(), which captures an image without comparing it. Configure an image-comparison plugin or service and call its documented snapshot command.
The snapshot shows a loading or incomplete state The capture ran before the route, data, font, or target UI state was ready. Wait for and assert the specific rendered state before the snapshot. Make network data deterministic where possible.
Snapshots differ across runs or machines Viewport, browser rendering, time, live data, animation, or other dynamic content changed. Standardize the rendering environment and viewport; control the data and clock; settle animations; mask only unavoidable variation.
A baseline update hides an unintended change The changed image was approved without reviewing the diff. Inspect the changed region and confirm the UI change is intentional before updating the baseline.
A component-test recipe does not work with the Remix app The app’s framework, bundler, or runtime needs differ from the component-testing setup. Check Cypress’s current component-testing support and your app’s actual requirements. Use E2E against the running app when testing routes and server-rendered behavior.

7. Performance, reliability, and cost

Visual tests add browser work, image comparison, and baseline review to a suite. Keep the suite focused on high-value states; a large number of near-duplicate snapshots increases runtime and maintenance without adding much coverage. Prefer component-level captures for isolated component changes and a deliberate selection of page-level captures for layout.

Local comparison can keep images within your infrastructure, but your team needs to maintain baseline storage, CI artifacts, and review. Hosted services can manage parts of that workflow and may charge a subscription. Compare the actual review experience, browser coverage, CI and pull-request workflow, data controls, and current pricing for the options you evaluate. No particular vendor’s price or capability is assumed here.

Reliability depends on capturing the same UI state under a consistent rendering setup. When a diff appears, first determine whether the product changed or the test environment did. Review and approve only intentional visual changes.

8. Or skip the browser setup

For a one-off screenshot of a public page, ScreenshotNeo can return an image or PDF from one API request. It does not replace Cypress’s route interaction and visual-baseline workflow for testing your Remix app, but it can capture a deployed page without you setting up a browser script. See the ScreenshotNeo API docs.

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}`);

Replace the example target with the URL you want to capture. 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, and the free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Sign up for 1,000 free screenshots a month, no card required.

9. FAQ

Does Cypress automatically compare screenshots?

No. Cypress captures screenshots; add a comparison plugin or service for visual regression checks.

Is Cypress the test runner documented by Remix?

Remix’s documented E2E guide uses Playwright. Cypress is an external browser-testing workflow that can run against a Remix app served locally.

Should every route have a visual snapshot?

No. Select representative states where appearance matters and where a visual change would be useful to catch and review.

Can I use this workflow to test an authenticated route?

Yes, if your Cypress test can establish the required authenticated state reliably. Keep authentication setup and test data deterministic so the snapshot represents the intended page.