DEV Community

Simon
Simon

Posted on • Originally published at blog.simonireilly.com on

Cypress component tests for Lit Elements (web components)

Recently I have been using lit, a web component framework.

If you are not familiar with web components here is a quick summary:

Web Components is a suite of different technologies allowing you to create reusable custom elements - with their functionality encapsulated away from the rest of your code - and utilize them in your web apps.

MDN - https://developer.mozilla.org/en-US/docs/Web/Web_Components

Lit is a popular framework for these components with over 1 million weekly downloads. (Oct 10th 2022).

Testing Lit

For how to test Lit Elements I had been following some big notable projects,

Spectrum web components by Adobe is really mature design system that makes a lot of usage of Lit Elements. Their testing setup uses the suggested web test runner. Lit's documentation on testing suggests using that library.

Cypress has become one of the most popular test runners in the space. So I was a little surprised to not see some guidance for it on the lit documentation. I think it's an ideal candidate for Web Component testing on modern browsers, and because it recently added support for safari it has great coverage across chromium, webkit, electron and quantum (firefox) based browsers.

Testing Lit Components with Cypress

To test with lit elements you will need a project with cypress.

TL;DR

An example project: https://github.com/simonireilly/cypress-lit

The testing setup consists of writing a cy.mount command for mounting Lit Elements into the DOM.

import { getContainerEl } from "@cypress/mount-utils";
import { LitElement, render, TemplateResult } from "lit";

export const mount = (_element: LitElement, template: TemplateResult) => {
  const target = getContainerEl();

  render(template, target);

  return cy
    .wait(0, { log: false })
    .then(() => {
      const mountMessage = `
        <${String(_element.constructor.name)} ... />
      `;

      Cypress.log({
        name: "mount",
        message: [mountMessage],
      })
        .snapshot("mounted")
        .end();
    })
    .get("[data-cy-root]")
    .children()
    .first()
    .shadow();
};
Enter fullscreen mode Exit fullscreen mode

And this means you can write test like this:

import { html } from "lit";
import { MyElement } from "../../src/my-element";

describe("my-element.cy.ts", () => {
  it("playground", () => {
    cy.mount(new MyElement(), html`<my-element></my-element>`).as("element");

    cy.get("@element").contains("count").click();
  });
});
Enter fullscreen mode Exit fullscreen mode

And just like that, you can run a cypress test for the component. Here is a quick snapshot of me testing one.

lit element tested with cypress running electron

Wrap up

That is a quick example of how to run component tests for Lit with cypress. Hopefully it's just enough information for you to get it setup.

Since cypress components supports so many frameworks already, I don't think it will be long before an official lit component implementation lands. In the meantime, happy testing!

Top comments (2)

Collapse
 
augustjkim profile image
Augustine Kim

Very nice! This is awesome to see!!

I think this doesn't necessarily need to be Lit specific since really all web components can be tested the same way, similar to how the open-wc test fixtures work open-wc.org/docs/testing/helpers/#...

Perhaps the mount command can just take either a string or a Lit template result. Passing in the component instance feels not necessary. If we're assuming users will pass in a single custom element, we can grab the first child of the container and get its .tagName property for the logging.

I need to look more into Cypress APIs on how it works with promises but if we're working with Lit elements, awaiting on the component's updateComplete promise could be enough to ensure the component is mounted, or perhaps waiting on a requestAnimationFrame, instead of the cy.wait(0).

Also, last nit would be that I'd probably like the mount command to return the custom element and not its shadow root by default. Users might want to test against the component's attributes or properties, and probably would intentionally dive into its shadow DOM.

Collapse
 
simonireilly profile image
Simon

Really appreciate your comments on this one. Would you consider adding them here as I've opened a PR for cypress

github.com/cypress-io/cypress/pull...

I found that without passing the Element through to the Mount helper there was no instance on the DOM (customElementRegistry). There is likely a work around, I agree that supporting all web components would be the best global conclusion.

Yes to the tagName point, much preferred 👍

Your point on the promises to await is really neat, and not something I've considered. Would be really cool if you can contribute this to the PR, or provide feedback if I try and implement it.

Final point I also agree with. It seems a little opaque to return the shadow root.

Summary; really appreciate the feedback 👍