blob: 97cd2429f22ebd23b5756792fbf97efbf4fcc1b2 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <string>
#include <utility>
#include "base/compiler_specific.h"
#if BUILDFLAG(IS_WIN)
#include <fcntl.h>
#include <windows.h>
#endif
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_writer.h"
#include "base/logging.h"
#include "base/process/launch.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/test/chromedriver/chrome/browser_info.h"
#include "chrome/test/chromedriver/chrome/devtools_client.h"
#include "chrome/test/chromedriver/chrome/devtools_client_impl.h"
#include "chrome/test/chromedriver/chrome/devtools_event_listener.h"
#include "chrome/test/chromedriver/chrome/javascript_dialog_manager.h"
#include "chrome/test/chromedriver/chrome/navigation_tracker.h"
#include "chrome/test/chromedriver/chrome/page_load_strategy.h"
#include "chrome/test/chromedriver/chrome/page_tracker.h"
#include "chrome/test/chromedriver/chrome/status.h"
#include "chrome/test/chromedriver/chrome/target_utils.h"
#include "chrome/test/chromedriver/chrome/web_view_impl.h"
#include "chrome/test/chromedriver/chrome/web_view_info.h"
#include "chrome/test/chromedriver/chrome_launcher.h"
#include "chrome/test/chromedriver/net/pipe_builder.h"
#include "chrome/test/chromedriver/net/test_http_server.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
std::string ToString(const base::Value::Dict& node) {
std::string json;
base::JSONWriter::Write(node, &json);
return json;
}
testing::AssertionResult StatusOk(const Status& status) {
if (status.IsOk()) {
return testing::AssertionSuccess();
} else {
return testing::AssertionFailure() << status.message();
}
}
class DevToolsClientImplTest : public ::testing::Test {
protected:
DevToolsClientImplTest() = default;
void SetUp() override {
Status status{kOk};
http_server_.Start();
const base::CommandLine* cur_proc_cmd =
base::CommandLine::ForCurrentProcess();
ASSERT_TRUE(cur_proc_cmd->HasSwitch("chrome"))
<< "path to chrome is not provided";
base::FilePath path_to_chrome = cur_proc_cmd->GetSwitchValuePath("chrome");
ASSERT_TRUE(base::PathExists(path_to_chrome))
<< "file not found: " << path_to_chrome;
ASSERT_TRUE(user_data_dir_temp_dir_.CreateUniqueTempDir())
<< "cannot create temp dir for user data dir";
base::CommandLine command(path_to_chrome);
Switches switches = GetDesktopSwitches();
switches.SetSwitch("remote-debugging-pipe");
switches.SetSwitch("user-data-dir",
user_data_dir_temp_dir_.GetPath().AsUTF8Unsafe());
switches.AppendToCommandLine(&command);
base::LaunchOptions options;
pipe_builder_.SetProtocolMode(PipeBuilder::kAsciizProtocolMode);
status = pipe_builder_.SetUpPipes(&options, &command);
ASSERT_TRUE(StatusOk(status));
command.AppendArg("data:,");
#if BUILDFLAG(IS_POSIX)
options.fds_to_remap.emplace_back(1, 1);
options.fds_to_remap.emplace_back(2, 2);
#elif BUILDFLAG(IS_WIN)
options.stdin_handle = INVALID_HANDLE_VALUE;
options.stdout_handle = GetStdHandle(STD_OUTPUT_HANDLE);
options.stderr_handle = GetStdHandle(STD_ERROR_HANDLE);
options.handles_to_inherit.push_back(options.stdout_handle);
if (options.stderr_handle != options.stdout_handle) {
options.handles_to_inherit.push_back(options.stderr_handle);
}
#endif
process_ = base::LaunchProcess(command, options);
ASSERT_TRUE(process_.IsValid());
int exit_code;
base::TerminationStatus chrome_status =
base::GetTerminationStatus(process_.Handle(), &exit_code);
ASSERT_EQ(base::TERMINATION_STATUS_STILL_RUNNING, chrome_status);
browser_client_ = std::make_unique<DevToolsClientImpl>(
DevToolsClientImpl::kBrowserwideDevToolsClientId, "");
}
void TearDown() override {
if (browser_client_) {
Status status =
browser_client_->SendCommand("Browser.close", base::Value::Dict());
EXPECT_TRUE(StatusOk(status));
}
web_views_.clear();
browser_client_.reset();
if (process_.IsValid()) {
process_.Close();
int exit_code;
if (!process_.WaitForExitWithTimeout(base::Seconds(10), &exit_code)) {
process_.Terminate(0, true);
}
}
http_server_.Stop();
}
void SetUpConnection() {
Status status{kOk};
status = pipe_builder_.BuildSocket();
ASSERT_TRUE(StatusOk(status));
std::unique_ptr<SyncWebSocket> socket = pipe_builder_.TakeSocket();
EXPECT_TRUE(socket->Connect(GURL()));
status = browser_client_->SetSocket(std::move(socket));
ASSERT_TRUE(StatusOk(status));
Timeout timeout{base::Seconds(10)};
base::Value::Dict result;
status = browser_client_->SendCommandAndGetResultWithTimeout(
"Browser.getVersion", base::Value::Dict(), &timeout, &result);
ASSERT_TRUE(StatusOk(status));
status = browser_info_.FillFromBrowserVersionResponse(result);
ASSERT_TRUE(StatusOk(status));
pipe_builder_.CloseChildEndpoints();
}
base::test::SingleThreadTaskEnvironment task_environment_;
TestHttpServer http_server_;
base::ScopedTempDir user_data_dir_temp_dir_;
base::Process process_;
PipeBuilder pipe_builder_;
std::list<std::unique_ptr<WebViewImpl>> web_views_;
std::unique_ptr<DevToolsClientImpl> browser_client_;
BrowserInfo browser_info_;
};
} // namespace
TEST_F(DevToolsClientImplTest, DeleteGlobalJSON) {
// During page initialization two frames are created in a row.
// In this test we want to verity that if the global JSON object was saved in
// the first frame the saved value will be available in the second frame as
// well.
Status status{kOk};
SetUpConnection();
Timeout timeout{base::Seconds(60)};
status = target_utils::WaitForPage(*browser_client_, timeout);
ASSERT_TRUE(StatusOk(status));
WebViewsInfo views_info;
status =
target_utils::GetWebViewsInfo(*browser_client_, &timeout, views_info);
ASSERT_TRUE(StatusOk(status));
const WebViewInfo* view_info = views_info.FindFirst(WebViewInfo::kPage);
ASSERT_NE(view_info, nullptr);
std::unique_ptr<DevToolsClient> client;
status = target_utils::AttachToPageTarget(*browser_client_, view_info->id,
&timeout, client);
ASSERT_TRUE(StatusOk(status));
DevToolsClientImpl* browser_client_impl =
static_cast<DevToolsClientImpl*>(browser_client_.get());
DevToolsClientImpl* page_client_impl =
static_cast<DevToolsClientImpl*>(client.get());
status = page_client_impl->AttachTo(browser_client_impl);
ASSERT_TRUE(StatusOk(status));
base::Value::Dict params;
base::Value::Dict result;
params.Set(
"expression",
"window.page_label_for_test = \"starting\"; window.page_label_for_test");
status = client->SendCommandAndGetResultWithTimeout(
"Runtime.evaluate", params, &timeout, &result);
const std::string* value = result.FindStringByDottedPath("result.value");
ASSERT_THAT(value, ::testing::Pointee(testing::Eq("starting")))
<< ToString(result);
bool frame_has_changed = false;
Timeout navigation_timeout(base::Seconds(10), &timeout);
while (!frame_has_changed && !navigation_timeout.IsExpired()) {
params.clear();
result.clear();
params.Set("expression", "window.page_label_for_test");
status = client->SendCommandAndGetResultWithTimeout(
"Runtime.evaluate", params, &navigation_timeout, &result);
ASSERT_TRUE(StatusOk(status));
value = result.FindStringByDottedPath("result.value");
frame_has_changed = value == nullptr;
}
if (frame_has_changed) {
// frame_has_changed is only true if the value of window.page_label_for_test
// was set in the first frame and the page has switched to the second frame.
// This means that the global JSON was saved in the first frame as well.
// Now being in the second frame we can verify that the saved value of the
// global JSON object indeed can be accessed.
params.clear();
result.clear();
params.Set("expression",
"window.cdc_adoQpoasnfa76pfcZLmcfl_JSON.stringify(321)");
status = client->SendCommandAndGetResultWithTimeout(
"Runtime.evaluate", params, &timeout, &result);
EXPECT_TRUE(StatusOk(status));
value = result.FindStringByDottedPath("result.value");
EXPECT_THAT(value, testing::Pointee(testing::Eq("321")))
<< ToString(result);
} else {
VLOG(0) << "frame has not changed, the test has not effect";
}
}