Development

Shadow DOM Support for Cypress

4 min. read

Introduction

When it comes to end-to-end (E2E) testing, Cypress has taken the software testing world by storm. For years E2E testing has been overly complex and something many developers dreaded doing.

As Angular developers, we usually write all E2E tests using Protractor. It wasn't until a recent workshop, called Effective Automated Testing with Cypress.io by Joe Eames at NG-conf, that I really saw the power and simplicity of writing tests using Cypress. Check out my first blog about getting started using Cypress.

It turns out, the project I was working on at the time was an Ionic/Angular application using Ionic Framework v4. This version of Ionic Framework was written using Stencil and made heavy use of Web Components and Shadow DOM.

At the time of the workshop Cypress didn't support Shadow DOM.

I found an open GitHub issue from 2016 regarding support for Shadow DOM. It seems the issue was on the back burner for awhile but that is no longer the case.

TLDR: Cypress now supports Shadow DOM using an experimental flag.

Low and behold, Cypress now supports Shadow DOM using an experimental flag that you can enable in your configuration file.

Update: Cypress now fully supports Shadow DOM with the release of version 5.2.

Getting Started

Create a Sample Ionic Application

To get started, create a quick Ionic application to test out the Shadow DOM support.

For this example, we'll use one of Ionic's starter templates. Note: If you don't have the Ionic CLI installed, you will want to do this first.

npm install -g ionic@latest

Next, we create an Ionic application using the starter template called sidemenu.

ionic start cypress-testing sidemenu

Install Cypress

Now that we have our Ionic test application setup, we need to install Cypress.

npm install cypress	--save-dev

Create Test Case

First, let's create a test case to show what happens when you do not have the experimental Shadow DOM support enabled.

Here is what our test will look like. Open the Ionic application and try to click the hamburger icon to open the side menu.

// ./cypress/integration/side-menu.spec.js

context('SideMenu Testing', () => {
    beforeEach(() => {
        // Emulate an iPhone
        cy.viewport('iphone-6+')

        // Open the application
        cy.visit('http://localhost:8100')
    })

    it('should open side menu and click menu option Favorites', () => {
        // Open the side menu
        cy.get('ion-menu-button')
            .find('button')
            .click();

        // Count number of items in the side menu
        cy.get('#inbox-list ion-item')
            .as('inbox-list')
            .should('have.length', '6')

        // Click the Favorites menu option
        cy.get('@inbox-list')
            .eq('2')
            .click();
    })
})

Run Test

Now that we have our test created, run it, and see if it passes. I prefer to open separate terminals and run the following commands.

// Start Ionic
ionic s

// Start Cypress
npx cypress open

Failing Test

As you can see, after running our test, we get a time-out error since we can't pierce the Shadow DOM root and locate the button we need to click.

Failing Test

Enable Shadow DOM Support

Enabling the experimental flag to allow for Shadow DOM support is easy enough. We edit our cypress.json file and add the following experimental flag.

Update: Cypress now fully supports Shadow DOM with the release of version 5.2, you no longer have to enable the experimental flag when using a newer version.
{
  "experimentalShadowDomSupport": true
}

Adjust Our Test

To make our tests pass, add .shadow() (Line 10) to our test before we try to locate the menu button we want to click.

// ./cypress/integration/side-menu.spec.js

context('SideMenu Testing', () => {
    beforeEach(() => {
        // Emulate an iPhone
        cy.viewport('iphone-6+')

        // Open the application
        cy.visit('http://localhost:8100')
    })

    it('should open side menu and click menu option Favorites', () => {
        // Open the side menu
        cy.get('ion-menu-button')
            .shadow()
            .find('button')
            .click();

        // Count number of items in the side menu
        cy.get('#inbox-list ion-item')
            .as('inbox-list')
            .should('have.length', '6')

        // Click the Favorites menu option
        cy.get('@inbox-list')
            .eq('2')
            .click();
    })
})

Passing Test

As you can see, our test ran and passed successfully. The test was able to find the side menu hamburger icon, click it, open the side menu, and click the favorites menu item.

Passing Test

Conclusion 🧡

I'm super excited to start using Cypress on all my Ionic projects now that Shadow DOM support is possible. While it's experimental, it looks like a step in the right direction. Web Components and Shadow DOM aren't going anywhere. Once support for piercing the shadow-root becomes easier it should make everyone's lives easier.

https://github.com/Tevpro/cypress-testing

If you like our content and want more tutorials, sign up for our monthly newsletter below. Or feel free to reach out to us on Twitter or via email with any questions.

Photo by Ray Hennessy

Justin Waldrip

Angular • React • Ionic • NestJS • C# • Azure • Next.js