ScreenshotNeo

BlogHow-to

How to Test a Mobile Web App with Cypress

Test responsive mobile web behavior with Cypress by setting deliberate viewports, checking real user outcomes, and understanding what browser tests cannot emulate.

By the ScreenshotNeo team4 October 20269 min read

Use Cypress to test mobile web behavior by setting a deliberate browser viewport, loading or mounting your interface, and asserting what a user should see and be able to do at that size. For example, test that desktop navigation is hidden on a phone, the mobile menu opens, and primary actions remain usable. Cypress can test responsive websites and web apps; changing the viewport does not turn the browser into a complete iOS or Android device emulator.

What Cypress mobile web tests cover

Cypress runs tests in a browser. Its viewport controls let you check how a website or web app responds to chosen width and height values or documented device presets. This is useful for responsive behavior such as navigation changes, content wrapping, and controls appearing or disappearing.

Viewport testing is not native mobile testing. Cypress says it will not run on a native mobile app. A browser viewport change also does not simulate a device’s devicePixelRatio or reproduce every characteristic of physical hardware. Treat these tests as responsive mobile web testing in a browser, and use appropriate real-device or platform-specific testing when your requirement concerns native apps or hardware-specific behavior. See Cypress’s FAQ and viewport API.

Choose viewports around your app’s breakpoints

Do not try to test every possible screen resolution. Choose a small set that covers the responsive states your interface supports, then add sizes near important breakpoints where a layout switches. Cypress’s documented default application viewport is 1000 × 660 pixels, which is usually not a phone-sized test. Set the dimensions explicitly so the test exercises the intended layout.

Example viewport Useful checks
320 × 568 Narrow phone layout, clipped content, and long labels
375 × 812 Common phone layout and mobile navigation interaction
768 × 1024 Tablet layout and transitions between phone and desktop navigation
Near a CSS breakpoint The exact state immediately below or above a layout change

These are example dimensions, not a required device matrix. Replace them with sizes tied to your CSS breakpoints, supported audience, and important user flows. If a breakpoint is 768 CSS pixels, test just below and at or above it to catch an off-by-one layout assumption.

Set the viewport in Cypress

Set a size inside a test

cy.viewport(width, height) sets the current test’s application viewport in pixels. Cypress also documents named presets and landscape orientation. The following example uses numeric dimensions so the test matrix is explicit:

const sizes = [
  { name: 'small phone', width: 320, height: 568 },
  { name: 'phone', width: 375, height: 812 },
  { name: 'tablet', width: 768, height: 1024 },
]

describe('responsive navigation', () => {
  for (const size of sizes) {
    it(`works at ${size.name}`, () => {
      cy.viewport(size.width, size.height)
      cy.visit('/')

      cy.get('[data-testid="desktop-nav"]').should('not.be.visible')
      cy.get('[data-testid="mobile-menu-button"]')
        .should('be.visible')
        .click()
      cy.get('[data-testid="mobile-nav"]').should('be.visible')
    })
  }
})

Use selectors that are stable for tests, such as dedicated data-testid attributes. Replace the example selectors and dimensions with those in your application. This is an illustrative pattern based on the documented Cypress API; it is not a claim that the code was executed.

Use a documented preset or landscape orientation

Cypress supports named viewport presets as well as explicit pixel dimensions. A preset can make intent readable when it matches your test, while numeric dimensions are useful for a particular breakpoint. Cypress also supports switching a preset to landscape orientation. Check the API documentation for the current preset names and accepted signatures.

// Example using a documented preset and orientation
cy.viewport('iphone-6', 'landscape')

// Explicit dimensions are useful for breakpoint-focused tests
cy.viewport(375, 812)

Configure defaults or test-specific sizes

Set viewportWidth and viewportHeight in Cypress configuration if most tests need the same default. Use test configuration for a suite or individual test that needs its own defaults, or call cy.viewport() when a test needs to change size during execution. Cypress automatically restores the default viewport size between tests.

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

module.exports = defineConfig({
  e2e: {
    viewportWidth: 375,
    viewportHeight: 812,
  },
})

Configuration shape depends on whether you use Cypress end-to-end or component testing; use the relevant configuration section in Cypress’s configuration guide. Since Cypress 16.0.0, changing viewportWidth or viewportHeight through Cypress.config() during a test throws. Use cy.viewport() or test configuration instead, as described in the Cypress.config() API.

Test responsive behavior, not just screen dimensions

A viewport command only sets the space available to the app. The test becomes useful when it asserts user-visible outcomes at that size. Organize cases around the behaviors your interface promises.

  1. Navigation: confirm the appropriate navigation is visible, then open and close the mobile menu.
  2. Content layout: assert important content is visible and check for horizontal overflow where it would block use.
  3. Forms and primary actions: verify fields, labels, and submit controls are visible and usable at narrow widths.
  4. Menus and dialogs: open them at phone sizes and check that their contents and close controls remain accessible.
  5. Touch-like interactions: test the interaction your app actually uses. Cypress can mimic some mobile-like behavior, such as swiping, with custom commands, but this does not provide a full device simulation.

For end-to-end tests, visit the running application with cy.visit(). The app must be available at the configured base URL or at the URL you pass to the command. Cypress documents navigation and page-load behavior in cy.visit(). For isolated component layouts, Cypress component testing mounts a component in a real browser; it can complement end-to-end coverage of complete flows. See the component testing guide.

Separate desktop and mobile states when that improves clarity

If mobile and desktop have distinct behavior, write separate cases or suites with explicit sizes. This makes failures easier to interpret and avoids accidentally inheriting a size that does not match the scenario. Cypress’s viewport guide includes examples for organizing desktop and mobile tests and for changing the viewport to test responsive navigation.

describe('mobile navigation', () => {
  beforeEach(() => {
    cy.viewport(375, 812)
    cy.visit('/')
  })

  it('opens the mobile menu', () => {
    cy.get('[data-testid="mobile-menu-button"]').click()
    cy.get('[data-testid="mobile-nav"]').should('be.visible')
  })
})

describe('desktop navigation', () => {
  beforeEach(() => {
    cy.viewport(1280, 800)
    cy.visit('/')
  })

  it('shows desktop links', () => {
    cy.get('[data-testid="desktop-nav"]').should('be.visible')
    cy.get('[data-testid="mobile-menu-button"]').should('not.be.visible')
  })
})

Visual checks and screenshot consistency

Functional assertions catch behavior failures; screenshot comparison can catch unexpected layout or rendering changes. Keep the viewport fixed for comparisons and control the rendering environment as much as practical. Browser version, operating system, display scaling, and fonts can all affect rendered screenshots. A difference can be caused by an environment change rather than an application change, so record the conditions used for visual checks. Cypress explains these considerations in its visual testing guide.

For screenshots of a public page outside a Cypress test run, [ScreenshotNeo](https://screenshotneo.com) provides a website screenshot API and MCP server. It is separate from Cypress assertions and does not replace viewport-based interaction tests.

Or skip the browser setup

For a one-off screenshot of a URL, ScreenshotNeo returns an image or PDF with one GET request. See the ScreenshotNeo API documentation for the full set of 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,
)
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}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Cookie banners are accepted like a visitor and removed before the shot, along with known consent platforms, newsletter popups, and chat widgets. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed; response headers report the page verdict and billing status. AI agents can use the MCP server’s take_screenshot, get_page_info, and capture_pdf tools. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up for 1,000 free screenshots a month, with no card.

Troubleshooting

Symptom Likely cause Fix
The page looks desktop-sized in a mobile test The viewport was not set before visiting, or the test uses the default 1000 × 660 size. Call cy.viewport() before cy.visit() and use explicit dimensions.
A mobile selector is missing The app has not switched to the mobile layout, the chosen size is outside the expected breakpoint, or the selector differs. Confirm the CSS breakpoint, set a size on the intended side of it, and use a stable test hook.
The test passes at one phone size but fails at another Content wrapping, a breakpoint transition, or a fixed-width element may change the layout. Keep the failing size in the matrix, inspect the affected element, and assert the expected behavior at breakpoint boundaries.
Cypress.config() throws while changing viewport dimensions In Cypress 16.0.0 and later, viewport dimensions cannot be changed that way during test execution. Use cy.viewport(width, height) in the test or set dimensions through test configuration.
A screenshot differs even though the app code did not change Browser, OS, display scaling, or fonts may differ between rendering environments. Stabilize the viewport and rendering environment before comparing images.
A viewport test is being treated as proof of native behavior A browser viewport does not run a native app or reproduce all hardware behavior. Keep Cypress coverage scoped to mobile web and use a suitable native or device test for native requirements.
cy.visit() fails to load the app The development server may not be running or the URL/base URL may be incorrect. Start the app as required by the project and verify the configured base URL and route.

Performance, reliability, and maintenance

  • Keep the matrix purposeful. A small set of representative sizes plus breakpoint-edge cases usually gives clearer coverage than a large grid of nearly identical dimensions.
  • Make each case explicit. Set the viewport in the test or suite and avoid relying on a previous test’s state; Cypress restores its default between tests.
  • Use stable selectors and user outcomes. Assert visibility and interaction rather than implementation-specific pixel positions unless a visual comparison is the goal.
  • Separate functional and visual failures. Functional assertions explain what behavior failed; screenshot comparisons are sensitive to the rendering environment.
  • Account for the full test run. Each additional viewport case repeats navigation and assertions, so reserve broad matrices for flows where responsive behavior matters.

Cypress viewport tests do not carry a separate per-screenshot service cost in this workflow; execution time depends on the number and duration of your tests and your Cypress setup. For API screenshots, ScreenshotNeo charges only clean shots; the supplied plan prices are Free for 1,000 per month, Starter $5 for 3,000, Growth $15 for 15,000, Pro $39 for 60,000, Scale $99 for 250,000, and Business $249 for 1,000,000. Yearly billing gives two months free, and every feature is on every plan. Confirm current details on the product site before choosing a plan.

FAQ

Can Cypress test a mobile web app?

Yes. Cypress can test websites and web apps in a browser at chosen viewport sizes and check responsive behavior.

Does cy.viewport() emulate an iPhone or Android phone?

No. It changes the application viewport. It does not simulate device pixel ratio or all characteristics of physical devices.

Can Cypress test a native iOS or Android app?

No. Cypress’s FAQ says Cypress will not run on a native mobile app.

Should I use end-to-end or component tests?

Use end-to-end tests for complete page flows and component tests when you want to inspect a component’s responsive rendering in isolation. They complement each other.

Can I use ScreenshotNeo in place of Cypress?

Use ScreenshotNeo to capture a URL as an image or PDF. Use Cypress when you need browser-driven assertions and interactions across responsive states.