End-to-end Testing
How-tos & Guides
8 min read

Testing mobile web apps in Cypress

Tips and tricks for how to emulate mobile devices in your Cypress tests.

Kevin Tomas
Published September 28, 2022
AI Assistant for Playwright
Code AI-powered test steps with the free ZeroStep JavaScript library
Learn more

Introduction

Nowadays, the importance of mobile traffic should not be underestimated. Statistically speaking, it is more likely that you’re reading this article on a mobile device than on a desktop device. This fact has not only a huge impact on adapting the contents of a website to both mobile and desktop devices, but also for how to test a web application being rendered on mobile devices. This article will give you an overview of the distribution of the internet traffic between mobile and desktop devices and how to test web applications on mobile devices using Cypress.

The shift to mobile

The percentage of the internet traffic coming from mobile devices has increased dramatically over the past few years. While it was only 11% percent in 2011, global web traffic on mobile devices nowadays amounts to nearly 60%! This is hardly surprising, since according to oberlo.com the average US adult spends five and a half hours per day using their mobile phones. This is also reflected in the expenses for mobile ads, which will exceed the desktop ads by the end of the year 2022. Another factor which contributes to this development is the consumption of social media. According to techjury.com, 25% of the time spent on the web goes to social media. And since social media is mostly consumed on mobile devices, this is probably also a factor for the massive increase of mobile web traffic.

Introducing our example app

In order to demonstrate how to test mobile web apps in Cypress, I created a simple React App with TypeScript. Its design is fully responsive and the content itself depends on the screen size the web app is being rendered on. Below you can find screenshots for both the mobile and desktop version of the demo web app. You will notice that the background, navigation and header are different between those two versions of the demo web app. The source code of this whole project including the tests can be found here.

Below you can find the code of the App.tsx file, where either the <DesktopContent /> or <MobileContent /> is being rendered. This decision depends on the screen width, which is calculated with the custom useWindowDimensions() hook. Each time the size of the window object changes, this hook gets triggered. If the screen width is below 767 pixels, the mobile version of the web page is rendered.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// App.tsx
import useWindowDimensions from "./utils/useWindowDimensions";
import DesktopContent from "./DesktopContent";
import MobileContent from "./MobileContent";

function App() {
  const { width } = useWindowDimensions();

  return (
    <div className="flex justify-center items-start bg-orange-300 md:bg-gray-200 h-auto md:h-screen">
      {width > 767 ? <DesktopContent /> : <MobileContent />}
    </div>
  );
}

export default App;

Testing mobile web apps in Cypress using cy.viewport()

By default, Cypress has limited support for testing mobile devices. However, there are a few tricks and workarounds we can use to get Cypress to more closely emulate a mobile browser. The first approach we’ll cover is the cy.viewport() command, which you can use to control the size and the orientation of the screen for our tests.

Let’s cover all of the available ways to work with the cy.viewport() command. The test file that includes all these different options can be found at cypress/e2e/viewport.cy.js inside the GitHub repo.

I created two custom Cypress commands for testing both the mobile and desktop version of the demo app in order to avoid repeating the same code in every test case:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Cypress.Commands.add("checkMobile", () => {
  cy.get("#desktop-nav").should("not.exist");
  cy.get("#desktop-span").should("not.exist");
  cy.get("#mobile-nav").should("be.visible");
  cy.get("#mobile-span").should("be.visible");
  cy.get("h1").contains("Mobile header");
});

Cypress.Commands.add("checkDesktop", () => {
  cy.get("#mobile-nav").should("not.exist");
  cy.get("#mobile-span").should("not.exist");
  cy.get("#desktop-nav").should("be.visible");
  cy.get("#desktop-span").should("be.visible");
  cy.get("h1").contains("Desktop header");
});

By default, Cypress sets the screen width to 1000 pixels and the screen height to 600 pixels.

These default values can be overwritten in the cypress.config.ts file. Simply add these lines of code to your config file and Cypress will automatically set the viewport to these values.

1
2
3
4
5
6
7
// cypress.config.ts
import { defineConfig } from 'cypress'

export default defineConfig({
 viewportWidth:<width>,
 viewportHeight:<height>
})

Alternatively, you can set the viewport right away when starting Cypress via the command line:

1
npm run cypress --config viewportWidth=300,viewportHeight=600

Another option is to call cy.viewport(width, height) inside of your tests:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
context("Viewport", () => {
  beforeEach(() => {
    cy.visit("http://localhost:3000/");
  });

  it("cy.viewport() - set the viewport size manually to mobile", () => {
    cy.viewport(320, 480);
    cy.checkMobile();
  });

  it("cy.viewport() - set the viewport size manually to desktop", () => {
    cy.viewport(800, 1300);
    cy.checkDesktop();
  });
});

Instead of explicitly setting the screen size like in the code snippet above, you can also use certain presets to set the viewport:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
it("cy.viewport() - set the viewport size to mobile using predefined presets", () => {
  cy.viewport("samsung-s10");
  cy.wait(300);
  cy.checkMobile();
});

it("cy.viewport() - set the viewport size to desktop using predefined presets", () => {
  cy.viewport("macbook-15");
  cy.wait(300);
  cy.checkDesktop();
});

The screenshot below lists all available presets with their corresponding screen sizes:

With the following example, you can dynamically test multiple viewports. We iterate over an array of presets/screen sizes:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
const mobileSizes = ["iphone-6", "samsung-note9", "iphone-xr", [300, 800]];
const desktopSizes = ["macbook-15", "macbook-13", "macbook-11", [1024, 768]];

mobileSizes.forEach((size) => {
  it(`Should render mobile version on ${size} screen`, () => {
    if (Cypress._.isArray(size)) {
      cy.viewport(size[0], size[1]);
    } else {
      cy.viewport(size);
    }

    cy.wait(300);
    cy.visit("http://localhost:3000/");
    cy.checkMobile();
  });
});

desktopSizes.forEach((size) => {
  it(`Should render desktop version on ${size} screen`, () => {
    if (Cypress._.isArray(size)) {
      cy.viewport(size[0], size[1]);
    } else {
      cy.viewport(size);
    }
    cy.wait(300);
    cy.visit("http://localhost:3000/");
    cy.checkDesktop();
  });
});

With the cy.viewport() command it’s even possible to change the orientation of the device to landscape:

1
2
3
4
5
6
7
8
9
it("cy.viewport() - change the orientation of device", () => {
  cy.viewport("iphone-xr");
  cy.wait(300);
  cy.checkMobile();

  cy.viewport("iphone-xr", "landscape");
  cy.wait(300);
  cy.checkDesktop();
});

Emulating taps and swipes via the Chrome DevTools Protocol

Apart from using the cy.viewport() command to simulate a mobile device in Cypress, you can tell Cypress that the application is running on a touch device. When working with the Chrome DevTools, you can set the **“mobileEmulation” **flag by clicking the Icon shown on the screenshot below. This will enable Chrome’s tapping and swiping emulation support.

In Cypress, you can also programatically set this property using the Chrome Debugger Protocol (CDP). There is a npm package called cypress-cdp which exposes the CDP through a custom Cypress command. Run following command in your shell to install the package:

1
npm i -D cypress-cdp

Inside the cypress/e2e/touchDevice.cy.js file you can find following test:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <reference types="cypress" />
import "cypress-cdp";

context("Mobile Testing", () => {
  beforeEach(() => {
    cy.visit("http://localhost:3000/");

    cy.CDP("Emulation.setTouchEmulationEnabled", {
      enabled: true,
      maxTouchPoints: 1,
    });
    cy.CDP("Emulation.setEmitTouchEventsForMouse", {
      enabled: true,
      configuration: "mobile",
    });
  });

  it("simulating touch device via CDP", () => {
    cy.visit("http://localhost:3000/");
    cy.get("#touchDevice").contains("Touch device");
  });
});

In the code snippet above we invoked the setTouchEmulationEnabled and setEmitTouchEventsForMouse CDP commands with the help of the cypress-cdp package:

I extended the React app by adding a <div> element containing text that states either “Touch device” or “Not touch device”:

To determine, whether a touch device is simulated or not, the following code inside the App.tsx was used:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function App() {
  const { width } = useWindowDimensions();
  const [isTouch, setIsTouch] = useState(false);

  useEffect(() => {
    setIsTouch("ontouchstart" in window || navigator.maxTouchPoints > 0);
  }, []);

  return (
    <div className="flex justify-center items-start bg-orange-300  md:bg-gray-200 h-auto md:h-screen">
      {width > 767 ? <DesktopContent isTouch={isTouch} /> : <MobileContent isTouch={isTouch} />}
    </div>
  );
}

Setting the proper User Agent

Another way to determine on which device a web page is being rendered is the user agent. The user agent is a string inside the header of a request, which holds information about the user’s browser and operating system. Within Cypress, there are two ways to explicitly set the user agents for your tests.

The cypress.json file allows us to globally set a default value for the user agent for all tests like this:

1
2
3
{
  "userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
}

However, the downside of this approach is that you set the user agent for all of your tests. A more convenient way is to again make use of the onBeforeLoad event and set the user agent there:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
it("changing user agent", () => {
  cy.visit("http://localhost:3000/", {
    onBeforeLoad: (win) => {
      Object.defineProperty(win.navigator, "userAgent", {
        value:
          "Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1",
      });
    },
  });
});

Conclusion

This article covered several different strategies for more closely emulating mobile devices in your Cypress tests. This includes setting a proper viewport, using CDP commands to leverage Chrome’s native mobile emulating, and setting a user agent that make requests appear like they’re coming from a mobile browser.

Reflect: An alternative to Cypress with robust mobile support

Reflect is a no-code testing tool that can emulate a mobile device and test virtually any action you can take on a browser.

Creating a test in Reflect is easy: the tool records your actions as you use your site and automatically translates those actions into a repeatable test that you can run any time.

Get automated test coverage for your web app today — try it for free.

Get started with Reflect today

Create your first test in 2 minutes, no installation or setup required. Accelerate your testing efforts with fast and maintainable test suites without writing a line of code.

Copyright © Reflect Software Inc. All Rights Reserved.