blob: 804d118982c2873ab7b79641d6ee7da9c063a97d [file] [log] [blame]
// Copyright 2020 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/actions/action_delegate_util.h"
#include "base/guid.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "base/time/time.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill_assistant/browser/action_value.pb.h"
#include "components/autofill_assistant/browser/actions/action_test_utils.h"
#include "components/autofill_assistant/browser/actions/mock_action_delegate.h"
#include "components/autofill_assistant/browser/mock_website_login_manager.h"
#include "components/autofill_assistant/browser/selector.h"
#include "components/autofill_assistant/browser/user_data.h"
#include "components/autofill_assistant/browser/web/element_store.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "url/gurl.h"
namespace autofill_assistant {
namespace action_delegate_util {
namespace {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::InSequence;
using ::testing::Return;
class ActionDelegateUtilTest : public content::RenderViewHostTestHarness {
public:
ActionDelegateUtilTest()
: RenderViewHostTestHarness(
base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
~ActionDelegateUtilTest() override {}
void SetUp() override {
RenderViewHostTestHarness::SetUp();
ON_CALL(mock_action_delegate_, GetUserData)
.WillByDefault(Return(&user_data_));
ON_CALL(mock_action_delegate_, GetWebsiteLoginManager)
.WillByDefault(Return(&mock_website_login_manager_));
}
MOCK_METHOD2(MockAction,
void(const ElementFinder::Result& element,
base::OnceCallback<void(const ClientStatus&)> done));
MOCK_METHOD3(MockIndexedAction,
void(int index,
const ElementFinder::Result&,
base::OnceCallback<void(const ClientStatus&)> done));
MOCK_METHOD1(MockDone, void(const ClientStatus& status));
MOCK_METHOD2(MockGetAction,
void(const ElementFinder::Result& element,
base::OnceCallback<void(const ClientStatus&,
const std::string&)> done));
MOCK_METHOD2(MockDoneGet,
void(const ClientStatus& status, const std::string& value));
MOCK_METHOD3(MockValueAction,
void(const std::string& value,
const ElementFinder::Result& element,
base::OnceCallback<void(const ClientStatus&)> done));
MOCK_METHOD3(MockElementAction,
void(const ElementFinder::Result& parameter_element,
const ElementFinder::Result& element,
base::OnceCallback<void(const ClientStatus&)> done));
protected:
MockActionDelegate mock_action_delegate_;
UserData user_data_;
MockWebsiteLoginManager mock_website_login_manager_;
};
TEST_F(ActionDelegateUtilTest, FindElementFails) {
EXPECT_CALL(mock_action_delegate_, FindElement(_, _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
EXPECT_CALL(*this, MockAction(_, _)).Times(0);
EXPECT_CALL(*this,
MockDone(EqualsStatus(ClientStatus(ELEMENT_RESOLUTION_FAILED))));
FindElementAndPerform(&mock_action_delegate_, Selector({"#nothing"}),
base::BindOnce(&ActionDelegateUtilTest::MockAction,
base::Unretained(this)),
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, FindElementAndExecuteSingleAction) {
Selector expected_selector({"#element"});
auto expected_element =
test_util::MockFindElement(mock_action_delegate_, expected_selector);
EXPECT_CALL(*this, MockAction(EqualsElement(expected_element), _))
.WillOnce(RunOnceCallback<1>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
FindElementAndPerform(&mock_action_delegate_, expected_selector,
base::BindOnce(&ActionDelegateUtilTest::MockAction,
base::Unretained(this)),
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, FindElementAndExecuteMultipleActions) {
InSequence sequence;
Selector expected_selector({"#element"});
auto expected_element =
test_util::MockFindElement(mock_action_delegate_, expected_selector);
EXPECT_CALL(*this, MockIndexedAction(1, EqualsElement(expected_element), _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockIndexedAction(2, EqualsElement(expected_element), _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockIndexedAction(3, EqualsElement(expected_element), _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
auto actions = std::make_unique<ElementActionVector>();
actions->emplace_back(base::BindOnce(
&ActionDelegateUtilTest::MockIndexedAction, base::Unretained(this), 1));
actions->emplace_back(base::BindOnce(
&ActionDelegateUtilTest::MockIndexedAction, base::Unretained(this), 2));
actions->emplace_back(base::BindOnce(
&ActionDelegateUtilTest::MockIndexedAction, base::Unretained(this), 3));
FindElementAndPerform(&mock_action_delegate_, expected_selector,
base::BindOnce(&PerformAll, std::move(actions)),
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest,
FindElementAndExecuteMultipleActionsAbortsOnError) {
InSequence sequence;
Selector expected_selector({"#element"});
auto expected_element =
test_util::MockFindElement(mock_action_delegate_, expected_selector);
EXPECT_CALL(*this, MockIndexedAction(1, EqualsElement(expected_element), _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockIndexedAction(2, EqualsElement(expected_element), _))
.WillOnce(RunOnceCallback<2>(ClientStatus(UNEXPECTED_JS_ERROR)));
EXPECT_CALL(*this, MockIndexedAction(3, EqualsElement(expected_element), _))
.Times(0);
EXPECT_CALL(*this, MockDone(EqualsStatus(ClientStatus(UNEXPECTED_JS_ERROR))));
auto actions = std::make_unique<ElementActionVector>();
actions->emplace_back(base::BindOnce(
&ActionDelegateUtilTest::MockIndexedAction, base::Unretained(this), 1));
actions->emplace_back(base::BindOnce(
&ActionDelegateUtilTest::MockIndexedAction, base::Unretained(this), 2));
actions->emplace_back(base::BindOnce(
&ActionDelegateUtilTest::MockIndexedAction, base::Unretained(this), 3));
FindElementAndPerform(&mock_action_delegate_, expected_selector,
base::BindOnce(&PerformAll, std::move(actions)),
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, ActionDelegateDeletedDuringExecution) {
InSequence sequence;
auto mock_delegate = std::make_unique<MockActionDelegate>();
Selector expected_selector({"#element"});
auto expected_element =
test_util::MockFindElement(*mock_delegate, expected_selector);
EXPECT_CALL(*mock_delegate, WaitUntilDocumentIsInReadyState(_, _, _, _))
.WillOnce(RunOnceCallback<3>(OkClientStatus(),
base::TimeDelta::FromSeconds(0)));
EXPECT_CALL(*mock_delegate, ScrollToElementPosition(
_, _, _, EqualsElement(expected_element), _))
.Times(0);
EXPECT_CALL(*this, MockDone(_)).Times(0);
auto actions = std::make_unique<ElementActionVector>();
AddStepIgnoreTiming(
base::BindOnce(&ActionDelegate::WaitUntilDocumentIsInReadyState,
mock_delegate->GetWeakPtr(),
base::TimeDelta::FromMilliseconds(0), DOCUMENT_COMPLETE),
actions.get());
actions->emplace_back(base::BindOnce(
[](base::OnceCallback<void()> destroy_delegate,
const ElementFinder::Result& element,
base::OnceCallback<void(const ClientStatus&)> done) {
std::move(destroy_delegate).Run();
std::move(done).Run(OkClientStatus());
},
base::BindLambdaForTesting([&]() { mock_delegate.reset(); })));
actions->emplace_back(base::BindOnce(
&ActionDelegate::ScrollToElementPosition, mock_delegate->GetWeakPtr(),
Selector({"#element"}), TopPadding(), nullptr));
FindElementAndPerform(mock_delegate.get(), expected_selector,
base::BindOnce(&PerformAll, std::move(actions)),
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, TakeElementAndPerform) {
auto expected_element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(mock_action_delegate_, FindElement(_, _)).Times(0);
EXPECT_CALL(*this, MockAction(EqualsElement(*expected_element), _))
.WillOnce(RunOnceCallback<1>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
TakeElementAndPerform(
base::BindOnce(&ActionDelegateUtilTest::MockAction,
base::Unretained(this)),
base::BindOnce(&ActionDelegateUtilTest::MockDone, base::Unretained(this)),
OkClientStatus(), std::move(expected_element));
}
TEST_F(ActionDelegateUtilTest, TakeElementAndPerformWithFailedStatus) {
auto expected_element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(mock_action_delegate_, FindElement(_, _)).Times(0);
EXPECT_CALL(*this, MockAction(_, _)).Times(0);
EXPECT_CALL(*this,
MockDone(EqualsStatus(ClientStatus(ELEMENT_RESOLUTION_FAILED))));
TakeElementAndPerform(
base::BindOnce(&ActionDelegateUtilTest::MockAction,
base::Unretained(this)),
base::BindOnce(&ActionDelegateUtilTest::MockDone, base::Unretained(this)),
ClientStatus(ELEMENT_RESOLUTION_FAILED), std::move(expected_element));
}
TEST_F(ActionDelegateUtilTest, TakeElementAndGetProperty) {
auto expected_element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(mock_action_delegate_, FindElement(_, _)).Times(0);
EXPECT_CALL(*this, MockGetAction(EqualsElement(*expected_element), _))
.WillOnce(RunOnceCallback<1>(OkClientStatus(), "value"));
EXPECT_CALL(*this, MockDoneGet(EqualsStatus(OkClientStatus()), "value"));
TakeElementAndGetProperty<std::string>(
base::BindOnce(&ActionDelegateUtilTest::MockGetAction,
base::Unretained(this)),
base::BindOnce(&ActionDelegateUtilTest::MockDoneGet,
base::Unretained(this)),
OkClientStatus(), std::move(expected_element));
}
TEST_F(ActionDelegateUtilTest, TakeElementAndGetPropertyWithFailedStatus) {
auto expected_element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(mock_action_delegate_, FindElement(_, _)).Times(0);
EXPECT_CALL(*this, MockGetAction(_, _)).Times(0);
EXPECT_CALL(*this,
MockDoneGet(EqualsStatus(ClientStatus(ELEMENT_RESOLUTION_FAILED)),
std::string()));
TakeElementAndGetProperty<std::string>(
base::BindOnce(&ActionDelegateUtilTest::MockGetAction,
base::Unretained(this)),
base::BindOnce(&ActionDelegateUtilTest::MockDoneGet,
base::Unretained(this)),
ClientStatus(ELEMENT_RESOLUTION_FAILED), std::move(expected_element));
}
TEST_F(ActionDelegateUtilTest, PerformWithStringValue) {
auto element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(*this, MockValueAction("Hello World", _, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
TextValue text_value;
text_value.set_text("Hello World");
PerformWithTextValue(&mock_action_delegate_, text_value,
base::BindOnce(&ActionDelegateUtilTest::MockValueAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, PerformWithAutofillValue) {
auto element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(*this, MockValueAction("John", _, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
std::unique_ptr<autofill::AutofillProfile> contact =
std::make_unique<autofill::AutofillProfile>(base::GenerateGUID(),
autofill::test::kEmptyOrigin);
autofill::test::SetProfileInfo(contact.get(), "John", /* middle name */ "",
"Doe", "", "", "", "", "", "", "", "", "");
user_data_.selected_addresses_["contact"] = std::move(contact);
TextValue text_value;
auto* autofill_value = text_value.mutable_autofill_value();
autofill_value->mutable_profile()->set_identifier("contact");
autofill_value->set_value_expression(
base::StrCat({"${",
base::NumberToString(static_cast<int>(
autofill::ServerFieldType::NAME_FIRST)),
"}"}));
PerformWithTextValue(&mock_action_delegate_, text_value,
base::BindOnce(&ActionDelegateUtilTest::MockValueAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, PerformWithPasswordManagerValue) {
auto element = std::make_unique<ElementFinder::Result>();
content::NavigationSimulator::NavigateAndCommitFromDocument(
GURL("https://www.example.com"), web_contents()->GetMainFrame());
element->container_frame_host = web_contents()->GetMainFrame();
user_data_.selected_login_ = base::make_optional<WebsiteLoginManager::Login>(
GURL("https://www.example.com"), "username");
EXPECT_CALL(*this, MockValueAction("username", _, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
TextValue text_value;
auto* password_manager_value = text_value.mutable_password_manager_value();
password_manager_value->set_credential_type(PasswordManagerValue::USERNAME);
PerformWithTextValue(&mock_action_delegate_, text_value,
base::BindOnce(&ActionDelegateUtilTest::MockValueAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, PerformWithFailingPasswordManagerValue) {
auto element = std::make_unique<ElementFinder::Result>();
content::NavigationSimulator::NavigateAndCommitFromDocument(
GURL("https://www.other.com"), web_contents()->GetMainFrame());
element->container_frame_host = web_contents()->GetMainFrame();
user_data_.selected_login_ = base::make_optional<WebsiteLoginManager::Login>(
GURL("https://www.example.com"), "username");
EXPECT_CALL(*this, MockValueAction("username", _, _)).Times(0);
EXPECT_CALL(*this, MockDone(_));
TextValue text_value;
auto* password_manager_value = text_value.mutable_password_manager_value();
password_manager_value->set_credential_type(PasswordManagerValue::USERNAME);
PerformWithTextValue(&mock_action_delegate_, text_value,
base::BindOnce(&ActionDelegateUtilTest::MockValueAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, PerformWithClientMemoryKey) {
auto element = std::make_unique<ElementFinder::Result>();
ValueProto value_proto;
value_proto.mutable_strings()->add_values("Hello World");
user_data_.additional_values_["key"] = value_proto;
EXPECT_CALL(*this, MockValueAction("Hello World", _, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
TextValue text_value;
text_value.set_client_memory_key("key");
PerformWithTextValue(&mock_action_delegate_, text_value,
base::BindOnce(&ActionDelegateUtilTest::MockValueAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, PerformWithExistingElementValue) {
auto element = std::make_unique<ElementFinder::Result>();
ElementFinder::Result option;
option.dom_object.object_data.object_id = "option";
mock_action_delegate_.GetElementStore()->AddElement("o", option.dom_object);
EXPECT_CALL(*this, MockElementAction(EqualsElement(option), _, _))
.WillOnce(RunOnceCallback<2>(OkClientStatus()));
EXPECT_CALL(*this, MockDone(EqualsStatus(OkClientStatus())));
ClientIdProto option_id;
option_id.set_identifier("o");
PerformWithElementValue(
&mock_action_delegate_, option_id,
base::BindOnce(&ActionDelegateUtilTest::MockElementAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
TEST_F(ActionDelegateUtilTest, PerformWithMissingElementValue) {
auto element = std::make_unique<ElementFinder::Result>();
EXPECT_CALL(*this, MockElementAction(_, _, _)).Times(0);
EXPECT_CALL(
*this, MockDone(EqualsStatus(ClientStatus(CLIENT_ID_RESOLUTION_FAILED))));
ClientIdProto option_id;
option_id.set_identifier("o");
PerformWithElementValue(
&mock_action_delegate_, option_id,
base::BindOnce(&ActionDelegateUtilTest::MockElementAction,
base::Unretained(this)),
*element,
base::BindOnce(&ActionDelegateUtilTest::MockDone,
base::Unretained(this)));
}
} // namespace
} // namespace action_delegate_util
} // namespace autofill_assistant