Bug 1845760 - Mock Shopping analysis and recommendations for automated testing r=geckoview-reviewers,owlish

Differential Revision: https://phabricator.services.mozilla.com/D193961
This commit is contained in:
Cathy Lu 2023-11-20 20:46:16 +00:00
parent ef501dfcdb
commit 65ceaadd8e
5 changed files with 304 additions and 228 deletions

View file

@ -271,9 +271,8 @@ pref("formhelper.autozoom", true);
pref("geckoview.console.enabled", false);
#ifdef NIGHTLY_BUILD
// Testing flag for geckoview shopping product. When true, sendAttributionEvent
// will return "TEST_AID_RESPONSE" for products with "TEST_AID" id.
pref("geckoview.shopping.test_response", false);
// Used for mocking data for GeckoView shopping tests, should use in addition with an automation check.
pref("geckoview.shopping.mock_test_response", false);
#endif
pref("image.cache.size", 1048576); // bytes

View file

@ -11,9 +11,6 @@ import android.view.Surface
import androidx.annotation.AnyThread
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports
import org.json.JSONObject
import org.junit.Assume.assumeThat
@ -26,8 +23,6 @@ import org.mozilla.geckoview.GeckoSession.ContentDelegate
import org.mozilla.geckoview.GeckoSession.NavigationDelegate
import org.mozilla.geckoview.GeckoSession.NavigationDelegate.LoadRequest
import org.mozilla.geckoview.GeckoSession.ProgressDelegate
import org.mozilla.geckoview.GeckoSession.Recommendation
import org.mozilla.geckoview.GeckoSession.ReviewAnalysis
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
@ -662,222 +657,4 @@ class ContentDelegateTest : BaseSessionTest() {
equalTo(true),
)
}
@Test
fun onProductUrl() {
mainSession.loadUri("example.com")
sessionRule.waitForPageStop()
mainSession.loadUri("example.com/dp/ABCDEFG")
sessionRule.waitForPageStop()
// test below working product urls
mainSession.loadUri("example.com/dp/ABCDEFG123")
sessionRule.waitForPageStop()
mainSession.loadUri("example.com/dp/HIJKLMN456")
sessionRule.waitForPageStop()
mainSession.loadUri("example.com/dp/OPQRSTU789")
sessionRule.waitForPageStop()
mainSession.delegateUntilTestEnd(object : ContentDelegate {
@AssertCalled(count = 3)
override fun onProductUrl(session: GeckoSession) {}
})
}
@Test
fun requestCreateAnalysisAndStatus() {
if (!sessionRule.env.isAutomation) {
val result = mainSession.requestCreateAnalysis("https://www.amazon.com/Furmax-Electric-Adjustable-Standing-Computer/dp/B09TJGHL5F/")
assertThat("Analysis status is not null", sessionRule.waitForResult(result), notNullValue())
val status = mainSession.requestAnalysisCreationStatus("https://www.amazon.com/Furmax-Electric-Adjustable-Standing-Computer/dp/B09TJGHL5F/")
assertThat("Analysis status is not null", sessionRule.waitForResult(status), notNullValue())
}
}
@Test
fun requestAnalysis() {
// Test for the builder constructor
val productId = "banana"
val grade = "A"
val adjustedRating = 4.5
val lastAnalysisTime = 12345.toLong()
val analysisURL = "https://analysis.com"
val analysisObject = ReviewAnalysis.Builder(productId)
.grade(grade)
.adjustedRating(adjustedRating)
.analysisUrl(analysisURL)
.needsAnalysis(true)
.pageNotSupported(false)
.notEnoughReviews(false)
.highlights(null)
.lastAnalysisTime(lastAnalysisTime)
.deletedProductReported(true)
.deletedProduct(true)
.build()
assertThat("Product grade should match", analysisObject.grade, equalTo(grade))
assertThat("Product id should match", analysisObject.productId, equalTo(productId))
assertThat("Product adjusted rating should match", analysisObject.adjustedRating, equalTo(adjustedRating))
assertTrue("Product should not be reported that it was deleted", analysisObject.deletedProductReported)
assertTrue("Not a deleted product", analysisObject.deletedProduct)
assertThat("Analysis URL should match", analysisObject.analysisURL, equalTo(analysisURL))
assertTrue("NeedsAnalysis should match", analysisObject.needsAnalysis)
assertFalse("PageNotSupported should match", analysisObject.pageNotSupported)
assertFalse("NotEnoughReviews should match", analysisObject.notEnoughReviews)
assertNull("Highlights should match", analysisObject.highlights)
assertThat("Last analysis time should match", analysisObject.lastAnalysisTime, equalTo(lastAnalysisTime))
// TODO: bug1845760 replace with static example.com product page and enable in automation
if (!sessionRule.env.isAutomation) {
// verify a non product page
val nonProductPageResult = mainSession.requestAnalysis("https://www.example.com/").accept {
assertTrue("Should not return analysis", false)
}
try {
sessionRule.waitForResult(nonProductPageResult)
} catch (e: Exception) {
assertTrue("Should have an exception", true)
}
// verify product with no analysis data
val noAnalysisResult = mainSession.requestAnalysis("https://www.amazon.com/Philips-LED-Aluminum-Resistant-Certified/dp/B0BRQS99T2")
sessionRule.waitForResult(noAnalysisResult).let {
assertThat("Product grade should match", it.grade, equalTo(null))
assertThat("Product id should match", it.productId, equalTo(null))
assertThat("Product adjusted rating should match", it.adjustedRating, equalTo(null))
assertThat("Product highlights should match", it.highlights, equalTo(null))
assertThat("Product pageNotSupported should match", it.pageNotSupported, equalTo(false))
assertThat("Product notEnoughReviews should match", it.notEnoughReviews, equalTo(false))
}
// verify product with integer adjusted rating
val resultIntAdjustedRating = mainSession.requestAnalysis("https://www.amazon.com/dp/B084BZZW9J")
sessionRule.waitForResult(resultIntAdjustedRating).let {
assertThat("Product grade should match", it.grade, equalTo("A"))
assertThat("Product id should match", it.productId, equalTo("B084BZZW9J"))
assertThat("Product adjusted rating should match", it.adjustedRating, equalTo(4.0))
}
// verify unsupported product page
val resultNotSupported = mainSession.requestAnalysis("https://www.amazon.com/dp/B07FYYKKQK")
sessionRule.waitForResult(resultNotSupported).let {
assertThat("Product id should match", it.productId, equalTo("B07FYYKKQK"))
assertThat("Product pageNotSupported should match", it.pageNotSupported, equalTo(true))
}
val result = mainSession.requestAnalysis("https://www.amazon.com/Furmax-Electric-Adjustable-Standing-Computer/dp/B09TJGHL5F/")
sessionRule.waitForResult(result).let {
assertThat("Product grade should match", it.grade, equalTo("B"))
assertThat("Product id should match", it.productId, equalTo("B09TJGHL5F"))
assertThat("Product adjusted rating should match", it.adjustedRating, equalTo(4.5))
assertThat("Product should not be reported that it was deleted", it.deletedProductReported, equalTo(false))
assertThat("Not a deleted product", it.deletedProduct, equalTo(false))
}
}
}
@Test
fun requestRecommendations() {
// Test the Builder constructor
val recommendationUrl = "https://recommendation.com"
val adjustedRating = 3.5
val imageUrl = "http://image.com"
val aid = "banana"
val name = "apple"
val grade = "C"
val price = "450"
val currency = "USD"
val recommendationObject = Recommendation.Builder(recommendationUrl)
.adjustedRating(adjustedRating)
.sponsored(true)
.imageUrl(imageUrl)
.aid(aid)
.name(name)
.grade(grade)
.price(price)
.currency(currency)
.build()
assertThat("Recommendation URL should match", recommendationObject.url, equalTo(recommendationUrl))
assertThat("Adjusted rating should match", recommendationObject.adjustedRating, equalTo(adjustedRating))
assertThat("Recommendation sponsored field should match", recommendationObject.sponsored, equalTo(true))
assertThat("Image URL should match", recommendationObject.imageUrl, equalTo(imageUrl))
assertThat("Aid should match", recommendationObject.aid, equalTo(aid))
assertThat("Name should match", recommendationObject.name, equalTo(name))
assertThat("Grade should match", recommendationObject.grade, equalTo(grade))
assertThat("Price should match", recommendationObject.price, equalTo(price))
assertThat("Currency should match", recommendationObject.currency, equalTo(currency))
// TODO: bug1845760 replace with static example.com product page
if (!sessionRule.env.isAutomation) {
// verify a non product page
val nonProductPageResult = mainSession.requestRecommendations("https://www.amazon.com/").accept {
assertTrue("Should not return recommendation", false)
}
try {
sessionRule.waitForResult(nonProductPageResult)
} catch (e: Exception) {
assertTrue("Should have an exception", true)
}
// verify product with no recommendations
val noRecResult = mainSession.requestRecommendations("https://www.amazon.com/Travel-Self-Inflatable-Sleeping-Airplane-Adjustable/dp/B0B8NVW9YX")
assertThat("Product recommendations should be empty", sessionRule.waitForResult(noRecResult).size, equalTo(0))
val result = mainSession.requestRecommendations("https://www.amazon.com/Furmax-Electric-Adjustable-Standing-Computer/dp/B09TJGHL5F/")
sessionRule.waitForResult(result)
.let {
assertThat("Recommendation adjusted rating should match", it[0].adjustedRating, equalTo(4.5))
assertThat("Recommendation sponsored field should match", it[0].sponsored, equalTo(true))
}
}
}
@Test
fun sendAttributionEvents() {
// TODO (bug 1861175): enable in automation
if (!sessionRule.env.isAutomation) {
// Checks that the pref value is also consistent with the runtime settings
val originalPrefs = sessionRule.getPrefs(
"geckoview.shopping.test_response",
)
assertThat("Pref is correct", originalPrefs[0] as Boolean, equalTo(false))
val aid = "TEST_AID"
val invalidClickResult = mainSession.sendClickAttributionEvent(aid)
assertThat(
"Click event success result should be false",
sessionRule.waitForResult(invalidClickResult),
equalTo(false),
)
val invalidImpressionResult = mainSession.sendImpressionAttributionEvent(aid)
assertThat(
"Impression event result result should be false",
sessionRule.waitForResult(invalidImpressionResult),
equalTo(false),
)
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.test_response" to true,
),
)
val validClickResult = mainSession.sendClickAttributionEvent(aid)
assertThat(
"Click event success result should be true",
sessionRule.waitForResult(validClickResult),
equalTo(true),
)
val validImpressionResult = mainSession.sendImpressionAttributionEvent(aid)
assertThat(
"Impression event success result should be true",
sessionRule.waitForResult(validImpressionResult),
equalTo(true),
)
}
}
}

View file

@ -0,0 +1,240 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
import junit.framework.TestCase.assertFalse
import junit.framework.TestCase.assertNull
import junit.framework.TestCase.assertTrue
import org.hamcrest.Matchers.* // ktlint-disable no-wildcard-imports
import org.junit.Assume.assumeThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.geckoview.* // ktlint-disable no-wildcard-imports
import org.mozilla.geckoview.GeckoSession.ContentDelegate
import org.mozilla.geckoview.GeckoSession.Recommendation
import org.mozilla.geckoview.GeckoSession.ReviewAnalysis
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
@RunWith(AndroidJUnit4::class)
@MediumTest
class ReviewQualityCheckerTest : BaseSessionTest() {
@Before
fun setup() {
sessionRule.setPrefsUntilTestEnd(
mapOf(
"toolkit.shopping.ohttpRelayURL" to "",
"toolkit.shopping.ohttpConfigURL" to "",
),
)
}
@Test
fun onProductUrl() {
mainSession.loadUri("example.com")
sessionRule.waitForPageStop()
mainSession.loadUri("example.com/dp/ABCDEFG")
sessionRule.waitForPageStop()
// test below working product urls
mainSession.loadUri("example.com/dp/ABCDEFG123")
sessionRule.waitForPageStop()
mainSession.loadUri("example.com/dp/HIJKLMN456")
sessionRule.waitForPageStop()
mainSession.loadUri("example.com/dp/OPQRSTU789")
sessionRule.waitForPageStop()
mainSession.delegateUntilTestEnd(object : ContentDelegate {
@AssertCalled(count = 3)
override fun onProductUrl(session: GeckoSession) {}
})
}
@Test
fun requestAnalysis() {
assumeThat(sessionRule.env.isAutomation, equalTo(true))
// Test for the builder constructor
val productId = "banana"
val grade = "A"
val adjustedRating = 4.5
val lastAnalysisTime = 12345.toLong()
val analysisURL = "https://analysis.com"
val analysisObject = ReviewAnalysis.Builder(productId)
.analysisUrl(analysisURL)
.grade(grade)
.adjustedRating(adjustedRating)
.needsAnalysis(true)
.pageNotSupported(false)
.notEnoughReviews(false)
.highlights(null)
.lastAnalysisTime(lastAnalysisTime)
.deletedProductReported(true)
.deletedProduct(true)
.build()
assertThat("Analysis URL should match", analysisObject.analysisURL, equalTo(analysisURL))
assertThat("Product id should match", analysisObject.productId, equalTo(productId))
assertThat("Product grade should match", analysisObject.grade, equalTo(grade))
assertThat("Product adjusted rating should match", analysisObject.adjustedRating, equalTo(adjustedRating))
assertTrue("NeedsAnalysis should match", analysisObject.needsAnalysis)
assertFalse("PageNotSupported should match", analysisObject.pageNotSupported)
assertFalse("NotEnoughReviews should match", analysisObject.notEnoughReviews)
assertNull("Highlights should match", analysisObject.highlights)
assertTrue("Product should not be reported that it was deleted", analysisObject.deletedProductReported)
assertTrue("Not a deleted product", analysisObject.deletedProduct)
assertThat("Last analysis time should match", analysisObject.lastAnalysisTime, equalTo(lastAnalysisTime))
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.mock_test_response" to false,
),
)
// verify a non product page
val nonProductPageResult = mainSession.requestAnalysis("https://www.example.com/").accept {
assertTrue("Should not return analysis", false)
}
try {
sessionRule.waitForResult(nonProductPageResult)
} catch (e: Exception) {
assertTrue("Should have an exception", true)
}
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.mock_test_response" to true,
),
)
val result = mainSession.requestAnalysis("https://www.example.com/mock")
sessionRule.waitForResult(result).let {
assertThat("Review analysis url should match", it.analysisURL, equalTo("https://www.example.com/mock_analysis_url"))
assertThat("Product id should match", it.productId, equalTo("ABCDEFG123"))
assertThat("Product grade should match", it.grade, equalTo("B"))
assertThat("Product adjusted rating should match", it.adjustedRating, equalTo(4.5))
assertTrue("NeedsAnalysis should match", it.needsAnalysis)
assertTrue("PageNotSupported should match", it.pageNotSupported)
assertTrue("NotEnoughReviews should match", it.notEnoughReviews)
assertNull("Highlights should match", analysisObject.highlights)
assertThat("Last analysis time should match", analysisObject.lastAnalysisTime, equalTo(lastAnalysisTime))
assertTrue("DeletedProductReported should match", it.deletedProductReported)
assertTrue("DeletedProduct should match", it.deletedProduct)
}
}
@Test
fun requestCreateAnalysisAndStatus() {
assumeThat(sessionRule.env.isAutomation, equalTo(true))
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.mock_test_response" to true,
),
)
val result = mainSession.requestCreateAnalysis("https://www.example.com/mock/")
assertThat("Analysis status should match", sessionRule.waitForResult(result), equalTo("pending"))
val status = mainSession.requestAnalysisCreationStatus("https://www.example.com/mock/")
assertThat("Analysis status should match", sessionRule.waitForResult(status), equalTo("in_progress"))
}
@Test
fun requestRecommendations() {
assumeThat(sessionRule.env.isAutomation, equalTo(true))
// Test the Builder constructor
val url = "https://example.com/mock_url"
val adjustedRating = 3.5
val imageUrl = "https://example.com/mock_image_url"
val aid = "mock_aid"
val name = "Mock Product"
val grade = "C"
val price = "450"
val currency = "USD"
val recommendationObject = Recommendation.Builder(url)
.adjustedRating(adjustedRating)
.sponsored(true)
.imageUrl(imageUrl)
.aid(aid)
.name(name)
.grade(grade)
.price(price)
.currency(currency)
.build()
assertThat("Recommendation URL should match", recommendationObject.url, equalTo(url))
assertThat("Adjusted rating should match", recommendationObject.adjustedRating, equalTo(adjustedRating))
assertThat("Recommendation sponsored field should match", recommendationObject.sponsored, equalTo(true))
assertThat("Image URL should match", recommendationObject.imageUrl, equalTo(imageUrl))
assertThat("Aid should match", recommendationObject.aid, equalTo(aid))
assertThat("Name should match", recommendationObject.name, equalTo(name))
assertThat("Grade should match", recommendationObject.grade, equalTo(grade))
assertThat("Price should match", recommendationObject.price, equalTo(price))
assertThat("Currency should match", recommendationObject.currency, equalTo(currency))
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.mock_test_response" to false,
),
)
// verify a non product page
val nonProductPageResult = mainSession.requestRecommendations("https://www.amazon.com/").accept {
assertTrue("Should not return recommendation", false)
}
try {
sessionRule.waitForResult(nonProductPageResult)
} catch (e: Exception) {
assertTrue("Should have an exception", true)
}
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.mock_test_response" to true,
),
)
val result = mainSession.requestRecommendations("https://www.example.com/mock")
sessionRule.waitForResult(result)
.let {
assertThat("Recommendation URL should match", recommendationObject.url, equalTo(url))
assertThat("Adjusted rating should match", recommendationObject.adjustedRating, equalTo(adjustedRating))
assertThat("Recommendation sponsored field should match", recommendationObject.sponsored, equalTo(true))
assertThat("Image URL should match", recommendationObject.imageUrl, equalTo(imageUrl))
assertThat("Aid should match", recommendationObject.aid, equalTo(aid))
assertThat("Name should match", recommendationObject.name, equalTo(name))
assertThat("Grade should match", recommendationObject.grade, equalTo(grade))
assertThat("Price should match", recommendationObject.price, equalTo(price))
assertThat("Currency should match", recommendationObject.currency, equalTo(currency))
}
}
@Test
fun sendAttributionEvents() {
assumeThat(sessionRule.env.isAutomation, equalTo(true))
val aid = "TEST_AID"
sessionRule.setPrefsUntilTestEnd(
mapOf(
"geckoview.shopping.mock_test_response" to true,
),
)
val validClickResult = mainSession.sendClickAttributionEvent(aid)
assertThat(
"Click event success result should be true",
sessionRule.waitForResult(validClickResult),
equalTo(true),
)
val validImpressionResult = mainSession.sendImpressionAttributionEvent(aid)
assertThat(
"Impression event success result should be true",
sessionRule.waitForResult(validImpressionResult),
equalTo(true),
)
}
}

View file

@ -3686,10 +3686,10 @@ public class GeckoSession {
* @param builder A ReviewAnalysis.Builder instance
*/
protected ReviewAnalysis(final @NonNull Builder builder) {
adjustedRating = builder.mAdjustedRating;
analysisURL = builder.mAnalysisUrl;
productId = builder.mProductId;
grade = builder.mGrade;
adjustedRating = builder.mAdjustedRating;
needsAnalysis = builder.mNeedsAnalysis;
pageNotSupported = builder.mPageNotSupported;
notEnoughReviews = builder.mNotEnoughReviews;

View file

@ -377,6 +377,26 @@ export class GeckoViewContent extends GeckoViewModule {
}
async _requestAnalysis(aData, aCallback) {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref("geckoview.shopping.mock_test_response", false)
) {
const analysis = {
analysis_url: "https://www.example.com/mock_analysis_url",
product_id: "ABCDEFG123",
grade: "B",
adjusted_rating: 4.5,
needs_analysis: true,
page_not_supported: true,
not_enough_reviews: true,
highlights: null,
last_analysis_time: 12345,
deleted_product_reported: true,
deleted_product: true,
};
aCallback.onSuccess({ analysis });
return;
}
const url = Services.io.newURI(aData.url);
if (!lazy.isProductURL(url)) {
aCallback.onError(`Cannot requestAnalysis on a non-product url.`);
@ -392,6 +412,14 @@ export class GeckoViewContent extends GeckoViewModule {
}
async _requestCreateAnalysis(aData, aCallback) {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref("geckoview.shopping.mock_test_response", false)
) {
const status = "pending";
aCallback.onSuccess(status);
return;
}
const url = Services.io.newURI(aData.url);
if (!lazy.isProductURL(url)) {
aCallback.onError(`Cannot requestCreateAnalysis on a non-product url.`);
@ -407,6 +435,14 @@ export class GeckoViewContent extends GeckoViewModule {
}
async _requestAnalysisCreationStatus(aData, aCallback) {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref("geckoview.shopping.mock_test_response", false)
) {
const status = "in_progress";
aCallback.onSuccess(status);
return;
}
const url = Services.io.newURI(aData.url);
if (!lazy.isProductURL(url)) {
aCallback.onError(
@ -446,7 +482,10 @@ export class GeckoViewContent extends GeckoViewModule {
async _sendAttributionEvent(aEvent, aData, aCallback) {
let result;
if (Services.prefs.getBoolPref("geckoview.shopping.test_response", true)) {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref("geckoview.shopping.mock_test_response", false)
) {
result = { TEST_AID: "TEST_AID_RESPONSE" };
} else {
result = await lazy.ShoppingProduct.sendAttributionEvent(
@ -463,6 +502,27 @@ export class GeckoViewContent extends GeckoViewModule {
}
async _requestRecommendations(aData, aCallback) {
if (
Cu.isInAutomation &&
Services.prefs.getBoolPref("geckoview.shopping.mock_test_response", false)
) {
const recommendations = [
{
name: "Mock Product",
url: "https://example.com/mock_url",
image_url: "https://example.com/mock_image_url",
price: "450",
currency: "USD",
grade: "C",
adjusted_rating: 3.5,
analysis_url: "https://example.com/mock_analysis_url",
sponsored: true,
aid: "mock_aid",
},
];
aCallback.onSuccess({ recommendations });
return;
}
const url = Services.io.newURI(aData.url);
if (!lazy.isProductURL(url)) {
aCallback.onError(`Cannot requestRecommendations on a non-product url.`);