blob: bd2f273916a1e415f04667135240d9f88fa6d32e [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/compose/proactive_nudge_tracker.h"
#include <memory>
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/task_environment.h"
#include "base/test/test_future.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_data_test_api.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/signatures.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/compose/core/browser/compose_metrics.h"
#include "components/compose/core/browser/config.h"
#include "components/segmentation_platform/public/constants.h"
#include "components/segmentation_platform/public/testing/mock_segmentation_platform_service.h"
#include "components/segmentation_platform/public/trigger.h"
#include "components/segmentation_platform/public/types/processed_value.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/origin.h"
namespace compose {
namespace {
using base::test::TestFuture;
using testing::_;
using testing::Pair;
const autofill::FieldRendererId kFieldRendererId(123);
const autofill::FieldRendererId kFieldRendererId2(4);
constexpr float kSegmentationForceShowResult = 0.01;
segmentation_platform::TrainingRequestId TrainingRequestId(
int request_number = 0) {
return segmentation_platform::TrainingRequestId(request_number + 456);
}
autofill::FormFieldData CreateTestFormFieldData(
autofill::FieldRendererId renderer_id = kFieldRendererId) {
autofill::FormFieldData f;
f.set_max_length(524);
f.set_host_frame(autofill::test::MakeLocalFrameToken());
f.set_renderer_id(renderer_id);
f.set_value(u"FormFieldDataInitialValue");
f.set_selected_text(u"selected text");
f.set_placeholder(u"Please enter your value");
f.set_bounds(gfx::RectF(300, 100));
f.set_form_control_type(autofill::FormControlType::kTextArea);
f.set_label(u"field label");
f.set_aria_label(u"aria label");
f.set_aria_description(u"aria description");
return f;
}
autofill::FormData CreateFormData() {
// Set up a form with two fields, the compose-eligible field is the first one.
autofill::FormFieldData other_field;
other_field.set_form_control_type(autofill::FormControlType::kInputMonth);
autofill::FormData form;
form.set_fields({CreateTestFormFieldData(), std::move(other_field)});
return form;
}
GURL TestURL() {
return GURL("https://example.com/test");
}
url::Origin TestOrigin() {
return url::Origin::Create(TestURL());
}
class MockProactiveNudgeTrackerDelegate
: public ProactiveNudgeTracker::Delegate {
public:
MOCK_METHOD(void,
ShowProactiveNudge,
(autofill::FormGlobalId,
autofill::FieldGlobalId,
compose::ComposeEntryPoint));
MOCK_METHOD(float, SegmentationFallbackShowResult, ());
float SegmentationForceShowResult() override {
return kSegmentationForceShowResult;
}
compose::PageUkmTracker* GetPageUkmTracker() override {
return page_ukm_tracker_.get();
}
compose::ComposeHintMetadata GetComposeHintMetadata() override {
return compose_metadata_;
}
void SetComposeHintMetadata(compose::ComposeHintMetadata metadata) {
compose_metadata_ = metadata;
}
private:
const ukm::SourceId valid_test_source_id_{1};
std::unique_ptr<compose::PageUkmTracker> page_ukm_tracker_ =
std::make_unique<compose::PageUkmTracker>(valid_test_source_id_);
compose::ComposeHintMetadata compose_metadata_;
};
class ProactiveNudgeTrackerTestBase : public testing::Test {
public:
ProactiveNudgeTrackerTestBase() = default;
ProactiveNudgeTrackerTestBase(const ProactiveNudgeTrackerTestBase&) = delete;
ProactiveNudgeTrackerTestBase& operator=(
const ProactiveNudgeTrackerTestBase&) = delete;
~ProactiveNudgeTrackerTestBase() override = default;
void SetUpNudgeTrackerTest(bool use_segmentation) {
compose::Config& config = compose::GetMutableConfigForTesting();
config.proactive_nudge_enabled = true;
config.proactive_nudge_segmentation = use_segmentation;
config.proactive_nudge_focus_delay = base::Microseconds(4);
nudge_tracker_ = std::make_unique<ProactiveNudgeTracker>(
&segmentation_service_, &delegate_);
if (use_segmentation) {
SetSegmentationResult();
} else {
EXPECT_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
.Times(0);
}
}
void TearDown() override { compose::ResetConfigForTesting(); }
segmentation_platform::MockSegmentationPlatformService&
segmentation_service() {
return segmentation_service_;
}
MockProactiveNudgeTrackerDelegate& delegate() { return delegate_; }
base::test::SingleThreadTaskEnvironment& task_environment() {
return task_environment_;
}
ProactiveNudgeTracker& nudge_tracker() { return *nudge_tracker_; }
void SetSegmentationResult(std::string label = "Show") {
ON_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
.WillByDefault(testing::WithArg<3>(testing::Invoke(
[label, this](
segmentation_platform::ClassificationResultCallback callback) {
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.request_id = TrainingRequestId(training_request_number_++);
result.ordered_labels = {label};
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), result));
})));
}
// This helper function is a shortcut to adding a test future to listen for
// compose responses.
void BindFutureToSegmentationRequest(
base::test::TestFuture<
segmentation_platform::ClassificationResultCallback>& future) {
ON_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
.WillByDefault(testing::WithArg<3>(testing::Invoke(
[&](segmentation_platform::ClassificationResultCallback cb) {
future.SetValue(std::move(cb));
})));
}
private:
base::test::SingleThreadTaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
autofill::test::AutofillUnitTestEnvironment autofill_test_environment_;
testing::NiceMock<MockProactiveNudgeTrackerDelegate> delegate_;
testing::NiceMock<segmentation_platform::MockSegmentationPlatformService>
segmentation_service_;
std::unique_ptr<ProactiveNudgeTracker> nudge_tracker_;
int training_request_number_ = 0;
};
class ProactiveNudgeTrackerTest : public ProactiveNudgeTrackerTestBase,
public testing::WithParamInterface<bool> {
public:
ProactiveNudgeTrackerTest() = default;
~ProactiveNudgeTrackerTest() override = default;
void SetUp() override {
ProactiveNudgeTrackerTestBase::SetUpNudgeTrackerTest(uses_segmentation());
}
bool uses_segmentation() { return GetParam(); }
};
ProactiveNudgeTracker::Signals TestSignals(
autofill::FormFieldData field = CreateTestFormFieldData(),
base::TimeTicks page_change_time = base::TimeTicks::Now()) {
ProactiveNudgeTracker::Signals signals;
signals.ukm_source_id = ukm::kInvalidSourceId;
signals.field = field;
signals.form = CreateFormData();
signals.page_change_time = page_change_time;
signals.page_origin = TestOrigin();
signals.page_url = TestURL();
return signals;
}
TEST_P(ProactiveNudgeTrackerTest, TestWait) {
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
BindFutureToSegmentationRequest(future);
auto field = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
// Should not nudge if nudge is requested too soon.
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
if (uses_segmentation()) {
EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {"Show"};
future.Take().Run(result);
}
EXPECT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_P(ProactiveNudgeTrackerTest, TestFocusChangePreventsNudge) {
auto field = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
nudge_tracker().FocusChangedInPage();
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_P(ProactiveNudgeTrackerTest, TestTrackingDifferentFormField) {
auto field = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
auto field2 = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field2.renderer_form_id(), field2.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field2)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_P(ProactiveNudgeTrackerTest, TestFocusChangeInUninitializedState) {
auto field = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
nudge_tracker().FocusChangedInPage();
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
}
TEST_P(ProactiveNudgeTrackerTest, TestNoNudgeDelay) {
compose::Config& config = compose::GetMutableConfigForTesting();
config.proactive_nudge_focus_delay = base::Milliseconds(0);
auto field = CreateTestFormFieldData();
if (uses_segmentation()) {
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
BindFutureToSegmentationRequest(future);
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field)));
// Wait just in case the timer could be pending.
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
} else {
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field)));
// Wait just in case the timer could be pending.
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
}
}
TEST_P(ProactiveNudgeTrackerTest, TestOneNudgeUntilCleared) {
compose::Config& config = compose::GetMutableConfigForTesting();
config.proactive_nudge_field_per_navigation = true;
auto field = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
nudge_tracker().FocusChangedInPage();
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
nudge_tracker().Clear();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
nudge_tracker().FocusChangedInPage();
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
}
TEST_P(ProactiveNudgeTrackerTest, TestOneNudgePerFocus) {
compose::Config& config = compose::GetMutableConfigForTesting();
config.proactive_nudge_field_per_navigation = false;
auto field = CreateTestFormFieldData();
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(2);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
nudge_tracker().FocusChangedInPage();
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
EXPECT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
}
INSTANTIATE_TEST_SUITE_P(,
ProactiveNudgeTrackerTest,
::testing::Bool(),
[](const auto& info) {
return info.param ? "SegmentationON"
: "SegmentationOFF";
});
class ProactiveNudgeTrackerSegmentationTest
: public ProactiveNudgeTrackerTestBase {
public:
void SetUp() override {
ProactiveNudgeTrackerTestBase::SetUpNudgeTrackerTest(true);
}
};
TEST_F(ProactiveNudgeTrackerSegmentationTest, SegmentationDontShow) {
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
BindFutureToSegmentationRequest(future);
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {
segmentation_platform::kComposePrmotionLabelDontShow};
future.Take().Run(result);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_F(ProactiveNudgeTrackerSegmentationTest,
SegmentationShowWithoutCollectingTrainingData) {
EXPECT_CALL(segmentation_service(), CollectTrainingData(_, _, _, _, _))
.Times(0);
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
BindFutureToSegmentationRequest(future);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {segmentation_platform::kComposePrmotionLabelShow};
future.Take().Run(result);
EXPECT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_F(ProactiveNudgeTrackerSegmentationTest,
SegmentationShowWithAlwaysCollectTrainingData) {
EXPECT_CALL(segmentation_service(), CollectTrainingData(_, _, _, _, _))
.Times(1);
compose::GetMutableConfigForTesting()
.proactive_nudge_always_collect_training_data = true;
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
BindFutureToSegmentationRequest(future);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {segmentation_platform::kComposePrmotionLabelShow};
future.Take().Run(result);
EXPECT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
// Test that when the segmentation result is kComposePrmotionLabelDontShow, the
// nudge can still be shown due to proactive_nudge_force_show_probability.
TEST_F(ProactiveNudgeTrackerSegmentationTest, SegmentationRandomForceShow) {
compose::GetMutableConfigForTesting().proactive_nudge_force_show_probability =
kSegmentationForceShowResult + 1e-6;
EXPECT_CALL(segmentation_service(), CollectTrainingData(_, _, _, _, _))
.Times(1);
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
BindFutureToSegmentationRequest(future);
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {
segmentation_platform::kComposePrmotionLabelDontShow};
future.Take().Run(result);
EXPECT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_F(ProactiveNudgeTrackerSegmentationTest,
SegmentationNotReadyFallbackDontShow) {
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
// Cause fallback to set DontShow.
EXPECT_CALL(delegate(), SegmentationFallbackShowResult)
.WillOnce(testing::Return(1.0f));
BindFutureToSegmentationRequest(future);
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(0);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kNotReady);
future.Take().Run(result);
EXPECT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_F(ProactiveNudgeTrackerSegmentationTest,
SegmentationNotReadyFallbackShow) {
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
// Cause fallback to set Show.
EXPECT_CALL(delegate(), SegmentationFallbackShowResult)
.WillOnce(testing::Return(0.0f));
BindFutureToSegmentationRequest(future);
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kNotReady);
future.Take().Run(result);
EXPECT_TRUE(
nudge_tracker().ProactiveNudgeRequestedForFormField(TestSignals(field)));
}
TEST_F(ProactiveNudgeTrackerSegmentationTest, InputContextNoModelParams) {
compose::Config& config = compose::GetMutableConfigForTesting();
// Need a longer delay for this test to measure time on page in seconds.
config.proactive_nudge_focus_delay = base::Seconds(2);
scoped_refptr<segmentation_platform::InputContext> input_context;
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
ON_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
.WillByDefault(testing::Invoke(
[&](auto, auto, scoped_refptr<segmentation_platform::InputContext> ic,
segmentation_platform::ClassificationResultCallback cb) {
input_context = ic;
future.SetValue(std::move(cb));
}));
auto page_load_time = base::TimeTicks::Now();
task_environment().FastForwardBy(base::Seconds(63));
ASSERT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field, page_load_time)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field, page_load_time)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {
segmentation_platform::kComposePrmotionLabelDontShow};
future.Take().Run(result);
ASSERT_TRUE(input_context);
using Val = segmentation_platform::processing::ProcessedValue;
EXPECT_THAT(
input_context->metadata_args,
testing::UnorderedElementsAre(
Pair("field_max_length", Val(524.0f)),
Pair("field_width_pixels", Val(300.0f)),
Pair("field_height_pixels", Val(100.0f)),
Pair("field_form_control_type",
Val(static_cast<float>(autofill::FormControlType::kTextArea))),
Pair("total_field_count", Val(2.0f)),
Pair("multiline_field_count", Val(1.0f)),
Pair("time_spent_on_page",
Val(63.0f +
GetComposeConfig().proactive_nudge_focus_delay.InSeconds())),
Pair("field_signature",
Val(autofill::HashFieldSignature(
autofill::CalculateFieldSignatureForField(field)))),
Pair("form_signature",
Val(autofill::HashFormSignature(
autofill::CalculateFormSignature(CreateFormData())))),
Pair("page_url", Val(GURL("https://example.com/test"))),
Pair("origin", Val(GURL("https://example.com"))),
Pair("field_value", Val(std::string("FormFieldDataInitialValue"))),
Pair("field_selected_text", Val(std::string("selected text"))),
Pair("field_placeholder_text",
Val(std::string("Please enter your value"))),
Pair("field_label", Val(std::string("field label"))),
Pair("field_aria_label", Val(std::string("aria label"))),
Pair("field_aria_description",
Val(std::string("aria description")))));
}
TEST_F(ProactiveNudgeTrackerSegmentationTest, InputContextWithModelParams) {
compose::Config& config = compose::GetMutableConfigForTesting();
// Need a longer delay for this test to measure time on page in seconds.
config.proactive_nudge_focus_delay = base::Seconds(2);
scoped_refptr<segmentation_platform::InputContext> input_context;
base::test::TestFuture<segmentation_platform::ClassificationResultCallback>
future;
auto field = CreateTestFormFieldData();
compose::ComposeHintMetadata metadata;
(*metadata.mutable_model_params())["name1"] = 1.1;
(*metadata.mutable_model_params())["name2"] = 2.2;
(*metadata.mutable_model_params())["name3"] = 3.3;
delegate().SetComposeHintMetadata(metadata);
ON_CALL(segmentation_service(), GetClassificationResult(_, _, _, _))
.WillByDefault(testing::Invoke(
[&](auto, auto, scoped_refptr<segmentation_platform::InputContext> ic,
segmentation_platform::ClassificationResultCallback cb) {
input_context = ic;
future.SetValue(std::move(cb));
}));
auto page_load_time = base::TimeTicks::Now();
task_environment().FastForwardBy(base::Seconds(63));
ASSERT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field, page_load_time)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
ASSERT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field, page_load_time)));
auto result = segmentation_platform::ClassificationResult(
segmentation_platform::PredictionStatus::kSucceeded);
result.ordered_labels = {
segmentation_platform::kComposePrmotionLabelDontShow};
future.Take().Run(result);
ASSERT_TRUE(input_context);
using Val = segmentation_platform::processing::ProcessedValue;
EXPECT_THAT(
input_context->metadata_args,
testing::UnorderedElementsAre(
Pair("field_max_length", Val(524.0f)),
Pair("field_width_pixels", Val(300.0f)),
Pair("field_height_pixels", Val(100.0f)),
Pair("field_form_control_type",
Val(static_cast<float>(autofill::FormControlType::kTextArea))),
Pair("total_field_count", Val(2.0f)),
Pair("multiline_field_count", Val(1.0f)),
Pair("time_spent_on_page",
Val(63.0f +
GetComposeConfig().proactive_nudge_focus_delay.InSeconds())),
Pair("field_signature",
Val(autofill::HashFieldSignature(
autofill::CalculateFieldSignatureForField(field)))),
Pair("form_signature",
Val(autofill::HashFormSignature(
autofill::CalculateFormSignature(CreateFormData())))),
Pair("page_url", Val(GURL("https://example.com/test"))),
Pair("origin", Val(GURL("https://example.com"))),
Pair("field_value", Val(std::string("FormFieldDataInitialValue"))),
Pair("field_selected_text", Val(std::string("selected text"))),
Pair("field_placeholder_text",
Val(std::string("Please enter your value"))),
Pair("field_label", Val(std::string("field label"))),
Pair("field_aria_label", Val(std::string("aria label"))),
Pair("field_aria_description", Val(std::string("aria description"))),
Pair("name1", Val(1.1f)), Pair("name2", Val(2.2f)),
Pair("name3", Val(3.3f))));
}
class ProactiveNudgeTrackerDerivedEngagementTest
: public ProactiveNudgeTrackerTestBase {
public:
void SetUp() override {
// CollectTrainingData is only called for force-shown nudges.
compose::GetMutableConfigForTesting()
.proactive_nudge_force_show_probability = 1.0;
ProactiveNudgeTrackerTestBase::SetUpNudgeTrackerTest(true);
}
// Set up a scenario where the nudge is shown for a field.
TestFuture<segmentation_platform::TrainingLabels>& TriggerNudgeForField(
int request_number,
const autofill::FormFieldData& field) {
EXPECT_CALL(delegate(),
ShowProactiveNudge(field.renderer_form_id(), field.global_id(),
compose::ComposeEntryPoint::kProactiveNudge))
.Times(1);
EXPECT_FALSE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field)));
task_environment().FastForwardBy(
GetComposeConfig().proactive_nudge_focus_delay);
EXPECT_TRUE(nudge_tracker().ProactiveNudgeRequestedForFormField(
TestSignals(field)));
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
training_labels_futures_.emplace_back();
EXPECT_CALL(segmentation_service(),
CollectTrainingData(
segmentation_platform::proto::SegmentId::
OPTIMIZATION_TARGET_SEGMENTATION_COMPOSE_PROMOTION,
TrainingRequestId(request_number), _, _, _))
.Times(1)
.WillOnce(testing::Invoke([&](auto, auto, auto, auto labels, auto) {
training_labels.SetValue(labels);
}));
return training_labels;
}
// Destroy the nudge tracker. This triggers CollectTrainingData() if
// necessary.
void Reset() { SetUpNudgeTrackerTest(true); }
private:
// Just holds memory for futures created in TriggerNudgeForField(), not for
// direct use.
std::deque<TestFuture<segmentation_platform::TrainingLabels>>
training_labels_futures_;
};
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, NoEngagement) {
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, CreateTestFormFieldData());
Reset();
EXPECT_EQ(training_labels.Get().output_metric,
std::make_pair("Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kIgnored)));
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, MinimalUse) {
auto form_field_data = CreateTestFormFieldData();
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, form_field_data);
compose::ComposeSessionEvents events;
nudge_tracker().ComposeSessionCompleted(
form_field_data.global_id(),
ComposeSessionCloseReason::kCloseButtonPressed, events);
Reset();
EXPECT_EQ(
training_labels.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kOpenedComposeMinimalUse)));
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, SuggestionGenerated) {
base::HistogramTester histograms;
auto form_field_data = CreateTestFormFieldData();
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, form_field_data);
compose::ComposeSessionEvents events;
events.compose_requests_count = 1;
nudge_tracker().ComposeSessionCompleted(
form_field_data.global_id(),
ComposeSessionCloseReason::kCloseButtonPressed, events);
// This test should work with or without deleting the tracker.
Reset();
EXPECT_EQ(
training_labels.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kGeneratedComposeSuggestion)));
histograms.ExpectUniqueSample(
"Compose.ProactiveNudge.DerivedEngagement",
ProactiveNudgeDerivedEngagement::kGeneratedComposeSuggestion, 1);
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, AcceptedSuggestion) {
auto form_field_data = CreateTestFormFieldData();
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, form_field_data);
compose::ComposeSessionEvents events;
events.compose_requests_count = 1;
events.inserted_results = true;
nudge_tracker().ComposeSessionCompleted(
form_field_data.global_id(), ComposeSessionCloseReason::kInsertedResponse,
events);
EXPECT_EQ(
training_labels.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion)));
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest,
IgnoresSessionForDifferentField) {
auto form_field_data = CreateTestFormFieldData();
auto form_field_data_2 =
CreateTestFormFieldData(autofill::FieldRendererId(999));
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, form_field_data);
compose::ComposeSessionEvents events;
// This one is ignored because it has the wrong field id.
nudge_tracker().ComposeSessionCompleted(form_field_data_2.global_id(),
ComposeSessionCloseReason::kAbandoned,
events);
events.compose_requests_count = 1;
events.inserted_results = true;
nudge_tracker().ComposeSessionCompleted(
form_field_data.global_id(), ComposeSessionCloseReason::kInsertedResponse,
events);
EXPECT_EQ(
training_labels.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion)));
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, TwoSessions) {
auto form_field_data = CreateTestFormFieldData();
TestFuture<segmentation_platform::TrainingLabels>& training_labels1 =
TriggerNudgeForField(0, form_field_data);
TestFuture<segmentation_platform::TrainingLabels>& training_labels2 =
TriggerNudgeForField(1, CreateTestFormFieldData(kFieldRendererId2));
compose::ComposeSessionEvents events;
events.compose_requests_count = 1;
events.inserted_results = true;
nudge_tracker().ComposeSessionCompleted(
form_field_data.global_id(), ComposeSessionCloseReason::kInsertedResponse,
events);
Reset();
EXPECT_EQ(
training_labels1.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kAcceptedComposeSuggestion)));
EXPECT_EQ(training_labels2.Get().output_metric,
std::make_pair("Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kIgnored)));
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, NudgeDisabledSingleSite) {
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, CreateTestFormFieldData());
nudge_tracker().OnUserDisabledNudge(/*single_site_only=*/true);
Reset();
EXPECT_EQ(
training_labels.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kNudgeDisabledOnSingleSite)));
}
TEST_F(ProactiveNudgeTrackerDerivedEngagementTest, NudgeDisabledAllSites) {
TestFuture<segmentation_platform::TrainingLabels>& training_labels =
TriggerNudgeForField(0, CreateTestFormFieldData());
nudge_tracker().OnUserDisabledNudge(/*single_site_only=*/false);
Reset();
EXPECT_EQ(
training_labels.Get().output_metric,
std::make_pair(
"Compose.ProactiveNudge.DerivedEngagement",
static_cast<base::HistogramBase::Sample32>(
ProactiveNudgeDerivedEngagement::kNudgeDisabledOnAllSites)));
}
} // namespace
} // namespace compose