Self-testing JAM pages

Back to Cypress blog

JAM Stack has become a popular way to build websites - you can write your pages using Markdown (the M in JAM), build using JavaScript tools that fetch data from APIs and get mostly static sites that are very fast. In this blog post I will show how each Markdown page can also include its own end-to-end tests using the Cypress Test Runner.

Code: The source repo for this blog post is at bahmutov/self-testing-jam and the deployed site is available at https://self-testing-jam.netlify.com/. It is a JAM static site that uses a Vuepress site deployed on Netlify.

Recently I have talked about end-to-end testing at ReactiveConf 2019 in Prague, Czech Republic. It was an excellent conference, and the 25 minute video of my talk was posted online almost immediately. We wanted to distribute the talk widely because it covered several topics very relevant to the testing community: code coverage, linting source code, and writing faster tests. You can watch the video down below and find the slides at https://slides.com/bahmutov/state-of-the-art/

Watching an entire 25 minute video and flipping through the 100 slide deck seems like a big commitment. I have created aliases that immediately go to the specific slide of the presentation for each topic, and found the video timestamp when I begin talking about it.

Topic slide alias Video at
Types of linters #lint-pyramid 3m46s
Tests and plugins #only-tests 7m9s
Cover coverage #code-coverage 11m39s
Splitting long tests #test-length 20m5s

Whenever I need to send someone additional information about code coverage for an example, I can send urls to the slides https://slides.com/bahmutov/state-of-the-art/#/code-coverage and the video https://youtu.be/JL3QKQO80fs?t=699 which is great.

Self-testing the Markdown file

Publishing a separate documentation page with each talk thus should embed the talk video and a table of shortcuts, plus possible additional information. Here is the Markdown of the page.

ReactiveConf page with talk and individual links

The page might get complex, thus it is a good idea to write at least a simple Cypress end-to-end test. For me, the best test is the one that sits closely to the code it is testing. Placing a spec file next to the code file is great, I could place tests next to Markdown files like this.

self-testing-jam/
  README.md
  reactiveconf.md
  reactive-conf-spec.js
  cypress.json

By default Cypress expects spec files to be placed in the cypress/integration folder, so we need to set the integration folder to be . using cypress.json

{
  "integrationFolder": ".",
  "fixturesFolder": false,
  "supportFile": false
}

This is good, but we can go one step further. Cypress transpiles each spec file using cypress-browserify-preprocessor, which you can change to tweak options and even transpile other file types. Recently I have written a Cypress Markdown transpiler and placed it in the cypress-io/cypress-fiddle repository. To use this preprocessor, install it and add the following code to your plugins file:

// cypress/plugins/index.js
const mdPreprocessor = require('@cypress/fiddle/src/markdown-preprocessor')
module.exports = (on, config) => {
  on('file:preprocessor', mdPreprocessor)
}

The new preprocessor allows you to write end-to-end tests inside Markdown text files - just surround the test's JavaScript test block with special HTML comments like this:

Special "fiddle" comments tell Cypress Markdown preprocessor that there is a test inside

Great, we can write a test right inside the ReactiveConf video talk reactiveconf.md file. For example, we can confirm the video player is loading and the individual section links are present.

cy.visit('/reactiveconf.html')
// YouTube player is embedded
cy.contains('Cypress.io - the State of the Art End-to-end Testing Tool')
cy.get('[data-cy=talk]')
  .then($iframe => {
    // this ensures the frame loaded
    cy.wrap($iframe.contents()).should('have.length', 1)
    return cy.wrap($iframe.contents().find("body"))
  })
  .find('.html5-video-player').should('be.visible')
// main sections links
;['Lint pyramid', 'Tests and plugins', 'Code coverage'].forEach(section => {
  cy.contains('li', section).should('be.visible')
})

Tip: because the video player comes from 3rd party domain, you need to disable the browser security check using cypress.json like this:

{
  "baseUrl": "http://localhost:8080",
  "chromeWebSecurity": false,
  "viewportHeight": 1000
}

Hiding the test itself

To prevent the test itself from showing up and taking up the majority of the rendered page, we can place it inside <details> tag right inside Markdown. Here is a screenshot of the page that shows the page and the embedded test fiddle.

Markdown page includes end-to-end test inside "details" tag

Here is the final result running a local Cypress test against the built page.

Self-testing Markdown page

Tip: you can completely hide the test by setting the test's tag to display:none like this

<details style="display:none">
<summary>Cypress test for the current page</summary>
<!-- fiddle Talk and contents list -->
    ...
</details>