blob: af88e996cd2fcd3e439cbe82a8f8874b3e828981 [file] [log] [blame]
// 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/script_executor.h"
#include <map>
#include <utility>
#include "base/strings/strcat.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/mock_callback.h"
#include "base/test/task_environment.h"
#include "components/autofill_assistant/browser/actions/action_test_utils.h"
#include "components/autofill_assistant/browser/fake_script_executor_delegate.h"
#include "components/autofill_assistant/browser/service/mock_service.h"
#include "components/autofill_assistant/browser/service/service.h"
#include "components/autofill_assistant/browser/test_util.h"
#include "components/autofill_assistant/browser/web/mock_web_controller.h"
#include "net/http/http_status_code.h"
#include "testing/gmock/include/gmock/gmock.h"
namespace autofill_assistant {
namespace {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::Invoke;
using ::testing::InvokeWithoutArgs;
using ::testing::IsEmpty;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Pair;
using ::testing::Property;
using ::testing::ReturnRef;
using ::testing::SaveArg;
using ::testing::SizeIs;
using ::testing::StrEq;
using ::testing::StrictMock;
using ::testing::UnorderedElementsAreArray;
using ::testing::WithArgs;
const char* kScriptPath = "script_path";
class ScriptExecutorTest : public testing::Test,
public ScriptExecutor::Listener {
public:
void SetUp() override {
delegate_.SetService(&mock_service_);
delegate_.SetWebController(&mock_web_controller_);
delegate_.SetCurrentURL(GURL("http://example.com/"));
TriggerContext::Options options;
options.experiment_ids = "additional_exp";
executor_ = std::make_unique<ScriptExecutor>(
kScriptPath,
std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(
std::map<std::string, std::string>{
{"additional_param", "additional_param_value"}}),
options),
/* global_payload= */ "initial global payload",
/* script_payload= */ "initial payload",
/* listener= */ this, &ordered_interrupts_,
/* delegate= */ &delegate_);
test_util::MockFindAnyElement(mock_web_controller_);
// In this test, "tell" actions always succeed and "highlight element"
// actions always fail.
ON_CALL(mock_web_controller_, HighlightElement(_, _))
.WillByDefault(RunOnceCallback<1>(ClientStatus(UNEXPECTED_JS_ERROR)));
}
protected:
ScriptExecutorTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME) {}
// Implements ScriptExecutor::Listener
void OnServerPayloadChanged(const std::string& global_payload,
const std::string& script_payload) override {
last_global_payload_ = global_payload;
last_script_payload_ = script_payload;
}
void OnScriptListChanged(
std::vector<std::unique_ptr<Script>> scripts) override {
should_update_scripts_ = true;
scripts_update_ = std::move(scripts);
++scripts_update_count_;
}
std::string Serialize(const google::protobuf::MessageLite& message) {
std::string output;
message.SerializeToString(&output);
return output;
}
// Creates a script that contains a wait_for_dom allow_interrupt=true followed
// by a tell. It will succeed if |element| eventually becomes visible.
void SetupInterruptibleScript(const std::string& path,
const std::string& element) {
ActionsResponseProto interruptible;
interruptible.set_global_payload("main script global payload");
interruptible.set_script_payload("main script payload");
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
*wait_action->mutable_wait_condition()->mutable_match() =
ToSelectorProto(element);
wait_action->set_allow_interrupt(true);
interruptible.add_actions()->mutable_tell()->set_message(path);
EXPECT_CALL(mock_service_, OnGetActions(StrEq(path), _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
}
// Creates an interrupt that contains a tell. It will always succeed.
void SetupInterrupt(const std::string& path, const std::string& trigger) {
RegisterInterrupt(path, trigger);
ActionsResponseProto interrupt_actions;
InitInterruptActions(&interrupt_actions, path);
EXPECT_CALL(mock_service_, OnGetActions(StrEq(path), _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)));
}
void InitInterruptActions(ActionsResponseProto* interrupt_actions,
const std::string& path) {
interrupt_actions->set_global_payload(
base::StrCat({"global payload for ", path}));
interrupt_actions->set_script_payload(base::StrCat({"payload for ", path}));
interrupt_actions->add_actions()->mutable_tell()->set_message(path);
}
// Registers an interrupt, but do not define actions for it.
void RegisterInterrupt(const std::string& path, const std::string& trigger) {
auto interrupt = std::make_unique<Script>();
interrupt->handle.path = path;
ScriptPreconditionProto interrupt_preconditions;
*interrupt_preconditions.mutable_element_condition()->mutable_match() =
ToSelectorProto(trigger);
interrupt->precondition =
ScriptPrecondition::FromProto(path, interrupt_preconditions);
ordered_interrupts_.emplace_back(std::move(interrupt));
}
// task_environment_ must be first to guarantee other field
// creation run in that environment.
base::test::TaskEnvironment task_environment_;
FakeScriptExecutorDelegate delegate_;
Script script_;
StrictMock<MockService> mock_service_;
NiceMock<MockWebController> mock_web_controller_;
std::vector<std::unique_ptr<Script>> ordered_interrupts_;
std::string last_global_payload_;
std::string last_script_payload_;
bool should_update_scripts_ = false;
std::vector<std::unique_ptr<Script>> scripts_update_;
int scripts_update_count_ = 0;
std::unique_ptr<ScriptExecutor> executor_;
StrictMock<base::MockCallback<ScriptExecutor::RunScriptCallback>>
executor_callback_;
UserData user_data_;
};
TEST_F(ScriptExecutorTest, GetActionsFails) {
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(executor_callback_,
Run(AllOf(Field(&ScriptExecutor::Result::success, false),
Field(&ScriptExecutor::Result::at_end,
ScriptExecutor::CONTINUE))));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, ForwardParameters) {
TriggerContext::Options options;
options.experiment_ids = "exp";
delegate_.SetTriggerContext(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(
std::map<std::string, std::string>{{"param", "value"}}),
options));
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(Invoke([](const std::string& script_path, const GURL& url,
const TriggerContext& trigger_context,
const std::string& global_payload,
const std::string& script_payload,
Service::ResponseCallback& callback) {
// |trigger_context| includes data passed to
// ScriptExecutor constructor as well as data from the
// delegate's TriggerContext.
EXPECT_THAT(trigger_context.GetExperimentIds(),
Eq("exp,additional_exp"));
EXPECT_THAT(
trigger_context.GetScriptParameters().ToProto(),
UnorderedElementsAreArray(std::map<std::string, std::string>(
{{"additional_param", "additional_param_value"},
{"param", "value"}})));
std::move(callback).Run(net::HTTP_OK, "");
}));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, RunOneActionReportAndReturn) {
ActionsResponseProto actions_response;
*actions_response.add_actions()
->mutable_highlight_element()
->mutable_element() = ToSelectorProto("will fail");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(AllOf(Field(&ScriptExecutor::Result::success, true),
Field(&ScriptExecutor::Result::at_end,
ScriptExecutor::CONTINUE))));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_EQ(1u, processed_actions_capture.size());
EXPECT_EQ(UNEXPECTED_JS_ERROR, processed_actions_capture[0].status());
EXPECT_TRUE(processed_actions_capture[0].has_run_time_ms());
EXPECT_GE(processed_actions_capture[0].run_time_ms(), 0);
}
TEST_F(ScriptExecutorTest, RunMultipleActions) {
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
initial_actions_response.add_actions()->mutable_tell()->set_message("2");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response)));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("3");
std::vector<ProcessedActionProto> processed_actions1_capture;
std::vector<ProcessedActionProto> processed_actions2_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
SaveArg<3>(&processed_actions1_capture),
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response))))
.WillOnce(DoAll(SaveArg<3>(&processed_actions2_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(2u, processed_actions1_capture.size());
EXPECT_EQ(1u, processed_actions2_capture.size());
}
ACTION_P2(Delay, env, delay) {
env->FastForwardBy(base::Milliseconds(delay));
}
TEST_F(ScriptExecutorTest, ShowsSlowConnectionWarningReplace) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow";
client_settings->enable_slow_connection_warnings = true;
client_settings->max_consecutive_slow_roundtrips = 2;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "2");
}
TEST_F(ScriptExecutorTest, ShowsSlowConnectionWarningConcatenate) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "... slow";
client_settings->enable_slow_connection_warnings = true;
client_settings->max_consecutive_slow_roundtrips = 2;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::CONCATENATE;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "1... slow");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "2");
}
TEST_F(ScriptExecutorTest, SlowConnectionWarningTriggersOnlyOnce) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow";
client_settings->enable_slow_connection_warnings = true;
client_settings->only_show_connection_warning_once = true;
client_settings->max_consecutive_slow_roundtrips = 1;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow");
task_environment_.FastForwardBy(base::Milliseconds(100));
EXPECT_EQ(delegate_.GetStatusMessage(), "2");
}
TEST_F(ScriptExecutorTest, SlowConnectionWarningTriggersMultipleTimes) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow";
client_settings->enable_slow_connection_warnings = true;
client_settings->only_show_connection_warning_once = false;
client_settings->only_show_warning_once = false;
client_settings->max_consecutive_slow_roundtrips = 1;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow");
task_environment_.FastForwardBy(base::Milliseconds(100));
EXPECT_EQ(delegate_.GetStatusMessage(), "slow");
task_environment_.FastForwardBy(base::Milliseconds(100));
EXPECT_EQ(delegate_.GetStatusMessage(), "2");
}
TEST_F(ScriptExecutorTest, SlowConnectionWarningNotShowingIfNotConsecutive) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow";
client_settings->enable_slow_connection_warnings = true;
client_settings->max_consecutive_slow_roundtrips = 2;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_NE(delegate_.GetStatusMessage(), "slow");
}
TEST_F(ScriptExecutorTest, SlowConnectionWarningNotShowingIfOnCompleted) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow";
client_settings->enable_slow_connection_warnings = true;
client_settings->max_consecutive_slow_roundtrips = 2;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response))));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(
RunOnceCallback<5>(net::HTTP_OK, Serialize(initial_actions_response)))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_NE(delegate_.GetStatusMessage(), "slow");
}
TEST_F(ScriptExecutorTest, SlowConnectionWarningNotShownIfSlowWebsiteFirst) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow connection";
client_settings->slow_website_message = "slow website";
client_settings->enable_slow_website_warnings = true;
client_settings->warning_delay = base::Milliseconds(1500);
client_settings->enable_slow_connection_warnings = true;
client_settings->only_show_warning_once = true;
client_settings->max_consecutive_slow_roundtrips = 2;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto tell1_waitfordom;
tell1_waitfordom.add_actions()->mutable_tell()->set_message("1");
auto* wait_for_dom = tell1_waitfordom.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(
DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell1_waitfordom))));
// Active check takes longer than warning timeout.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(DoAll(
Delay(&task_environment_, 2000),
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr)))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
ActionsResponseProto tell2;
tell2.add_actions()->mutable_tell()->set_message("2");
ActionsResponseProto tell3;
tell3.add_actions()->mutable_tell()->set_message("3");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell2))))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell3))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow website");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "3");
}
TEST_F(ScriptExecutorTest, SlowWebsiteWarningReplace) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_website_message = "slow";
client_settings->enable_slow_website_warnings = true;
client_settings->warning_delay = base::Milliseconds(1500);
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("1");
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
// Active check takes longer than warning timeout.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(Delay(&task_environment_, 2000));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow");
}
TEST_F(ScriptExecutorTest, SlowWebsiteWarningConcatenate) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_website_message = "... slow";
client_settings->enable_slow_website_warnings = true;
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::CONCATENATE;
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("1");
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
// Active check takes longer than warning timeout.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(Delay(&task_environment_, 2000));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "1... slow");
}
TEST_F(ScriptExecutorTest, SlowWebsiteWarningTriggersOnlyOnce) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_website_message = "slow";
client_settings->enable_slow_website_warnings = true;
client_settings->only_show_website_warning_once = true;
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("1");
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
auto* second_wait_for_dom =
next_actions_response.add_actions()->mutable_wait_for_dom();
*second_wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element2");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
// Active check takes longer than warning timeout.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(DoAll(
Delay(&task_environment_, 2000),
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr)))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element2"}), _))
.WillOnce(DoAll(Delay(&task_environment_, 2000),
RunOnceCallback<1>(
ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "2");
}
TEST_F(ScriptExecutorTest, SlowWebsiteWarningNotShownIfSlowConnectionFirst) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow connection";
client_settings->slow_website_message = "slow website";
client_settings->enable_slow_website_warnings = true;
client_settings->warning_delay = base::Milliseconds(1500);
client_settings->enable_slow_connection_warnings = true;
client_settings->only_show_warning_once = true;
client_settings->max_consecutive_slow_roundtrips = 1;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto tell1;
tell1.add_actions()->mutable_tell()->set_message("1");
ActionsResponseProto tell2_waitfordom;
tell2_waitfordom.add_actions()->mutable_tell()->set_message("2");
auto* wait_for_dom = tell2_waitfordom.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
ActionsResponseProto tell3;
tell3.add_actions()->mutable_tell()->set_message("3");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell1))));
// Active check takes longer than warning timeout.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(DoAll(
Delay(&task_environment_, 2000),
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr)))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(
DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell2_waitfordom))))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell3))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow connection");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "2");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "3");
}
TEST_F(ScriptExecutorTest, SlowWarningsBothShownIfConfigured) {
ClientSettings* client_settings = delegate_.GetMutableSettings();
client_settings->slow_connection_message = "slow connection";
client_settings->slow_website_message = "slow website";
client_settings->enable_slow_website_warnings = true;
client_settings->warning_delay = base::Milliseconds(1500);
client_settings->enable_slow_connection_warnings = true;
client_settings->only_show_warning_once = false;
client_settings->max_consecutive_slow_roundtrips = 1;
client_settings->slow_roundtrip_threshold = base::Milliseconds(100);
client_settings->minimum_warning_duration = base::Milliseconds(100);
client_settings->message_mode =
ClientSettingsProto::SlowWarningSettings::REPLACE;
ActionsResponseProto tell1;
tell1.add_actions()->mutable_tell()->set_message("1");
ActionsResponseProto tell2_and_waitfordom;
tell2_and_waitfordom.add_actions()->mutable_tell()->set_message("2");
auto* wait_for_dom =
tell2_and_waitfordom.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
ActionsResponseProto tell3;
tell3.add_actions()->mutable_tell()->set_message("3");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell1))));
// Active check takes longer than warning timeout.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(DoAll(
Delay(&task_environment_, 2000),
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr)))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell2_and_waitfordom))))
.WillOnce(DoAll(Delay(&task_environment_, 600),
RunOnceCallback<5>(net::HTTP_OK, Serialize(tell3))))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow connection");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "slow website");
// task_environment_.DescribeCurrentTasks();
//// EXPECT_EQ(delegate_.GetStatusMessage(), "2");
task_environment_.FastForwardBy(
task_environment_.NextMainThreadPendingTaskDelay());
EXPECT_EQ(delegate_.GetStatusMessage(), "3");
}
TEST_F(ScriptExecutorTest, UnsupportedAction) {
ActionsResponseProto actions_response;
actions_response.add_actions(); // action definition missing
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_EQ(1u, processed_actions_capture.size());
EXPECT_EQ(UNSUPPORTED_ACTION, processed_actions_capture[0].status());
}
TEST_F(ScriptExecutorTest, StopAfterEnd) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_stop();
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(AllOf(Field(&ScriptExecutor::Result::success, true),
Field(&ScriptExecutor::Result::at_end,
ScriptExecutor::SHUTDOWN))));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, InterruptActionListOnError) {
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message(
"will pass");
*initial_actions_response.add_actions()
->mutable_highlight_element()
->mutable_element() = ToSelectorProto("will fail");
initial_actions_response.add_actions()->mutable_tell()->set_message(
"never run");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response)));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message(
"will run after error");
std::vector<ProcessedActionProto> processed_actions1_capture;
std::vector<ProcessedActionProto> processed_actions2_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(
SaveArg<3>(&processed_actions1_capture),
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response))))
.WillOnce(DoAll(SaveArg<3>(&processed_actions2_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_EQ(2u, processed_actions1_capture.size());
EXPECT_EQ(ACTION_APPLIED, processed_actions1_capture[0].status());
EXPECT_EQ(UNEXPECTED_JS_ERROR, processed_actions1_capture[1].status());
ASSERT_EQ(1u, processed_actions2_capture.size());
EXPECT_EQ(ACTION_APPLIED, processed_actions2_capture[0].status());
// make sure "never run" wasn't the one that was run.
EXPECT_EQ("will run after error",
processed_actions2_capture[0].action().tell().message());
}
TEST_F(ScriptExecutorTest, RunDelayedAction) {
ActionsResponseProto actions_response;
ActionProto* action = actions_response.add_actions();
action->mutable_tell()->set_message("delayed");
action->set_action_delay_ms(1000);
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// executor_callback_.Run() not expected to be run just yet, as the action is
// delayed.
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_TRUE(task_environment_.NextTaskIsDelayed());
// Moving forward in time triggers action execution.
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
task_environment_.FastForwardBy(base::Milliseconds(1000));
EXPECT_EQ(task_environment_.GetPendingMainThreadTaskCount(), 0u);
}
TEST_F(ScriptExecutorTest, ClearDetailsWhenFinished) {
ActionsResponseProto actions_response;
ActionProto click_with_clean_contextual_ui;
click_with_clean_contextual_ui.set_clean_contextual_ui(true);
click_with_clean_contextual_ui.mutable_tell()->set_message("clean");
*actions_response.add_actions() = click_with_clean_contextual_ui;
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
// empty, but not null
delegate_.SetDetails(std::make_unique<Details>(), base::TimeDelta());
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_THAT(delegate_.GetDetails(), IsEmpty());
}
TEST_F(ScriptExecutorTest, DontClearDetailsIfOtherActionsAreLeft) {
ActionsResponseProto actions_response;
ActionProto click_with_clean_contextual_ui;
click_with_clean_contextual_ui.set_clean_contextual_ui(true);
click_with_clean_contextual_ui.mutable_tell()->set_message("clean");
*actions_response.add_actions() = click_with_clean_contextual_ui;
actions_response.add_actions()->mutable_tell()->set_message("Wait no!");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
// empty, but not null
delegate_.SetDetails(std::make_unique<Details>(), base::TimeDelta());
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_THAT(delegate_.GetDetails(), Not(IsEmpty()));
}
TEST_F(ScriptExecutorTest, ClearDetailsOnError) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("Hello");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, false)));
// empty, but not null
delegate_.SetDetails(std::make_unique<Details>(), base::TimeDelta());
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_THAT(delegate_.GetDetails(), IsEmpty());
}
TEST_F(ScriptExecutorTest, ForwardLastPayloadOnSuccess) {
ActionsResponseProto actions_response;
actions_response.set_global_payload("actions global payload");
actions_response.set_script_payload("actions payload");
actions_response.add_actions()->mutable_tell()->set_message("ok");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, "initial global payload",
"initial payload", _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
ActionsResponseProto next_actions_response;
next_actions_response.set_global_payload("last global payload");
next_actions_response.set_script_payload("last payload");
EXPECT_CALL(mock_service_, OnGetNextActions(_, "actions global payload",
"actions payload", _, _, _))
.WillOnce(
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response)));
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("last global payload", last_global_payload_);
EXPECT_EQ("last payload", last_script_payload_);
}
TEST_F(ScriptExecutorTest, ForwardLastPayloadOnError) {
ActionsResponseProto actions_response;
actions_response.set_global_payload("actions global payload");
actions_response.set_script_payload("actions payload");
actions_response.add_actions()->mutable_tell()->set_message("ok");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, "initial global payload",
"initial payload", _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, "actions global payload",
"actions payload", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("actions global payload", last_global_payload_);
EXPECT_EQ("actions payload", last_script_payload_);
}
TEST_F(ScriptExecutorTest, WaitForDomWaitUntil) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// First check does not find the element, wait for dom waits 1s, then the
// element is found, and the action succeeds.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(executor_callback_, Run(_));
task_environment_.FastForwardBy(base::Seconds(1));
ASSERT_EQ(1u, processed_actions_capture.size());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
}
TEST_F(ScriptExecutorTest, RunInterrupt) {
// All elements exist, so first the interrupt should be run, then the element
// should be reported as found.
SetupInterruptibleScript(kScriptPath, "element");
SetupInterrupt("interrupt", "interrupt_trigger");
// Both scripts end after the first set of actions. Capture the results.
std::vector<ProcessedActionProto> processed_actions1_capture;
std::vector<ProcessedActionProto> processed_actions2_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions1_capture),
RunOnceCallback<5>(net::HTTP_OK, "")))
.WillOnce(DoAll(SaveArg<3>(&processed_actions2_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
// The first script to call OnGetNextActions is the interrupt, which starts
// with a tell.
ASSERT_THAT(processed_actions1_capture, Not(IsEmpty()));
EXPECT_EQ(ActionProto::ActionInfoCase::kTell,
processed_actions1_capture[0].action().action_info_case());
EXPECT_EQ(ACTION_APPLIED, processed_actions1_capture[0].status());
// The second script to call OnGetNextActions is the main script, with starts
// with a wait_for_dom
ASSERT_THAT(processed_actions2_capture, Not(IsEmpty()));
EXPECT_EQ(ActionProto::ActionInfoCase::kWaitForDom,
processed_actions2_capture[0].action().action_info_case());
EXPECT_EQ(ACTION_APPLIED, processed_actions2_capture[0].status());
}
TEST_F(ScriptExecutorTest, RunMultipleInterruptInOrder) {
// All elements exist. The two interrupts should run, in order, then the
// element should be reported as found.
SetupInterruptibleScript(kScriptPath, "element");
SetupInterrupt("interrupt1", "interrupt_trigger1");
SetupInterrupt("interrupt2", "interrupt_trigger2");
{
testing::InSequence seq;
EXPECT_CALL(mock_service_,
OnGetNextActions(_, _, "payload for interrupt1", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(mock_service_,
OnGetNextActions(_, _, "payload for interrupt2", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(mock_service_,
OnGetNextActions(_, _, "main script payload", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
}
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, RunSameInterruptMultipleTimes) {
// In a main script with three wait_for_dom with allow_interrupt=true...
ActionsResponseProto interruptible;
for (int i = 0; i < 3; i++) {
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
*wait_action->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
wait_action->set_allow_interrupt(true);
}
EXPECT_CALL(mock_service_, OnGetActions(StrEq("script_path"), _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
// 'interrupt' with matching preconditions runs exactly three times.
RegisterInterrupt("interrupt", "interrupt_trigger");
ActionsResponseProto interrupt_actions;
InitInterruptActions(&interrupt_actions, "interrupt");
EXPECT_CALL(mock_service_, OnGetActions(StrEq("interrupt"), _, _, _, _, _))
.Times(3)
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)));
// All scripts succeed with no more actions.
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, ForwardMainScriptPayloadWhenInterruptRuns) {
SetupInterruptibleScript(kScriptPath, "element");
SetupInterrupt("interrupt", "interrupt_trigger");
ActionsResponseProto next_interrupt_actions_response;
next_interrupt_actions_response.set_global_payload(
"last global payload from interrupt");
next_interrupt_actions_response.set_script_payload(
"last payload from interrupt");
EXPECT_CALL(mock_service_, OnGetNextActions(_, "global payload for interrupt",
"payload for interrupt", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK,
Serialize(next_interrupt_actions_response)));
ActionsResponseProto next_main_actions_response;
next_main_actions_response.set_global_payload(
"last global payload from main");
next_main_actions_response.set_script_payload("last payload from main");
EXPECT_CALL(mock_service_,
OnGetNextActions(_, "last global payload from interrupt",
"main script payload", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK,
Serialize(next_main_actions_response)));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("last global payload from main", last_global_payload_);
EXPECT_EQ("last payload from main", last_script_payload_);
}
TEST_F(ScriptExecutorTest, ForwardMainScriptPayloadWhenInterruptFails) {
SetupInterruptibleScript(kScriptPath, "element");
SetupInterrupt("interrupt", "interrupt_trigger");
EXPECT_CALL(mock_service_, OnGetNextActions(_, "global payload for interrupt",
"payload for interrupt", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(mock_service_, OnGetNextActions(_, "global payload for interrupt",
"main script payload", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("global payload for interrupt", last_global_payload_);
EXPECT_EQ("main script payload", last_script_payload_);
}
TEST_F(ScriptExecutorTest, DoNotRunInterruptIfPreconditionsDontMatch) {
// interrupt_trigger does not exist, but element does, so wait_for_dom will
// succeed without calling the interrupt.
SetupInterruptibleScript(kScriptPath, "element");
SetupInterrupt("interrupt", "interrupt_trigger");
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(mock_web_controller_,
OnFindElement(Selector({"interrupt_trigger"}), _))
.WillRepeatedly(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
// The script ends after the first set of actions. There is only one call
// from the main script, running a WaitForDom - none from the interrupt.
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_THAT(processed_actions_capture, Not(IsEmpty()));
EXPECT_EQ(ActionProto::ActionInfoCase::kWaitForDom,
processed_actions_capture[0].action().action_info_case());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
}
TEST_F(ScriptExecutorTest, DoNotRunInterruptIfNotInterruptible) {
// The main script has a wait_for_dom, but it is not interruptible.
ActionsResponseProto interruptible;
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
*wait_action->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
// allow_interrupt is not set
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
// The interrupt would trigger, since interrupt_trigger exits, but it's not
// given an opportunity to.
SetupInterrupt("interrupt", "interrupt_trigger");
// The script ends after the first set of actions. There is only one call
// from the main script, running a WaitForDom - none from the interrupt.
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_THAT(processed_actions_capture, Not(IsEmpty()));
EXPECT_EQ(ActionProto::ActionInfoCase::kWaitForDom,
processed_actions_capture[0].action().action_info_case());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
}
TEST_F(ScriptExecutorTest, InterruptFailsMainScript) {
// The interrupt is run and fails. Failure should cascade.
SetupInterruptibleScript(kScriptPath, "element");
SetupInterrupt("interrupt", "interrupt_trigger");
// The interrupt fails.
EXPECT_CALL(mock_service_,
OnGetNextActions(_, _, "payload for interrupt", _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
// The main script gets a report of the failure from the interrupt, and fails
// in turn.
EXPECT_CALL(
mock_service_,
OnGetNextActions(
_, _, "main script payload",
ElementsAre(Property(&ProcessedActionProto::status,
ProcessedActionStatusProto::INTERRUPT_FAILED)),
_, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, false)));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, InterruptReturnsShutdown) {
// The interrupt succeeds, but executes the stop action. This should stop the
// execution of the main script and make it return result.at_end=SHUTDOWN
SetupInterruptibleScript(kScriptPath, "element");
RegisterInterrupt("interrupt", "interrupt_trigger");
ActionsResponseProto interrupt_actions;
interrupt_actions.add_actions()->mutable_stop();
// Get interrupt actions
EXPECT_CALL(mock_service_, OnGetActions(StrEq("interrupt"), _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)));
// We expect to get result of interrupt action, then result of the main script
// action.
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.Times(2)
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(AllOf(Field(&ScriptExecutor::Result::success, true),
Field(&ScriptExecutor::Result::at_end,
ScriptExecutor::SHUTDOWN))));
executor_->Run(&user_data_, executor_callback_.Get());
}
TEST_F(ScriptExecutorTest, RunInterruptDuringPrompt) {
SetupInterrupt("interrupt", "interrupt_trigger");
// Main script has a prompt with an "auto_select" element. This functions very
// much like a WaitForDom, except for the UI changes triggered by the switches
// between PROMPT and RUNNING states.
ActionsResponseProto interruptible;
auto* prompt_action = interruptible.add_actions()->mutable_prompt();
prompt_action->set_allow_interrupt(true);
*prompt_action->add_choices()->mutable_auto_select_when()->mutable_match() =
ToSelectorProto("end_prompt");
interruptible.add_actions()->mutable_tell()->set_message("done");
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
EXPECT_CALL(mock_web_controller_,
OnFindElement(Selector({"interrupt_trigger"}), _))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}))
.WillRepeatedly(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"end_prompt"}), _))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
// Expected scenario:
// - show prompt (enter PROMPT state)
// - notice interrupt_trigger element
// - run interrupt (enter RUNNING state)
// - show prompt again (enter PROMPT state)
// - notice end_prompt element
// - end prompt, continue main script (enter RUNNING state)
// - run tell, which sets message to "done"
EXPECT_THAT(delegate_.GetStateHistory(),
ElementsAre(AutofillAssistantState::PROMPT,
AutofillAssistantState::RUNNING,
AutofillAssistantState::PROMPT,
AutofillAssistantState::RUNNING));
EXPECT_EQ("done", delegate_.GetStatusMessage());
}
TEST_F(ScriptExecutorTest, RunPromptInBrowseMode) {
ActionsResponseProto actions_response;
auto* prompt = actions_response.add_actions()->mutable_prompt();
prompt->add_choices()->mutable_chip()->set_text("done");
prompt->set_browse_mode(true);
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(AutofillAssistantState::BROWSE, delegate_.GetState());
}
TEST_F(ScriptExecutorTest, RunPromptInPromptMode) {
ActionsResponseProto actions_response;
auto* prompt = actions_response.add_actions()->mutable_prompt();
prompt->add_choices()->mutable_chip()->set_text("done");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(AutofillAssistantState::PROMPT, delegate_.GetState());
}
TEST_F(ScriptExecutorTest, RunInterruptMultipleTimesDuringPrompt) {
SetupInterrupt("interrupt", "interrupt_trigger");
// Main script has a prompt with an "auto_select" element. This functions very
// much like a WaitForDom, except for the UI changes triggered by the switches
// between PROMPT and RUNNING states.
ActionsResponseProto interruptible;
auto* prompt_action = interruptible.add_actions()->mutable_prompt();
prompt_action->set_allow_interrupt(true);
*prompt_action->add_choices()->mutable_auto_select_when()->mutable_match() =
ToSelectorProto("end_prompt");
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
// interrupt_trigger goes away and come back, which means that the interrupt
// will be run twice.
EXPECT_CALL(mock_web_controller_,
OnFindElement(Selector({"interrupt_trigger"}), _))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}))
.WillRepeatedly(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
// It takes a several rounds for end_prompt to appear, which gives time for
// the interrupt to run.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"end_prompt"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
for (int try_count = 0; try_count < 10; try_count++) {
task_environment_.FastForwardBy(base::Milliseconds(1000));
}
EXPECT_THAT(
delegate_.GetStateHistory(),
ElementsAre(
AutofillAssistantState::PROMPT, AutofillAssistantState::RUNNING,
AutofillAssistantState::PROMPT, AutofillAssistantState::RUNNING,
AutofillAssistantState::PROMPT, AutofillAssistantState::RUNNING));
}
TEST_F(ScriptExecutorTest, UpdateScriptListGetNext) {
should_update_scripts_ = false;
scripts_update_.clear();
scripts_update_count_ = 0;
ActionsResponseProto initial_actions_response;
initial_actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK,
Serialize(initial_actions_response)));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("2");
auto* script =
next_actions_response.mutable_update_script_list()->add_scripts();
script->set_path("path");
auto* presentation = script->mutable_presentation();
presentation->mutable_chip()->set_text("name");
presentation->mutable_precondition();
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(
RunOnceCallback<5>(net::HTTP_OK, Serialize(next_actions_response)))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_TRUE(should_update_scripts_);
EXPECT_THAT(scripts_update_, SizeIs(1));
EXPECT_THAT(scripts_update_count_, Eq(1));
EXPECT_THAT("path", scripts_update_[0]->handle.path);
EXPECT_THAT("name", scripts_update_[0]->handle.chip.text);
}
TEST_F(ScriptExecutorTest, UpdateScriptListShouldNotifyMultipleTimes) {
should_update_scripts_ = false;
scripts_update_.clear();
scripts_update_count_ = 0;
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("hi");
auto* script = actions_response.mutable_update_script_list()->add_scripts();
script->set_path("path");
auto* presentation = script->mutable_presentation();
presentation->mutable_chip()->set_text("name");
presentation->mutable_precondition();
EXPECT_CALL(mock_service_, OnGetActions(StrEq(kScriptPath), _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
script->set_path("path2");
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_TRUE(should_update_scripts_);
EXPECT_THAT(scripts_update_count_, Eq(2));
EXPECT_THAT(scripts_update_, SizeIs(1));
EXPECT_THAT("path2", scripts_update_[0]->handle.path);
}
TEST_F(ScriptExecutorTest, UpdateScriptListFromInterrupt) {
should_update_scripts_ = false;
scripts_update_.clear();
scripts_update_count_ = 0;
SetupInterruptibleScript(kScriptPath, "element");
RegisterInterrupt("interrupt", "interrupt_trigger");
ActionsResponseProto interrupt_actions;
interrupt_actions.add_actions()->mutable_tell()->set_message("abc");
EXPECT_CALL(mock_service_, OnGetActions(StrEq("interrupt"), _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)));
auto* script = interrupt_actions.mutable_update_script_list()->add_scripts();
script->set_path("path");
auto* presentation = script->mutable_presentation();
presentation->mutable_chip()->set_text("update_from_interrupt");
presentation->mutable_precondition();
// We expect a call from the interrupt which will update the script list and a
// second call from the interrupt to terminate. Then a call from the main
// script which will finish without running any actions.
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.Times(3)
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)))
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_TRUE(should_update_scripts_);
EXPECT_THAT(scripts_update_, SizeIs(1));
EXPECT_THAT(scripts_update_count_, Eq(1));
EXPECT_THAT("path", scripts_update_[0]->handle.path);
EXPECT_THAT("update_from_interrupt", scripts_update_[0]->handle.chip.text);
}
TEST_F(ScriptExecutorTest, RestorePreInterruptStatusMessage) {
ActionsResponseProto interruptible;
interruptible.add_actions()->mutable_tell()->set_message(
"pre-interrupt status");
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
*wait_action->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
wait_action->set_allow_interrupt(true);
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
RegisterInterrupt("interrupt", "interrupt_trigger");
ActionsResponseProto interrupt_actions;
interrupt_actions.add_actions()->mutable_tell()->set_message(
"interrupt status");
EXPECT_CALL(mock_service_, OnGetActions(StrEq("interrupt"), _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
delegate_.SetStatusMessage("pre-run status");
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("pre-interrupt status", delegate_.GetStatusMessage());
}
TEST_F(ScriptExecutorTest, KeepStatusMessageWhenNotInterrupted) {
ActionsResponseProto interruptible;
interruptible.add_actions()->mutable_tell()->set_message(
"pre-interrupt status");
auto* wait_action = interruptible.add_actions()->mutable_wait_for_dom();
*wait_action->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
wait_action->set_allow_interrupt(true);
EXPECT_CALL(mock_service_, OnGetActions(kScriptPath, _, _, _, _, _))
.WillRepeatedly(
RunOnceCallback<5>(net::HTTP_OK, Serialize(interruptible)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillRepeatedly(RunOnceCallback<5>(net::HTTP_OK, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
delegate_.SetStatusMessage("pre-run status");
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("pre-interrupt status", delegate_.GetStatusMessage());
}
TEST_F(ScriptExecutorTest, PauseWaitForDomWhileNavigating) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
wait_for_dom->set_timeout_ms(2000);
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// First check does not find the element, wait for dom waits 1s.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
executor_->Run(&user_data_, executor_callback_.Get());
// Navigation starts while WaitForDom is waiting. The action doesn't fail,
// even though navigation takes a few seconds longer than the WaitForDom
// timeout.
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
for (int i = 0; i < 5; i++) {
task_environment_.FastForwardBy(base::Seconds(1));
}
// The end of navigation un-pauses WaitForDom.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
EXPECT_CALL(executor_callback_, Run(_));
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_EQ(1u, processed_actions_capture.size());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
}
TEST_F(ScriptExecutorTest, StartWaitForDomWhileNavigating) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
wait_for_dom->set_timeout_ms(2000);
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// Navigation starts before WaitForDom starts. WaitForDom does not wait and
// completes successfully.
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
// Navigation finishes after the WaitForDom has finished.
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_EQ(1u, processed_actions_capture.size());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_FALSE(processed_actions_capture[0].navigation_info().started());
EXPECT_FALSE(processed_actions_capture[0].navigation_info().ended());
}
TEST_F(ScriptExecutorTest, NavigateWhileRunningInterrupt) {
SetupInterruptibleScript(kScriptPath, "element");
RegisterInterrupt("interrupt", "interrupt_trigger");
ActionsResponseProto interrupt_actions;
InitInterruptActions(&interrupt_actions, "interrupt");
// A load even happens when loading the interrupt scripts, so while the
// interrupt is being executed. This should not interfere with the WaitForDom
// action that's running the interrupt.
EXPECT_CALL(mock_service_, OnGetActions(StrEq("interrupt"), _, _, _, _, _))
.WillRepeatedly(
DoAll(InvokeWithoutArgs(
[this]() { delegate_.UpdateNavigationState(true, false); }),
RunOnceCallback<5>(net::HTTP_OK, Serialize(interrupt_actions)),
InvokeWithoutArgs([this]() {
delegate_.UpdateNavigationState(false, false);
})));
std::vector<ProcessedActionProto> processed_actions1_capture;
std::vector<ProcessedActionProto> processed_actions2_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions1_capture),
RunOnceCallback<5>(net::HTTP_OK, "")))
.WillOnce(DoAll(SaveArg<3>(&processed_actions2_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(ACTION_APPLIED, processed_actions1_capture[0].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions2_capture[0].status());
}
TEST_F(ScriptExecutorTest, ReportNavigationErrors) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("a");
actions_response.add_actions()->mutable_tell()->set_message("b");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ true);
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_THAT(processed_actions_capture, SizeIs(2));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_TRUE(processed_actions_capture[0].navigation_info().has_error());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[1].status());
EXPECT_TRUE(processed_actions_capture[1].navigation_info().has_error());
}
TEST_F(ScriptExecutorTest, ReportNavigationEnd) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// WaitForDom does NOT wait for navigation to end, it immediately checks for
// the element, which fails.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
// Navigation starts before the script is run.
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
// Checking for the element succeeds on the second try.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
task_environment_.FastForwardBy(base::Seconds(1));
ASSERT_THAT(processed_actions_capture, SizeIs(1));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_FALSE(processed_actions_capture[0].navigation_info().started());
EXPECT_TRUE(processed_actions_capture[0].navigation_info().ended());
}
TEST_F(ScriptExecutorTest, ReportUnexpectedNavigationStart) {
ActionsResponseProto actions_response;
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// As the element doesn't exist, WaitForDom returns and waits for 1s.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
// Navigation end forces a re-check, which succeeds
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_THAT(processed_actions_capture, SizeIs(1));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_TRUE(processed_actions_capture[0].navigation_info().started());
EXPECT_TRUE(processed_actions_capture[0].navigation_info().unexpected());
}
TEST_F(ScriptExecutorTest, ReportExpectedNavigationStart) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_expect_navigation();
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// As the element doesn't exist, WaitForDom returns and waits for 1s.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
// Navigation end forces a re-check, which succeeds
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillRepeatedly(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_THAT(processed_actions_capture, SizeIs(2));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_TRUE(processed_actions_capture[1].navigation_info().started());
EXPECT_FALSE(processed_actions_capture[1].navigation_info().unexpected());
}
TEST_F(ScriptExecutorTest, WaitForNavigationWithoutExpectation) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_wait_for_navigation();
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// WaitForNavigation returns immediately
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_THAT(processed_actions_capture, SizeIs(1));
EXPECT_EQ(INVALID_ACTION, processed_actions_capture[0].status());
}
TEST_F(ScriptExecutorTest, ExpectNavigation) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_expect_navigation();
actions_response.add_actions()->mutable_wait_for_navigation();
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// WaitForNavigation waits for navigation to start after expect_navigation
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_THAT(processed_actions_capture, SizeIs(2));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[1].status());
}
TEST_F(ScriptExecutorTest, MultipleWaitForNavigation) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_expect_navigation();
actions_response.add_actions()->mutable_wait_for_navigation();
actions_response.add_actions()->mutable_wait_for_navigation();
actions_response.add_actions()->mutable_wait_for_navigation();
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// The first wait_for_navigation waits for the navigation to happen. After
// that, the other wait_for_navigation return immediately.
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_THAT(processed_actions_capture, SizeIs(4));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[1].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[2].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[3].status());
}
TEST_F(ScriptExecutorTest, ExpectLaterNavigationIgnoringNavigationInProgress) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_expect_navigation();
actions_response.add_actions()->mutable_wait_for_navigation();
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
// WaitForNavigation waits for navigation to *start* after expect_navigation
executor_->Run(&user_data_, executor_callback_.Get());
// This ends the navigation that was in progress when expect_navigation was
// called. wait_for_navigation should not return, since navigation started
// after expect_navigation was called.
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
// This starts the new navigation.
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
// This ends the new navigation. wait_for_navigation returns.
EXPECT_CALL(executor_callback_, Run(_));
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ false);
ASSERT_THAT(processed_actions_capture, SizeIs(2));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[1].status());
}
TEST_F(ScriptExecutorTest, WaitForNavigationReportsError) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_expect_navigation();
actions_response.add_actions()->mutable_wait_for_navigation();
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<3>(&processed_actions_capture),
RunOnceCallback<5>(net::HTTP_OK, "")));
// WaitForNavigation waits for navigation to start after expect_navigation
EXPECT_CALL(executor_callback_, Run(_));
executor_->Run(&user_data_, executor_callback_.Get());
delegate_.UpdateNavigationState(/* navigating= */ true, /* error= */ false);
delegate_.UpdateNavigationState(/* navigating= */ false, /* error= */ true);
ASSERT_THAT(processed_actions_capture, SizeIs(2));
EXPECT_EQ(ACTION_APPLIED, processed_actions_capture[0].status());
EXPECT_EQ(NAVIGATION_ERROR, processed_actions_capture[1].status());
}
TEST_F(ScriptExecutorTest, InterceptUserActions) {
ActionsResponseProto actions_response;
actions_response.add_actions()
->mutable_prompt()
->add_choices()
->mutable_chip()
->set_text("done");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ(AutofillAssistantState::PROMPT, delegate_.GetState());
ASSERT_NE(nullptr, delegate_.GetUserActions());
ASSERT_THAT(*delegate_.GetUserActions(), SizeIs(1));
// The prompt action must finish. We don't bother continuing with the script
// in this test.
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _));
(*delegate_.GetUserActions())[0].Call(std::make_unique<TriggerContext>());
EXPECT_EQ(AutofillAssistantState::RUNNING, delegate_.GetState());
}
TEST_F(ScriptExecutorTest, ReportDirectActionsChoices) {
ActionsResponseProto actions_response;
actions_response.add_actions()
->mutable_prompt()
->add_choices()
->mutable_direct_action()
->add_names("done");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
std::vector<ProcessedActionProto> processed_actions_capture;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(SaveArg<3>(&processed_actions_capture));
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_NE(nullptr, delegate_.GetUserActions());
ASSERT_THAT(*delegate_.GetUserActions(), SizeIs(1));
TriggerContext::Options options;
options.is_direct_action = true;
(*delegate_.GetUserActions())[0].Call(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(), options));
ASSERT_THAT(processed_actions_capture, SizeIs(1));
EXPECT_TRUE(processed_actions_capture[0].direct_action());
}
TEST_F(ScriptExecutorTest, PauseAndResume) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("Tell");
actions_response.add_actions()
->mutable_prompt()
->add_choices()
->mutable_chip()
->set_text("Chip");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("Tell", delegate_.GetStatusMessage());
EXPECT_EQ(AutofillAssistantState::PROMPT, delegate_.GetState());
executor_->OnPause("Paused", "Button");
EXPECT_EQ("Paused", delegate_.GetStatusMessage());
EXPECT_EQ(AutofillAssistantState::STOPPED, delegate_.GetState());
ASSERT_THAT(*delegate_.GetUserActions(), SizeIs(1));
EXPECT_THAT(
*delegate_.GetUserActions(),
ElementsAre(Property(&UserAction::chip,
AllOf(Field(&Chip::text, StrEq("Button")),
Field(&Chip::type, HIGHLIGHTED_ACTION)))));
(*delegate_.GetUserActions())[0].Call(std::make_unique<TriggerContext>());
EXPECT_EQ("Tell", delegate_.GetStatusMessage());
EXPECT_THAT(delegate_.GetStateHistory(),
ElementsAre(AutofillAssistantState::PROMPT,
AutofillAssistantState::STOPPED,
AutofillAssistantState::RUNNING,
AutofillAssistantState::PROMPT));
}
TEST_F(ScriptExecutorTest, PauseAndResumeWithOngoingAction) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("Tell");
auto* wait_for_dom = actions_response.add_actions()->mutable_wait_for_dom();
wait_for_dom->set_timeout_ms(5000);
*wait_for_dom->mutable_wait_condition()->mutable_match() =
ToSelectorProto("element");
auto* prompt = actions_response.add_actions()->mutable_prompt();
prompt->set_message("Prompt");
prompt->add_choices()->mutable_chip()->set_text("Chip");
actions_response.add_actions()->mutable_tell()->set_message("Finished");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
// At first we don't find the element, to keep the |WaitForDomAction| running.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(
RunOnceCallback<1>(ClientStatus(ELEMENT_RESOLUTION_FAILED), nullptr));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_EQ("Tell", delegate_.GetStatusMessage());
EXPECT_THAT(delegate_.GetState(), Not(Eq(AutofillAssistantState::PROMPT)));
executor_->OnPause("Paused", "Button");
EXPECT_EQ("Paused", delegate_.GetStatusMessage());
EXPECT_EQ(AutofillAssistantState::STOPPED, delegate_.GetState());
ASSERT_THAT(*delegate_.GetUserActions(), SizeIs(1));
EXPECT_THAT(
*delegate_.GetUserActions(),
ElementsAre(Property(&UserAction::chip,
AllOf(Field(&Chip::text, StrEq("Button")),
Field(&Chip::type, HIGHLIGHTED_ACTION)))));
// Resume, this should not restart the |WaitForDomAction|, it should also
// not advance to the next action (i.e. |PromptAction|), so the status
// status message is the one from |TellAction|.
EXPECT_CALL(mock_web_controller_, OnFindElement(_, _)).Times(0);
(*delegate_.GetUserActions())[0].Call(std::make_unique<TriggerContext>());
EXPECT_EQ("Tell", delegate_.GetStatusMessage());
EXPECT_EQ(AutofillAssistantState::RUNNING, delegate_.GetState());
// We have resumed, the |WaitForDom| should now finish and advance the script.
EXPECT_CALL(mock_web_controller_, OnFindElement(Selector({"element"}), _))
.WillOnce(WithArgs<1>([](auto&& callback) {
std::move(callback).Run(OkClientStatus(),
std::make_unique<ElementFinder::Result>());
}));
task_environment_.FastForwardBy(base::Milliseconds(1000));
EXPECT_EQ("Prompt", delegate_.GetStatusMessage());
EXPECT_EQ(AutofillAssistantState::PROMPT, delegate_.GetState());
}
TEST_F(ScriptExecutorTest, RoundtripTimingStats) {
ActionsResponseProto actions_response;
ActionProto* action = actions_response.add_actions();
action->mutable_tell()->set_message("1");
action->set_action_delay_ms(1000);
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(
DoAll(Delay(&task_environment_, 200),
RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response))));
ActionsResponseProto next_actions_response;
next_actions_response.add_actions()->mutable_tell()->set_message("3");
RoundtripTimingStats timing_stats;
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(DoAll(SaveArg<4>(&timing_stats),
RunOnceCallback<5>(net::HTTP_OK, "")));
executor_->Run(&user_data_, executor_callback_.Get());
EXPECT_TRUE(task_environment_.NextTaskIsDelayed());
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, true)));
task_environment_.FastForwardBy(base::Milliseconds(1000));
// Moving forward in time triggers action execution.
EXPECT_EQ(200, timing_stats.roundtrip_time_ms());
EXPECT_EQ(1000, timing_stats.client_time_ms());
}
TEST_F(ScriptExecutorTest, ClearPersistentUiOnError) {
ActionsResponseProto actions_response;
actions_response.add_actions()->mutable_tell()->set_message("1");
EXPECT_CALL(mock_service_, OnGetActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_OK, Serialize(actions_response)));
EXPECT_CALL(mock_service_, OnGetNextActions(_, _, _, _, _, _))
.WillOnce(RunOnceCallback<5>(net::HTTP_UNAUTHORIZED, ""));
EXPECT_CALL(executor_callback_,
Run(Field(&ScriptExecutor::Result::success, false)));
// empty, but not null
delegate_.SetPersistentGenericUi(
std::make_unique<GenericUserInterfaceProto>(), base::DoNothing());
ASSERT_NE(nullptr, delegate_.GetPersistentGenericUi());
executor_->Run(&user_data_, executor_callback_.Get());
ASSERT_EQ(nullptr, delegate_.GetPersistentGenericUi());
}
} // namespace
} // namespace autofill_assistant