visit
What approaches does this library recommend, and does it make sense for a tester to use it? How do we usually write e2e tests?
There is a simple TODO application. How would a typical test with adding a task to the list look like?
export default class SimpleTodoPage {
constructor(page) {
this.page = page;
}
async navigate() {
await this.page.goto('//localhost:8080');
}
async addTask(text) {
const selector = '[data-testid="task-name-input"]';
await this.page.waitForSelector(selector);
const elementHandle = await this.page.$(selector);
await elementHandle.type(text, { delay: 25 });
await this.page.keyboard.press('Enter');
}
}
it('should add new task', async () => {
const page = await browser.newPage();
const simpleTodoPage = new SimpleTodoPage(page);
await simpleTodoPage.navigate();
await simpleTodoPage.addTask('my task');
await expect(page).toMatch('my task');
});
We usually use testId
for the selectors in e2e. That’s what we did inside our test. Jest report may look like:
PASS __tests__/test.js
Simple Todo
✓ should add new task (2309 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.998 s, estimated 3 s
The has its own philosophy. This tool is not as universal as libraries for e2e tests. But you get fast and pretty "honest" tests, which, if well organized, won't break after every refactoring. And thanks to the fast feedback, you can keep them running with jest --watch
. The watch flag tells jest to watch for file changes and run the tests affected by those changes.
The core library, DOM Testing Library, is a light-weight solution for testing web pages by querying and interacting with DOM nodes (whether simulated with JSDOM/Jest or in the browser). The main utilities it provides involve querying the DOM for nodes in a way that's similar to how the user finds elements on the page. In this way, the library helps ensure your tests give you confidence that your application will work when a real user uses it.
I would emphasize two main points. Firstly, why is it light-weight solution? The library leverages to run tests quickly. What is that?
jsdom is a pure-JavaScript implementation of many web standards, notably the WHATWG DOM and HTML Standards, for use with Node.js. In general, the goal of the project is to emulate enough of a subset of a web browser to be useful for testing and scraping real-world web applications.
Testing-library has wrappers for popular frameworks (React, Vue, Angular) that expose APIs to deal with framework components. Wrappers use the DOM Testing Library with a universal API to interact with DOM. And the DOM Testing Library internally calls jsdom
methods:
FRAMEWORK SPECIFIC WRAPPER → DOM Testing Library → JSDOM
(Eg. React Testing Library)
beforeEach(() => {
// ...
app = {
tasksContainer: document.querySelector('[data-container="tasks"]'),
newTaskForm: document.querySelector('[data-container="new-task-form"]'),
};
});
it('should add new task', () => {
const taskName = 'new task';
app.newTaskForm.querySelector('[data-testid="add-task-input"]').value = taskName;
const addTaskBtn = app.newTaskForm.querySelector('[data-testid="add-task-button"]');
addTaskBtn.click();
expect(app.newTaskForm).toHaveFormValues({
name: '',
});
expect(app.tasksContainer).toHaveTextContent(taskName);
});
PASS __tests__/application.test.js
✓ should add new task (33 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.361 s, estimated 2 s
The test uses CSS selectors with classes. To enter the task text, cut the corner and assign the desired result to the value
attribute, i.e. modify the DOM directly. Then call the click()
event for the button. The test is low-level, verbose and a little bit synthetic cause the nodes are modified directly.
it('should add new task', () => {
const taskName = 'new task';
const newTaskInput = screen.getByLabelText('New Task Name');
userEvent.type(newTaskInput, taskName);
userEvent.click(screen.getByRole('button', { name: /add task/i }));
expect(newTaskInput).toHaveDisplayValue('');
expect(screen.getByText(taskName)).toBeInTheDocument();
});
PASS __tests__/application.test.js
✓ should add new task (150 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.642 s, estimated 2 s
document.querySelector
, the testing-library API is used to get the elements (screen.getByRole
, screen.getByLabelText
).
render
function for that.jest --watch
is a real productivity booster.