To avoid unintended changes to Handsontable's UI, we use visual regression testing.
We run visual tests automatically by using the following tools:
| Tool | Description |
|---|---|
| Playwright | An open-source testing framework backed by Microsoft. We use it to write and run visual tests. |
| Argos | An external visual testing service. We use it to compare screenshots. |
| GitHub Actions | GitHub's CI platform. We use it to automate our test workflows. |
When you push changes to a GitHub pull request:
- The Visual tests linter workflow checks the code of each visual test.
- The Tests workflow runs all of Handsontable's tests.
- After all tests pass successfully, the Visual tests job runs the visual tests and uploads the resulting screenshots to Argos.
- Argos compares your feature branch screenshots against the reference branch (
develop) screenshots (so-called "reference", "baseline" or "golden" screenshots).
If Argos spots differences between two corresponding screenshots,
the Visual tests check on on your pull request fails, and you can't merge your changes to develop. In that case:
- Open the log of the Visual tests job:
At the bottom of your pull request, find the Visual tests check. Select Details. - Open the Argos URL and review the differences.
You can:
- Reject the modified screenshots, update your code, and re-run the visual tests.
- Accept the modified screenshots.
You can then merge your changes to
develop. As a result, the modified screenshots become the new baseline.
Our GitHub Actions configuration runs the visual tests automatically, but you can run them manually as well:
- On GitHub, at the bottom of your pull request, find the Visual tests check. Select Details.
- On the left, next to the Visual tests job, select 🔄.
- Select Re-run jobs.
You can manually run visual tests on your machine and then upload the resulting screenshots to Argos.
First, prepare your local visual testing environment:
- Make sure your Docker Desktop app is running.
- Make sure you're using the Node and npm versions mentioned here.
- From the
./visual-tests/directory, runnpm install. - In the
./visual-tests/directory, create a file called.env. In the file, add the Argos token:Ask your supervisor about the token's value.ARGOS_TOKEN=xxx
To run the visual tests locally:
-
From the
./visual-tests/directory, run one of the following commands:Command Action npm run testRun all the visual tests,
for all the configured frameworks,
for all the supported browsers.npx playwright test {{ file name }}Run a specific test.
For example:npx playwright test mouse-wheelThe resulting screenshots are saved in
./visual-tests/screenshots/. -
From the
./visual-tests/directory, runnpm run upload. -
Open the Argos URL displayed in the terminal.
To add a new visual test:
- On your machine, in the
./visual-tests/tests/directory, create a new.spec.tsfile.
Give your file a descriptive name. This name is later used in test logs and screenshot names.- ✅ Good:
open-dropdown-menu.spec.ts. - ❌ Bad:
my-test-1.spec.ts.
- ✅ Good:
- Copy the template code from
./visual-tests/tests/.empty-test-template.tsinto your file. - Write your test. For more information, see:
- Push your changes to a pull request.
The Visual tests linter workflow checks the code of your test.
To capture a screenshot and save it to a file, add this line anywhere in your test:
await page.screenshot({ path: helpers.screenshotPath() });In each test, you can take as many screenshots as you want. For example:
await cell.click();
await page.screenshot({ path: helpers.screenshotPath() });
await anotherCell.click();
await page.screenshot({ path: helpers.screenshotPath() });To take a screenshot of a specific element of Handsontable,
use Playwright's locator() method. For example:
const dropdownMenu = page.locator(helpers.selectors.dropdownMenu);
await dropdownMenu.screenshot({ path: helpers.screenshotPath() });To write tests faster, use the custom helper functions and variables stored in the ./visual-tests/src/helpers.ts file.
Returns the current modifier key: Ctrl for Windows or Meta for Mac.
// copy the contents of the selected cell
await page.keyboard.press(`${helpers.modifier}+c`);Returns true if the test runs on Mac.
if (helpers.isMac) {
// do something
}Returns the specified cell.
Syntax: findCell({ row: number, cell: number, cellType: 'td / th' }).
const cell = helpers.tbody.locator(helpers.findCell({ row: 2, cell: 2, cellType: 'td' }));
await cell.click();Returns the button that expands the dropdown menu (also known as column menu) of the specified column.
Syntax: findDropdownMenuExpander({ col: number }).
// select the column menu button of the second column
const changeTypeButton = table.locator(helpers.findDropdownMenuExpander({ col: 2 }));
await changeTypeButton.click();