| // 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 "components/autofill_assistant/browser/web/web_controller.h" |
| |
| #include <chrono> |
| #include <thread> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/test/bind.h" |
| #include "base/time/time.h" |
| #include "components/autofill/core/browser/field_types.h" |
| #include "components/autofill_assistant/browser/action_value.pb.h" |
| #include "components/autofill_assistant/browser/service.pb.h" |
| #include "components/autofill_assistant/browser/string_conversions_util.h" |
| #include "components/autofill_assistant/browser/top_padding.h" |
| #include "components/autofill_assistant/browser/user_data.h" |
| #include "components/autofill_assistant/browser/user_model.h" |
| #include "components/autofill_assistant/browser/web/element_action_util.h" |
| #include "components/autofill_assistant/browser/web/element_finder.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "third_party/blink/public/common/switches.h" |
| |
| namespace autofill_assistant { |
| |
| using ::testing::AnyOf; |
| using ::testing::IsEmpty; |
| |
| // Flag to enable site per process to enforce OOPIFs. |
| const char* kSitePerProcess = "site-per-process"; |
| const char* kTargetWebsitePath = "/autofill_assistant_target_website.html"; |
| |
| class WebControllerBrowserTest : public content::ContentBrowserTest, |
| public content::WebContentsObserver { |
| public: |
| WebControllerBrowserTest() {} |
| |
| WebControllerBrowserTest(const WebControllerBrowserTest&) = delete; |
| WebControllerBrowserTest& operator=(const WebControllerBrowserTest&) = delete; |
| |
| ~WebControllerBrowserTest() override {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(kSitePerProcess); |
| // Necessary to avoid flakiness or failure due to input arriving |
| // before the first compositor commit. |
| command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); |
| } |
| |
| void SetUpOnMainThread() override { |
| ContentBrowserTest::SetUpOnMainThread(); |
| |
| // Start a mock server for hosting an OOPIF. |
| http_server_iframe_ = std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTP); |
| http_server_iframe_->ServeFilesFromSourceDirectory( |
| "components/test/data/autofill_assistant/html_iframe"); |
| ASSERT_TRUE(http_server_iframe_->Start(8081)); |
| |
| // Start the main server hosting the test page. |
| http_server_ = std::make_unique<net::EmbeddedTestServer>( |
| net::EmbeddedTestServer::TYPE_HTTP); |
| http_server_->ServeFilesFromSourceDirectory( |
| "components/test/data/autofill_assistant/html"); |
| ASSERT_TRUE(http_server_->Start(8080)); |
| ASSERT_TRUE( |
| NavigateToURL(shell(), http_server_->GetURL(kTargetWebsitePath))); |
| web_controller_ = WebController::CreateForWebContents( |
| shell()->web_contents(), &user_data_); |
| Observe(shell()->web_contents()); |
| } |
| |
| void WaitTillPageIsIdle(base::TimeDelta continuous_paint_timeout) { |
| base::TimeTicks finished_load_time = base::TimeTicks::Now(); |
| while (true) { |
| content::RenderFrameSubmissionObserver frame_submission_observer( |
| web_contents()); |
| // Runs a loop for 3 seconds to see if the renderer is idle. |
| { |
| base::RunLoop heart_beat; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, heart_beat.QuitClosure(), base::Seconds(3)); |
| heart_beat.Run(); |
| } |
| bool page_is_loading = |
| web_contents()->IsWaitingForResponse() || web_contents()->IsLoading(); |
| if (page_is_loading) { |
| finished_load_time = base::TimeTicks::Now(); |
| } else if ((base::TimeTicks::Now() - finished_load_time) > |
| continuous_paint_timeout) { |
| // |continuous_paint_timeout| has expired since Chrome loaded the page. |
| // During this period of time, Chrome has been continuously painting |
| // the page. In this case, the page is probably idle, but a bug, a |
| // blinking caret or a persistent animation is making Chrome paint at |
| // regular intervals. Exit. |
| break; |
| } else if (frame_submission_observer.render_frame_count() == 0) { |
| // If the renderer has stopped submitting frames for 3 seconds then |
| // we're done. |
| break; |
| } |
| } |
| } |
| |
| void RunStrictElementCheck(const Selector& selector, bool result) { |
| RunElementCheck(/* strict= */ true, selector, result); |
| } |
| |
| void RunLaxElementCheck(const Selector& selector, bool result) { |
| RunElementCheck(/* strict= */ false, selector, result); |
| } |
| |
| void RunElementCheck(bool strict, const Selector& selector, bool result) { |
| std::vector<Selector> selectors{selector}; |
| std::vector<bool> results{result}; |
| RunElementChecks(strict, selectors, results); |
| } |
| |
| void RunElementChecks(bool strict, |
| const std::vector<Selector>& selectors, |
| const std::vector<bool> results) { |
| base::RunLoop run_loop; |
| ASSERT_EQ(selectors.size(), results.size()); |
| size_t pending_number_of_checks = selectors.size(); |
| for (size_t i = 0; i < selectors.size(); i++) { |
| web_controller_->FindElement( |
| selectors[i], strict, |
| base::BindOnce(&WebControllerBrowserTest::ElementCheckCallback, |
| base::Unretained(this), run_loop.QuitClosure(), |
| selectors[i], &pending_number_of_checks, results[i])); |
| } |
| run_loop.Run(); |
| } |
| |
| void ElementCheckCallback( |
| base::OnceClosure done_callback, |
| const Selector& selector, |
| size_t* pending_number_of_checks_output, |
| bool expected_result, |
| const ClientStatus& result, |
| std::unique_ptr<ElementFinder::Result> ignored_element) { |
| EXPECT_EQ(expected_result, result.ok()) |
| << "selector: " << selector << " status: " << result; |
| *pending_number_of_checks_output -= 1; |
| if (*pending_number_of_checks_output == 0) { |
| std::move(done_callback).Run(); |
| } |
| } |
| |
| void WaitForElementRemove(const Selector& selector) { |
| base::RunLoop run_loop; |
| web_controller_->FindElement( |
| selector, /* strict= */ false, |
| base::BindOnce(&WebControllerBrowserTest::OnWaitForElementRemove, |
| base::Unretained(this), run_loop.QuitClosure(), |
| selector)); |
| run_loop.Run(); |
| } |
| |
| void OnWaitForElementRemove( |
| base::OnceClosure done_callback, |
| const Selector& selector, |
| const ClientStatus& result, |
| std::unique_ptr<ElementFinder::Result> ignored_element) { |
| std::move(done_callback).Run(); |
| if (result.ok()) { |
| WaitForElementRemove(selector); |
| } |
| } |
| |
| void OnClientStatus(base::OnceClosure done_callback, |
| ClientStatus* status_output, |
| const ClientStatus& status) { |
| *status_output = status; |
| std::move(done_callback).Run(); |
| } |
| |
| void OnClientStatusAndReadyState(base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| DocumentReadyState* ready_state_out, |
| const ClientStatus& status, |
| DocumentReadyState ready_state, |
| base::TimeDelta) { |
| *result_output = status; |
| *ready_state_out = ready_state; |
| std::move(done_callback).Run(); |
| } |
| |
| ClientStatus FindElementAndPerformAll( |
| const Selector& selector, |
| std::unique_ptr<element_action_util::ElementActionVector> |
| perform_actions) { |
| base::RunLoop run_loop; |
| ClientStatus status; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce(&element_action_util::TakeElementAndPerform, |
| base::BindOnce(&element_action_util::PerformAll, |
| std::move(perform_actions)), |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), |
| run_loop.QuitClosure(), &status))); |
| |
| run_loop.Run(); |
| return status; |
| } |
| |
| ClientStatus FindElementAndGetString( |
| const Selector& selector, |
| element_action_util::ElementActionGetCallback<const std::string&> |
| perform_and_get, |
| std::string* get_output) { |
| base::RunLoop run_loop; |
| ClientStatus status; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &element_action_util::TakeElementAndGetProperty<const std::string&>, |
| std::move(perform_and_get), std::string(), |
| base::BindOnce(&WebControllerBrowserTest::OnPerformAndGetString, |
| base::Unretained(this), run_loop.QuitClosure(), |
| &status, get_output))); |
| |
| run_loop.Run(); |
| return status; |
| } |
| |
| void OnPerformAndGetString(base::OnceClosure done_callback, |
| ClientStatus* status_output, |
| std::string* get_output, |
| const ClientStatus& status, |
| const std::string& get) { |
| *status_output = status; |
| *get_output = get; |
| std::move(done_callback).Run(); |
| } |
| |
| void ClickOrTapElement(const Selector& selector, ClickType click_type) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| if (click_type == ClickType::JAVASCRIPT) { |
| actions->emplace_back(base::BindOnce(&WebController::JsClickElement, |
| web_controller_->GetWeakPtr())); |
| } else { |
| actions->emplace_back(base::BindOnce( |
| &WebController::ScrollIntoView, web_controller_->GetWeakPtr(), |
| /* animation= */ std::string(), /* vertical_alignment= */ "center", |
| /* horizontal_alignment= */ "center")); |
| actions->emplace_back(base::BindOnce(&WebController::ClickOrTapElement, |
| web_controller_->GetWeakPtr(), |
| click_type)); |
| } |
| |
| const ClientStatus& status = |
| FindElementAndPerformAll(selector, std::move(actions)); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| } |
| |
| void ScrollToElementPosition( |
| const Selector& selector, |
| const TopPadding& top_padding, |
| const Selector& container_selector = Selector()) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| if (container_selector.empty()) { |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &element_action_util::TakeElementAndPerform, |
| base::BindOnce(&WebController::ScrollToElementPosition, |
| web_controller_->GetWeakPtr(), |
| /* container= */ nullptr, top_padding), |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), |
| &result))); |
| } else { |
| web_controller_->FindElement( |
| container_selector, /* strict_mode= */ true, |
| base::BindOnce(&WebControllerBrowserTest::FindContainerCallback, |
| base::Unretained(this), selector, top_padding, |
| run_loop.QuitClosure(), &result)); |
| } |
| |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| } |
| |
| void FindContainerCallback( |
| const Selector& selector, |
| const TopPadding& top_padding, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> container_result) { |
| if (!status.ok()) { |
| *result_output = status; |
| std::move(done_callback).Run(); |
| return; |
| } |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &element_action_util::TakeElementAndPerform, |
| base::BindOnce(&WebController::ScrollToElementPosition, |
| web_controller_->GetWeakPtr(), |
| std::move(container_result), top_padding), |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), std::move(done_callback), |
| result_output))); |
| } |
| |
| ClientStatus SelectOption( |
| const Selector& selector, |
| const std::string& re2, |
| bool case_sensitive, |
| SelectOptionProto::OptionComparisonAttribute option_comparison_attribute, |
| bool strict) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SelectOption, web_controller_->GetWeakPtr(), re2, |
| case_sensitive, option_comparison_attribute, strict)); |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus SelectOptionElement(const Selector& selector, |
| const ElementFinder::Result& option) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| actions->emplace_back(base::BindOnce(&WebController::SelectOptionElement, |
| web_controller_->GetWeakPtr(), |
| option)); |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus CheckSelectedOptionElement(const ElementFinder::Result& select, |
| const ElementFinder::Result& option) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->CheckSelectedOptionElement( |
| option, select, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), |
| &result)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| ClientStatus HighlightElement(const Selector& selector) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| actions->emplace_back(base::BindOnce(&WebController::HighlightElement, |
| web_controller_->GetWeakPtr())); |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus GetOuterHtml(const Selector& selector, |
| bool include_all_inner_text, |
| std::string* html_output) { |
| const ClientStatus& result = FindElementAndGetString( |
| selector, |
| base::BindOnce(&WebController::GetOuterHtml, |
| web_controller_->GetWeakPtr(), include_all_inner_text), |
| html_output); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| return result; |
| } |
| |
| ClientStatus GetOuterHtmls(const Selector& selector, |
| bool include_all_inner_text, |
| std::vector<std::string>* htmls_output) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindAllElements( |
| selector, |
| base::BindOnce( |
| &WebControllerBrowserTest::OnFindAllElementsForGetOuterHtmls, |
| base::Unretained(this), run_loop.QuitClosure(), &result, |
| htmls_output, include_all_inner_text)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| void OnFindAllElementsForGetOuterHtmls( |
| base::OnceClosure done_callback, |
| ClientStatus* client_status_output, |
| std::vector<std::string>* htmls_output, |
| bool include_all_inner_text, |
| const ClientStatus& client_status, |
| std::unique_ptr<ElementFinder::Result> elements) { |
| EXPECT_EQ(ACTION_APPLIED, client_status.proto_status()); |
| ASSERT_TRUE(elements); |
| |
| const ElementFinder::Result* elements_ptr = elements.get(); |
| web_controller_->GetOuterHtmls( |
| include_all_inner_text, *elements_ptr, |
| base::BindOnce(&WebControllerBrowserTest::OnGetOuterHtmls, |
| base::Unretained(this), std::move(elements), |
| std::move(done_callback), client_status_output, |
| htmls_output)); |
| } |
| |
| void OnGetOuterHtmls(std::unique_ptr<ElementFinder::Result> elements, |
| base::OnceClosure done_callback, |
| ClientStatus* client_status_output, |
| std::vector<std::string>* htmls_output, |
| const ClientStatus& client_status, |
| const std::vector<std::string>& htmls) { |
| *client_status_output = client_status; |
| *htmls_output = htmls; |
| std::move(done_callback).Run(); |
| } |
| |
| ClientStatus GetElementTag(const Selector& selector, |
| std::string* element_tag_output) { |
| const ClientStatus& result = |
| FindElementAndGetString(selector, |
| base::BindOnce(&WebController::GetElementTag, |
| web_controller_->GetWeakPtr()), |
| element_tag_output); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| return result; |
| } |
| |
| ClientStatus SendChangeEvent(const Selector& selector) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| actions->emplace_back(base::BindOnce(&WebController::SendChangeEvent, |
| web_controller_->GetWeakPtr())); |
| const ClientStatus& result = |
| FindElementAndPerformAll(selector, std::move(actions)); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| return result; |
| } |
| |
| ClientStatus GetStringAttribute(const Selector& selector, |
| const std::vector<std::string>& attributes, |
| std::string* value) { |
| return FindElementAndGetString( |
| selector, |
| base::BindOnce(&WebController::GetStringAttribute, |
| web_controller_->GetWeakPtr(), attributes), |
| value); |
| } |
| |
| ClientStatus CheckOnTop(const ElementFinder::Result& element) { |
| ClientStatus captured_status; |
| base::RunLoop run_loop; |
| web_controller_->CheckOnTop( |
| element, base::BindLambdaForTesting( |
| [&captured_status, &run_loop](const ClientStatus& status) { |
| captured_status = status; |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| return captured_status; |
| } |
| |
| ClientStatus WaitUntilElementIsStable(const ElementFinder::Result& element, |
| int max_rounds, |
| base::TimeDelta check_interval) { |
| ClientStatus captured_status; |
| base::RunLoop run_loop; |
| web_controller_->WaitUntilElementIsStable( |
| max_rounds, check_interval, element, |
| base::BindLambdaForTesting( |
| [&captured_status, &run_loop](const ClientStatus& status, |
| base::TimeDelta) { |
| captured_status = status; |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| return captured_status; |
| } |
| |
| void FindElement(const Selector& selector, |
| ClientStatus* status_out, |
| ElementFinder::Result* result_out) { |
| base::RunLoop run_loop; |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce(&WebControllerBrowserTest::OnFindElement, |
| base::Unretained(this), run_loop.QuitClosure(), |
| base::Unretained(status_out), |
| base::Unretained(result_out))); |
| run_loop.Run(); |
| } |
| |
| void OnFindElement(base::OnceClosure done_callback, |
| ClientStatus* status_out, |
| ElementFinder::Result* result_out, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> result) { |
| ASSERT_TRUE(result); |
| std::move(done_callback).Run(); |
| if (status_out) |
| *status_out = status; |
| |
| if (result_out) |
| *result_out = *result; |
| } |
| |
| void FindElementAndCheck(const Selector& selector, bool is_main_frame) { |
| SCOPED_TRACE(::testing::Message() << selector << " strict"); |
| ClientStatus status; |
| ElementFinder::Result result; |
| FindElement(selector, &status, &result); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| CheckFindElementResult(result, is_main_frame); |
| } |
| |
| void FindElementExpectEmptyResult(const Selector& selector) { |
| SCOPED_TRACE(::testing::Message() << selector << " strict"); |
| ClientStatus status; |
| ElementFinder::Result result; |
| FindElement(selector, &status, &result); |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status()); |
| EXPECT_THAT(result.object_id(), IsEmpty()); |
| } |
| |
| void CheckFindElementResult(const ElementFinder::Result& result, |
| bool is_main_frame) { |
| if (is_main_frame) { |
| EXPECT_EQ(shell()->web_contents()->GetMainFrame(), |
| result.container_frame_host); |
| EXPECT_EQ(result.frame_stack().size(), 0u); |
| } else { |
| EXPECT_NE(shell()->web_contents()->GetMainFrame(), |
| result.container_frame_host); |
| EXPECT_GE(result.frame_stack().size(), 1u); |
| } |
| EXPECT_FALSE(result.object_id().empty()); |
| } |
| |
| void GetFieldsValue(const std::vector<Selector>& selectors, |
| const std::vector<std::string>& expected_values) { |
| base::RunLoop run_loop; |
| ASSERT_EQ(selectors.size(), expected_values.size()); |
| size_t pending_number_of_checks = selectors.size(); |
| for (size_t i = 0; i < selectors.size(); i++) { |
| web_controller_->FindElement( |
| selectors[i], /* strict= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::GetFieldValueElementCallback, |
| base::Unretained(this), run_loop.QuitClosure(), |
| &pending_number_of_checks, expected_values[i])); |
| } |
| run_loop.Run(); |
| } |
| |
| void GetFieldValueElementCallback( |
| base::OnceClosure done_callback, |
| size_t* pending_number_of_checks_output, |
| const std::string& expected_value, |
| const ClientStatus& element_status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| if (!element_status.ok()) { |
| OnGetFieldValue(nullptr, std::move(done_callback), |
| pending_number_of_checks_output, expected_value, |
| element_status, std::string()); |
| return; |
| } |
| const ElementFinder::Result* element_result_ptr = element_result.get(); |
| web_controller_->GetFieldValue( |
| *element_result_ptr, |
| base::BindOnce(&WebControllerBrowserTest::OnGetFieldValue, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), |
| pending_number_of_checks_output, expected_value)); |
| } |
| |
| void OnGetFieldValue(std::unique_ptr<ElementFinder::Result> element, |
| base::OnceClosure done_callback, |
| size_t* pending_number_of_checks_output, |
| const std::string& expected_value, |
| const ClientStatus& status, |
| const std::string& value) { |
| // Don't use ASSERT: If the check fails, this would result in an endless |
| // loop without meaningful test results. |
| EXPECT_EQ(expected_value, value); |
| *pending_number_of_checks_output -= 1; |
| if (*pending_number_of_checks_output == 0) { |
| std::move(done_callback).Run(); |
| } |
| } |
| |
| ClientStatus SetFieldValue(const Selector& selector, |
| const std::string& value, |
| KeyboardValueFillStrategy fill_strategy) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| if (value.empty()) { |
| actions->emplace_back(base::BindOnce(&WebController::SetValueAttribute, |
| web_controller_->GetWeakPtr(), |
| std::string())); |
| } else { |
| switch (fill_strategy) { |
| case SET_VALUE: |
| actions->emplace_back( |
| base::BindOnce(&WebController::SetValueAttribute, |
| web_controller_->GetWeakPtr(), value)); |
| break; |
| case SIMULATE_KEY_PRESSES: |
| actions->emplace_back( |
| base::BindOnce(&WebController::SetValueAttribute, |
| web_controller_->GetWeakPtr(), std::string())); |
| actions->emplace_back(base::BindOnce( |
| &WebController::ScrollIntoView, web_controller_->GetWeakPtr(), |
| /* animation= */ std::string(), |
| /* vertical_alignment= */ "center", |
| /* horizontal_alignment= */ "center")); |
| actions->emplace_back( |
| base::BindOnce(&WebController::ClickOrTapElement, |
| web_controller_->GetWeakPtr(), ClickType::CLICK)); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, web_controller_->GetWeakPtr(), |
| UTF8ToUnicode(value), /* delay_in_milli= */ 0)); |
| break; |
| case SIMULATE_KEY_PRESSES_SELECT_VALUE: |
| actions->emplace_back(base::BindOnce(&WebController::SelectFieldValue, |
| web_controller_->GetWeakPtr())); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, web_controller_->GetWeakPtr(), |
| UTF8ToUnicode(value), /* delay_in_milli= */ 0)); |
| break; |
| case SIMULATE_KEY_PRESSES_FOCUS: |
| actions->emplace_back( |
| base::BindOnce(&WebController::SetValueAttribute, |
| web_controller_->GetWeakPtr(), std::string())); |
| actions->emplace_back(base::BindOnce(&WebController::FocusField, |
| web_controller_->GetWeakPtr())); |
| actions->emplace_back(base::BindOnce( |
| &WebController::SendKeyboardInput, web_controller_->GetWeakPtr(), |
| UTF8ToUnicode(value), /* delay_in_milli= */ 0)); |
| break; |
| case UNSPECIFIED_KEYBAORD_STRATEGY: |
| return ClientStatus(INVALID_ACTION); |
| } |
| } |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus SendKeyboardInput(const Selector& selector, |
| const std::vector<UChar32>& codepoints, |
| int delay_in_milli, |
| bool use_js_focus) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| if (use_js_focus) { |
| actions->emplace_back(base::BindOnce(&WebController::FocusField, |
| web_controller_->GetWeakPtr())); |
| } else { |
| actions->emplace_back(base::BindOnce( |
| &WebController::ScrollIntoView, web_controller_->GetWeakPtr(), |
| /* animation= */ std::string(), /* vertical_alignment= */ "center", |
| /* horizontal_alignment= */ "center")); |
| actions->emplace_back(base::BindOnce(&WebController::ClickOrTapElement, |
| web_controller_->GetWeakPtr(), |
| ClickType::CLICK)); |
| } |
| actions->emplace_back(base::BindOnce(&WebController::SendKeyboardInput, |
| web_controller_->GetWeakPtr(), |
| codepoints, delay_in_milli)); |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus SendKeyboardInput(const Selector& selector, |
| const std::vector<UChar32>& codepoints) { |
| return SendKeyboardInput(selector, codepoints, -1, false); |
| } |
| |
| ClientStatus SendKeyEvent(const Selector& selector, |
| const KeyEvent& key_event) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| actions->emplace_back(base::BindOnce(&WebController::FocusField, |
| web_controller_->GetWeakPtr())); |
| actions->emplace_back(base::BindOnce(&WebController::SendKeyEvent, |
| web_controller_->GetWeakPtr(), |
| key_event)); |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus SetAttribute(const Selector& selector, |
| const std::vector<std::string>& attributes, |
| const std::string& value) { |
| auto actions = std::make_unique<element_action_util::ElementActionVector>(); |
| actions->emplace_back(base::BindOnce(&WebController::SetAttribute, |
| web_controller_->GetWeakPtr(), |
| attributes, value)); |
| return FindElementAndPerformAll(selector, std::move(actions)); |
| } |
| |
| ClientStatus GetElementRect(const Selector& selector, RectF* rect_output) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict= */ true, |
| base::BindOnce(&WebControllerBrowserTest::GetElementRectElementCallback, |
| base::Unretained(this), run_loop.QuitClosure(), &result, |
| rect_output)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| void GetElementRectElementCallback( |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| RectF* rect_output, |
| const ClientStatus& element_status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| if (!element_status.ok()) { |
| *result_output = element_status; |
| std::move(done_callback).Run(); |
| return; |
| } |
| |
| ASSERT_TRUE(element_result != nullptr); |
| const ElementFinder::Result* element_result_ptr = element_result.get(); |
| web_controller_->GetElementRect( |
| *element_result_ptr, |
| base::BindOnce(&WebControllerBrowserTest::OnGetElementRect, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output, rect_output)); |
| } |
| |
| void OnGetElementRect(std::unique_ptr<ElementFinder::Result> element, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| RectF* rect_output, |
| const ClientStatus& rect_status, |
| const RectF& rect) { |
| if (rect_status.ok()) { |
| *rect_output = rect; |
| } |
| *result_output = rect_status; |
| std::move(done_callback).Run(); |
| } |
| |
| ClientStatus GetElementQueryIndex(const std::string& query_selector, |
| const ElementFinder::Result& element, |
| int* index) { |
| ClientStatus status; |
| |
| base::RunLoop run_loop; |
| web_controller_->GetElementQueryIndex( |
| query_selector, element, |
| base::BindOnce(&WebControllerBrowserTest::OnGetElementQueryIndex, |
| base::Unretained(this), run_loop.QuitClosure(), &status, |
| index)); |
| run_loop.Run(); |
| |
| return status; |
| } |
| |
| void OnGetElementQueryIndex(base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| int* index_output, |
| const ClientStatus& query_status, |
| int index) { |
| *result_output = query_status; |
| *index_output = index; |
| std::move(done_callback).Run(); |
| } |
| |
| ClientStatus GetUniqueElementSelector(const ElementFinder::Result& element, |
| std::string* query, |
| int* index) { |
| ClientStatus status; |
| |
| base::RunLoop run_loop; |
| web_controller_->GetUniqueElementSelector( |
| element, |
| base::BindOnce(&WebControllerBrowserTest::OnGetUniqueElementSelector, |
| base::Unretained(this), run_loop.QuitClosure(), &status, |
| query, index)); |
| run_loop.Run(); |
| |
| return status; |
| } |
| |
| void OnGetUniqueElementSelector(base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| std::string* query_output, |
| int* index_output, |
| const ClientStatus& query_status, |
| const std::string& query, |
| int index) { |
| *result_output = query_status; |
| *query_output = query; |
| *index_output = index; |
| std::move(done_callback).Run(); |
| } |
| |
| // Show the overlay in the main page, which covers everything. |
| void ShowOverlay() { |
| EXPECT_TRUE(ExecJs(shell(), |
| R"( |
| document.getElementById("overlay").style.visibility='visible'; |
| )")); |
| } |
| |
| // Show the overlay in the first iframe, which covers the content |
| // of that frame. |
| void ShowOverlayInFrame() { |
| EXPECT_TRUE(ExecJs(ChildFrameAt(shell()->web_contents(), 0), |
| R"( |
| document.getElementById("overlay_in_frame").style.visibility='visible'; |
| )")); |
| } |
| |
| // Hide the overlay in the main page. |
| void HideOverlay() { |
| EXPECT_TRUE(ExecJs(shell(), |
| R"( |
| document.getElementById("overlay").style.visibility='hidden'; |
| )")); |
| } |
| |
| // Hide the overlay in the first iframe. |
| void HideOverlayInFrame() { |
| EXPECT_TRUE(ExecJs(ChildFrameAt(shell()->web_contents(), 0), |
| R"( |
| document.getElementById("overlay_in_frame").style.visibility='hidden'; |
| )")); |
| } |
| |
| // Make sure scrolling is necessary for #scroll_container , no matter the |
| // screen height |
| void SetupScrollContainerHeights() { |
| EXPECT_TRUE(content::ExecJs(shell(), |
| R"( |
| let before = document.querySelector("#before_scroll_container"); |
| before.style.height = window.innerHeight + "px"; |
| let after = document.querySelector("#after_scroll_container"); |
| after.style.height = window.innerHeight + "px";)")); |
| } |
| |
| // Scrolls #scroll_container to the given y position. |
| void ScrollContainerTo(int y) { |
| EXPECT_TRUE(content::ExecJs(shell(), base::StringPrintf( |
| R"( |
| let container = document.querySelector("#scroll_container"); |
| container.scrollTo(0, %d);)", |
| y))); |
| } |
| |
| // Scrolls the window to the given y position. |
| void ScrollWindowTo(int y) { |
| EXPECT_TRUE(content::ExecJs( |
| shell(), base::StringPrintf("window.scrollTo(0, %d);", y))); |
| } |
| |
| // Scroll an element into view that's within a container element. This |
| // requires scrolling the container, then the window, to get the element to |
| // the desired y position. |
| void TestScrollIntoView(int initial_window_scroll_y, |
| int initial_container_scroll_y) { |
| Selector selector({"#scroll_item_5"}); |
| |
| SetupScrollContainerHeights(); |
| ScrollWindowTo(initial_window_scroll_y); |
| ScrollContainerTo(initial_window_scroll_y); |
| |
| TopPadding top_padding{0.25, TopPadding::Unit::RATIO}; |
| ScrollToElementPosition(selector, top_padding); |
| base::Value eval_result = content::EvalJs(shell(), R"( |
| let item = document.querySelector("#scroll_item_5"); |
| let itemRect = item.getBoundingClientRect(); |
| let container = document.querySelector("#scroll_container"); |
| let containerRect = container.getBoundingClientRect(); |
| [itemRect.top, itemRect.bottom, window.innerHeight, |
| containerRect.top, containerRect.bottom])") |
| .ExtractList(); |
| double top = eval_result.GetList()[0].GetDouble(); |
| double bottom = eval_result.GetList()[1].GetDouble(); |
| double window_height = eval_result.GetList()[2].GetDouble(); |
| double container_top = eval_result.GetList()[3].GetDouble(); |
| double container_bottom = eval_result.GetList()[4].GetDouble(); |
| |
| // Element is at the desired position. (top is relative to the viewport) |
| EXPECT_NEAR(top, window_height * 0.25, 1); |
| |
| // Element is within the visible portion of its container. |
| EXPECT_GT(bottom, container_top); |
| EXPECT_LT(top, container_bottom); |
| } |
| |
| // Send a Runtime.Evaluate protocol message. Useful for evaluating JS in the |
| // page as there is no ordering guarantee between protocol messages and e.g. |
| // ExecJs(). |
| void RuntimeEvaluate(const std::string& code) { |
| web_controller_->devtools_client_->GetRuntime()->Evaluate( |
| code, |
| /* node_frame_id= */ std::string()); |
| } |
| |
| protected: |
| std::unique_ptr<WebController> web_controller_; |
| UserData user_data_; |
| UserModel user_model_; |
| |
| private: |
| std::unique_ptr<net::EmbeddedTestServer> http_server_; |
| std::unique_ptr<net::EmbeddedTestServer> http_server_iframe_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementExistenceCheck) { |
| // A visible element |
| RunLaxElementCheck(Selector({"#button"}), true); |
| |
| // A hidden element. |
| RunLaxElementCheck(Selector({"#hidden"}), true); |
| |
| // A nonexistent element. |
| RunLaxElementCheck(Selector({"#doesnotexist"}), false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoElementChecks) { |
| // A pseudo-element |
| RunLaxElementCheck(Selector({"#terms-and-conditions"}).SetPseudoType(BEFORE), |
| true); |
| |
| // An invisible pseudo-element |
| // |
| // TODO(b/129461999): This is wrong; it should exist. Fix it. |
| RunLaxElementCheck(Selector({"#button"}).SetPseudoType(BEFORE), false); |
| |
| // A non-existent pseudo-element |
| RunLaxElementCheck(Selector({"#button"}).SetPseudoType(AFTER), false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementInFrameChecks) { |
| // An iFrame. |
| RunLaxElementCheck(Selector({"#iframe"}), true); |
| |
| // An element in a same-origin iFrame. |
| RunLaxElementCheck(Selector({"#iframe", "#button"}), true); |
| |
| // An element in a same-origin iFrame. |
| RunLaxElementCheck(Selector({"#iframe", "#doesnotexist"}), false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementInExternalFrameChecks) { |
| // An OOPIF. |
| RunLaxElementCheck(Selector({"#iframeExternal"}), true); |
| |
| // An element in an OOPIF. |
| RunLaxElementCheck(Selector({"#iframeExternal", "#button"}), true); |
| |
| // An element in an OOPIF. |
| RunLaxElementCheck(Selector({"#iframeExternal", "#doesnotexist"}), false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, VisibilityRequirementCheck) { |
| // A visible element |
| RunLaxElementCheck(Selector({"#button"}).MustBeVisible(), true); |
| |
| // A hidden element. |
| RunLaxElementCheck(Selector({"#hidden"}).MustBeVisible(), false); |
| |
| // A non-existent element |
| RunLaxElementCheck(Selector({"#doesnotexist"}).MustBeVisible(), false); |
| |
| // A pseudo-element |
| RunLaxElementCheck( |
| Selector({"#terms-and-conditions"}).MustBeVisible().SetPseudoType(BEFORE), |
| true); |
| |
| // An invisible pseudo-element |
| RunLaxElementCheck( |
| Selector({"#button"}).MustBeVisible().SetPseudoType(BEFORE), false); |
| |
| // A non-existent pseudo-element |
| RunLaxElementCheck(Selector({"#button"}).MustBeVisible().SetPseudoType(AFTER), |
| false); |
| |
| // An iFrame. |
| RunLaxElementCheck(Selector({"#iframe"}).MustBeVisible(), true); |
| |
| // An element in a same-origin iFrame. |
| RunLaxElementCheck(Selector({"#iframe", "#button"}).MustBeVisible(), true); |
| |
| // An OOPIF. |
| RunLaxElementCheck(Selector({"#iframeExternal"}).MustBeVisible(), true); |
| |
| // An element in an OOPIF. |
| RunLaxElementCheck(Selector({"#iframeExternal", "#button"}).MustBeVisible(), |
| true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, RequireNonemptyBoundingBox) { |
| Selector button = Selector({"#button"}); |
| button.proto.add_filters()->mutable_bounding_box()->set_require_nonempty( |
| true); |
| RunLaxElementCheck(button, true); |
| |
| Selector hidden = Selector({"#hidden"}); |
| RunLaxElementCheck(hidden, true); |
| hidden.proto.add_filters()->mutable_bounding_box()->set_require_nonempty( |
| true); |
| RunLaxElementCheck(hidden, false); |
| |
| Selector emptydiv = Selector({"#emptydiv"}); |
| RunLaxElementCheck(emptydiv, true); |
| auto* emptydiv_box = emptydiv.proto.add_filters()->mutable_bounding_box(); |
| emptydiv_box->set_require_nonempty(true); |
| RunLaxElementCheck(emptydiv, false); |
| emptydiv_box->set_require_nonempty(false); |
| RunLaxElementCheck(emptydiv, true); |
| |
| EXPECT_TRUE(content::ExecJs(shell(), R"( |
| document.getElementById("emptydiv").style.height = '100px'; |
| )")); |
| RunLaxElementCheck(emptydiv, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MultipleVisibleElementCheck) { |
| // both visible |
| RunLaxElementCheck(Selector({"#button,#select"}).MustBeVisible(), true); |
| RunStrictElementCheck(Selector({"#button,#select"}).MustBeVisible(), false); |
| |
| // one visible (first non-visible) |
| RunLaxElementCheck(Selector({"#hidden,#select"}).MustBeVisible(), true); |
| RunStrictElementCheck(Selector({"#hidden,#select"}).MustBeVisible(), true); |
| |
| // one visible (first visible) |
| RunLaxElementCheck(Selector({"#button,#hidden"}).MustBeVisible(), true); |
| RunStrictElementCheck(Selector({"#hidden,#select"}).MustBeVisible(), true); |
| |
| // one invisible, one non-existent |
| RunLaxElementCheck(Selector({"#doesnotexist,#hidden"}).MustBeVisible(), |
| false); |
| RunStrictElementCheck(Selector({"#doesnotexist,#hidden"}).MustBeVisible(), |
| false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SearchMultipleIframes) { |
| // There are two "iframe" elements in the document so the selector would need |
| // to search in both iframes, which isn't supported. |
| SelectorProto proto; |
| proto.add_filters()->set_css_selector("iframe"); |
| proto.add_filters()->mutable_enter_frame(); |
| proto.add_filters()->set_css_selector("#element_in_iframe_two"); |
| |
| ClientStatus status; |
| FindElement(Selector(proto), &status, nullptr); |
| EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, InnerTextCondition) { |
| const Selector base_selector({"#with_inner_text span"}); |
| Selector selector = base_selector; |
| selector.MustBeVisible(); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector.MustBeVisible(), false); |
| |
| // No matches |
| selector = base_selector; |
| selector.MatchingInnerText("no match"); |
| RunLaxElementCheck(selector, false); |
| selector.MustBeVisible(); |
| RunLaxElementCheck(selector, false); |
| |
| // Matches exactly one visible element. |
| selector = base_selector; |
| selector.MatchingInnerText("hello, world"); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, true); |
| selector.MustBeVisible(); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, true); |
| |
| // Matches case (in)sensitive. |
| selector = base_selector; |
| selector.MatchingInnerText("HELLO, WORLD", /* case_sensitive=*/false); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, true); |
| selector = base_selector; |
| selector.MatchingInnerText("HELLO, WORLD", /* case_sensitive=*/true); |
| RunLaxElementCheck(selector, false); |
| RunStrictElementCheck(selector, false); |
| |
| // Matches two visible elements |
| selector = base_selector; |
| selector.MatchingInnerText("^hello"); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, false); |
| selector.MustBeVisible(); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, false); |
| |
| // Matches one visible, one invisible element |
| selector = base_selector; |
| selector.MatchingInnerText("world$"); |
| RunLaxElementCheck(selector, true); |
| selector.MustBeVisible(); |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeAndInnerText) { |
| // Inner text conditions then pseudo type vs pseudo type then inner text |
| // condition. |
| Selector selector({"#with_inner_text span"}); |
| selector.MatchingInnerText("world"); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| RunLaxElementCheck(selector, true); |
| |
| // "before" is the content of the :before, checking the text of pseudo-types |
| // doesn't work. |
| selector = Selector({"#with_inner_text span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| selector.MatchingInnerText("before"); |
| RunLaxElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MultipleBefore) { |
| Selector selector({"span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| |
| // There's more than one "span" with a before, so only a lax check can |
| // succeed. |
| RunLaxElementCheck(selector, true); |
| RunStrictElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenBoundingBox) { |
| Selector selector({"span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| selector.proto.add_filters()->mutable_bounding_box(); |
| |
| RunLaxElementCheck(selector, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenPickOne) { |
| Selector selector({"span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| selector.proto.add_filters()->mutable_nth_match()->set_index(0); |
| |
| RunStrictElementCheck(selector, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenCss) { |
| Selector selector({"span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| selector.proto.add_filters()->set_css_selector("div"); |
| |
| // This makes no sense, but shouldn't return an unexpected error. |
| ClientStatus status; |
| ElementFinder::Result result; |
| FindElement(selector, &status, &result); |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeThenInnerText) { |
| Selector selector({"span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| selector.proto.add_filters()->mutable_inner_text()->set_re2("before"); |
| |
| // This isn't supported yet. |
| RunLaxElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, PseudoTypeContent) { |
| Selector selector({"#with_inner_text span"}); |
| auto* content = |
| selector.proto.add_filters()->mutable_pseudo_element_content(); |
| content->set_pseudo_type(PseudoType::BEFORE); |
| content->mutable_content()->set_re2("before"); |
| RunLaxElementCheck(selector, true); |
| |
| content->mutable_content()->set_re2("nomatch"); |
| RunLaxElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| PseudoElementContentWithCssStyle) { |
| Selector selector({"#with_inner_text span"}); |
| auto* style = selector.proto.add_filters()->mutable_css_style(); |
| style->set_property("content"); |
| style->set_pseudo_element("before"); |
| style->mutable_value()->set_re2("\"before\""); |
| RunLaxElementCheck(selector, true); |
| |
| style->mutable_value()->set_re2("\"nomatch\""); |
| RunLaxElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, CssVisibility) { |
| Selector selector({"#button"}); |
| auto* style = selector.proto.add_filters()->mutable_css_style(); |
| style->set_property("visibility"); |
| style->mutable_value()->set_re2("visible"); |
| |
| EXPECT_TRUE(content::ExecJs(shell(), R"( |
| document.getElementById("button").style.visibility = 'hidden'; |
| )")); |
| RunLaxElementCheck(selector, false); |
| style->set_should_match(false); |
| RunLaxElementCheck(selector, true); |
| |
| EXPECT_TRUE(content::ExecJs(shell(), R"( |
| document.getElementById("button").style.visibility = 'visible'; |
| )")); |
| style->set_should_match(true); |
| RunLaxElementCheck(selector, true); |
| style->set_should_match(false); |
| RunLaxElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, InnerTextThenCss) { |
| // There are two divs containing "Section with text", but only one has a |
| // button, which removes #button. |
| SelectorProto proto; |
| proto.add_filters()->set_css_selector("div"); |
| proto.add_filters()->mutable_inner_text()->set_re2("Section with text"); |
| proto.add_filters()->set_css_selector("button"); |
| |
| ClickOrTapElement(Selector(proto), ClickType::CLICK); |
| WaitForElementRemove(Selector({"#button"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindFormInputByLabel) { |
| // #option1_label refers to the labelled control by id. |
| Selector option1; |
| option1.proto.add_filters()->set_css_selector("#option1_label"); |
| option1.proto.add_filters()->mutable_labelled(); |
| |
| const std::string option1_checked = R"( |
| document.querySelector("#option1").checked; |
| )"; |
| EXPECT_FALSE(content::EvalJs(shell(), option1_checked).ExtractBool()); |
| ClickOrTapElement(option1, ClickType::CLICK); |
| EXPECT_TRUE(content::EvalJs(shell(), option1_checked).ExtractBool()); |
| |
| // #option2 contains the labelled control. |
| Selector option2; |
| option2.proto.add_filters()->set_css_selector("#option2_label"); |
| option2.proto.add_filters()->mutable_labelled(); |
| |
| const std::string option2_checked = R"( |
| document.querySelector("#option2").checked; |
| )"; |
| EXPECT_FALSE(content::EvalJs(shell(), option2_checked).ExtractBool()); |
| ClickOrTapElement(option2, ClickType::CLICK); |
| EXPECT_TRUE(content::EvalJs(shell(), option2_checked).ExtractBool()); |
| |
| // #button is not a label. |
| Selector not_a_label; |
| not_a_label.proto.add_filters()->set_css_selector("#button"); |
| not_a_label.proto.add_filters()->mutable_labelled(); |
| |
| // #bad_label1 and #bad_label2 are labels that don't reference a valid |
| // element. They must not cause JavaScript errors. |
| Selector bad_label1; |
| bad_label1.proto.add_filters()->set_css_selector("#bad_label1"); |
| bad_label1.proto.add_filters()->mutable_labelled(); |
| |
| ClientStatus status; |
| FindElement(bad_label1, &status, nullptr); |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status()); |
| |
| Selector bad_label2; |
| bad_label2.proto.add_filters()->set_css_selector("#bad_label2"); |
| bad_label2.proto.add_filters()->mutable_labelled(); |
| |
| FindElement(bad_label2, &status, nullptr); |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, MatchCssSelectorFilter) { |
| Selector selector({"label"}); |
| selector.MatchingInnerText("terms and conditions"); |
| selector.proto.add_filters()->mutable_labelled(); |
| |
| RunStrictElementCheck(selector, true); |
| |
| auto* last_filter = selector.proto.add_filters(); |
| |
| last_filter->set_match_css_selector("input[type='checkbox']"); |
| RunStrictElementCheck(selector, true); |
| |
| last_filter->set_match_css_selector("input[type='text']"); |
| RunStrictElementCheck(selector, false); |
| |
| last_filter->set_match_css_selector(":checked"); |
| RunStrictElementCheck(selector, false); |
| |
| last_filter->set_match_css_selector(":not(:checked)"); |
| RunStrictElementCheck(selector, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ValueCondition) { |
| // One match |
| RunLaxElementCheck(Selector({"#input1"}).MatchingValue("helloworld1"), true); |
| RunStrictElementCheck(Selector({"#input1"}).MatchingValue("helloworld1"), |
| true); |
| |
| // Case (in)sensitive match |
| RunLaxElementCheck(Selector({"#input1"}).MatchingValue("HELLOWORLD1", false), |
| true); |
| RunLaxElementCheck(Selector({"#input1"}).MatchingValue("HELLOWORLD1", true), |
| false); |
| RunStrictElementCheck( |
| Selector({"#input1"}).MatchingValue("HELLOWORLD1", false), true); |
| RunStrictElementCheck( |
| Selector({"#input1"}).MatchingValue("HELLOWORLD1", true), false); |
| |
| // No matches |
| RunLaxElementCheck(Selector({"#input2"}).MatchingValue("doesnotmatch"), |
| false); |
| RunStrictElementCheck(Selector({"#input2"}).MatchingValue("doesnotmatch"), |
| false); |
| |
| // Multiple matches |
| RunLaxElementCheck(Selector({"#input1,#input2"}).MatchingValue("^hello"), |
| true); |
| RunStrictElementCheck(Selector({"#input1,#input2"}).MatchingValue("^hello"), |
| false); |
| |
| // Multiple selector matches, one value match |
| RunLaxElementCheck(Selector({"#input1,#input2"}).MatchingValue("helloworld1"), |
| true); |
| RunStrictElementCheck( |
| Selector({"#input1,#input2"}).MatchingValue("helloworld1"), true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ConcurrentElementsVisibilityCheck) { |
| std::vector<Selector> selectors; |
| std::vector<bool> results; |
| |
| Selector visible_button({"#button"}); |
| visible_button.MustBeVisible(); |
| selectors.emplace_back(visible_button); |
| results.emplace_back(true); |
| |
| Selector visible_with_iframe({"#button", "#watever"}); |
| visible_with_iframe.MustBeVisible(); |
| selectors.emplace_back(visible_with_iframe); |
| results.emplace_back(false); |
| |
| // IFrame. |
| selectors.emplace_back(Selector({"#iframe", "#button"})); |
| results.emplace_back(true); |
| |
| selectors.emplace_back(Selector({"#iframe", "#button", "#whatever"})); |
| results.emplace_back(false); |
| |
| selectors.emplace_back(Selector({"#iframe", "[name=name]"})); |
| results.emplace_back(true); |
| |
| // OOPIF. |
| selectors.emplace_back(Selector({"#iframeExternal", "#button"})); |
| results.emplace_back(true); |
| |
| // Shadow DOM. |
| selectors.emplace_back( |
| Selector({"#iframe", "#shadowsection", "#shadowbutton"})); |
| results.emplace_back(true); |
| |
| selectors.emplace_back( |
| Selector({"#iframe", "#shadowsection", "#shadowbutton", "#whatever"})); |
| results.emplace_back(false); |
| |
| // IFrame inside IFrame. |
| selectors.emplace_back(Selector({"#iframe", "#iframe", "#button"})); |
| results.emplace_back(true); |
| |
| selectors.emplace_back( |
| Selector({"#iframe", "#iframe", "#button", "#whatever"})); |
| results.emplace_back(false); |
| |
| // Hidden element. |
| selectors.emplace_back(Selector({"#hidden"}).MustBeVisible()); |
| results.emplace_back(false); |
| |
| RunElementChecks(/* strict= */ false, selectors, results); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElement) { |
| Selector selector({"#button"}); |
| ClickOrTapElement(selector, ClickType::CLICK); |
| |
| WaitForElementRemove(selector); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInIFrame) { |
| ClickOrTapElement(Selector({"#iframe", "#shadowsection", "#shadowbutton"}), |
| ClickType::CLICK); |
| |
| WaitForElementRemove(Selector({"#iframe", "#button"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickElementInOOPIF) { |
| ClickOrTapElement(Selector({"#iframeExternal", "#button"}), ClickType::CLICK); |
| |
| WaitForElementRemove(Selector({"#iframeExternal", "#div"})); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ClickElementInScrollContainer) { |
| // Make sure #scroll_item_3 is not visible, no matter the screen height. It |
| // also makes sure that there's enough room on the visual viewport to scroll |
| // everything to the center. |
| SetupScrollContainerHeights(); |
| ScrollWindowTo(0); |
| ScrollContainerTo(0); |
| |
| EXPECT_TRUE(content::ExecJs(shell(), |
| R"(var scrollItem3WasClicked = false; |
| let item = document.querySelector("#scroll_item_3"); |
| item.addEventListener("click", function() { |
| scrollItem3WasClicked = true; |
| });)")); |
| |
| Selector selector({"#scroll_item_3"}); |
| ClickOrTapElement(selector, ClickType::CLICK); |
| |
| EXPECT_TRUE(content::EvalJs(shell(), "scrollItem3WasClicked").ExtractBool()); |
| |
| // TODO(b/135909926): Find a reliable way of verifying that the button was |
| // mover roughly to the center. |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapElement) { |
| Selector area_five({"#touch_area_five"}); |
| ClickOrTapElement(area_five, ClickType::TAP); |
| WaitForElementRemove(area_five); |
| |
| Selector area_one({"#touch_area_one"}); |
| ClickOrTapElement(area_one, ClickType::TAP); |
| WaitForElementRemove(area_one); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| DISABLED_TapElementMovingOutOfView) { |
| Selector selector({"#touch_area_three"}); |
| ClickOrTapElement(selector, ClickType::TAP); |
| WaitForElementRemove(selector); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| DISABLED_TapElementAfterPageIsIdle) { |
| // Set a very long timeout to make sure either the page is idle or the test |
| // timeout. |
| WaitTillPageIsIdle(base::Hours(1)); |
| |
| Selector selector({"#touch_area_one"}); |
| ClickOrTapElement(selector, ClickType::TAP); |
| |
| WaitForElementRemove(selector); |
| } |
| |
| // TODO(crbug.com/920948) Disabled for strong flakiness. |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, DISABLED_TapElementInIFrame) { |
| Selector selector({"#iframe", "#touch_area"}); |
| ClickOrTapElement(selector, ClickType::TAP); |
| |
| WaitForElementRemove(selector); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| DISABLED_TapRandomMovingElementRepeatedly) { |
| Selector button_selector({"#random_moving_button"}); |
| int num_clicks = 100; |
| for (int i = 0; i < num_clicks; ++i) { |
| ClickOrTapElement(button_selector, ClickType::JAVASCRIPT); |
| } |
| |
| std::vector<Selector> click_counter_selectors; |
| std::vector<std::string> expected_values; |
| expected_values.emplace_back(base::NumberToString(num_clicks)); |
| Selector click_counter_selector({"#random_moving_click_counter"}); |
| click_counter_selectors.emplace_back(click_counter_selector); |
| GetFieldsValue(click_counter_selectors, expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapMovingElementRepeatedly) { |
| Selector button_selector({"#moving_button"}); |
| int num_clicks = 100; |
| for (int i = 0; i < num_clicks; ++i) { |
| ClickOrTapElement(button_selector, ClickType::JAVASCRIPT); |
| } |
| |
| std::vector<Selector> click_counter_selectors; |
| std::vector<std::string> expected_values; |
| expected_values.emplace_back(base::NumberToString(num_clicks)); |
| Selector click_counter_selector({"#moving_click_counter"}); |
| click_counter_selectors.emplace_back(click_counter_selector); |
| GetFieldsValue(click_counter_selectors, expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, TapStaticElementRepeatedly) { |
| Selector button_selector({"#static_button"}); |
| |
| int num_clicks = 100; |
| for (int i = 0; i < num_clicks; ++i) { |
| ClickOrTapElement(button_selector, ClickType::JAVASCRIPT); |
| } |
| |
| std::vector<Selector> click_counter_selectors; |
| std::vector<std::string> expected_values; |
| expected_values.emplace_back(base::NumberToString(num_clicks)); |
| Selector click_counter_selector({"#static_click_counter"}); |
| click_counter_selectors.emplace_back(click_counter_selector); |
| GetFieldsValue(click_counter_selectors, expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ClickPseudoElement) { |
| const std::string javascript = R"( |
| document.querySelector("#terms-and-conditions").checked; |
| )"; |
| EXPECT_FALSE(content::EvalJs(shell(), javascript).ExtractBool()); |
| Selector selector({R"(label[for="terms-and-conditions"])"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| ClickOrTapElement(selector, ClickType::CLICK); |
| EXPECT_TRUE(content::EvalJs(shell(), javascript).ExtractBool()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElement) { |
| Selector selector({"#button"}); |
| FindElementAndCheck(selector, true); |
| selector.MustBeVisible(); |
| FindElementAndCheck(selector, true); |
| |
| // IFrame. |
| selector = Selector({"#iframe", "#button"}); |
| FindElementAndCheck(selector, false); |
| selector.MustBeVisible(); |
| FindElementAndCheck(selector, false); |
| |
| selector = Selector({"#iframe", "[name=name]"}); |
| FindElementAndCheck(selector, false); |
| selector.MustBeVisible(); |
| FindElementAndCheck(selector, false); |
| |
| // IFrame inside IFrame. |
| selector = Selector({"#iframe", "#iframe", "#button"}); |
| FindElementAndCheck(selector, false); |
| selector.MustBeVisible(); |
| FindElementAndCheck(selector, false); |
| |
| // OutOfProcessIFrame. |
| selector = Selector({"#iframeExternal", "#button"}); |
| FindElementAndCheck(selector, false); |
| selector.MustBeVisible(); |
| FindElementAndCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementNotFound) { |
| FindElementExpectEmptyResult(Selector({"#notfound"})); |
| FindElementExpectEmptyResult(Selector({"#hidden"}).MustBeVisible()); |
| FindElementExpectEmptyResult(Selector({"#iframe", "#iframe", "#notfound"})); |
| FindElementExpectEmptyResult( |
| Selector({"#iframe", "#iframe", "#hidden"}).MustBeVisible()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindElementErrorStatus) { |
| ClientStatus status; |
| |
| FindElement(Selector(SelectorProto::default_instance()), &status, nullptr); |
| EXPECT_EQ(INVALID_SELECTOR, status.proto_status()); |
| |
| FindElement(Selector({"#doesnotexist"}), &status, nullptr); |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, status.proto_status()); |
| |
| FindElement(Selector({"div"}), &status, nullptr); |
| EXPECT_EQ(TOO_MANY_ELEMENTS, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ScrollToElementPosition) { |
| Selector selector({"#iframe", "#focus"}); |
| |
| const std::string checkVisibleScript = R"( |
| let iframe = document.querySelector("#iframe"); |
| let div = iframe.contentDocument.querySelector("#focus"); |
| let iframeRect = iframe.getBoundingClientRect(); |
| let divRect = div.getBoundingClientRect(); |
| iframeRect.y + divRect.y < window.innerHeight; |
| )"; |
| EXPECT_EQ(false, content::EvalJs(shell(), checkVisibleScript)); |
| TopPadding top_padding; |
| ScrollToElementPosition(selector, top_padding); |
| EXPECT_EQ(true, content::EvalJs(shell(), checkVisibleScript)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ScrollToElementPositionWithContainer) { |
| Selector selector({"#scroll_item_2"}); |
| Selector container_selector({"#scroll_container"}); |
| |
| TopPadding top_padding{0, TopPadding::Unit::PIXELS}; |
| |
| // std::this_thread::sleep_for(std::chrono::milliseconds(3000)); |
| ScrollToElementPosition(selector, top_padding, container_selector); |
| base::Value eval_result = content::EvalJs(shell(), R"( |
| let item = document.querySelector("#scroll_item_2"); |
| let itemRect = item.getBoundingClientRect(); |
| let container = document.querySelector("#scroll_container"); |
| let containerRect = container.getBoundingClientRect(); |
| [itemRect.top, containerRect.top])") |
| .ExtractList(); |
| double element_top = eval_result.GetList()[0].GetDouble(); |
| double container_top = eval_result.GetList()[1].GetDouble(); |
| |
| // Element is at the desired position. |
| EXPECT_NEAR(element_top, container_top, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ScrollToElementPosition_WithScrollIntoViewNeeded) { |
| TestScrollIntoView(/* initial_window_scroll_y= */ 0, |
| /* initial_container_scroll_y=*/0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ScrollToElementPosition_WithScrollIntoViewNotNeeded) { |
| TestScrollIntoView(/* initial_window_scroll_y= */ 0, |
| /* initial_container_scroll_y=*/200); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ScrollToElementPosition_WithPaddingInPixels) { |
| Selector selector({"#scroll-me"}); |
| |
| const std::string checkScrollDifferentThanTargetScript = R"( |
| window.scrollTo(0, 0); |
| let scrollTarget = document.querySelector("#scroll-me"); |
| let scrollTargetRect = scrollTarget.getBoundingClientRect(); |
| scrollTargetRect.y > 360; |
| )"; |
| |
| EXPECT_EQ(true, |
| content::EvalJs(shell(), checkScrollDifferentThanTargetScript)); |
| |
| // Scroll 360px from the top. |
| TopPadding top_padding{/* value= */ 360, TopPadding::Unit::PIXELS}; |
| ScrollToElementPosition(selector, top_padding); |
| |
| double eval_result = content::EvalJs(shell(), R"( |
| let scrollTarget = document.querySelector("#scroll-me"); |
| let scrollTargetRect = scrollTarget.getBoundingClientRect(); |
| scrollTargetRect.top; |
| )") |
| .ExtractDouble(); |
| |
| EXPECT_NEAR(360, eval_result, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ScrollToElementPosition_WithPaddingInRatio) { |
| Selector selector({"#scroll-me"}); |
| |
| const std::string checkScrollDifferentThanTargetScript = R"( |
| window.scrollTo(0, 0); |
| let scrollTarget = document.querySelector("#scroll-me"); |
| let scrollTargetRect = scrollTarget.getBoundingClientRect(); |
| let targetScrollY = window.innerHeight * 0.7; |
| scrollTargetRect.y > targetScrollY; |
| )"; |
| |
| EXPECT_EQ(true, |
| content::EvalJs(shell(), checkScrollDifferentThanTargetScript)); |
| |
| // Scroll 70% from the top. |
| TopPadding top_padding{/* value= */ 0.7, TopPadding::Unit::RATIO}; |
| ScrollToElementPosition(selector, top_padding); |
| |
| base::Value eval_result = content::EvalJs(shell(), R"( |
| let scrollTarget = document.querySelector("#scroll-me"); |
| let scrollTargetRect = scrollTarget.getBoundingClientRect(); |
| [scrollTargetRect.top, window.innerHeight] |
| )") |
| .ExtractList(); |
| |
| double top = eval_result.GetList()[0].GetDouble(); |
| double window_inner_height = eval_result.GetList()[1].GetDouble(); |
| |
| EXPECT_NEAR(top, window_inner_height * 0.7, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOption) { |
| Selector selector({"#select"}); |
| |
| const std::string javascript = R"( |
| let select = document.querySelector("#select"); |
| select.options[select.selectedIndex].label; |
| )"; |
| |
| // Selecting on a non-<select> element. |
| EXPECT_EQ(INVALID_TARGET, |
| SelectOption(Selector({"#input1"}), std::string(), |
| /* case_sensitive= */ false, SelectOptionProto::LABEL, |
| /* strict= */ true) |
| .proto_status()); |
| |
| // Selecting with an invalid regular expression. |
| EXPECT_EQ(INVALID_ACTION, |
| SelectOption(selector, "*", |
| /* case_sensitive= */ false, SelectOptionProto::LABEL, |
| /* strict= */ true) |
| .proto_status()); |
| |
| // Fails if no comparison attribute is set. |
| EXPECT_EQ(INVALID_ACTION, |
| SelectOption(selector, "one", /* case_sensitive= */ false, |
| SelectOptionProto::NOT_SET, /* strict= */ true) |
| .proto_status()); |
| |
| // Select value not matching anything. |
| EXPECT_EQ(OPTION_VALUE_NOT_FOUND, |
| SelectOption(selector, "incorrect label", |
| /* case_sensitive= */ false, SelectOptionProto::LABEL, |
| /* strict= */ true) |
| .proto_status()); |
| |
| // Select value matching everything. |
| EXPECT_EQ(TOO_MANY_OPTION_VALUES_FOUND, |
| SelectOption(selector, ".*", /* case_sensitive= */ false, |
| SelectOptionProto::LABEL, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(selector, ".*", /* case_sensitive= */ false, |
| SelectOptionProto::LABEL, /* strict= */ false) |
| .proto_status()); |
| EXPECT_EQ("One", content::EvalJs(shell(), javascript)); |
| |
| // Select value matching the option's label. |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(selector, "^ZÜRICH", /* case_sensitive= */ false, |
| SelectOptionProto::LABEL, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ("Zürich Hauptbahnhof", content::EvalJs(shell(), javascript)); |
| |
| // Select value matching the option's value. |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(selector, "^Aü万𠜎$", /* case_sensitive= */ false, |
| SelectOptionProto::VALUE, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ("Character Test Entry", content::EvalJs(shell(), javascript)); |
| |
| // With a regular expression matching the option's value. |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(selector, "^O.E$", /* case_sensitive= */ false, |
| SelectOptionProto::VALUE, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ("One", content::EvalJs(shell(), javascript)); |
| |
| // With a regular expression matching the option's value case sensitive. |
| EXPECT_EQ(OPTION_VALUE_NOT_FOUND, |
| SelectOption(selector, "^O.E$", /* case_sensitive= */ true, |
| SelectOptionProto::VALUE, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ("One", content::EvalJs(shell(), javascript)); |
| |
| // Ignore disabled options. |
| EXPECT_EQ(OPTION_VALUE_NOT_FOUND, |
| SelectOption(selector, "five", /* case_sensitive= */ true, |
| SelectOptionProto::VALUE, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(selector, "^\\w{4}$", /* case_sensitive= */ false, |
| SelectOptionProto::VALUE, /* strict= */ true) |
| .proto_status()); |
| EXPECT_EQ("Four", content::EvalJs(shell(), javascript)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOptionInIFrame) { |
| // IFrame. |
| Selector select_selector({"#iframe", "select[name=state]"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(select_selector, "^NY", /* case_sensitive= */ false, |
| SelectOptionProto::LABEL, /* strict= */ true) |
| .proto_status()); |
| |
| const std::string javascript = R"( |
| let iframe = document.querySelector("iframe").contentDocument; |
| let select = iframe.querySelector("select[name=state]"); |
| select.options[select.selectedIndex].label; |
| )"; |
| EXPECT_EQ("NY", content::EvalJs(shell(), javascript)); |
| |
| // OOPIF. |
| // Checking elements through EvalJs in OOPIF is blocked by cross-site. |
| select_selector = Selector({"#iframeExternal", "select[name=pet]"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(select_selector, "^Cat", /* case_sensitive= */ false, |
| SelectOptionProto::LABEL, /* strict= */ true) |
| .proto_status()); |
| |
| Selector result_selector({"#iframeExternal", "#myPet"}); |
| GetFieldsValue({result_selector}, {"Cat"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetOuterHtml) { |
| std::string html; |
| |
| // Div. |
| Selector div_selector({"#testOuterHtml"}); |
| ASSERT_EQ(ACTION_APPLIED, |
| GetOuterHtml(div_selector, |
| /* include_all_inner_text*/ true, &html) |
| .proto_status()); |
| EXPECT_EQ( |
| R"(<div id="testOuterHtml"><span>Span</span><p>Paragraph</p></div>)", |
| html); |
| |
| // IFrame. |
| Selector iframe_selector({"#iframe", "#input"}); |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetOuterHtml(iframe_selector, /* include_all_inner_text*/ true, &html) |
| .proto_status()); |
| EXPECT_EQ(R"(<input id="input" type="text">)", html); |
| |
| // OOPIF. |
| Selector oopif_selector({"#iframeExternal", "#divToRemove"}); |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetOuterHtml(oopif_selector, /* include_all_inner_text*/ true, &html) |
| .proto_status()); |
| EXPECT_EQ(R"(<div id="divToRemove">Text</div>)", html); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetOuterHtmls) { |
| std::vector<std::string> htmls; |
| |
| Selector div_selector({".label"}); |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetOuterHtmls(div_selector, /* include_all_inner_text*/ true, &htmls) |
| .proto_status()); |
| |
| EXPECT_THAT(htmls, |
| testing::ElementsAre(R"(<div class="label">Label 1</div>)", |
| R"(<div class="label">Label 2</div>)", |
| R"(<div class="label">Label 3</div>)")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| GetOuterHtmlWithRedactedInnerText) { |
| std::string html; |
| |
| // Div. |
| Selector div_selector({"#testOuterHtml"}); |
| ASSERT_EQ(ACTION_APPLIED, |
| GetOuterHtml(div_selector, /* include_all_inner_text*/ false, &html) |
| .proto_status()); |
| EXPECT_EQ(R"(<div id="testOuterHtml"><span></span><p></p></div>)", html); |
| |
| // IFrame. |
| Selector iframe_selector({"#iframe", "#input"}); |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetOuterHtml(iframe_selector, /* include_all_inner_text*/ false, &html) |
| .proto_status()); |
| EXPECT_EQ(R"(<input id="input" type="text">)", html); |
| |
| // OOPIF. |
| Selector oopif_selector({"#iframeExternal", "#divToRemove"}); |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetOuterHtml(oopif_selector, /* include_all_inner_text*/ false, &html) |
| .proto_status()); |
| EXPECT_EQ(R"(<div id="divToRemove"></div>)", html); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| GetOuterHtmlsWithRedactedInnerText) { |
| std::vector<std::string> htmls; |
| |
| Selector div_selector({".label"}); |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetOuterHtmls(div_selector, /* include_all_inner_text*/ false, &htmls) |
| .proto_status()); |
| |
| EXPECT_THAT(htmls, testing::ElementsAre(R"(<div class="label"></div>)", |
| R"(<div class="label"></div>)", |
| R"(<div class="label"></div>)")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetElementTag) { |
| std::string element_tag; |
| |
| // Div. |
| ASSERT_EQ( |
| ACTION_APPLIED, |
| GetElementTag(Selector({"#testOuterHtml"}), &element_tag).proto_status()); |
| EXPECT_EQ("DIV", element_tag); |
| |
| // Select. |
| ASSERT_EQ(ACTION_APPLIED, |
| GetElementTag(Selector({"#select"}), &element_tag).proto_status()); |
| EXPECT_EQ("SELECT", element_tag); |
| |
| // Input. |
| ASSERT_EQ(ACTION_APPLIED, |
| GetElementTag(Selector({"#input1"}), &element_tag).proto_status()); |
| EXPECT_EQ("INPUT", element_tag); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValue) { |
| std::vector<Selector> selectors; |
| std::vector<std::string> expected_values; |
| |
| Selector a_selector({"body"}); // Body has 'undefined' value |
| selectors.emplace_back(a_selector); |
| expected_values.emplace_back(""); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#input1"}); |
| selectors.emplace_back(a_selector); |
| expected_values.clear(); |
| expected_values.emplace_back("helloworld1"); |
| GetFieldsValue(selectors, expected_values); |
| |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue(a_selector, "foo", SET_VALUE).proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back("foo"); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#uppercase_input"}); |
| selectors.emplace_back(a_selector); |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue(a_selector, /* Zürich */ "Z\xc3\xbcrich", |
| SIMULATE_KEY_PRESSES) |
| .proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back(/* ZÜRICH */ "Z\xc3\x9cRICH"); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#input2"}); |
| selectors.emplace_back(a_selector); |
| expected_values.clear(); |
| expected_values.emplace_back("helloworld2"); |
| GetFieldsValue(selectors, expected_values); |
| EXPECT_EQ( |
| ACTION_APPLIED, |
| SetFieldValue(a_selector, /* value= */ "", SET_VALUE).proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back(""); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#input3"}); |
| selectors.emplace_back(a_selector); |
| expected_values.clear(); |
| expected_values.emplace_back("helloworld3"); |
| GetFieldsValue(selectors, expected_values); |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue(a_selector, "new value", SIMULATE_KEY_PRESSES) |
| .proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back("new value"); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#input4"}); |
| selectors.emplace_back(a_selector); |
| expected_values.clear(); |
| expected_values.emplace_back("helloworld4"); |
| GetFieldsValue(selectors, expected_values); |
| EXPECT_EQ(ACTION_APPLIED, SetFieldValue(a_selector, "new value", |
| SIMULATE_KEY_PRESSES_SELECT_VALUE) |
| .proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back("new value"); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#input5"}); |
| selectors.emplace_back(a_selector); |
| expected_values.clear(); |
| expected_values.emplace_back("helloworld5"); |
| GetFieldsValue(selectors, expected_values); |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue(a_selector, "new value", SIMULATE_KEY_PRESSES_FOCUS) |
| .proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back("new value"); |
| GetFieldsValue(selectors, expected_values); |
| |
| selectors.clear(); |
| a_selector = Selector({"#invalid_selector"}); |
| selectors.emplace_back(a_selector); |
| expected_values.clear(); |
| expected_values.emplace_back(""); |
| GetFieldsValue(selectors, expected_values); |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, |
| SetFieldValue(a_selector, "foobar", SET_VALUE).proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetAndSetFieldValueInIFrame) { |
| // IFrame. |
| Selector a_selector({"#iframe", "#input"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue(a_selector, "text", SET_VALUE).proto_status()); |
| GetFieldsValue({a_selector}, {"text"}); |
| |
| // OOPIF. |
| a_selector = Selector({"#iframeExternal", "#input"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue(a_selector, "text", SET_VALUE).proto_status()); |
| GetFieldsValue({a_selector}, {"text"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendKeyboardInput) { |
| auto input = UTF8ToUnicode("Zürich"); |
| std::string expected_output = "Zürich"; |
| |
| std::vector<Selector> selectors; |
| Selector a_selector({"#input6"}); |
| selectors.emplace_back(a_selector); |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyboardInput(a_selector, input).proto_status()); |
| Selector b_selector({"#input7"}); |
| selectors.emplace_back(b_selector); |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyboardInput(b_selector, input, /* delay_in_milli= */ -1, |
| /* use_js_focus= */ true) |
| .proto_status()); |
| GetFieldsValue(selectors, {expected_output, expected_output}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| SendKeyboardInputAndCheckPasswordField) { |
| auto input = UTF8ToUnicode("password"); |
| std::string output; |
| |
| Selector selector({"#input_password"}); |
| EXPECT_EQ(ACTION_APPLIED, SendKeyboardInput(selector, input).proto_status()); |
| EXPECT_EQ(ACTION_APPLIED, |
| GetStringAttribute(selector, {"value"}, &output).proto_status()); |
| EXPECT_EQ("password", output); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendKeyboardInputWithDelay) { |
| Selector selector({"#input6"}); |
| const ClientStatus& status = SendKeyboardInput(selector, UTF8ToUnicode("abc"), |
| /* delay_in_milli= */ 1, |
| /* use_js_focus= */ true); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| GetFieldsValue({selector}, {"abc"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, BackspaceKeyEvent) { |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| document.getElementById('input4').addEventListener('keyup', (e) => { |
| document.getElementById('input6').value = "triggered"; |
| }); |
| document.getElementById('input5').addEventListener('keyup', (e) => { |
| document.getElementById('input7').value = "triggered"; |
| }); |
| )")); |
| |
| std::vector<Selector> selectors; |
| Selector a_selector({"#input4"}); |
| selectors.emplace_back(a_selector); |
| // The initial value of #input4 is "helloworld4", sending a backspace should |
| // remove the last character. |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyboardInput(a_selector, UTF8ToUnicode("\b")).proto_status()); |
| Selector b_selector({"#input5"}); |
| selectors.emplace_back(b_selector); |
| // The initial value of #input5 is "helloworld5", selecting the value and |
| // sending a backspace should remove the entire text. |
| EXPECT_EQ(ACTION_APPLIED, |
| SetFieldValue( |
| b_selector, "\b", |
| KeyboardValueFillStrategy::SIMULATE_KEY_PRESSES_SELECT_VALUE) |
| .proto_status()); |
| GetFieldsValue(selectors, {"helloworld", std::string()}); |
| |
| selectors.clear(); |
| Selector a_trigger_selector({"#input6"}); |
| selectors.emplace_back(a_trigger_selector); |
| Selector b_trigger_selector({"#input7"}); |
| selectors.emplace_back(b_trigger_selector); |
| GetFieldsValue(selectors, {"triggered", "triggered"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendKeyCommands) { |
| std::vector<Selector> selectors; |
| // The initial value of #input2 is "helloworld2", sending a DeleteBackward |
| // command should remove the last character. |
| Selector a_selector({"#input2"}); |
| selectors.emplace_back(a_selector); |
| KeyEvent a_key_event; |
| a_key_event.add_command("MoveToEndOfLine"); |
| a_key_event.add_command("DeleteBackward"); |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyEvent(a_selector, a_key_event).proto_status()); |
| // The initial value of #input3 is "helloworld3", sending SelectAll + |
| // DeleteBackward commands should clear the text. |
| Selector b_selector({"#input3"}); |
| selectors.emplace_back(b_selector); |
| KeyEvent b_key_event; |
| b_key_event.add_command("SelectAll"); |
| b_key_event.add_command("DeleteBackward"); |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyEvent(b_selector, b_key_event).proto_status()); |
| GetFieldsValue(selectors, {"helloworld", std::string()}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| SendKeyboardInputDevtoolsFailure) { |
| // This makes devtools action fail and is used as a way of testing that the |
| // case where SendKeyboardInput ends prematurely with 0 delay doesn't cause |
| // issues. |
| ElementFinder::Result bad_element; |
| bad_element.dom_object.object_data.node_frame_id = "doesnotexist"; |
| |
| ClientStatus status; |
| base::RunLoop run_loop; |
| web_controller_->SendKeyboardInput( |
| UTF8ToUnicode("never sent"), |
| /* key_press_delay_in_millisecond= */ 0, bad_element, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), &status)); |
| run_loop.Run(); |
| EXPECT_EQ(OTHER_ACTION_STATUS, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| SendKeyboardInputSetsKeyProperty) { |
| auto input = UTF8ToUnicode("Zürich\r"); |
| std::string expected_output = "ZürichEnter"; |
| |
| std::vector<Selector> selectors; |
| Selector a_selector({"#input_js_event_listener"}); |
| selectors.emplace_back(a_selector); |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyboardInput(a_selector, input).proto_status()); |
| GetFieldsValue(selectors, {expected_output}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| SendKeyboardInputSetsKeyPropertyWithTimeout) { |
| // Sends input keys to a field where JS will intercept KeyDown and |
| // at index 3 it will set a timeout whick inserts an space after the |
| // timeout. If key press delay is enabled, it should handle this |
| // correctly. |
| auto input = UTF8ToUnicode("012345"); |
| std::string expected_output = "012 345"; |
| |
| std::vector<Selector> selectors; |
| Selector a_selector({"#input_js_event_with_timeout"}); |
| selectors.emplace_back(a_selector); |
| EXPECT_EQ(ACTION_APPLIED, |
| SendKeyboardInput(a_selector, input, /* delay_in_milli= */ 100, |
| /* use_js_focus= */ false) |
| .proto_status()); |
| GetFieldsValue(selectors, {expected_output}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SetAttribute) { |
| std::vector<std::string> attribute; |
| |
| Selector selector({"#full_height_section"}); |
| attribute.emplace_back("style"); |
| attribute.emplace_back("backgroundColor"); |
| std::string value = "red"; |
| |
| EXPECT_EQ(ACTION_APPLIED, |
| SetAttribute(selector, attribute, value).proto_status()); |
| const std::string javascript = R"( |
| document.querySelector("#full_height_section").style.backgroundColor; |
| )"; |
| EXPECT_EQ(value, content::EvalJs(shell(), javascript)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ConcurrentGetFieldsValue) { |
| std::vector<Selector> selectors; |
| std::vector<std::string> expected_values; |
| |
| Selector a_selector({"#input1"}); |
| selectors.emplace_back(a_selector); |
| expected_values.emplace_back("helloworld1"); |
| |
| a_selector = Selector({"#input2"}); |
| selectors.emplace_back(a_selector); |
| expected_values.emplace_back("helloworld2"); |
| |
| a_selector = Selector({"#input3"}); |
| selectors.emplace_back(a_selector); |
| expected_values.emplace_back("helloworld3"); |
| |
| a_selector = Selector({"#input4"}); |
| selectors.emplace_back(a_selector); |
| expected_values.emplace_back("helloworld4"); |
| |
| a_selector = Selector({"#input5"}); |
| selectors.emplace_back(a_selector); |
| expected_values.emplace_back("helloworld5"); |
| |
| GetFieldsValue(selectors, expected_values); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, NavigateToUrl) { |
| EXPECT_EQ(kTargetWebsitePath, |
| shell()->web_contents()->GetLastCommittedURL().path()); |
| web_controller_->LoadURL(GURL(url::kAboutBlankURL)); |
| EXPECT_TRUE(WaitForLoadStop(shell()->web_contents())); |
| EXPECT_EQ(url::kAboutBlankURL, |
| shell()->web_contents()->GetLastCommittedURL().spec()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, HighlightElement) { |
| Selector selector({"#select"}); |
| |
| const std::string javascript = R"( |
| let select = document.querySelector("#select"); |
| select.style.boxShadow; |
| )"; |
| EXPECT_EQ("", content::EvalJs(shell(), javascript)); |
| EXPECT_EQ(ACTION_APPLIED, HighlightElement(selector).proto_status()); |
| // We only make sure that the element has a non-empty boxShadow style without |
| // requiring an exact string match. |
| EXPECT_NE("", content::EvalJs(shell(), javascript)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, WaitForHeightChange) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| web_controller_->WaitForWindowHeightChange( |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), &result)); |
| |
| RuntimeEvaluate("window.dispatchEvent(new Event('resize'))"); |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| WaitMainDocumentReadyStateInteractive) { |
| ClientStatus status; |
| DocumentReadyState end_state; |
| base::RunLoop run_loop; |
| web_controller_->WaitForDocumentReadyState( |
| ElementFinder::Result(), DOCUMENT_INTERACTIVE, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatusAndReadyState, |
| base::Unretained(this), run_loop.QuitClosure(), &status, |
| &end_state)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()) << "Status: " << status; |
| EXPECT_THAT(end_state, AnyOf(DOCUMENT_INTERACTIVE, DOCUMENT_COMPLETE)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| WaitMainDocumentReadyStateComplete) { |
| ClientStatus status; |
| DocumentReadyState end_state; |
| base::RunLoop run_loop; |
| web_controller_->WaitForDocumentReadyState( |
| ElementFinder::Result(), DOCUMENT_COMPLETE, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatusAndReadyState, |
| base::Unretained(this), run_loop.QuitClosure(), &status, |
| &end_state)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()) << "Status: " << status; |
| EXPECT_EQ(DOCUMENT_COMPLETE, end_state); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| WaitFrameDocumentReadyStateComplete) { |
| ClientStatus status; |
| |
| ElementFinder::Result iframe_element; |
| FindElement(Selector({"#iframe"}), &status, &iframe_element); |
| ASSERT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| DocumentReadyState end_state; |
| base::RunLoop run_loop; |
| web_controller_->WaitForDocumentReadyState( |
| iframe_element, DOCUMENT_COMPLETE, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatusAndReadyState, |
| base::Unretained(this), run_loop.QuitClosure(), &status, |
| &end_state)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()) << "Status: " << status; |
| EXPECT_THAT(end_state, DOCUMENT_COMPLETE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| WaitExternalFrameDocumentReadyStateComplete) { |
| ClientStatus status; |
| |
| ElementFinder::Result iframe_element; |
| FindElement(Selector({"#iframeExternal"}), &status, &iframe_element); |
| ASSERT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| DocumentReadyState end_state; |
| base::RunLoop run_loop; |
| web_controller_->WaitForDocumentReadyState( |
| iframe_element, DOCUMENT_COMPLETE, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatusAndReadyState, |
| base::Unretained(this), run_loop.QuitClosure(), &status, |
| &end_state)); |
| run_loop.Run(); |
| |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()) << "Status: " << status; |
| EXPECT_THAT(end_state, DOCUMENT_COMPLETE); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetElementRect) { |
| RectF document_element_rect; |
| Selector document_element({"#full_height_section"}); |
| EXPECT_EQ( |
| ACTION_APPLIED, |
| GetElementRect(document_element, &document_element_rect).proto_status()); |
| |
| // The iFrame must be after the #full_height_section element to check that |
| // the resulting rect is global. |
| RectF iframe_element_rect; |
| Selector iframe_element({"#iframe", "#touch_area_1"}); |
| EXPECT_EQ( |
| ACTION_APPLIED, |
| GetElementRect(iframe_element, &iframe_element_rect).proto_status()); |
| |
| EXPECT_GT(iframe_element_rect.top, document_element_rect.bottom); |
| |
| // Make sure the element is within the iframe. |
| RectF iframe_rect; |
| Selector iframe({"#iframe"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| GetElementRect(iframe, &iframe_rect).proto_status()); |
| |
| EXPECT_GT(iframe_element_rect.left, iframe_rect.left); |
| EXPECT_LT(iframe_element_rect.right, iframe_rect.right); |
| EXPECT_GT(iframe_element_rect.top, iframe_rect.top); |
| EXPECT_LT(iframe_element_rect.bottom, iframe_rect.bottom); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetStringAttribute) { |
| std::string value; |
| |
| std::vector<std::string> inner_text_attribute = {"innerText"}; |
| ASSERT_EQ(ACTION_APPLIED, GetStringAttribute(Selector({"#testOuterHtml p"}), |
| inner_text_attribute, &value) |
| .proto_status()); |
| EXPECT_EQ("Paragraph", value); |
| |
| std::vector<std::string> option_label_attribute = {"options", "2", "label"}; |
| ASSERT_EQ(ACTION_APPLIED, GetStringAttribute(Selector({"#select"}), |
| option_label_attribute, &value) |
| .proto_status()); |
| EXPECT_EQ("Three", value); |
| |
| std::vector<std::string> bad_access = {"none", "none"}; |
| ASSERT_EQ(UNEXPECTED_JS_ERROR, |
| GetStringAttribute(Selector({"#button"}), bad_access, &value) |
| .proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, OnTop) { |
| Selector button({"#button"}); |
| RunLaxElementCheck(button, true); |
| |
| button.proto.add_filters()->mutable_on_top(); |
| RunLaxElementCheck(button, true); |
| |
| ShowOverlay(); |
| RunLaxElementCheck(button, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, OnTopNeedsScrolling) { |
| // Scroll button out of the viewport. |
| double target_bottom = content::EvalJs(shell(), |
| R"( |
| const target = document.getElementById("touch_area_one"); |
| const box = target.getBoundingClientRect(); |
| window.scrollBy(0, box.bottom + 10); |
| target.getBoundingClientRect().bottom |
| )") |
| .ExtractDouble(); |
| |
| // Before running the test, verify that the target is outside of the viewport, |
| // as we wanted. full_height_section guarantees that this is never a problem. |
| ASSERT_LE(target_bottom, 0); |
| |
| Selector target({"#touch_area_one"}); |
| RunLaxElementCheck(target, true); |
| |
| auto* on_top = target.proto.add_filters()->mutable_on_top(); |
| |
| // Apply on_top without scrolling. |
| on_top->set_scroll_into_view_if_needed(false); |
| RunLaxElementCheck(target, false); |
| on_top->set_accept_element_if_not_in_view(true); |
| RunLaxElementCheck(target, true); |
| |
| // Allow on_top to scroll. |
| on_top->set_scroll_into_view_if_needed(true); |
| on_top->set_accept_element_if_not_in_view(false); |
| RunLaxElementCheck(target, true); |
| |
| ASSERT_GE(content::EvalJs(shell(), |
| R"( |
| document.getElementById("touch_area_one").getBoundingClientRect().bottom |
| )") |
| .ExtractDouble(), |
| 0); |
| |
| ShowOverlay(); |
| RunLaxElementCheck(target, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ALabelIsNotAnOverlay) { |
| Selector input({"#input1"}); |
| RunLaxElementCheck(input, true); |
| |
| input.proto.add_filters()->mutable_on_top(); |
| RunLaxElementCheck(input, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, OnTopFindsOverlayInFrame) { |
| Selector button; |
| button.proto.add_filters()->set_css_selector("#iframe"); |
| button.proto.add_filters()->mutable_enter_frame(); |
| button.proto.add_filters()->set_css_selector("button"); |
| button.proto.add_filters()->mutable_on_top(); |
| RunLaxElementCheck(button, true); |
| |
| ShowOverlayInFrame(); |
| RunLaxElementCheck(button, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, OnTopFindsOverlayOverFrame) { |
| Selector button; |
| button.proto.add_filters()->set_css_selector("#iframe"); |
| button.proto.add_filters()->mutable_on_top(); |
| button.proto.add_filters()->mutable_enter_frame(); |
| button.proto.add_filters()->set_css_selector("button"); |
| RunLaxElementCheck(button, true); |
| |
| ShowOverlay(); |
| RunLaxElementCheck(button, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, OnTopFindsElementInShadow) { |
| Selector button; |
| button.proto.add_filters()->set_css_selector("#iframe"); |
| button.proto.add_filters()->mutable_enter_frame(); |
| button.proto.add_filters()->set_css_selector("#shadowsection"); |
| button.proto.add_filters()->mutable_enter_frame(); |
| button.proto.add_filters()->set_css_selector("#shadowbutton"); |
| RunLaxElementCheck(button, true); |
| button.proto.add_filters()->mutable_on_top(); |
| RunLaxElementCheck(button, true); |
| |
| ShowOverlayInFrame(); |
| RunLaxElementCheck(button, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, CheckOnTop) { |
| ClientStatus status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#button"}), &status, &element); |
| ASSERT_TRUE(status.ok()); |
| |
| // Make sure the button is visible. |
| EXPECT_TRUE(ExecJs( |
| shell(), "document.getElementById('button').scrollIntoViewIfNeeded();")); |
| |
| // The button is the topmost element. |
| status = CheckOnTop(element); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| EXPECT_EQ(WebControllerErrorInfoProto::UNSPECIFIED_WEB_ACTION, |
| status.details().web_controller_error_info().failed_web_action()); |
| |
| // The button is not the topmost element. |
| ShowOverlay(); |
| status = CheckOnTop(element); |
| EXPECT_EQ(ELEMENT_NOT_ON_TOP, status.proto_status()); |
| EXPECT_EQ(WebControllerErrorInfoProto::ON_TOP, |
| status.details().web_controller_error_info().failed_web_action()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, CheckOnTopInFrame) { |
| ClientStatus status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#iframe", "#button"}), &status, &element); |
| ASSERT_TRUE(status.ok()); |
| |
| // Make sure the button is visible. |
| EXPECT_TRUE( |
| ExecJs(ChildFrameAt(shell()->web_contents(), 0), |
| "document.getElementById('button').scrollIntoViewIfNeeded();")); |
| |
| // The button is covered by an overlay in the main frame |
| ShowOverlay(); |
| EXPECT_EQ(ELEMENT_NOT_ON_TOP, CheckOnTop(element).proto_status()); |
| |
| // The button is covered by an overlay in the iframe |
| HideOverlay(); |
| ShowOverlayInFrame(); |
| EXPECT_EQ(ELEMENT_NOT_ON_TOP, CheckOnTop(element).proto_status()); |
| |
| // The button is not covered by any overlay |
| HideOverlayInFrame(); |
| EXPECT_EQ(ACTION_APPLIED, CheckOnTop(element).proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, NthMatch) { |
| Selector selector; |
| selector.proto.add_filters()->set_css_selector(".nth_match_parent"); |
| selector.proto.add_filters()->mutable_nth_match()->set_index(1); |
| selector.proto.add_filters()->set_css_selector(".nth_match_child"); |
| |
| auto* pick_at_filter = selector.proto.add_filters(); |
| std::string element_tag; |
| |
| pick_at_filter->mutable_nth_match()->set_index(0); |
| ASSERT_EQ(ACTION_APPLIED, |
| GetElementTag(selector, &element_tag).proto_status()); |
| EXPECT_EQ("P", element_tag); |
| |
| pick_at_filter->mutable_nth_match()->set_index(1); |
| ASSERT_EQ(ACTION_APPLIED, |
| GetElementTag(selector, &element_tag).proto_status()); |
| EXPECT_EQ("UL", element_tag); |
| |
| pick_at_filter->mutable_nth_match()->set_index(2); |
| ASSERT_EQ(ACTION_APPLIED, |
| GetElementTag(selector, &element_tag).proto_status()); |
| EXPECT_EQ("LI", element_tag); |
| |
| pick_at_filter->mutable_nth_match()->set_index(3); |
| ASSERT_EQ(ACTION_APPLIED, |
| GetElementTag(selector, &element_tag).proto_status()); |
| EXPECT_EQ("STRONG", element_tag); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendChangeEvent) { |
| Selector selector({"#input_with_onchange"}); |
| |
| GetFieldsValue({selector}, {"0"}); |
| EXPECT_EQ(ACTION_APPLIED, SendChangeEvent(selector).proto_status()); |
| GetFieldsValue({selector}, {"1"}); |
| EXPECT_EQ(ACTION_APPLIED, SendChangeEvent(selector).proto_status()); |
| GetFieldsValue({selector}, {"2"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SendDuplexwebEvent) { |
| Selector selector({"#duplexweb"}); |
| GetFieldsValue({selector}, {"empty"}); |
| web_controller_->DispatchJsEvent(base::DoNothing()); |
| GetFieldsValue({selector}, {"received"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, WaitForElementToBecomeStable) { |
| ClientStatus element_status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#touch_area_one"}), &element_status, &element); |
| ASSERT_TRUE(element_status.ok()); |
| |
| // Move the element indefinitely. |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| (function() { |
| let i = 0; |
| document.getElementById('touch_area_one').style.position = 'absolute'; |
| document.browserTestInterval = setInterval(function() { |
| document.getElementById('touch_area_one').style.left = |
| `${10 * i++}px`; |
| }, 100); |
| })())")); |
| EXPECT_EQ(ELEMENT_UNSTABLE, |
| WaitUntilElementIsStable(element, 10, base::Milliseconds(100)) |
| .proto_status()); |
| |
| // Stop moving the element. |
| EXPECT_TRUE(ExecJs(shell(), "clearInterval(document.browserTestInterval);")); |
| EXPECT_TRUE( |
| WaitUntilElementIsStable(element, 10, base::Milliseconds(100)).ok()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| WaitForElementToBecomeStableForEmptyBoxModel) { |
| ClientStatus element_status; |
| |
| // The element has an empty box model. |
| ElementFinder::Result empty_element; |
| FindElement(Selector({"#emptydiv"}), &element_status, &empty_element); |
| ASSERT_TRUE(element_status.ok()); |
| EXPECT_EQ(ELEMENT_POSITION_NOT_FOUND, |
| WaitUntilElementIsStable(empty_element, 10, base::Milliseconds(10)) |
| .proto_status()); |
| |
| // The element is always hidden and has no box model. |
| ElementFinder::Result hidden_element; |
| FindElement(Selector({"#hidden"}), &element_status, &hidden_element); |
| ASSERT_TRUE(element_status.ok()); |
| EXPECT_EQ(ELEMENT_POSITION_NOT_FOUND, |
| WaitUntilElementIsStable(hidden_element, 10, base::Milliseconds(10)) |
| .proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| WaitForElementToBecomeStableDevtoolsFailure) { |
| // This makes the devtools action fail. |
| ElementFinder::Result element; |
| element.dom_object.object_data.node_frame_id = "doesnotexist"; |
| element.container_frame_host = web_contents()->GetMainFrame(); |
| |
| EXPECT_EQ(ELEMENT_POSITION_NOT_FOUND, |
| WaitUntilElementIsStable(element, 10, base::Milliseconds(100)) |
| .proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ElementQueryIndex) { |
| ClientStatus element_status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#input3"}), &element_status, &element); |
| ASSERT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| |
| int index; |
| EXPECT_EQ(ACTION_APPLIED, |
| GetElementQueryIndex("input", element, &index).proto_status()); |
| EXPECT_EQ(3, index); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, UniqueElementSelector) { |
| ClientStatus element_status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#input3"}), &element_status, &element); |
| ASSERT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| |
| std::string query; |
| int index; |
| EXPECT_EQ(ACTION_APPLIED, |
| GetUniqueElementSelector(element, &query, &index).proto_status()); |
| EXPECT_EQ("INPUT", query); |
| EXPECT_EQ(3, index); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOptionElement) { |
| ClientStatus option_status; |
| ElementFinder::Result option; |
| FindElement(Selector({"#select option:nth-child(2)"}), &option_status, |
| &option); |
| ASSERT_EQ(ACTION_APPLIED, option_status.proto_status()); |
| |
| Selector selector({"#select"}); |
| |
| GetFieldsValue({selector}, {"one"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOptionElement(selector, option).proto_status()); |
| GetFieldsValue({selector}, {"two"}); |
| |
| // Using on a non-<select> element. |
| EXPECT_EQ(INVALID_TARGET, |
| SelectOptionElement(Selector({"#input1"}), option).proto_status()); |
| |
| // Random element that is certainly not an option in the <select>. |
| FindElement(Selector({"#input1"}), &option_status, &option); |
| ASSERT_EQ(ACTION_APPLIED, option_status.proto_status()); |
| EXPECT_EQ(OPTION_VALUE_NOT_FOUND, |
| SelectOptionElement(selector, option).proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, CheckSelectedOptionElement) { |
| ClientStatus status; |
| |
| ElementFinder::Result input; |
| FindElement(Selector({"#input1"}), &status, &input); |
| ASSERT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| ElementFinder::Result select; |
| FindElement(Selector({"#select"}), &status, &select); |
| ASSERT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| ElementFinder::Result selected_option; |
| FindElement(Selector({"#select option:nth-child(1)"}), &status, |
| &selected_option); |
| ASSERT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| ElementFinder::Result not_selected_option; |
| FindElement(Selector({"#select option:nth-child(2)"}), &status, |
| ¬_selected_option); |
| ASSERT_EQ(ACTION_APPLIED, status.proto_status()); |
| |
| EXPECT_EQ(ACTION_APPLIED, |
| CheckSelectedOptionElement(select, selected_option).proto_status()); |
| EXPECT_EQ( |
| ELEMENT_MISMATCH, |
| CheckSelectedOptionElement(select, not_selected_option).proto_status()); |
| |
| // Using on a non-<select> element. |
| EXPECT_EQ(INVALID_TARGET, |
| CheckSelectedOptionElement(input, selected_option).proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FindUserDataElement) { |
| autofill::AutofillProfile shipping; |
| shipping.SetRawInfo(autofill::ServerFieldType::ADDRESS_HOME_CITY, u"Zürich"); |
| user_model_.SetSelectedAutofillProfile( |
| "SHIPPING", std::make_unique<autofill::AutofillProfile>(shipping), |
| &user_data_); |
| |
| SelectorProto selector_proto; |
| selector_proto.add_filters()->set_css_selector("#select option"); |
| auto* property = selector_proto.add_filters()->mutable_property(); |
| property->set_property("innerText"); |
| auto* inner_text = property->mutable_autofill_value_regexp(); |
| inner_text->mutable_profile()->set_identifier("SHIPPING"); |
| inner_text->mutable_value_expression_re2()->set_case_sensitive(false); |
| inner_text->mutable_value_expression_re2() |
| ->mutable_value_expression() |
| ->add_chunk() |
| ->set_key(static_cast<int>(autofill::ADDRESS_HOME_CITY)); |
| ClientStatus option_status; |
| ElementFinder::Result option; |
| FindElement(Selector(selector_proto), &option_status, &option); |
| ASSERT_EQ(ACTION_APPLIED, option_status.proto_status()); |
| |
| Selector selector({"#select"}); |
| |
| GetFieldsValue({selector}, {"one"}); |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOptionElement(selector, option).proto_status()); |
| GetFieldsValue({selector}, {"two"}); |
| |
| // Unknown profile. |
| SelectorProto failing_selector_proto; |
| failing_selector_proto.add_filters()->set_css_selector("#select option"); |
| auto* failing_property = |
| failing_selector_proto.add_filters()->mutable_property(); |
| failing_property->set_property("innerText"); |
| auto* failing_inner_text = failing_property->mutable_autofill_value_regexp(); |
| failing_inner_text->mutable_profile()->set_identifier("BILLING"); |
| failing_inner_text->mutable_value_expression_re2() |
| ->mutable_value_expression() |
| ->add_chunk() |
| ->set_key(static_cast<int>(autofill::ADDRESS_HOME_CITY)); |
| ClientStatus failing_option_status; |
| ElementFinder::Result failing_option; |
| FindElement(Selector(failing_selector_proto), &failing_option_status, |
| &failing_option); |
| ASSERT_EQ(PRECONDITION_FAILED, failing_option_status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ScrollIntoViewIfNeeded) { |
| EXPECT_EQ(content::EvalJs(shell(), "window.scrollY").ExtractInt(), 0); |
| |
| // Make sure the target element is on top, such that no scrolling is |
| // necessary. |
| ClientStatus no_scroll_element_status; |
| ElementFinder::Result no_scroll_element; |
| FindElement(Selector({"#trigger-keyboard"}), &no_scroll_element_status, |
| &no_scroll_element); |
| EXPECT_EQ(ACTION_APPLIED, no_scroll_element_status.proto_status()); |
| |
| ClientStatus no_scroll_status; |
| base::RunLoop no_scroll_run_loop; |
| web_controller_->ScrollIntoViewIfNeeded( |
| true, no_scroll_element, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), no_scroll_run_loop.QuitClosure(), |
| &no_scroll_status)); |
| no_scroll_run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, no_scroll_status.proto_status()); |
| EXPECT_EQ(content::EvalJs(shell(), "window.scrollY").ExtractInt(), 0); |
| |
| // Make sure the target element is after the full height view. |
| ClientStatus scroll_element_status; |
| ElementFinder::Result scroll_element; |
| FindElement(Selector({"#touch_area_five"}), &scroll_element_status, |
| &scroll_element); |
| EXPECT_EQ(ACTION_APPLIED, scroll_element_status.proto_status()); |
| |
| ClientStatus scroll_status; |
| base::RunLoop scroll_run_loop; |
| web_controller_->ScrollIntoViewIfNeeded( |
| true, scroll_element, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), scroll_run_loop.QuitClosure(), |
| &scroll_status)); |
| scroll_run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, scroll_status.proto_status()); |
| EXPECT_GT(content::EvalJs(shell(), "window.scrollY").ExtractDouble(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ScrollWindow) { |
| EXPECT_EQ(content::EvalJs(shell(), "window.scrollY").ExtractInt(), 0); |
| |
| ScrollDistance scroll_distance; |
| scroll_distance.set_pixels(20); |
| |
| ClientStatus status; |
| base::RunLoop run_loop; |
| web_controller_->ScrollWindow( |
| scroll_distance, /* animation= */ std::string(), |
| /* optional_frame= */ ElementFinder::Result(), |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), &status)); |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| EXPECT_NEAR(content::EvalJs(shell(), "window.scrollY").ExtractDouble(), 20.0, |
| 0.5); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ScrollWindowOfIFrame) { |
| EXPECT_EQ(content::EvalJs(shell(), "window.scrollY").ExtractInt(), 0); |
| |
| ScrollDistance scroll_distance; |
| scroll_distance.set_pixels(20); |
| |
| // This needs to target an OOPIF, otherwise it will have the same `window` |
| // and the test will fail. |
| ClientStatus frame_status; |
| ElementFinder::Result frame; |
| FindElement(Selector({"#iframeExternal", "body"}), &frame_status, &frame); |
| EXPECT_EQ(ACTION_APPLIED, frame_status.proto_status()); |
| |
| ClientStatus status; |
| base::RunLoop run_loop; |
| web_controller_->ScrollWindow( |
| scroll_distance, /* animation= */ std::string(), frame, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), &status)); |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| EXPECT_EQ(content::EvalJs(shell(), "window.scrollY").ExtractInt(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ScrollContainer) { |
| EXPECT_EQ( |
| content::EvalJs(shell(), |
| "document.getElementById('scroll_container').scrollTop") |
| .ExtractInt(), |
| 0); |
| |
| ScrollDistance scroll_distance; |
| scroll_distance.set_pixels(20); |
| |
| ClientStatus element_status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#scroll_container"}), &element_status, &element); |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| |
| ClientStatus status; |
| base::RunLoop run_loop; |
| web_controller_->ScrollContainer( |
| scroll_distance, /* animation= */ std::string(), element, |
| base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), run_loop.QuitClosure(), &status)); |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| EXPECT_NEAR( |
| content::EvalJs(shell(), |
| "document.getElementById('scroll_container').scrollTop") |
| .ExtractDouble(), |
| 20.0, 0.5); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, KeyMappings) { |
| EXPECT_TRUE(ExecJs(shell(), R"( |
| document.getElementById('input1').addEventListener('keydown', (e) => { |
| lastKeydownEvent = `${e.key} ${e.keyCode} ${e.which}`; |
| }); |
| document.getElementById('input1').addEventListener('keypress', (e) => { |
| lastKeypressEvent = `${e.key} ${e.keyCode} ${e.which}`; |
| }); |
| document.getElementById('input1').addEventListener('keyup', (e) => { |
| lastKeyupEvent = `${e.key} ${e.keyCode} ${e.which}`; |
| }); |
| )")); |
| |
| Selector selector({"#input1"}); |
| |
| EXPECT_EQ(SendKeyboardInput(selector, UTF8ToUnicode("a")).proto_status(), |
| ACTION_APPLIED); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeydownEvent").ExtractString(), |
| "a 65 65"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeypressEvent").ExtractString(), |
| "a 97 97"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeyupEvent").ExtractString(), |
| "a 65 65"); |
| |
| EXPECT_EQ(SendKeyboardInput(selector, UTF8ToUnicode("A")).proto_status(), |
| ACTION_APPLIED); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeydownEvent").ExtractString(), |
| "A 65 65"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeypressEvent").ExtractString(), |
| "A 65 65"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeyupEvent").ExtractString(), |
| "A 65 65"); |
| |
| EXPECT_EQ(SendKeyboardInput(selector, UTF8ToUnicode("\b")).proto_status(), |
| ACTION_APPLIED); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeydownEvent").ExtractString(), |
| "Backspace 8 8"); |
| // No keypress for backspace. |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeyupEvent").ExtractString(), |
| "Backspace 8 8"); |
| |
| EXPECT_EQ(SendKeyboardInput(selector, UTF8ToUnicode("\r")).proto_status(), |
| ACTION_APPLIED); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeydownEvent").ExtractString(), |
| "Enter 13 13"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeypressEvent").ExtractString(), |
| "Enter 13 13"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeyupEvent").ExtractString(), |
| "Enter 13 13"); |
| |
| EXPECT_EQ(SendKeyboardInput(selector, UTF8ToUnicode(",")).proto_status(), |
| ACTION_APPLIED); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeydownEvent").ExtractString(), |
| ", 188 188"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeypressEvent").ExtractString(), |
| ", 44 44"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeyupEvent").ExtractString(), |
| ", 188 188"); |
| |
| EXPECT_EQ(SendKeyboardInput(selector, UTF8ToUnicode("<")).proto_status(), |
| ACTION_APPLIED); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeydownEvent").ExtractString(), |
| "< 188 188"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeypressEvent").ExtractString(), |
| "< 60 60"); |
| EXPECT_EQ(content::EvalJs(shell(), "lastKeyupEvent").ExtractString(), |
| "< 188 188"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, FocusAndBlur) { |
| ClientStatus element_status; |
| ElementFinder::Result element; |
| FindElement(Selector({"#input1"}), &element_status, &element); |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| |
| ClientStatus focus_status; |
| base::RunLoop focus_run_loop; |
| web_controller_->FocusField( |
| element, base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), |
| focus_run_loop.QuitClosure(), &focus_status)); |
| focus_run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, focus_status.proto_status()); |
| EXPECT_TRUE( |
| content::EvalJs( |
| shell(), |
| R"(document.activeElement === document.getElementById('input1'))") |
| .ExtractBool()); |
| |
| ClientStatus blur_status; |
| base::RunLoop blur_run_loop; |
| web_controller_->BlurField( |
| element, base::BindOnce(&WebControllerBrowserTest::OnClientStatus, |
| base::Unretained(this), |
| blur_run_loop.QuitClosure(), &blur_status)); |
| blur_run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, blur_status.proto_status()); |
| EXPECT_TRUE( |
| content::EvalJs(shell(), R"(document.activeElement === document.body)") |
| .ExtractBool()); |
| } |
| |
| } // namespace autofill_assistant |