Back to Home

Setting Up Cypress for Svelte's Sapper

A few weeks ago, I decided to try Svelte's Sapper framework to handle the front-end of a simple app. I'm using this small project to try out some new technologies and to practice TDD/BDD. I had already built a back-end API in Go so just needed to build and wire-up a UI. I don't have a lot of experience in modern front-end tech or end-to-end testing, so I was excited to see that Sapper comes ready-to-use with the Cypress test framework. I was able to write some simple BDD(ish) tests to define the functionality I wanted and then implement in Svelte fairly seamlessly. Overall this was a natural and fluid flow, after overcoming a small gotcha.

Cypress Tests Stall After Visiting New Pages

Most of my tests started with a cy.visit(url) to ensure a fresh page reload, but I quickly found that any click or DOM action within my Svelte components timed-out and failed sporadically, but frequently:
CypressError: Timed out retrying: Expected to find [...]

I believe this was due to the fact that Sapper does an excellent job of preloading and chunking out the app code and data asynchronously, so Cypress identified that the page was completely loaded and continued with the test script before all of the Sapper app chunks had actually been fully loaded.

Initially, I solved this with a hacky cy.visit(url).wait(1000) that would wait for 1 second after the page loaded. Using wait() for something like this is pretty horrible practice, but it allowed me to continue development. But anyone who has worked with UI or E2E testing knows that one of the biggest issues is the time it takes to run them. Even with my little app only partially completed, testing time quickly ballooned out to over a minute to run all the tests due to the full second of wait time.

Solution

My final solution uses Svelte's lifecycle functions to set a DOM attribute when all components are available. Use the onMount() function in the top-level routes/_layout.svelte component, wait an additional tick() to ensure all child components had been loaded, then set an attribute in the DOM that Cypress can easily check:


// File: src/routes/_layout.svelte

<script>
	import Nav from '../components/Nav.svelte';
	import { onMount, tick } from 'svelte';

	export let segment;

	let testID = 'loading';
	onMount(() => {
		tick().then(() => {
			testID = 'loaded';
		});
	});
</script>

<style>
  ...
</style>

<Nav {segment}/>

<main data-test={testID}>
	<slot></slot>
</main>

In Cypress create a simple command called visitWait or similar that waits for the correct attribute value:


// File: cypress/support/commands.js

Cypress.Commands.add("visitWait", (url, options) => {
  cy.visit(url, options);
  cy.get('[data-test=loaded]');
});

In your tests, simply use cy.visitWait(url) as you would normally use cy.visit(url) and Cypress will wait just long enough for all the Svelte components to load before continuing with your test script.

Related Posts

The essential design concepts I use when developing an evolvable, distributed system.

Read More

How can we continuously integrate small changes while practicing acceptance test-driven development?

Read More

TDD and Testing Behavior

January 24, 2024

The importance of testing behavior when using test-driven development

Read More

When is it appropriate to use centralized orchestration versus event-driven choreography?

Read More

When defining a business problem and planning its solution, keep the two conversations separate...

Read More

Modern message brokers provide many important benefits to a distributed system...

Read More

Printable cheat sheets to help remember some of Uncle Bob's valuable contributions to the industry

Read More

Why Terraform?

December 25, 2019

Terraform leads the way in the infrastructure-as-code world...

Read More

I was looking for a quick and easy way to put together a personal static site and...

Read More

After years of consulting, I find myself continually coming back to three basic principles of system design...

Read More

In this fifth and final part of the Go middleware tutorial series, we'll use what we've learned to create a more structured API example...

Read More

Go Middleware - Part 4

February 24, 2019

In this fourth part of the Go middleware tutorial series, we'll discuss passing custom state along the request chain.

Read More

Go Middleware - Part 3

February 15, 2019

In this third part of the Go middleware tutorial series, we'll quickly look at a common variant on the recursive middleware implementation from part 2.

Read More

Go Middleware - Part 2

February 9, 2019

In this second part of the Go middleware tutorial series, we'll cover a recursive approach that provides a couple benefits beyond the simple loop chain example from part 1.

Read More

Go Middleware - Part 1

February 6, 2019

This is the first in a series of simple tutorials explaining the usage of HTTP middleware in Go.

Read More

How do we manage the architectural complexity that inevitably arises from using cloud services?

Read More

This Old Blog

January 20, 2019

I've decided to resurrect this old blog to publish some nuggets about software architecture and development, and perhaps...

Read More

Drupal 6 Theme Info Error

September 14, 2011

Recently one of my client sites had an issue where the custom theme info was corrupted...

Read More

Here's a slight modification to the handy Google Bookmarks Bookmarklet...

Read More

While building a Drupal site for one of my clients, I was having a heck of a time integrating...

Read More