blob: 30bc779ae52b5c7fbb27f69fb04d0d30e8a0bc62 [file] [log] [blame]
// Copyright 2016 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 "headless/test/headless_browser_test.h"
#include <memory>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/message_loop/message_loop_current.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "build/build_config.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/url_constants.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "headless/lib/browser/headless_browser_impl.h"
#include "headless/lib/browser/headless_web_contents_impl.h"
#include "headless/lib/headless_content_main_delegate.h"
#include "headless/public/devtools/domains/emulation.h"
#include "headless/public/devtools/domains/runtime.h"
#include "headless/public/headless_devtools_client.h"
#include "headless/public/headless_devtools_target.h"
#include "headless/public/headless_web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gl/gl_switches.h"
#include "url/gurl.h"
namespace headless {
namespace {
class SynchronousLoadObserver {
public:
SynchronousLoadObserver(HeadlessBrowserTest* browser_test,
HeadlessWebContents* web_contents)
: web_contents_(web_contents),
devtools_client_(HeadlessDevToolsClient::Create()) {
web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
load_observer_.reset(new LoadObserver(
devtools_client_.get(),
base::Bind(&HeadlessBrowserTest::FinishAsynchronousTest,
base::Unretained(browser_test))));
}
~SynchronousLoadObserver() {
web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
}
bool navigation_succeeded() const {
return load_observer_->navigation_succeeded();
}
private:
HeadlessWebContents* web_contents_; // Not owned.
std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
std::unique_ptr<LoadObserver> load_observer_;
};
class EvaluateHelper {
public:
EvaluateHelper(HeadlessBrowserTest* browser_test,
HeadlessWebContents* web_contents,
const std::string& script_to_eval)
: browser_test_(browser_test),
web_contents_(web_contents),
devtools_client_(HeadlessDevToolsClient::Create()) {
web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
devtools_client_->GetRuntime()->Evaluate(
script_to_eval, base::BindOnce(&EvaluateHelper::OnEvaluateResult,
base::Unretained(this)));
}
~EvaluateHelper() {
web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
}
void OnEvaluateResult(std::unique_ptr<runtime::EvaluateResult> result) {
result_ = std::move(result);
browser_test_->FinishAsynchronousTest();
}
std::unique_ptr<runtime::EvaluateResult> TakeResult() {
return std::move(result_);
}
private:
HeadlessBrowserTest* browser_test_; // Not owned.
HeadlessWebContents* web_contents_; // Not owned.
std::unique_ptr<HeadlessDevToolsClient> devtools_client_;
std::unique_ptr<runtime::EvaluateResult> result_;
DISALLOW_COPY_AND_ASSIGN(EvaluateHelper);
};
} // namespace
LoadObserver::LoadObserver(HeadlessDevToolsClient* devtools_client,
base::OnceClosure callback)
: callback_(std::move(callback)),
devtools_client_(devtools_client),
navigation_succeeded_(true) {
devtools_client_->GetNetwork()->AddObserver(this);
devtools_client_->GetNetwork()->Enable();
devtools_client_->GetPage()->AddObserver(this);
devtools_client_->GetPage()->Enable();
}
LoadObserver::~LoadObserver() {
devtools_client_->GetNetwork()->RemoveObserver(this);
devtools_client_->GetPage()->RemoveObserver(this);
}
void LoadObserver::OnLoadEventFired(const page::LoadEventFiredParams& params) {
std::move(callback_).Run();
}
void LoadObserver::OnResponseReceived(
const network::ResponseReceivedParams& params) {
if (params.GetResponse()->GetStatus() != 200 ||
params.GetResponse()->GetUrl() == content::kUnreachableWebDataURL) {
navigation_succeeded_ = false;
}
}
HeadlessBrowserTest::HeadlessBrowserTest() {
#if defined(OS_MACOSX)
// On Mac the source root is not set properly. We override it by assuming
// that is two directories up from the execution test file.
base::FilePath dir_exe_path;
CHECK(base::PathService::Get(base::DIR_EXE, &dir_exe_path));
dir_exe_path = dir_exe_path.Append("../../");
CHECK(base::PathService::Override(base::DIR_SOURCE_ROOT, dir_exe_path));
#endif // defined(OS_MACOSX)
base::FilePath headless_test_data(FILE_PATH_LITERAL("headless/test/data"));
CreateTestServer(headless_test_data);
}
void HeadlessBrowserTest::SetUp() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
// Enable GPU usage (i.e., SwiftShader, hardware GL on macOS) in all tests
// since that's the default configuration of --headless.
command_line->AppendSwitch(switches::kUseGpuInTests);
SetUpCommandLine(command_line);
BrowserTestBase::SetUp();
}
void HeadlessBrowserTest::SetUpWithoutGPU() {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
SetUpCommandLine(command_line);
BrowserTestBase::SetUp();
}
HeadlessBrowserTest::~HeadlessBrowserTest() = default;
void HeadlessBrowserTest::PreRunTestOnMainThread() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// Pump startup related events.
base::RunLoop().RunUntilIdle();
}
void HeadlessBrowserTest::PostRunTestOnMainThread() {
browser()->Shutdown();
for (content::RenderProcessHost::iterator i(
content::RenderProcessHost::AllHostsIterator());
!i.IsAtEnd(); i.Advance()) {
i.GetCurrentValue()->FastShutdownIfPossible();
}
}
HeadlessBrowser* HeadlessBrowserTest::browser() const {
return HeadlessContentMainDelegate::GetInstance()->browser();
}
HeadlessBrowser::Options* HeadlessBrowserTest::options() const {
return HeadlessContentMainDelegate::GetInstance()->browser()->options();
}
bool HeadlessBrowserTest::WaitForLoad(HeadlessWebContents* web_contents) {
HeadlessWebContentsImpl* web_contents_impl =
HeadlessWebContentsImpl::From(web_contents);
content::TestNavigationObserver observer(web_contents_impl->web_contents(),
1);
observer.Wait();
return observer.last_navigation_succeeded();
}
void HeadlessBrowserTest::WaitForLoadAndGainFocus(
HeadlessWebContents* web_contents) {
content::WebContents* content =
HeadlessWebContentsImpl::From(web_contents)->web_contents();
// To finish loading and to gain focus are two independent events. Which one
// is issued first is undefined. The following code is waiting on both, in any
// order.
content::TestNavigationObserver load_observer(content, 1);
content::FrameFocusedObserver focus_observer(content->GetMainFrame());
load_observer.Wait();
focus_observer.Wait();
}
std::unique_ptr<runtime::EvaluateResult> HeadlessBrowserTest::EvaluateScript(
HeadlessWebContents* web_contents,
const std::string& script) {
EvaluateHelper helper(this, web_contents, script);
RunAsynchronousTest();
return helper.TakeResult();
}
void HeadlessBrowserTest::RunAsynchronousTest() {
EXPECT_FALSE(run_loop_);
run_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
PreRunAsynchronousTest();
run_loop_->Run();
PostRunAsynchronousTest();
run_loop_ = nullptr;
}
void HeadlessBrowserTest::FinishAsynchronousTest() {
run_loop_->Quit();
}
HeadlessAsyncDevTooledBrowserTest::HeadlessAsyncDevTooledBrowserTest()
: browser_context_(nullptr),
web_contents_(nullptr),
render_process_exited_(false) {}
HeadlessAsyncDevTooledBrowserTest::~HeadlessAsyncDevTooledBrowserTest() =
default;
void HeadlessAsyncDevTooledBrowserTest::DevToolsTargetReady() {
EXPECT_TRUE(web_contents_->GetDevToolsTarget());
web_contents_->GetDevToolsTarget()->AttachClient(devtools_client_.get());
#if defined(OS_MACOSX)
devtools_client_->GetEmulation()->SetDeviceMetricsOverride(
emulation::SetDeviceMetricsOverrideParams::Builder()
.SetWidth(0)
.SetHeight(0)
.SetDeviceScaleFactor(1)
.SetMobile(false)
.Build(),
base::BindOnce(
[](HeadlessAsyncDevTooledBrowserTest* self) {
self->RunDevTooledTest();
},
base::Unretained(this)));
#else
RunDevTooledTest();
#endif
}
void HeadlessAsyncDevTooledBrowserTest::RenderProcessExited(
base::TerminationStatus status,
int exit_code) {
if (status == base::TERMINATION_STATUS_NORMAL_TERMINATION)
return;
FinishAsynchronousTest();
render_process_exited_ = true;
FAIL() << "Abnormal renderer termination";
}
void HeadlessAsyncDevTooledBrowserTest::RunTest() {
devtools_client_ = HeadlessDevToolsClient::Create();
browser_devtools_client_ = HeadlessDevToolsClient::Create();
interceptor_ = std::make_unique<TestNetworkInterceptor>();
HeadlessBrowserContext::Builder builder =
browser()->CreateBrowserContextBuilder();
CustomizeHeadlessBrowserContext(builder);
browser_context_ = builder.Build();
browser()->SetDefaultBrowserContext(browser_context_);
browser()->GetDevToolsTarget()->AttachClient(browser_devtools_client_.get());
HeadlessWebContents::Builder web_contents_builder =
browser_context_->CreateWebContentsBuilder();
web_contents_builder.SetEnableBeginFrameControl(GetEnableBeginFrameControl());
CustomizeHeadlessWebContents(web_contents_builder);
web_contents_ = web_contents_builder.Build();
web_contents_->AddObserver(this);
RunAsynchronousTest();
interceptor_.reset();
if (!render_process_exited_)
web_contents_->GetDevToolsTarget()->DetachClient(devtools_client_.get());
web_contents_->RemoveObserver(this);
web_contents_->Close();
web_contents_ = nullptr;
browser()->GetDevToolsTarget()->DetachClient(browser_devtools_client_.get());
browser_context_->Close();
browser_context_ = nullptr;
}
bool HeadlessAsyncDevTooledBrowserTest::GetEnableBeginFrameControl() {
return false;
}
void HeadlessAsyncDevTooledBrowserTest::CustomizeHeadlessBrowserContext(
HeadlessBrowserContext::Builder& builder) {}
void HeadlessAsyncDevTooledBrowserTest::CustomizeHeadlessWebContents(
HeadlessWebContents::Builder& builder) {}
} // namespace headless