blob: c32ea913e829a6efd0053e0ee58a483784c998d7 [file] [log] [blame]
// Copyright 2021 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/starter.h"
#include <memory>
#include <string>
#include "base/base64url.h"
#include "base/containers/flat_map.h"
#include "base/containers/lru_cache.h"
#include "base/strings/string_piece.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/mock_callback.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/tick_clock.h"
#include "components/autofill_assistant/browser/fake_starter_platform_delegate.h"
#include "components/autofill_assistant/browser/features.h"
#include "components/autofill_assistant/browser/mock_website_login_manager.h"
#include "components/autofill_assistant/browser/public/mock_runtime_manager.h"
#include "components/autofill_assistant/browser/script_parameters.h"
#include "components/autofill_assistant/browser/service/mock_service_request_sender.h"
#include "components/autofill_assistant/browser/switches.h"
#include "components/autofill_assistant/browser/test_util.h"
#include "components/autofill_assistant/browser/trigger_context.h"
#include "components/autofill_assistant/browser/trigger_scripts/mock_trigger_script_ui_delegate.h"
#include "components/autofill_assistant/browser/trigger_scripts/trigger_script_coordinator.h"
#include "components/autofill_assistant/browser/ukm_test_util.h"
#include "components/ukm/content/source_url_recorder.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "net/http/http_status_code.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/common/features.h"
namespace autofill_assistant {
using ::base::test::RunOnceCallback;
using ::testing::_;
using ::testing::AllOf;
using ::testing::Contains;
using ::testing::ElementsAreArray;
using ::testing::Eq;
using ::testing::IsEmpty;
using ::testing::Ne;
using ::testing::NiceMock;
using ::testing::Optional;
using ::testing::Pair;
using ::testing::Pointee;
using ::testing::Property;
using ::testing::UnorderedElementsAre;
using ::testing::WithArg;
using ::testing::WithArgs;
const char kExampleDeeplink[] = "https://www.example.com";
class StarterTest : public testing::Test {
public:
StarterTest() {
// Must be initialized before |starter_| is instantiated to take effect.
enable_fake_heuristic_ = std::make_unique<base::test::ScopedFeatureList>();
enable_fake_heuristic_->InitAndEnableFeatureWithParameters(
features::kAutofillAssistantUrlHeuristics, {{"json_parameters",
R"(
{
"heuristics":[
{
"intent":"FAKE_INTENT_CART",
"conditionSet":{
"urlContains":"cart"
}
}
]
}
)"}});
}
void SetUp() override {
web_contents_ = content::WebContentsTester::CreateTestWebContents(
&browser_context_, nullptr);
ukm::InitializeSourceUrlRecorderForWebContents(web_contents());
SimulateNavigateToUrl(GURL(kExampleDeeplink));
PrepareTriggerScriptUiDelegate();
PrepareTriggerScriptRequestSender();
fake_platform_delegate_.website_login_manager_ =
&mock_website_login_manager_;
ON_CALL(mock_website_login_manager_, GetLoginsForUrl)
.WillByDefault(
RunOnceCallback<1>(std::vector<WebsiteLoginManager::Login>()));
starter_ = std::make_unique<Starter>(
web_contents(), &fake_platform_delegate_, &ukm_recorder_,
mock_runtime_manager_.GetWeakPtr(),
task_environment()->GetMockTickClock());
}
void TearDown() override {
// We need to clear the static cache to avoid cross-talk between tests.
GetFailedTriggerFetchesCacheForTest()->Clear();
// Note: it is important to reset the starter explicitly here to ensure that
// destructors are called on the right thread, as required by devtools.
starter_.reset();
}
content::WebContents* web_contents() { return web_contents_.get(); }
content::BrowserTaskEnvironment* task_environment() {
return &task_environment_;
}
base::HashingLRUCache<std::string, base::TimeTicks>*
GetFailedTriggerFetchesCacheForTest() {
return starter_->cached_failed_trigger_script_fetches_;
}
base::HashingLRUCache<std::string, base::TimeTicks>*
GetUserDenylistedCacheForTest() const {
return &starter_->user_denylisted_domains_;
}
protected:
void SetupPlatformDelegateForFirstTimeUser() {
fake_platform_delegate_.feature_module_installed_ = false;
fake_platform_delegate_.is_first_time_user_ = true;
fake_platform_delegate_.onboarding_accepted_ = false;
fake_platform_delegate_.feature_module_installation_result_ = Metrics::
FeatureModuleInstallation::DFM_FOREGROUND_INSTALLATION_SUCCEEDED;
fake_platform_delegate_.show_onboarding_result_shown_ = true;
fake_platform_delegate_.show_onboarding_result_ =
OnboardingResult::ACCEPTED;
}
void SetupPlatformDelegateForReturningUser() {
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.is_first_time_user_ = false;
fake_platform_delegate_.onboarding_accepted_ = true;
}
// Returns a base64-encoded trigger script response, as created by
// |CreateTriggerScriptResponseForTest|.
std::string CreateBase64TriggerScriptResponseForTest(
TriggerScriptProto::TriggerUIType trigger_ui_type =
TriggerScriptProto::UNSPECIFIED_TRIGGER_UI_TYPE) {
std::string serialized_get_trigger_scripts_response =
CreateTriggerScriptResponseForTest(trigger_ui_type);
std::string base64_get_trigger_scripts_response;
base::Base64UrlEncode(serialized_get_trigger_scripts_response,
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&base64_get_trigger_scripts_response);
return base64_get_trigger_scripts_response;
}
// Returns a serialized GetTriggerScriptsResponseProto containing a single
// trigger script without any trigger conditions. As such, it will be shown
// immediately upon startup.
std::string CreateTriggerScriptResponseForTest(
TriggerScriptProto::TriggerUIType trigger_ui_type =
TriggerScriptProto::UNSPECIFIED_TRIGGER_UI_TYPE) {
GetTriggerScriptsResponseProto get_trigger_scripts_response;
get_trigger_scripts_response.add_trigger_scripts()->set_trigger_ui_type(
trigger_ui_type);
std::string serialized_get_trigger_scripts_response;
get_trigger_scripts_response.SerializeToString(
&serialized_get_trigger_scripts_response);
return serialized_get_trigger_scripts_response;
}
// Simulates a navigation from the last committed URL to urls[size-1] along
// the intermediate redirect-hops in |urls|.
void SimulateRedirectToUrl(const std::vector<GURL>& urls) {
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(
web_contents()->GetLastCommittedURL(),
web_contents()->GetMainFrame());
simulator->Start();
for (const auto& url : urls) {
simulator->Redirect(url);
}
simulator->Commit();
navigation_ids_.emplace_back(
ukm::GetSourceIdForWebContentsDocument(web_contents()));
}
void SimulateNavigateToUrl(const GURL& url) {
content::WebContentsTester::For(web_contents())->NavigateAndCommit(url);
navigation_ids_.emplace_back(
ukm::GetSourceIdForWebContentsDocument(web_contents()));
}
// Each request sender is only good for one trigger script. This call will
// create a new mock and prepare it to be used in the next call.
void PrepareTriggerScriptRequestSender() {
auto mock_trigger_script_service_request_sender =
std::make_unique<NiceMock<MockServiceRequestSender>>();
mock_trigger_script_service_request_sender_ =
mock_trigger_script_service_request_sender.get();
fake_platform_delegate_.trigger_script_request_sender_for_test_ =
std::move(mock_trigger_script_service_request_sender);
}
// Each trigger script UI delegate is only good for one trigger script and
// must be prepared anew if a test needs to show more than one trigger script.
void PrepareTriggerScriptUiDelegate() {
auto mock_trigger_script_ui_delegate =
std::make_unique<NiceMock<MockTriggerScriptUiDelegate>>();
mock_trigger_script_ui_delegate_ = mock_trigger_script_ui_delegate.get();
fake_platform_delegate_.trigger_script_ui_delegate_ =
std::move(mock_trigger_script_ui_delegate);
fake_platform_delegate_.start_regular_script_callback_ =
mock_start_regular_script_callback_.Get();
ON_CALL(*mock_trigger_script_ui_delegate_, Attach)
.WillByDefault(WithArg<0>(
[&](TriggerScriptCoordinator* trigger_script_coordinator) {
trigger_script_coordinator_ = trigger_script_coordinator;
}));
ON_CALL(*mock_trigger_script_ui_delegate_, Detach).WillByDefault([&]() {
trigger_script_coordinator_ = nullptr;
});
}
content::BrowserTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
content::RenderViewHostTestEnabler rvh_test_enabler_;
content::TestBrowserContext browser_context_;
std::unique_ptr<content::WebContents> web_contents_;
NiceMock<MockTriggerScriptUiDelegate>* mock_trigger_script_ui_delegate_ =
nullptr;
NiceMock<MockServiceRequestSender>*
mock_trigger_script_service_request_sender_ = nullptr;
NiceMock<MockWebsiteLoginManager> mock_website_login_manager_;
// Only set while a trigger script is running.
TriggerScriptCoordinator* trigger_script_coordinator_ = nullptr;
FakeStarterPlatformDelegate fake_platform_delegate_;
ukm::TestAutoSetUkmRecorder ukm_recorder_;
MockRuntimeManager mock_runtime_manager_;
std::unique_ptr<Starter> starter_;
base::HistogramTester histogram_tester_;
base::MockCallback<base::OnceCallback<void(
GURL url,
std::unique_ptr<TriggerContext> trigger_context,
const absl::optional<TriggerScriptProto>& trigger_script)>>
mock_start_regular_script_callback_;
std::unique_ptr<base::test::ScopedFeatureList> enable_fake_heuristic_;
std::vector<ukm::SourceId> navigation_ids_;
};
TEST_F(StarterTest, RegularScriptFailsWithoutInitialUrl) {
base::flat_map<std::string, std::string> params = {
{"ENABLED", "true"}, {"START_IMMEDIATELY", "true"}};
TriggerContext::Options options;
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(params), options));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, TriggerScriptFailsWithoutInitialUrl) {
base::flat_map<std::string, std::string> params = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", "abc"}};
TriggerContext::Options options;
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(params), options));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::NO_INITIAL_URL}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, FailWithoutMandatoryScriptParameter) {
// ENABLED is missing from |params|.
base::flat_map<std::string, std::string> params = {
{"START_IMMEDIATELY", "false"}, {"TRIGGER_SCRIPTS_BASE64", "abc"}};
TriggerContext::Options options;
options.initial_url = kExampleDeeplink;
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(params), options));
EXPECT_THAT(
GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::MANDATORY_PARAMETER_MISSING}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, FailWhenFeatureDisabled) {
base::flat_map<std::string, std::string> params = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", "abc"}};
TriggerContext::Options options;
options.initial_url = kExampleDeeplink;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndDisableFeature(
features::kAutofillAssistantProactiveHelp);
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(params), options));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::FEATURE_DISABLED}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RegularStartupForReturningUsersSucceeds) {
SetupPlatformDelegateForReturningUser();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
EXPECT_CALL(mock_start_regular_script_callback_,
Run(GURL(kExampleDeeplink), _, _))
.WillOnce(WithArg<1>([](std::unique_ptr<TriggerContext> trigger_context) {
EXPECT_THAT(trigger_context->GetOnboardingShown(), Eq(false));
}));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_THAT(fake_platform_delegate_.num_install_feature_module_called_,
Eq(0));
EXPECT_THAT(fake_platform_delegate_.num_show_onboarding_called_, Eq(0));
EXPECT_THAT(fake_platform_delegate_.GetOnboardingAccepted(), Eq(true));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_ACCEPTED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_NOT_SHOWN, 1u);
}
TEST_F(StarterTest, RegularStartupForFirstTimeUsersSucceeds) {
SetupPlatformDelegateForFirstTimeUser();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
EXPECT_CALL(mock_start_regular_script_callback_,
Run(GURL(kExampleDeeplink), _, _))
.WillOnce(WithArg<1>([](std::unique_ptr<TriggerContext> trigger_context) {
EXPECT_THAT(trigger_context->GetOnboardingShown(), Eq(true));
}));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_THAT(fake_platform_delegate_.num_install_feature_module_called_,
Eq(1));
EXPECT_THAT(fake_platform_delegate_.num_show_onboarding_called_, Eq(1));
EXPECT_THAT(fake_platform_delegate_.GetOnboardingAccepted(), Eq(true));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_FOREGROUND_INSTALLATION_SUCCEEDED,
1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_ACCEPTED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_SHOWN, 1u);
}
TEST_F(StarterTest, ForceOnboardingFlagForReturningUsersSucceeds) {
SetupPlatformDelegateForReturningUser();
base::MockCallback<base::OnceCallback<void(
base::OnceCallback<void(bool, OnboardingResult)>)>>
mock_onboarding_callback;
fake_platform_delegate_.on_show_onboarding_callback_ =
mock_onboarding_callback.Get();
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAutofillAssistantForceOnboarding, "true");
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", "https://www.example.com"}};
EXPECT_CALL(mock_onboarding_callback, Run);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options()));
}
TEST_F(StarterTest, ForceFirstTimeUserExperienceForReturningUser) {
GetTriggerScriptsResponseProto get_trigger_scripts_response;
auto* first_time_user_script =
get_trigger_scripts_response.add_trigger_scripts();
first_time_user_script->mutable_user_interface()->set_status_message(
"First time user");
first_time_user_script->mutable_trigger_condition()
->mutable_is_first_time_user();
auto* returning_user_script =
get_trigger_scripts_response.add_trigger_scripts();
returning_user_script->mutable_user_interface()->set_status_message(
"Returning user");
returning_user_script->mutable_trigger_condition()
->mutable_none_of()
->add_conditions()
->mutable_is_first_time_user();
std::string serialized_get_trigger_scripts_response;
get_trigger_scripts_response.SerializeToString(
&serialized_get_trigger_scripts_response);
std::string base64_get_trigger_scripts_response;
base::Base64UrlEncode(serialized_get_trigger_scripts_response,
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&base64_get_trigger_scripts_response);
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAutofillAssistantForceFirstTimeUser, "true");
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", base64_get_trigger_scripts_response},
{"ORIGINAL_DEEPLINK", "https://www.example.com"}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_,
ShowTriggerScript(first_time_user_script->user_interface()));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options()));
}
TEST_F(StarterTest, RegularStartupFailsIfDfmInstallationFails) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installation_result_ =
Metrics::FeatureModuleInstallation::DFM_FOREGROUND_INSTALLATION_FAILED;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_THAT(fake_platform_delegate_.num_install_feature_module_called_,
Eq(1));
EXPECT_THAT(fake_platform_delegate_.num_show_onboarding_called_, Eq(0));
EXPECT_THAT(fake_platform_delegate_.GetOnboardingAccepted(), Eq(false));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_FOREGROUND_INSTALLATION_FAILED,
1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RegularStartupFailsIfOnboardingRejected) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.show_onboarding_result_ = OnboardingResult::REJECTED;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink},
{"INTENT", "SHOPPING_ASSISTED_CHECKOUT"}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_THAT(fake_platform_delegate_.GetOnboardingAccepted(), Eq(false));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_CANCELLED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_SHOWN, 1u);
}
TEST_F(StarterTest, RpcTriggerScriptFailsIfMsbbIsDisabled) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.msbb_enabled_ = false;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options()));
EXPECT_THAT(
GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::PROACTIVE_TRIGGERING_DISABLED}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RpcTriggerScriptFailsIfProactiveHelpIsDisabled) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.proactive_help_enabled_ = false;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options()));
EXPECT_THAT(
GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::PROACTIVE_TRIGGERING_DISABLED}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RpcTriggerScriptSucceeds) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
options.onboarding_shown = false;
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::ACCEPT);
});
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(
WithArgs<1, 2>([&](const std::string& request_body,
ServiceRequestSender::ResponseCallback& callback) {
// Note that trigger scripts should be fetched for the
// ORIGINAL_DEEPLINK, not for the |initial_url|.
GetTriggerScriptsRequestProto request;
ASSERT_TRUE(request.ParseFromString(request_body));
EXPECT_THAT(request.url(), Eq(GURL(kExampleDeeplink)));
EXPECT_FALSE(request.client_context().is_in_chrome_triggered());
std::move(callback).Run(
net::HTTP_OK,
CreateTriggerScriptResponseForTest(
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER));
}));
GetTriggerScriptsResponseProto get_trigger_scripts_response;
get_trigger_scripts_response.ParseFromString(
CreateTriggerScriptResponseForTest(
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER));
EXPECT_CALL(
mock_start_regular_script_callback_,
Run(GURL(kExampleDeeplink),
Pointee(AllOf(
Property(&TriggerContext::GetOnboardingShown, true),
Property(&TriggerContext::GetInChromeTriggered, false),
Property(&TriggerContext::GetTriggerUIType,
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER))),
Optional(get_trigger_scripts_response.trigger_scripts(0))));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_THAT(fake_platform_delegate_.num_show_onboarding_called_, Eq(1));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::FIRST_TIME_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptFinishedState::PROMPT_SUCCEEDED,
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER}}})));
EXPECT_THAT(
GetUkmTriggerScriptOnboarding(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptOnboarding::ONBOARDING_SEEN_AND_ACCEPTED,
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER}}})));
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, Base64TriggerScriptFailsForInvalidBase64) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", "#invalid_hashtag"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options()));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::RETURNING_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptFinishedState::BASE64_DECODING_ERROR,
TriggerScriptProto::UNSPECIFIED_TRIGGER_UI_TYPE}}})));
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, Base64TriggerScriptFailsIfProactiveHelpIsDisabled) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.proactive_help_enabled_ = false;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_, Attach).Times(0);
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options()));
EXPECT_THAT(
GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::PROACTIVE_TRIGGERING_DISABLED}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, Base64TriggerScriptSucceeds) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
// Base64 trigger scripts should not require MSBB to be enabled.
fake_platform_delegate_.msbb_enabled_ = false;
// No need to inject a mock request sender for base64 trigger scripts, we can
// use the real one.
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64",
CreateBase64TriggerScriptResponseForTest(
TriggerScriptProto::SHOPPING_CART_RETURNING_USER)},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
options.onboarding_shown = false;
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::ACCEPT);
});
EXPECT_CALL(mock_start_regular_script_callback_,
Run(GURL(kExampleDeeplink),
Pointee(Property(&TriggerContext::GetOnboardingShown, true)),
testing::Ne(absl::nullopt)));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_THAT(fake_platform_delegate_.num_show_onboarding_called_, Eq(1));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::FIRST_TIME_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptFinishedState::PROMPT_SUCCEEDED,
TriggerScriptProto::SHOPPING_CART_RETURNING_USER}}})));
EXPECT_THAT(
GetUkmTriggerScriptOnboarding(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptOnboarding::ONBOARDING_SEEN_AND_ACCEPTED,
TriggerScriptProto::SHOPPING_CART_RETURNING_USER}}})));
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, CancelPendingTriggerScriptWhenTransitioningFromCctToTab) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_custom_tab_ = true;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, HideTriggerScript);
fake_platform_delegate_.is_custom_tab_ = false;
starter_->CheckSettings();
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::RETURNING_USER}}})));
EXPECT_THAT(
GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptFinishedState::CCT_TO_TAB_NOT_SUPPORTED,
TriggerScriptProto::UNSPECIFIED_TRIGGER_UI_TYPE}}})));
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
}
TEST_F(StarterTest, CancelPendingTriggerScriptWhenHandlingNewStartupRequest) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, HideTriggerScript);
PrepareTriggerScriptUiDelegate();
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptFinishedState::CANCELED,
TriggerScriptProto::UNSPECIFIED_TRIGGER_UI_TYPE}}})));
}
TEST_F(StarterTest, RegularStartupFailsIfNavigationDuringOnboarding) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
// Empty callback to keep the onboarding open indefinitely.
fake_platform_delegate_.on_show_onboarding_callback_ = base::DoNothing();
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
SimulateNavigateToUrl(GURL("https://www.different.com"));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_NO_ANSWER, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_SHOWN, 1u);
}
TEST_F(StarterTest, TriggerScriptStartupFailsIfNavigationDuringOnboarding) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
// Empty callback to keep the onboarding open indefinitely.
fake_platform_delegate_.on_show_onboarding_callback_ = base::DoNothing();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64",
CreateBase64TriggerScriptResponseForTest(
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER)},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::ACCEPT);
});
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
SimulateNavigateToUrl(GURL("https://www.different.com"));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptStarted::FIRST_TIME_USER}}})));
EXPECT_THAT(
GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptFinishedState::PROMPT_FAILED_NAVIGATE,
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::TriggerScriptOnboarding::
ONBOARDING_SEEN_AND_INTERRUPTED_BY_NAVIGATION,
TriggerScriptProto::SHOPPING_CART_FIRST_TIME_USER}}})));
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RegularStartupAllowsCertainNavigationsDuringOnboarding) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
// Empty callback to keep the onboarding open indefinitely.
fake_platform_delegate_.on_show_onboarding_callback_ = base::DoNothing();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
// Expect that the onboarding is not interrupted by a navigation to a
// subdomain of the ORIGINAL_DEEPLINK, nor by redirects along the way.
SimulateRedirectToUrl(
{GURL("http://redirect.com/example"), GURL("https://login.example.com")});
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
// Navigating to a different domain will cancel the onboarding.
SimulateNavigateToUrl(GURL("https://www.different.com"));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_NO_ANSWER, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_SHOWN, 1u);
}
TEST_F(StarterTest, TriggerScriptAllowsHttpToHttpsRedirect) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
SimulateNavigateToUrl(GURL("http://www.example.com"));
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", "http://www.example.com"}};
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(1);
EXPECT_CALL(*mock_trigger_script_ui_delegate_, HideTriggerScript).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
// Redirects from http to https are allowed.
SimulateRedirectToUrl({GURL("https://www.example.com")});
}
TEST_F(StarterTest, RegularStartupIgnoresLastCommittedUrl) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
// Empty callback to keep the onboarding open indefinitely.
fake_platform_delegate_.on_show_onboarding_callback_ = base::DoNothing();
// Note: the starter does not actually care about the last committed URL at
// the time of startup. All that matters is that it has received the startup
// intent, and that there is a valid ORIGINAL_DEEPLINK to expect.
SimulateNavigateToUrl(GURL("https://www.ignored.com"));
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, ImplicitStartupOnSupportedDomain) {
SetupPlatformDelegateForReturningUser();
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(
WithArgs<1, 2>([&](const std::string& request_body,
ServiceRequestSender::ResponseCallback& callback) {
GetTriggerScriptsRequestProto request;
ASSERT_TRUE(request.ParseFromString(request_body));
EXPECT_THAT(request.url(),
Eq(GURL("https://www.some-website.com/cart")));
EXPECT_TRUE(request.client_context().is_in_chrome_triggered());
std::move(callback).Run(
net::HTTP_OK,
CreateTriggerScriptResponseForTest(
TriggerScriptProto::SHOPPING_CART_RETURNING_USER));
}));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::ACCEPT);
});
EXPECT_CALL(
mock_start_regular_script_callback_,
Run(GURL("https://www.some-website.com/cart"),
Pointee(AllOf(
Property(&TriggerContext::GetOnboardingShown, false),
Property(&TriggerContext::GetInChromeTriggered, true),
Property(&TriggerContext::GetTriggerUIType,
TriggerScriptProto::SHOPPING_CART_RETURNING_USER))),
testing::Ne(absl::nullopt)));
// Implicit startup by navigating to an autofill-assistant-enabled site.
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[1],
{Metrics::TriggerScriptStarted::RETURNING_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[1],
{Metrics::TriggerScriptFinishedState::PROMPT_SUCCEEDED,
TriggerScriptProto::SHOPPING_CART_RETURNING_USER}}})));
EXPECT_THAT(
GetUkmTriggerScriptOnboarding(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[1],
{Metrics::TriggerScriptOnboarding::ONBOARDING_ALREADY_ACCEPTED,
TriggerScriptProto::SHOPPING_CART_RETURNING_USER}}})));
EXPECT_THAT(
GetUkmInChromeTriggering(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::InChromeTriggerAction::NO_HEURISTIC_MATCH}},
{navigation_ids_[1],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}}})));
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, DoNotStartImplicitlyIfSettingDisabled) {
SetupPlatformDelegateForReturningUser();
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
fake_platform_delegate_.proactive_help_enabled_ = false;
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(GetUkmInChromeTriggering(ukm_recorder_), IsEmpty());
}
TEST_F(StarterTest, DoNotStartImplicitlyForNonAgaCct) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_tab_created_by_gsa_ = false;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(GetUkmInChromeTriggering(ukm_recorder_), IsEmpty());
}
TEST_F(StarterTest, ImplicitStartupOnCurrentUrlAfterSettingEnabled) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.proactive_help_enabled_ = false;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK,
CreateTriggerScriptResponseForTest()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(1);
// Implicit startup by enabling proactive help while already on an
// autofill-assistant-enabled site.
fake_platform_delegate_.proactive_help_enabled_ = true;
starter_->CheckSettings();
task_environment()->RunUntilIdle();
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[1],
{Metrics::TriggerScriptStarted::RETURNING_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
EXPECT_THAT(
GetUkmInChromeTriggering(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[1],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}}})));
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, StartTriggerScriptBeforeRedirectRecordsUkmForTargetUrl) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
// Simulate a real flow that starts on some trigger site, which then redirects
// to the deeplink.
SimulateNavigateToUrl(GURL("https://some-trigger-site.com"));
// Start the flow before the trigger site has had a chance to navigate to the
// target domain. This commonly happens due to android intent handling
// happening before navigations are started.
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
SimulateRedirectToUrl({GURL("https://redirect.com/to/www/example/com"),
GURL(kExampleDeeplink),
GURL("https://signin.example.com")});
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[2],
{Metrics::TriggerScriptStarted::RETURNING_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RedirectFailsDuringPendingTriggerScriptStart) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
// Simulate a real flow that starts on some trigger site, which then redirects
// to the deeplink.
SimulateNavigateToUrl(GURL("https://some-trigger-site.com"));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(0);
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(
web_contents()->GetLastCommittedURL(),
web_contents()->GetMainFrame());
simulator->Start();
simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
simulator->Fail(net::ERR_BLOCKED_BY_CLIENT);
simulator->CommitErrorPage();
navigation_ids_.emplace_back(
ukm::GetSourceIdForWebContentsDocument(web_contents()));
// Note that this impression is recorded for the last URL that a navigation-
// start event occurred for. We never reached the target domain, so this is
// unfortunately the best we can do.
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[2],
{Metrics::TriggerScriptStarted::NAVIGATION_ERROR}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, StartTriggerScriptDuringRedirectRecordsUkmForTargetUrl) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.feature_module_installed_ = true;
fake_platform_delegate_.trigger_script_request_sender_for_test_ = nullptr;
mock_trigger_script_service_request_sender_ = nullptr;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"TRIGGER_SCRIPTS_BASE64", CreateBase64TriggerScriptResponseForTest()},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
// Simulate a real flow that starts on some trigger site, which then redirects
// to the deeplink.
SimulateNavigateToUrl(GURL("https://some-trigger-site.com"));
// Begin a navigation, then start the flow before the navigation is committed.
// UKM should still be recorded for the final URL, not the redirect URL.
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript);
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(
web_contents()->GetLastCommittedURL(),
web_contents()->GetMainFrame());
simulator->Start();
simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
simulator->Redirect(GURL(kExampleDeeplink));
simulator->Commit();
navigation_ids_.emplace_back(
ukm::GetSourceIdForWebContentsDocument(web_contents()));
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[2],
{Metrics::TriggerScriptStarted::RETURNING_USER}}})));
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, RegularStartupDoesNotWaitForNavigationToFinish) {
SetupPlatformDelegateForReturningUser();
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
SimulateNavigateToUrl(GURL("https://some-trigger-site.com"));
std::unique_ptr<content::NavigationSimulator> simulator =
content::NavigationSimulator::CreateRendererInitiated(
web_contents()->GetLastCommittedURL(),
web_contents()->GetMainFrame());
simulator->Start();
simulator->Redirect(GURL("https://redirect.com/to/www/example/com"));
{
EXPECT_CALL(mock_start_regular_script_callback_,
Run(GURL(kExampleDeeplink), _, _));
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
}
simulator->Redirect(GURL(kExampleDeeplink));
simulator->Commit();
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_ACCEPTED, 1u);
histogram_tester_.ExpectBucketCount("Android.AutofillAssistant.OnBoarding",
Metrics::OnBoarding::OB_NOT_SHOWN, 1u);
}
TEST_F(StarterTest, DoNotStartImplicitlyIfAlreadyRunning) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_regular_script_running_ = true;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
TriggerContext::Options options;
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmInChromeTriggering(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectTotalCount(
"Android.AutofillAssistant.FeatureModuleInstallation", 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
TEST_F(StarterTest, FailedTriggerScriptFetchesForImplicitStartupAreCached) {
SetupPlatformDelegateForReturningUser();
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_FORBIDDEN, std::string()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript).Times(0);
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
// Implicit startup by navigating to an autofill-assistant-enabled site.
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(
Pair("some-website.com",
task_environment()->GetMockTickClock()->NowTicks())));
// Since we failed to communicate with the backend, we won't try again for the
// same domain or sub-domain.
PrepareTriggerScriptRequestSender();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.some-website.com/checkout"));
SimulateNavigateToUrl(GURL("https://some-website.com/signin"));
SimulateNavigateToUrl(GURL("https://signin.some-website.com"));
task_environment()->RunUntilIdle();
// Navigations to different autofill-assistant-enabled URLs will still trigger
// implicit startup.
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(1);
SimulateNavigateToUrl(GURL("https://www.different-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(
GetUkmInChromeTriggering(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::InChromeTriggerAction::NO_HEURISTIC_MATCH}},
{navigation_ids_[1],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}},
{navigation_ids_[2],
{Metrics::InChromeTriggerAction::CACHE_HIT_UNSUPPORTED_DOMAIN}},
{navigation_ids_[3],
{Metrics::InChromeTriggerAction::CACHE_HIT_UNSUPPORTED_DOMAIN}},
{navigation_ids_[4],
{Metrics::InChromeTriggerAction::CACHE_HIT_UNSUPPORTED_DOMAIN}},
{navigation_ids_[5],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}}})));
}
TEST_F(StarterTest,
CancelingTriggerScriptsDenylistsTheDomainForImplicitStartup) {
SetupPlatformDelegateForReturningUser();
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(RunOnceCallback<2>(net::HTTP_OK,
CreateTriggerScriptResponseForTest()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::CANCEL_SESSION);
});
// Implicit startup by navigating to an autofill-assistant-enabled site.
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(*GetUserDenylistedCacheForTest(),
UnorderedElementsAre(
Pair("some-website.com",
task_environment()->GetMockTickClock()->NowTicks())));
// Since the user chose CANCEL_SESSION, subsequent navigations to the same
// domain or sub-domains should not trigger implicit startup.
PrepareTriggerScriptRequestSender();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.some-website.com/checkout"));
SimulateNavigateToUrl(GURL("https://some-website.com/signin"));
SimulateNavigateToUrl(GURL("https://signin.some-website.com"));
task_environment()->RunUntilIdle();
// Navigations to different autofill-assistant-enabled URLs will still trigger
// implicit startup.
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(1);
SimulateNavigateToUrl(GURL("https://www.different-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(
GetUkmInChromeTriggering(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::InChromeTriggerAction::NO_HEURISTIC_MATCH}},
{navigation_ids_[1],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}},
{navigation_ids_[2],
{Metrics::InChromeTriggerAction::USER_DENYLISTED_DOMAIN}},
{navigation_ids_[3],
{Metrics::InChromeTriggerAction::USER_DENYLISTED_DOMAIN}},
{navigation_ids_[4],
{Metrics::InChromeTriggerAction::USER_DENYLISTED_DOMAIN}},
{navigation_ids_[5],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}}})));
}
TEST_F(StarterTest, EmptyTriggerScriptFetchesForImplicitStartupAreCached) {
SetupPlatformDelegateForReturningUser();
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(
WithArg<2>([&](ServiceRequestSender::ResponseCallback& callback) {
// Empty response == no trigger scripts available.
std::move(callback).Run(net::HTTP_OK, std::string());
}));
// Implicit startup by navigating to an autofill-assistant-enabled site.
SimulateNavigateToUrl(GURL("https://www.some-website.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(
Pair("some-website.com",
task_environment()->GetMockTickClock()->NowTicks())));
// Subsequent navigations to the same domain should not talk to the backend
// again. This includes navigations to subdomains etc.
PrepareTriggerScriptRequestSender();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.some-website.com/checkout"));
SimulateNavigateToUrl(GURL("https://some-website.com/signin"));
SimulateNavigateToUrl(GURL("https://signin.some-website.com"));
task_environment()->RunUntilIdle();
// However, explicit requests still communicate with the backend.
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(1);
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"},
{"ORIGINAL_DEEPLINK", "https://www.some-website.com/cart"}};
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
EXPECT_THAT(
GetUkmInChromeTriggering(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::InChromeTriggerAction::NO_HEURISTIC_MATCH}},
{navigation_ids_[1],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}},
{navigation_ids_[2],
{Metrics::InChromeTriggerAction::CACHE_HIT_UNSUPPORTED_DOMAIN}},
{navigation_ids_[3],
{Metrics::InChromeTriggerAction::CACHE_HIT_UNSUPPORTED_DOMAIN}},
{navigation_ids_[4],
{Metrics::InChromeTriggerAction::CACHE_HIT_UNSUPPORTED_DOMAIN}}})));
}
TEST_F(StarterTest, FailedExplicitTriggerFetchesAreCached) {
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"}};
std::vector<std::string> unsupported_sites = {
"https://www.example.com", "https://signing.example.com",
"https://different.com", "https://different.com/test?q=12345"};
for (const auto& url : unsupported_sites) {
SimulateNavigateToUrl(GURL(url));
PrepareTriggerScriptRequestSender();
PrepareTriggerScriptUiDelegate();
// Send empty response == no trigger script available. Note that explicit
// start requests like these will always attempt to talk to the backend, no
// matter the cache contents.
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string()));
script_parameters["ORIGINAL_DEEPLINK"] = url;
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
}
// Cache should contain one entry per organization-identifying domain.
base::TimeTicks now_ticks =
task_environment()->GetMockTickClock()->NowTicks();
EXPECT_THAT(*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(Pair("example.com", now_ticks),
Pair("different.com", now_ticks)));
EXPECT_THAT(GetUkmInChromeTriggering(ukm_recorder_), IsEmpty());
}
TEST_F(StarterTest, FailedImplicitTriggerFetchesAreCached) {
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
std::vector<std::string> implicit_unsupported_sites = {
"https://www.example-shopping-site.com/cart",
"https://different-shopping-site.com/cart"};
for (const auto& url : implicit_unsupported_sites) {
PrepareTriggerScriptRequestSender();
PrepareTriggerScriptUiDelegate();
// Send empty response == no trigger script available.
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string()));
SimulateNavigateToUrl(GURL(url));
task_environment()->RunUntilIdle();
}
// Failed attempts to start implicitly are added to the cache.
base::TimeTicks now_ticks =
task_environment()->GetMockTickClock()->NowTicks();
EXPECT_THAT(
*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(Pair("example-shopping-site.com", now_ticks),
Pair("different-shopping-site.com", now_ticks)));
EXPECT_THAT(
GetUkmInChromeTriggering(ukm_recorder_),
ElementsAreArray(ToHumanReadableMetrics(
{{navigation_ids_[0],
{Metrics::InChromeTriggerAction::NO_HEURISTIC_MATCH}},
{navigation_ids_[1],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}},
{navigation_ids_[2],
{Metrics::InChromeTriggerAction::TRIGGER_SCRIPT_REQUESTED}}})));
}
TEST_F(StarterTest, FailedTriggerFetchesCacheEntriesExpire) {
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
GetFailedTriggerFetchesCacheForTest()->Put(
"example.com", task_environment()->GetMockTickClock()->NowTicks());
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string()));
task_environment()->FastForwardBy(base::Hours(1));
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
// Since the request failed again, the cache entry should be updated with the
// new time.
EXPECT_THAT(
*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(Pair(
"example.com", task_environment()->GetMockTickClock()->NowTicks())));
}
TEST_F(StarterTest, UserDenylistedCacheUpdateAndExpire) {
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK,
CreateTriggerScriptResponseForTest()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::CANCEL_SESSION);
});
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_THAT(
*GetUserDenylistedCacheForTest(),
UnorderedElementsAre(Pair(
"example.com", task_environment()->GetMockTickClock()->NowTicks())));
PrepareTriggerScriptRequestSender();
PrepareTriggerScriptUiDelegate();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK,
CreateTriggerScriptResponseForTest()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::CANCEL_SESSION);
});
task_environment()->FastForwardBy(base::Hours(1));
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
// Since the request was cancelled again, the cache entry should have been
// updated with the new time.
EXPECT_THAT(
*GetUserDenylistedCacheForTest(),
UnorderedElementsAre(Pair(
"example.com", task_environment()->GetMockTickClock()->NowTicks())));
}
TEST_F(StarterTest, RemoveEntryFromCacheOnSuccessForExplicitRequest) {
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
GetFailedTriggerFetchesCacheForTest()->Put(
"example.com", task_environment()->GetMockTickClock()->NowTicks());
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK,
CreateTriggerScriptResponseForTest()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::ACCEPT);
});
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"},
{"ORIGINAL_DEEPLINK", "https://www.example.com"}};
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters),
TriggerContext::Options{}));
EXPECT_THAT(*GetFailedTriggerFetchesCacheForTest(), IsEmpty());
}
TEST_F(StarterTest, ImplicitInCctTriggeringSmokeTest) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_custom_tab_ = true;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
}
TEST_F(StarterTest, ImplicitInTabTriggeringSmokeTest) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_custom_tab_ = false;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInTabTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
}
TEST_F(StarterTest, ImplicitInCctTriggeringDoesNotTriggerInTab) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_custom_tab_ = false;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
}
TEST_F(StarterTest, ImplicitInTabTriggeringDoesNotTriggerInCct) {
SetupPlatformDelegateForReturningUser();
fake_platform_delegate_.is_custom_tab_ = true;
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInTabTriggering);
starter_->CheckSettings();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.Times(0);
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
}
TEST_F(StarterTest, RecordsDependenciesInvalidatedOutsideFlow) {
starter_->OnDependenciesInvalidated();
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.DependenciesInvalidated",
Metrics::DependenciesInvalidated::OUTSIDE_FLOW, 1);
}
TEST_F(StarterTest, RecordsDependenciesInvalidatedDuringStartup) {
SetupPlatformDelegateForFirstTimeUser();
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"}, {"START_IMMEDIATELY", "true"}};
TriggerContext::Options options;
options.initial_url = "https://redirect.com/to/www/example/com";
// Empty callback to keep the onboarding open indefinitely.
fake_platform_delegate_.on_show_onboarding_callback_ = base::DoNothing();
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
starter_->OnDependenciesInvalidated();
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.DependenciesInvalidated",
Metrics::DependenciesInvalidated::DURING_STARTUP, 1);
}
TEST_F(StarterTest, RecordsDependenciesInvalidatedDuringFlow) {
fake_platform_delegate_.is_regular_script_running_ = true;
starter_->OnDependenciesInvalidated();
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.DependenciesInvalidated",
Metrics::DependenciesInvalidated::DURING_FLOW, 1);
}
TEST(MultipleStarterTest, HeuristicUsedByMultipleInstances) {
content::BrowserTaskEnvironment task_environment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
content::RenderViewHostTestEnabler rvh_test_enabler;
content::TestBrowserContext browser_context;
ukm::TestAutoSetUkmRecorder ukm_recorder;
FakeStarterPlatformDelegate fake_platform_delegate_01;
FakeStarterPlatformDelegate fake_platform_delegate_02;
MockRuntimeManager mock_runtime_manager;
auto web_contents_01 = content::WebContentsTester::CreateTestWebContents(
&browser_context, nullptr);
ukm::InitializeSourceUrlRecorderForWebContents(web_contents_01.get());
auto web_contents_02 = content::WebContentsTester::CreateTestWebContents(
&browser_context, nullptr);
ukm::InitializeSourceUrlRecorderForWebContents(web_contents_02.get());
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
auto enable_fake_heuristic =
std::make_unique<base::test::ScopedFeatureList>();
enable_fake_heuristic->InitAndEnableFeatureWithParameters(
features::kAutofillAssistantUrlHeuristics, {{"json_parameters",
R"(
{
"heuristics":[
{
"intent":"FAKE_INTENT_CART",
"conditionSet":{
"urlContains":"cart"
}
}
]
}
)"}});
Starter starter_01(web_contents_01.get(), &fake_platform_delegate_01,
&ukm_recorder, mock_runtime_manager.GetWeakPtr(),
task_environment.GetMockTickClock());
Starter starter_02(web_contents_02.get(), &fake_platform_delegate_02,
&ukm_recorder, mock_runtime_manager.GetWeakPtr(),
task_environment.GetMockTickClock());
auto service_request_sender_01 =
std::make_unique<NiceMock<MockServiceRequestSender>>();
auto* service_request_sender_01_ptr = service_request_sender_01.get();
fake_platform_delegate_01.trigger_script_request_sender_for_test_ =
std::move(service_request_sender_01);
auto service_request_sender_02 =
std::make_unique<NiceMock<MockServiceRequestSender>>();
auto* service_request_sender_02_ptr = service_request_sender_02.get();
fake_platform_delegate_02.trigger_script_request_sender_for_test_ =
std::move(service_request_sender_02);
EXPECT_CALL(*service_request_sender_01_ptr, OnSendRequest).Times(1);
EXPECT_CALL(*service_request_sender_02_ptr, OnSendRequest).Times(1);
content::WebContentsTester::For(web_contents_01.get())
->NavigateAndCommit(GURL("https://www.some-website.com/cart"));
content::WebContentsTester::For(web_contents_02.get())
->NavigateAndCommit(GURL("https://www.some-other-website.com/cart"));
task_environment.RunUntilIdle();
}
TEST_F(StarterTest, StaleCacheEntriesAreRemovedOnInsertingNewEntries) {
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
starter_->CheckSettings();
base::TimeTicks t0 = task_environment()->GetMockTickClock()->NowTicks();
GetFailedTriggerFetchesCacheForTest()->Put("failed-t0.com", t0);
GetUserDenylistedCacheForTest()->Put("denylisted-t0.com", t0);
task_environment()->FastForwardBy(base::Minutes(30));
base::TimeTicks t1 = task_environment()->GetMockTickClock()->NowTicks();
EXPECT_THAT(*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(Pair("failed-t0.com", t0)));
EXPECT_THAT(*GetUserDenylistedCacheForTest(),
UnorderedElementsAre(Pair("denylisted-t0.com", t0)));
GetFailedTriggerFetchesCacheForTest()->Put("failed-t1.com", t1);
GetUserDenylistedCacheForTest()->Put("denylisted-t1.com", t1);
task_environment()->FastForwardBy(base::Minutes(30));
base::TimeTicks t2 = task_environment()->GetMockTickClock()->NowTicks();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK, std::string()));
SimulateNavigateToUrl(GURL("https://www.example.com/cart"));
task_environment()->RunUntilIdle();
// failed-t0.com should have been removed from the cache due to going stale.
EXPECT_THAT(
*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(Pair("failed-t1.com", t1), Pair("example.com", t2)));
// denylisted-t0.com is stale and will be removed the next time a domain is
// denylisted.
EXPECT_THAT(*GetUserDenylistedCacheForTest(),
UnorderedElementsAre(Pair("denylisted-t0.com", t0),
Pair("denylisted-t1.com", t1)));
PrepareTriggerScriptRequestSender();
PrepareTriggerScriptUiDelegate();
EXPECT_CALL(*mock_trigger_script_service_request_sender_, OnSendRequest)
.WillOnce(RunOnceCallback<2>(net::HTTP_OK,
CreateTriggerScriptResponseForTest()));
EXPECT_CALL(*mock_trigger_script_ui_delegate_, ShowTriggerScript)
.WillOnce([&]() {
ASSERT_TRUE(trigger_script_coordinator_ != nullptr);
trigger_script_coordinator_->PerformTriggerScriptAction(
TriggerScriptProto::CANCEL_SESSION);
});
SimulateNavigateToUrl(GURL("https://supported.com/cart"));
task_environment()->RunUntilIdle();
// No change to the failed fetches cache.
EXPECT_THAT(
*GetFailedTriggerFetchesCacheForTest(),
UnorderedElementsAre(Pair("failed-t1.com", t1), Pair("example.com", t2)));
// denylisted-t0.com should have been removed due to going stale.
EXPECT_THAT(*GetUserDenylistedCacheForTest(),
UnorderedElementsAre(Pair("denylisted-t1.com", t1),
Pair("supported.com", t2)));
}
TEST_F(StarterTest, CommandLineScriptParametersAreAddedToImplicitTriggers) {
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
ImplicitTriggeringDebugParametersProto proto;
auto* param = proto.add_additional_script_parameters();
param->set_name("DEBUG_SOCKET_ID");
param->set_value("FAKE_SOCKET_ID");
param = proto.add_additional_script_parameters();
param->set_name("DEBUG_BUNDLE_ID");
param->set_value("FAKE_BUNDLE_ID");
param = proto.add_additional_script_parameters();
param->set_name("INTENT");
param->set_value("NEW_INTENT");
param = proto.add_additional_script_parameters();
param->set_name("NOT_ALLOWLISTED");
param->set_value("SHOULD_NOT_BE_SENT_TO_BACKEND");
std::string implicit_triggering_debug_parameters;
proto.SerializeToString(&implicit_triggering_debug_parameters);
base::Base64UrlEncode(implicit_triggering_debug_parameters,
base::Base64UrlEncodePolicy::INCLUDE_PADDING,
&implicit_triggering_debug_parameters);
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kAutofillAssistantImplicitTriggeringDebugParameters,
implicit_triggering_debug_parameters);
// Create new instance of the starter to force the changed command line to
// take effect.
starter_ = std::make_unique<Starter>(web_contents(), &fake_platform_delegate_,
&ukm_recorder_,
mock_runtime_manager_.GetWeakPtr(),
task_environment()->GetMockTickClock());
EXPECT_CALL(*mock_trigger_script_service_request_sender_,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(WithArg<1>([&](const std::string& request_body) {
GetTriggerScriptsRequestProto request;
ASSERT_TRUE(request.ParseFromString(request_body));
EXPECT_THAT(request.url(), Eq(GURL("https://example.com/cart")));
EXPECT_THAT(
request.script_parameters(),
UnorderedElementsAre(
AllOf(Property(&ScriptParameterProto::name, "DEBUG_SOCKET_ID"),
Property(&ScriptParameterProto::value, "FAKE_SOCKET_ID")),
AllOf(Property(&ScriptParameterProto::name, "DEBUG_BUNDLE_ID"),
Property(&ScriptParameterProto::value, "FAKE_BUNDLE_ID")),
AllOf(Property(&ScriptParameterProto::name, "INTENT"),
Property(&ScriptParameterProto::value, "NEW_INTENT"))));
}));
SimulateNavigateToUrl(GURL("https://example.com/cart"));
task_environment()->RunUntilIdle();
}
TEST(MultipleIntentStarterTest, ImplicitTriggeringSendsAllMatchingIntents) {
content::BrowserTaskEnvironment task_environment(
base::test::TaskEnvironment::TimeSource::MOCK_TIME);
content::RenderViewHostTestEnabler rvh_test_enabler;
content::TestBrowserContext browser_context;
ukm::TestAutoSetUkmRecorder ukm_recorder;
FakeStarterPlatformDelegate fake_platform_delegate;
MockRuntimeManager mock_runtime_manager;
auto web_contents = content::WebContentsTester::CreateTestWebContents(
&browser_context, nullptr);
ukm::InitializeSourceUrlRecorderForWebContents(web_contents.get());
auto scoped_feature_list = std::make_unique<base::test::ScopedFeatureList>();
scoped_feature_list->InitAndEnableFeature(
features::kAutofillAssistantInCCTTriggering);
auto enable_fake_heuristic =
std::make_unique<base::test::ScopedFeatureList>();
enable_fake_heuristic->InitAndEnableFeatureWithParameters(
features::kAutofillAssistantUrlHeuristics, {{"json_parameters",
R"(
{
"heuristics":[
{
"intent":"FAKE_INTENT_A",
"conditionSet":{
"urlContains":"intent_a"
}
},
{
"intent":"FAKE_INTENT_B",
"conditionSet":{
"urlContains":"intent_b"
}
}
]
}
)"}});
Starter starter(web_contents.get(), &fake_platform_delegate, &ukm_recorder,
mock_runtime_manager.GetWeakPtr(),
task_environment.GetMockTickClock());
auto service_request_sender =
std::make_unique<NiceMock<MockServiceRequestSender>>();
auto* service_request_sender_ptr = service_request_sender.get();
fake_platform_delegate.trigger_script_request_sender_for_test_ =
std::move(service_request_sender);
EXPECT_CALL(*service_request_sender_ptr,
OnSendRequest(
GURL("https://automate-pa.googleapis.com/v1/triggers"), _, _))
.WillOnce(WithArg<1>([&](const std::string& request_body) {
GetTriggerScriptsRequestProto request;
ASSERT_TRUE(request.ParseFromString(request_body));
EXPECT_THAT(request.url(),
Eq(GURL("https://example.com/intent_a/intent_b")));
EXPECT_TRUE(request.client_context().is_in_chrome_triggered());
const auto& actual_intent_param = std::find_if(
request.script_parameters().begin(),
request.script_parameters().end(),
[&](const auto& param) { return param.name() == "INTENT"; });
ASSERT_THAT(actual_intent_param, Ne(request.script_parameters().end()));
auto actual_intents =
base::SplitString(actual_intent_param->value(), ",",
base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
EXPECT_THAT(actual_intents,
UnorderedElementsAre("FAKE_INTENT_A", "FAKE_INTENT_B"));
}));
content::WebContentsTester::For(web_contents.get())
->NavigateAndCommit(GURL("https://example.com/intent_a/intent_b"));
task_environment.RunUntilIdle();
}
class StarterPrerenderTest : public StarterTest {
public:
StarterPrerenderTest() {
feature_list_.InitWithFeatures(
{blink::features::kPrerender2},
// Disable the memory requirement of Prerender2 so the test can run on
// any bot.
{blink::features::kPrerender2MemoryControls});
}
~StarterPrerenderTest() override = default;
private:
base::test::ScopedFeatureList feature_list_;
};
TEST_F(StarterPrerenderTest, DoNotAffectRecordUkmDuringPrendering) {
SetupPlatformDelegateForFirstTimeUser();
fake_platform_delegate_.feature_module_installed_ = true;
// Empty callback to keep the onboarding open indefinitely.
fake_platform_delegate_.on_show_onboarding_callback_ = base::DoNothing();
EXPECT_CALL(mock_start_regular_script_callback_, Run).Times(0);
TriggerContext::Options options;
options.initial_url = kExampleDeeplink;
base::flat_map<std::string, std::string> script_parameters = {
{"ENABLED", "true"},
{"START_IMMEDIATELY", "false"},
{"REQUEST_TRIGGER_SCRIPT", "true"},
{"ORIGINAL_DEEPLINK", kExampleDeeplink}};
SimulateNavigateToUrl(GURL("https://www.different.com"));
// Trigger scripts wait for navigation to the deeplink domain.
starter_->Start(std::make_unique<TriggerContext>(
std::make_unique<ScriptParameters>(script_parameters), options));
// Start prerendering a page.
const GURL prerendering_url("https://www.different.com/prerendering");
auto* prerender_rfh = content::WebContentsTester::For(web_contents())
->AddPrerenderAndCommitNavigation(prerendering_url);
ASSERT_TRUE(prerender_rfh);
// Prerendering should not affect any trigger script's behaviour and not
// record anything.
EXPECT_THAT(GetUkmTriggerScriptStarted(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptFinished(ukm_recorder_), IsEmpty());
EXPECT_THAT(GetUkmTriggerScriptOnboarding(ukm_recorder_), IsEmpty());
histogram_tester_.ExpectUniqueSample(
"Android.AutofillAssistant.FeatureModuleInstallation",
Metrics::FeatureModuleInstallation::DFM_ALREADY_INSTALLED, 0u);
histogram_tester_.ExpectTotalCount("Android.AutofillAssistant.OnBoarding",
0u);
}
} // namespace autofill_assistant