Draft page
Access: Prod

Cypress

Cypress is a browser testing framework built around fast feedback. It runs close to the application, gives developers an interactive runner, records command history, and makes it easy to inspect what happened before an assertion failed.

This tutorial covers the Cypress workflow from a clean project to CI and then explains how the same suite can be routed through webmate with CypressBridge for desktop browser execution.

Goal

By the end of this tutorial you will understand how to structure Cypress specs, choose stable selectors, run tests interactively and headlessly, collect useful failure evidence, and configure CypressBridge when you want webmate to provide the desktop browser.

Before You Start

You need a Node.js project, an application URL, and Cypress installed in the application repository. For webmate execution, you also need the webmate-cypressbridge binary, a bridge configuration file, a webmate API key, and a project ID. The existing webmate Cypress documentation states that CypressBridge is designed for desktop browsers and is not compatible with mobile browsers.

Project Setup

Install Cypress in the project that owns the application tests:

1
npm install --save-dev cypress

Open Cypress once to initialize configuration:

1
npx cypress open

Cypress will create a configuration file and example structure. A simple end-to-end spec can live under cypress/e2e/documentation.cy.js:

1
2
3
4
5
6
describe('documentation search', () => {
  it('opens the docs home page', () => {
    cy.visit('https://docs.webmate.io/');
    cy.title().should('match', /webmate/i);
  });
});

Use cypress open while writing and debugging tests. Use cypress run for CI and repeatable local checks.

Step 1: Model User Flows

Cypress specs are easiest to maintain when each test describes one visible user outcome. Keep setup in commands or API helpers, then let the test focus on the browser behavior.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
describe('project creation', () => {
  beforeEach(() => {
    cy.loginByApi();
    cy.visit('/projects');
  });

  it('creates a project from the form', () => {
    cy.findByLabelText('Project name').type('Checkout smoke');
    cy.findByRole('button', { name: 'Create project' }).click();
    cy.contains('Checkout smoke').should('be.visible');
  });
});

The example uses Testing Library-style queries. If your project does not use that plugin, the same principle still applies: prefer selectors that describe user-visible labels, roles, or stable test IDs.

Step 2: Choose Selectors Deliberately

Cypress can select anything in the DOM, but not every selector is a good test contract. Generated classes and deep structural selectors break when the UI is refactored. Stable attributes are better.

1
<button data-testid="create-project">Create project</button>
1
cy.get('[data-testid="create-project"]').click();

Keep data-testid names semantic. create-project is useful; blue-button-3 is not. If the selector names the user’s intent, the test remains readable even after the layout changes.

Step 3: Run Interactively

The Cypress app is useful when writing tests because it shows the command log, browser state, screenshots, console errors, and time-travel snapshots.

1
npx cypress open

Use interactive mode to develop selectors and understand failures. Once the test is stable, run the same spec headlessly:

1
npx cypress run --spec "cypress/e2e/documentation.cy.js"

Headless execution is what CI uses. Keep the local command and CI command as similar as possible so a developer can reproduce failures.

Step 4: Configure Base URL and Environment

Put environment-specific values in cypress.config.js, environment variables, or CI secrets. Do not hard-code credentials in specs.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: process.env.APP_BASE_URL || 'http://localhost:3000',
    defaultCommandTimeout: 8000,
    video: false,
    screenshotOnRunFailure: true,
  },
});

Then tests can use relative URLs:

1
cy.visit('/login');

Use custom commands for repeated setup, but keep them small. A command named loginByApi is clear. A command named prepareEverything usually hides too much.

Step 5: Run in CI

CI should install dependencies, start the application, wait for it to be reachable, and run Cypress headlessly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
name: Cypress

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - run: npm start &
      - run: npx cypress run

For larger suites, split specs across jobs or use the Cypress dashboard if your team already uses it. Keep smoke tests small enough to run on every merge request.

Step 6: Configure CypressBridge for webmate

CypressBridge makes Cypress see webmate as a browser binary. Download the bridge for your operating system, then create webmate-cypressbridge.conf in your home directory or in .webmate/. You can also point to a specific config file with WEBMATE_CYPRESSBRIDGE_CONFIGPATH.

1
2
3
4
5
6
7
8
9
webmate:
  apikey: "${WEBMATE_APIKEY}"
  project: "${WEBMATE_PROJECTID}"
browser:
  browserName: "chrome"
  platformName: "WINDOWS_11_64"
  browserVersion: "latest"
sessionTimeout: "6m"
cypressVersion: "v15"

The exact configuration keys and supported browser versions are maintained in the existing Cypress documentation. Use the latest CypressBridge version documented there unless a project needs a pinned version for compatibility.

Start Cypress with the bridge as the browser:

1
npx cypress run --browser="/path/to/webmate-cypressbridge"

On Windows, some Cypress versions may need the browser type appended to the executable path:

1
cypress run --browser="C:\Users\tester\Downloads\webmate-cypressbridge-win.exe:chrome"

How This Maps to webmate

When the bridge starts, webmate selects or starts a desktop environment matching the requested browser and platform. Cypress still executes the spec, but browser traffic is tunneled through the bridge. The existing docs note that the bridge communicates with app.webmate.io over SSL on port 443 and uses WebSocket connections.

Use the bridge when you need desktop browser coverage on webmate infrastructure. Keep normal local Cypress execution for fast authoring. If a failure only appears through the bridge, compare browser version, platform, viewport, network access, and test data before changing selectors.

Debugging

Cypress failures usually fall into three groups. Selector failures mean the element is missing, hidden, renamed, or queried too early. Assertion failures mean the application reached a different state than expected. Infrastructure failures happen before the test reaches the page and often point to base URL, browser, proxy, or credentials.

CypressBridge writes logs to webmate-cypressbridge.log in the user’s home directory by default. Configure logfile and verbose in the bridge config when troubleshooting. In webmate, inspect the corresponding device workbench and session results to see what happened in the remote browser.

Troubleshooting

If cypress open works but cypress run fails, check that the application server is started in the same way CI starts it. If Cypress cannot launch the bridge, verify executable permissions and the path passed to --browser. If webmate cannot create a session, check the API key, project ID, browser name, platform name, and browser version.

If tests are flaky, remove fixed waits and wait for application-visible states instead. Cypress commands already retry many queries and assertions; explicit cy.wait(5000) usually means the test is missing a better signal.

Verify the Result

A working Cypress setup should run locally with npx cypress open, run headlessly with npx cypress run, and produce screenshots on failure. A working webmate setup should start Cypress through CypressBridge and create a desktop browser session that can be inspected in webmate.

Next Steps

Use the existing Cypress Testing page for the latest CypressBridge binary links, compatibility notes, proxy configuration, and network requirements. Use Testing with Local Services if the application under test is not publicly reachable.