Cypress is a testing framework. It takes a drastically different approach than Selenium based test frameworks like WebDriver. Unlike Selenium, which executes remote commands through network requests, Cypress runs a Node.js server process that runs in the same event loop as the application. This means Cypress can access everything within the application code. The DOM, window object, classes, functions, etc. are all available through the test code.
Cypress runs synchronously with the application and creates a highly reliable and natural way to write and run tests. With Cypress there's no longer a need for statements to wait for certain elements, it's all built in to the framework. You can use debugger statements within your tests or code and jump into the DevTools as the test runs.
Testing practices generally follow the arrange/act/assert pattern. In Cypress, it is preferable to "arrange" by setting the desired state of the application with programmatic changes to the state rather than manually clicking through the UI to change elements and data. Shortcuts should be used to stub functions, expose and modify Redux stores, intercept and mutate response codes, or add environment variables so that code behaves differently within a Cypress test.
Tests should be independent and isolated from other parts of the application. Examples of this could be a spec specifically for the navigation bar and its contents. Pages such as a login or a landing page can be written in separate spec files. All permutations of errors/success states should be tested within a spec file for a given page. Tests should be implemented to ensure events work as expected, such as button clicks. It is also recommended to test that content on the page matches data fetched from another source.
Several approaches to setting up tests, each with their own benefits and drawbacks. Use what feels right for the test and weigh the costs of speed over accuracy.
- Stubs: API requests can be overridden by stubs in order to fake the server and DB to make tests fast. Use Fixtures to mock data responses. This is not truly E2E but may serve the needs to set up a test.
- Static Data: Create a static entry that exists in the DB. Use caution as this creates shared state between tests.
- Dynamic Data: Set up data in the test itself by making API calls. This approach is likely to be slow and grow the complexity of the test.
Make abstractions for common workflows and prevent writing test steps that overlap with things that can be tested in isolation. Use Support files to execute Cypress commands before tests are run. Through these support files, custom Cypress commands can be implemented and are available for all tests. Through these abstractions and optimizations, development cycles and deployments become much faster.
Logging in a user is a common task that many tests likely need before the tests can be executed. Abstract this logic in a way that directly invokes the request and sets the user token programmatically. A common mistake in Cypress is to use the UI to type in the login information and click a button manually. Cypress provides tools to create shortcuts around commands that must be used for every test. The 'cy.request' method can be used to directly invoke the async request that the form uses when the user clicks the log in button. The response can be intercepted and used to set the token on the window object directly and thus ends up being much faster.
Don't try to emulate the steps a user would take for each test case. Set up state programmatically, especially if specific actions to obtain the desired state is common across all tests. Recommend exposing Redux stores to the test and and dispatch actions directly.
Write like an integration test, not a unit test. It is encouraged to use multiple assertions per test. Each Cypress test resets state between tests and becomes slower with each additional test.
Assertions may not be explicit, they are inherently part of each command. Tests may not require an explicit assertion at all for this reason. Assertions also help prevent the need for commands that wait on elements.
If cleaning up is necessary (usually only for server side state), clean up before and not after test runs. This "dangling state" enables you to interactively debug the test from the point of failure.
For more tips, visit the Cypress Best Practices Guide
Use data-* attributes to look up elements instead of an element's id, class, or textContent. This protects your tests from changes in CSS, etc. Recommended approach is to add a data-cy attribute and rely on that in the selector.
Page objects should not be used between specs for different pages. This practice will decouple and isolate tests. If tests are written so that state is set up without using the UI, page objects become less useful for this purpose. There are still some page object abstractions that can be valuable when used as part of several similar tests. If the workflow is complicated, it may be best to place that logic within a page object to make the test more readable.
The Cypress team is focused on providing cross browser support within the framework. For a bit of background, events are usually simulated in Cypress. The Cypress team is redesigning so that Native events are used by default. This will also have the benefit of gaining support for drag and drop, tab, upload, etc. out of the box. These changes will be implemented so that the advantages of running in the browser are not lost.
For the most part, they have communicated that they have many of the events and APIs set up for IE, Firefox, Android Chrome and iOS Safari. Firefox can leverage the same kind of APIs as supported in Chrome. Safari on MacOS is the most challenging and not a lot can be done at this time to support it.
However, Cypress using IE 11 will selectively use WebDriver under the hood for some native events.