📜 ⬆️ ⬇️

Testing components with Puppeteer and Jest


On Habré there is a publication describing writing tests using Puppeteer and Jest . I recommend to familiarize, if you still do not know what Puppeteer is. In this article, on the example of the React-component, the method of testing calls of callback functions will be described. For example, there is a component with props onChange , and it is necessary to test that for some user actions a callback function will be called with the expected parameters passed. For this, the Puppeteer-io library will be used. But first, consider a small example on html and pure javascript without binding to libraries or frameworks ...

Suppose there is a function addEvent , which hangs an event handler on the elements by a selector. You need to write a test that will ensure that the handler is invoked on an event. Create the index.html file:

 <button> </button> <script> function addEvent(selector, eventType, handler) { let elements = document.querySelectorAll(selector); Array.prototype.forEach.call(elements, element => { element.addEventListener(eventType, handler, false); }); } addEvent("button", "click", event => console.log("Button.click")); </script> 

This is the page on which the testing takes place. It has a button on which the click event handler is hung. For this event, the handler calls console.log , passing an action identifier string, the receipt of which means that the test was successful. Now create the index.html.test.js file, which will contain the test code for the Jest:

 const puppeteer = require("puppeteer"); const io = require("puppeteer-io"); test(`addEvent()    `, async () => { let browser = await puppeteer.launch(); let page = await browser.newPage(); await page.goto(`file://${__dirname}/test.html`); await io({ page, async input() { await page.click("button"); }, async output({ message }) { await message("Button.click"); } }); await page.close(); await browser.close(); }); 

Now more about Puppeteer-io. This library accepts two asynchronous functions that run in parallel. The input() function controls the browser, for example, clicks on elements or simulating keyboard input, while in output(api) , data from the browser is obtained and processed. In this case, the message function is used, to which the string identifier of the expected message is passed. If console.log with such an identifier is not called in the browser, the test will hang and the Jest will consider it failed.
')

Testing React Component


As an example, this component will be used:

 import React from "react"; import PropTypes from "prop-types"; const iItem = PropTypes.shape({ id: PropTypes.string.isRequired, text: PropTypes.string.isRequired }); export default class Select extends React.Component { static propTypes = { items: PropTypes.arrayOf(iItem).isRequired, onChange: PropTypes.func }; getChangeHandler() { return ({ target }) => { if (this.props.onChange) { this.props.onChange(this.props.items[target.selectedIndex].id); } }; } toOption(item, index) { return <option key={`id_${index}_${item.id}`}> {item.text} </option> } render() { return <select onChange={this.getChangeHandler()}> {this.props.items.map(this.toOption)} </select> } } 

This is just a wrapper over the standard select th. When any item is selected in it, a callback function is called, to which the id of the selected item is passed. Actually, we will test this functionality. To do this, create a special page for testing, which Puppeteer will open in the browser:

 import React from "react"; import ReactDOM from "react-dom"; import Select from "path-to/select-component.js"; const testItems = [ { id: "0e210d4a-ccfd-4733-a179-8b51bda1a7a5", text: "text 1"}, { id: "ea2cecbd-206c-4118-a1c9-8d88474e5a87", text: "text 2"}, { id: "c812a9dc-6a54-409e-adb5-8eb09337e576", text: "text 3"} ]; //    console.log("test-items", testItems); function TestPage() { const onChange = id => console.log("Select: change", id); return <div> <Select items={testItems} onChange={onChange} /> </div> } ReactDOM.render(<TestPage />, document.getElementById("application")); 

And we will expand this page, for example, by url http: // localhost: 8080 . Note that now two arguments are passed to console.log : the first is the id, and the second argument is the data. Now we will write the test code:

 const puppeteer = require("puppeteer"); const io = require("puppeteer-io"); test(` onChange   id`, async () => { let browser = await puppeteer.launch(); let page = await browser.newPage(); await io({ page, async input() { await page.goto("http://localhost:8080"); let select = await page.$("select"); await select.focus(); await select.press("Enter"); await select.press("ArrowDown"); await select.press("Enter"); }, async output({ dataFromMessage }) { let [,secondItem] = await dataFromMessage("test-items"); let selectedId = await dataFromMessage("Select: change"); expect(selectedId).toBe(secondItem.id); } }); await page.close(); await browser.close(); }); 

Consider the function code output. The first step is to get the test data. For this, it is important to trigger the url transition in the input stream, because page.goto waiting for the page page.goto event, and by that time the console.log("test-items", testItems) already completed and the message will not be received. To obtain data, use the dataFromMessage function, which returns the second argument passed to console.log . When the test data is received, you can wait for the selected id, and compare the result with the expected. Functionality tested.

Catching errors


Finally, an example of how to handle errors. Create a page:

 <script> throw new Error("test-error"); </script> 

To catch an error , the error function will be used, which takes a string or a regular expression as a parameter to search for an error with the corresponding text in the message property, and returns the full text of the error. A test that will verify that an error has occurred on the page:

 const puppeteer = require("puppeteer"); const io = require("puppeteer-io"); test(`,     `, async () => { let browser = await puppeteer.launch(); let page = await browser.newPage(); await io({ page, async input() { await page.goto(`file://${__dirname}/index.html`); }, async output({ error }) { await error("test-error"); } }); await page.close(); await browser.close(); }); 

That's all. We considered a testing method based not on checking for the presence of html elements or their textual content, but on testing based on getting the result and checking it against the expected one.

Source: https://habr.com/ru/post/346676/


All Articles