Visual Testing
What you'll learn
- How visual testing complements functional testing
- How to implement visual diffing yourself or using 3rd party service
- How to ensure the application is in consistent state before capturing an image
Functional vs visual testing
Cypress is a functional Test Runner. It drives the web application the way a
user would, and checks if the app functions as expected: if the expected
message appears, an element is removed, or a CSS class is added after the
appropriate user action. A typical Cypress test, for example, can check if a
toggled "Todo" item gets a class of "completed" after the .toggle
is checked:
it('completes todo', () => {
// opens TodoMVC running at "baseUrl"
cy.visit('/')
cy.get('.new-todo').type('write tests{enter}')
cy.contains('.todo-list li', 'write tests').find('.toggle').check()
cy.contains('.todo-list li', 'write tests').should('have.class', 'completed')
})
Cypress does NOT see how the page actually looks though. For example, Cypress
will not see if the CSS class completed
grays out the label element and adds a
strike-through line.
You could technically write a functional test asserting the CSS properties using
the have.css
assertion, but these may
quickly become cumbersome to write and maintain, especially when visual styles
rely on a lot of CSS styles.
cy.get('.completed').should('have.css', 'text-decoration', 'line-through')
cy.get('.completed').should('have.css', 'color', 'rgb(217,217,217)')
Your visual styles may also rely on more than CSS, perhaps you want to ensure an SVG or image has rendered correctly or shapes were correctly drawn to a canvas.
Luckily, Cypress gives a stable platform for writing plugins that can perform visual testing.
Typically such plugins take an image snapshot of the entire application under test or a specific element, and then compare the image to a previously approved baseline image. If the images are the same (within a set pixel tolerance), it is determined that the web application looks the same to the user. If there are differences, then there has been some change to the DOM layout, fonts, colors or other visual properties that needs to be investigated.
For example, one can use the cypress-plugin-snapshots plugin and catch the following visual regression:
.todo-list li.completed label {
color: #d9d9d9;
/* removed the line-through */
}
it('completes todo', () => {
cy.visit('/')
cy.get('.new-todo').type('write tests{enter}')
cy.contains('.todo-list li', 'write tests').find('.toggle').check()
cy.contains('.todo-list li', 'write tests').should('have.class', 'completed')
// run 'npm i cypress-plugin-snapshots -S'
// capture the element screenshot and
// compare to the baseline image
cy.get('.todoapp').toMatchImageSnapshot({
imageConfig: {
threshold: 0.001,
},
})
})
This open source plugin compares the baseline and the current images side by side within the Cypress Test Runner if pixel difference is above the threshold; notice how the baseline image (Expected result) has the label text with the line through, while the new image (Actual result) does not have it.
Like most image comparison tools, the plugin also shows a difference view on mouse hover:
Tooling
There are several published, open source plugins, listed in the Visual Testing plugins section, and several commercial companies have developed visual testing solutions on top of the Cypress Test Runner listed below.
Open source
Listed in the Visual Testing plugins section.
Applitools
First joint webinar with Applitools
Second joint webinar with Applitools with a focus on Component Testing
https://applitools.comResource | Description |
---|---|
Official docs | Applitools' Cypress documentation |
Tutorial | Applitools' Cypress tutorial |
Webinar | Creating a Flawless User Experience, End-to-End, Functional to Visual – Practical Hands-on Session, a webinar recorded together with Cypress and Applitools |
Blog | Testing a chart with Cypress and Applitools |
Percyhttps://percy.io
New
Real World Example The Cypress
Real World App (RWA) uses
the cy.percySnapshot()
command provided by the
Cypress Percy plugin to take visual
snapshots throughout the user journey end-to-end tests
Check out the Real World App test suites to see these Percy and Cypress in action.
Resource | Description |
---|---|
Official docs | Percy's Cypress documentation |
Tutorial | Percy's Cypress tutorial |
Webinar | Cypress + Percy = End-to-end functional and visual testing for the web, a webinar recorded together with Cypress and Percy.io |
Blog | The companion blog for the Cypress + Percy webinar |
Slides | The companion slides for the Cypress + Percy webinar |
Blog | Testing how an application renders a drawing with Cypress and Percy |
Happohttps://happo.io/
Resource | Description |
---|---|
Official docs | Happo's Cypress documentation |
Webinar | Keep your UI Sharp: Ensuring Functional and Visual Quality with Cypress.io + Happo.io, a webinar recorded together with Cypress and Happo |
Blog | The companion blog for the Cypress + Happo webinar |
Slides | The companion slides for the Cypress + Happo webinar |
Do It Yourself
Even if you decide to skip using a 3rd party image storage and comparison service, you can still perform visual testing. Follow these examples
- Visual Regression testing with Cypress and cypress-image-snapshot tutorial.
- Visual testing for React components using open source tools with companion videos.
You will want to consider the development costs of implementing a visual testing tool yourself versus using an external 3rd party provider. Storing, reviewing and analyzing image differences are non-trivial tasks and they can quickly become a chore when going with a DIY solution.
Best practices
As a general rule there are some best practices when visual testing.
Recognize the need for visual testing
assertions that verify style properties
cy.get('.completed').should('have.css', 'text-decoration', 'line-through')
.and('have.css', 'color', 'rgb(217,217,217)')
cy.get('.user-info').should('have.css', 'display', 'none')
...
If your end-to-end tests become full of assertions checking visibility, color and other style properties, it might be time to start using visual diffing to verify the page appearance.
DOM state
For example, if the snapshot command is cy.mySnapshotCommand
:
Incorrect Usage
// the web application takes time to add the new item,
// sometimes it takes the snapshot BEFORE the new item appears
cy.get('.new-todo').type('write tests{enter}')
cy.mySnapshotCommand()
Correct Usage
// use a functional assertion to ensure
// the web application has re-rendered the page
cy.get('.new-todo').type('write tests{enter}')
cy.contains('.todo-list li', 'write tests')
// great, the new item is displayed,
// now we can take the snapshot
cy.mySnapshotCommand()
Timestamps
Below we freeze the operating system's time to Jan 1, 2018
using
cy.clock() to ensure all images displaying dates and
times match.
const now = new Date(2018, 1, 1)
cy.clock(now)
// ... test
cy.mySnapshotCommand()
Application state
Below we stub network calls using cy.intercept() to return the same response data for each XHR request. This ensures that the data displayed in our application images does not change.
cy.intercept('/api/items', { fixture: 'items' }).as('getItems')
// ... action
cy.wait('@getItems')
cy.mySnapshotCommand()
Visual diff elements
Targeting specific DOM element will help avoid visual changes from component "X" breaking tests in other unrelated components.
Component testing
If you are working on React components, read Visual testing for React components using open source tools, browse slides, and watch the companion videos.
See also
- After Screenshot API
- cy.screenshot()
- Cypress.Screenshot
- Plugins
- Visual Testing Plugins
- Writing a Plugin
- Cypress Real World App (RWA) is a full stack example application that demonstrates best practices and scalable strategies with Cypress in practical and realistic scenarios .
- Read the blog post Debug a Flaky Visual Regression Test
- Read the blog post Canvas Visual Testing with Retries