1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "headless/test/headless_protocol_browsertest.h"
6 
7 #include "base/base64.h"
8 #include "base/base_paths.h"
9 #include "base/files/file_path.h"
10 #include "base/files/file_util.h"
11 #include "base/json/json_reader.h"
12 #include "base/path_service.h"
13 #include "build/build_config.h"
14 #include "headless/app/headless_shell_switches.h"
15 #include "net/test/embedded_test_server/embedded_test_server.h"
16 #include "services/network/public/cpp/network_switches.h"
17 
18 namespace headless {
19 
20 namespace {
21 static const char kResetResults[] = "reset-results";
22 static const char kDumpDevToolsProtocol[] = "dump-devtools-protocol";
23 }  // namespace
24 
HeadlessProtocolBrowserTest()25 HeadlessProtocolBrowserTest::HeadlessProtocolBrowserTest() {
26   embedded_test_server()->ServeFilesFromSourceDirectory(
27       "third_party/blink/web_tests/http/tests/inspector-protocol");
28   EXPECT_TRUE(embedded_test_server()->Start());
29 }
30 
SetUpCommandLine(base::CommandLine * command_line)31 void HeadlessProtocolBrowserTest::SetUpCommandLine(
32     base::CommandLine* command_line) {
33   command_line->AppendSwitchASCII(::network::switches::kHostResolverRules,
34                                   "MAP *.test 127.0.0.1");
35   HeadlessAsyncDevTooledBrowserTest::SetUpCommandLine(command_line);
36 
37   // Make sure the navigations spawn new processes. We run test harness
38   // in one process (harness.test) and tests in another.
39   command_line->AppendSwitch(::switches::kSitePerProcess);
40 
41   // Make sure proxy related tests are not affected by a platform specific
42   // system proxy configuration service.
43   command_line->AppendSwitch(switches::kNoSystemProxyConfigService);
44 }
45 
GetPageUrlExtraParams()46 std::vector<std::string> HeadlessProtocolBrowserTest::GetPageUrlExtraParams() {
47   return {};
48 }
49 
RunDevTooledTest()50 void HeadlessProtocolBrowserTest::RunDevTooledTest() {
51   browser_devtools_client_->SetRawProtocolListener(this);
52   devtools_client_->GetRuntime()->GetExperimental()->AddObserver(this);
53   devtools_client_->GetRuntime()->Enable();
54   devtools_client_->GetRuntime()->GetExperimental()->AddBinding(
55       headless::runtime::AddBindingParams::Builder()
56           .SetName("sendProtocolMessage")
57           .Build(),
58       base::BindOnce(&HeadlessProtocolBrowserTest::BindingCreated,
59                      base::Unretained(this)));
60 }
61 
BindingCreated(std::unique_ptr<headless::runtime::AddBindingResult>)62 void HeadlessProtocolBrowserTest::BindingCreated(
63     std::unique_ptr<headless::runtime::AddBindingResult>) {
64   base::ScopedAllowBlockingForTesting allow_blocking;
65   base::FilePath src_dir;
66   CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
67   static const base::FilePath kTestsDirectory(
68       FILE_PATH_LITERAL("headless/test/data/protocol"));
69   base::FilePath test_path =
70       src_dir.Append(kTestsDirectory).AppendASCII(script_name_);
71   std::string script;
72   if (!base::ReadFileToString(test_path, &script)) {
73     ADD_FAILURE() << "Unable to read test at " << test_path;
74     FinishTest();
75     return;
76   }
77   GURL test_url = embedded_test_server()->GetURL("harness.test",
78                                                  "/protocol/" + script_name_);
79   GURL target_url =
80       embedded_test_server()->GetURL("127.0.0.1", "/protocol/" + script_name_);
81 
82   std::string extra_params;
83   for (const auto& param : GetPageUrlExtraParams()) {
84     extra_params += "&" + param;
85   }
86 
87   GURL page_url = embedded_test_server()->GetURL(
88       "harness.test",
89       "/protocol/inspector-protocol-test.html?test=" + test_url.spec() +
90           "&target=" + target_url.spec() + extra_params);
91   devtools_client_->GetPage()->Navigate(page_url.spec());
92 }
93 
OnBindingCalled(const runtime::BindingCalledParams & params)94 void HeadlessProtocolBrowserTest::OnBindingCalled(
95     const runtime::BindingCalledParams& params) {
96   std::string json_message = params.GetPayload();
97   std::unique_ptr<base::Value> message =
98       base::JSONReader::ReadDeprecated(json_message);
99   const base::DictionaryValue* message_dict;
100   const base::DictionaryValue* params_dict;
101   std::string method;
102   int id;
103   if (!message || !message->GetAsDictionary(&message_dict) ||
104       !message_dict->GetString("method", &method) ||
105       !message_dict->GetDictionary("params", &params_dict) ||
106       !message_dict->GetInteger("id", &id)) {
107     LOG(ERROR) << "Poorly formed message " << json_message;
108     FinishTest();
109     return;
110   }
111 
112   if (method != "DONE") {
113     if (base::CommandLine::ForCurrentProcess()->HasSwitch(
114             kDumpDevToolsProtocol)) {
115       LOG(INFO) << "FromJS: " << json_message;
116     }
117     // Pass unhandled commands onto the inspector.
118     browser_devtools_client_->SendRawDevToolsMessage(json_message);
119     return;
120   }
121 
122   std::string test_result;
123   message_dict->GetString("result", &test_result);
124   static const base::FilePath kTestsDirectory(
125       FILE_PATH_LITERAL("headless/test/data/protocol"));
126 
127   base::ScopedAllowBlockingForTesting allow_blocking;
128 
129   base::FilePath src_dir;
130   CHECK(base::PathService::Get(base::DIR_SOURCE_ROOT, &src_dir));
131   base::FilePath expectation_path =
132       src_dir.Append(kTestsDirectory)
133           .AppendASCII(script_name_.substr(0, script_name_.length() - 3) +
134                        "-expected.txt");
135 
136   if (base::CommandLine::ForCurrentProcess()->HasSwitch(kResetResults)) {
137     LOG(INFO) << "Updating expectations at " << expectation_path;
138     int result = base::WriteFile(expectation_path, test_result.data(),
139                                  static_cast<int>(test_result.size()));
140     CHECK(test_result.size() == static_cast<size_t>(result));
141   }
142 
143   std::string expectation;
144   if (!base::ReadFileToString(expectation_path, &expectation)) {
145     ADD_FAILURE() << "Unable to read expectations at " << expectation_path;
146   }
147   EXPECT_EQ(test_result, expectation);
148   FinishTest();
149 }
150 
OnProtocolMessage(base::span<const uint8_t> json_message,const base::DictionaryValue & parsed_message)151 bool HeadlessProtocolBrowserTest::OnProtocolMessage(
152     base::span<const uint8_t> json_message,
153     const base::DictionaryValue& parsed_message) {
154   SendMessageToJS(base::StringPiece(
155       reinterpret_cast<const char*>(json_message.data()), json_message.size()));
156   return true;
157 }
158 
SendMessageToJS(base::StringPiece message)159 void HeadlessProtocolBrowserTest::SendMessageToJS(base::StringPiece message) {
160   if (test_finished_)
161     return;
162 
163   if (base::CommandLine::ForCurrentProcess()->HasSwitch(
164           kDumpDevToolsProtocol)) {
165     LOG(INFO) << "ToJS: " << message;
166   }
167 
168   std::string encoded;
169   base::Base64Encode(message, &encoded);
170   devtools_client_->GetRuntime()->Evaluate("onmessage(atob(\"" + encoded +
171                                            "\"))");
172 }
173 
FinishTest()174 void HeadlessProtocolBrowserTest::FinishTest() {
175   test_finished_ = true;
176   FinishAsynchronousTest();
177 }
178 
179 // TODO(crbug.com/1086872): The whole test suite is flaky on Mac ASAN.
180 #if (defined(OS_MAC) && defined(ADDRESS_SANITIZER))
181 #define HEADLESS_PROTOCOL_TEST(TEST_NAME, SCRIPT_NAME)                        \
182   IN_PROC_BROWSER_TEST_F(HeadlessProtocolBrowserTest, DISABLED_##TEST_NAME) { \
183     test_folder_ = "/protocol/";                                              \
184     script_name_ = SCRIPT_NAME;                                               \
185     RunTest();                                                                \
186   }
187 #else
188 #define HEADLESS_PROTOCOL_TEST(TEST_NAME, SCRIPT_NAME)             \
189   IN_PROC_BROWSER_TEST_F(HeadlessProtocolBrowserTest, TEST_NAME) { \
190     test_folder_ = "/protocol/";                                   \
191     script_name_ = SCRIPT_NAME;                                    \
192     RunTest();                                                     \
193   }
194 #endif
195 
196 // Headless-specific tests
197 HEADLESS_PROTOCOL_TEST(VirtualTimeBasics, "emulation/virtual-time-basics.js")
198 HEADLESS_PROTOCOL_TEST(VirtualTimeInterrupt,
199                        "emulation/virtual-time-interrupt.js")
200 
201 // Flaky on Linux, Mac & Win. TODO(crbug.com/930717): Re-enable.
202 #if defined(OS_LINUX) || defined(OS_CHROMEOS) || defined(OS_MAC) || \
203     defined(OS_WIN) || defined(OS_FUCHSIA)
204 #define MAYBE_VirtualTimeCrossProcessNavigation \
205   DISABLED_VirtualTimeCrossProcessNavigation
206 #else
207 #define MAYBE_VirtualTimeCrossProcessNavigation \
208   VirtualTimeCrossProcessNavigation
209 #endif
210 HEADLESS_PROTOCOL_TEST(MAYBE_VirtualTimeCrossProcessNavigation,
211                        "emulation/virtual-time-cross-process-navigation.js")
212 HEADLESS_PROTOCOL_TEST(VirtualTimeDetachFrame,
213                        "emulation/virtual-time-detach-frame.js")
214 HEADLESS_PROTOCOL_TEST(VirtualTimeNoBlock404, "emulation/virtual-time-404.js")
215 HEADLESS_PROTOCOL_TEST(VirtualTimeLocalStorage,
216                        "emulation/virtual-time-local-storage.js")
217 HEADLESS_PROTOCOL_TEST(VirtualTimePendingScript,
218                        "emulation/virtual-time-pending-script.js")
219 HEADLESS_PROTOCOL_TEST(VirtualTimeRedirect,
220                        "emulation/virtual-time-redirect.js")
221 HEADLESS_PROTOCOL_TEST(VirtualTimeSessionStorage,
222                        "emulation/virtual-time-session-storage.js")
223 HEADLESS_PROTOCOL_TEST(VirtualTimeStarvation,
224                        "emulation/virtual-time-starvation.js")
225 HEADLESS_PROTOCOL_TEST(VirtualTimeVideo, "emulation/virtual-time-video.js")
226 HEADLESS_PROTOCOL_TEST(VirtualTimeErrorLoop,
227                        "emulation/virtual-time-error-loop.js")
228 HEADLESS_PROTOCOL_TEST(VirtualTimeFetchStream,
229                        "emulation/virtual-time-fetch-stream.js")
230 HEADLESS_PROTOCOL_TEST(VirtualTimeDialogWhileLoading,
231                        "emulation/virtual-time-dialog-while-loading.js")
232 HEADLESS_PROTOCOL_TEST(VirtualTimeHistoryNavigation,
233                        "emulation/virtual-time-history-navigation.js")
234 HEADLESS_PROTOCOL_TEST(VirtualTimeHistoryNavigationSameDoc,
235                        "emulation/virtual-time-history-navigation-same-doc.js")
236 HEADLESS_PROTOCOL_TEST(VirtualTimeFetchKeepalive,
237                        "emulation/virtual-time-fetch-keepalive.js")
238 HEADLESS_PROTOCOL_TEST(VirtualTimeDisposeWhileRunning,
239                        "emulation/virtual-time-dispose-while-running.js")
240 HEADLESS_PROTOCOL_TEST(VirtualTimePausesDocumentLoading,
241                        "emulation/virtual-time-pauses-document-loading.js")
242 
243 HEADLESS_PROTOCOL_TEST(PageBeforeUnload, "page/page-before-unload.js")
244 
245 // http://crbug.com/633321
246 #if defined(OS_ANDROID)
247 #define MAYBE_VirtualTimeTimerOrder DISABLED_VirtualTimeTimerOrder
248 #define MAYBE_VirtualTimeTimerSuspend DISABLED_VirtualTimeTimerSuspend
249 #else
250 #define MAYBE_VirtualTimeTimerOrder VirtualTimeTimerOrder
251 #define MAYBE_VirtualTimeTimerSuspend VirtualTimeTimerSuspend
252 #endif
253 HEADLESS_PROTOCOL_TEST(MAYBE_VirtualTimeTimerOrder,
254                        "emulation/virtual-time-timer-order.js")
255 HEADLESS_PROTOCOL_TEST(MAYBE_VirtualTimeTimerSuspend,
256                        "emulation/virtual-time-timer-suspended.js")
257 #undef MAYBE_VirtualTimeTimerOrder
258 #undef MAYBE_VirtualTimeTimerSuspend
259 
260 HEADLESS_PROTOCOL_TEST(HeadlessSessionBasicsTest,
261                        "sessions/headless-session-basics.js")
262 
263 HEADLESS_PROTOCOL_TEST(HeadlessSessionCreateContextDisposeOnDetach,
264                        "sessions/headless-createContext-disposeOnDetach.js")
265 
266 HEADLESS_PROTOCOL_TEST(BrowserSetInitialProxyConfig,
267                        "sanity/browser-set-initial-proxy-config.js")
268 
269 class HeadlessProtocolBrowserTestWithProxy
270     : public HeadlessProtocolBrowserTest {
271  public:
HeadlessProtocolBrowserTestWithProxy()272   HeadlessProtocolBrowserTestWithProxy()
273       : proxy_server_(net::EmbeddedTestServer::TYPE_HTTP) {
274     proxy_server_.AddDefaultHandlers(
275         base::FilePath(FILE_PATH_LITERAL("headless/test/data")));
276   }
277 
SetUp()278   void SetUp() override {
279     ASSERT_TRUE(proxy_server_.Start());
280     HeadlessProtocolBrowserTest::SetUp();
281   }
282 
TearDown()283   void TearDown() override {
284     EXPECT_TRUE(proxy_server_.ShutdownAndWaitUntilComplete());
285     HeadlessProtocolBrowserTest::TearDown();
286   }
287 
proxy_server()288   net::EmbeddedTestServer* proxy_server() { return &proxy_server_; }
289 
290  protected:
GetPageUrlExtraParams()291   std::vector<std::string> GetPageUrlExtraParams() override {
292     std::string proxy = proxy_server()->host_port_pair().ToString();
293     return {"&proxy=" + proxy};
294   }
295 
296  private:
297   net::EmbeddedTestServer proxy_server_;
298 };
299 
300 #define HEADLESS_PROTOCOL_TEST_WITH_PROXY(TEST_NAME, SCRIPT_NAME)           \
301   IN_PROC_BROWSER_TEST_F(HeadlessProtocolBrowserTestWithProxy, TEST_NAME) { \
302     test_folder_ = "/protocol/";                                            \
303     script_name_ = SCRIPT_NAME;                                             \
304     RunTest();                                                              \
305   }
306 
307 HEADLESS_PROTOCOL_TEST_WITH_PROXY(BrowserSetProxyConfig,
308                                   "sanity/browser-set-proxy-config.js")
309 
310 }  // namespace headless
311