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
|
// Copyright 2022 The Chromium Authors
// 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_utils.h"
#include <optional>
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/run_loop.h"
#include "components/devtools/simple_devtools_protocol_client/simple_devtools_protocol_client.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "headless/lib/browser/headless_web_contents_impl.h"
#include "headless/public/headless_web_contents.h"
#include "testing/gtest/include/gtest/gtest.h"
using simple_devtools_protocol_client::SimpleDevToolsProtocolClient;
namespace headless {
base::Value::Dict SendCommandSync(SimpleDevToolsProtocolClient& devtools_client,
const std::string& command) {
return SendCommandSync(devtools_client, command, base::Value::Dict());
}
base::Value::Dict SendCommandSync(
simple_devtools_protocol_client::SimpleDevToolsProtocolClient&
devtools_client,
const std::string& command,
base::Value::Dict params) {
base::Value::Dict command_result;
base::RunLoop run_loop(base::RunLoop::Type::kNestableTasksAllowed);
devtools_client.SendCommand(
command, std::move(params),
base::BindOnce(
[](base::RunLoop* run_loop, base::Value::Dict* command_result,
base::Value::Dict result) {
*command_result = std::move(result);
run_loop->Quit();
},
base::Unretained(&run_loop), base::Unretained(&command_result)));
run_loop.Run();
return command_result;
}
base::Value::Dict EvaluateScript(HeadlessWebContents* web_contents,
const std::string& script) {
SimpleDevToolsProtocolClient devtools_client;
devtools_client.AttachToWebContents(
HeadlessWebContentsImpl::From(web_contents)->web_contents());
base::Value::Dict result = SendCommandSync(
devtools_client, "Runtime.evaluate", Param("expression", script));
devtools_client.DetachClient();
return result;
}
bool WaitForLoad(HeadlessWebContents* web_contents, net::Error* error) {
content::WebContents* content_web_contents =
HeadlessWebContentsImpl::From(web_contents)->web_contents();
content::TestNavigationObserver observer(content_web_contents, 1);
observer.Wait();
if (error)
*error = observer.last_net_error_code();
return observer.last_navigation_succeeded();
}
void WaitForLoadAndGainFocus(HeadlessWebContents* web_contents) {
content::WebContents* content_web_contents =
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_web_contents, 1);
content::FrameFocusedObserver focus_observer(
content_web_contents->GetPrimaryMainFrame());
load_observer.Wait();
focus_observer.Wait();
}
///////////////////////////////////////////////////////////////////////
// base::Value::Dict helpers.
std::string DictString(const base::Value::Dict& dict, std::string_view path) {
const std::string* result = dict.FindStringByDottedPath(path);
CHECK(result) << "Missing value for '" << path << "' in:\n"
<< dict.DebugString();
return *result;
}
int DictInt(const base::Value::Dict& dict, std::string_view path) {
std::optional<int> result = dict.FindIntByDottedPath(path);
CHECK(result) << "Missing value for '" << path << "' in:\n"
<< dict.DebugString();
return *result;
}
bool DictBool(const base::Value::Dict& dict, std::string_view path) {
std::optional<bool> result = dict.FindBoolByDottedPath(path);
CHECK(result) << "Missing value for '" << path << "' in:\n"
<< dict.DebugString();
return *result;
}
bool DictHas(const base::Value::Dict& dict, std::string_view path) {
return dict.FindByDottedPath(path) != nullptr;
}
///////////////////////////////////////////////////////////////////////
// GMock matchers
namespace {
// Cannot use Value::DebugString here due to newlines.
std::string ToJSON(const base::ValueView& value) {
std::string json;
base::JSONWriter::Write(value, &json);
return json;
}
class DictHasPathValueMatcher
: public testing::MatcherInterface<const base::Value::Dict&> {
public:
DictHasPathValueMatcher(const std::string& path, base::Value expected_value)
: path_(path), expected_value_(std::move(expected_value)) {}
DictHasPathValueMatcher& operator=(const DictHasPathValueMatcher& other) =
delete;
~DictHasPathValueMatcher() override = default;
bool MatchAndExplain(const base::Value::Dict& dict,
testing::MatchResultListener* listener) const override {
const base::Value* dict_value = dict.FindByDottedPath(path_);
if (!dict_value) {
*listener << "Dictionary '" << ToJSON(dict) << "' does not have path '"
<< path_ << "'";
return false;
}
if (*dict_value != expected_value_) {
*listener << "Dictionary path value '" << path_ << "' is '"
<< ToJSON(*dict_value) << "', expected '"
<< ToJSON(expected_value_) << "'";
return false;
}
return true;
}
void DescribeTo(std::ostream* os) const override {
*os << "has path '" << path_ << "' with value '" << ToJSON(expected_value_)
<< "'";
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "does not have path '" << path_ << "' with value '"
<< ToJSON(expected_value_) << "'";
}
private:
const std::string path_;
const base::Value expected_value_;
};
class DictHasKeyMatcher
: public testing::MatcherInterface<const base::Value::Dict&> {
public:
explicit DictHasKeyMatcher(const std::string& key) : key_(key) {}
DictHasKeyMatcher& operator=(const DictHasKeyMatcher& other) = delete;
~DictHasKeyMatcher() override = default;
bool MatchAndExplain(const base::Value::Dict& dict,
testing::MatchResultListener* listener) const override {
const base::Value* dict_value = dict.Find(key_);
if (!dict_value) {
*listener << "Dictionary '" << ToJSON(dict) << "' does not have key '"
<< key_ << "'";
return false;
}
return true;
}
void DescribeTo(std::ostream* os) const override {
*os << "has key '" << key_ << "'";
}
void DescribeNegationTo(std::ostream* os) const override {
*os << "does not have key '" << key_ << "'";
}
private:
const std::string key_;
};
} // namespace
testing::Matcher<const base::Value::Dict&> DictHasPathValue(
const std::string& path,
base::Value expected_value) {
return testing::MakeMatcher(
new DictHasPathValueMatcher(path, std::move(expected_value)));
}
testing::Matcher<const base::Value::Dict&> DictHasKey(const std::string& key) {
return testing::MakeMatcher(new DictHasKeyMatcher(key));
}
} // namespace headless
|