blob: 7b46c84f9861d1228b68c3d1775a187959f932d3 [file] [log] [blame] [view]
Dana Fried005eebdf2025-06-03 16:29:361# Testing Chrome browser UI with Pixel Tests
taptedf38739a2017-02-09 04:34:522
Dana Fried005eebdf2025-06-03 16:29:363Pixel Tests compare one or more screenshots with already-approved images using
4[Skia Gold](/docs/ui/learn/glossary.md#skia-gold). They guarantee that the UI
5does not change its appearance unexpectedly and are a good addition to a
6portfolio of regression tests.
taptedf38739a2017-02-09 04:34:527
Dana Fried005eebdf2025-06-03 16:29:368There are two ways that pixel tests can be written:
9 - Kombucha, using the `Screenshot` test verbs (preferred)
10 - Using the `TestBrowserUi` API (provided for legacy support; do not use)
taptedf38739a2017-02-09 04:34:5211
12[TOC]
13
Dana Fried005eebdf2025-06-03 16:29:3614## Common Requirements
taptedf38739a2017-02-09 04:34:5215
Dana Fried005eebdf2025-06-03 16:29:3616All pixel tests must be placed in
17[pixel_tests.filter](/testing/buildbot/filters/pixel_tests.filter).
18
19Tests in `browser_tests` will also be run in the `pixel_browser_tests` job on
20eligible builders. Tests in `interactive_ui_tests` will also be run in the
21`pixel_interactive_ui_tests` job on eligible builders.
22
23### Baseline CLs
24
25All methods of taking screenshots allow you to set a baseline CL. This ensures
26that old expected images are thrown away when the UI is modified. If the
27baseline were not set, a regression that caused some new UI to appear might pass
28a pixel test because it matched an older version of the UI surface.
29
30The procedure for baseline CLs is:
311. Put in a placeholder string.
321. Upload your CL to Gerrit.
331. Find the number of your new CL (e.g. from the URL)
341. Replace the placeholder with the number.
351. Re-upload the CL.
36
37### Running Tests Locally
38
39To run a pixel test locally, use:
40```sh
41test_executable --gtest_filter=Test.Name --browser-ui-tests-verify-pixels --enable-pixel-output-in-tests --test-launcher-retry-limit=0
42```
43Where `<test_executable>` is either `browser_tests` or `interactive_ui_tests`,
44and `<Test.Name>` is the full name of your test, with dot.
45
46If you want to see UI as it's being screenshot, replace
47`--enable-pixel-output-in-tests` with `--test-launcher-interactive` - this will
48freeze the test just after the screenshot is taken. Dismiss the UI to continue
49the test.
50
51### Diagnosing Test Failures
52
53Failed pixel tests will have a URL link to Skia Gold in the test output through
54which you can view the expected and actual screenshot images. Log in and either
55accept or reject images that do not match the baseline. Once you've accepted a
56new image, future tests that produce the same output won't fail.
57
58## Writing Pixel Tests with Kombucha
59
60[Kombucha](/chrome/test/interaction/README.md) tests use a declarative syntax to
61perform interaction testing on the browser. They are the preferred way to create
62end-to-end interaction and critical user journey regression tests for Chrome
63Desktop.
64
65Nearly all Kombucha tests can derive directly from
66[InteractiveBrowserTest](/chrome/test/interaction/interactive_browser_test.h).
67`InteractiveBrowserTest` is a strict superset of `InProcessBrowserTest` so it is
68usually safe to simply swap one for the other.
69
70### Taking Screenshots
71
72To use Kombucha to pixel-test UI, use the `Screenshot` or `ScreenshotSurface`
73verb. Because these tests will also be run in non-pixel-test mode, you will need
74to precede the first screenshot with `SetOnIncompatibleAction()` with an option
75other than `kFailTest` (which is the default).
76
77The `Screenshot` verb takes a picture of _exactly the UI element specified_.
78
79The `ScreenshotSurface` verb takes a picture of the entire dialog or window
80containing the element. Be careful not to capture other elements that are likely
81to change on their own!
82
83Example:
84
85```cpp
86// Inherit from InteractiveBrowserTest[Api]:
87class MyNewDialogUiTest : public InteractiveBrowserTest { ... };
88
89// Baseline Gerrit CL number of the most recent CL that modified the UI.
90constexpr char kScreenshotBaselineCL[] = "12345678";
91
92// Screenshot the feature's entrypoint button, then click it, wait for the
93// feature's dialog, then screenshot the entire dialog.
94IN_PROC_BROWSER_TEST_F(MyNewDialogUiTest, OpenAndVerifyContents) {
95 RunTestSequence(
96 SetOnIncompatibleAction(
97 OnIncompatibleAction::kIgnoreAndContinue
98 "Screenshots not supported in all testing environments."),
99
100 // Grab a screenshot of the toolbar button that is the entry point for the feature.
101 Screenshot(kMyNewToolbarButtonElementId,
102 /*screenshot_name=*/"entry_point",
103 /*baseline_cl=*/kScreenshotBaselineCL)
104
105 PressButton(kMyNewToolbarButtonElementId),
106
107 WaitForShow(MyNewDialog::kDialogElementId),
108
109 // Grab a screenshot of the entire dialog that pops up.
110 ScreenshotSurface(MyNewDialog::kDialogElementId,
111 /*screenshot_name=*/"whole_dialog",
112 /*baseline_cl=*/kScreenshotBaselineCL));
113}
114```
115
116Note that a test can take multiple screenshots; they must have unique names. In
117the above example, `entry_point` and `whole_dialog` will be treated as two
118separate screenshots to be compared against separate Skia Gold masters.
119
120## Writing Pixel Tests with TestBrowserUi
121
122`UiBrowserTest` and `DialogBrowserTest` provide an alternate (and older) method
123of capturing a surface for pixel testing. These are also base classes your test
124harness needs to inherit from, and also replace `InProcessBrowserTest`.
125
126For example, assume the existing
Peter Kastingcf49b7b792017-12-18 23:27:45127`InProcessBrowserTest` is in `foo_browsertest.cc`:
Dana Fried005eebdf2025-06-03 16:29:36128```
Peter Kastingcf49b7b792017-12-18 23:27:45129 class FooUiTest : public InProcessBrowserTest { ...
Dana Fried005eebdf2025-06-03 16:29:36130```
Peter Kastingcf49b7b792017-12-18 23:27:45131Change this to inherit from `DialogBrowserTest` (for dialogs) or `UiBrowserTest`
132(for non-dialogs), and override `ShowUi(std::string)`. For non-dialogs, also
133override `VerifyUi()` and `WaitForUserDismissal()`. See
134[Advanced Usage](#Advanced-Usage) for details.
taptedf38739a2017-02-09 04:34:52135
136```cpp
Peter Kastingcf49b7b792017-12-18 23:27:45137class FooUiTest : public UiBrowserTest {
taptedf38739a2017-02-09 04:34:52138 public:
139 ..
Peter Kastingcf49b7b792017-12-18 23:27:45140 // UiBrowserTest:
141 void ShowUi(const std::string& name) override {
142 /* Show Ui attached to browser() and leave it open. */
143 }
144 // These next two are not necessary if subclassing DialogBrowserTest.
145 bool VerifyUi() override {
146 /* Return true if the UI was successfully shown. */
147 }
148 void WaitForUserDismissal() override {
149 /* Block until the UI has been dismissed. */
taptedf38739a2017-02-09 04:34:52150 }
151 ..
152};
153```
154
Peter Kastingcf49b7b792017-12-18 23:27:45155Finally, add test invocations using the usual GTest macros, in
156`foo_browsertest.cc`:
taptedf38739a2017-02-09 04:34:52157
158```cpp
Peter Kastingcf49b7b792017-12-18 23:27:45159IN_PROC_BROWSER_TEST_F(FooUiTest, InvokeUi_default) {
160 ShowAndVerifyUi();
taptedf38739a2017-02-09 04:34:52161}
162```
163
164Notes:
165
Peter Kastingcf49b7b792017-12-18 23:27:45166* The body of the test is always just "`ShowAndVerifyUi();`".
167* "`default`" is the `std::string` passed to `ShowUi()` and can be
taptedf38739a2017-02-09 04:34:52168 customized. See
Peter Kastingcf49b7b792017-12-18 23:27:45169 [Testing additional UI "styles"](#Testing-additional-ui-styles).
170* The text before `default` (in this case) must always be "`InvokeUi_`".
taptedf38739a2017-02-09 04:34:52171
172### Concrete examples
173
174* [chrome/browser/ui/ask_google_for_suggestions_dialog_browsertest.cc]
Peter Kastingcf49b7b792017-12-18 23:27:45175* [chrome/browser/infobars/infobars_browsertest.cc]
taptedf38739a2017-02-09 04:34:52176
Dana Fried005eebdf2025-06-03 16:29:36177### Running the tests
taptedf38739a2017-02-09 04:34:52178
Dana Fried005eebdf2025-06-03 16:29:36179List the available `TestBrowserUi` tests:
taptedf38739a2017-02-09 04:34:52180
Peter Kastingcf49b7b792017-12-18 23:27:45181 $ ./browser_tests --gtest_filter=BrowserUiTest.Invoke
Keren Zhu1f7b99da2023-10-04 23:47:32182 $ ./interactive_ui_tests --gtest_filter=BrowserInteractiveUiTest.Invoke
taptedf38739a2017-02-09 04:34:52183
Peter Kastingcf49b7b792017-12-18 23:27:45184E.g. `FooUiTest.InvokeUi_default` should be listed. To show the UI
taptedf38739a2017-02-09 04:34:52185interactively, run
186
Keren Zhu1f7b99da2023-10-04 23:47:32187 # If FooUiTest is a browser test.
Peter Boströme2732ef2018-02-21 21:53:24188 $ ./browser_tests --gtest_filter=BrowserUiTest.Invoke \
189 --test-launcher-interactive --ui=FooUiTest.InvokeUi_default
taptedf38739a2017-02-09 04:34:52190
Keren Zhu1f7b99da2023-10-04 23:47:32191 # If FooUiTest is an interactive UI test.
192 $ ./interactive_ui_tests --gtest_filter=BrowserInteractiveUiTest.Invoke \
193 --test-launcher-interactive --ui=FooUiTest.InvokeUi_default
194
taptedf38739a2017-02-09 04:34:52195### Implementation
196
Peter Kastingcf49b7b792017-12-18 23:27:45197`BrowserUiTest.Invoke` searches for gtests that have "`InvokeUi_`" in their
198names, so they can be collected in a list. Providing a `--ui` argument will
Peter Boströme2732ef2018-02-21 21:53:24199invoke that test case in a subprocess. Including `--test-launcher-interactive`
200will set up an environment for that subprocess that allows interactivity, e.g.,
201to take screenshots. The test ends once the UI is dismissed.
taptedf38739a2017-02-09 04:34:52202
Peter Kastingcf49b7b792017-12-18 23:27:45203The `FooUiTest.InvokeUi_default` test case **will still be run in the usual
204browser_tests test suite**. Ensure it passes, and isn’t flaky. This will
205give your UI some regression test coverage. `ShowAndVerifyUi()` checks to ensure
206UI is actually created when it invokes `ShowUi("default")`.
taptedf38739a2017-02-09 04:34:52207
Keren Zhu1f7b99da2023-10-04 23:47:32208`BrowserInteractiveUiTest` is the equivalent of `BrowserUiTest` for
209interactive_ui_tests.
210
Peter Kastingcf49b7b792017-12-18 23:27:45211### BrowserUiTest.Invoke
taptedf38739a2017-02-09 04:34:52212
213This is also run in browser_tests but, when run that way, the test case just
214lists the registered test harnesses (it does *not* iterate over them). A
Peter Kastingcf49b7b792017-12-18 23:27:45215subprocess is never created unless --ui is passed on the command line.
taptedf38739a2017-02-09 04:34:52216
217## Advanced Usage
218
219If your test harness inherits from a descendant of `InProcessBrowserTest` (one
Peter Kastingcf49b7b792017-12-18 23:27:45220example: [ExtensionBrowserTest]) then the `SupportsTestUi<>` and
221`SupportsTestDialog` templates are provided. E.g.
taptedf38739a2017-02-09 04:34:52222
223```cpp
224class ExtensionInstallDialogViewTestBase : public ExtensionBrowserTest { ...
225```
226
227becomes
228
229```cpp
230class ExtensionInstallDialogViewTestBase :
231 public SupportsTestDialog<ExtensionBrowserTest> { ...
232```
233
Peter Kastingcf49b7b792017-12-18 23:27:45234If you need to do any setup before `ShowUi()` is called, or any teardown in the
235non-interactive case, you can override the `PreShow()` and `DismissUi()
236methods.
taptedf38739a2017-02-09 04:34:52237
Peter Kastingcf49b7b792017-12-18 23:27:45238### Testing additional UI "styles"
239
240Add additional test cases, with a different string after "`InvokeUi_`".
taptedf38739a2017-02-09 04:34:52241Example:
242
243```cpp
Peter Kastingcf49b7b792017-12-18 23:27:45244IN_PROC_BROWSER_TEST_F(CardUnmaskViewBrowserTest, InvokeUi_expired) {
245 ShowAndVerifyUi();
taptedf38739a2017-02-09 04:34:52246}
247
Peter Kastingcf49b7b792017-12-18 23:27:45248IN_PROC_BROWSER_TEST_F(CardUnmaskViewBrowserTest, InvokeUi_valid) {
249 ShowAndVerifyUi();
taptedf38739a2017-02-09 04:34:52250}
251```
252
253The strings "`expired`" or `valid` will be given as arguments to
Peter Kastingcf49b7b792017-12-18 23:27:45254`ShowUi(std::string)`.
taptedf38739a2017-02-09 04:34:52255
256## Rationale
257
258Bug reference: [Issue 654151](http://crbug.com/654151).
259
Peter Kastingcf49b7b792017-12-18 23:27:45260Chrome has a lot of browser UI; often for obscure use-cases and often hard to
261invoke. It has traditionally been difficult to be systematic while checking UI
262for possible regressions. For example, to investigate changes to shared layout
263parameters which are testable only with visual inspection.
taptedf38739a2017-02-09 04:34:52264
265For Chrome UI review, screenshots need to be taken. Iterating over all the
Peter Kastingcf49b7b792017-12-18 23:27:45266"styles" that UI may appear with is fiddly. E.g. a login or particular web
taptedf38739a2017-02-09 04:34:52267server setup may be required. Its important to provide a consistent look for
268UI review (e.g. same test data, same browser size, anchoring position, etc.).
269
Peter Kastingcf49b7b792017-12-18 23:27:45270Some UI lacks tests. Some UI has zero coverage on the bots. UI elements can have
271tricky lifetimes and common mistakes are repeated. TestBrowserUi runs simple
272"Show UI" regression tests and can be extended to do more.
taptedf38739a2017-02-09 04:34:52273
Peter Kastingcf49b7b792017-12-18 23:27:45274Even discovering the full set of UI present for each platform in Chrome is
taptedf38739a2017-02-09 04:34:52275[difficult](http://crbug.com/686239).
276
277### Why browser_tests?
278
279* `browser_tests` already provides a `browser()->window()` of a consistent
280 size that can be used as a dialog anchor and to take screenshots for UI
281 review.
282 * UI review have requested that screenshots be provided with the entire
Peter Kastingcf49b7b792017-12-18 23:27:45283 browser window so that the relative size of the UI element/change under
taptedf38739a2017-02-09 04:34:52284 review can be assessed.
285
Peter Kastingcf49b7b792017-12-18 23:27:45286* Some UI already has a test harness with appropriate setup (e.g. test data)
287 running in browser_tests.
288 * Supporting `BrowserUiTest` should require minimal setup and minimal
taptedf38739a2017-02-09 04:34:52289 ongoing maintenance.
290
291* An alternative is to maintain a working end-to-end build target executable
292 to do this, but this has additional costs (and is hard).
Peter Kastingc6fd7f2d2020-03-10 21:21:08293 * E.g. setup/teardown of low-level functions
294 (`InitializeGLOneOffPlatform()`, etc.).
taptedf38739a2017-02-09 04:34:52295
296* Why not chrome.exe?
Peter Kastingcf49b7b792017-12-18 23:27:45297 * E.g. a scrappy chrome:// page with links to invoke UI would be great!
taptedf38739a2017-02-09 04:34:52298 * But...
Peter Kastingcf49b7b792017-12-18 23:27:45299 * UI may have test data (e.g. credit card info) which shouldnt be in
300 the release build.
301 * UI may use EmbeddedTestServer.
taptedf38739a2017-02-09 04:34:52302 * Higher maintenance cost - cant leverage existing test harnesses.
303
304## Future Work
305
Peter Kastingcf49b7b792017-12-18 23:27:45306* Opt in more UI!
307 * Eventually, all of it.
taptedf38739a2017-02-09 04:34:52308
309* Automatically generate screenshots (for each platform, in various languages)
310 * Build upon [CL 2008283002](https://codereview.chromium.org/2008283002/)
311
312* (maybe) Try removing the subprocess
313 * Probably requires altering the browser_test suite code directly rather
314 than just adding a test case as in the current approach
315
Peter Kastingcf49b7b792017-12-18 23:27:45316* Find obscure workflows for invoking UI that has no test coverage and causes
317 crashes (e.g. [http://crrev.com/426302](http://crrev.com/426302))
taptedf38739a2017-02-09 04:34:52318 * Supporting window-modal dialogs with a null parent window.
319
320* Find memory leaks, e.g. [http://crrev.com/432320](http://crrev.com/432320)
321 * "Fix memory leak for extension uninstall dialog".
322
323## Appendix: Sample output
324
Peter Kastingcf49b7b792017-12-18 23:27:45325**$ ./out/gn_Debug/browser_tests --gtest_filter=BrowserUiTest.Invoke**
taptedf38739a2017-02-09 04:34:52326```
Peter Kastingcf49b7b792017-12-18 23:27:45327Note: Google Test filter = BrowserUiTest.Invoke
taptedf38739a2017-02-09 04:34:52328[==========] Running 1 test from 1 test case.
329[----------] Global test environment set-up.
Peter Kastingcf49b7b792017-12-18 23:27:45330[----------] 1 test from BrowserUiTest
331[ RUN ] BrowserUiTest.Invoke
332[26879:775:0207/134949.118352:30434675...:INFO:browser_ui_browsertest.cc(46)
333Pass one of the following after --ui=
334 AppInfoDialogBrowserTest.InvokeUi_default
335 AskGoogleForSuggestionsDialogTest.DISABLED_InvokeUi_default
336 BluetoothChooserBrowserTest.InvokeUi_ConnectedBubble
337 BluetoothChooserBrowserTest.InvokeUi_ConnectedModal
338/* and many more */
339[ OK ] BrowserUiTest.Invoke (0 ms)
340[----------] 1 test from BrowserUiTest (0 ms total)
taptedf38739a2017-02-09 04:34:52341[----------] Global test environment tear-down
342[==========] 1 test from 1 test case ran. (1 ms total)
343[ PASSED ] 1 test.
Peter Kastingcf49b7b792017-12-18 23:27:45344[1/1] BrowserUiTest.Invoke (334 ms)
taptedf38739a2017-02-09 04:34:52345SUCCESS: all tests passed.
346```
347
Peter Kastingcf49b7b792017-12-18 23:27:45348**$ ./out/gn_Debug/browser_tests --gtest_filter=BrowserUiTest.Invoke
349--ui=CardUnmaskPromptViewBrowserTest.InvokeUi_expired**
taptedf38739a2017-02-09 04:34:52350
351```
Peter Kastingcf49b7b792017-12-18 23:27:45352Note: Google Test filter = BrowserUiTest.Invoke
taptedf38739a2017-02-09 04:34:52353[==========] Running 1 test from 1 test case.
354[----------] Global test environment set-up.
Peter Kastingcf49b7b792017-12-18 23:27:45355[----------] 1 test from BrowserUiTest
356[ RUN ] BrowserUiTest.Invoke
taptedf38739a2017-02-09 04:34:52357Note: Google Test filter = CardUnmaskPromptViewBrowserTest.InvokeDefault
358[==========] Running 1 test from 1 test case.
359[----------] Global test environment set-up.
360[----------] 1 test from CardUnmaskPromptViewBrowserTest, where TypeParam =
Peter Kastingcf49b7b792017-12-18 23:27:45361[ RUN ] CardUnmaskPromptViewBrowserTest.InvokeUi_expired
taptedf38739a2017-02-09 04:34:52362/* 7 lines of uninteresting log spam */
Peter Kastingcf49b7b792017-12-18 23:27:45363[ OK ] CardUnmaskPromptViewBrowserTest.InvokeUi_expired (1324 ms)
taptedf38739a2017-02-09 04:34:52364[----------] 1 test from CardUnmaskPromptViewBrowserTest (1324 ms total)
365[----------] Global test environment tear-down
366[==========] 1 test from 1 test case ran. (1325 ms total)
367[ PASSED ] 1 test.
Peter Kastingcf49b7b792017-12-18 23:27:45368[ OK ] BrowserUiTest.Invoke (1642 ms)
369[----------] 1 test from BrowserUiTest (1642 ms total)
taptedf38739a2017-02-09 04:34:52370[----------] Global test environment tear-down
371[==========] 1 test from 1 test case ran. (1642 ms total)
372[ PASSED ] 1 test.
Peter Kastingcf49b7b792017-12-18 23:27:45373[1/1] BrowserUiTest.Invoke (2111 ms)
taptedf38739a2017-02-09 04:34:52374SUCCESS: all tests passed.
375```
376
Peter Kastingcf49b7b792017-12-18 23:27:45377**$ ./out/gn_Debug/browser_tests --gtest_filter=BrowserUiTest.Invoke
Peter Boströme2732ef2018-02-21 21:53:24378--ui=CardUnmaskPromptViewBrowserTest.InvokeUi_expired
379--test-launcher-interactive**
taptedf38739a2017-02-09 04:34:52380```
381/*
382 * Output as above, except the test are not interleaved, and the browser window
Peter Kastingcf49b7b792017-12-18 23:27:45383 * should remain open until the UI is dismissed
taptedf38739a2017-02-09 04:34:52384 */
385```
386
Peter Kastingcf49b7b792017-12-18 23:27:45387[chrome/browser/ui/test/test_browser_ui.h]: https://cs.chromium.org/chromium/src/chrome/browser/ui/test/test_browser_ui.h
taptedf38739a2017-02-09 04:34:52388[chrome/browser/ui/test/test_browser_dialog.h]: https://cs.chromium.org/chromium/src/chrome/browser/ui/test/test_browser_dialog.h
Peter Kastingcf49b7b792017-12-18 23:27:45389[chrome/browser/ui/ask_google_for_suggestions_dialog_browsertest.cc]: https://cs.chromium.org/chromium/src/chrome/browser/ui/ask_google_for_suggestions_dialog_browsertest.cc?l=18&q=ShowUi
390[chrome/browser/infobars/infobars_browsertest.cc]: https://cs.chromium.org/chromium/src/chrome/browser/infobars/infobars_browsertest.cc?l=134&q=UiBrowserTest
taptedf38739a2017-02-09 04:34:52391[ExtensionBrowserTest]: https://cs.chromium.org/chromium/src/chrome/browser/extensions/extension_browsertest.h?q=extensionbrowsertest&l=40