What is Visual Testing?
Visual testing is a software testing method that compares the software's UI, such that colors, the position of the elements, images, fonts, and layouts are displayed correctly without overlaps or missing elements.
This is useful when you need to compare the design with different screen sizes or complex UI like dashboards, which could be very hard to test checking if the element is visible with traditional end-to-end tests that only check if the element is visible but check all the styles, colors and position like not overlapped elements will be very hard.
How works
There are different tools to help with visual testing, like Percy, applitools, or imagium. For most of them, you need a base image (reference image), and the next test will compare the actual result with the base image.
Tips for Visual Testing
Add a mask for dynamic elements.
If you have some dynamic field like the current date, you can add a mask to the date element, like a pink box over the element, to always have the same image. And manually check if the date is displayed correctly with a traditional end-to-end test.
For example, I added a red mask for the top right button in the following snapshot.
Generate the snapshot until the UI is finished.
If the UI development is in the early stage and the UI will change constantly, it is better to wait until the final design. If not, you will need to update the base snapshot many times.
Mock the API to get the same results.
If you need to test some info that changes periodically, like a dashboard that shows daily info, you can:
Mock the API to get the same results
Add API testing to check that the info for the actual API is returned correctly.
Add the visual testing
How to create visual testing with Playwright.
Playwright includes visual comparisons in the Node.js version for the complete page or a locator.
import { test, expect } from '@playwright/test';
test('example test', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page).toHaveScreenshot('home.png');
});
The first time you execute the test, it will fail because the base image doesn’t exist, and a snapshot will created with the name of the image-browser-OS. For example, home-chromium-darwin.png. For macOS, it is generated with Darwin, and for Windows, it is win32.
To mask an element, you can set the options mask to add the elements you want to mask and maskColor to set the color for the mask.
import { test, expect } from '@playwright/test';
test('example test', async ({ page }) => {
await page.goto('https://playwright.dev');
await expect(page).toHaveScreenshot(snapshotName,
{
mask: [this.page.locator('a.gh-count')],
maskColor: '#FF0000',
});
});
The reporter will add the actual image, the expected one side-by-side, and a slider that you can use to compare the snapshots when the snapshot fails.
When the UI changes, you can update your snapshots with the following command:
npx playwright test --update-snapshots
How to mock the API with Playwright.
To mock the API:
Get the JSON from the Postman or the browser
Store it as a JSON file in your automation framework. I am adding all mock data to the api/data folder.
Create a class to mock all the APIs. In this sample is AccountReceivableApi
Import the JSON file in step 1.
Add the function to mock the API
async mockApi(url: string, jsonData: any) {
await this.page.route(url, async route => {
await route.fulfill({ body: JSON.stringify(jsonData) });
});
}
The code should be something like this
import { Page } from '@playwright/test';
import { ApiHelper } from '../../utils/ApiHelper';
import { AnnotationHelper } from '../../utils/annotations/AnnotationHelper';
import { AnnotationType } from '../../utils/annotations/AnnotationType';
import summary from '../../api/data/summary.json';
export class AccountReceivableApi {
apiHelper: ApiHelper;
private annotationHelper = new AnnotationHelper(this.page, '');
constructor(private page: Page) {
const baseURL = 'https://effizienteauthdemo.azurewebsites.net';
this.apiHelper = new ApiHelper(this.page, baseURL);
}
/**
* Change the summary to fixed json file to get always the same data
*/
async mockSummary() {
const stepDescription = 'Modify the summary with fixed data';
this.annotationHelper.addAnnotation(AnnotationType.Mock, stepDescription);
// eslint-disable-next-line playwright/valid-title
await test.step(stepDescription, async () => {
await this.apiHelper.mockApi('*/**/api/Cobranza/Resumen', summary);
});
}
For the test:
Call the mock API functions; in my sample, I have several APIs, and I added a function called mockAllApis to mock all the API calls for all the charts.
Go to the page for the dashboard.
Wait until the chart is generated. I checked that the canvas elements are visible for all the charts.
And finally, compare the snapshot.
import { test } from '@playwright/test';
import { DashboardPage } from '../../pages/Effiziente/dashboardPage';
import { AccountReceivableApi } from '../../api/Effiziente/AccountsReceivable.api';
test.describe('Check Dashboard', async () => {
test.use({ storageState: 'auth/user.json' });
// eslint-disable-next-line playwright/expect-expect
test('Should show dashboard', async ({ page }) => {
const dashboardPage = new DashboardPage(page);
const accountReceivableApi = new AccountReceivableApi(page);
//1. Mock all the API
await accountReceivableApi.mockAllApis();
//2. Go to the dashboard Page
await dashboardPage.goTo();
//3. Wait to the canvas element of the chart are visible
await dashboardPage.waitForCharts();
//4. Compare the snapshot
await dashboardPage.checkSnapshot();
});
});
You can see the full repository with my playwright framework template.
Thank you for reading. If you like this article, feel free to share or add a comment with your suggestions for the following topics.