| // 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 "base/bind.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/strings/strcat.h" |
| #include "components/autofill_assistant/browser/client_settings.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/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" |
| |
| 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() override {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| command_line->AppendSwitch(kSitePerProcess); |
| } |
| |
| 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(), &settings_); |
| 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::TimeDelta::FromSeconds(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_->ElementCheck( |
| selectors[i], strict, |
| base::BindOnce(&WebControllerBrowserTest::CheckElementVisibleCallback, |
| base::Unretained(this), run_loop.QuitClosure(), |
| selectors[i], &pending_number_of_checks, results[i])); |
| } |
| run_loop.Run(); |
| } |
| |
| void CheckElementVisibleCallback(base::OnceClosure done_callback, |
| const Selector& selector, |
| size_t* pending_number_of_checks_output, |
| bool expected_result, |
| const ClientStatus& result) { |
| 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 ElementRetainingCallback(std::unique_ptr<ElementFinder::Result> element, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status) { |
| EXPECT_TRUE(element != nullptr); |
| *result_output = status; |
| std::move(done_callback).Run(); |
| } |
| |
| void ElementRetainingStringCallback( |
| std::unique_ptr<ElementFinder::Result> element, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| std::string* result, |
| const ClientStatus& status, |
| const std::string& value) { |
| EXPECT_TRUE(element != nullptr); |
| *result_output = status; |
| result->assign(value); |
| std::move(done_callback).Run(); |
| } |
| |
| void ClickOrTapElement(const Selector& selector, ClickType click_type) { |
| base::RunLoop run_loop; |
| ClientStatus result_output; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce(&WebControllerBrowserTest::FindClickOrTapElementCallback, |
| base::Unretained(this), click_type, |
| run_loop.QuitClosure(), &result_output)); |
| |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, result_output.proto_status()); |
| } |
| |
| void FindClickOrTapElementCallback( |
| ClickType click_type, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| ASSERT_TRUE(element_result != nullptr); |
| PerformClickOrTap( |
| click_type, *element_result, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| void PerformClickOrTap( |
| ClickType click_type, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> callback) { |
| web_controller_->ScrollIntoView( |
| element, |
| base::BindOnce(&WebControllerBrowserTest::OnScrollIntoViewForClickOrTap, |
| base::Unretained(this), click_type, element, |
| std::move(callback))); |
| } |
| |
| void OnScrollIntoViewForClickOrTap( |
| ClickType click_type, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> callback, |
| const ClientStatus& scroll_status) { |
| if (!scroll_status.ok()) { |
| std::move(callback).Run(scroll_status); |
| return; |
| } |
| |
| web_controller_->ClickOrTapElement(element, click_type, |
| std::move(callback)); |
| } |
| |
| void WaitForElementRemove(const Selector& selector) { |
| base::RunLoop run_loop; |
| web_controller_->ElementCheck( |
| 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::move(done_callback).Run(); |
| if (result.ok()) { |
| WaitForElementRemove(selector); |
| } |
| } |
| |
| void FocusElement(const Selector& selector, const TopPadding& top_padding) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce(&WebControllerBrowserTest::FindFocusElementCallback, |
| base::Unretained(this), top_padding, |
| run_loop.QuitClosure(), &result)); |
| |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| } |
| |
| void FindFocusElementCallback( |
| const TopPadding& top_padding, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| if (!status.ok()) { |
| *result_output = status; |
| std::move(done_callback).Run(); |
| return; |
| } |
| |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->FocusElement( |
| *element_result, top_padding, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| ClientStatus SelectOption(const Selector& selector, |
| const std::string& value, |
| DropdownSelectStrategy select_strategy) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindSelectOptionElementCallback, |
| base::Unretained(this), value, select_strategy, |
| run_loop.QuitClosure(), &result)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| void FindSelectOptionElementCallback( |
| const std::string& value, |
| DropdownSelectStrategy select_strategy, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| if (!status.ok()) { |
| *result_output = status; |
| std::move(done_callback).Run(); |
| return; |
| } |
| |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->SelectOption( |
| *element_result, value, select_strategy, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| void OnClientStatus(base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status) { |
| *result_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) { |
| *result_output = status; |
| *ready_state_out = ready_state; |
| std::move(done_callback).Run(); |
| } |
| |
| ClientStatus HighlightElement(const Selector& selector) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce(&WebControllerBrowserTest::FindHighlightElementCallback, |
| base::Unretained(this), run_loop.QuitClosure(), |
| &result)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| void FindHighlightElementCallback( |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| if (!status.ok()) { |
| *result_output = status; |
| std::move(done_callback).Run(); |
| return; |
| } |
| |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->HighlightElement( |
| *element_result, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| ClientStatus GetOuterHtml(const Selector& selector, |
| std::string* html_output) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindGetOuterHtmlElementCallback, |
| base::Unretained(this), run_loop.QuitClosure(), &result, |
| html_output)); |
| |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| return result; |
| } |
| |
| void FindGetOuterHtmlElementCallback( |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| std::string* html_output, |
| const ClientStatus& element_status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->GetOuterHtml( |
| *element_result, |
| base::BindOnce( |
| &WebControllerBrowserTest::ElementRetainingStringCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output, html_output)); |
| } |
| |
| ClientStatus GetElementTag(const Selector& selector, |
| std::string* element_tag_output) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindGetElementTagElementCallback, |
| base::Unretained(this), run_loop.QuitClosure(), &result, |
| element_tag_output)); |
| |
| run_loop.Run(); |
| EXPECT_EQ(ACTION_APPLIED, result.proto_status()); |
| return result; |
| } |
| |
| void FindGetElementTagElementCallback( |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| std::string* element_tag_output, |
| const ClientStatus& element_status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->GetElementTag( |
| *element_result, |
| base::BindOnce( |
| &WebControllerBrowserTest::ElementRetainingStringCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output, element_tag_output)); |
| } |
| |
| 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()); |
| } |
| |
| ClientStatus GetStringAttribute(const Selector& selector, |
| const std::vector<std::string>& attributes, |
| std::string* value) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindGetStringAttributeElementCallback, |
| base::Unretained(this), attributes, run_loop.QuitClosure(), &result, |
| value)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| void FindGetStringAttributeElementCallback( |
| const std::vector<std::string>& attributes, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| std::string* value, |
| const ClientStatus& element_status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->GetStringAttribute( |
| *element_result, attributes, |
| base::BindOnce( |
| &WebControllerBrowserTest::ElementRetainingStringCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output, value)); |
| } |
| |
| 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_->GetFieldValue( |
| selectors[i], |
| base::BindOnce(&WebControllerBrowserTest::OnGetFieldValue, |
| base::Unretained(this), run_loop.QuitClosure(), |
| &pending_number_of_checks, expected_values[i])); |
| } |
| run_loop.Run(); |
| } |
| |
| void OnGetFieldValue(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_EQ here: 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) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindSetFieldValueElementCallback, |
| base::Unretained(this), value, fill_strategy, |
| run_loop.QuitClosure(), &result)); |
| run_loop.Run(); |
| return result; |
| } |
| |
| void FindSetFieldValueElementCallback( |
| const std::string& value, |
| KeyboardValueFillStrategy fill_strategy, |
| base::OnceClosure done_callback, |
| ClientStatus* result_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; |
| } |
| |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->SetFieldValue( |
| *element_result, value, fill_strategy, |
| /* key_press_delay_in_millisecond= */ 0, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| ClientStatus SendKeyboardInput(const Selector& selector, |
| const std::vector<UChar32>& codepoints, |
| int delay_in_milli) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindSendKeyboardInputElementCallback, |
| base::Unretained(this), codepoints, delay_in_milli, |
| run_loop.QuitClosure(), &result)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| ClientStatus SendKeyboardInput(const Selector& selector, |
| const std::vector<UChar32>& codepoints) { |
| return SendKeyboardInput(selector, codepoints, -1); |
| } |
| |
| void FindSendKeyboardInputElementCallback( |
| const std::vector<UChar32>& codepoints, |
| int delay_in_milli, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& element_status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| EXPECT_EQ(ACTION_APPLIED, element_status.proto_status()); |
| ASSERT_TRUE(element_result != nullptr); |
| PerformSendKeyboardInput( |
| codepoints, delay_in_milli, *element_result, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| void PerformSendKeyboardInput( |
| const std::vector<UChar32>& codepoints, |
| int delay_in_milli, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> callback) { |
| PerformClickOrTap( |
| ClickType::CLICK, element, |
| base::BindOnce( |
| &WebControllerBrowserTest::OnClickOrTapForSendKeyboardInput, |
| base::Unretained(this), codepoints, delay_in_milli, element, |
| std::move(callback))); |
| } |
| |
| void OnClickOrTapForSendKeyboardInput( |
| const std::vector<UChar32>& codepoints, |
| int delay_in_milli, |
| const ElementFinder::Result& element, |
| base::OnceCallback<void(const ClientStatus&)> callback, |
| const ClientStatus& click_status) { |
| if (!click_status.ok()) { |
| std::move(callback).Run(click_status); |
| return; |
| } |
| |
| web_controller_->SendKeyboardInput(element, codepoints, delay_in_milli, |
| std::move(callback)); |
| } |
| |
| ClientStatus SetAttribute(const Selector& selector, |
| const std::vector<std::string>& attributes, |
| const std::string& value) { |
| base::RunLoop run_loop; |
| ClientStatus result; |
| |
| web_controller_->FindElement( |
| selector, /* strict_mode= */ true, |
| base::BindOnce( |
| &WebControllerBrowserTest::FindSetAttributeElementCallback, |
| base::Unretained(this), attributes, value, run_loop.QuitClosure(), |
| &result)); |
| |
| run_loop.Run(); |
| return result; |
| } |
| |
| void FindSetAttributeElementCallback( |
| const std::vector<std::string>& attributes, |
| const std::string& value, |
| base::OnceClosure done_callback, |
| ClientStatus* result_output, |
| const ClientStatus& status, |
| std::unique_ptr<ElementFinder::Result> element_result) { |
| if (!status.ok()) { |
| *result_output = status; |
| std::move(done_callback).Run(); |
| return; |
| } |
| |
| ASSERT_TRUE(element_result != nullptr); |
| web_controller_->SetAttribute( |
| *element_result, attributes, value, |
| base::BindOnce(&WebControllerBrowserTest::ElementRetainingCallback, |
| base::Unretained(this), std::move(element_result), |
| std::move(done_callback), result_output)); |
| } |
| |
| bool GetElementPosition(const Selector& selector, RectF* rect_output) { |
| base::RunLoop run_loop; |
| bool result; |
| web_controller_->GetElementPosition( |
| selector, |
| base::BindOnce(&WebControllerBrowserTest::OnGetElementPosition, |
| base::Unretained(this), run_loop.QuitClosure(), &result, |
| rect_output)); |
| run_loop.Run(); |
| return result; |
| } |
| |
| void OnGetElementPosition(base::OnceClosure done_callback, |
| bool* result_output, |
| RectF* rect_output, |
| bool non_empty, |
| const RectF& rect) { |
| if (non_empty) { |
| *rect_output = rect; |
| } |
| *result_output = non_empty; |
| std::move(done_callback).Run(); |
| } |
| |
| // 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}; |
| FocusElement(selector, top_padding); |
| base::ListValue 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, 0.5); |
| |
| // 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_; |
| ClientSettings settings_; |
| |
| private: |
| std::unique_ptr<net::EmbeddedTestServer> http_server_; |
| std::unique_ptr<net::EmbeddedTestServer> http_server_iframe_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WebControllerBrowserTest); |
| }; |
| |
| 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, 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_pick_one(); |
| |
| 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, 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, 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_two({"#touch_area_two"}); |
| ClickOrTapElement(area_two, ClickType::TAP); |
| WaitForElementRemove(area_two); |
| |
| 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::TimeDelta::FromHours(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, FocusElement) { |
| 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; |
| FocusElement(selector, top_padding); |
| EXPECT_EQ(true, content::EvalJs(shell(), checkVisibleScript)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| FocusElementWithScrollIntoViewNeeded) { |
| TestScrollIntoView(/* initial_window_scroll_y= */ 0, |
| /* initial_container_scroll_y=*/0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| FocusElementWithScrollIntoViewNotNeeded) { |
| TestScrollIntoView(/* initial_window_scroll_y= */ 0, |
| /* initial_container_scroll_y=*/200); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| FocusElement_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}; |
| FocusElement(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, |
| FocusElement_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}; |
| FocusElement(selector, top_padding); |
| |
| base::ListValue 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; |
| )"; |
| |
| // Select value not matching anything. |
| EXPECT_EQ(OPTION_VALUE_NOT_FOUND, |
| SelectOption(selector, "incorrect label", LABEL_STARTS_WITH) |
| .proto_status()); |
| |
| // Selects nothing if no strategy is set. |
| EXPECT_EQ(OPTION_VALUE_NOT_FOUND, |
| SelectOption(selector, "one", UNSPECIFIED_SELECT_STRATEGY) |
| .proto_status()); |
| |
| // Select value matching the option's label. |
| EXPECT_EQ(ACTION_APPLIED, |
| SelectOption(selector, "ZÃœRICH", LABEL_STARTS_WITH).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ü万𠜎", VALUE_MATCH).proto_status()); |
| EXPECT_EQ("Character Test Entry", content::EvalJs(shell(), javascript)); |
| |
| EXPECT_EQ(ELEMENT_RESOLUTION_FAILED, |
| SelectOption(Selector({"#incorrect_selector"}), "not important", |
| LABEL_STARTS_WITH) |
| .proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, SelectOptionInIFrame) { |
| // IFrame. |
| Selector select_selector({"#iframe", "select[name=state]"}); |
| EXPECT_EQ( |
| ACTION_APPLIED, |
| SelectOption(select_selector, "NY", LABEL_STARTS_WITH).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", LABEL_STARTS_WITH).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, &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, &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, &html).proto_status()); |
| EXPECT_EQ(R"(<div id="divToRemove">Text</div>)", html); |
| } |
| |
| 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= */ "", SIMULATE_KEY_PRESSES) |
| .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, /* value= */ "", |
| SIMULATE_KEY_PRESSES_SELECT_VALUE) |
| .proto_status()); |
| expected_values.clear(); |
| expected_values.emplace_back(""); |
| 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()); |
| GetFieldsValue(selectors, {expected_output}); |
| } |
| |
| 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) |
| .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( |
| Selector(), 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( |
| Selector(), 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, |
| WaitFrameDocumentReadyStateLoaded) { |
| ClientStatus status; |
| DocumentReadyState end_state; |
| base::RunLoop run_loop; |
| web_controller_->WaitForDocumentReadyState( |
| Selector({"#iframe"}), DOCUMENT_LOADED, |
| 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_LOADED, DOCUMENT_INTERACTIVE, DOCUMENT_COMPLETE)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, GetElementPosition) { |
| RectF document_element_rect; |
| Selector document_element({"#full_height_section"}); |
| EXPECT_TRUE(GetElementPosition(document_element, &document_element_rect)); |
| |
| // 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_TRUE(GetElementPosition(iframe_element, &iframe_element_rect)); |
| |
| 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_TRUE(GetElementPosition(iframe, &iframe_rect)); |
| |
| 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, GetElementByProximity) { |
| Selector input1_selector({"input"}); |
| auto* input1_closest = input1_selector.proto.add_filters()->mutable_closest(); |
| input1_closest->add_target()->set_css_selector("label"); |
| input1_closest->add_target()->mutable_inner_text()->set_re2("Input1"); |
| |
| GetFieldsValue({input1_selector}, {"helloworld1"}); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| GetElementByProximityWithTooManyCandidates) { |
| Selector selector({"input.pairs"}); |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("label.pairs"); |
| closest->set_max_pairs(24); |
| |
| ClientStatus status; |
| ElementFinder::Result result; |
| FindElement(selector, &status, &result); |
| EXPECT_EQ(TOO_MANY_CANDIDATES, status.proto_status()); |
| |
| closest->set_max_pairs(25); |
| FindElement(selector, &status, &result); |
| EXPECT_EQ(ACTION_APPLIED, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ProximityRelative_Position) { |
| Selector selector({"#at_center"}); |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("table.proximity td"); |
| auto* inner_text = closest->add_target()->mutable_inner_text(); |
| |
| // The cells of the table look like the following: |
| // |
| // One Two Three |
| // Four Center Five |
| // Six Seven Eight |
| // |
| // The element is "Center", the target is "One" to "Eight". The |
| // relative_position specify that the element should be below|above|... the |
| // target. |
| |
| closest->set_relative_position(SelectorProto::ProximityFilter::BELOW); |
| inner_text->set_re2("One"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Two"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Three"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Four"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Five"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Six"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Seven"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Eight"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Center"); |
| RunStrictElementCheck(selector, false); |
| |
| closest->set_relative_position(SelectorProto::ProximityFilter::ABOVE); |
| inner_text->set_re2("One"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Two"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Three"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Four"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Five"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Six"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Seven"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Eight"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Center"); |
| RunStrictElementCheck(selector, false); |
| |
| closest->set_relative_position(SelectorProto::ProximityFilter::LEFT); |
| inner_text->set_re2("One"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Two"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Three"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Four"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Five"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Six"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Seven"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Eight"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Center"); |
| RunStrictElementCheck(selector, false); |
| |
| closest->set_relative_position(SelectorProto::ProximityFilter::RIGHT); |
| inner_text->set_re2("One"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Two"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Three"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Four"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Five"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Six"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Seven"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Eight"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Center"); |
| RunStrictElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, ProximityAlignment) { |
| Selector selector({"#at_center"}); |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("table.proximity td"); |
| auto* inner_text = closest->add_target()->mutable_inner_text(); |
| |
| closest->set_in_alignment(true); |
| inner_text->set_re2("One"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Two"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Three"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Four"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Five"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Six"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Seven"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Eight"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Center"); |
| RunStrictElementCheck(selector, true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| ProximityAlignmentWithPosition) { |
| Selector selector({"#at_center"}); |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("table.proximity td"); |
| auto* inner_text = closest->add_target()->mutable_inner_text(); |
| |
| closest->set_in_alignment(true); |
| closest->set_relative_position(SelectorProto::ProximityFilter::LEFT); |
| |
| inner_text->set_re2("One"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Two"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Three"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Four"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Five"); |
| RunStrictElementCheck(selector, true); |
| inner_text->set_re2("Six"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Seven"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Eight"); |
| RunStrictElementCheck(selector, false); |
| inner_text->set_re2("Center"); |
| RunStrictElementCheck(selector, false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| FindPseudoElementToClickByProximity) { |
| const std::string javascript = R"( |
| document.querySelector("#terms-and-conditions").checked; |
| )"; |
| EXPECT_FALSE(content::EvalJs(shell(), javascript).ExtractBool()); |
| |
| // This test clicks on the before pseudo-element that's closest to |
| // #terms-and-conditions - this has the same effect as clicking on |
| // #terms-and-conditions. This checks that pseudo-elements have positions and |
| // that we can go through an array of pseudo-elements and choose the closest |
| // one. |
| Selector selector({"label, span"}); |
| selector.SetPseudoType(PseudoType::BEFORE); |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("#terms-and-conditions"); |
| |
| ClickOrTapElement(selector, ClickType::CLICK); |
| EXPECT_TRUE(content::EvalJs(shell(), javascript).ExtractBool()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| GetElementByProximityDifferentFrames) { |
| Selector selector({"input"}); |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("#iframe"); |
| closest->add_target()->mutable_pick_one(); |
| closest->add_target()->mutable_enter_frame(); |
| closest->add_target()->set_css_selector("div"); |
| |
| // Cannot compare position of elements on different frames. |
| ClientStatus status; |
| FindElement(Selector(SelectorProto::default_instance()), &status, nullptr); |
| EXPECT_EQ(INVALID_SELECTOR, status.proto_status()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(WebControllerBrowserTest, |
| GetElementByProximitySameFrame) { |
| Selector selector({"#iframe", "input[name='email']"}); |
| |
| // The target is searched within #iframe. |
| auto* closest = selector.proto.add_filters()->mutable_closest(); |
| closest->add_target()->set_css_selector("span"); |
| closest->add_target()->mutable_inner_text()->set_re2("Email"); |
| |
| RunLaxElementCheck(selector, true); |
| GetFieldsValue({selector}, {"email@example.com"}); |
| } |
| |
| 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()); |
| } |
| |
| } // namespace autofill_assistant |