Skip to content

Commit ef059de

Browse files
committed
blog: screenshot stabilisation article
1 parent d277c48 commit ef059de

File tree

4 files changed

+157
-2
lines changed

4 files changed

+157
-2
lines changed
+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
---
2+
title: "Stabilize Flaky Tests for Visual Testing"
3+
description: "Explore common use-cases responsible for flaky tests in visual testing and best practices to resolve them."
4+
category: Visual testing
5+
author: Jeremy Sfez
6+
date: 2024-02-15
7+
image: ./main.jpg
8+
imageAlt: Image not loaded
9+
---
10+
11+
Visual testing is the most efficient way to test your app as it's **fast** and **robust** (doesn't require maintenance). Yet, flaky tests create frustration and erode developers' trust in their testing suite, potentially leading to the abandonment of visual testing altogether. In this article, I'll describe the **common culprits responsible for flaky screenshots and how to fix them**. I'll also introduce [Argos](https://argos-ci.com)'s solutions to stabilize screenshots.
12+
13+
<MainImage />
14+
15+
## What is a Flaky Test?
16+
17+
In visual testing, a flaky test produces screenshots that differ randomly between test runs without any changes to the code. The common causes are often **network latency, lazy loading, animations, and dynamic content like dates and external scripts**.
18+
19+
#### Disclaimer: A Flaky Test Means a Flaky UI
20+
21+
Flaky screenshots are usually a sign of underlying instability in the app's UI. To minimize flaky tests, the first step is to minimize random behaviors and ensure the UI is as stable as possible. For example, if you have a news website, you should rely on a stable dataset for your test environment.
22+
23+
## Network Latency
24+
25+
Network latency can cause flaky screenshots if assets (**font**, **images**, ...) are not fully loaded before the screenshot is taken.
26+
27+
<img
28+
src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5gm3x4uaymqhm3qunbm8.jpg"
29+
width={600}
30+
alt="font-not-loaded"
31+
/>
32+
33+
To stabilize screenshots, you must ensure your resources are fully loaded before capturing screenshots. For example, the `argosScreenshot` command waits for resources to load before taking a screenshot.
34+
35+
## Lazy Loading
36+
37+
Lazy loading can cause flaky screenshots if the screenshot is taken before the elements are fully loaded.
38+
39+
<img
40+
src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4nginqza6q17jvrqs16r.png"
41+
width={600}
42+
alt="loader"
43+
/>
44+
45+
To stabilize screenshots, use a loading indicator and wait for its removal before proceeding. [Aligning with ARIA specification](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-busy), I recommend using the `[aria-busy]` attribute on your loader component. At Argos, the `argosScreenshot` command delays the screenshot capture until all components with the `[aria-busy]` attribute are removed.
46+
47+
Example of a loader component:
48+
49+
```JavaScript
50+
<div id="loader" aria-busy />
51+
```
52+
53+
## Dynamic Content
54+
55+
By nature, dynamic elements like **dates or time** introduce variability in screenshots. The easiest solution is to hide these elements by injecting CSS before taking a screenshot. With Argos, you can use the `data-visual-test` attribute to hide these elements from the screenshot.
56+
57+
```JavaScript
58+
<div id="clock" data-visual-test="transparent">...</div>
59+
```
60+
61+
For dates, another solution is to use a fixed date for your test environment. You can see here [how to do it with Cypress](https://docs.cypress.io/api/commands/clock). But this solution is not always possible in a real-world scenario.
62+
63+
## Animation
64+
65+
Capturing a screenshot during an animation will lead to flaky screenshots. They must be either completed or paused before taking a screenshot. With Argos, CSS animations are automatically paused, but JavaScript animations require manual intervention or hiding with `data-visual-test`.
66+
67+
**Note**: If an animation causes layout shifts, it's recommended to remove it from the DOM with `data-visual-test="removed"` instead of merely hiding it with `data-visual-test="transparent"`.
68+
69+
## External Scripts and Iframes
70+
71+
External scripts and iframes (e.g. Intercom snippet) introduce flakiness due to network latency or rendering inconsistencies. There are several solutions to avoid flakiness, consider the following in this order:
72+
73+
1. Avoid loading external scripts in your test environment.
74+
2. Inject CSS to hide their UI impacts.
75+
76+
```JavaScript
77+
await argosScreenshot(page, "homepage", {
78+
argosCSS: `
79+
.__argos__ iframe {
80+
display: none;
81+
}
82+
`,
83+
});
84+
```
85+
86+
3. Wait for the iframe's content to be loaded before taking the screenshot. If you follow this path, I recommend reading [Debbie O'Brien](https://twitter.com/debs_obrien)'s article about how to [test an iframes with Playwright](https://debbie.codes/blog/testing-iframes-with-playwright/).
87+
88+
## Data inconsistency
89+
90+
The two main causes of data inconsistency are **randomized data seed** and **missing sorting order**. Usually, it's easy to fix by using a fixed dataset for your test environment. But if not, you can use the `data-visual-test` attribute to hide the area where the data is displayed but still keep the layout consistent in the screenshot.
91+
92+
```JavaScript
93+
<article>
94+
<div class="title" data-visual-test="blackout">Breaking news: XXX</div>
95+
<div class="content" data-visual-test="blackout">...</div>
96+
</article>
97+
```
98+
99+
## Mobile Status Bars
100+
101+
Mobile status bars introduce flakiness in screenshots due to their dynamic nature: notifications, battery, and time. The best practice is to hide the mobile status bar before taking a screenshot, crop it out, or ignore this area in the comparison. Argos offers a mask option for the latter.
102+
103+
Example with Argos' WebDriverIO integration:
104+
105+
```JavaScript
106+
import { argosScreenshot } from "@argos-ci/webdriverio";
107+
import { browser } from "@wdio/globals";
108+
109+
describe("Integration test with visual testing", () => {
110+
it("covers homepage", async () => {
111+
await browser.url("http://localhost:3000");
112+
await argosScreenshot(browser, "homepage", {
113+
mask: [{ x: 0, y: 0, width: 1170, height: 120 }],
114+
});
115+
});
116+
});
117+
```
118+
119+
## Browser-Related Inconsistencies
120+
121+
Browser-related inconsistencies are challenging because they are generated from the browser itself, not your code. Addressing them requires in-depth research and development. Nevertheless, Argos provides built-in solutions for most of them, ensuring screenshot consistency across runs.
122+
123+
**Common browser inconsistency causes:**
124+
125+
- **Glitches**: border radius, shadow, scrolling bar, and caret visibility
126+
- **Rendering**: text aliasing, image rendering
127+
- **Navigation**: random focus, random hover, and scrolling position
128+
129+
## Cropped Screenshots Due to Layout Shift
130+
131+
Sometimes, flaky tests produce cropped screenshots because Playwright (or another browser automation library) measures the page size before a layout shift, then takes the screenshot after the shift. The best way to fix this is to identify and correct the root cause of the layout shift. It can be challenging!
132+
133+
## Capturing Screenshots on CI/CD or Locally
134+
135+
Capturing screenshots on different machines introduces variability, as each device's settings can affect the output. Using a CI/CD pipeline for screenshot capture standardizes the environment and enforces stability, ensuring every screenshot is produced under identical conditions.
136+
137+
## Conclusion
138+
139+
Flaky tests are a common challenge in visual testing, but once you know the patterns, it will be easier to stabilize your screenshots. The most important thing is to not give up on any flaky test and risk introducing a bug in your app. Keeping your test suite stable might seem tough and thankless at times, but in the end, it's always worth the effort.
140+
141+
I hope these best practices help you stabilize your visual testing suite. If you have any questions or want to share your experience, feel free to reach out or join [Argos' Discord community](https://argos-ci.com/discord).
157 KB
Loading

components/Post.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const MainImage = ({
1515
<div
1616
className="not-prose rounded-lg"
1717
style={{
18-
aspectRatio: "2/1",
18+
aspectRatio: "1000/420",
1919
position: "relative",
2020
overflow: "hidden",
2121
}}

styles/globals.css

+15-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,21 @@
1919
}
2020

2121
.prose.prose {
22-
--tw-prose-pre-bg: #0d1117;
22+
--tw-prose-pre-bg: theme(backgroundColor.primary.ui);
23+
pre:has(code) {
24+
margin-top: -10px;
25+
}
26+
27+
:not(pre) > code {
28+
background-color: theme(backgroundColor.ui);
29+
border-radius: 4px;
30+
padding: 4px 6px;
31+
32+
&::before,
33+
&::after {
34+
content: "";
35+
}
36+
}
2337
}
2438

2539
/* Hide number input arrows */

0 commit comments

Comments
 (0)