| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_timeouts.h" |
| #include "build/build_config.h" |
| #include "build/chromeos_buildflags.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/cart/commerce_hint_service.h" |
| #include "chrome/browser/new_tab_page/new_tab_page_util.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_test_util.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/chrome_test_utils.h" |
| #include "components/commerce/core/commerce_feature_list.h" |
| #include "components/commerce/core/commerce_heuristics_data.h" |
| #include "components/commerce/core/commerce_heuristics_data_metrics_helper.h" |
| #include "components/metrics/content/subprocess_metrics_provider.h" |
| #include "components/network_session_configurator/common/network_switches.h" |
| #include "components/optimization_guide/core/optimization_guide_features.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/ukm/test_ukm_recorder.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/test/back_forward_cache_util.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/fenced_frame_test_util.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "services/metrics/public/cpp/ukm_builders.h" |
| #include "third_party/blink/public/common/features.h" |
| |
| #if BUILDFLAG(IS_ANDROID) |
| #include "chrome/test/base/android/android_browser_test.h" |
| #else |
| #include "chrome/browser/cart/cart_service.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/scoped_browser_locale.h" |
| #include "components/commerce/core/proto/cart_db_content.pb.h" |
| #include "components/session_proto_db/session_proto_db.h" |
| #include "components/signin/public/identity_manager/identity_manager.h" |
| #include "components/signin/public/identity_manager/identity_test_utils.h" |
| #include "components/variations/service/variations_service.h" |
| #include "components/variations/variations_switches.h" |
| #endif |
| |
| namespace { |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| cart_db::ChromeCartContentProto BuildProto(const char* domain, |
| const char* cart_url) { |
| cart_db::ChromeCartContentProto proto; |
| proto.set_key(domain); |
| proto.set_merchant(domain); |
| proto.set_merchant_cart_url(cart_url); |
| proto.set_timestamp(base::Time::Now().InSecondsFSinceUnixEpoch()); |
| return proto; |
| } |
| |
| cart_db::ChromeCartProductProto BuildProductInfoProto(const char* product_id) { |
| cart_db::ChromeCartProductProto proto; |
| proto.set_product_id(product_id); |
| return proto; |
| } |
| |
| cart_db::ChromeCartContentProto BuildProtoWithProducts( |
| const char* domain, |
| const char* cart_url, |
| const std::vector<const char*>& product_urls) { |
| cart_db::ChromeCartContentProto proto; |
| proto.set_key(domain); |
| proto.set_merchant_cart_url(cart_url); |
| for (const auto* const v : product_urls) { |
| proto.add_product_image_urls(v); |
| } |
| return proto; |
| } |
| |
| cart_db::ChromeCartContentProto BuildProtoWithProducts( |
| const char* domain, |
| const char* cart_url, |
| const std::vector<const char*>& product_urls, |
| const std::vector<const char*>& product_ids) { |
| cart_db::ChromeCartContentProto proto; |
| proto.set_key(domain); |
| proto.set_merchant_cart_url(cart_url); |
| for (const auto* const url : product_urls) { |
| proto.add_product_image_urls(url); |
| } |
| for (const auto* const id : product_ids) { |
| auto* added_product = proto.add_product_infos(); |
| *added_product = BuildProductInfoProto(id); |
| } |
| return proto; |
| } |
| |
| const char kMockExample[] = "guitarcenter.com"; |
| const char kMockExampleFallbackURL[] = "https://www.guitarcenter.com/cart"; |
| const char kMockExampleLinkURL[] = |
| "https://www.guitarcenter.com/shopping-cart/"; |
| const char kMockExampleURL[] = "https://www.guitarcenter.com/cart.html"; |
| |
| const cart_db::ChromeCartContentProto kMockExampleProtoFallbackCart = |
| BuildProto(kMockExample, kMockExampleFallbackURL); |
| const cart_db::ChromeCartContentProto kMockExampleProtoLinkCart = |
| BuildProto(kMockExample, kMockExampleLinkURL); |
| const cart_db::ChromeCartContentProto kMockExampleProto = |
| BuildProto(kMockExample, kMockExampleURL); |
| const cart_db::ChromeCartContentProto kMockExampleProtoWithProducts = |
| BuildProtoWithProducts( |
| kMockExample, |
| kMockExampleURL, |
| {"https://static.guitarcenter.com/product-image/foo_123", |
| "https://images.cymax.com/Images/3/bar_456-baz_789", |
| "https://static.guitarcenter.com/product-image/qux_357"}); |
| const cart_db::ChromeCartContentProto |
| kMockExampleProtoWithProductsWithoutSaved = BuildProtoWithProducts( |
| kMockExample, |
| kMockExampleURL, |
| {"https://static.guitarcenter.com/product-image/foo_123", |
| "https://images.cymax.com/Images/3/bar_456-baz_789"}); |
| |
| const char kMockAmazon[] = "amazon.com"; |
| const char kMockAmazonURL[] = "https://www.amazon.com/gp/cart/view.html"; |
| const cart_db::ChromeCartContentProto kMockAmazonProto = |
| BuildProto(kMockAmazon, kMockAmazonURL); |
| |
| const char kMockWalmart[] = "walmart.com"; |
| const char kMockWalmartURL[] = "https://www.walmart.com/cart"; |
| const cart_db::ChromeCartContentProto kMockWalmartProto = |
| BuildProto(kMockWalmart, kMockWalmartURL); |
| |
| using ShoppingCarts = |
| std::vector<SessionProtoDB<cart_db::ChromeCartContentProto>::KeyAndValue>; |
| const ShoppingCarts kExpectedExampleFallbackCart = { |
| {kMockExample, kMockExampleProtoFallbackCart}}; |
| const ShoppingCarts kExpectedExampleLinkCart = { |
| {kMockExample, kMockExampleProtoLinkCart}}; |
| const ShoppingCarts kExpectedExample = {{kMockExample, kMockExampleProto}}; |
| const ShoppingCarts kExpectedExampleWithProducts = { |
| {kMockExample, kMockExampleProtoWithProducts}}; |
| const ShoppingCarts kExpectedExampleWithProductsWithoutSaved = { |
| {kMockExample, kMockExampleProtoWithProductsWithoutSaved}}; |
| const ShoppingCarts kExpectedAmazon = {{kMockAmazon, kMockAmazonProto}}; |
| const ShoppingCarts kExpectedWalmart = {{kMockWalmart, kMockWalmartProto}}; |
| const ShoppingCarts kEmptyExpected = {}; |
| #endif |
| |
| std::unique_ptr<net::test_server::HttpResponse> BasicResponse( |
| const net::test_server::HttpRequest& request) { |
| // This should be served from test data. |
| if (request.relative_url == "/purchase.html") |
| return nullptr; |
| if (request.relative_url == "/product.html") |
| return nullptr; |
| if (request.relative_url == "/cart.html") |
| return nullptr; |
| if (request.relative_url == "/shopping-cart.html") |
| return nullptr; |
| if (request.relative_url == "/product-page.html") |
| return nullptr; |
| |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| response->set_content("dummy"); |
| response->set_content_type("text/html"); |
| return response; |
| } |
| |
| // Tests CommerceHintAgent. |
| class CommerceHintAgentTest : public PlatformBrowserTest { |
| public: |
| using FormSubmittedEntry = ukm::builders::Shopping_FormSubmitted; |
| using XHREntry = ukm::builders::Shopping_WillSendRequest; |
| using ExtractionEntry = ukm::builders::Shopping_CartExtraction; |
| using AddToCartEntry = ukm::builders::Shopping_AddToCartDetection; |
| |
| CommerceHintAgentTest() = default; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"product-skip-pattern", "(^|\\W)(?i)(skipped)(\\W|$)"}, |
| // Extend timeout to avoid flakiness. |
| {"cart-extraction-timeout", "1m"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| // HTTPS server only serves a valid cert for localhost, so this is needed |
| // to load pages from other hosts without an error. |
| command_line->AppendSwitch(switches::kIgnoreCertificateErrors); |
| // TODO(crbug.com/1491942): This fails with the field trial testing config. |
| command_line->AppendSwitch("disable-field-trial-config"); |
| } |
| |
| void SetUpOnMainThread() override { |
| PlatformBrowserTest::SetUpOnMainThread(); |
| #if !BUILDFLAG(IS_ANDROID) |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| service_ = CartServiceFactory::GetForProfile(profile); |
| auto* identity_manager = IdentityManagerFactory::GetForProfile(profile); |
| ASSERT_TRUE(identity_manager); |
| signin::SetPrimaryAccount(identity_manager, "user@gmail.com", |
| signin::ConsentLevel::kSync); |
| #endif |
| |
| // This is necessary to test non-localhost domains. See |NavigateToURL|. |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| |
| https_server_.ServeFilesFromSourceDirectory("chrome/test/data/cart/"); |
| https_server_.RegisterRequestHandler(base::BindRepeating(&BasicResponse)); |
| ASSERT_TRUE(https_server_.InitializeAndListen()); |
| https_server_.StartAcceptingConnections(); |
| |
| ukm_recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>(); |
| } |
| |
| protected: |
| content::WebContents* web_contents() { |
| return chrome_test_utils::GetActiveWebContents(this); |
| } |
| |
| void NavigateToURL(const std::string& url) { |
| // All domains resolve to 127.0.0.1 in this test. |
| GURL gurl(url); |
| ASSERT_TRUE(content::NavigateToURL( |
| web_contents(), https_server_.GetURL(gurl.host(), gurl.path()))); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void SendXHR(const std::string& relative_path, const char post_data[]) { |
| const char script_template[] = R"( |
| new Promise(function (resolve, reject) { |
| let xhr = new XMLHttpRequest(); |
| xhr.open('POST', $1, true); |
| xhr.onload = () => { |
| resolve(true); |
| }; |
| xhr.send($2); |
| }); |
| )"; |
| std::string script = |
| content::JsReplace(script_template, relative_path, post_data); |
| ASSERT_EQ(true, EvalJs(web_contents(), script)) << script; |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| void WaitForCartCount(const ShoppingCarts& expected) { |
| satisfied_ = false; |
| while (true) { |
| base::RunLoop().RunUntilIdle(); |
| base::RunLoop run_loop; |
| service_->LoadAllActiveCarts(base::BindOnce( |
| &CommerceHintAgentTest::CheckCartCount, base::Unretained(this), |
| run_loop.QuitClosure(), expected)); |
| run_loop.Run(); |
| if (satisfied_) |
| break; |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); |
| } |
| } |
| |
| void CheckCartCount(base::OnceClosure closure, |
| ShoppingCarts expected, |
| bool result, |
| ShoppingCarts found) { |
| bool same_size = found.size() == expected.size(); |
| satisfied_ = same_size; |
| if (same_size) { |
| for (size_t i = 0; i < expected.size(); i++) { |
| EXPECT_EQ(found[i].first, expected[i].first); |
| GURL::Replacements remove_port; |
| remove_port.ClearPort(); |
| EXPECT_EQ(GURL(found[i].second.merchant_cart_url()) |
| .ReplaceComponents(remove_port) |
| .spec(), |
| expected[i].second.merchant_cart_url()); |
| } |
| } else { |
| VLOG(3) << "Found " << found.size() << " but expecting " |
| << expected.size(); |
| } |
| std::move(closure).Run(); |
| } |
| |
| void WaitForCarts(const ShoppingCarts& expected) { |
| satisfied_ = false; |
| while (true) { |
| base::RunLoop().RunUntilIdle(); |
| base::RunLoop run_loop; |
| service_->LoadAllActiveCarts(base::BindOnce( |
| &CommerceHintAgentTest::CheckCarts, base::Unretained(this), |
| run_loop.QuitClosure(), expected)); |
| run_loop.Run(); |
| if (satisfied_) |
| break; |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); |
| } |
| } |
| |
| void CheckCarts(base::OnceClosure closure, |
| ShoppingCarts expected, |
| bool result, |
| ShoppingCarts found) { |
| bool same_size = found.size() == expected.size(); |
| satisfied_ = same_size; |
| if (same_size) { |
| for (size_t i = 0; i < expected.size(); i++) { |
| satisfied_ &= found[i].first == expected[i].first; |
| GURL::Replacements remove_port; |
| remove_port.ClearPort(); |
| satisfied_ &= GURL(found[i].second.merchant_cart_url()) |
| .ReplaceComponents(remove_port) |
| .spec() == expected[i].second.merchant_cart_url(); |
| satisfied_ &= |
| found[i].second.merchant() == expected[i].second.merchant(); |
| } |
| } |
| std::move(closure).Run(); |
| } |
| |
| void WaitForProductCount(const ShoppingCarts& expected) { |
| satisfied_ = false; |
| while (true) { |
| base::RunLoop().RunUntilIdle(); |
| base::RunLoop run_loop; |
| service_->LoadAllActiveCarts(base::BindOnce( |
| &CommerceHintAgentTest::CheckCartWithProducts, base::Unretained(this), |
| run_loop.QuitClosure(), expected)); |
| run_loop.Run(); |
| if (satisfied_) |
| break; |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout()); |
| } |
| } |
| |
| void CheckCartWithProducts(base::OnceClosure closure, |
| ShoppingCarts expected, |
| bool result, |
| ShoppingCarts found) { |
| bool fail = false; |
| bool same_size = found.size() == expected.size(); |
| if (!same_size) |
| fail = true; |
| for (size_t i = 0; i < std::min(found.size(), expected.size()); i++) { |
| EXPECT_EQ(found[i].first, expected[i].first); |
| GURL::Replacements remove_port; |
| remove_port.ClearPort(); |
| EXPECT_EQ(GURL(found[i].second.merchant_cart_url()) |
| .ReplaceComponents(remove_port) |
| .spec(), |
| expected[i].second.merchant_cart_url()); |
| same_size = found[i].second.product_image_urls_size() == |
| expected[i].second.product_image_urls_size(); |
| if (!same_size) { |
| fail = true; |
| } else { |
| for (int j = 0; j < found[i].second.product_image_urls_size(); j++) { |
| EXPECT_EQ(found[i].second.product_image_urls(j), |
| expected[i].second.product_image_urls(j)); |
| } |
| } |
| same_size = found[i].second.product_infos_size() == |
| expected[i].second.product_infos_size(); |
| if (!same_size) { |
| fail = true; |
| } else { |
| for (int j = 0; j < found[i].second.product_infos_size(); j++) { |
| EXPECT_EQ(found[i].second.product_infos(j).product_id(), |
| expected[i].second.product_infos(j).product_id()); |
| } |
| } |
| } |
| satisfied_ = !fail; |
| std::move(closure).Run(); |
| } |
| #endif |
| |
| void ExpectUKMCount(base::StringPiece entry_name, |
| const std::string& metric_name, |
| int expected_count) { |
| auto entries = ukm_recorder()->GetEntriesByName(entry_name); |
| int count = 0; |
| for (const ukm::mojom::UkmEntry* const entry : entries) { |
| if (ukm_recorder()->GetEntryMetric(entry, metric_name)) { |
| count += 1; |
| } |
| } |
| EXPECT_EQ(count, expected_count); |
| } |
| |
| ukm::TestAutoSetUkmRecorder* ukm_recorder() { return ukm_recorder_.get(); } |
| |
| void WaitForUmaCount(base::StringPiece name, |
| base::HistogramBase::Count expected_count) { |
| while (true) { |
| base::RunLoop().RunUntilIdle(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| base::HistogramBase::Count count = 0; |
| for (const auto& bucket : histogram_tester_.GetAllSamples(name)) |
| count += bucket.count; |
| ASSERT_LE(count, expected_count) << "WaitForUmaCount(" << name |
| << ") has more counts than expectation."; |
| if (count == expected_count) |
| break; |
| LOG(INFO) << "WaitForUmaCount() is expecting " << expected_count |
| << " but found " << count; |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 10); |
| } |
| } |
| |
| void WaitForUmaBucketCount(base::StringPiece name, |
| base::HistogramBase::Sample sample, |
| base::HistogramBase::Count expected_count) { |
| while (true) { |
| base::RunLoop().RunUntilIdle(); |
| metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting(); |
| auto count = histogram_tester_.GetBucketCount(name, sample); |
| if (count == expected_count) |
| break; |
| LOG(INFO) << "WaitForUmaBucketCount() is expecting " << expected_count |
| << " but found " << count; |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 10); |
| } |
| } |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| #if !BUILDFLAG(IS_ANDROID) |
| raw_ptr<CartService, DanglingUntriaged> service_; |
| #endif |
| net::EmbeddedTestServer https_server_{net::EmbeddedTestServer::TYPE_HTTPS}; |
| std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_; |
| bool satisfied_; |
| base::HistogramTester histogram_tester_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, AddToCartByURL) { |
| // For add-to-cart by URL, normally a URL in that domain has already been |
| // committed. |
| NavigateToURL("https://www.guitarcenter.com/"); |
| NavigateToURL("https://www.guitarcenter.com/add-to-cart?product=1"); |
| |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, AddToCartByForm) { |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| ExpectUKMCount(XHREntry::kEntryName, "IsAddToCart", 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, AddToCartByForm_WithLink) { |
| NavigateToURL("https://www.guitarcenter.com/product.html"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleLinkCart); |
| #endif |
| ExpectUKMCount(XHREntry::kEntryName, "IsAddToCart", 1); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, AddToCartByForm_WithWrongLink) { |
| // Mismatching eTLD+1 domain uses cart URL in the look-up table. |
| NavigateToURL("https://walmart.com/product.html"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| WaitForCartCount(kExpectedWalmart); |
| ExpectUKMCount(XHREntry::kEntryName, "IsAddToCart", 1); |
| } |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, AddToCartByURL_XHR) { |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| ExpectUKMCount(XHREntry::kEntryName, "IsAddToCart", 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, SkipAddToCart_FromComponent) { |
| bool is_populated = |
| cart::CommerceHintService::InitializeCommerceHeuristicsForTesting( |
| base::Version("0.0.0.1"), R"###( |
| { |
| "guitarcenter.com": { |
| "skip_add_to_cart_regex": "dummy-request" |
| } |
| } |
| )###", |
| "{}", "", ""); |
| DCHECK(is_populated); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| |
| SendXHR("/add-to-cart/dummy-request-url", "product: 123"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| } |
| |
| // TODO(https://crbug/1310497, https://crbug.com/1362442): This test is flaky |
| // on ChromeOS and Linux Asan. |
| #if BUILDFLAG(IS_CHROMEOS) |
| #define MAYBE_VisitCart DISABLED_VisitCart |
| #elif BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER) |
| #define MAYBE_VisitCart DISABLED_VisitCart |
| #else |
| #define MAYBE_VisitCart VisitCart |
| #endif |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, MAYBE_VisitCart) { |
| // Cannot use dummy page with zero products, or the cart would be deleted. |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExample); |
| #endif |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, |
| VisitCart_GeneralPattern_FromComponent) { |
| bool is_populated = |
| cart::CommerceHintService::InitializeCommerceHeuristicsForTesting( |
| base::Version("0.0.0.1"), "{}", R"###( |
| { |
| "cart_page_url_regex": "(special|lol)" |
| } |
| )###", |
| "", ""); |
| DCHECK(is_populated); |
| |
| NavigateToURL("https://www.guitarcenter.com/special.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/lol.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, |
| VisitCart_PerDomain_FromComponent) { |
| bool is_populated = |
| cart::CommerceHintService::InitializeCommerceHeuristicsForTesting( |
| base::Version("0.0.0.1"), R"###( |
| { |
| "guitarcenter.com": { |
| "cart_url_regex" : "unique|laugh" |
| } |
| } |
| )###", |
| R"###( |
| { |
| "cart_page_url_regex": "(special|lol)" |
| } |
| )###", |
| "", ""); |
| DCHECK(is_populated); |
| |
| NavigateToURL("https://www.guitarcenter.com/unique.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/special.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/laugh.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, ExtractCart_ScriptFromResource) { |
| // This page has three products. |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForProductCount(kExpectedExampleWithProductsWithoutSaved); |
| #endif |
| WaitForUmaCount("Commerce.Carts.ExtractionExecutionTime", 1); |
| WaitForUmaCount("Commerce.Carts.ExtractionLongestTaskTime", 1); |
| WaitForUmaCount("Commerce.Carts.ExtractionTotalTasksTime", 1); |
| WaitForUmaCount("Commerce.Carts.ExtractionElapsedTime", 1); |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 1); |
| WaitForUmaBucketCount( |
| "Commerce.Heuristics.CartExtractionScriptSource", |
| int(CommerceHeuristicsDataMetricsHelper::HeuristicsSource::FROM_RESOURCE), |
| 1); |
| WaitForUmaBucketCount("Commerce.Heuristics.ProductIDExtractionPatternSource", |
| int(CommerceHeuristicsDataMetricsHelper:: |
| HeuristicsSource::FROM_FEATURE_PARAMETER), |
| 1); |
| ExpectUKMCount(ExtractionEntry::kEntryName, "ExtractionExecutionTime", 1); |
| ExpectUKMCount(ExtractionEntry::kEntryName, "ExtractionLongestTaskTime", 1); |
| ExpectUKMCount(ExtractionEntry::kEntryName, "ExtractionTotalTasksTime", 1); |
| ExpectUKMCount(ExtractionEntry::kEntryName, "ExtractionElapsedTime", 1); |
| ExpectUKMCount(ExtractionEntry::kEntryName, "ExtractionTimedOut", 1); |
| |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, ExtractCart_ScriptFromComponent) { |
| // Initialize component with a dummy script that returns immediately. |
| std::string extraction_script = R"###( |
| async function extractAllItems(root) { |
| return { |
| "products":[ |
| { |
| "imageUrl": "https://foo.com/bar/image", |
| "price": "$10", |
| "title": "Foo bar", |
| "url": "https://foo.com/bar", |
| } |
| ] |
| }; |
| } |
| extracted_results_promise = extractAllItems(document); |
| )###"; |
| std::string product_id_json = "{\"foo.com\": \"test\"}"; |
| bool is_populated = |
| cart::CommerceHintService::InitializeCommerceHeuristicsForTesting( |
| base::Version("0.0.0.1"), "{}", "{}", std::move(product_id_json), |
| std::move(extraction_script)); |
| DCHECK(is_populated); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts("guitarcenter.com", |
| "https://www.guitarcenter.com/cart.html", |
| {"https://foo.com/bar/image"}); |
| const ShoppingCarts expected_carts = { |
| {"guitarcenter.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| #endif |
| WaitForUmaBucketCount("Commerce.Heuristics.CartExtractionScriptSource", |
| int(CommerceHeuristicsDataMetricsHelper:: |
| HeuristicsSource::FROM_COMPONENT), |
| 1); |
| WaitForUmaBucketCount("Commerce.Heuristics.ProductIDExtractionPatternSource", |
| int(CommerceHeuristicsDataMetricsHelper:: |
| HeuristicsSource::FROM_COMPONENT), |
| 1); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, |
| ExtractCart_ProductIDFromComponent) { |
| std::string global_heuristics = R"###( |
| { |
| "rule_discount_partner_merchant_regex": "(guitarcenter.com)" |
| } |
| )###"; |
| std::string product_id_json = R"###( |
| { |
| "product_element": {"www.guitarcenter.com": "<a href=\"#modal-(\\w+)"} |
| } |
| )###"; |
| bool is_populated = |
| cart::CommerceHintService::InitializeCommerceHeuristicsForTesting( |
| base::Version("0.0.0.1"), "{}", global_heuristics, |
| std::move(product_id_json), ""); |
| DCHECK(is_populated); |
| |
| // This page has two products. |
| NavigateToURL("https://www.guitarcenter.com/shopping-cart.html"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts( |
| "aaa.com", "https://www.guitarcenter.com/shopping-cart.html", |
| {"https://static.guitarcenter.com/product-image/foo_2-0-medium", |
| "https://static.guitarcenter.com/product-image/bar_2-0-medium"}, |
| {"foo_1", "bar_1"}); |
| const ShoppingCarts expected_carts = { |
| {"guitarcenter.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| WaitForUmaBucketCount( |
| "Commerce.Heuristics.CartExtractionScriptSource", |
| int(CommerceHeuristicsDataMetricsHelper::HeuristicsSource::FROM_RESOURCE), |
| 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, AddCartFromComponent) { |
| bool is_populated = |
| cart::CommerceHintService::InitializeCommerceHeuristicsForTesting( |
| base::Version("0.0.0.1"), R"###( |
| { |
| "guitarcenter.com": { |
| "merchant_name" : "SPECIAL_NAME", |
| "cart_url" : "https://www.guitarcenter.com/special_cart" |
| } |
| } |
| )###", |
| "{}", "", ""); |
| DCHECK(is_populated); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| // Browser-side commerce heuristics are still correct despite being |
| // used to populate renderer side commerce heuristics. |
| cart_db::ChromeCartContentProto expected_cart_protos = BuildProto( |
| "guitarcenter.com", "https://www.guitarcenter.com/special_cart"); |
| expected_cart_protos.set_merchant("SPECIAL_NAME"); |
| const ShoppingCarts expected_carts = { |
| {"guitarcenter.com", expected_cart_protos}}; |
| WaitForCarts(expected_carts); |
| } |
| |
| class CommerceHintNoRateControlTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"cart-extraction-gap-time", "0s"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // TODO(crbug.com/1241582): Add the rate control back for this test after |
| // figuring out why rate control makes this test flaky. |
| // Disabled due to failing tests. https://crbug.com/1254802 |
| IN_PROC_BROWSER_TEST_F(CommerceHintNoRateControlTest, DISABLED_CartPriority) { |
| NavigateToURL("https://www.guitarcenter.com/"); |
| NavigateToURL("https://www.guitarcenter.com/add-to-cart?product=1"); |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| WaitForCarts(kExpectedExample); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| NavigateToURL("https://www.guitarcenter.com/add-to-cart?product=1"); |
| WaitForCarts(kExpectedExample); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, VisitCheckout) { |
| GURL example_url = GURL("https://www.guitarcenter.com/"); |
| #if !BUILDFLAG(IS_ANDROID) |
| service_->AddCart(example_url, std::nullopt, kMockExampleProto); |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| |
| NavigateToURL(example_url.spec()); |
| NavigateToURL("https://www.guitarcenter.com/123/checkout/456"); |
| // URL is checked against checkout twice. |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 2); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, PurchaseByURL) { |
| GURL amazon_url = GURL("https://www.amazon.com/"); |
| #if !BUILDFLAG(IS_ANDROID) |
| service_->AddCart(amazon_url, std::nullopt, kMockAmazonProto); |
| WaitForCartCount(kExpectedAmazon); |
| #endif |
| |
| NavigateToURL(amazon_url.spec()); |
| NavigateToURL( |
| "http://amazon.com/gp/buy/spc/handlers/static-submit-decoupled.html"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByURL", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, PurchaseByForm) { |
| GURL example_url = GURL("https://www.guitarcenter.com/"); |
| #if !BUILDFLAG(IS_ANDROID) |
| service_->AddCart(example_url, std::nullopt, kMockExampleProto); |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| |
| NavigateToURL("https://www.guitarcenter.com/purchase.html"); |
| |
| std::string script = "document.getElementById('submit').click()"; |
| ASSERT_TRUE(ExecJs(web_contents(), script)); |
| content::TestNavigationObserver load_observer(web_contents()); |
| load_observer.WaitForNavigationFinished(); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| ExpectUKMCount(FormSubmittedEntry::kEntryName, "IsTransaction", 1); |
| } |
| |
| // TODO(crbug.com/1180268): CrOS multi-profiles implementation is different from |
| // the rest and below tests don't work on CrOS yet. Re-enable them on CrOS after |
| // figuring out the reason for failure. |
| // Signing out on Lacros is not possible. |
| // TODO(crbug.com/1332878): Intentionally skip below two tests for Android for |
| // now. |
| #if !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) |
| // TODO(crbug/1258803): Skip work on non-eligible profiles. |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, NonSignInUser) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| auto* identity_manager = IdentityManagerFactory::GetForProfile(profile); |
| ASSERT_TRUE(identity_manager); |
| signin::ClearPrimaryAccount(identity_manager); |
| NavigateToURL("https://www.guitarcenter.com/cart"); |
| WaitForCartCount(kEmptyExpected); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| WaitForCartCount(kEmptyExpected); |
| |
| SendXHR("/add-to-cart", "product: 123"); |
| WaitForCartCount(kEmptyExpected); |
| |
| signin::SetPrimaryAccount(identity_manager, "user@gmail.com", |
| signin::ConsentLevel::kSync); |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| } |
| |
| // TODO(crbug/1258803): Skip work on non-eligible profiles. |
| // Flaky on Linux Asan and Mac: https://crbug.com/1306908. |
| #if (BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER)) || BUILDFLAG(IS_MAC) |
| #define MAYBE_MultipleProfiles DISABLED_MultipleProfiles |
| #else |
| #define MAYBE_MultipleProfiles MultipleProfiles |
| #endif |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentTest, MAYBE_MultipleProfiles) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| auto* identity_manager = IdentityManagerFactory::GetForProfile(profile); |
| ProfileManager* profile_manager = g_browser_process->profile_manager(); |
| ASSERT_TRUE(identity_manager); |
| ASSERT_EQ(profile_manager->GetNumberOfProfiles(), 1U); |
| signin::ClearPrimaryAccount(identity_manager); |
| NavigateToURL("https://www.guitarcenter.com/cart"); |
| WaitForCartCount(kEmptyExpected); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| WaitForCartCount(kEmptyExpected); |
| |
| SendXHR("/add-to-cart", "product: 123"); |
| WaitForCartCount(kEmptyExpected); |
| |
| // Create another profile. |
| base::FilePath profile_path2 = |
| profile_manager->GenerateNextProfileDirectoryPath(); |
| profiles::testing::CreateProfileSync(profile_manager, profile_path2); |
| ASSERT_EQ(profile_manager->GetNumberOfProfiles(), 2U); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| } |
| #endif // !BUILDFLAG(IS_CHROMEOS) && !BUILDFLAG(IS_ANDROID) |
| |
| class CommerceHintCacaoTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeatures( |
| { |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| optimization_guide::features::kOptimizationHints |
| }, |
| {commerce::kChromeCartDomBasedHeuristics}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintCacaoTest, Passed) { |
| auto* optimization_guide_decider = |
| #if !BUILDFLAG(IS_ANDROID) |
| OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile()); |
| #else |
| OptimizationGuideKeyedServiceFactory::GetForProfile( |
| chrome_test_utils::GetProfile(this)); |
| #endif |
| // Need the non-default port here. |
| optimization_guide_decider->AddHintForTesting( |
| https_server_.GetURL("www.guitarcenter.com", "/"), |
| optimization_guide::proto::SHOPPING_PAGE_PREDICTOR, std::nullopt); |
| optimization_guide_decider->AddHintForTesting( |
| GURL("https://www.guitarcenter.com/cart"), |
| optimization_guide::proto::SHOPPING_PAGE_PREDICTOR, std::nullopt); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| } |
| |
| // If command line argument "optimization_guide_hints_override" is not given, |
| // nothing is specified in AddHintForTesting(), and the real hints are not |
| // downloaded, all the URLs are considered non-shopping. |
| IN_PROC_BROWSER_TEST_F(CommerceHintCacaoTest, Rejected) { |
| NavigateToURL("https://www.guitarcenter.com/cart"); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| |
| SendXHR("/add-to-cart", "product: 123"); |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 30); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 0); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.VisitCart", 0); |
| } |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| class CommerceHintProductInfoTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ntp_features::kNtpChromeCartModule, |
| {{ntp_features::kNtpChromeCartModuleAbandonedCartDiscountParam, |
| "true"}, |
| {"partner-merchant-pattern", |
| "(guitarcenter.com|aaa.com|bbb.com|ccc.com|ddd.com)"}, |
| {"product-skip-pattern", "(^|\\W)(?i)(skipped)(\\W|$)"}, |
| {"product-id-pattern-mapping", |
| R"###( |
| { |
| "product_element": |
| {"www.aaa.com": "<a href=\"#modal-(\\w+)"}, |
| "product_image_url": |
| {"www.bbb.com": "(\\w+)-\\d+-medium", |
| "www.ddd.com": ["(\\w+)-\\d+-medium", 0] |
| }, |
| "product_url": |
| {"www.ccc.com": "products-(\\w+)", |
| "www.guitarcenter.com": "products-(\\w+)"} |
| } |
| )###"}, |
| // Extend timeout to avoid flakiness. |
| {"cart-extraction-timeout", "1m"}}}, |
| {commerce::kRetailCoupons, |
| {{"coupon-partner-merchant-pattern", "(eee.com)"}, |
| {"coupon-product-id-pattern-mapping", |
| R"###( |
| {"product_url": {"www.eee.com": "products-(\\w+)"}} |
| )###"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, AddToCartByForm_CaptureId) { |
| NavigateToURL("https://www.guitarcenter.com/product.html"); |
| SendXHR("/cart/update", "product_id=id_foo&add_to_cart=true"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts(kMockExample, kMockExampleLinkURL, {}, {"id_foo"}); |
| const ShoppingCarts expected_carts = {{kMockExample, expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, AddToCartByURL_CaptureId) { |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart?pr1id=id_foo", ""); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts(kMockExample, kMockExampleFallbackURL, {}, |
| {"id_foo"}); |
| const ShoppingCarts expected_carts = {{kMockExample, expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, |
| ExtractCart_CaptureId_FromElement) { |
| // This page has two products. |
| NavigateToURL("https://www.aaa.com/shopping-cart.html"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts( |
| "aaa.com", "https://www.aaa.com/shopping-cart.html", |
| {"https://static.guitarcenter.com/product-image/foo_2-0-medium", |
| "https://static.guitarcenter.com/product-image/bar_2-0-medium"}, |
| {"foo_1", "bar_1"}); |
| const ShoppingCarts expected_carts = {{"aaa.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, |
| ExtractCart_CaptureId_FromImageURL) { |
| // This page has two products. |
| NavigateToURL("https://www.bbb.com/shopping-cart.html"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts( |
| "bbb.com", "https://www.bbb.com/shopping-cart.html", |
| {"https://static.guitarcenter.com/product-image/foo_2-0-medium", |
| "https://static.guitarcenter.com/product-image/bar_2-0-medium"}, |
| {"foo_2", "bar_2"}); |
| const ShoppingCarts expected_carts = {{"bbb.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, |
| ExtractCart_CaptureId_FromProductURL) { |
| // This page has two products. |
| NavigateToURL("https://www.ccc.com/shopping-cart.html"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts( |
| "ccc.com", "https://www.ccc.com/shopping-cart.html", |
| {"https://static.guitarcenter.com/product-image/foo_2-0-medium", |
| "https://static.guitarcenter.com/product-image/bar_2-0-medium"}, |
| {"foo_3", "bar_3"}); |
| const ShoppingCarts expected_carts = {{"ccc.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, |
| ExtractCart_CaptureId_CaptureGroupIndex) { |
| // This page has two products. |
| NavigateToURL("https://www.ddd.com/shopping-cart.html"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts( |
| "ddd.com", "https://www.ddd.com/shopping-cart.html", |
| {"https://static.guitarcenter.com/product-image/foo_2-0-medium", |
| "https://static.guitarcenter.com/product-image/bar_2-0-medium"}, |
| {"foo_2-0-medium", "bar_2-0-medium"}); |
| const ShoppingCarts expected_carts = {{"ddd.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, |
| ExtractCart_CaptureId_CouponPartnerMerchants) { |
| // This page has two products. |
| NavigateToURL("https://www.eee.com/shopping-cart.html"); |
| |
| const cart_db::ChromeCartContentProto expected_cart_protos = |
| BuildProtoWithProducts( |
| "eee.com", "https://www.eee.com/shopping-cart.html", |
| {"https://static.guitarcenter.com/product-image/foo_2-0-medium", |
| "https://static.guitarcenter.com/product-image/bar_2-0-medium"}, |
| {"foo_3", "bar_3"}); |
| const ShoppingCarts expected_carts = {{"eee.com", expected_cart_protos}}; |
| WaitForProductCount(expected_carts); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintProductInfoTest, |
| RBDPartnerCartURLNotOverwrite) { |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents()->GetBrowserContext()); |
| profile->GetPrefs()->SetBoolean(prefs::kCartDiscountEnabled, true); |
| EXPECT_TRUE(service_->IsCartDiscountEnabled()); |
| |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| } |
| #endif // !BUILDFLAG(IS_ANDROID) |
| |
| // Product extraction would always timeout and return empty results. |
| class CommerceHintTimeoutTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"cart-extraction-timeout", "0"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Flaky on Linux, ChromeOS and Windows: https://crbug.com/1257964. |
| // Falky on Mac: https://crbug.com/1312849. |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || \ |
| BUILDFLAG(IS_MAC) |
| #define MAYBE_ExtractCart DISABLED_ExtractCart |
| #else |
| #define MAYBE_ExtractCart ExtractCart |
| #endif |
| IN_PROC_BROWSER_TEST_F(CommerceHintTimeoutTest, MAYBE_ExtractCart) { |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 1, 1); |
| } |
| |
| class CommerceHintMaxCountTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"cart-extraction-max-count", "1"}, |
| // Extend timeout to avoid flakiness. |
| {"cart-extraction-timeout", "1m"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Flaky on Linux: https://crbug.com/1257964. |
| // See definition of MAYBE_ExtractCart above. |
| IN_PROC_BROWSER_TEST_F(CommerceHintMaxCountTest, MAYBE_ExtractCart) { |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| // Wait for trying to fetch extraction script from browser process. |
| base::PlatformThread::Sleep(TestTimeouts::tiny_timeout() * 30); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleWithProducts); |
| #endif |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 1); |
| |
| // This would have triggered another extraction if not limited by max count |
| // per navigation. |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| // Navigation resets count, so can do another extraction. |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 2); |
| } |
| |
| // Override add-to-cart pattern. |
| class CommerceHintAddToCartPatternTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"add-to-cart-pattern", "(special|text)"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAddToCartPatternTest, AddToCartByURL) { |
| NavigateToURL("https://www.guitarcenter.com/Special?product=1"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/add-to-cart?product=1"); |
| NavigateToURL("https://www.guitarcenter.com/add-to-cart?product=1"); |
| NavigateToURL("https://www.guitarcenter.com/add-to-cart?product=1"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/Text?product=1"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByURL", 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAddToCartPatternTest, AddToCartByForm) { |
| NavigateToURL("https://www.guitarcenter.com/"); |
| |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_Special"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_Text"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 2); |
| } |
| |
| // Override per-domain add-to-cart pattern. |
| class CommerceHintSkippAddToCartTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"skip-add-to-cart-mapping", R"({"guitarcenter.com": ".*"})"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintSkippAddToCartTest, AddToCartByForm) { |
| NavigateToURL("https://www.guitarcenter.com/"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 0); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| |
| // Test AddToCart that is supposed to be skipped based on resources is now |
| // overwritten. |
| const cart_db::ChromeCartContentProto qvc_cart = |
| BuildProto("qvc.com", "https://www.qvc.com/checkout/cart.html"); |
| const ShoppingCarts result = {{"qvc.com", qvc_cart}}; |
| #endif |
| NavigateToURL("https://www.qvc.com/"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(result); |
| #endif |
| } |
| |
| #if BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER) |
| // Override per-domain and generic cart pattern. |
| class CommerceHintCartPatternTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"cart-pattern", "chicken|egg"}, |
| {"cart-pattern-mapping", |
| R"({"guitarcenter.com": "(special|text)lol"})"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // TODO(https://crbug.com/1362442): Deflake this test. |
| IN_PROC_BROWSER_TEST_F(CommerceHintCartPatternTest, DISABLED_VisitCart) { |
| // The test is flaky with same-site back/forward cache, presumably because it |
| // doesn't expect a RenderView change on same-site navigations. |
| // TODO(https://crbug.com/1302902): Investigate and fix this. |
| content::DisableBackForwardCacheForTesting( |
| web_contents(), |
| content::BackForwardCache::TEST_ASSUMES_NO_RENDER_FRAME_CHANGE); |
| |
| NavigateToURL("https://www.guitarcenter.com/SpecialLoL"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| NavigateToURL("https://www.guitarcenter.com/chicken"); |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/TextLoL"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 2); |
| |
| // Unspecified domains fall back to generic pattern. |
| NavigateToURL("https://www.example.com/SpecialLoL"); |
| NavigateToURL("https://www.example.com/cart.html"); |
| NavigateToURL("https://www.example.com/TextLoL"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 2); |
| |
| NavigateToURL("https://www.example.com/Chicken"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 3); |
| |
| NavigateToURL("https://www.example.com/Egg"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 4); |
| } |
| #endif // BUILDFLAG(IS_LINUX) && defined(ADDRESS_SANITIZER) |
| |
| // Override per-domain and generic checkout pattern. |
| class CommerceHintCheckoutPatternTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"checkout-pattern", "meow|purr"}, |
| {"checkout-pattern-mapping", |
| R"({"guitarcenter.com": "special|text"})"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintCheckoutPatternTest, VisitCheckout) { |
| NavigateToURL("https://www.guitarcenter.com/Special"); |
| // URLs are checked against checkout twice. |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 2); |
| |
| NavigateToURL("https://www.guitarcenter.com/checkout/"); |
| NavigateToURL("https://www.guitarcenter.com/meow/"); |
| NavigateToURL("https://www.guitarcenter.com/purr/"); |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 2); |
| |
| NavigateToURL("https://www.guitarcenter.com/Text"); |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 4); |
| |
| // Unspecified domains fall back to generic pattern. |
| NavigateToURL("https://www.example.com/Special"); |
| NavigateToURL("https://www.example.com/checkout/"); |
| NavigateToURL("https://www.example.com/Text"); |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 4); |
| |
| NavigateToURL("https://www.example.com/Meow"); |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 6); |
| |
| NavigateToURL("https://www.example.com/Purr"); |
| WaitForUmaCount("Commerce.Carts.VisitCheckout", 8); |
| } |
| |
| // Override per-domain and generic purchase button pattern. |
| class CommerceHintPurchaseButtonPatternTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"purchase-button-pattern", "meow|purr"}, |
| {"purchase-button-pattern-mapping", |
| R"({"guitarcenter.com": "woof|bark"})"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintPurchaseButtonPatternTest, PurchaseByForm) { |
| std::string url; |
| auto test_button = [&](const char* button_text) { |
| NavigateToURL(url); |
| const std::string& script = base::StringPrintf( |
| R"( |
| const btn = document.getElementById('submit'); |
| btn.innerText = "%s"; |
| btn.click(); |
| )", |
| button_text); |
| ASSERT_TRUE(ExecJs(web_contents(), script)); |
| content::TestNavigationObserver load_observer(web_contents()); |
| load_observer.WaitForNavigationFinished(); |
| }; |
| url = "https://www.guitarcenter.com/purchase.html"; |
| |
| test_button("Woof"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 1); |
| |
| test_button("Meow"); |
| test_button("Pay now"); |
| test_button("Purr"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 1); |
| |
| test_button("Bark"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 2); |
| |
| // Unspecified domains fall back to generic pattern. |
| url = "https://www.example.com/purchase.html"; |
| |
| test_button("Meow"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 3); |
| |
| test_button("Woof"); |
| test_button("Pay now"); |
| test_button("Bark"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 3); |
| |
| test_button("Purr"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByPOST", 4); |
| } |
| |
| class CommerceHintPurchaseURLPatternTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"purchase-url-pattern-mapping", |
| R"({"guitarcenter.com": "special|text"})"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintPurchaseURLPatternTest, PurchaseByURL) { |
| NavigateToURL("https://www.guitarcenter.com/Special"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByURL", 1); |
| |
| NavigateToURL("https://www.guitarcenter.com/Text"); |
| WaitForUmaCount("Commerce.Carts.PurchaseByURL", 2); |
| } |
| |
| class CommerceHintOptimizeRendererTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {{"cart-extraction-gap-time", "0s"}}}, |
| {optimization_guide::features::kOptimizationHints, {{}}}}, |
| {}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Times out on multiple platforms. https://crbug.com/1258553 |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \ |
| BUILDFLAG(IS_ANDROID) |
| #define MAYBE_CartExtractionSkipped DISABLED_CartExtractionSkipped |
| #else |
| #define MAYBE_CartExtractionSkipped CartExtractionSkipped |
| #endif |
| IN_PROC_BROWSER_TEST_F(CommerceHintOptimizeRendererTest, |
| MAYBE_CartExtractionSkipped) { |
| // Without adding testing hints, all the URLs are considered non-shopping. |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 0); |
| |
| auto* optimization_guide_decider = |
| #if !BUILDFLAG(IS_ANDROID) |
| OptimizationGuideKeyedServiceFactory::GetForProfile(browser()->profile()); |
| #else |
| OptimizationGuideKeyedServiceFactory::GetForProfile( |
| chrome_test_utils::GetProfile(this)); |
| #endif |
| // Need the non-default port here. |
| optimization_guide_decider->AddHintForTesting( |
| https_server_.GetURL("www.guitarcenter.com", "/cart.html"), |
| optimization_guide::proto::SHOPPING_PAGE_PREDICTOR, std::nullopt); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCarts(kExpectedExample); |
| #endif |
| SendXHR("/add-to-cart", "product: 123"); |
| |
| WaitForUmaBucketCount("Commerce.Carts.ExtractionTimedOut", 0, 2); |
| } |
| |
| #if !BUILDFLAG(IS_CHROMEOS) |
| // TODO(crbug/1310497): This test is flaky on ChromeOS. |
| class CommerceHintAgentFencedFrameTest : public CommerceHintAgentTest { |
| public: |
| CommerceHintAgentFencedFrameTest() { |
| scoped_feature_list_.InitWithFeatures( |
| { |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule |
| #else |
| commerce::kCommerceHintAndroid |
| #endif |
| }, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| void SetUpInProcessBrowserTestFixture() override {} |
| |
| content::test::FencedFrameTestHelper& fenced_frame_test_helper() { |
| return fenced_frame_helper_; |
| } |
| |
| private: |
| content::test::FencedFrameTestHelper fenced_frame_helper_; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintAgentFencedFrameTest, |
| VisitCartInFencedFrame) { |
| // For add-to-cart by URL, normally a URL in that domain has already been |
| // committed. |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| |
| // Create a fenced frame. |
| GURL fenced_frame_url = |
| https_server_.GetURL("www.guitarcenter.com", "/cart.html"); |
| content::RenderFrameHost* fenced_frame_host = |
| fenced_frame_test_helper().CreateFencedFrame( |
| web_contents()->GetPrimaryMainFrame(), fenced_frame_url); |
| EXPECT_NE(nullptr, fenced_frame_host); |
| |
| // Do not affect counts. |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| } |
| |
| #endif // !BUILDFLAG(IS_CHROMEOS) |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| class CommerceHintFeatureDefaultWithoutGeoTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {}, {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintFeatureDefaultWithoutGeoTest, |
| DisableWithoutGeo) { |
| ASSERT_FALSE(IsCartModuleEnabled()); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| |
| WaitForUmaCount("Commerce.Carts.VisitCart", 0); |
| WaitForCartCount(kEmptyExpected); |
| } |
| |
| class CommerceHintFeatureDefaultWithGeoTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {}, {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| CommerceHintAgentTest::SetUpCommandLine(command_line); |
| command_line->AppendSwitchASCII( |
| variations::switches::kVariationsOverrideCountry, "us"); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintFeatureDefaultWithGeoTest, EnableWithGeo) { |
| auto locale = std::make_unique<ScopedBrowserLocale>("en-US"); |
| |
| #if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) |
| ASSERT_TRUE(IsCartModuleEnabled()); |
| |
| NavigateToURL("https://www.guitarcenter.com/cart.html"); |
| WaitForUmaCount("Commerce.Carts.VisitCart", 1); |
| #else |
| ASSERT_FALSE(IsCartModuleEnabled()); |
| |
| WaitForUmaCount("Commerce.Carts.VisitCart", 0); |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| } |
| #endif |
| |
| class CommerceHintDOMBasedHeuristicsTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {}}, |
| {commerce::kChromeCartDomBasedHeuristics, |
| {{"add-to-cart-button-active-time", "2s"}, |
| {"heuristics-execution-gap-time", "0s"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintDOMBasedHeuristicsTest, |
| DetectionRequiresAddToCartActive) { |
| NavigateToURL("https://www.guitarcenter.com/product-page.html"); |
| // AddToCart requests without active AddToCart button focus. |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 0); |
| |
| // Focus on an AddToCart button and then send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonOne\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 1); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintDOMBasedHeuristicsTest, |
| DetectionInactiveForWrongButton) { |
| NavigateToURL("https://www.guitarcenter.com/product-page.html"); |
| |
| // Focus on a non-AddToCart button and then send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonTwo\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 0); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 1); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 1); |
| |
| // Focus on an AddToCart button and then send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonOne\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 2); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintDOMBasedHeuristicsTest, |
| AddToCartActiveExpires) { |
| NavigateToURL("https://www.guitarcenter.com/product-page.html"); |
| |
| // Focus on an AddToCart button, but wait until it's no longer active and then |
| // send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonOne\")")); |
| base::PlatformThread::Sleep(base::Seconds(2)); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 0); |
| |
| // Focus on an AddToCart button and then send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonTwo\")")); |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonOne\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 3); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 3); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintDOMBasedHeuristicsTest, |
| TestAddToCartPattern) { |
| NavigateToURL("https://www.guitarcenter.com/product-page.html"); |
| // Focus on an AddToCart button and then send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonOne\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "{\"sku\": \"123\", \"quantity\":1"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 1); |
| } |
| |
| class CommerceHintDOMBasedHeuristicsSkipTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {}}, |
| {commerce::kChromeCartDomBasedHeuristics, |
| {{"skip-heuristics-domain-pattern", "guitarcenter"}, |
| {"add-to-cart-button-active-time", "0s"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintDOMBasedHeuristicsSkipTest, |
| SkipDOMBasedHeuristics) { |
| // Send AddToCart requests on a skipped domain without active AddToCart |
| // button. |
| NavigateToURL("https://www.guitarcenter.com/product-page.html"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 1); |
| |
| // Send AddToCart requests on a skipped domain after focusing on a invalid |
| // AddToCart button. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonTwo\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 2); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 0); |
| |
| // Send AddToCart requests on a non-skipped domain without active AddToCart |
| // button. |
| NavigateToURL("https://www.example.com/product-page.html"); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kExpectedExampleFallbackCart); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 2); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 0); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 0); |
| } |
| |
| class CommerceHintDOMBasedHeuristicsGapTimeTest : public CommerceHintAgentTest { |
| public: |
| void SetUpInProcessBrowserTestFixture() override { |
| scoped_feature_list_.InitWithFeaturesAndParameters( |
| {{ |
| #if !BUILDFLAG(IS_ANDROID) |
| ntp_features::kNtpChromeCartModule, |
| #else |
| commerce::kCommerceHintAndroid, |
| #endif |
| {}}, |
| {commerce::kChromeCartDomBasedHeuristics, |
| {{"add-to-cart-button-active-time", "2s"}, |
| {"heuristics-execution-gap-time", "100s"}}}}, |
| {optimization_guide::features::kOptimizationHints}); |
| } |
| |
| private: |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(CommerceHintDOMBasedHeuristicsGapTimeTest, |
| EnforceExecutionTimeGap) { |
| NavigateToURL("https://www.guitarcenter.com/product-page.html"); |
| |
| // Focus on a non-AddToCart button and then send AddToCart requests. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonTwo\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 0); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 1); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 1); |
| |
| // Focus on an AddToCart button and then send AddToCart requests. Since the |
| // gap time is shorter than the threshold, this focus event will be ignored. |
| EXPECT_EQ(nullptr, |
| content::EvalJs(web_contents(), "focusElement(\"buttonOne\")")); |
| SendXHR("/wp-admin/admin-ajax.php", "action: woocommerce_add_to_cart"); |
| |
| #if !BUILDFLAG(IS_ANDROID) |
| WaitForCartCount(kEmptyExpected); |
| #endif |
| WaitForUmaCount("Commerce.Carts.AddToCartByPOST", 0); |
| WaitForUmaCount("Commerce.Carts.AddToCartButtonDetection", 1); |
| ExpectUKMCount(AddToCartEntry::kEntryName, "HeuristicsExecutionTime", 1); |
| } |
| |
| } // namespace |