VuePress and some Cypress end-to-end testing tips

Using DOM snapshot to catch a disappearing element plus sharing application config.

Recently VuePress static site generator came out. It is powered by Vue.js under the hood, and I must say - I like it. Everything just works - my Markdown is turned into a pretty and fast static site with almost zero configuration. Even better, the built "production" site scored really well in Chrome DevTools performance audit.

We can configure output title and description meta information. For example, here is the src/.vuepress/config.js file that defines the options I want.

1
2
3
4
5
6
7
8
// see for all options https://vuepress.vuejs.org/config/
module.exports = {
title: 'Try Vuepress',
description: 'Just playing around',
dest: 'dist',
base: '/try-vuepress/',
evergreen: true
}

Here are the only two Markdown pages in the site - both in src folder

1
2
3
4
5
6
7
8
9
<!-- src/README.md -->
# Hello VuePress

This is an example page. There is also [about](./about.html) page.

<!-- src/about.md -->
# About

This is the "about" page. It is important.

Let us make sure the generated site is working in dev mode. I will install Cypress and will add a few tests.

1
npm install -D cypress
package.json
1
2
3
4
5
6
7
{
"scripts": {
"dev": "vuepress dev src",
"build": "vuepress build src",
"cy:open": "cypress open"
}
}

The local URL goes into cypress.json

cypress.json
1
2
3
{
"baseUrl": "http://localhost:8080/try-vuepress/"
}

Our first test is simple - it just makes sure the dev site is loading.

1
2
3
4
5
6
7
8
9
/// <reference types="cypress" />
describe('VuePress site', () => {
beforeEach(() => {
cy.visit('/')
})
it('loads', () => {
cy.contains('Hello VuePress').should('be.visible')
})
})

Start the server, open Cypress and ... the test fails! Here is the message and the element it finds by content "Hello VuePress".

Failed test

Turns out there are links made automatically by VuePress that use h1 page titles as content. We can see these links by changing their display property from the Elements tab.

Sidebar links

This happens sometimes in testing: if you search by text content or a common class name, you will find a wrong element. We can always use test ids or just make our selector a little more specific. Adding h1 element solves the problem.

1
2
3
it('loads', () => {
cy.contains('h1', 'Hello VuePress').should('be.visible')
})

We can test going to the "About" page and back

1
2
3
4
5
6
it('goes to About page and back', () => {
cy.contains('.next', 'About').click()
cy.url().should('contain', 'about.html')
cy.go('back')
cy.url().should('equal', Cypress.config('baseUrl') + '/')
})

Let's also test the text search VuePress provides for us. First, we will type our text into the search box. It is easy to find the search box selector using the Elements panel.

1
2
3
it('finds the about page', () => {
cy.get('.search-box input').type('about')
})

Hmm, we see the list of found results (with a single "About" page), but as soon as we go to inspect it, the list disappears! Luckily, Cypress keeps DOM snapshots for each command. We can click on the type('about') command and pin the after snapshot. Now we can take a look at the DOM node in the Elements tab without "scaring it away".

After command snapshot

We can write the search results assertions in several ways. We want to make sure the list of results has at least 1 found item, and that the first result has the page title "About".

1
2
3
4
5
6
7
8
9
10
it('finds the about page', () => {
cy.get('.search-box input').type('about')
cy.get('.suggestions').find('li')
.should('be.visible')
.and('have.length.gte', 1)
.first()
.contains('.page-title', 'About')
.click()
cy.title().should('contain', 'About')
})

The alternating "query / assertion" pattern shown above is the best way to make sure Cypress successfully goes step by step. The test runner retries the "query" step until the "assertion" passes. Thus by explicitly looking for visible suggestions to have a few items we make sure the application really behaves the way we think it should behave during the test.

Search test finishes in About page

Hmm, our cy.title() assertion only compared part of the full title. The full title was "Try Vuepress | About" which is the concatenation of the blog's and the page's titles. But the blog's title is specified in the src/.vuepress/config.js. Do we need to duplicate the blog title and put it into the spec file? No, we can import any JavaScript directly from our test file, thus reusing application's configuration.

cypress/integration/spec.js
1
2
3
4
5
6
7
8
9
/// <reference types="cypress" />
import {title} from '../../src/.vuepress/config'
describe('VuePress site', () => {
it('finds the about page', () => {
// same test code
// ...
cy.title().should('equal', `${title} | About`)
})
})

Our assertion is a lot stricter now!