Commit 9dd28324 authored by Pavel Feldman's avatar Pavel Feldman Committed by Commit Bot

Headless: introduce protocol test harness for nice and elegant headless testing

Change-Id: Ie171eb4fdddda28136f287d22a8de451a7ce9482
Reviewed-on: https://chromium-review.googlesource.com/1065495
Commit-Queue: Pavel Feldman <pfeldman@chromium.org>
Reviewed-by: default avatarAlex Clarke <alexclarke@chromium.org>
Reviewed-by: default avatarEric Seckler <eseckler@chromium.org>
Reviewed-by: default avatarDmitry Gozman <dgozman@chromium.org>
Cr-Commit-Position: refs/heads/master@{#560024}
parent 4a6e4e7b
......@@ -759,6 +759,7 @@ test("headless_browsertests") {
"public/util/testing/test_in_memory_protocol_handler.h",
"test/headless_browser_test.cc",
"test/headless_browser_test.h",
"test/headless_protocol_browsertest.cc",
"test/headless_test_launcher.cc",
"test/test_protocol_handler.cc",
"test/test_protocol_handler.h",
......
......@@ -83,56 +83,6 @@ class VirtualTimeBrowserTest : public HeadlessAsyncDevTooledBrowserTest,
bool runtime_enabled = false;
};
class VirtualTimeObserverTest : public VirtualTimeBrowserTest {
public:
VirtualTimeObserverTest() {
EXPECT_TRUE(embedded_test_server()->Start());
SetInitialURL(
embedded_test_server()->GetURL("/virtual_time_test.html").spec());
}
// emulation::Observer implementation:
void OnVirtualTimeBudgetExpired(
const emulation::VirtualTimeBudgetExpiredParams& params) override {
std::vector<std::string> expected_log = {"Paused @ 0ms",
"Advanced to 10ms",
"step1",
"Advanced to 110ms",
"step2",
"Paused @ 110ms",
"Advanced to 210ms",
"step3",
"Paused @ 210ms",
"Advanced to 310ms",
"step4",
"pass"};
// Note after the PASS step there are a number of virtual time advances, but
// this list seems to be non-deterministic because there's all sorts of
// timers in the system. We don't really care about that here.
ASSERT_GE(log_.size(), expected_log.size());
for (size_t i = 0; i < expected_log.size(); i++) {
EXPECT_EQ(expected_log[i], log_[i]) << "At index " << i;
}
EXPECT_THAT(log_, Contains("Advanced to 5000ms"));
EXPECT_THAT(log_, Contains("Paused @ 5000ms"));
FinishAsynchronousTest();
}
void OnVirtualTimeAdvanced(
const emulation::VirtualTimeAdvancedParams& params) override {
log_.push_back(base::StringPrintf("Advanced to %.fms",
params.GetVirtualTimeElapsed()));
}
void OnVirtualTimePaused(
const emulation::VirtualTimePausedParams& params) override {
log_.push_back(
base::StringPrintf("Paused @ %.fms", params.GetVirtualTimeElapsed()));
}
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeObserverTest);
class VirtualTimeBaseTest : public VirtualTimeBrowserTest {
public:
VirtualTimeBaseTest() {
......@@ -689,64 +639,6 @@ class PendingScriptVirtualTimeTest : public VirtualTimeBrowserTest {
HEADLESS_ASYNC_DEVTOOLED_TEST_F(PendingScriptVirtualTimeTest);
namespace {
static constexpr char kResourceErrorLoop[] = R"(
<html>
<script>
var counter = 1;
</script>
<img src="1" onerror="this.src='' + ++counter;">
</html>
)";
}
class VirtualTimeAndResourceErrorLoopTest : public VirtualTimeBrowserTest {
public:
VirtualTimeAndResourceErrorLoopTest() { SetInitialURL("http://foo.com/"); }
ProtocolHandlerMap GetProtocolHandlers() override {
ProtocolHandlerMap protocol_handlers;
std::unique_ptr<TestInMemoryProtocolHandler> http_handler(
new TestInMemoryProtocolHandler(browser()->BrowserIOThread(), nullptr));
http_handler_ = http_handler.get();
http_handler->InsertResponse("http://foo.com/",
{kResourceErrorLoop, "text/html"});
protocol_handlers[url::kHttpScheme] = std::move(http_handler);
return protocol_handlers;
}
void RunDevTooledTest() override {
http_handler_->SetHeadlessBrowserContext(browser_context_);
VirtualTimeBrowserTest::RunDevTooledTest();
}
void SetVirtualTimePolicy() override {
devtools_client_->GetEmulation()->GetExperimental()->SetVirtualTimePolicy(
emulation::SetVirtualTimePolicyParams::Builder()
.SetPolicy(
emulation::VirtualTimePolicy::PAUSE_IF_NETWORK_FETCHES_PENDING)
.SetBudget(5000)
.SetMaxVirtualTimeTaskStarvationCount(1000000) // Prevent flakes.
.SetWaitForNavigation(true)
.Build(),
base::BindRepeating(&VirtualTimeBrowserTest::SetVirtualTimePolicyDone,
base::Unretained(this)));
}
// emulation::Observer implementation:
void OnVirtualTimeBudgetExpired(
const emulation::VirtualTimeBudgetExpiredParams& params) override {
// The budget is 5000 virtual ms. The resources are delivered with 10
// virtual ms delay, so we should have 500 urls.
EXPECT_EQ(500u, http_handler_->urls_requested().size());
FinishAsynchronousTest();
}
TestInMemoryProtocolHandler* http_handler_; // NOT OWNED
};
HEADLESS_ASYNC_DEVTOOLED_TEST_F(VirtualTimeAndResourceErrorLoopTest);
namespace {
static constexpr char kSiteA[] = R"(
<html>
......
<html>
<script>
var counter = 1;
</script>
<img src="1" onerror="this.src='' + ++counter">
</html>
Tests that virtual time advances.
Paused @ 0ms
Paused @ 0ms
Advanced to 10ms
step1
Advanced to 110ms
step2
Paused @ 110ms
Advanced to 210ms
step3
Paused @ 210ms
Advanced to 310ms
step4
pass
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function(testRunner) {
var {page, session, dp} = await testRunner.startBlank(`Tests that virtual time advances.`);
await dp.Page.enable();
await dp.Runtime.enable();
dp.Emulation.onVirtualTimePaused(data =>
testRunner.log(`Paused @ ${data.params.virtualTimeElapsed}ms`));
dp.Emulation.onVirtualTimeAdvanced(data =>
testRunner.log(`Advanced to ${data.params.virtualTimeElapsed}ms`));
dp.Runtime.onConsoleAPICalled(data => {
const text = data.params.args[0].value;
testRunner.log(text);
if (text === 'pass')
testRunner.completeTest();
});
await dp.Emulation.setVirtualTimePolicy({policy: 'pause'});
dp.Page.navigate({url: testRunner.url('resources/virtual-time-advance.html')});
await dp.Emulation.setVirtualTimePolicy({
policy: 'pauseIfNetworkFetchesPending', budget: 5000, waitForNavigation: true});
})
Tests that virtual time advances 10ms on every navigation.
Resources loaded: 500
\ No newline at end of file
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
(async function(testRunner) {
var {page, session, dp} = await testRunner.startBlank(
`Tests that virtual time advances 10ms on every navigation.`);
await dp.Network.enable();
let resourceCounter = 0;
dp.Network.onRequestWillBeSent(() => { resourceCounter++ });
dp.Emulation.onVirtualTimeBudgetExpired(data => {
testRunner.log('Resources loaded: ' + resourceCounter);
testRunner.completeTest();
});
await dp.Emulation.setVirtualTimePolicy({policy: 'pause'});
dp.Page.navigate({url: testRunner.url('resources/virtual-time-error-loop.html')});
await dp.Emulation.setVirtualTimePolicy({
policy: 'pauseIfNetworkFetchesPending',
budget: 5000, waitForNavigation: true,
maxVirtualTimeTaskStarvationCount: 1000000}); // starvation prevents flakes
})
<!--
Copyright 2018 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<html>
<head>
<script>
var output = [];
var testRunner = {};
testRunner.dumpAsText = () => {};
testRunner.waitUntilDone = () => {};
testRunner.setCanOpenWindows = () => {};
testRunner.notifyDone = () => {
console.debug(JSON.stringify({id: 0, method: 'DONE', params: {}, result: output.join('\n')}));
};
DevToolsHost = {};
DevToolsHost.sendMessageToEmbedder = (message) => {
const object = JSON.parse(message);
console.debug(object.params[0]);
}
</script>
<script src="inspector-protocol-test.js"></script>
<script>
DevToolsAPI._log = (text) => output.push(text);
function onmessage(json) { DevToolsAPI.dispatchMessage(json); }
</script>
</head>
</html>
\ No newline at end of file
This diff is collapsed.
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include "base/base64.h"
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/path_service.h"
#include "base/threading/thread_restrictions.h"
#include "content/public/test/browser_test.h"
#include "headless/public/devtools/domains/runtime.h"
#include "headless/public/headless_browser.h"
#include "headless/public/headless_browser_context.h"
#include "headless/public/headless_devtools_client.h"
#include "headless/public/headless_devtools_target.h"
#include "headless/public/headless_web_contents.h"
#include "headless/test/headless_browser_test.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace headless {
namespace {
static const char kResetResults[] = "reset-results";
} // namespace
class HeadlessProtocolBrowserTest
: public HeadlessAsyncDevTooledBrowserTest,
public HeadlessDevToolsClient::RawProtocolListener,
public runtime::Observer {
public:
HeadlessProtocolBrowserTest() {
EXPECT_TRUE(embedded_test_server()->Start());
}
private:
// HeadlessWebContentsObserver implementation.
void DevToolsTargetReady() override {
HeadlessAsyncDevTooledBrowserTest::DevToolsTargetReady();
devtools_client_->GetRuntime()->AddObserver(this);
devtools_client_->GetRuntime()->Enable();
browser_devtools_client_->SetRawProtocolListener(this);
}
void RunDevTooledTest() override {
GURL test_url = embedded_test_server()->GetURL("/protocol/" + script_name_);
devtools_client_->GetPage()->Navigate(
embedded_test_server()
->GetURL("/protocol/inspector-protocol-test.html?test=" +
test_url.spec())
.spec());
}
// runtime::Observer implementation.
void OnConsoleAPICalled(
const runtime::ConsoleAPICalledParams& params) override {
const std::vector<std::unique_ptr<runtime::RemoteObject>>& args =
*params.GetArgs();
if (args.empty())
return;
if (params.GetType() != runtime::ConsoleAPICalledType::DEBUG)
return;
runtime::RemoteObject* object = args[0].get();
if (object->GetType() != runtime::RemoteObjectType::STRING)
return;
DispatchMessageFromJS(object->GetValue()->GetString());
}
void DispatchMessageFromJS(const std::string& json_message) {
std::unique_ptr<base::Value> message = base::JSONReader::Read(json_message);
const base::DictionaryValue* message_dict;
const base::DictionaryValue* params_dict;
std::string method;
int id;
if (!message || !message->GetAsDictionary(&message_dict) ||
!message_dict->GetString("method", &method) ||
!message_dict->GetDictionary("params", &params_dict) ||
!message_dict->GetInteger("id", &id)) {
LOG(ERROR) << "Poorly formed message " << json_message;
FinishTest();
return;
}
if (method != "DONE") {
// Pass unhandled commands onto the inspector.
browser_devtools_client_->SendRawDevToolsMessage(json_message);
return;
}
std::string test_result;
message_dict->GetString("result", &test_result);
static const base::FilePath kTestsDirectory(
FILE_PATH_LITERAL("headless/test/data/protocol"));
base::ScopedAllowBlockingForTesting allow_blocking;
base::FilePath src_dir;
CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
base::FilePath expectation_path =
src_dir.Append(kTestsDirectory)
.AppendASCII(script_name_.substr(0, script_name_.length() - 3) +
"-expected.txt");
if (base::CommandLine::ForCurrentProcess()->HasSwitch(kResetResults)) {
LOG(INFO) << "Updating expectations at " << expectation_path;
int result = base::WriteFile(expectation_path, test_result.data(),
static_cast<int>(test_result.size()));
CHECK(test_result.size() == static_cast<size_t>(result));
}
std::string expectation;
if (!base::ReadFileToString(expectation_path, &expectation)) {
ADD_FAILURE() << "Unable to read expectations at " << expectation_path;
}
EXPECT_EQ(test_result, expectation);
FinishTest();
}
// HeadlessDevToolsClient::RawProtocolListener
bool OnProtocolMessage(const std::string& devtools_agent_host_id,
const std::string& json_message,
const base::DictionaryValue& parsed_message) override {
SendMessageToJS(json_message);
return true;
}
void SendMessageToJS(const std::string& message) {
if (test_finished_)
return;
std::string encoded;
base::Base64Encode(message, &encoded);
devtools_client_->GetRuntime()->Evaluate("onmessage(atob(\"" + encoded +
"\"))");
}
void FinishTest() {
test_finished_ = true;
FinishAsynchronousTest();
}
protected:
bool test_finished_ = false;
std::string script_name_;
};
#define HEADLESS_PROTOCOL_TEST(TEST_NAME, SCRIPT_NAME) \
IN_PROC_BROWSER_TEST_F(HeadlessProtocolBrowserTest, TEST_NAME) { \
script_name_ = SCRIPT_NAME; \
RunTest(); \
}
// Headless-specific tests
HEADLESS_PROTOCOL_TEST(VirtualTimeAdvance, "emulation/virtual-time-advance.js");
HEADLESS_PROTOCOL_TEST(VirtualTimeErrorLoop,
"emulation/virtual-time-error-loop.js");
} // namespace headless
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