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():
// 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.
// 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():
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:
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:
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:
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.
NavigateToURL() begins and waits for the navigation to complete, as if the
navigation was browser-initiated, e.g. from the omnibox:
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.
NavigateToURLFromRenderer() begins and waits for the navigation to complete,
as if the navigation was renderer-initiated, e.g. by setting window.location:
// 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.
cross_site_iframe_factory.html 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:
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.
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:
<!-- 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:
void SetUpOnMainThread() override {
...
SetupCrossSiteRedirector(embedded_test_server());
...
}
Simulating a slow load
Navigates to a page that takes 60 seconds to load.
GURL url(embedded_test_server()->GetURL("/slow?60");
EXPECT_TRUE(NavigateToURL(shell(), url));
The embedded test server also registers other 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.
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));