1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225

content / public / test / README.md [blame]

# Content Browser Test Tips

A random collection of useful things to know when writing browser tests.

[TOC]

## Executing JavaScript

If the test needs to use the return value of the script, use `EvalJs()`:

```c++
  // Works with numerical types...
  EXPECT_EQ(0, EvalJs(shell(), "1 * 0");

  // ... string types ...
  EXPECT_EQ("BODY", EvalJs(shell(), "document.body.tagName");

  // and booleans too. Note the explicit use of EXPECT_EQ() instead of
  // EXPECT_TRUE() or EXPECT_FALSE(); this is intentional, and the latter
  // will not compile.
  EXPECT_EQ(false, EvalJs(shell(), "2 + 2 == 5"));
```

Like many other test helpers (e.g. the navigation helpers), the first argument
accepts `RenderFrameHost`, `WebContents`, and other types.

```c++
  // Executes in the main frame.
  EXPECT_EQ(true, EvalJs(shell()->web_contents(), "window.top == window"));

  // Also executes in the main frame.
  EXPECT_EQ(true, EvalJs(shell(), "window.top == window"));

  // Executes in the first child frame of the main frame.
  EXPECT_EQ(
      false,
      EvalJs(ChildFrameAt(shell()->web_contents()->GetPrimaryMainFrame(), 0),
             "window.top == window"));
```

Otherwise, simply use `ExecJs()`:

```c++
  EXPECT_TRUE(ExecJs("console.log('Hello world!')"));
```

Note that these helpers block until the script completes. For async
execution, use `ExecuteScriptAsync()`.

Finally, `JsReplace()` provides a convenient way to build strings for script
execution:

```c++
  EXPECT_EQ("00", EvalJs(JsReplace("$1 + $2", 0, "0")));
```

## Simulating Input

A wide range of methods are provided to simulate input such as clicks, touch,
mouse moves and so on. Many reside in
https://source.chromium.org/chromium/chromium/src/+/main:content/public/test/browser_test_utils.h.

When using input in tests, be aware that the renderer drops all input
received when the main frame is not being updated or rendered immediately
after load. There are three ways, in order of preference, to ensure that
the input will be processed. Use these when your test input seems to be having
no effect:

* Use the 'WaitForHitTestData method' from
  `content/public/test/hit_test_region_observer.h`

* Include visible text in the web contents you are interacting with.

* Add 'blink::switches::kAllowPreCommitInput' as a command line flag.

## Cross-origin navigations

For cross-origin navigations, it is to simplest to configure all hostnames to
resolve to `127.0.0.1` in tests, using a snippet like this:

```c++
  void SetUpOnMainThread() override {
    host_resolver()->AddRule("*", "127.0.0.1");
    ASSERT_TRUE(embedded_test_server()->Start());
  }
```

After that, `EmbeddedTestServer::GetURL()` can be used to generate navigable
URLs with the specific origin:

```c++
  const GURL& url_a = embedded_test_server()->GetURL("a.com", "/title1.html");
  const GURL& url_b = embedded_test_server()->GetURL("b.com", "/empty.html");
```

Most test resources are located in `//content/test/data`, e.g. navigating to
`GetURL("a.com", "/title1.html")` will serve `//content/test/data/title1.html`
as the content.

## Browser-initiated navigation to a specific origin

> **Note:** using arbitrary hostnames requires the [host resolver to
> be correctly configured][host-resolver-config].

`NavigateToURL()` begins and waits for the navigation to complete, as if the
navigation was browser-initiated, e.g. from the omnibox:

```c++
  GURL url(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
```

> **Note**: NavigateToURL() allows subframes to be targetted, but outside of history
> navigations, subframe navigations are generally _renderer-initiated_.

## Renderer-initiated navigation to a specific origin

> **Note:** using arbitrary hostnames requires the [host resolver to
> be correctly configured][host-resolver-config].

`NavigateToURLFromRenderer()` begins and waits for the navigation to complete,
as if the navigation was renderer-initiated, e.g. by setting `window.location`:

```c++
  // Navigates the main frame.
  GURL url_1(embedded_test_server()->GetURL("a.com", "/title1.html"));
  EXPECT_TRUE(NavigateToURLFromRenderer(shell()->web_contents(), url_1));

  // Navigates the main frame too.
  GURL url_2(embedded_test_server()->GetURL("b.com", "/page_with_iframe.html"));
  EXPECT_TRUE(NavigateToURLFromRenderer(shell()->web_contents(), url_2));

  // Navigates the first child frame.
  GURL url_3(embedded_test_server()->GetURL("a.com", "/empty.html"));
  EXPECT_TRUE(
      NavigateToURLFromRenderer(
          ChildFrameAt(shell()->web_contents()->GetPrimaryMainFrame(), 0),
          url_3));
```

## Dynamically generating a page with iframes

> **Note:** using arbitrary hostnames requires the [host resolver to
> be correctly configured][host-resolver-config].

[`cross_site_iframe_factory.html`][cross-site-iframe-factory] is a helper that
makes it easy to generate a page with an arbitrary frame tree by navigating to
a URL. The query string to the URL allows configuration of the frame tree, the
origin of each frame, and a number of other options:

```c++
  GURL url(embedded_test_server()->GetURL(
      "a.com", "/cross_site_iframe_factory.html?a(b(a),c,example.com)"));
  EXPECT_TRUE(NavigateToURL(shell(), url));
```

Will generate a page with:

```
Main frame with origin `a.com`
  ├─ Child frame #1 with origin `b.com`
  │    └─ Grandchild frame with origin `a.com`
  ├─ Child frame #2 with origin `c.com`
  └─ Child frame #3 with origin `example.com`
```

## Embedding an `<iframe>` with a specific origin

> **Note:** using arbitrary hostnames requires the [host resolver to
> be correctly configured][host-resolver-config].

Sometimes, a test page may need to embed a cross-origin URL. This is
problematic for pages that contain only static HTML, as the embedded test
server runs on a randomly selected port. Instead, static HTML can use the
cross-site redirector to generate a cross-origin frame:

```html
<!-- static_iframe.html -->
<html>
  <body>
    <iframe src="/cross-site/b.com/title1.html">
  </body>
</iframe>
```

**Important**: the cross-site redirector is not enabled by default.
Override `SetUpOnMainThread()` to configure it like this:

```c++
  void SetUpOnMainThread() override {
    ...
    SetupCrossSiteRedirector(embedded_test_server());
    ...
  }
```

## Simulating a slow load

Navigates to a page that takes 60 seconds to load.

```c++
  GURL url(embedded_test_server()->GetURL("/slow?60");
  EXPECT_TRUE(NavigateToURL(shell(), url));
```

The embedded test server also registers [other default
handlers][test-server-default-handlers] that may be useful.

## Simulating a failed navigation

Note that this is distinct from a navigation that results in an HTTP error,
since those navigations still load arbitrary HTML from the server-supplied error
page; a failed navigation is one that results in committing a Chrome-supplied
error page, i.e. `RenderFrameHost::IsErrorDocument()` returns `true`.

```c++
  GURL url = embedded_test_server()->GetURL("/title1.html");
  std::unique_ptr<URLLoaderInterceptor> url_interceptor =
      URLLoaderInterceptor::SetupRequestFailForURL(url, net::ERR_DNS_TIMED_OUT);
  EXPECT_FALSE(NavigateToURLFromRenderer(shell()->web_contents(), url));
```

[host-resolver-config]: README.md#Cross_origin-navigations
[cross-site-iframe-factory]: https://source.chromium.org/chromium/chromium/src/+/main:content/test/data/cross_site_iframe_factory.html
[test-server-default-handlers]: https://source.chromium.org/chromium/chromium/src/+/main:net/test/embedded_test_server/default_handlers.cc