Commit 3d1e7a12 authored by Sergei Datsenko's avatar Sergei Datsenko Committed by Commit Bot

Add virtualtime support to render tests.

Clean up render tests structure and require virtual time exhaustion as
an indication of rendering completion.

Change-Id: I62a7ed9389facaab223ab3ddebb80496ece5c203
Reviewed-on: https://chromium-review.googlesource.com/776517Reviewed-by: default avatarAlex Clarke <alexclarke@chromium.org>
Commit-Queue: Sergei Datsenko <dats@chromium.org>
Cr-Commit-Position: refs/heads/master@{#517813}
parent a9230364
<html>
<body>
<script type="text/javascript">
setTimeout(() => {
var div = document.getElementById("content");
var p = document.createElement("p");
p.textContent = "delayed text";
div.appendChild(p);
}, 3000);
</script>
</body>
<div id="content"/>
</html>
......@@ -14,6 +14,10 @@
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#define HEADLESS_RENDER_BROWSERTEST(clazz) \
class HeadlessRenderBrowserTest##clazz : public clazz {}; \
HEADLESS_ASYNC_DEVTOOLED_TEST_F(HeadlessRenderBrowserTest##clazz)
namespace headless {
namespace {
......@@ -46,21 +50,6 @@ bool IsTag(const DOMNode& node) {
bool IsText(const DOMNode& node) {
return HasType(3, node);
}
bool IsTagWithName(const char* name, const DOMNode& node) {
return IsTag(node) && HasName(name, node);
}
std::vector<std::string> Structure(const GetSnapshotResult* snapshot) {
return ElementsView<std::string, DOMNode>(
*snapshot->GetDomNodes(), IsTag,
[](const auto& node) { return node.GetNodeName(); });
}
std::vector<std::string> Contents(const GetSnapshotResult* snapshot) {
return ElementsView<std::string, DOMNode>(
*snapshot->GetDomNodes(), IsText,
[](const auto& node) { return node.GetNodeValue(); });
}
std::vector<std::string> TextLayout(const GetSnapshotResult* snapshot) {
return ElementsView<std::string, LayoutTreeNode>(
......@@ -76,6 +65,13 @@ std::vector<const DOMNode*> FilterDOM(
*snapshot->GetDomNodes(), filter, [](const auto& n) { return &n; });
}
std::vector<const DOMNode*> FindTags(const GetSnapshotResult* snapshot,
const char* name = nullptr) {
return FilterDOM(snapshot, [name](const auto& n) {
return IsTag(n) && (!name || HasName(name, n));
});
}
size_t IndexInDOM(const GetSnapshotResult* snapshot, const DOMNode* node) {
for (size_t i = 0; i < snapshot->GetDomNodes()->size(); ++i) {
if (snapshot->GetDomNodes()->at(i).get() == node)
......@@ -113,57 +109,54 @@ using testing::StartsWith;
class HelloWorldTest : public HeadlessRenderTest {
private:
GURL GetPageUrl(HeadlessDevToolsClient* client) override {
return embedded_test_server()->GetURL("/hello.html");
return GetURL("/hello.html");
}
void VerifyDom(GetSnapshotResult* dom_snapshot) override {
EXPECT_THAT(Structure(dom_snapshot),
ElementsAre("HTML", "HEAD", "BODY", "H1"));
EXPECT_THAT(Contents(dom_snapshot),
ElementsAre("Hello headless world!", "\n"));
EXPECT_THAT(FindTags(dom_snapshot),
ElementsAre(NodeName("HTML"), NodeName("HEAD"),
NodeName("BODY"), NodeName("H1")));
EXPECT_THAT(
FilterDOM(dom_snapshot, IsText),
ElementsAre(NodeValue("Hello headless world!"), NodeValue("\n")));
EXPECT_THAT(TextLayout(dom_snapshot), ElementsAre("Hello headless world!"));
AllDone();
}
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(HelloWorldTest);
HEADLESS_RENDER_BROWSERTEST(HelloWorldTest);
class TimeoutTest : public HeadlessRenderTest {
private:
void OnPageRenderCompleted() override {
// Never complete.
}
GURL GetPageUrl(HeadlessDevToolsClient* client) override {
base::RunLoop run_loop;
client->GetPage()->Disable(run_loop.QuitClosure());
base::MessageLoop::ScopedNestableTaskAllower nest_loop(
base::MessageLoop::current());
run_loop.Run();
return embedded_test_server()->GetURL("/hello.html");
return GetURL("/hello.html");
}
void VerifyDom(GetSnapshotResult* dom_snapshot) override {
FAIL() << "Should not reach here";
}
void OnTimeout() override { AllDone(); }
void OnTimeout() override { SetTestCompleted(); }
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(TimeoutTest);
HEADLESS_RENDER_BROWSERTEST(TimeoutTest);
class JavaScriptOverrideTitle_JsEnabled : public HeadlessRenderTest {
private:
GURL GetPageUrl(HeadlessDevToolsClient* client) override {
return embedded_test_server()->GetURL(
"/render/javascript_override_title.html");
return GetURL("/render/javascript_override_title.html");
}
void VerifyDom(GetSnapshotResult* dom_snapshot) override {
auto dom = FilterDOM(
dom_snapshot, [](const auto& n) { return IsTagWithName("TITLE", n); });
auto dom = FindTags(dom_snapshot, "TITLE");
ASSERT_THAT(dom, ElementsAre(NodeName("TITLE")));
size_t pos = IndexInDOM(dom_snapshot, dom[0]);
const DOMNode* value = GetAt(dom_snapshot, pos + 1);
EXPECT_THAT(value, NodeValue("JavaScript is on"));
AllDone();
}
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(JavaScriptOverrideTitle_JsEnabled);
HEADLESS_RENDER_BROWSERTEST(JavaScriptOverrideTitle_JsEnabled);
class JavaScriptOverrideTitle_JsDisabled : public HeadlessRenderTest {
private:
......@@ -173,21 +166,18 @@ class JavaScriptOverrideTitle_JsDisabled : public HeadlessRenderTest {
}
GURL GetPageUrl(HeadlessDevToolsClient* client) override {
return embedded_test_server()->GetURL(
"/render/javascript_override_title.html");
return GetURL("/render/javascript_override_title.html");
}
void VerifyDom(GetSnapshotResult* dom_snapshot) override {
auto dom = FilterDOM(
dom_snapshot, [](const auto& n) { return IsTagWithName("TITLE", n); });
auto dom = FindTags(dom_snapshot, "TITLE");
ASSERT_THAT(dom, ElementsAre(NodeName("TITLE")));
size_t pos = IndexInDOM(dom_snapshot, dom[0]);
const DOMNode* value = GetAt(dom_snapshot, pos + 1);
EXPECT_THAT(value, NodeValue("JavaScript is off"));
AllDone();
}
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(JavaScriptOverrideTitle_JsDisabled);
HEADLESS_RENDER_BROWSERTEST(JavaScriptOverrideTitle_JsDisabled);
class JavaScriptConsoleErrors : public HeadlessRenderTest,
public runtime::ExperimentalObserver {
......@@ -204,7 +194,7 @@ class JavaScriptConsoleErrors : public HeadlessRenderTest,
base::MessageLoop::ScopedNestableTaskAllower nest_loop(
base::MessageLoop::current());
run_loop.Run();
return embedded_test_server()->GetURL("/render/console_errors.html");
return GetURL("/render/console_errors.html");
}
void OnConsoleAPICalled(
......@@ -229,9 +219,36 @@ class JavaScriptConsoleErrors : public HeadlessRenderTest,
StartsWith("Uncaught ReferenceError: func1"),
StartsWith("Uncaught ReferenceError: func2"),
StartsWith("Uncaught ReferenceError: func3")));
AllDone();
}
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(JavaScriptConsoleErrors);
HEADLESS_RENDER_BROWSERTEST(JavaScriptConsoleErrors);
class DelayedCompletion : public HeadlessRenderTest {
private:
base::TimeTicks start_;
GURL GetPageUrl(HeadlessDevToolsClient* client) override {
start_ = base::TimeTicks::Now();
return GetURL("/render/delayed_completion.html");
}
void VerifyDom(GetSnapshotResult* dom_snapshot) override {
base::TimeTicks end = base::TimeTicks::Now();
EXPECT_THAT(
FindTags(dom_snapshot),
ElementsAre(NodeName("HTML"), NodeName("HEAD"), NodeName("BODY"),
NodeName("SCRIPT"), NodeName("DIV"), NodeName("P")));
auto dom = FindTags(dom_snapshot, "P");
ASSERT_THAT(dom, ElementsAre(NodeName("P")));
size_t pos = IndexInDOM(dom_snapshot, dom[0]);
const DOMNode* value = GetAt(dom_snapshot, pos + 1);
EXPECT_THAT(value, NodeValue("delayed text"));
// The page delays output for 3 seconds. Due to virtual time this should
// take significantly less actual time.
base::TimeDelta passed = end - start_;
EXPECT_THAT(passed.InSecondsF(), testing::Le(2.9f));
}
};
HEADLESS_RENDER_BROWSERTEST(DelayedCompletion);
} // namespace headless
......@@ -6,30 +6,39 @@
#include "headless/public/devtools/domains/dom_snapshot.h"
#include "headless/public/headless_devtools_client.h"
#include "headless/public/util/virtual_time_controller.h"
#include "net/url_request/url_request.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace headless {
namespace {
void SetVirtualTimePolicyDoneCallback(
base::RunLoop* run_loop,
std::unique_ptr<emulation::SetVirtualTimePolicyResult>) {
run_loop->Quit();
}
} // namespace
HeadlessRenderTest::HeadlessRenderTest() : weak_ptr_factory_(this) {}
HeadlessRenderTest::~HeadlessRenderTest() {}
void HeadlessRenderTest::PostRunAsynchronousTest() {
// Make sure the test did complete.
EXPECT_TRUE(done_called_) << "The test did not finish. AllDone() not called.";
EXPECT_EQ(FINISHED, state_) << "The test did not finish.";
}
void HeadlessRenderTest::RunDevTooledTest() {
EXPECT_TRUE(embedded_test_server()->Start());
base::RunLoop run_loop;
virtual_time_controller_ =
std::make_unique<VirtualTimeController>(devtools_client_.get());
devtools_client_->GetPage()->GetExperimental()->AddObserver(this);
devtools_client_->GetNetwork()->GetExperimental()->AddObserver(this);
devtools_client_->GetPage()->Enable(run_loop.QuitClosure());
base::MessageLoop::ScopedNestableTaskAllower nest_loop(
base::MessageLoop::current());
run_loop.Run();
devtools_client_->GetNetwork()->Enable();
devtools_client_->GetPage()->Enable(Sync());
devtools_client_->GetNetwork()->Enable(Sync());
std::unique_ptr<headless::network::RequestPattern> match_all =
headless::network::RequestPattern::Builder().SetUrlPattern("*").Build();
......@@ -41,16 +50,34 @@ void HeadlessRenderTest::RunDevTooledTest() {
.Build());
GURL url = GetPageUrl(devtools_client_.get());
// Pause virtual time until we actually start loading content.
{
base::RunLoop run_loop;
devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
emulation::SetVirtualTimePolicyParams::Builder()
.SetPolicy(emulation::VirtualTimePolicy::PAUSE)
.Build(),
base::Bind(&SetVirtualTimePolicyDoneCallback, &run_loop));
base::MessageLoop::ScopedNestableTaskAllower nest_loop(
base::MessageLoop::current());
run_loop.Run();
}
state_ = STARTING;
devtools_client_->GetPage()->Navigate(url.spec());
browser()->BrowserMainThread()->PostDelayedTask(
FROM_HERE,
base::Bind(&HeadlessRenderTest::HandleTimeout,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(10));
// The caller will loop until FinishAsynchronousTest() is called either
// from OnGetDomSnapshotDone() or from HandleTimeout().
}
void HeadlessRenderTest::OnTimeout() {
FAIL() << "Renderer timeout";
ADD_FAILURE() << "Rendering timed out!";
}
void HeadlessRenderTest::CustomizeHeadlessBrowserContext(
......@@ -71,14 +98,15 @@ void HeadlessRenderTest::UrlRequestFailed(net::URLRequest* request,
bool canceled_by_devtools) {
if (canceled_by_devtools)
return;
FAIL() << "Network request failed: " << net_error << " for "
<< request->url().spec();
ADD_FAILURE() << "Network request failed: " << net_error << " for "
<< request->url().spec();
}
void HeadlessRenderTest::OnRequestIntercepted(
const network::RequestInterceptedParams& params) {
CHECK_NE(INIT, state_);
if (params.GetIsNavigationRequest())
navigation_requested_ = true;
navigation_performed_ = true;
requests_.push_back(params.Clone());
// Allow the navigation to proceed.
devtools_client_->GetNetwork()->GetExperimental()->ContinueInterceptedRequest(
......@@ -87,8 +115,33 @@ void HeadlessRenderTest::OnRequestIntercepted(
.Build());
}
void HeadlessRenderTest::OnLoadEventFired(
const page::LoadEventFiredParams& params) {
void HeadlessRenderTest::OnLoadEventFired(const page::LoadEventFiredParams&) {
CHECK_NE(INIT, state_);
if (state_ == LOADING || state_ == STARTING) {
state_ = RENDERING;
}
}
void HeadlessRenderTest::OnFrameStartedLoading(
const page::FrameStartedLoadingParams&) {
CHECK_NE(INIT, state_);
if (state_ == STARTING) {
state_ = LOADING;
virtual_time_controller_->GrantVirtualTimeBudget(
emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING, 5000,
base::Closure(),
base::Bind(&HeadlessRenderTest::HandleVirtualTimeExhausted,
weak_ptr_factory_.GetWeakPtr()));
}
}
void HeadlessRenderTest::OnPageRenderCompleted() {
CHECK(navigation_performed_);
CHECK_GE(state_, LOADING);
if (state_ >= DONE)
return;
state_ = DONE;
devtools_client_->GetDOMSnapshot()->GetExperimental()->GetSnapshot(
dom_snapshot::GetSnapshotParams::Builder()
.SetComputedStyleWhitelist(std::vector<std::string>())
......@@ -97,19 +150,25 @@ void HeadlessRenderTest::OnLoadEventFired(
weak_ptr_factory_.GetWeakPtr()));
}
void HeadlessRenderTest::HandleVirtualTimeExhausted() {
if (state_ < DONE) {
OnPageRenderCompleted();
}
}
void HeadlessRenderTest::OnGetDomSnapshotDone(
std::unique_ptr<dom_snapshot::GetSnapshotResult> result) {
EXPECT_TRUE(navigation_requested_);
CHECK_EQ(DONE, state_);
state_ = FINISHED;
FinishAsynchronousTest();
VerifyDom(result.get());
if (done_called_)
FinishAsynchronousTest();
}
void HeadlessRenderTest::HandleTimeout() {
if (!done_called_)
if (state_ != FINISHED) {
FinishAsynchronousTest();
OnTimeout();
EXPECT_TRUE(done_called_);
FinishAsynchronousTest();
}
}
} // namespace headless
......@@ -9,6 +9,7 @@
#include <string>
#include "base/macros.h"
#include "headless/public/devtools/domains/emulation.h"
#include "headless/public/devtools/domains/network.h"
#include "headless/public/devtools/domains/page.h"
#include "headless/public/headless_browser.h"
......@@ -19,6 +20,7 @@
namespace headless {
class HeadlessDevToolsClient;
class VirtualTimeController;
namespace dom_snapshot {
class GetSnapshotResult;
} // namespace dom_snapshot
......@@ -32,16 +34,39 @@ class HeadlessRenderTest : public HeadlessAsyncDevTooledBrowserTest,
void RunDevTooledTest() override;
protected:
// Automatically waits in destructor until callback is called.
class Sync {
public:
Sync() {}
~Sync() {
base::MessageLoop::ScopedNestableTaskAllower nest_loop(
base::MessageLoop::current());
run_loop.Run();
}
operator base::Closure() { return run_loop.QuitClosure(); }
private:
base::RunLoop run_loop;
DISALLOW_COPY_AND_ASSIGN(Sync);
};
HeadlessRenderTest();
~HeadlessRenderTest() override;
void SetTestCompleted() { state_ = FINISHED; }
GURL GetURL(const std::string& path) const {
return embedded_test_server()->GetURL(path);
}
void PostRunAsynchronousTest() override;
virtual GURL GetPageUrl(HeadlessDevToolsClient* client) = 0;
virtual void VerifyDom(dom_snapshot::GetSnapshotResult* dom_snapshot) = 0;
virtual void OnPageRenderCompleted();
virtual void OnTimeout();
virtual void OverrideWebPreferences(WebPreferences* preferences);
void AllDone() { done_called_ = true; }
void CustomizeHeadlessBrowserContext(
HeadlessBrowserContext::Builder& builder) override;
......@@ -53,6 +78,8 @@ class HeadlessRenderTest : public HeadlessAsyncDevTooledBrowserTest,
// page::ExperimentalObserver implementation:
void OnLoadEventFired(const page::LoadEventFiredParams& params) override;
void OnFrameStartedLoading(
const page::FrameStartedLoadingParams& params) override;
// network::ExperimentalObserver
void OnRequestIntercepted(
......@@ -61,12 +88,23 @@ class HeadlessRenderTest : public HeadlessAsyncDevTooledBrowserTest,
std::vector<std::unique_ptr<network::RequestInterceptedParams>> requests_;
private:
void HandleVirtualTimeExhausted();
void OnGetDomSnapshotDone(
std::unique_ptr<dom_snapshot::GetSnapshotResult> result);
void HandleTimeout();
bool navigation_requested_ = false;
bool done_called_ = false;
enum State {
INIT, // Setting up the client, no navigation performed yet.
STARTING, // Navigation request issued but URL not being loaded yet.
LOADING, // URL was requested but resources are not fully loaded yet.
RENDERING, // Main resources were loaded but page is still being rendered.
DONE, // Page considered to be fully rendered.
FINISHED, // Test has finished.
};
State state_ = INIT;
std::unique_ptr<VirtualTimeController> virtual_time_controller_;
bool navigation_performed_ = false;
base::WeakPtrFactory<HeadlessRenderTest> weak_ptr_factory_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment