Test Plain Or Markdown File Using Cypress

How to request a text file and validate the response using Cypress.

Let's imagine a web server that also returns a plain text file. We are already testing the web pages using Cypress test runner, so how can we request and validate the plain text file? What if the returned file is in Markdown format? Let's learn.

🎁 You can find the source code for the application and the Cypress tests in the repo bahmutov/check-text-file-example.

Requesting the file

To download the file we can use the cy.request command.

spec.js
1
2
3
4
5
6
/// <reference types="cypress" />

it('receives the right text file', () => {
// request the text file using "baseUrl + /text-file" endpoint
cy.request('/text-file')
})

There is no page in the browser, but we can still see the server's response by clicking on the "REQUEST" command. The response is dumped in the DevTools Console.

File response

We can save the response as a file using the cy.writeFile command.

1
2
3
4
5
6
7
8
9
it('receives the right text file', () => {
// request the text file using "baseUrl + /text-file" endpoint
cy.request('/text-file')
.its('body')
.then((text) => {
// cy.writeFile automatically creates the output folder
cy.writeFile('output/file.txt', text)
})
})

Write file to disk

Tip: store the downloaded file as a test artifact on your CI to be able to debug the testing step.

Compare the file text

We can compare the response text with the expected string. We can put the expected string inline into the spec file, or store it as a file in the repository, which we can read using the cy.readFile command.

1
2
3
4
5
6
7
8
9
10
11
12
13
it('receives the right text file', () => {
// request the text file using "baseUrl + /text-file" endpoint
cy.request('/text-file')
.its('body')
.then((text) => {
// cy.writeFile automatically creates the output folder
cy.writeFile('output/file.txt', text)

cy.readFile('expected/file.txt').then((expectedText) => {
expect(text).to.equal(expectedText)
})
})
})

Compare the result to the file from the disk

You can also check the file text for specific string or against a regular expression.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
it('receives the right text file', () => {
// request the text file using "baseUrl + /text-file" endpoint
cy.request('/text-file')
.its('body')
.then((text) => {
// cy.writeFile automatically creates the output folder
cy.writeFile('output/file.txt', text)

cy.readFile('expected/file.txt').then((expectedText) => {
expect(text).to.equal(expectedText)
})

expect(text).to.include('This is a file')
expect(text).to.match(/a plain/i)
})
})

Checking the text using assertions

Tip: Cypress includes a large number of assertions thanks to the bundled Chai library.

Show the file

One thing we are not using during this test is the application iframe itself - it is empty because we are not visiting the site. We cannot use the cy.visit command because the returned content type is NOT text/html. Thus we need to write the contents into the document ourselves.

1
2
3
4
5
6
7
it('visits the text file', () => {
cy.request('/text-file')
.its('body')
.then((text) => {
cy.document().invoke('write', text)
})
})

Tip: you can clear the application iframe before writing the new content by visiting the blank page, see the blog post Cypress Tips and Tricks.

The plain text written into the application iframe

The text does not look right - because it does not show the newlines correctly. Let's wrap the text in a <pre> element.

1
2
3
4
5
6
7
it('visits the text file', () => {
cy.request('/text-file')
.its('body')
.then((text) => {
cy.document().invoke('write', '<pre>' + text + '</pre>')
})
})

Preformatted text

Much better.

Now we can use the other Cypress commands like cy.contains to assert the text.

1
2
3
4
5
6
7
8
it('visits the text file', () => {
cy.request('/text-file')
.its('body')
.then((text) => {
cy.document().invoke('write', '<pre>' + text + '</pre>')
})
cy.contains('This is a file')
})

Using the cy.contains command

Markdown files

What if we receive a plain text Markdown file? We can render it using the <pre> element.

1
2
3
4
5
6
7
8
9
10
it('visits the Markdown file', () => {
cy.request('/markdown-file')
.its('body')
.then((text) => {
cy.document().invoke('write', '<pre>' + text + '</pre>')
})
cy.contains('- one')
cy.contains('- two')
cy.contains('- three')
})

Checking the contents of the Markdown file

Markdown file has structure. We can convert Markdown into HTML using a 3rd party library instead of using the <pre> element when writing the document. Then the test could use the CSS selectors to query the contents better.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const mdToHtml = require('nano-markdown')
it('converts the Markdown file', () => {
cy.request('/markdown-file')
.its('body')
.then(mdToHtml)
.then((html) => {
cy.document().invoke('write', html)
})
cy.contains('h1', 'Example Topic')
cy.get('li')
.should('have.length', 3)
.and(($list) => {
expect($list[0]).to.contain('one')
expect($list[1]).to.contain('two')
expect($list[2]).to.contain('three')
})
})

The HTML document from the converted Markdown text certainly looks nice.

Markdown file converted to HTML and shown in the app iframe

Running all tests

When running all tests together, the document.write(...) command keeps appending to the same document.

Output of running all tests in the spec file

You can clear the document before writing HTML using the document.open() method

1
2
3
4
.then((html) => {
cy.document().invoke('open')
cy.document().invoke('write', html)
})

We can open the document before each test

1
2
3
beforeEach(() => {
cy.document().invoke('open')
})

Alternatively, we could visit the blank page to clear the document before each test. See this tip in the blog post Visit The Blank Page Between Cypress Tests.