|
| 1 | +# Testing Chrome browser dialogs with TestBrowserDialog |
| 2 | + |
| 3 | +\#include "[chrome/browser/ui/test/test_browser_dialog.h]" |
| 4 | + |
| 5 | +`TestBrowserDialog` provides a way to register an `InProcessBrowserTest` testing |
| 6 | +harness with a framework that invokes Chrome browser dialogs in a consistent |
| 7 | +way. It optionally provides a way to invoke dialogs "interactively". This allows |
| 8 | +screenshots to be generated easily, with the same test data, to assist with UI |
| 9 | +review. It also provides a registry of dialogs so they can be systematically |
| 10 | +checked for subtle changes and regressions. |
| 11 | + |
| 12 | +[TOC] |
| 13 | + |
| 14 | +## How to register a dialog |
| 15 | + |
| 16 | +If registering an existing dialog, there's a chance it already has a test |
| 17 | +harness inheriting, using, or with `typedef InProcessBrowserTest` (or a |
| 18 | +descendant of it). If so, using `TestBrowserDialog` is straightforward. Assume |
| 19 | +the existing InProcessBrowserTest is in `foo_dialog_browsertest.cc`: |
| 20 | + |
| 21 | + class FooDialogTest : public InProcessBrowserTest { ... |
| 22 | + |
| 23 | +Change this to inherit from DialogBrowserTest, and override |
| 24 | +`ShowDialog(std::string)`. See [Advanced Usage](#Advanced-Usage) for details. |
| 25 | + |
| 26 | +```cpp |
| 27 | +class FooDialogTest : public DialogBrowserTest { |
| 28 | + public: |
| 29 | + .. |
| 30 | + // DialogBrowserTest: |
| 31 | + void ShowDialog(const std::string& name) override { |
| 32 | + /* Show dialog attached to browser() and leave it open. */ |
| 33 | + } |
| 34 | + .. |
| 35 | +}; |
| 36 | +``` |
| 37 | +
|
| 38 | +Then add test invocations using the usual GTest macros, in |
| 39 | +`foo_dialog_browsertest.cc`: |
| 40 | +
|
| 41 | +```cpp |
| 42 | +IN_PROC_BROWSER_TEST_F(FooDialogTest, InvokeDialog_default) { |
| 43 | + RunDialog(); |
| 44 | +} |
| 45 | +``` |
| 46 | + |
| 47 | +Notes: |
| 48 | + |
| 49 | +* The body of the test is always just "`RunDialog();`". |
| 50 | +* "`default`" is the `std::string` passed to `ShowDialog()` and can be |
| 51 | + customized. See |
| 52 | + [Testing additional dialog "styles"](#Testing-additional-dialog-styles_). |
| 53 | +* The text before `default` (in this case) must always be "`InvokeDialog_`". |
| 54 | + |
| 55 | +### Concrete examples |
| 56 | + |
| 57 | +* [chrome/browser/ui/ask_google_for_suggestions_dialog_browsertest.cc] |
| 58 | +* [chrome/browser/ui/autofill/card_unmask_prompt_view_browsertest.cc] |
| 59 | +* [chrome/browser/ui/collected_cookies_browsertest.cc] |
| 60 | +* [chrome/browser/ui/update_chrome_dialog_browsertest.cc] |
| 61 | + |
| 62 | +## Running the tests |
| 63 | + |
| 64 | +List the available dialogs with |
| 65 | + |
| 66 | + $ ./browser_tests --gtest_filter=BrowserDialogTest.Invoke |
| 67 | + |
| 68 | +E.g. `FooDialogTest.InvokeDialog_default` should be listed. To show the dialog |
| 69 | +interactively, run |
| 70 | + |
| 71 | + $ ./browser_tests --gtest_filter=BrowserDialogTest.Invoke --interactive \ |
| 72 | + --dialog=FooDialogTest.InvokeDialog_default |
| 73 | + |
| 74 | +### Implementation |
| 75 | + |
| 76 | +`BrowserDialogTest.Invoke` searches for gtests that have "`InvokeDialog_`" in |
| 77 | +their name, so they can be collected in a list. Providing a `--dialog` argument |
| 78 | +will invoke that test case in a subprocess. Including `--interactive` will set |
| 79 | +up an environment for that subprocess that allows interactivity, e.g., to take |
| 80 | +screenshots. The test ends once the dialog is dismissed. |
| 81 | + |
| 82 | +The `FooDialogTest.InvokeDialog_default` test case **will still be run in the |
| 83 | +usual browser_tests test suite**. Ensure it passes, and isn’t flaky. This will |
| 84 | +give your dialog some regression test coverage. `RunDialog()` checks to ensure a |
| 85 | +dialog is actually created when it invokes `ShowDialog("default")`. |
| 86 | + |
| 87 | +### BrowserDialogTest.Invoke |
| 88 | + |
| 89 | +This is also run in browser_tests but, when run that way, the test case just |
| 90 | +lists the registered test harnesses (it does *not* iterate over them). A |
| 91 | +subprocess is never created unless --dialog is passed on the command line. |
| 92 | + |
| 93 | +## Advanced Usage |
| 94 | + |
| 95 | +If your test harness inherits from a descendant of `InProcessBrowserTest` (one |
| 96 | +example: [ExtensionBrowserTest]) then the `SupportsTestDialog<>` template is |
| 97 | +provided. E.g. |
| 98 | + |
| 99 | +```cpp |
| 100 | +class ExtensionInstallDialogViewTestBase : public ExtensionBrowserTest { ... |
| 101 | +``` |
| 102 | +
|
| 103 | +becomes |
| 104 | +
|
| 105 | +```cpp |
| 106 | +class ExtensionInstallDialogViewTestBase : |
| 107 | + public SupportsTestDialog<ExtensionBrowserTest> { ... |
| 108 | +``` |
| 109 | + |
| 110 | +### Testing additional dialog "styles" |
| 111 | + |
| 112 | +Add additional test cases, with a different string after "`InvokeDialog_`". |
| 113 | +Example: |
| 114 | + |
| 115 | +```cpp |
| 116 | +IN_PROC_BROWSER_TEST_F(CardUnmaskViewBrowserTest, InvokeDialog_expired) { |
| 117 | + RunDialog(); |
| 118 | +} |
| 119 | + |
| 120 | +IN_PROC_BROWSER_TEST_F(CardUnmaskViewBrowserTest, InvokeDialog_valid) { |
| 121 | + RunDialog(); |
| 122 | +} |
| 123 | +``` |
| 124 | +
|
| 125 | +The strings "`expired`" or “`valid`” will be given as arguments to |
| 126 | +`ShowDialog(std::string)`. |
| 127 | +
|
| 128 | +## Rationale |
| 129 | +
|
| 130 | +Bug reference: [Issue 654151](http://crbug.com/654151). |
| 131 | +
|
| 132 | +Chrome has a lot of browser dialogs; often for obscure use-cases and often hard |
| 133 | +to invoke. It has traditionally been difficult to be systematic while checking |
| 134 | +dialogs for possible regressions. For example, to investigate changes to shared |
| 135 | +layout parameters which are testable only with visual inspection. |
| 136 | +
|
| 137 | +For Chrome UI review, screenshots need to be taken. Iterating over all the |
| 138 | +"styles" that a dialog may appear with is fiddly. E.g. a login or particular web |
| 139 | +server setup may be required. It’s important to provide a consistent “look” for |
| 140 | +UI review (e.g. same test data, same browser size, anchoring position, etc.). |
| 141 | +
|
| 142 | +Some dialogs lack tests. Some dialogs have zero coverage on the bots. Dialogs |
| 143 | +can have tricky lifetimes and common mistakes are repeated. TestBrowserDialog |
| 144 | +runs simple "Show dialog" regression tests and can be extended to do more. |
| 145 | +
|
| 146 | +Even discovering the full set of dialogs present for each platform in Chrome is |
| 147 | +[difficult](http://crbug.com/686239). |
| 148 | +
|
| 149 | +### Why browser_tests? |
| 150 | +
|
| 151 | +* `browser_tests` already provides a `browser()->window()` of a consistent |
| 152 | + size that can be used as a dialog anchor and to take screenshots for UI |
| 153 | + review. |
| 154 | + * UI review have requested that screenshots be provided with the entire |
| 155 | + browser window so that the relative size of the dialog/change under |
| 156 | + review can be assessed. |
| 157 | +
|
| 158 | +* Some dialogs already have a test harness with appropriate setup (e.g. test |
| 159 | + data) running in browser_tests. |
| 160 | + * Supporting `BrowserDialogTest` should require minimal setup and minimal |
| 161 | + ongoing maintenance. |
| 162 | +
|
| 163 | +* An alternative is to maintain a working end-to-end build target executable |
| 164 | + to do this, but this has additional costs (and is hard). |
| 165 | + * E.g. setup/teardown of low-level functions ( |
| 166 | + `InitializeGLOneOffPlatform()`, |
| 167 | + `MaterialDesignController::Initialize()`, etc.). |
| 168 | +
|
| 169 | +* Why not chrome.exe? |
| 170 | + * E.g. a scrappy chrome:// page with links to invoke dialogs would be |
| 171 | + great! |
| 172 | + * But... |
| 173 | + * A dialog may have test data (e.g. credit card info) which shouldn’t |
| 174 | + be in the release build. |
| 175 | + * A dialog may use EmbeddedTestServer. |
| 176 | + * Higher maintenance cost - can’t leverage existing test harnesses. |
| 177 | +
|
| 178 | +## Future Work |
| 179 | +
|
| 180 | +* Opt in more dialogs! |
| 181 | + * Eventually, all of them. |
| 182 | + * A `BrowserDialogTest` for every descendant of `views::DialogDelegate`. |
| 183 | +
|
| 184 | +* Automatically generate screenshots (for each platform, in various languages) |
| 185 | + * Build upon [CL 2008283002](https://codereview.chromium.org/2008283002/) |
| 186 | +
|
| 187 | +* (maybe) Try removing the subprocess |
| 188 | + * Probably requires altering the browser_test suite code directly rather |
| 189 | + than just adding a test case as in the current approach |
| 190 | +
|
| 191 | +* An automated test suite for dialogs |
| 192 | + * Test various ways to dismiss or hide a dialog |
| 193 | + * e.g. native close (via taskbar?) |
| 194 | + * close parent window (possibly via task bar) |
| 195 | + * close parent tab |
| 196 | + * switch tabs |
| 197 | + * close via `DialogClientView::AcceptWindow` (and `CancelWindow`) |
| 198 | + * close via `Widget::Close` |
| 199 | + * close via `Widget::CloseNow` |
| 200 | + * Drag tab off browser into a new window |
| 201 | + * Fullscreen that may create a new window/parent |
| 202 | +
|
| 203 | +* Find obscure workflows for invoking dialogs that have no test coverage and |
| 204 | + cause crashes (e.g. [http://crrev.com/426302](http://crrev.com/426302)) |
| 205 | + * Supporting window-modal dialogs with a null parent window. |
| 206 | +
|
| 207 | +* Find memory leaks, e.g. [http://crrev.com/432320](http://crrev.com/432320) |
| 208 | + * "Fix memory leak for extension uninstall dialog". |
| 209 | +
|
| 210 | +## Appendix: Sample output |
| 211 | +
|
| 212 | +**$ ./out/gn_Debug/browser_tests --gtest_filter=BrowserDialogTest.Invoke** |
| 213 | +``` |
| 214 | +Note: Google Test filter = BrowserDialogTest.Invoke |
| 215 | +[==========] Running 1 test from 1 test case. |
| 216 | +[----------] Global test environment set-up. |
| 217 | +[----------] 1 test from BrowserDialogTest |
| 218 | +[ RUN ] BrowserDialogTest.Invoke |
| 219 | +[26879:775:0207/134949.118352:30434675...:INFO:browser_dialog_browsertest.cc(46) |
| 220 | +Pass one of the following after --dialog= |
| 221 | + AskGoogleForSuggestionsDialogTest.InvokeDialog_default |
| 222 | + CardUnmaskPromptViewBrowserTest.InvokeDialog_expired |
| 223 | + CardUnmaskPromptViewBrowserTest.InvokeDialog_valid |
| 224 | + CollectedCookiesTestMd.InvokeDialog_default |
| 225 | + UpdateRecommendedDialogTest.InvokeDialog_default |
| 226 | +/* lots more will eventually be listed here */ |
| 227 | +[ OK ] BrowserDialogTest.Invoke (0 ms) |
| 228 | +[----------] 1 test from BrowserDialogTest (0 ms total) |
| 229 | +[----------] Global test environment tear-down |
| 230 | +[==========] 1 test from 1 test case ran. (1 ms total) |
| 231 | +[ PASSED ] 1 test. |
| 232 | +[1/1] BrowserDialogTest.Invoke (334 ms) |
| 233 | +SUCCESS: all tests passed. |
| 234 | +``` |
| 235 | +
|
| 236 | +**$ ./out/gn_Debug/browser_tests --gtest_filter=BrowserDialogTest.Invoke |
| 237 | +--dialog=CardUnmaskPromptViewBrowserTest.InvokeDialog_expired** |
| 238 | +
|
| 239 | +``` |
| 240 | +Note: Google Test filter = BrowserDialogTest.Invoke |
| 241 | +[==========] Running 1 test from 1 test case. |
| 242 | +[----------] Global test environment set-up. |
| 243 | +[----------] 1 test from BrowserDialogTest |
| 244 | +[ RUN ] BrowserDialogTest.Invoke |
| 245 | +Note: Google Test filter = CardUnmaskPromptViewBrowserTest.InvokeDefault |
| 246 | +[==========] Running 1 test from 1 test case. |
| 247 | +[----------] Global test environment set-up. |
| 248 | +[----------] 1 test from CardUnmaskPromptViewBrowserTest, where TypeParam = |
| 249 | +[ RUN ] CardUnmaskPromptViewBrowserTest.InvokeDialog_expired |
| 250 | +/* 7 lines of uninteresting log spam */ |
| 251 | +[ OK ] CardUnmaskPromptViewBrowserTest.InvokeDialog_expired (1324 ms) |
| 252 | +[----------] 1 test from CardUnmaskPromptViewBrowserTest (1324 ms total) |
| 253 | +[----------] Global test environment tear-down |
| 254 | +[==========] 1 test from 1 test case ran. (1325 ms total) |
| 255 | +[ PASSED ] 1 test. |
| 256 | +[ OK ] BrowserDialogTest.Invoke (1642 ms) |
| 257 | +[----------] 1 test from BrowserDialogTest (1642 ms total) |
| 258 | +[----------] Global test environment tear-down |
| 259 | +[==========] 1 test from 1 test case ran. (1642 ms total) |
| 260 | +[ PASSED ] 1 test. |
| 261 | +[1/1] BrowserDialogTest.Invoke (2111 ms) |
| 262 | +SUCCESS: all tests passed. |
| 263 | +``` |
| 264 | +
|
| 265 | +**$ ./out/gn_Debug/browser_tests --gtest_filter=BrowserDialogTest.Invoke |
| 266 | +--dialog=CardUnmaskPromptViewBrowserTest.InvokeDialog_expired --interactive** |
| 267 | +``` |
| 268 | +/* |
| 269 | + * Output as above, except the test are not interleaved, and the browser window |
| 270 | + * should remain open until the dialog is dismissed |
| 271 | + */ |
| 272 | +``` |
| 273 | +
|
| 274 | +[chrome/browser/ui/test/test_browser_dialog.h]: https://cs.chromium.org/chromium/src/chrome/browser/ui/test/test_browser_dialog.h |
| 275 | +[chrome/browser/ui/autofill/card_unmask_prompt_view_browsertest.cc]: https://cs.chromium.org/chromium/src/chrome/browser/ui/autofill/card_unmask_prompt_view_browsertest.cc?l=104&q=ShowDialog |
| 276 | +[chrome/browser/ui/collected_cookies_browsertest.cc]: https://cs.chromium.org/chromium/src/chrome/browser/ui/collected_cookies_browsertest.cc?l=26&q=ShowDialog |
| 277 | +[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=ShowDialog |
| 278 | +[chrome/browser/ui/update_chrome_dialog_browsertest.cc]: https://cs.chromium.org/chromium/src/chrome/browser/ui/update_chrome_dialog_browsertest.cc?l=15&q=ShowDialog |
| 279 | +[ExtensionBrowserTest]: https://cs.chromium.org/chromium/src/chrome/browser/extensions/extension_browsertest.h?q=extensionbrowsertest&l=40 |
0 commit comments