[Go Make Things] Testing custom events with Playwright

I'm about to dive into building all of the Web Components for Kelp, my UI library for people who love HTML.

Today, I wanted to share how to test custom events in Playwright, because it's not quite as straightforward as you'd expect.

Let's dig in!

Embracing Playwright & TDD

As part of this project, I really want to embrace test-driven development (TDD) now that I've had my lightbulb moment about how it works…

Only test what a component does, not how it works.

While I love buildless testing for its simplicity, I've decided to use Playwright for Kelp for a few reasons…

  • Automation. When I create or receive PRs for features, I don't want to have to manually test every component to make sure there's no unintended interactions or side-effects. Being able to run one line of code and test everything is super useful.
  • Continuous Integration. I can tie it into a Continuous Integration (CI) process, and have tests automatically run when code is pushed or a PR is opened.
  • Real Browsers. Playwright doesn't emulate browsers. It runs tests in actual browser instances. That means I don't have to mess around with mocking up browser-native functions for a JS environment. A fetch() call uses the real fetch() method.
  • Built for UI. While Playwright is often thought of as an end-to-end testing tool, it's great for any UI-based testing. It runs code in a browser and runs tests against it. That's perfect for the kind of testing I want to do.

Testing custom events

Kelp's web components emit various custom events custom events developers can hook into to extend their behavior, and I want to make sure those work as expected.

document.addEventListener('kelp-accordion-ready', () => {  	console.log('The accordion component is ready!');  });  

Playwright includes a few helpful methods for listening to events…

  • Page.on() for listening to events
  • Page.once() for listening to an event just once
  • Page.waitForEvent() for waiting for an event before continuing

As I learned the hard way, though, the events it listens for are a subset of predefined events. These are not general-purpose methods for listening to any event you want.

After a lot of web searches and trial and error, I figured out how to test this.

const component = page.getByTestId('accordion-1');  const readyEvent = await component.evaluate((element) => {  		return new Promise((resolve) => {  			return element.addEventListener('kelp-accordion-ready', () => {  				return resolve(true);  			});  		})  	});  await expect(readyEvent).toBeTruthy();  

Let's break down what's happening here…

  1. I'm getting the web component to listen to events on.
  2. I'm using the locator.evaluate() function to run some JS and create a new Promise().
  3. That promise creates an event listener, and resolves as true when the event fires.
  4. I can then expect() the promise, and make sure it's truthy.

I'm surprised this isn't a baked in feature, TBH!

Test utilities

I don't want to have to write this out every time I test a component, so I created a test-utilities.js file to make testing a bit easier.

In it, I created an asynchronous waitForCustomEvent() function that I export. It accepts the component to test and the eventID to listen for, and returns the resolved promise back out.

/**   * Wait for a custom event to run   * @param  {Locator} component The component to listen for the event on   * @param  {String}  eventName The event name to listen for   * @return {Promise}           Resolves to true if event emits   */  export async function waitForCustomEvent(component, eventID) {  	return await component.evaluate((element, eventID) => {  		return new Promise((resolve) => {  			return element.addEventListener(eventID, () => {  				return resolve(true);  			});  		});  	}, eventID);  }  

In my test spec, I can do this…

const readyEvent = await waitForCustomEvent(page.getByTestId('accordion-1'), 'kelp-accordion-ready');  expect(readyEvent).toBeTruthy();  

This is useful because I can also test other custom events with the same method/approach.

I also created another method to automate testing that the entire set of "this web component is ready" behaviors works…

/**   * Validate component setup was completed   * @param  {Locator} component The component to listen for the event on   */  export async function expectComponentReadyState(component, componentID) {  	const readyEvent = await waitForCustomEvent(component, `kelp-${componentID}-ready`);  	await expect(readyEvent).toBeTruthy();  	await expect(component).toHaveAttribute('is-ready');  }  

This listens for the event using the waitForCustomEvent() method, and also checks that an [is-ready] attribute has been added to component.

Testing doesn't have to be hard

Testing often feels really hard, and I think that's related to…

  1. Trying to fit tests into fuzzy and overlapping categories like "unit," "integration," and "end-to-end."
  2. Testing implementation details.
  3. Over-fixating on code coverage.

Testing gets a lot easier when you instead focus on writing tests that fail for good reasons and only test the stuff that someone using the code interacts with.

Like this? A Go Make Things membership is the best way to support my work and help me create more free content.

Cheers,
Chris

Want to share this with others or read it later? View it in a browser.

Share :

Facebook Twitter Google+ Lintasme

Related Post:

0 Komentar untuk "[Go Make Things] Testing custom events with Playwright"

Back To Top