Merge remote-tracking branch 'origin/fenix/118.0' into iceraven

pull/700/head
akliuxingyuan 8 months ago
commit faaa23a167

@ -1 +1 @@
Subproject commit 7cbe28b680add53fd08a7036c6e6e080036536d8
Subproject commit af760b7d977c0390af7b844a657ff6b56056abf1

@ -7,6 +7,14 @@ cookie-banners:
sections-enabled:
type: json
description: This property provides a lookup table of whether or not the given section should be enabled.
extensions-process:
description: A feature to rollout the extensions process.
hasExposure: true
exposureDescription: ""
variables:
enabled:
type: boolean
description: "If true, the extensions process is enabled."
glean:
description: A feature that provides server-side configurations for Glean metrics (aka Server Knobs).
hasExposure: true
@ -139,6 +147,14 @@ print:
share-print-enabled:
type: boolean
description: "If true, a print button from the share menu is available."
private-browsing:
description: Private Browsing Mode
hasExposure: true
exposureDescription: ""
variables:
felt-privacy-enabled:
type: boolean
description: "if true, enable felt privacy related UI"
re-engagement-notification:
description: A feature that shows the re-engagement notification if the user is inactive.
hasExposure: true

@ -624,11 +624,12 @@ dependencies {
implementation ComponentsDependencies.androidx_annotation
implementation ComponentsDependencies.androidx_compose_ui
implementation ComponentsDependencies.androidx_compose_ui_tooling_preview
implementation ComponentsDependencies.androidx_compose_animation
implementation ComponentsDependencies.androidx_compose_foundation
implementation ComponentsDependencies.androidx_compose_material
implementation FenixDependencies.androidx_legacy
implementation ComponentsDependencies.androidx_biometric
implementation FenixDependencies.androidx_paging
implementation ComponentsDependencies.androidx_paging
implementation ComponentsDependencies.androidx_preferences
implementation ComponentsDependencies.androidx_fragment
implementation FenixDependencies.androidx_navigation_fragment

File diff suppressed because it is too large Load Diff

@ -13,6 +13,7 @@ channels:
- forkRelease
includes:
- onboarding.fml.yaml
- pbm.fml.yaml
import:
- path: ../android-components/components/service/nimbus/messaging.fml.yaml
channel: release
@ -271,6 +272,14 @@ features:
type: Boolean
default: true
extensions-process:
description: A feature to rollout the extensions process.
variables:
enabled:
description: If true, the extensions process is enabled.
type: Boolean
default: false
growth-data:
description: A feature measuring campaign growth data
variables:

@ -23,6 +23,16 @@ features:
primary-button-label: juno_onboarding_default_browser_positive_button
secondary-button-label: juno_onboarding_default_browser_negative_button
add-search-widget:
card-type: add-search-widget
enabled: false
title: juno_onboarding_add_search_widget_title
body: juno_onboarding_add_search_widget_description
image-res: ic_onboarding_search_widget
ordering: 15
primary-button-label: juno_onboarding_add_search_widget_positive_button
secondary-button-label: juno_onboarding_add_search_widget_negative_button
sync-sign-in:
card-type: sync-sign-in
title: juno_onboarding_sign_in_title
@ -59,6 +69,10 @@ objects:
description: The type of the card.
# This should never be defaulted.
default: default-browser
enabled:
type: Boolean
description: If true, this card is shown to the user.
default: true
title:
type: Text
description: The title text displayed to the user.
@ -81,10 +95,6 @@ objects:
description: The resource id of the image to be displayed.
# This should never be defaulted.
default: ic_onboarding_welcome
image-is-illustration:
type: Boolean
description: True if the image type is an illustration.
default: true
ordering:
type: Int
description: Used to sequence the cards.
@ -112,3 +122,5 @@ enums:
description: Allows user to sync with a Firefox account.
notification-permission:
description: Allows user to enable notification permission.
add-search-widget:
description: Allows user to add search widget to homescreen.

@ -0,0 +1,19 @@
---
features:
private-browsing:
description: Private Browsing Mode
variables:
felt-privacy-enabled:
description: if true, enable felt privacy related UI
type: Boolean
default: false
defaults:
- channel: developer
value:
felt-privacy-enabled: true
- channel: nightly
value:
felt-privacy-enabled: false

@ -0,0 +1,17 @@
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
pytest = "*"
pytest-html = "*"
pytest-metadata = "*"
requests = "*"
[dev-packages]
black = "*"
flake8 = "*"
[requires]
python_version = "3.11"

@ -0,0 +1,285 @@
{
"_meta": {
"hash": {
"sha256": "917d5c85bd6545dedfbce6aedbd76bd1516993e65943ecfbf7affbece9a2a0ab"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.11"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.python.org/simple",
"verify_ssl": true
}
]
},
"default": {
"black": {
"hashes": [
"sha256:064101748afa12ad2291c2b91c960be28b817c0c7eaa35bec09cc63aa56493c5",
"sha256:0945e13506be58bf7db93ee5853243eb368ace1c08a24c65ce108986eac65915",
"sha256:11c410f71b876f961d1de77b9699ad19f939094c3a677323f43d7a29855fe326",
"sha256:1c7b8d606e728a41ea1ccbd7264677e494e87cf630e399262ced92d4a8dac940",
"sha256:1d06691f1eb8de91cd1b322f21e3bfc9efe0c7ca1f0e1eb1db44ea367dff656b",
"sha256:3238f2aacf827d18d26db07524e44741233ae09a584273aa059066d644ca7b30",
"sha256:32daa9783106c28815d05b724238e30718f34155653d4d6e125dc7daec8e260c",
"sha256:35d1381d7a22cc5b2be2f72c7dfdae4072a3336060635718cc7e1ede24221d6c",
"sha256:3a150542a204124ed00683f0db1f5cf1c2aaaa9cc3495b7a3b5976fb136090ab",
"sha256:48f9d345675bb7fbc3dd85821b12487e1b9a75242028adad0333ce36ed2a6d27",
"sha256:50cb33cac881766a5cd9913e10ff75b1e8eb71babf4c7104f2e9c52da1fb7de2",
"sha256:562bd3a70495facf56814293149e51aa1be9931567474993c7942ff7d3533961",
"sha256:67de8d0c209eb5b330cce2469503de11bca4085880d62f1628bd9972cc3366b9",
"sha256:6b39abdfb402002b8a7d030ccc85cf5afff64ee90fa4c5aebc531e3ad0175ddb",
"sha256:6f3c333ea1dd6771b2d3777482429864f8e258899f6ff05826c3a4fcc5ce3f70",
"sha256:714290490c18fb0126baa0fca0a54ee795f7502b44177e1ce7624ba1c00f2331",
"sha256:7c3eb7cea23904399866c55826b31c1f55bbcd3890ce22ff70466b907b6775c2",
"sha256:92c543f6854c28a3c7f39f4d9b7694f9a6eb9d3c5e2ece488c327b6e7ea9b266",
"sha256:a6f6886c9869d4daae2d1715ce34a19bbc4b95006d20ed785ca00fa03cba312d",
"sha256:a8a968125d0a6a404842fa1bf0b349a568634f856aa08ffaff40ae0dfa52e7c6",
"sha256:c7ab5790333c448903c4b721b59c0d80b11fe5e9803d8703e84dcb8da56fec1b",
"sha256:e114420bf26b90d4b9daa597351337762b63039752bdf72bf361364c1aa05925",
"sha256:e198cf27888ad6f4ff331ca1c48ffc038848ea9f031a3b40ba36aced7e22f2c8",
"sha256:ec751418022185b0c1bb7d7736e6933d40bbb14c14a0abcf9123d1b159f98dd4",
"sha256:f0bd2f4a58d6666500542b26354978218a9babcdc972722f4bf90779524515f3"
],
"index": "pypi",
"version": "==23.3.0"
},
"certifi": {
"hashes": [
"sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7",
"sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716"
],
"markers": "python_version >= '3.6'",
"version": "==2023.5.7"
},
"charset-normalizer": {
"hashes": [
"sha256:04afa6387e2b282cf78ff3dbce20f0cc071c12dc8f685bd40960cc68644cfea6",
"sha256:04eefcee095f58eaabe6dc3cc2262f3bcd776d2c67005880894f447b3f2cb9c1",
"sha256:0be65ccf618c1e7ac9b849c315cc2e8a8751d9cfdaa43027d4f6624bd587ab7e",
"sha256:0c95f12b74681e9ae127728f7e5409cbbef9cd914d5896ef238cc779b8152373",
"sha256:0ca564606d2caafb0abe6d1b5311c2649e8071eb241b2d64e75a0d0065107e62",
"sha256:10c93628d7497c81686e8e5e557aafa78f230cd9e77dd0c40032ef90c18f2230",
"sha256:11d117e6c63e8f495412d37e7dc2e2fff09c34b2d09dbe2bee3c6229577818be",
"sha256:11d3bcb7be35e7b1bba2c23beedac81ee893ac9871d0ba79effc7fc01167db6c",
"sha256:12a2b561af122e3d94cdb97fe6fb2bb2b82cef0cdca131646fdb940a1eda04f0",
"sha256:12d1a39aa6b8c6f6248bb54550efcc1c38ce0d8096a146638fd4738e42284448",
"sha256:1435ae15108b1cb6fffbcea2af3d468683b7afed0169ad718451f8db5d1aff6f",
"sha256:1c60b9c202d00052183c9be85e5eaf18a4ada0a47d188a83c8f5c5b23252f649",
"sha256:1e8fcdd8f672a1c4fc8d0bd3a2b576b152d2a349782d1eb0f6b8e52e9954731d",
"sha256:20064ead0717cf9a73a6d1e779b23d149b53daf971169289ed2ed43a71e8d3b0",
"sha256:21fa558996782fc226b529fdd2ed7866c2c6ec91cee82735c98a197fae39f706",
"sha256:22908891a380d50738e1f978667536f6c6b526a2064156203d418f4856d6e86a",
"sha256:3160a0fd9754aab7d47f95a6b63ab355388d890163eb03b2d2b87ab0a30cfa59",
"sha256:322102cdf1ab682ecc7d9b1c5eed4ec59657a65e1c146a0da342b78f4112db23",
"sha256:34e0a2f9c370eb95597aae63bf85eb5e96826d81e3dcf88b8886012906f509b5",
"sha256:3573d376454d956553c356df45bb824262c397c6e26ce43e8203c4c540ee0acb",
"sha256:3747443b6a904001473370d7810aa19c3a180ccd52a7157aacc264a5ac79265e",
"sha256:38e812a197bf8e71a59fe55b757a84c1f946d0ac114acafaafaf21667a7e169e",
"sha256:3a06f32c9634a8705f4ca9946d667609f52cf130d5548881401f1eb2c39b1e2c",
"sha256:3a5fc78f9e3f501a1614a98f7c54d3969f3ad9bba8ba3d9b438c3bc5d047dd28",
"sha256:3d9098b479e78c85080c98e1e35ff40b4a31d8953102bb0fd7d1b6f8a2111a3d",
"sha256:3dc5b6a8ecfdc5748a7e429782598e4f17ef378e3e272eeb1340ea57c9109f41",
"sha256:4155b51ae05ed47199dc5b2a4e62abccb274cee6b01da5b895099b61b1982974",
"sha256:49919f8400b5e49e961f320c735388ee686a62327e773fa5b3ce6721f7e785ce",
"sha256:53d0a3fa5f8af98a1e261de6a3943ca631c526635eb5817a87a59d9a57ebf48f",
"sha256:5f008525e02908b20e04707a4f704cd286d94718f48bb33edddc7d7b584dddc1",
"sha256:628c985afb2c7d27a4800bfb609e03985aaecb42f955049957814e0491d4006d",
"sha256:65ed923f84a6844de5fd29726b888e58c62820e0769b76565480e1fdc3d062f8",
"sha256:6734e606355834f13445b6adc38b53c0fd45f1a56a9ba06c2058f86893ae8017",
"sha256:6baf0baf0d5d265fa7944feb9f7451cc316bfe30e8df1a61b1bb08577c554f31",
"sha256:6f4f4668e1831850ebcc2fd0b1cd11721947b6dc7c00bf1c6bd3c929ae14f2c7",
"sha256:6f5c2e7bc8a4bf7c426599765b1bd33217ec84023033672c1e9a8b35eaeaaaf8",
"sha256:6f6c7a8a57e9405cad7485f4c9d3172ae486cfef1344b5ddd8e5239582d7355e",
"sha256:7381c66e0561c5757ffe616af869b916c8b4e42b367ab29fedc98481d1e74e14",
"sha256:73dc03a6a7e30b7edc5b01b601e53e7fc924b04e1835e8e407c12c037e81adbd",
"sha256:74db0052d985cf37fa111828d0dd230776ac99c740e1a758ad99094be4f1803d",
"sha256:75f2568b4189dda1c567339b48cba4ac7384accb9c2a7ed655cd86b04055c795",
"sha256:78cacd03e79d009d95635e7d6ff12c21eb89b894c354bd2b2ed0b4763373693b",
"sha256:80d1543d58bd3d6c271b66abf454d437a438dff01c3e62fdbcd68f2a11310d4b",
"sha256:830d2948a5ec37c386d3170c483063798d7879037492540f10a475e3fd6f244b",
"sha256:891cf9b48776b5c61c700b55a598621fdb7b1e301a550365571e9624f270c203",
"sha256:8f25e17ab3039b05f762b0a55ae0b3632b2e073d9c8fc88e89aca31a6198e88f",
"sha256:9a3267620866c9d17b959a84dd0bd2d45719b817245e49371ead79ed4f710d19",
"sha256:a04f86f41a8916fe45ac5024ec477f41f886b3c435da2d4e3d2709b22ab02af1",
"sha256:aaf53a6cebad0eae578f062c7d462155eada9c172bd8c4d250b8c1d8eb7f916a",
"sha256:abc1185d79f47c0a7aaf7e2412a0eb2c03b724581139193d2d82b3ad8cbb00ac",
"sha256:ac0aa6cd53ab9a31d397f8303f92c42f534693528fafbdb997c82bae6e477ad9",
"sha256:ac3775e3311661d4adace3697a52ac0bab17edd166087d493b52d4f4f553f9f0",
"sha256:b06f0d3bf045158d2fb8837c5785fe9ff9b8c93358be64461a1089f5da983137",
"sha256:b116502087ce8a6b7a5f1814568ccbd0e9f6cfd99948aa59b0e241dc57cf739f",
"sha256:b82fab78e0b1329e183a65260581de4375f619167478dddab510c6c6fb04d9b6",
"sha256:bd7163182133c0c7701b25e604cf1611c0d87712e56e88e7ee5d72deab3e76b5",
"sha256:c36bcbc0d5174a80d6cccf43a0ecaca44e81d25be4b7f90f0ed7bcfbb5a00909",
"sha256:c3af8e0f07399d3176b179f2e2634c3ce9c1301379a6b8c9c9aeecd481da494f",
"sha256:c84132a54c750fda57729d1e2599bb598f5fa0344085dbde5003ba429a4798c0",
"sha256:cb7b2ab0188829593b9de646545175547a70d9a6e2b63bf2cd87a0a391599324",
"sha256:cca4def576f47a09a943666b8f829606bcb17e2bc2d5911a46c8f8da45f56755",
"sha256:cf6511efa4801b9b38dc5546d7547d5b5c6ef4b081c60b23e4d941d0eba9cbeb",
"sha256:d16fd5252f883eb074ca55cb622bc0bee49b979ae4e8639fff6ca3ff44f9f854",
"sha256:d2686f91611f9e17f4548dbf050e75b079bbc2a82be565832bc8ea9047b61c8c",
"sha256:d7fc3fca01da18fbabe4625d64bb612b533533ed10045a2ac3dd194bfa656b60",
"sha256:dd5653e67b149503c68c4018bf07e42eeed6b4e956b24c00ccdf93ac79cdff84",
"sha256:de5695a6f1d8340b12a5d6d4484290ee74d61e467c39ff03b39e30df62cf83a0",
"sha256:e0ac8959c929593fee38da1c2b64ee9778733cdf03c482c9ff1d508b6b593b2b",
"sha256:e1b25e3ad6c909f398df8921780d6a3d120d8c09466720226fc621605b6f92b1",
"sha256:e633940f28c1e913615fd624fcdd72fdba807bf53ea6925d6a588e84e1151531",
"sha256:e89df2958e5159b811af9ff0f92614dabf4ff617c03a4c1c6ff53bf1c399e0e1",
"sha256:ea9f9c6034ea2d93d9147818f17c2a0860d41b71c38b9ce4d55f21b6f9165a11",
"sha256:f645caaf0008bacf349875a974220f1f1da349c5dbe7c4ec93048cdc785a3326",
"sha256:f8303414c7b03f794347ad062c0516cee0e15f7a612abd0ce1e25caf6ceb47df",
"sha256:fca62a8301b605b954ad2e9c3666f9d97f63872aa4efcae5492baca2056b74ab"
],
"markers": "python_full_version >= '3.7.0'",
"version": "==3.1.0"
},
"click": {
"hashes": [
"sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
"sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"
],
"markers": "python_version >= '3.7'",
"version": "==8.1.3"
},
"flake8": {
"hashes": [
"sha256:3833794e27ff64ea4e9cf5d410082a8b97ff1a06c16aa3d2027339cd0f1195c7",
"sha256:c61007e76655af75e6785a931f452915b371dc48f56efd765247c8fe68f2b181"
],
"index": "pypi",
"version": "==6.0.0"
},
"idna": {
"hashes": [
"sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4",
"sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"
],
"markers": "python_version >= '3.5'",
"version": "==3.4"
},
"iniconfig": {
"hashes": [
"sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3",
"sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.0"
},
"mccabe": {
"hashes": [
"sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325",
"sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"
],
"markers": "python_version >= '3.6'",
"version": "==0.7.0"
},
"mypy-extensions": {
"hashes": [
"sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d",
"sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"
],
"markers": "python_version >= '3.5'",
"version": "==1.0.0"
},
"packaging": {
"hashes": [
"sha256:994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
"sha256:a392980d2b6cffa644431898be54b0045151319d1e7ec34f0cfed48767dd334f"
],
"markers": "python_version >= '3.7'",
"version": "==23.1"
},
"pathspec": {
"hashes": [
"sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687",
"sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293"
],
"markers": "python_version >= '3.7'",
"version": "==0.11.1"
},
"platformdirs": {
"hashes": [
"sha256:b0cabcb11063d21a0b261d557acb0a9d2126350e63b70cdf7db6347baea456dc",
"sha256:ca9ed98ce73076ba72e092b23d3c93ea6c4e186b3f1c3dad6edd98ff6ffcca2e"
],
"markers": "python_version >= '3.7'",
"version": "==3.8.0"
},
"pluggy": {
"hashes": [
"sha256:c2fd55a7d7a3863cba1a013e4e2414658b1d07b6bc57b3919e0c63c9abb99849",
"sha256:d12f0c4b579b15f5e054301bb226ee85eeeba08ffec228092f8defbaa3a4c4b3"
],
"markers": "python_version >= '3.7'",
"version": "==1.2.0"
},
"py": {
"hashes": [
"sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719",
"sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==1.11.0"
},
"pycodestyle": {
"hashes": [
"sha256:347187bdb476329d98f695c213d7295a846d1152ff4fe9bacb8a9590b8ee7053",
"sha256:8a4eaf0d0495c7395bdab3589ac2db602797d76207242c17d470186815706610"
],
"markers": "python_version >= '3.6'",
"version": "==2.10.0"
},
"pyflakes": {
"hashes": [
"sha256:ec55bf7fe21fff7f1ad2f7da62363d749e2a470500eab1b555334b67aa1ef8cf",
"sha256:ec8b276a6b60bd80defed25add7e439881c19e64850afd9b346283d4165fd0fd"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.1"
},
"pytest": {
"hashes": [
"sha256:78bf16451a2eb8c7a2ea98e32dc119fd2aa758f1d5d66dbf0a59d69a3969df32",
"sha256:b4bf8c45bd59934ed84001ad51e11b4ee40d40a1229d2c79f9c592b0a3f6bd8a"
],
"index": "pypi",
"version": "==7.4.0"
},
"pytest-html": {
"hashes": [
"sha256:868c08564a68d8b2c26866f1e33178419bb35b1e127c33784a28622eb827f3f3",
"sha256:c4e2f4bb0bffc437f51ad2174a8a3e71df81bbc2f6894604e604af18fbe687c3"
],
"index": "pypi",
"version": "==3.2.0"
},
"pytest-metadata": {
"hashes": [
"sha256:769a9c65d2884bd583bc626b0ace77ad15dbe02dd91a9106d47fd46d9c2569ca",
"sha256:a17b1e40080401dc23177599208c52228df463db191c1a573ccdffacd885e190"
],
"index": "pypi",
"version": "==3.0.0"
},
"requests": {
"hashes": [
"sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f",
"sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"
],
"index": "pypi",
"version": "==2.31.0"
},
"urllib3": {
"hashes": [
"sha256:48e7fafa40319d358848e1bc6809b208340fafe2096f1725d05d67443d0483d1",
"sha256:bee28b5e56addb8226c96f7f13ac28cb4c301dd5ea8a6ca179c0b9835e032825"
],
"markers": "python_version >= '3.7'",
"version": "==2.0.3"
}
},
"develop": {}
}

@ -0,0 +1,50 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.experimentintegration
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
/**
* Tests for verifying functionality of the message survey surface
*/
class SurveyExperimentIntegrationTest {
private val surveyURL = "qsurvey.mozilla.com"
private val experimentName = "Viewpoint"
@get:Rule
val activityTestRule = HomeActivityTestRule()
@Before
fun setUp() {
TestHelper.appContext.settings().showSecretDebugMenuThisSession = true
}
@After
fun tearDown() {
TestHelper.appContext.settings().showSecretDebugMenuThisSession = false
}
@Test
fun checkSurveyNavigatesCorrectly() {
browserScreen {
verifySurveyButton()
}.clickSurveyButton {}
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openExperimentsMenu {
verifyExperimentExists(experimentName)
}
}
}

@ -0,0 +1,138 @@
import json
import os
from pathlib import Path
import subprocess
import time
import pytest
import requests
from experimentintegration.gradlewbuild import GradlewBuild
KLAATU_SERVER_URL = "http://localhost:1378"
KLAATU_LOCAL_SERVER_URL = "http://localhost:1378"
here = Path()
def load_branches():
branches = []
data = requests.get(f"{KLAATU_SERVER_URL}/experiment").json()
for item in reversed(data):
if isinstance(item, dict):
exit()
else:
data = item
break
experiment = requests.get(data).json()
for item in experiment["branches"]:
branches.append(item["slug"])
return branches
@pytest.fixture
def gradlewbuild_log(pytestconfig, tmpdir):
gradlewbuild_log = f"{tmpdir.join('gradlewbuild.log')}"
pytestconfig._gradlewbuild_log = gradlewbuild_log
yield gradlewbuild_log
@pytest.fixture
def gradlewbuild(gradlewbuild_log):
yield GradlewBuild(gradlewbuild_log)
@pytest.fixture(name="experiment_data")
def fixture_experiment_data(experiment_url):
data = requests.get(experiment_url).json()
del(data["branches"][0]["features"][0]["value"]["message-under-experiment"])
for item in data["branches"][0]["features"][0]["value"]["messages"].values():
for count, trigger in enumerate(item["trigger"]):
if "USER_EN_SPEAKER" not in trigger:
del(item["trigger"][count])
return [data]
@pytest.fixture(name="experiment_url", scope="module")
def fixture_experiment_url():
data = requests.get(f"{KLAATU_LOCAL_SERVER_URL}/experiment").json()
url = None
for item in data:
if isinstance(item, dict):
continue
else:
url = item
yield url
return_data = {"url": url}
requests.put(f"{KLAATU_SERVER_URL}/experiment", json=return_data)
@pytest.fixture(name="json_data")
def fixture_json_data(tmp_path, experiment_data):
path = tmp_path / "data"
path.mkdir()
json_path = path / "data.json"
with open(json_path, "w", encoding="utf-8") as f:
# URL of experiment/klaatu server
data = {"data": experiment_data}
json.dump(data, f)
return json_path
@pytest.fixture(name="experiment_slug")
def fixture_experiment_slug(experiment_data):
return experiment_data[0]["slug"]
@pytest.fixture(name="start_app")
def fixture_start_app():
def _():
command = f"nimbus-cli --app fenix --channel developer open"
try:
out = subprocess.check_output(
command,
cwd=os.path.join(here, os.pardir),
stderr=subprocess.STDOUT,
universal_newlines=True,
shell=True,
)
except subprocess.CalledProcessError as e:
out = e.output
raise
finally:
with open(gradlewbuild_log, "w") as f:
f.write(out)
time.sleep(
15
) # Wait a while as there's no real way to know when the app has started
return _
@pytest.fixture(name="send_test_results", autouse=True)
def fixture_send_test_results():
yield
here = Path()
with open(f"{here.resolve()}/results/index.html", "rb") as f:
files = {"file": f}
requests.post(f"{KLAATU_SERVER_URL}/test_results", files=files)
@pytest.fixture(name="setup_experiment", params=load_branches(), autouse=True)
def fixture_setup_experiment(experiment_slug, json_data, gradlewbuild_log, request):
def _():
command = f"nimbus-cli --app fenix --channel developer enroll {experiment_slug} --branch {request.param} --file {json_data} --reset-app"
try:
out = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
out = e.output
raise
finally:
with open(gradlewbuild_log, "w") as f:
f.write(f"{out}")
time.sleep(
15
) # Wait a while as there's no real way to know when the app has started
return _

@ -0,0 +1,45 @@
import logging
import os
import subprocess
from syncintegration.adbrun import ADBrun
here = os.path.dirname(__file__)
logging.getLogger(__name__).addHandler(logging.NullHandler())
class GradlewBuild(object):
binary = "./gradlew"
logger = logging.getLogger()
adbrun = ADBrun()
def __init__(self, log):
self.log = log
def test(self, identifier):
# self.adbrun.launch()
# Change path accordingly to go to root folder to run gradlew
os.chdir("../../../../../../../..")
cmd = f"adb shell am instrument -w -e class org.mozilla.fenix.experimentintegration.{identifier} org.mozilla.fenix.debug.test/androidx.test.runner.AndroidJUnitRunner"
self.logger.info("Running cmd: {}".format(cmd))
out = ""
try:
out = subprocess.check_output(
cmd, encoding="utf8", shell=True, stderr=subprocess.STDOUT
)
if "FAILURES" in out:
raise (AssertionError(out))
except subprocess.CalledProcessError as e:
out = e.output
raise
finally:
# Set the path correctly
tests_path = (
"app/src/androidTest/java/org/mozilla/fenix/experimentintegration/"
)
os.chdir(tests_path)
with open(self.log, "w") as f:
f.write(str(out))

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -e
echo "Waiting emulator is ready..."
~/Library/Android/sdk/emulator/emulator -avd Pixel_3_API_28 -wipe-data -no-boot-anim -screen no-touch &
bootanim=""
failcounter=0
timeout_in_sec=360
until [[ "$bootanim" =~ "stopped" ]]; do
bootanim=`~/Library/Android/sdk/platform-tools/adb -e shell getprop init.svc.bootanim 2>&1 &`
if [[ "$bootanim" =~ "device not found" || "$bootanim" =~ "device offline"
|| "$bootanim" =~ "running" ]]; then
let "failcounter += 1"
echo "Waiting for emulator to start"
if [[ $failcounter -gt timeout_in_sec ]]; then
echo "Timeout ($timeout_in_sec seconds) reached; failed to start emulator"
exit 1
fi
fi
sleep 1
done
echo "Emulator is ready"
sleep 10

@ -0,0 +1,4 @@
[pytest]
addopts = --verbose --html=results/index.html --self-contained-html
log_cli = true
log_cli_level = info

@ -0,0 +1,3 @@
def test_survey_navigates_correctly(setup_experiment, gradlewbuild):
setup_experiment()
gradlewbuild.test("SurveyExperimentIntegrationTest#checkSurveyNavigatesCorrectly")

@ -0,0 +1,62 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.extensions
import android.content.Context
import mozilla.components.concept.engine.EngineSession
import org.json.JSONObject
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mozilla.experiments.nimbus.HardcodedNimbusFeatures
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.gecko.GeckoProvider
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.nimbus.FxNimbus
/**
* Instrumentation test for verifying that the extensions process can be controlled with Nimbus.
*/
class ExtensionProcessTest {
private lateinit var context: Context
private lateinit var policy: EngineSession.TrackingProtectionPolicy
@Before
fun setUp() {
context = TestHelper.appContext
policy =
context.components.core.trackingProtectionPolicyFactory.createTrackingProtectionPolicy()
}
@Test
fun test_extension_process_can_be_controlled_by_nimbus() {
val hardcodedNimbus = HardcodedNimbusFeatures(
context,
"extensions-process" to JSONObject(
"""
{
"enabled":true
}
""".trimIndent(),
),
)
hardcodedNimbus.connectWith(FxNimbus)
val runtime = GeckoProvider.createRuntimeSettings(context, policy)
assertTrue(FxNimbus.features.extensionsProcess.value().enabled)
assertTrue(runtime.extensionsProcessEnabled!!)
}
@Test
fun test_extension_process_must_be_disabled_by_default() {
val runtime = GeckoProvider.createRuntimeSettings(context, policy)
assertFalse(FxNimbus.features.extensionsProcess.value().enabled)
assertFalse(runtime.extensionsProcessEnabled!!)
}
}

@ -34,6 +34,7 @@ import androidx.test.espresso.IdlingRegistry
import androidx.test.espresso.IdlingResource
import androidx.test.espresso.action.ViewActions.longClick
import androidx.test.espresso.assertion.ViewAssertions
import androidx.test.espresso.intent.Intents
import androidx.test.espresso.intent.Intents.intended
import androidx.test.espresso.intent.matcher.IntentMatchers.toPackage
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
@ -115,6 +116,15 @@ object TestHelper {
}
}
fun closeApp(activity: HomeActivityIntentTestRule) =
activity.activity.finishAndRemoveTask()
fun relaunchCleanApp(activity: HomeActivityIntentTestRule) {
closeApp(activity)
Intents.release()
activity.launchActivity(null)
}
fun getPermissionAllowID(): String {
return when
(Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {

@ -1,6 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.onboarding.view
import androidx.compose.ui.layout.ContentScale
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
@ -17,32 +20,51 @@ class JunoOnboardingMapperTest {
HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Test
fun showNotificationTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages() {
fun showNotificationTrue_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutAddWidgetPage() {
val expected = listOf(defaultBrowserPageUiData, syncPageUiData, notificationPageUiData)
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(true))
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(true, false))
}
@Test
fun showNotificationFalse_pagesToDisplay_returnsSortedListOfConvertedPagesWithoutNotificationPage() {
fun showNotificationFalse_showAddWidgetFalse_pagesToDisplay_returnsSortedListOfConvertedPages_withoutNotificationPage_and_addWidgetPage() {
val expected = listOf(defaultBrowserPageUiData, syncPageUiData)
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(false))
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(false, false))
}
@Test
fun showNotificationFalse_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfAllConvertedPages_withoutNotificationPage() {
val expected = listOf(defaultBrowserPageUiData, addSearchWidgetPageUiData, syncPageUiData)
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(false, true))
}
@Test
fun showNotificationTrue_and_showAddWidgetTrue_pagesToDisplay_returnsSortedListOfConvertedPages() {
val expected = listOf(defaultBrowserPageUiData, addSearchWidgetPageUiData, syncPageUiData, notificationPageUiData)
assertEquals(expected, unsortedAllKnownCardData.toPageUiData(true, true))
}
}
private val defaultBrowserPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.DEFAULT_BROWSER,
imageRes = R.drawable.ic_onboarding_welcome,
imageResContentScale = ContentScale.Fit,
title = "default browser title",
description = "default browser body with link text",
linkText = "link text",
primaryButtonLabel = "default browser primary button text",
secondaryButtonLabel = "default browser secondary button text",
)
private val addSearchWidgetPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.ADD_SEARCH_WIDGET,
imageRes = R.drawable.ic_onboarding_search_widget,
title = "add search widget title",
description = "add search widget body with link text",
linkText = "link text",
primaryButtonLabel = "add search widget primary button text",
secondaryButtonLabel = "add search widget secondary button text",
)
private val syncPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.SYNC_SIGN_IN,
imageRes = R.drawable.ic_onboarding_sync,
imageResContentScale = ContentScale.Fit,
title = "sync title",
description = "sync body",
primaryButtonLabel = "sync primary button text",
@ -51,7 +73,6 @@ private val syncPageUiData = OnboardingPageUiData(
private val notificationPageUiData = OnboardingPageUiData(
type = OnboardingPageUiData.Type.NOTIFICATION_PERMISSION,
imageRes = R.drawable.ic_notification_permission,
imageResContentScale = ContentScale.Crop,
title = "notification title",
description = "notification body",
primaryButtonLabel = "notification primary button text",
@ -61,7 +82,6 @@ private val notificationPageUiData = OnboardingPageUiData(
private val defaultBrowserCardData = OnboardingCardData(
cardType = OnboardingCardType.DEFAULT_BROWSER,
imageRes = R.drawable.ic_onboarding_welcome,
imageIsIllustration = true,
title = StringHolder(null, "default browser title"),
body = StringHolder(null, "default browser body with link text"),
linkText = StringHolder(null, "link text"),
@ -69,10 +89,19 @@ private val defaultBrowserCardData = OnboardingCardData(
secondaryButtonLabel = StringHolder(null, "default browser secondary button text"),
ordering = 10,
)
private val addSearchWidgetCardData = OnboardingCardData(
cardType = OnboardingCardType.ADD_SEARCH_WIDGET,
imageRes = R.drawable.ic_onboarding_search_widget,
title = StringHolder(null, "add search widget title"),
body = StringHolder(null, "add search widget body with link text"),
linkText = StringHolder(null, "link text"),
primaryButtonLabel = StringHolder(null, "add search widget primary button text"),
secondaryButtonLabel = StringHolder(null, "add search widget secondary button text"),
ordering = 15,
)
private val syncCardData = OnboardingCardData(
cardType = OnboardingCardType.SYNC_SIGN_IN,
imageRes = R.drawable.ic_onboarding_sync,
imageIsIllustration = true,
title = StringHolder(null, "sync title"),
body = StringHolder(null, "sync body"),
primaryButtonLabel = StringHolder(null, "sync primary button text"),
@ -82,7 +111,6 @@ private val syncCardData = OnboardingCardData(
private val notificationCardData = OnboardingCardData(
cardType = OnboardingCardType.NOTIFICATION_PERMISSION,
imageRes = R.drawable.ic_notification_permission,
imageIsIllustration = false,
title = StringHolder(null, "notification title"),
body = StringHolder(null, "notification body"),
primaryButtonLabel = StringHolder(null, "notification primary button text"),
@ -94,4 +122,5 @@ private val unsortedAllKnownCardData = listOf(
syncCardData,
notificationCardData,
defaultBrowserCardData,
addSearchWidgetCardData,
)

@ -33,7 +33,7 @@ import org.mozilla.fenix.helpers.HomeActivityTestRule
*
* Say no to main thread IO! 🙅
*/
private const val EXPECTED_SUPPRESSION_COUNT = 19
private const val EXPECTED_SUPPRESSION_COUNT = 16
/**
* The number of times we call the `runBlocking` coroutine method on the main thread during this

@ -43,7 +43,7 @@ class DefaultHomeScreenTest : ScreenshotTest() {
@Test
fun showDefaultHomeScreen() {
homeScreen {
verifyAccountsSignInButton()
verifyHomeScreen()
Screengrab.screenshot("HomeScreenRobot_home-screen-scroll")
TestAssetHelper.waitingTime
}

@ -22,6 +22,7 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
@ -80,6 +81,7 @@ class SyncIntegrationTest {
bookmarkAfterSyncIsShown()
}
@SmokeTest
@Test
fun checkAccountSettings() {
signInFxSync()

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.core.net.toUri

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer

@ -14,21 +14,25 @@ import mozilla.appservices.places.BookmarkRoot
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.bookmarkStorage
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createBookmarkItem
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appContext
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.registerAndCleanupIdlingResources
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.ui.robots.bookmarksMenu
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
@ -289,6 +293,7 @@ class BookmarksTest {
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun openAllInTabsTest() {
val webPages = listOf(
@ -331,6 +336,7 @@ class BookmarksTest {
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun openAllInPrivateTabsTest() {
val webPages = listOf(
@ -749,8 +755,8 @@ class BookmarksTest {
fun verifySearchBookmarksViewTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
browserScreen {
createBookmark(defaultWebPage.url)
createBookmarkItem(defaultWebPage.url.toString(), defaultWebPage.title, 1u)
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
}.clickSearchButton {
@ -763,17 +769,14 @@ class BookmarksTest {
tapOutsideToDismissSearchBar()
verifySearchToolbar(false)
}
bookmarksMenu {
}.goBackToBrowserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openCustomizeSubMenu {
clickTopToolbarToggle()
}
exitMenu()
runBlocking {
// Switching to top toolbar position
appContext.settings().shouldUseBottomToolbar = false
restartApp(activityTestRule.activityRule)
}
browserScreen {
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
}.clickSearchButton {
@ -790,28 +793,27 @@ class BookmarksTest {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
createFolder(bookmarksFolderName)
}
exitMenu()
createBookmarkItem(firstWebPage.url.toString(), firstWebPage.title, 1u)
createBookmarkItem(secondWebPage.url.toString(), secondWebPage.title, 2u)
browserScreen {
createBookmark(firstWebPage.url, bookmarksFolderName)
createBookmark(secondWebPage.url)
homeScreen {
}.openThreeDotMenu {
}.openBookmarks {
}.clickSearchButton {
// Search for a valid term
typeSearch(firstWebPage.title)
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString(), searchTerm = firstWebPage.title)
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
}.dismissSearchBar {}
bookmarksMenu {
}.clickSearchButton {
// Search for invalid term
typeSearch("Android")
verifyNoSuggestionsAreDisplayed(activityTestRule, firstWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(
activityTestRule,
firstWebPage.url.toString(),
secondWebPage.url.toString(),
)
}
}
@ -853,7 +855,7 @@ class BookmarksTest {
typeSearch("generic")
verifyNoSuggestionsAreDisplayed(activityTestRule, firstWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString(), searchTerm = "generic")
pressBack()
}
bookmarksMenu {

@ -809,7 +809,7 @@ class ComposeBookmarksTest {
}.clickSearchButton {
// Search for a valid term
typeSearch(firstWebPage.title)
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString(), searchTerm = firstWebPage.title)
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
// Search for invalid term
typeSearch("Android")
@ -856,7 +856,7 @@ class ComposeBookmarksTest {
typeSearch("generic")
verifyNoSuggestionsAreDisplayed(activityTestRule, firstWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString(), searchTerm = "generic")
pressBack()
}
bookmarksMenu {

@ -13,7 +13,6 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -75,7 +74,6 @@ class ComposeContextMenusTest {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun verifyContextOpenLinkNewTab() {
val pageLinks =
@ -99,7 +97,6 @@ class ComposeContextMenusTest {
}
}
@SmokeTest
@Test
fun verifyContextOpenLinkPrivateTab() {
val pageLinks =

@ -408,7 +408,7 @@ class ComposeHistoryTest {
}.clickSearchButton {
// Search for a valid term
typeSearch(firstWebPage.title)
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString(), searchTerm = firstWebPage.title)
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
clickClearButton()
// Search for invalid term
@ -447,7 +447,7 @@ class ComposeHistoryTest {
typeSearch("generic")
verifyNoSuggestionsAreDisplayed(activityTestRule, firstWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString(), searchTerm = "generic")
pressBack()
}
historyMenu {

@ -13,8 +13,8 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.POCKET_RECOMMENDED_STORIES_UTM_PARAM
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
@ -31,7 +31,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
class ComposeHomeScreenTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private lateinit var firstPocketStoryPublisher: String
@get:Rule(order = 0)
val activityTestRule =
@ -60,6 +59,7 @@ class ComposeHomeScreenTest {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/235396
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1844580")
@Test
fun homeScreenItemsTest() {
@ -83,8 +83,9 @@ class ComposeHomeScreenTest {
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/244199
@Test
fun privateModeScreenItemsTest() {
fun privateBrowsingHomeScreenItemsTest() {
homeScreen { }.dismissOnboarding()
homeScreen { }.togglePrivateBrowsingMode()
@ -95,6 +96,8 @@ class ComposeHomeScreenTest {
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1364362
@SmokeTest
@Test
fun verifyJumpBackInSectionTest() {
activityTestRule.activityRule.applySettingsExceptions {
@ -145,111 +148,25 @@ class ComposeHomeScreenTest {
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1844580")
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1569867
@Test
fun verifyPocketHomepageStoriesTest() {
fun verifyJumpBackInContextualHintTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
it.isJumpBackInCFREnabled = true
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyThoughtProvokingStories(true)
scrollToPocketProvokingStories()
verifyPocketRecommendedStoriesItems()
// Sponsored Pocket stories are only advertised for a limited time.
// See also known issue https://bugzilla.mozilla.org/show_bug.cgi?id=1828629
// verifyPocketSponsoredStoriesItems(2, 8)
verifyDiscoverMoreStoriesButton()
verifyStoriesByTopic(true)
verifyPoweredByPocket()
}.openThreeDotMenu {
}.openCustomizeHome {
clickPocketButton()
}.goBackToHomeScreen {
verifyThoughtProvokingStories(false)
verifyStoriesByTopic(false)
}
}
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1844580")
@Test
fun openPocketStoryItemTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyThoughtProvokingStories(true)
scrollToPocketProvokingStories()
firstPocketStoryPublisher = getProvokingStoryPublisher(1)
}.clickPocketStoryItem(firstPocketStoryPublisher, 1) {
verifyUrl(POCKET_RECOMMENDED_STORIES_UTM_PARAM)
}
}
@Test
fun openPocketDiscoverMoreTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
scrollToPocketProvokingStories()
verifyDiscoverMoreStoriesButton()
}.clickPocketDiscoverMoreButton {
verifyUrl("getpocket.com/explore")
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1844580")
@Test
fun selectStoriesByTopicItemTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyStoriesByTopicItemState(activityTestRule, false, 1)
clickStoriesByTopicItem(activityTestRule, 1)
verifyStoriesByTopicItemState(activityTestRule, true, 1)
}
}
@Test
fun verifyPocketLearnMoreLinkTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyPoweredByPocket()
}.clickPocketLearnMoreLink(activityTestRule) {
verifyUrl("mozilla.org/en-US/firefox/pocket")
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.goToHomescreen {
verifyJumpBackInMessage(activityTestRule)
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1569839
@Test
fun verifyCustomizeHomepageTest() {
fun verifyCustomizeHomepageButtonTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule

@ -224,6 +224,7 @@ class ComposeNavigationToolbarTest {
}
}
@SmokeTest
@Test
fun verifySecurePageSecuritySubMenuTest() {
val defaultWebPage = "https://mozilla-mobile.github.io/testapp/loginForm"
@ -238,6 +239,7 @@ class ComposeNavigationToolbarTest {
}
}
@SmokeTest
@Test
fun verifyInsecurePageSecuritySubMenuTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -20,11 +20,14 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createBookmarkItem
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createTabItem
import org.mozilla.fenix.helpers.MockBrowserDataHelper.setCustomSearchEngine
import org.mozilla.fenix.helpers.SearchDispatcher
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.verifyKeyboardVisibility
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.homeScreen
@ -618,4 +621,164 @@ class ComposeSearchTest {
}
}
}
// Test that verifies the Firefox Suggest results in a general search context
@Test
fun firefoxSuggestHeaderForBrowsingDataSuggestionsTest() {
val firstPage = TestAssetHelper.getGenericAsset(searchMockServer, 1)
val secondPage = TestAssetHelper.getGenericAsset(searchMockServer, 2)
createTabItem(firstPage.url.toString())
createBookmarkItem(secondPage.url.toString(), secondPage.title, 1u)
homeScreen {
}.openSearch {
typeSearch("generic")
verifySearchEngineSuggestionResults(
rule = activityTestRule,
searchSuggestions = arrayOf(
"Firefox Suggest",
firstPage.url.toString(),
secondPage.url.toString(),
),
searchTerm = "generic",
)
}
}
@Test
fun verifySearchTabsItemsTest() {
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod("Tabs")
verifyKeyboardVisibility(isExpectedToBeVisible = true)
verifyScanButtonVisibility(visible = false)
verifyVoiceSearchButtonVisibility(enabled = true)
verifySearchBarPlaceholder(text = "Search tabs")
}
}
@Test
fun verifySearchTabsWithoutOpenTabsTest() {
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod(searchEngineName = "Tabs")
typeSearch(searchTerm = "Mozilla")
verifyNoSuggestionsAreDisplayed(rule = activityTestRule, "Mozilla")
clickClearButton()
verifySearchBarPlaceholder("Search tabs")
}
}
@SmokeTest
@Test
fun verifySearchTabsWithOpenTabsTest() {
val firstPageUrl = TestAssetHelper.getGenericAsset(searchMockServer, 1)
val secondPageUrl = TestAssetHelper.getGenericAsset(searchMockServer, 2)
createTabItem(firstPageUrl.url.toString())
createTabItem(secondPageUrl.url.toString())
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod(searchEngineName = "Tabs")
typeSearch(searchTerm = "Mozilla")
verifyNoSuggestionsAreDisplayed(rule = activityTestRule, "Mozilla")
clickClearButton()
typeSearch(searchTerm = "generic")
verifyTypedToolbarText("generic")
verifySearchEngineSuggestionResults(
rule = activityTestRule,
searchSuggestions = arrayOf(
"Firefox Suggest",
firstPageUrl.url.toString(),
secondPageUrl.url.toString(),
),
searchTerm = "generic",
)
}.clickSearchSuggestion(firstPageUrl.url.toString()) {
verifyTabCounter("2")
}.openComposeTabDrawer(activityTestRule) {
verifyOpenTabsOrder(position = 1, title = firstPageUrl.url.toString())
verifyOpenTabsOrder(position = 2, title = secondPageUrl.url.toString())
}
}
@Test
fun verifySearchForBookmarksUITest() {
navigationToolbar {
}.clickSearchSelectorButton {
selectTemporarySearchMethod("Bookmarks")
verifySearchBarPlaceholder("Search bookmarks")
verifyKeyboardVisibility(isExpectedToBeVisible = true)
verifyScanButtonVisibility(visible = false)
verifyVoiceSearchButtonVisibility(enabled = true)
}
}
@Test
fun bookmarkSearchWithNoBookmarksTest() {
navigationToolbar {
}.clickSearchSelectorButton {
selectTemporarySearchMethod("Bookmarks")
typeSearch("test")
verifyNoSuggestionsAreDisplayed(activityTestRule, "test")
}
}
@Test
fun bookmarkSearchWhenBookmarksExistTest() {
createBookmarkItem(url = "https://bookmarktest1.com", title = "Test1", position = 1u)
createBookmarkItem(url = "https://bookmarktest2.com", title = "Test2", position = 2u)
navigationToolbar {
}.clickSearchSelectorButton {
selectTemporarySearchMethod("Bookmarks")
typeSearch("test")
verifySearchEngineSuggestionResults(
activityTestRule,
searchSuggestions = arrayOf(
"Firefox Suggest",
"Test1",
"https://bookmarktest1.com/",
"Test2",
"https://bookmarktest2.com/",
),
searchTerm = "test",
)
}.dismissSearchBar {
}.openSearch {
typeSearch("mozilla ")
verifyNoSuggestionsAreDisplayed(activityTestRule, "Test1", "Test2")
}
}
@Test
fun verifySearchHistoryItemsTest() {
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod("History")
verifyKeyboardVisibility(isExpectedToBeVisible = true)
verifyScanButtonVisibility(visible = false)
verifyVoiceSearchButtonVisibility(enabled = true)
verifySearchBarPlaceholder(text = "Search history")
}
}
@Test
fun verifySearchHistoryWithoutBrowsingDataTest() {
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod(searchEngineName = "History")
typeSearch(searchTerm = "Mozilla")
verifyNoSuggestionsAreDisplayed(rule = activityTestRule, "Mozilla")
clickClearButton()
verifySearchBarPlaceholder("Search history")
}
}
}

@ -120,7 +120,35 @@ class ComposeSettingsDeleteBrowsingDataOnQuitTest {
}
@Test
fun deleteHistoryAndSiteStorageOnQuitTest() {
fun deleteHistoryOnQuitTest() {
val genericPage =
getStorageTestAsset(mockWebServer, "generic1.html")
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingDataOnQuit {
clickDeleteBrowsingOnQuitButtonSwitch()
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.goToHomescreen {
}.openThreeDotMenu {
clickQuit()
restartApp(composeTestRule.activityRule)
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()
exitMenu()
}
}
@Test
fun deleteCookiesAndSiteDataOnQuitTest() {
val storageWritePage =
getStorageTestAsset(mockWebServer, "storage_write.html")
val storageCheckPage =
@ -143,12 +171,6 @@ class ComposeSettingsDeleteBrowsingDataOnQuitTest {
restartApp(composeTestRule.activityRule)
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage.url) {
verifyPageContent("Session storage empty")

@ -167,20 +167,15 @@ class ComposeSettingsDeleteBrowsingDataTest {
@SmokeTest
@Test
fun deleteBrowsingHistoryAndSiteDataTest() {
val storageWritePage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageCheckPage = getStorageTestAsset(mockWebServer, "storage_check.html").url
fun deleteBrowsingHistoryTest() {
val genericPage = getStorageTestAsset(mockWebServer, "generic1.html").url
navigationToolbar {
}.enterURLAndEnterToBrowser(storageWritePage) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage has value")
verifyPageContent("Local storage has value")
}.enterURLAndEnterToBrowser(genericPage) {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyBrowsingHistoryDetails("2")
verifyBrowsingHistoryDetails("1")
selectOnlyBrowsingHistoryCheckBox()
clickDeleteBrowsingDataButton()
verifyDeleteBrowsingDataDialog()
@ -198,27 +193,27 @@ class ComposeSettingsDeleteBrowsingDataTest {
verifyEmptyHistoryView()
mDevice.pressBack()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage empty")
verifyPageContent("Local storage empty")
}
}
@SmokeTest
@Test
fun deleteCookiesTest() {
fun deleteCookiesAndSiteDataTest() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val cookiesTestPage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageWritePage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageCheckPage = getStorageTestAsset(mockWebServer, "storage_check.html").url
// Browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(cookiesTestPage) {
}.enterURLAndEnterToBrowser(storageWritePage) {
verifyPageContent("No cookies set")
clickPageObject(itemWithResId("setCookies"))
verifyPageContent("user=android")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage has value")
verifyPageContent("Local storage has value")
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
@ -228,9 +223,12 @@ class ComposeSettingsDeleteBrowsingDataTest {
confirmDeletionAndAssertSnackbar()
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage empty")
verifyPageContent("Local storage empty")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageWritePage) {
verifyPageContent("No cookies set")
}
}

@ -17,6 +17,7 @@ import mozilla.components.concept.engine.mediasession.MediaSession
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.IntentReceiverActivity
@ -86,33 +87,6 @@ class ComposeSmokeTest {
mockWebServer.shutdown()
}
/* Verifies the nav bar:
- opening a web page
- the existence of nav bar items
- editing the url bar
- the tab drawer button
- opening a new search and dismissing the nav bar
*/
@Test
fun verifyBasicNavigationToolbarFunctionality() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
verifyNavURLBarItems()
}.openNavigationToolbar {
}.goBackToWebsite {
}.openComposeTabDrawer(activityTestRule) {
verifyNormalTabsList()
}.openNewTab {
}.dismissSearchBar {
verifyHomeScreen()
}
}
}
// Device or AVD requires a Google Services Android OS installation with Play Store installed
// Verifies the Open in app button when an app is installed
@Test
@ -206,23 +180,6 @@ class ComposeSmokeTest {
}
}
@Test
fun emptyTabsTrayViewPrivateBrowsingTest() {
homeScreen {
}.openComposeTabDrawer(activityTestRule) {
}.toggleToPrivateTabs {
verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInPrivateBrowsing()
verifyFab()
verifyThreeDotButton()
}.openThreeDotMenu {
verifyTabSettingsButton()
verifyRecentlyClosedTabsButton()
}
}
@Test
fun privateTabsTrayWithOpenedTabTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -324,6 +281,7 @@ class ComposeSmokeTest {
}
}
@Ignore("Failing, see https://bugzilla.mozilla.org/show_bug.cgi?id=1846941")
@Test
fun tabMediaControlButtonTest() {
val audioTestPage = TestAssetHelper.getAudioPageAsset(mockWebServer)
@ -423,16 +381,4 @@ class ComposeSmokeTest {
verifyHomeScreen()
}
}
@Test
fun tabsSettingsMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openTabsSubMenu {
verifyTabViewOptions()
verifyCloseTabsOptions()
verifyMoveOldTabsToInactiveOptions()
}
}
}

@ -13,11 +13,14 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.clickSnackbarButton
import org.mozilla.fenix.helpers.TestHelper.closeApp
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.verifyKeyboardVisibility
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.browserScreen
@ -49,7 +52,8 @@ class ComposeTabbedBrowsingTest {
@get:Rule(order = 0)
val composeTestRule =
AndroidComposeTestRule(
HomeActivityTestRule.withDefaultSettingsOverrides(
HomeActivityIntentTestRule.withDefaultSettingsOverrides(
skipOnboarding = true,
tabsTrayRewriteEnabled = true,
),
) { it.activity }
@ -263,6 +267,7 @@ class ComposeTabbedBrowsingTest {
}
}
@SmokeTest
@Test
fun closePrivateTabsNotificationTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -321,6 +326,23 @@ class ComposeTabbedBrowsingTest {
}
}
@Test
fun emptyTabsTrayViewPrivateBrowsingTest() {
homeScreen {
}.openComposeTabDrawer(composeTestRule) {
}.toggleToPrivateTabs {
verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInPrivateBrowsing()
verifyFab()
verifyThreeDotButton()
}.openThreeDotMenu {
verifyTabSettingsButton()
verifyRecentlyClosedTabsButton()
}
}
@Test
fun verifyOpenTabDetails() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -388,4 +410,48 @@ class ComposeTabbedBrowsingTest {
verifyTurnOnSyncMenu()
}
}
@Test
fun privateModeStaysAsDefaultAfterRestartTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
}.togglePrivateBrowsingMode()
closeApp(composeTestRule.activityRule)
restartApp(composeTestRule.activityRule)
homeScreen {
verifyPrivateBrowsingHomeScreen()
}.openComposeTabDrawer(composeTestRule) {
}.toggleToNormalTabs {
verifyExistingOpenTabs(defaultWebPage.title)
}
}
@SmokeTest
@Test
fun privateTabsDoNotPersistAfterClosingAppTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
}.openComposeTabDrawer(composeTestRule) {
}.openNewTab {
}.submitQuery(secondWebPage.url.toString()) {
}
closeApp(composeTestRule.activityRule)
restartApp(composeTestRule.activityRule)
homeScreen {
verifyPrivateBrowsingHomeScreen()
}.openComposeTabDrawer(composeTestRule) {
verifyNoOpenTabsInPrivateBrowsing()
}
}
}

@ -13,7 +13,6 @@ import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -70,7 +69,6 @@ class ContextMenusTest {
mockWebServer.shutdown()
}
@SmokeTest
@Test
fun verifyContextOpenLinkNewTab() {
val pageLinks =
@ -94,7 +92,6 @@ class ContextMenusTest {
}
}
@SmokeTest
@Test
fun verifyContextOpenLinkPrivateTab() {
val pageLinks =

@ -10,14 +10,12 @@ import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -51,21 +49,6 @@ class ContextualHintsTest {
mockWebServer.shutdown()
}
@Test
fun jumpBackInCFRTest() {
val genericPage = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
verifyCookiesProtectionHintIsDisplayed(true)
// One back press to dismiss the TCP hint
mDevice.pressBack()
}.goToHomescreen {
verifyJumpBackInMessage()
}
}
@SmokeTest
@Test
fun openTotalCookieProtectionLearnMoreLinkTest() {
val genericPage = getGenericAsset(mockWebServer, 1)
@ -78,7 +61,6 @@ class ContextualHintsTest {
}
}
@SmokeTest
@Test
fun dismissTotalCookieProtectionHintTest() {
val genericPage = getGenericAsset(mockWebServer, 1)

@ -1,6 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
@ -16,6 +21,7 @@ class CookieBannerReductionTest {
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
// Bug causing flakiness https://bugzilla.mozilla.org/show_bug.cgi?id=1807440
@Ignore("Disabled: https://bugzilla.mozilla.org/show_bug.cgi?id=1852803")
@SmokeTest
@Test
fun verifyCookieBannerReductionTest() {

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule

@ -1,8 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
@ -617,6 +622,7 @@ class CreditCardAutofillTest {
}
}
@Ignore("Failing, see https://bugzilla.mozilla.org/show_bug.cgi?id=1847774")
@Test
fun verifySavedCreditCardsRedirectionToAutofillAfterInterruptionTest() {
homeScreen {

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@file:Suppress("DEPRECATION")
package org.mozilla.fenix.ui
@ -22,10 +26,12 @@ import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdAndText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.createCustomTabIntent
import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.openAppFromExternalLink
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.clickPageObject
import org.mozilla.fenix.ui.robots.customTabScreen
import org.mozilla.fenix.ui.robots.enhancedTrackingProtection
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.longClickPageObject
import org.mozilla.fenix.ui.robots.navigationToolbar
@ -324,4 +330,44 @@ class CustomTabsTest {
verifyHomeScreenAppBarItems()
}
}
@Test
fun verifyETPSheetAndToggleTest() {
val customTabPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
intentReceiverActivityTestRule.launchActivity(
createCustomTabIntent(
pageUrl = customTabPage.url.toString(),
customActionButtonDescription = customTabActionButton,
),
)
enhancedTrackingProtection {
}.openEnhancedTrackingProtectionSheet {
verifyEnhancedTrackingProtectionSheetStatus(status = "ON", state = true)
}.toggleEnhancedTrackingProtectionFromSheet {
verifyEnhancedTrackingProtectionSheetStatus(status = "OFF", state = false)
}
openAppFromExternalLink(customTabPage.url.toString())
browserScreen {
}.openThreeDotMenu {
}.openSettings {
}.openEnhancedTrackingProtectionSubMenu {
switchEnhancedTrackingProtectionToggle()
verifyEnhancedTrackingProtectionOptionsEnabled(enabled = false)
}
exitMenu()
browserScreen {
}.goBack {
// Actually exiting to the previously opened custom tab
}
enhancedTrackingProtection {
verifyETPSectionIsDisplayedInQuickSettingsSheet(isDisplayed = false)
}
}
}

@ -8,6 +8,7 @@ import androidx.core.net.toUri
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
@ -125,6 +126,7 @@ class DownloadTest {
deleteDownloadedFileOnStorage(downloadFile)
}
@Ignore("Failing: Bug https://bugzilla.mozilla.org/show_bug.cgi?id=1813521")
@SmokeTest
@Test
fun pauseResumeCancelDownloadTest() {
@ -350,6 +352,7 @@ class DownloadTest {
deleteDownloadedFileOnStorage(secondDownloadedFile)
}
@Ignore("Failing: https://bugzilla.mozilla.org/show_bug.cgi?id=1840994")
@Test
fun systemNotificationCantBeDismissedWhileDownloadingTest() {
// Clear the "Firefox Fenix default browser notification"

@ -23,6 +23,7 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.MockBrowserDataHelper
import org.mozilla.fenix.helpers.RecyclerViewIdlingResource
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.exitMenu
@ -396,23 +397,27 @@ class HistoryTest {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getHTMLControlsFormAsset(mockWebServer)
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
}
navigationToolbar {
}.enterURLAndEnterToBrowser(secondWebPage.url) {
MockBrowserDataHelper.createHistoryItem(firstWebPage.url.toString())
MockBrowserDataHelper.createHistoryItem(secondWebPage.url.toString())
homeScreen {
}.openThreeDotMenu {
}.openHistory {
}.clickSearchButton {
// Search for a valid term
typeSearch(firstWebPage.title)
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString())
typeSearch("generic")
verifySearchEngineSuggestionResults(activityTestRule, firstWebPage.url.toString(), searchTerm = "generic")
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
clickClearButton()
}.dismissSearchBar {}
historyMenu {
}.clickSearchButton {
// Search for invalid term
typeSearch("Android")
verifyNoSuggestionsAreDisplayed(activityTestRule, firstWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(
activityTestRule,
firstWebPage.url.toString(),
secondWebPage.url.toString(),
)
}
}
@ -445,7 +450,11 @@ class HistoryTest {
typeSearch("generic")
verifyNoSuggestionsAreDisplayed(activityTestRule, firstWebPage.url.toString())
verifyNoSuggestionsAreDisplayed(activityTestRule, secondWebPage.url.toString())
verifySearchEngineSuggestionResults(activityTestRule, thirdWebPage.url.toString())
verifySearchEngineSuggestionResults(
activityTestRule,
thirdWebPage.url.toString(),
searchTerm = "generic",
)
pressBack()
}
historyMenu {

@ -12,8 +12,8 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.Constants.POCKET_RECOMMENDED_STORIES_UTM_PARAM
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
@ -30,7 +30,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
class HomeScreenTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private lateinit var firstPocketStoryPublisher: String
@get:Rule(order = 0)
val activityTestRule =
@ -55,6 +54,7 @@ class HomeScreenTest {
mockWebServer.shutdown()
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/235396
@Test
fun homeScreenItemsTest() {
homeScreen {}.dismissOnboarding()
@ -77,8 +77,9 @@ class HomeScreenTest {
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/244199
@Test
fun privateModeScreenItemsTest() {
fun privateBrowsingHomeScreenItemsTest() {
homeScreen { }.dismissOnboarding()
homeScreen { }.togglePrivateBrowsingMode()
@ -89,6 +90,8 @@ class HomeScreenTest {
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1364362
@SmokeTest
@Test
fun verifyJumpBackInSectionTest() {
activityTestRule.activityRule.applySettingsExceptions {
@ -139,108 +142,9 @@ class HomeScreenTest {
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/1569839
@Test
fun verifyPocketHomepageStoriesTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyThoughtProvokingStories(true)
scrollToPocketProvokingStories()
verifyPocketRecommendedStoriesItems()
// Sponsored Pocket stories are only advertised for a limited time.
// See also known issue https://bugzilla.mozilla.org/show_bug.cgi?id=1828629
// verifyPocketSponsoredStoriesItems(2, 8)
verifyDiscoverMoreStoriesButton()
verifyStoriesByTopic(true)
verifyPoweredByPocket()
}.openThreeDotMenu {
}.openCustomizeHome {
clickPocketButton()
}.goBackToHomeScreen {
verifyThoughtProvokingStories(false)
verifyStoriesByTopic(false)
}
}
@Test
fun openPocketStoryItemTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyThoughtProvokingStories(true)
scrollToPocketProvokingStories()
firstPocketStoryPublisher = getProvokingStoryPublisher(1)
}.clickPocketStoryItem(firstPocketStoryPublisher, 1) {
verifyUrl(POCKET_RECOMMENDED_STORIES_UTM_PARAM)
}
}
@Test
fun openPocketDiscoverMoreTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
scrollToPocketProvokingStories()
verifyDiscoverMoreStoriesButton()
}.clickPocketDiscoverMoreButton {
verifyUrl("getpocket.com/explore")
}
}
@Test
fun selectStoriesByTopicItemTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyStoriesByTopicItemState(activityTestRule, false, 1)
clickStoriesByTopicItem(activityTestRule, 1)
verifyStoriesByTopicItemState(activityTestRule, true, 1)
}
}
@Test
fun verifyPocketLearnMoreLinkTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyPoweredByPocket()
}.clickPocketLearnMoreLink(activityTestRule) {
verifyUrl("mozilla.org/en-US/firefox/pocket")
}
}
@Test
fun verifyCustomizeHomepageTest() {
fun verifyCustomizeHomepageButtonTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {

@ -152,7 +152,6 @@ class LoginsTest {
}
}
@SmokeTest
@Test
fun openWebsiteForSavedLoginTest() {
val loginPage = "https://mozilla-mobile.github.io/testapp/loginForm"
@ -462,6 +461,7 @@ class LoginsTest {
}
}
@SmokeTest
@Test
fun verifyNeverSaveLoginOptionTest() {
val loginPage = TestAssetHelper.getSaveLoginAsset(mockWebServer)

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.test.platform.app.InstrumentationRegistry

@ -218,6 +218,7 @@ class NavigationToolbarTest {
}
}
@SmokeTest
@Test
fun verifySecurePageSecuritySubMenuTest() {
val defaultWebPage = "https://mozilla-mobile.github.io/testapp/loginForm"
@ -232,6 +233,7 @@ class NavigationToolbarTest {
}
}
@SmokeTest
@Test
fun verifyInsecurePageSecuritySubMenuTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)

@ -9,6 +9,7 @@ import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.R
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.TestHelper.setNetworkEnabled
@ -83,6 +84,7 @@ class NoNetworkAccessStartupTests {
}.refreshPage { }
}
@SmokeTest
@Test
fun testSignInPageWithNoNetworkConnection() {
setNetworkEnabled(false)

@ -1,242 +0,0 @@
package org.mozilla.fenix.ui
import android.content.res.Configuration
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.verifyDarkThemeApplied
import org.mozilla.fenix.helpers.TestHelper.verifyKeyboardVisibility
import org.mozilla.fenix.helpers.TestHelper.verifyLightThemeApplied
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
class OnboardingTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val privacyNoticeLink = "mozilla.org/en-US/privacy/firefox"
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
mockWebServer = MockWebServer().apply {
dispatcher = AndroidAssetDispatcher()
start()
}
}
@After
fun tearDown() {
mockWebServer.shutdown()
}
private fun getUITheme(): Boolean {
val mode =
activityTestRule.activity.resources?.configuration?.uiMode?.and(Configuration.UI_MODE_NIGHT_MASK)
return when (mode) {
Configuration.UI_MODE_NIGHT_YES -> true // dark theme is set
Configuration.UI_MODE_NIGHT_NO -> false // dark theme is not set, using light theme
else -> false // default option is light theme
}
}
// Verifies the first run onboarding screen
@SmokeTest
@Test
fun firstRunScreenTest() {
homeScreen {
verifyHomeScreenAppBarItems()
verifyHomeScreenWelcomeItems()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
verifySignInToSyncCard()
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
verifyPrivacyNoticeCard()
verifyStartBrowsingSection()
verifyNavigationToolbarItems("0")
}
}
// Verifies the functionality of the onboarding Start Browsing button
@SmokeTest
@Test
fun startBrowsingButtonTest() {
homeScreen {
verifyStartBrowsingButton()
}.clickStartBrowsingButton {
verifySearchView()
}
}
@Test
fun dismissOnboardingUsingSettingsTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openSettings {
verifyGeneralHeading()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun dismissOnboardingUsingBookmarksTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openBookmarks {
verifyBookmarksMenuView()
navigateUp()
}
homeScreen {
verifyExistingTopSitesList()
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun dismissOnboardingUsingHelpTest() {
homeScreen {
verifyWelcomeHeader()
}.openThreeDotMenu {
}.openHelp {
verifyHelpUrl()
}.goBack {
verifyExistingTopSitesList()
}
}
@Test
fun toolbarTapDoesntDismissOnboardingTest() {
homeScreen {
verifyStartBrowsingButton()
}.openSearch {
verifySearchView()
verifyKeyboardVisibility()
}.dismissSearchBar {
verifyStartBrowsingButton()
}
}
@Test
fun dismissOnboardingWithPageLoadTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
verifyStartBrowsingButton()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
verifyHomeScreen()
}
}
@Test
fun chooseYourThemeCardTest() {
homeScreen {
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
clickLightThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = true,
isAutomaticThemeChecked = false,
)
verifyLightThemeApplied(getUITheme())
clickDarkThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = true,
isLightThemeChecked = false,
isAutomaticThemeChecked = false,
)
verifyDarkThemeApplied(getUITheme())
clickAutomaticThemeButton()
verifyChooseYourThemeCard(
isDarkThemeChecked = false,
isLightThemeChecked = false,
isAutomaticThemeChecked = true,
)
verifyLightThemeApplied(getUITheme())
}
}
@Test
fun pickYourToolbarPlacementCardTest() {
homeScreen {
verifyToolbarPlacementCard(isBottomChecked = true, isTopChecked = false)
clickTopToolbarPlacementButton()
verifyToolbarPosition(defaultPosition = false)
clickBottomToolbarPlacementButton()
verifyToolbarPosition(defaultPosition = true)
}
}
@Test
fun privacyProtectionByDefaultCardTest() {
homeScreen {
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
clickStrictTrackingProtectionButton()
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = false,
isStrictChecked = true,
)
clickStandardTrackingProtectionButton()
verifyPrivacyProtectionCard(
settings = activityTestRule.activity.getSettings(),
isStandardChecked = true,
isStrictChecked = false,
)
}
}
@Test
fun pickUpWhereYouLeftOffCardTest() {
homeScreen {
verifySignInToSyncCard()
}.clickSignInButton {
verifyTurnOnSyncMenu()
}
}
@Test
@Ignore("Failing due to changes from https://github.com/mozilla-mobile/firefox-android/pull/969")
fun youControlYourDataCardTest() {
homeScreen {
verifyPrivacyNoticeCard()
}.clickPrivacyNoticeButton {
verifyCustomTabToolbarTitle("Firefox Privacy Notice")
}.goBackToOnboardingScreen {
verifyPrivacyNoticeCard()
}
}
}

@ -0,0 +1,139 @@
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.helpers.Constants
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.ui.robots.homeScreen
/**
* Tests for verifying the presence of the Pocket section and its elements
*/
class PocketTest {
private lateinit var mDevice: UiDevice
private lateinit var firstPocketStoryPublisher: String
@get:Rule(order = 0)
val activityTestRule =
AndroidComposeTestRule(HomeActivityTestRule.withDefaultSettingsOverrides()) { it.activity }
@Rule(order = 1)
@JvmField
val retryTestRule = RetryTestRule(3)
@Before
fun setUp() {
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2252509
@Test
fun verifyPocketSectionTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyThoughtProvokingStories(true)
scrollToPocketProvokingStories()
verifyPocketRecommendedStoriesItems()
// Sponsored Pocket stories are only advertised for a limited time.
// See also known issue https://bugzilla.mozilla.org/show_bug.cgi?id=1828629
// verifyPocketSponsoredStoriesItems(2, 8)
verifyDiscoverMoreStoriesButton()
verifyStoriesByTopic(true)
verifyPoweredByPocket()
}.openThreeDotMenu {
}.openCustomizeHome {
clickPocketButton()
}.goBackToHomeScreen {
verifyThoughtProvokingStories(false)
verifyStoriesByTopic(false)
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2252513
@Test
fun openPocketStoryItemTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyThoughtProvokingStories(true)
scrollToPocketProvokingStories()
firstPocketStoryPublisher = getProvokingStoryPublisher(1)
}.clickPocketStoryItem(firstPocketStoryPublisher, 1) {
verifyUrl(Constants.POCKET_RECOMMENDED_STORIES_UTM_PARAM)
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2252514
@Test
fun pocketDiscoverMoreButtonTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
scrollToPocketProvokingStories()
verifyDiscoverMoreStoriesButton()
}.clickPocketDiscoverMoreButton {
verifyUrl("getpocket.com/explore")
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2252515
@Test
fun selectPocketStoriesByTopicTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyStoriesByTopicItemState(activityTestRule, false, 1)
clickStoriesByTopicItem(activityTestRule, 1)
verifyStoriesByTopicItemState(activityTestRule, true, 1)
}
}
// TestRail link: https://testrail.stage.mozaws.net/index.php?/cases/view/2252516
@Test
fun pocketLearnMoreButtonTest() {
activityTestRule.activityRule.applySettingsExceptions {
it.isRecentTabsFeatureEnabled = false
it.isRecentlyVisitedFeatureEnabled = false
}
homeScreen {
}.dismissOnboarding()
homeScreen {
verifyPoweredByPocket()
}.clickPocketLearnMoreLink(activityTestRule) {
verifyUrl("mozilla.org/en-US/firefox/pocket")
}
}
}

@ -1,10 +1,13 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.core.net.toUri
import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.Constants.PackageName.GMAIL_APP
import org.mozilla.fenix.helpers.Constants.PackageName.PHONE_APP
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
@ -88,7 +91,6 @@ class PwaTest {
}
}
@SmokeTest
@Test
fun appLikeExperiencePWATest() {
navigationToolbar {

@ -10,6 +10,7 @@ import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.core.net.toUri
import androidx.test.espresso.Espresso.openActionBarOverflowOrOptionsMenu
import androidx.test.espresso.Espresso.pressBack
import mozilla.components.concept.engine.utils.EngineReleaseChannel
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assume.assumeTrue
@ -18,11 +19,14 @@ import org.junit.Ignore
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.ext.components
import org.mozilla.fenix.helpers.Constants.PackageName.ANDROID_SETTINGS
import org.mozilla.fenix.helpers.Constants.searchEngineCodes
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithText
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createHistoryItem
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createTabItem
import org.mozilla.fenix.helpers.MockBrowserDataHelper.setCustomSearchEngine
import org.mozilla.fenix.helpers.SearchDispatcher
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
@ -34,6 +38,7 @@ import org.mozilla.fenix.helpers.TestHelper.exitMenu
import org.mozilla.fenix.helpers.TestHelper.grantSystemPermission
import org.mozilla.fenix.helpers.TestHelper.longTapSelectItem
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.runWithCondition
import org.mozilla.fenix.helpers.TestHelper.verifyKeyboardVisibility
import org.mozilla.fenix.ui.robots.clickContextMenuItem
import org.mozilla.fenix.ui.robots.clickPageObject
@ -627,4 +632,77 @@ class SearchTest {
}
}
}
@SmokeTest
@Test
fun verifySearchHistoryWithBrowsingDataTest() {
val firstPageUrl = getGenericAsset(searchMockServer, 1)
val secondPageUrl = getGenericAsset(searchMockServer, 2)
createHistoryItem(firstPageUrl.url.toString())
createHistoryItem(secondPageUrl.url.toString())
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod(searchEngineName = "History")
typeSearch(searchTerm = "Mozilla")
verifyNoSuggestionsAreDisplayed(rule = activityTestRule, "Mozilla")
clickClearButton()
typeSearch(searchTerm = "generic")
verifyTypedToolbarText("generic")
verifySearchEngineSuggestionResults(
rule = activityTestRule,
searchSuggestions = arrayOf(
firstPageUrl.url.toString(),
secondPageUrl.url.toString(),
),
searchTerm = "generic",
)
}.clickSearchSuggestion(firstPageUrl.url.toString()) {
verifyUrl(firstPageUrl.url.toString())
}
}
@SmokeTest
@Test
fun verifySearchTabsWithOpenTabsTest() {
runWithCondition(
// This test will run only on Beta and RC builds
// The new composable tabs tray is only available in Nightly and Debug.
activityTestRule.activity.components.core.engine.version.releaseChannel == EngineReleaseChannel.RELEASE ||
activityTestRule.activity.components.core.engine.version.releaseChannel == EngineReleaseChannel.BETA,
) {
val firstPageUrl = getGenericAsset(searchMockServer, 1)
val secondPageUrl = getGenericAsset(searchMockServer, 2)
createTabItem(firstPageUrl.url.toString())
createTabItem(secondPageUrl.url.toString())
navigationToolbar {
}.clickUrlbar {
clickSearchSelectorButton()
selectTemporarySearchMethod(searchEngineName = "Tabs")
typeSearch(searchTerm = "Mozilla")
verifyNoSuggestionsAreDisplayed(rule = activityTestRule, "Mozilla")
clickClearButton()
typeSearch(searchTerm = "generic")
verifyTypedToolbarText("generic")
verifySearchEngineSuggestionResults(
rule = activityTestRule,
searchSuggestions = arrayOf(
"Firefox Suggest",
firstPageUrl.url.toString(),
secondPageUrl.url.toString(),
),
searchTerm = "generic",
)
}.clickSearchSuggestion(firstPageUrl.url.toString()) {
verifyTabCounter("2")
}.openTabDrawer {
verifyOpenTabsOrder(position = 1, title = firstPageUrl.url.toString())
verifyOpenTabsOrder(position = 2, title = secondPageUrl.url.toString())
}
}
}
}

@ -85,6 +85,7 @@ class SettingsAdvancedTest {
}
// Assumes Youtube is installed and enabled
@SmokeTest
@Test
fun neverOpenLinkInAppTest() {
val defaultWebPage = TestAssetHelper.getExternalLinksAsset(mockWebServer)
@ -198,7 +199,6 @@ class SettingsAdvancedTest {
}
// Assumes Youtube is installed and enabled
@SmokeTest
@Test
fun privateBrowsingAskBeforeOpeningLinkInAppCancelTest() {
val defaultWebPage = TestAssetHelper.getExternalLinksAsset(mockWebServer)
@ -232,7 +232,6 @@ class SettingsAdvancedTest {
}
// Assumes Youtube is installed and enabled
@SmokeTest
@Test
fun privateBrowsingAskBeforeOpeningLinkInAppOpenTest() {
val defaultWebPage = TestAssetHelper.getExternalLinksAsset(mockWebServer)

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import android.content.res.Configuration

@ -113,7 +113,35 @@ class SettingsDeleteBrowsingDataOnQuitTest {
}
@Test
fun deleteHistoryAndSiteStorageOnQuitTest() {
fun deleteHistoryOnQuitTest() {
val genericPage =
getStorageTestAsset(mockWebServer, "generic1.html")
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingDataOnQuit {
clickDeleteBrowsingOnQuitButtonSwitch()
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.goToHomescreen {
}.openThreeDotMenu {
clickQuit()
restartApp(activityTestRule)
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()
exitMenu()
}
}
@Test
fun deleteCookiesAndSiteDataOnQuitTest() {
val storageWritePage =
getStorageTestAsset(mockWebServer, "storage_write.html")
val storageCheckPage =
@ -136,12 +164,6 @@ class SettingsDeleteBrowsingDataOnQuitTest {
restartApp(activityTestRule)
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyEmptyHistoryView()
exitMenu()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage.url) {
verifyPageContent("Session storage empty")

@ -160,20 +160,15 @@ class SettingsDeleteBrowsingDataTest {
@SmokeTest
@Test
fun deleteBrowsingHistoryAndSiteDataTest() {
val storageWritePage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageCheckPage = getStorageTestAsset(mockWebServer, "storage_check.html").url
fun deleteBrowsingHistoryTest() {
val genericPage = getStorageTestAsset(mockWebServer, "generic1.html").url
navigationToolbar {
}.enterURLAndEnterToBrowser(storageWritePage) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage has value")
verifyPageContent("Local storage has value")
}.enterURLAndEnterToBrowser(genericPage) {
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
verifyBrowsingHistoryDetails("2")
verifyBrowsingHistoryDetails("1")
selectOnlyBrowsingHistoryCheckBox()
clickDeleteBrowsingDataButton()
verifyDeleteBrowsingDataDialog()
@ -191,27 +186,27 @@ class SettingsDeleteBrowsingDataTest {
verifyEmptyHistoryView()
mDevice.pressBack()
}
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage empty")
verifyPageContent("Local storage empty")
}
}
@SmokeTest
@Test
fun deleteCookiesTest() {
fun deleteCookiesAndSiteDataTest() {
val genericPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val cookiesTestPage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageWritePage = getStorageTestAsset(mockWebServer, "storage_write.html").url
val storageCheckPage = getStorageTestAsset(mockWebServer, "storage_check.html").url
// Browsing a generic page to allow GV to load on a fresh run
navigationToolbar {
}.enterURLAndEnterToBrowser(genericPage.url) {
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(cookiesTestPage) {
}.enterURLAndEnterToBrowser(storageWritePage) {
verifyPageContent("No cookies set")
clickPageObject(itemWithResId("setCookies"))
verifyPageContent("user=android")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage has value")
verifyPageContent("Local storage has value")
}.openThreeDotMenu {
}.openSettings {
}.openSettingsSubMenuDeleteBrowsingData {
@ -221,9 +216,12 @@ class SettingsDeleteBrowsingDataTest {
confirmDeletionAndAssertSnackbar()
exitMenu()
}
browserScreen {
}.openThreeDotMenu {
}.refreshPage {
navigationToolbar {
}.enterURLAndEnterToBrowser(storageCheckPage) {
verifyPageContent("Session storage empty")
verifyPageContent("Local storage empty")
}.openNavigationToolbar {
}.enterURLAndEnterToBrowser(storageWritePage) {
verifyPageContent("No cookies set")
}
}

@ -76,6 +76,7 @@ class SettingsGeneralTest {
}
}
@SmokeTest
@Test
fun changeAccessibiltySettings() {
// Goes through the settings and changes the default text on a webpage, then verifies if the text has changed.
@ -192,6 +193,18 @@ class SettingsGeneralTest {
}
}
@Test
fun tabsSettingsMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openTabsSubMenu {
verifyTabViewOptions()
verifyCloseTabsOptions()
verifyMoveOldTabsToInactiveOptions()
}
}
@Test
fun verifyTabsOptionSummaryUpdatesTest() {
homeScreen {

@ -230,9 +230,8 @@ class SettingsHomepageTest {
}
}
@SmokeTest
@Test
@Ignore("Intermittent test: https://github.com/mozilla-mobile/fenix/issues/26559")
@Test
fun setWallpaperTest() {
val wallpapers = listOf(
"Wallpaper Item: amethyst",

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
@ -12,6 +16,8 @@ import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.MockBrowserDataHelper.addCustomSearchEngine
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createBookmarkItem
import org.mozilla.fenix.helpers.MockBrowserDataHelper.createHistoryItem
import org.mozilla.fenix.helpers.SearchDispatcher
import org.mozilla.fenix.helpers.TestAssetHelper.getGenericAsset
import org.mozilla.fenix.helpers.TestHelper.appContext
@ -23,7 +29,6 @@ import org.mozilla.fenix.helpers.TestHelper.setTextToClipBoard
import org.mozilla.fenix.helpers.TestHelper.verifySnackBarText
import org.mozilla.fenix.ui.robots.EngineShortcut
import org.mozilla.fenix.ui.robots.homeScreen
import org.mozilla.fenix.ui.robots.navigationToolbar
import org.mozilla.fenix.ui.robots.searchScreen
import java.util.Locale
@ -138,31 +143,11 @@ class SettingsSearchTest {
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun toggleSearchHistoryTest() {
val page1 = getGenericAsset(mockWebServer, 1)
fun toggleOffHistorySearchSuggestionsTest() {
val websiteURL = getGenericAsset(mockWebServer, 1).url.toString()
navigationToolbar {
}.enterURLAndEnterToBrowser(page1.url) {
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openSearch {
typeSearch("test")
verifyFirefoxSuggestResults(
activityTestRule,
"test",
"Firefox Suggest",
"Test_Page_1",
)
}.clickSearchSuggestion("Test_Page_1") {
verifyUrl(page1.url.toString())
}.openTabDrawer {
closeTab()
}
createHistoryItem(websiteURL)
homeScreen {
}.openThreeDotMenu {
@ -178,46 +163,16 @@ class SettingsSearchTest {
verifyNoSuggestionsAreDisplayed(
activityTestRule,
"Firefox Suggest",
"Test_Page_1",
websiteURL,
)
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1807268")
@Test
fun toggleSearchBookmarksTest() {
fun toggleOffBookmarksSearchSuggestionsTest() {
val website = getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(website.url) {
}.openThreeDotMenu {
}.bookmarkPage {
}.openTabDrawer {
closeTab()
}
homeScreen {
}.openThreeDotMenu {
}.openHistory {
verifyHistoryListExists()
clickDeleteHistoryButton("Test_Page_1")
exitMenu()
}
homeScreen {
}.openSearch {
typeSearch("test")
verifyFirefoxSuggestResults(
activityTestRule,
"test",
"Firefox Suggest",
"Test_Page_1",
)
}.clickSearchSuggestion("Test_Page_1") {
verifyUrl(website.url.toString())
}.openTabDrawer {
closeTab()
}
createBookmarkItem(website.url.toString(), website.title, 1u)
homeScreen {
}.openThreeDotMenu {
@ -236,7 +191,7 @@ class SettingsSearchTest {
verifyNoSuggestionsAreDisplayed(
activityTestRule,
"Firefox Suggest",
"Test_Page_1",
website.title,
)
}
}
@ -354,6 +309,7 @@ class SettingsSearchTest {
}
}
@Ignore("Failing, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1848623")
@Test
fun errorForInvalidSearchEngineStringsTest() {
val customSearchEngine = object {
@ -455,8 +411,12 @@ class SettingsSearchTest {
fun toggleSearchSuggestionsTest() {
homeScreen {
}.openSearch {
typeSearch("mozilla")
verifySearchEngineSuggestionResults(activityTestRule, "mozilla firefox")
typeSearch("mozilla ")
verifySearchEngineSuggestionResults(
activityTestRule,
"mozilla firefox",
searchTerm = "mozilla ",
)
}.dismissSearchBar {
}.openThreeDotMenu {
}.openSettings {
@ -492,7 +452,11 @@ class SettingsSearchTest {
typeSearch("mozilla")
verifyAllowSuggestionsInPrivateModeDialog()
allowSuggestionsInPrivateMode()
verifySearchEngineSuggestionResults(activityTestRule, "mozilla firefox")
verifySearchEngineSuggestionResults(
activityTestRule,
"mozilla firefox",
searchTerm = "mozilla",
)
}.dismissSearchBar {
}.openThreeDotMenu {
}.openSettings {

@ -293,6 +293,7 @@ class SitePermissionsTest {
}
}
@SmokeTest
@Test
fun allowLocationPermissionsTest() {
mockLocationUpdatesRule.setMockLocation()
@ -307,6 +308,7 @@ class SitePermissionsTest {
}
}
@SmokeTest
@Test
fun blockLocationPermissionsTest() {
navigationToolbar {

@ -47,7 +47,6 @@ import org.mozilla.fenix.ui.robots.navigationToolbar
class SmokeTest {
private lateinit var mDevice: UiDevice
private lateinit var mockWebServer: MockWebServer
private val customMenuItem = "TestMenuItem"
private lateinit var browserStore: BrowserStore
@get:Rule(order = 0)
@ -85,33 +84,6 @@ class SmokeTest {
mockWebServer.shutdown()
}
/* Verifies the nav bar:
- opening a web page
- the existence of nav bar items
- editing the url bar
- the tab drawer button
- opening a new search and dismissing the nav bar
*/
@Test
fun verifyBasicNavigationToolbarFunctionality() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
homeScreen {
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
mDevice.waitForIdle()
verifyNavURLBarItems()
}.openNavigationToolbar {
}.goBackToWebsite {
}.openTabDrawer {
verifyExistingTabList()
}.openNewTab {
}.dismissSearchBar {
verifyHomeScreen()
}
}
}
// Device or AVD requires a Google Services Android OS installation with Play Store installed
// Verifies the Open in app button when an app is installed
@Test
@ -205,21 +177,6 @@ class SmokeTest {
}
}
@Test
fun emptyTabsTrayViewPrivateBrowsingTest() {
navigationToolbar {
}.openTabTray {
}.toggleToPrivateTabs() {
verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInPrivateBrowsing()
verifyPrivateBrowsingNewTabButton()
verifyTabTrayOverflowMenu(true)
verifyEmptyTabsTrayMenuButtons()
}
}
@Test
fun privateTabsTrayWithOpenedTabTest() {
val website = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -420,16 +377,4 @@ class SmokeTest {
verifyHomeScreen()
}
}
@Test
fun tabsSettingsMenuItemsTest() {
homeScreen {
}.openThreeDotMenu {
}.openSettings {
}.openTabsSubMenu {
verifyTabViewOptions()
verifyCloseTabsOptions()
verifyMoveOldTabsToInactiveOptions()
}
}
}

@ -12,10 +12,13 @@ import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.AndroidAssetDispatcher
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestAssetHelper
import org.mozilla.fenix.helpers.TestHelper.closeApp
import org.mozilla.fenix.helpers.TestHelper.restartApp
import org.mozilla.fenix.helpers.TestHelper.verifyKeyboardVisibility
import org.mozilla.fenix.ui.robots.browserScreen
import org.mozilla.fenix.ui.robots.homeScreen
@ -44,7 +47,7 @@ class TabbedBrowsingTest {
private lateinit var mockWebServer: MockWebServer
@get:Rule
val activityTestRule = HomeActivityTestRule.withDefaultSettingsOverrides()
val activityTestRule = HomeActivityIntentTestRule.withDefaultSettingsOverrides(skipOnboarding = true)
@Rule
@JvmField
@ -253,6 +256,7 @@ class TabbedBrowsingTest {
}
}
@SmokeTest
@Test
fun closePrivateTabsNotificationTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -309,6 +313,21 @@ class TabbedBrowsingTest {
}
}
@Test
fun emptyTabsTrayViewPrivateBrowsingTest() {
navigationToolbar {
}.openTabTray {
}.toggleToPrivateTabs {
verifyNormalBrowsingButtonIsSelected(false)
verifyPrivateBrowsingButtonIsSelected(true)
verifySyncedTabsButtonIsSelected(false)
verifyNoOpenTabsInPrivateBrowsing()
verifyPrivateBrowsingNewTabButton()
verifyTabTrayOverflowMenu(true)
verifyEmptyTabsTrayMenuButtons()
}
}
@Test
fun verifyOpenTabDetails() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
@ -377,4 +396,48 @@ class TabbedBrowsingTest {
verifyTurnOnSyncMenu()
}
}
@Test
fun privateModeStaysAsDefaultAfterRestartTest() {
val defaultWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
navigationToolbar {
}.enterURLAndEnterToBrowser(defaultWebPage.url) {
}.goToHomescreen {
}.togglePrivateBrowsingMode()
closeApp(activityTestRule)
restartApp(activityTestRule)
homeScreen {
verifyPrivateBrowsingHomeScreen()
}.openTabDrawer {
}.toggleToNormalTabs {
verifyExistingOpenTabs(defaultWebPage.title)
}
}
@SmokeTest
@Test
fun privateTabsDoNotPersistAfterClosingAppTest() {
val firstWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 1)
val secondWebPage = TestAssetHelper.getGenericAsset(mockWebServer, 2)
homeScreen {
}.togglePrivateBrowsingMode()
navigationToolbar {
}.enterURLAndEnterToBrowser(firstWebPage.url) {
}.openTabDrawer {
}.openNewTab {
}.submitQuery(secondWebPage.url.toString()) {
}
closeApp(activityTestRule)
restartApp(activityTestRule)
homeScreen {
verifyPrivateBrowsingHomeScreen()
}.openTabDrawer {
verifyNoOpenTabsInPrivateBrowsing()
}
}
}

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui
import androidx.test.platform.app.InstrumentationRegistry

@ -7,35 +7,50 @@ package org.mozilla.fenix.ui
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.junit.Rule
import org.junit.Test
import org.mozilla.fenix.customannotations.SmokeTest
import org.mozilla.fenix.helpers.HomeActivityTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.relaunchCleanApp
import org.mozilla.fenix.ui.robots.homeScreen
/**
* Tests for verifying the new onboarding features.
* Note: This involves setting the feature flag On for the onboarding dialog
* Tests for verifying the onboarding feature for users upgrading from a version older than 106.
* Note: This involves setting the feature flag On for the onboarding cards
*
*/
class OnboardingFeaturesTest {
class UpgradingUsersOnboardingTest {
@get:Rule
val activityTestRule = AndroidComposeTestRule(
HomeActivityTestRule(isHomeOnboardingDialogEnabled = true),
HomeActivityIntentTestRule(isHomeOnboardingDialogEnabled = true),
) { it.activity }
@SmokeTest
// https://testrail.stage.mozaws.net/index.php?/cases/view/1913592
@Test
fun upgradingUsersOnboardingScreensTest() {
homeScreen {
verifyUpgradingUserOnboardingFirstScreen(activityTestRule)
clickGetStartedButton(activityTestRule)
verifyUpgradingUserOnboardingSecondScreen(activityTestRule)
}
}
// https://testrail.stage.mozaws.net/index.php?/cases/view/1913591
@Test
fun upgradingUsersOnboardingCanBeSkippedTest() {
homeScreen {
verifyUpgradingUserOnboardingFirstScreen(activityTestRule)
clickCloseButton(activityTestRule)
verifyHomeScreen()
relaunchCleanApp(activityTestRule.activityRule)
clickGetStartedButton(activityTestRule)
verifyUpgradingUserOnboardingSecondScreen(activityTestRule)
clickSkipButton(activityTestRule)
verifyHomeScreen()
}
}
// https://testrail.stage.mozaws.net/index.php?/cases/view/1932156
@Test
fun upgradingUsersOnboardingSignInButtonTest() {
homeScreen {

@ -862,12 +862,35 @@ class BrowserRobot {
}
}
fun verifySurveyButton() {
val button = mDevice.findObject(
UiSelector().text(
getStringResource(
R.string.preferences_take_survey,
),
),
)
assertTrue(button.waitForExists(waitingTime))
}
fun clickOpenLinksInAppsDismissCFRButton() =
itemWithResIdContainingText(
"$packageName:id/dismiss",
getStringResource(R.string.open_in_app_cfr_negative_button_text),
).click()
fun clickTakeSurveyButton() {
val button = mDevice.findObject(
UiSelector().text(
getStringResource(
R.string.preferences_take_survey,
),
),
)
button.waitForExists(waitingTime)
button.click()
}
fun longClickToolbar() = mDevice.findObject(By.res("$packageName:id/mozac_browser_toolbar_url_view")).click(LONG_CLICK_DURATION)
fun verifyDownloadPromptIsDismissed() =
@ -915,6 +938,16 @@ class BrowserRobot {
fun fillPdfForm(name: String) {
// Set PDF form text for the text box
itemWithResId("pdfjs_internal_id_10R").setText(name)
mDevice.waitForWindowUpdate(packageName, waitingTime)
if (
!itemWithResId("pdfjs_internal_id_11R").exists() &&
mDevice
.executeShellCommand("dumpsys input_method | grep mInputShown")
.contains("mInputShown=true")
) {
// Close the keyboard
mDevice.pressBack()
}
// Click PDF form check box
itemWithResId("pdfjs_internal_id_11R").click()
}
@ -1170,6 +1203,14 @@ class BrowserRobot {
DownloadRobot().interact()
return DownloadRobot.Transition()
}
fun clickSurveyButton(interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
surveyButton.waitForExists(waitingTime)
surveyButton.click()
BrowserRobot().interact()
return Transition()
}
}
}
@ -1325,3 +1366,6 @@ private val contextMenuShareLink =
// Open in external app option
private val contextMenuOpenInExternalApp =
itemContainingText(getStringResource(R.string.mozac_feature_contextmenu_open_link_in_external_app))
private val surveyButton =
itemContainingText(getStringResource(R.string.preferences_take_survey))

@ -15,10 +15,12 @@ import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.filter
import androidx.compose.ui.test.hasAnyChild
import androidx.compose.ui.test.hasParent
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.longClick
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onChildAt
import androidx.compose.ui.test.onChildren
import androidx.compose.ui.test.onFirst
import androidx.compose.ui.test.onNodeWithContentDescription
@ -98,6 +100,12 @@ class ComposeTabDrawerRobot(private val composeTestRule: HomeActivityComposeTest
}
}
fun verifyOpenTabsOrder(title: String, position: Int) =
composeTestRule.normalTabsList()
.onChildAt(position - 1)
.assert(hasTestTag(TabsTrayTestTag.tabItemRoot))
.assert(hasAnyChild(hasText(title)))
fun verifyNoExistingOpenTabs(vararg titles: String) {
titles.forEach { title ->
TestCase.assertFalse(

@ -22,6 +22,8 @@ import org.hamcrest.Matchers.allOf
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.not
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestHelper.getStringResource
import org.mozilla.fenix.helpers.TestHelper.mDevice
@ -163,6 +165,12 @@ class EnhancedTrackingProtectionRobot {
)
}
fun verifyETPSectionIsDisplayedInQuickSettingsSheet(isDisplayed: Boolean) =
assertItemWithResIdExists(
itemWithResId("$packageName:id/trackingProtectionLayout"),
exists = isDisplayed,
)
fun navigateBackToDetails() {
onView(withId(R.id.details_back)).click()
}

@ -14,6 +14,7 @@ import androidx.compose.ui.test.assert
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertIsNotSelected
import androidx.compose.ui.test.assertIsSelected
import androidx.compose.ui.test.hasContentDescription
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onChildAt
@ -37,7 +38,6 @@ import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.matcher.ViewMatchers.withText
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiScrollable
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
@ -53,13 +53,10 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LISTS_MAXSWIPES
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.assertCheckedItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemContainingTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndDescriptionExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdExists
import org.mozilla.fenix.helpers.MatcherHelper.checkedItemWithResId
import org.mozilla.fenix.helpers.MatcherHelper.itemContainingText
import org.mozilla.fenix.helpers.MatcherHelper.itemWithDescription
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResId
@ -75,7 +72,6 @@ import org.mozilla.fenix.helpers.TestHelper.scrollToElementByText
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.helpers.ext.waitNotNull
import org.mozilla.fenix.tabstray.TabsTrayTestTag
import org.mozilla.fenix.utils.Settings
/**
* Implementation of Robot Pattern for the home screen menu.
@ -101,92 +97,6 @@ class HomeScreenRobot {
fun verifyHomeScreenAppBarItems() =
assertItemWithResIdExists(homeScreen, privateBrowsingButton, homepageWordmark)
fun verifyHomeScreenWelcomeItems() =
assertItemContainingTextExists(welcomeHeader, welcomeSubHeader)
fun verifyChooseYourThemeCard(
isDarkThemeChecked: Boolean,
isLightThemeChecked: Boolean,
isAutomaticThemeChecked: Boolean,
) {
scrollToElementByText(getStringResource(R.string.onboarding_theme_picker_header))
assertItemContainingTextExists(
chooseThemeHeader,
chooseThemeText,
darkThemeDescription,
lightThemeDescription,
)
assertCheckedItemWithResIdExists(
darkThemeToggle(isDarkThemeChecked),
lightThemeToggle(isLightThemeChecked),
automaticThemeToggle(isAutomaticThemeChecked),
)
assertItemWithResIdAndDescriptionExists(automaticThemeDescription)
}
fun clickLightThemeButton() =
itemWithResId("$packageName:id/theme_light_radio_button").click()
fun clickDarkThemeButton() =
itemWithResId("$packageName:id/theme_dark_radio_button").click()
fun clickAutomaticThemeButton() =
itemWithResId("$packageName:id/theme_automatic_radio_button").click()
fun verifyToolbarPlacementCard(isBottomChecked: Boolean, isTopChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_toolbar_placement_header_1))
assertItemContainingTextExists(toolbarPlacementHeader, toolbarPlacementDescription)
assertCheckedItemWithResIdExists(
toolbarPlacementBottomRadioButton(isBottomChecked),
toolbarPlacementTopRadioButton(isTopChecked),
)
assertItemWithResIdExists(
toolbarPlacementBottomImage,
toolbarPlacementBottomTitle,
toolbarPlacementTopImage,
toolbarPlacementTopTitle,
)
}
fun clickTopToolbarPlacementButton() =
itemWithResId("$packageName:id/toolbar_top_radio_button").click()
fun clickBottomToolbarPlacementButton() =
itemWithResId("$packageName:id/toolbar_bottom_radio_button").click()
fun verifySignInToSyncCard() {
scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header))
assertItemContainingTextExists(startSyncHeader, startSyncDescription)
assertItemWithResIdExists(signInButton)
}
fun verifyPrivacyProtectionCard(settings: Settings, isStandardChecked: Boolean, isStrictChecked: Boolean) {
scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_header_1))
assertItemContainingTextExists(privacyProtectionHeader, privacyProtectionDescription(settings))
assertCheckedItemWithResIdExists(
standardTrackingProtectionToggle(isStandardChecked),
strictTrackingProtectionToggle(isStrictChecked),
)
}
fun clickStandardTrackingProtectionButton() =
itemWithResId("$packageName:id/tracking_protection_standard_option").click()
fun clickStrictTrackingProtectionButton() =
itemWithResId("$packageName:id/tracking_protection_strict_default").click()
fun verifyPrivacyNoticeCard() {
scrollToElementByText(getStringResource(R.string.onboarding_privacy_notice_read_button))
assertItemContainingTextExists(privacyNoticeHeader, privacyNoticeDescription)
assertItemWithResIdExists(privacyNoticeButton)
}
fun verifyStartBrowsingSection() {
scrollToElementByText(getStringResource(R.string.onboarding_finish))
assertItemWithResIdExists(startBrowsingButton)
assertItemContainingTextExists(conclusionHeader)
}
fun verifyNavigationToolbarItems(numberOfOpenTabs: String = "0") {
assertItemWithResIdExists(navigationToolbar, menuButton)
assertItemWithResIdAndTextExists(tabCounter(numberOfOpenTabs))
@ -224,17 +134,6 @@ class HomeScreenRobot {
mDevice.findObject(UiSelector())
}
// First Run elements
fun verifyWelcomeHeader() = assertItemContainingTextExists(welcomeHeader)
fun verifyAccountsSignInButton() {
scrollToElementByText(getStringResource(R.string.onboarding_account_sign_in_header))
assertItemWithResIdExists(signInButton)
}
fun verifyStartBrowsingButton() {
scrollToElementByText(getStringResource(R.string.onboarding_finish))
assertItemWithResIdExists(startBrowsingButton)
}
// Upgrading users onboarding dialog
fun verifyUpgradingUserOnboardingFirstScreen(testRule: ComposeTestRule) {
testRule.also {
@ -252,6 +151,9 @@ class HomeScreenRobot {
fun clickGetStartedButton(testRule: ComposeTestRule) =
testRule.onNodeWithText(getStringResource(R.string.onboarding_home_get_started_button)).performClick()
fun clickCloseButton(testRule: ComposeTestRule) =
testRule.onNode(hasContentDescription("Close")).performClick()
fun verifyUpgradingUserOnboardingSecondScreen(testRule: ComposeTestRule) {
testRule.also {
it.onNodeWithText(getStringResource(R.string.onboarding_home_sync_title_3))
@ -497,15 +399,11 @@ class HomeScreenRobot {
}
}
fun verifyJumpBackInMessage() {
assertTrue(
mDevice.findObject(
UiSelector().text(
getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
),
).waitForExists(waitingTime),
)
}
fun verifyJumpBackInMessage(composeTestRule: ComposeTestRule) =
composeTestRule
.onNodeWithText(
getStringResource(R.string.onboarding_home_screen_jump_back_contextual_hint_2),
).assertExists()
fun getProvokingStoryPublisher(position: Int): String {
val publisher = mDevice.findObject(
@ -600,13 +498,6 @@ class HomeScreenRobot {
openThreeDotMenu { }.openSettings { }.goBack { }
}
fun clickStartBrowsingButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
startBrowsingButton.click()
SearchRobot().interact()
return SearchRobot.Transition()
}
fun clickUpgradingUserOnboardingSignInButton(
testRule: ComposeTestRule,
interact: SyncSignInRobot.() -> Unit,
@ -886,20 +777,6 @@ class HomeScreenRobot {
BrowserRobot().interact()
return BrowserRobot.Transition()
}
fun clickSignInButton(interact: SyncSignInRobot.() -> Unit): SyncSignInRobot.Transition {
signInButton.clickAndWaitForNewWindow(waitingTimeShort)
SyncSignInRobot().interact()
return SyncSignInRobot.Transition()
}
fun clickPrivacyNoticeButton(interact: CustomTabRobot.() -> Unit): CustomTabRobot.Transition {
privacyNoticeButton.clickAndWaitForNewWindow(waitingTimeShort)
CustomTabRobot().interact()
return CustomTabRobot.Transition()
}
}
}
@ -948,8 +825,7 @@ private fun assertHomeComponent() =
private fun threeDotButton() = onView(allOf(withId(R.id.menuButton)))
private fun assertExistingTopSitesList() =
onView(allOf(withId(R.id.top_sites_list)))
.check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
assertItemWithResIdExists(itemWithResId("$packageName:id/top_sites_list"))
private fun assertExistingTopSitesTabs(title: String) {
mDevice.findObject(
@ -1089,82 +965,13 @@ private fun sponsoredShortcut(sponsoredShortcutTitle: String) =
private fun storyByTopicItem(composeTestRule: ComposeTestRule, position: Int) =
composeTestRule.onNodeWithTag("pocket.categories").onChildAt(position - 1)
private fun privacyProtectionDescription(settings: Settings): UiObject {
val isTCPPublic = settings.enabledTotalCookieProtectionCFR
val descriptionText = when (isTCPPublic) {
true -> R.string.onboarding_tracking_protection_description
false -> R.string.onboarding_tracking_protection_description_old
}
return itemContainingText(getStringResource(descriptionText))
}
private val homeScreen =
itemWithResId("$packageName:id/homeLayout")
private val privateBrowsingButton =
itemWithResId("$packageName:id/privateBrowsingButton")
private val homepageWordmark =
itemWithResId("$packageName:id/wordmark")
private val welcomeHeader = itemContainingText(getStringResource(R.string.onboarding_header_2))
private val welcomeSubHeader =
itemContainingText(getStringResource(R.string.onboarding_message))
private val chooseThemeHeader =
itemContainingText(getStringResource(R.string.onboarding_theme_picker_header))
private val chooseThemeText =
itemContainingText(getStringResource(R.string.onboarding_theme_picker_description_2))
private val darkThemeDescription =
itemContainingText(getStringResource(R.string.onboarding_theme_dark_title))
private val lightThemeDescription =
itemContainingText(getStringResource(R.string.onboarding_theme_light_title))
private val automaticThemeDescription =
itemWithResIdAndDescription(
"$packageName:id/clickable_region_automatic",
"${getStringResource(R.string.onboarding_theme_automatic_title)} ${getStringResource(R.string.onboarding_theme_automatic_summary)}",
)
private fun darkThemeToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/theme_dark_radio_button", isChecked)
private fun lightThemeToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/theme_light_radio_button", isChecked)
private fun automaticThemeToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/theme_automatic_radio_button", isChecked)
private val toolbarPlacementHeader =
itemContainingText(getStringResource(R.string.onboarding_toolbar_placement_header_1))
private val toolbarPlacementDescription =
itemContainingText(getStringResource(R.string.onboarding_toolbar_placement_description))
private fun toolbarPlacementBottomRadioButton(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/toolbar_bottom_radio_button", isChecked)
private fun toolbarPlacementTopRadioButton(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/toolbar_top_radio_button", isChecked)
private val toolbarPlacementBottomImage =
itemWithResId("$packageName:id/toolbar_bottom_image")
private val toolbarPlacementBottomTitle =
itemWithResId("$packageName:id/toolbar_bottom_title")
private val toolbarPlacementTopTitle =
itemWithResId("$packageName:id/toolbar_top_title")
private val toolbarPlacementTopImage =
itemWithResId("$packageName:id/toolbar_top_image")
private val startSyncHeader =
itemContainingText(getStringResource(R.string.onboarding_account_sign_in_header))
private val startSyncDescription =
itemContainingText(getStringResource(R.string.onboarding_manual_sign_in_description))
private val signInButton =
itemWithResId("$packageName:id/fxa_sign_in_button")
private val privacyProtectionHeader =
itemContainingText(getStringResource(R.string.onboarding_tracking_protection_header))
private fun standardTrackingProtectionToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/tracking_protection_standard_option", isChecked)
private fun strictTrackingProtectionToggle(isChecked: Boolean) =
checkedItemWithResId("$packageName:id/tracking_protection_strict_default", isChecked)
private val privacyNoticeHeader =
itemContainingText(getStringResource(R.string.onboarding_privacy_notice_header_1))
private val privacyNoticeDescription =
itemContainingText(getStringResource(R.string.onboarding_privacy_notice_description))
private val privacyNoticeButton =
itemWithResId("$packageName:id/read_button")
private val startBrowsingButton =
itemWithResId("$packageName:id/finish_button")
private val conclusionHeader =
itemContainingText(getStringResource(R.string.onboarding_conclusion_header))
private val navigationToolbar =
itemWithResId("$packageName:id/toolbar")
private val menuButton =

@ -133,11 +133,9 @@ class NavigationToolbarRobot {
// New unified search UI selector
fun verifyDefaultSearchEngine(engineName: String) =
assertTrue(
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/search_selector")
.childSelector(UiSelector().description(engineName)),
).waitForExists(waitingTime),
searchSelectorButton
.getChild(UiSelector().description(engineName))
.waitForExists(waitingTime),
)
fun verifyTextSelectionOptions(vararg textSelectionOptions: String) {
@ -355,6 +353,14 @@ class NavigationToolbarRobot {
SearchRobot().interact()
return SearchRobot.Transition()
}
fun clickSearchSelectorButton(interact: SearchRobot.() -> Unit): SearchRobot.Transition {
searchSelectorButton.waitForExists(waitingTime)
searchSelectorButton.click()
SearchRobot().interact()
return SearchRobot.Transition()
}
}
}
@ -442,6 +448,9 @@ private fun assertCloseReaderViewDetected(visible: Boolean) {
)
}
private val searchSelectorButton =
mDevice.findObject(UiSelector().resourceId("$packageName:id/search_selector"))
inline fun runWithIdleRes(ir: IdlingResource?, pendingCheck: () -> Unit) {
try {
IdlingRegistry.getInstance().register(ir)

@ -18,7 +18,10 @@ import org.hamcrest.Matchers
import org.hamcrest.Matchers.allOf
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.HomeActivityComposeTestRule
import org.mozilla.fenix.helpers.MatcherHelper.assertItemWithResIdAndTextExists
import org.mozilla.fenix.helpers.MatcherHelper.itemWithResIdContainingText
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTime
import org.mozilla.fenix.helpers.TestAssetHelper.waitingTimeShort
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
@ -54,8 +57,9 @@ class RecentlyClosedTabsRobot {
}
fun verifyRecentlyClosedTabsPageTitle(title: String) =
recentlyClosedTabsPageTitle(title)
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
assertItemWithResIdAndTextExists(
recentlyClosedTabsPageTitle(title),
)
fun verifyRecentlyClosedTabsUrl(expectedUrl: Uri) {
onView(
@ -72,7 +76,10 @@ class RecentlyClosedTabsRobot {
class Transition {
fun clickRecentlyClosedItem(title: String, interact: BrowserRobot.() -> Unit): BrowserRobot.Transition {
recentlyClosedTabsPageTitle(title).click()
recentlyClosedTabsPageTitle(title).also {
it.waitForExists(waitingTimeShort)
it.click()
}
mDevice.waitForIdle()
BrowserRobot().interact()
@ -109,12 +116,11 @@ class RecentlyClosedTabsRobot {
}
}
private fun recentlyClosedTabsPageTitle(title: String) = onView(
allOf(
withId(R.id.title),
withText(title),
),
)
private fun recentlyClosedTabsPageTitle(title: String) =
itemWithResIdContainingText(
resourceId = "$packageName:id/title",
text = title,
)
private fun recentlyClosedTabDeleteButton() =
onView(

@ -6,8 +6,10 @@
package org.mozilla.fenix.ui.robots
import androidx.compose.ui.test.assertAny
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.performScrollToNode
import androidx.test.espresso.Espresso.onView
@ -90,26 +92,7 @@ class SearchRobot {
}
}
fun verifySearchEngineSuggestionResults(rule: ComposeTestRule, searchSuggestion: String) {
rule.waitForIdle()
for (i in 1..RETRY_COUNT) {
try {
assertTrue(
mDevice.findObject(UiSelector().textContains(searchSuggestion))
.waitForExists(waitingTime),
)
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
throw e
} else {
expandSearchSuggestionsList()
}
}
}
}
fun verifyFirefoxSuggestResults(rule: ComposeTestRule, searchTerm: String, vararg searchSuggestions: String) {
fun verifySearchEngineSuggestionResults(rule: ComposeTestRule, vararg searchSuggestions: String, searchTerm: String) {
rule.waitForIdle()
for (i in 1..RETRY_COUNT) {
try {
@ -119,7 +102,6 @@ class SearchRobot {
.performScrollToNode(hasText(searchSuggestion))
.assertExists()
}
break
} catch (e: AssertionError) {
if (i == RETRY_COUNT) {
@ -138,10 +120,11 @@ class SearchRobot {
fun verifyNoSuggestionsAreDisplayed(rule: ComposeTestRule, vararg searchSuggestions: String) {
rule.waitForIdle()
for (searchSuggestion in searchSuggestions) {
assertTrue(
mDevice.findObject(UiSelector().textContains(searchSuggestion))
.waitUntilGone(waitingTimeShort),
)
rule.onAllNodesWithTag("mozac.awesomebar.suggestions")
.assertAny(
hasText(searchSuggestion)
.not(),
)
}
}

@ -425,6 +425,15 @@ class SettingsRobot {
SettingsSubMenuHttpsOnlyModeRobot().interact()
return SettingsSubMenuHttpsOnlyModeRobot.Transition()
}
fun openExperimentsMenu(interact: SettingsSubMenuExperimentsRobot.() -> Unit): SettingsSubMenuExperimentsRobot.Transition {
scrollToElementByText("Nimbus Experiments")
fun nimbusExperimentsButton() = mDevice.findObject(textContains("Nimbus Experiments"))
nimbusExperimentsButton().click()
SettingsSubMenuExperimentsRobot().interact()
return SettingsSubMenuExperimentsRobot.Transition()
}
}
companion object {

@ -35,6 +35,7 @@ import org.mozilla.fenix.helpers.TestHelper
import org.mozilla.fenix.helpers.TestHelper.appName
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.helpers.click
import org.mozilla.fenix.settings.SupportUtils
import java.text.SimpleDateFormat
import java.time.LocalDateTime

@ -51,10 +51,10 @@ class SettingsSubMenuDeleteBrowsingDataOnQuitRobot {
openTabsCheckbox
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
browsingDataCheckbox
browsingHistoryCheckbox
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cookiesCheckbox
cookiesAndSiteDataCheckbox
.check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
onView(withText(R.string.preferences_delete_browsing_data_cookies_subtitle))
@ -107,10 +107,10 @@ private val deleteBrowsingOnQuitButton =
private val openTabsCheckbox =
onView(withText(R.string.preferences_delete_browsing_data_tabs_title_2))
private val browsingDataCheckbox =
onView(withText(R.string.preferences_delete_browsing_data_browsing_data_title))
private val browsingHistoryCheckbox =
onView(withText(R.string.preferences_delete_browsing_data_browsing_history_title))
private val cookiesCheckbox = onView(withText(R.string.preferences_delete_browsing_data_cookies))
private val cookiesAndSiteDataCheckbox = onView(withText(R.string.preferences_delete_browsing_data_cookies_and_site_data))
private val cachedFilesCheckbox =
onView(withText(R.string.preferences_delete_browsing_data_cached_files))

@ -9,7 +9,6 @@ import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.RootMatchers.isDialog
import androidx.test.espresso.matcher.ViewMatchers.Visibility
import androidx.test.espresso.matcher.ViewMatchers.hasSibling
import androidx.test.espresso.matcher.ViewMatchers.withChild
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.espresso.matcher.ViewMatchers.withEffectiveVisibility
import androidx.test.espresso.matcher.ViewMatchers.withId
@ -29,9 +28,6 @@ import org.mozilla.fenix.helpers.click
*/
class SettingsSubMenuDeleteBrowsingDataRobot {
fun verifyNavigationToolBarHeader() = assertNavigationToolBarHeader()
fun verifyDeleteBrowsingDataButton() = assertDeleteBrowsingDataButton()
fun verifyAllOptionsAndCheckBoxes() = assertAllOptionsAndCheckBoxes()
fun verifyAllCheckBoxesAreChecked() = assertAllCheckBoxesAreChecked()
fun verifyOpenTabsCheckBox(status: Boolean) = assertOpenTabsCheckBox(status)
fun verifyBrowsingHistoryDetails(status: Boolean) = assertBrowsingHistoryCheckBox(status)
@ -151,66 +147,30 @@ class SettingsSubMenuDeleteBrowsingDataRobot {
private fun goBackButton() =
onView(allOf(withContentDescription("Navigate up")))
private fun navigationToolBarHeader() =
onView(
allOf(
withId(R.id.navigationToolbar),
withChild(withText(R.string.preferences_delete_browsing_data)),
),
)
private fun deleteBrowsingDataButton() = onView(withId(R.id.delete_data))
private fun assertNavigationToolBarHeader() =
navigationToolBarHeader().check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
private fun assertDeleteBrowsingDataButton() =
deleteBrowsingDataButton().check((matches(withEffectiveVisibility(Visibility.VISIBLE))))
private fun dialogDeleteButton() = onView(withText("Delete")).inRoot(isDialog())
private fun dialogCancelButton() = onView(withText("Cancel")).inRoot(isDialog())
private fun openTabsSubsection() = onView(withText(R.string.preferences_delete_browsing_data_tabs_title_2))
private fun openTabsDescription(tabNumber: String) = onView(withText("$tabNumber tabs"))
private fun openTabsCheckBox() = onView(allOf(withId(R.id.checkbox), hasSibling(withText("Open tabs"))))
private fun browsingHistorySubsection() =
onView(withText(R.string.preferences_delete_browsing_data_browsing_data_title))
private fun browsingHistoryDescription(addresses: String) = mDevice.findObject(UiSelector().textContains("$addresses addresses"))
private fun browsingHistoryCheckBox() =
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Browsing history and site data"))))
private fun cookiesSubsection() =
onView(withText(R.string.preferences_delete_browsing_data_cookies))
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Browsing history"))))
private fun cookiesDescription() = onView(withText(R.string.preferences_delete_browsing_data_cookies_subtitle))
private fun cookiesCheckBox() =
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Cookies"))))
private fun cachedFilesSubsection() =
onView(withText(R.string.preferences_delete_browsing_data_cached_files))
private fun cachedFilesDescription() =
onView(withText(R.string.preferences_delete_browsing_data_cached_files_subtitle))
private fun cookiesAndSiteDataCheckBox() =
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Cookies and site data"))))
private fun cachedFilesCheckBox() =
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Cached images and files"))))
private fun sitePermissionsSubsection() =
onView(withText(R.string.preferences_delete_browsing_data_site_permissions))
private fun sitePermissionsCheckBox() =
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Site permissions"))))
private fun downloadsSubsection() =
onView(withText(R.string.preferences_delete_browsing_data_downloads))
private fun downloadsCheckBox() =
onView(allOf(withId(R.id.checkbox), hasSibling(withText("Downloads"))))
@ -218,29 +178,10 @@ private fun dialogMessage() =
onView(withText("$appName will delete the selected browsing data."))
.inRoot(isDialog())
private fun assertAllOptionsAndCheckBoxes() {
openTabsSubsection().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
openTabsDescription("0").check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
openTabsCheckBox().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
browsingHistorySubsection().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
assertTrue(browsingHistoryDescription("0").waitForExists(waitingTime))
browsingHistoryCheckBox().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cookiesSubsection().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cookiesDescription().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cookiesCheckBox().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cachedFilesSubsection().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cachedFilesDescription().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
cachedFilesCheckBox().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
sitePermissionsSubsection().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
sitePermissionsCheckBox().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
downloadsSubsection().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
downloadsCheckBox().check(matches(withEffectiveVisibility(Visibility.VISIBLE)))
}
private fun assertAllCheckBoxesAreChecked() {
openTabsCheckBox().assertIsChecked(true)
browsingHistoryCheckBox().assertIsChecked(true)
cookiesCheckBox().assertIsChecked(true)
cookiesAndSiteDataCheckBox().assertIsChecked(true)
cachedFilesCheckBox().assertIsChecked(true)
sitePermissionsCheckBox().assertIsChecked(true)
downloadsCheckBox().assertIsChecked(true)
@ -264,8 +205,8 @@ private fun clickOpenTabsCheckBox() = openTabsCheckBox().click()
private fun assertOpenTabsCheckBox(status: Boolean) = openTabsCheckBox().assertIsChecked(status)
private fun clickBrowsingHistoryCheckBox() = browsingHistoryCheckBox().click()
private fun assertBrowsingHistoryCheckBox(status: Boolean) = browsingHistoryCheckBox().assertIsChecked(status)
private fun clickCookiesCheckBox() = cookiesCheckBox().click()
private fun assertCookiesCheckBox(status: Boolean) = cookiesCheckBox().assertIsChecked(status)
private fun clickCookiesCheckBox() = cookiesAndSiteDataCheckBox().click()
private fun assertCookiesCheckBox(status: Boolean) = cookiesAndSiteDataCheckBox().assertIsChecked(status)
private fun clickCachedFilesCheckBox() = cachedFilesCheckBox().click()
private fun assertCachedFilesCheckBox(status: Boolean) = cachedFilesCheckBox().assertIsChecked(status)
private fun clickSitePermissionsCheckBox() = sitePermissionsCheckBox().click()

@ -0,0 +1,45 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ui.robots
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
import androidx.test.uiautomator.UiSelector
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.click
/**
* Implementation of Robot Pattern for the experiments sub menu.
*/
class SettingsSubMenuExperimentsRobot {
class Transition {
fun goBackToHomeScreen(interact: HomeScreenRobot.() -> Unit): HomeScreenRobot.Transition {
goBackButton().click()
HomeScreenRobot().interact()
return HomeScreenRobot.Transition()
}
fun goBack(interact: SettingsRobot.() -> Unit): SettingsRobot.Transition {
goBackButton().click()
SettingsRobot().interact()
return SettingsRobot.Transition()
}
}
private fun getExperiment(experimentName: String): UiSelector? {
return UiSelector().textContains(experimentName)
}
fun verifyExperimentExists(title: String) {
val experiment = getExperiment(title)
checkNotNull(experiment)
}
}
private fun goBackButton() = onView(withContentDescription(R.string.action_bar_up_description))

@ -70,7 +70,7 @@ class SettingsSubMenuSearchRobot {
}
fun verifySearchEnginesSectionHeader() {
onView(withText("Search Engines")).check(matches(isDisplayed()))
onView(withText("Search engines")).check(matches(isDisplayed()))
}
fun verifyDefaultSearchEngineHeader() {

@ -89,6 +89,20 @@ class TabDrawerRobot {
fun clickSyncedTabsButton() = syncedTabsButton().click()
fun verifyExistingOpenTabs(vararg titles: String) = assertExistingOpenTabs(*titles)
fun verifyOpenTabsOrder(position: Int, title: String) {
mDevice.findObject(
UiSelector()
.resourceId("$packageName:id/tab_item")
.childSelector(
UiSelector().textContains(title),
),
).getFromParent(
UiSelector()
.resourceId("$packageName:id/tab_tray_grid_item")
.index(position - 1),
)
}
fun verifyNoExistingOpenTabs(vararg titles: String) = assertNoExistingOpenTabs(*titles)
fun verifyCloseTabsButton(title: String) = assertCloseTabsButton(title)

@ -27,7 +27,6 @@ import androidx.test.uiautomator.Until
import org.hamcrest.Matchers.allOf
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.Constants.LONG_CLICK_DURATION
import org.mozilla.fenix.helpers.Constants.RETRY_COUNT
@ -112,7 +111,7 @@ class ThreeDotMenuMainRobot {
assertItemContainingTextExists(
settingsButton(),
)
if (FeatureFlags.print && FxNimbus.features.print.value().browserPrintEnabled) {
if (FxNimbus.features.print.value().browserPrintEnabled) {
assertItemContainingTextExists(printContentButton)
}
assertItemWithDescriptionExists(

@ -338,6 +338,13 @@
android:resource="@xml/search_widget_info" />
</receiver>
<receiver android:name=".onboarding.WidgetPinnedReceiver"
android:exported="true">
<intent-filter>
<action android:name="org.mozilla.fenix.onboarding.WidgetPinnedReceiver.widgetPinned"/>
</intent-filter>
</receiver>
<service android:name=".session.PrivateNotificationService"
android:exported="false" />

@ -63,11 +63,6 @@ object FeatureFlags {
*/
const val unifiedSearchSettings = true
/**
* Enables printing from the share and primary menu.
*/
val print = Config.channel.isNightlyOrDebug
/**
* Enables the lib-state HistoryFragment refactor
*/

@ -112,19 +112,10 @@ import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHO
import org.mozilla.fenix.wallpapers.Wallpaper
import java.util.UUID
import java.util.concurrent.TimeUnit
import kotlin.math.roundToLong
/**
* The actual RAM threshold is 2GB.
*
* To enable simpler reporting, we want to use the device's 'advertised' RAM.
* As [ActivityManager.MemoryInfo.totalMem] is not the device's 'advertised' RAM spec & we cannot
* access [ActivityManager.MemoryInfo.advertisedMem] across all Android versions, we will use a
* proxy value of 1.6GB. This is based on 1.5GB with a small 'excess' buffer. We assert that all
* values above this proxy value are 2GB or more.
*/
private const val RAM_THRESHOLD_PROXY_GB = 1.6F
private const val RAM_THRESHOLD_BYTES = RAM_THRESHOLD_PROXY_GB * (1e+9).toLong()
private const val RAM_THRESHOLD_MEGABYTES = 1024
private const val BYTES_TO_MEGABYTES_CONVERSION = 1024.0 * 1024.0
/**
*The main application class for Fenix. Records data to measure initialization performance.
@ -138,6 +129,10 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
private val logger = Logger("FenixApplication")
internal val isDeviceRamAboveThreshold by lazy {
isDeviceRamAboveThreshold()
}
open val components by lazy { Components(this) }
var visibilityLifecycleCallback: VisibilityLifecycleCallback? = null
@ -721,7 +716,6 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
settings: Settings,
browsersCache: BrowsersCache = BrowsersCache,
mozillaProductDetector: MozillaProductDetector = MozillaProductDetector,
isDeviceRamAboveThreshold: Boolean = isDeviceRamAboveThreshold(),
) {
setPreferenceMetrics(settings)
with(Metrics) {
@ -869,15 +863,18 @@ open class FenixApplication : LocaleAwareApplication(), Provider {
}
}
private fun deviceRamBytes(): Long {
private fun deviceRamApproxMegabytes(): Long {
val memoryInfo = ActivityManager.MemoryInfo()
val activityManager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
activityManager.getMemoryInfo(memoryInfo)
return memoryInfo.totalMem
val deviceRamBytes = memoryInfo.totalMem
return deviceRamBytes.toRoundedMegabytes()
}
private fun isDeviceRamAboveThreshold() = deviceRamBytes() > RAM_THRESHOLD_BYTES
private fun Long.toRoundedMegabytes(): Long = (this / BYTES_TO_MEGABYTES_CONVERSION).roundToLong()
private fun isDeviceRamAboveThreshold() = deviceRamApproxMegabytes() > RAM_THRESHOLD_MEGABYTES
@Suppress("ComplexMethod")
private fun setPreferenceMetrics(

@ -91,6 +91,7 @@ import org.mozilla.fenix.GleanMetrics.SplashScreen
import org.mozilla.fenix.GleanMetrics.StartOnHome
import org.mozilla.fenix.addons.AddonDetailsFragmentDirections
import org.mozilla.fenix.addons.AddonPermissionsDetailsFragmentDirections
import org.mozilla.fenix.addons.ExtensionProcessDisabledController
import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.browser.browsingmode.DefaultBrowsingModeManager
@ -192,6 +193,10 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
WebExtensionPopupFeature(components.core.store, ::openPopup)
}
private val extensionProcessDisabledPopupFeature by lazy {
ExtensionProcessDisabledController(this@HomeActivity, components.core.store)
}
private val serviceWorkerSupport by lazy {
ServiceWorkerSupportFeature(this)
}
@ -340,7 +345,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
supportActionBar?.hide()
lifecycle.addObservers(webExtensionPopupFeature, serviceWorkerSupport)
lifecycle.addObservers(webExtensionPopupFeature, extensionProcessDisabledPopupFeature, serviceWorkerSupport)
if (shouldAddToRecentsScreen(intent)) {
intent.removeExtra(START_IN_RECENTS_SCREEN)
@ -361,6 +366,11 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.core.contileTopSitesUpdater.startPeriodicWork()
}
if (settings().enableUnifiedSearchSettingsUI && !settings().hiddenEnginesRestored) {
settings().hiddenEnginesRestored = true
components.useCases.searchUseCases.restoreHiddenSearchEngines.invoke()
}
// To assess whether the Pocket stories are to be downloaded or not multiple SharedPreferences
// are read possibly needing to load them on the current thread. Move that to a background thread.
lifecycleScope.launch(IO) {
@ -939,6 +949,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
* value of [searchTermOrURL]).
*
* @param flags Flags that will be used when loading the URL (not applied to searches).
* @param additionalHeaders The extra headers to use when loading the URL.
*/
@Suppress("LongParameterList")
fun openToBrowserAndLoad(
@ -951,9 +962,19 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
requestDesktopMode: Boolean = false,
historyMetadata: HistoryMetadataKey? = null,
additionalHeaders: Map<String, String>? = null,
) {
openToBrowser(from, customTabSessionId)
load(searchTermOrURL, newTab, engine, forceSearch, flags, requestDesktopMode, historyMetadata)
load(
searchTermOrURL = searchTermOrURL,
newTab = newTab,
engine = engine,
forceSearch = forceSearch,
flags = flags,
requestDesktopMode = requestDesktopMode,
historyMetadata = historyMetadata,
additionalHeaders = additionalHeaders,
)
}
fun openToBrowser(from: BrowserDirection, customTabSessionId: String? = null) {
@ -1032,6 +1053,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
* @param flags Flags that will be used when loading the URL (not applied to searches).
* @param historyMetadata The [HistoryMetadataKey] of the new tab in case this tab
* was opened from history.
* @param additionalHeaders The extra headers to use when loading the URL.
*/
private fun load(
searchTermOrURL: String,
@ -1041,6 +1063,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
flags: EngineSession.LoadUrlFlags = EngineSession.LoadUrlFlags.none(),
requestDesktopMode: Boolean = false,
historyMetadata: HistoryMetadataKey? = null,
additionalHeaders: Map<String, String>? = null,
) {
val startTime = components.core.engine.profiler?.getProfilerTime()
val mode = browsingModeManager.mode
@ -1080,13 +1103,20 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
components.useCases.searchUseCases.newTabSearch
}
searchUseCase.invoke(
searchTermOrURL,
SessionState.Source.Internal.UserEntered,
true,
searchTerms = searchTermOrURL,
source = SessionState.Source.Internal.UserEntered,
selected = true,
searchEngine = engine,
flags = flags,
additionalHeaders = additionalHeaders,
)
} else {
components.useCases.searchUseCases.defaultSearch.invoke(searchTermOrURL, engine)
components.useCases.searchUseCases.defaultSearch.invoke(
searchTerms = searchTermOrURL,
searchEngine = engine,
flags = flags,
additionalHeaders = additionalHeaders,
)
}
}
@ -1119,11 +1149,7 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
}
open fun navigateToHome() {
if (components.fenixOnboarding.userHasBeenOnboarded()) {
navHost.navController.navigate(NavGraphDirections.actionStartupHome())
} else {
navHost.navController.navigate(NavGraphDirections.actionStartupOnboarding())
}
navHost.navController.navigate(NavGraphDirections.actionStartupHome())
}
override fun attachBaseContext(base: Context) {

@ -14,6 +14,7 @@ import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.VisibleForTesting
import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuHost
@ -32,6 +33,7 @@ import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.launch
import mozilla.components.concept.engine.webextension.WebExtensionInstallException
import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.AddonManager
import mozilla.components.feature.addons.AddonManagerException
import mozilla.components.feature.addons.ui.translateName
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
@ -46,6 +48,7 @@ import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getRootView
import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.runIfFragmentIsAttached
import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.ext.showToolbar
import org.mozilla.fenix.extension.WebExtensionPromptFeature
import org.mozilla.fenix.theme.ThemeManager
@ -97,7 +100,7 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
provideAddons = { addons!! },
context = requireContext(),
fragmentManager = parentFragmentManager,
view = view,
snackBarParentView = view,
onAddonChanged = {
runIfFragmentIsAttached {
adapter?.updateAddon(it)
@ -256,7 +259,10 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
lifecycleScope.launch(Dispatchers.Main) {
runIfFragmentIsAttached {
binding?.let {
showSnackBar(it.root, getString(R.string.mozac_feature_addons_failed_to_query_add_ons))
showSnackBar(
it.root,
getString(R.string.mozac_feature_addons_failed_to_query_add_ons),
)
}
isInstallationInProgress = false
binding?.addOnsProgressBar?.isVisible = false
@ -283,9 +289,9 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
}
@VisibleForTesting
internal fun showErrorSnackBar(text: String) {
internal fun showErrorSnackBar(text: String, anchorView: View? = this.view) {
runIfFragmentIsAttached {
view?.let {
anchorView?.let {
showSnackBar(it, text, FenixSnackbar.LENGTH_LONG)
}
}
@ -307,13 +313,27 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
)
}
@VisibleForTesting
internal fun provideAddonManger(): AddonManager {
return requireContext().components.addonManager
}
internal fun provideAccessibilityServicesEnabled(): Boolean {
return requireContext().settings().accessibilityServicesEnabled
}
internal fun installAddon(addon: Addon) {
requireContext().components.addonManager.installAddon(
binding?.addonProgressOverlay?.overlayCardView?.visibility = View.VISIBLE
if (provideAccessibilityServicesEnabled()) {
binding?.let { announceForAccessibility(it.addonProgressOverlay.addOnsOverlayText.text) }
}
val installOperation = provideAddonManger().installAddon(
addon,
onSuccess = {
runIfFragmentIsAttached {
isInstallationInProgress = false
adapter?.updateAddon(it)
binding?.addonProgressOverlay?.overlayCardView?.visibility = View.GONE
}
},
onError = { _, e ->
@ -321,20 +341,50 @@ class AddonsManagementFragment : Fragment(R.layout.fragment_add_ons_management)
// No need to display an error message if installation was cancelled by the user.
if (e !is CancellationException && e !is WebExtensionInstallException.UserCancelled) {
val rootView = activity?.getRootView() ?: view
var messageId = R.string.mozac_feature_addons_failed_to_install
if (e is WebExtensionInstallException.Blocklisted) {
messageId = R.string.mozac_feature_addons_blocklisted
}
context?.let {
showSnackBar(
rootView,
getString(
R.string.mozac_feature_addons_failed_to_install,
addon.translateName(it),
),
showErrorSnackBar(
text = getString(messageId, addon.translateName(it)),
anchorView = rootView,
)
}
}
binding?.addonProgressOverlay?.overlayCardView?.visibility = View.GONE
isInstallationInProgress = false
}
},
)
binding?.addonProgressOverlay?.cancelButton?.setOnClickListener {
lifecycleScope.launch(Dispatchers.Main) {
val safeBinding = binding
// Hide the installation progress overlay once cancellation is successful.
if (installOperation.cancel().await()) {
safeBinding?.addonProgressOverlay?.overlayCardView?.visibility = View.GONE
}
}
}
}
private fun announceForAccessibility(announcementText: CharSequence) {
val event = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
AccessibilityEvent(AccessibilityEvent.TYPE_ANNOUNCEMENT)
} else {
@Suppress("DEPRECATION")
AccessibilityEvent.obtain(AccessibilityEvent.TYPE_ANNOUNCEMENT)
}
binding?.addonProgressOverlay?.overlayCardView?.onInitializeAccessibilityEvent(event)
event.text.add(announcementText)
event.contentDescription = null
binding?.addonProgressOverlay?.overlayCardView?.let {
it.parent?.requestSendAccessibilityEvent(
it,
event,
)
}
}
companion object {

@ -0,0 +1,87 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.addons
import android.content.Context
import android.view.LayoutInflater
import android.widget.Button
import android.widget.TextView
import androidx.annotation.UiContext
import androidx.appcompat.app.AlertDialog
import mozilla.components.browser.state.action.ExtensionProcessDisabledPopupAction
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.Engine
import mozilla.components.support.ktx.android.content.appName
import mozilla.components.support.webextensions.ExtensionProcessDisabledPopupFeature
import org.mozilla.fenix.GleanMetrics.Addons
import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components
import org.mozilla.geckoview.WebExtensionController
/**
* Present a dialog to the user notifying of extension process spawning disabled and also asking
* whether they would like to continue trying or disable extensions. If the user chooses to retry,
* enable the extension process spawning with [WebExtensionController.enableExtensionProcessSpawning].
*
* @param context to show the AlertDialog
* @param store The [BrowserStore] which holds the state for showing the dialog
* @param webExtensionController to call when a user enables the process spawning
* @param builder to use for creating the dialog which can be styled as needed
* @param appName to be added to the message. Necessary to be added as a param for testing
*/
private fun presentDialog(
@UiContext context: Context,
store: BrowserStore,
engine: Engine,
builder: AlertDialog.Builder,
appName: String,
) {
val message = context.getString(R.string.addon_process_crash_dialog_message, appName)
var onDismissDialog: (() -> Unit)? = null
val layout = LayoutInflater.from(context)
.inflate(R.layout.crash_extension_dialog, null, false)
layout?.apply {
findViewById<TextView>(R.id.message)?.text = message
findViewById<Button>(R.id.positive)?.setOnClickListener {
engine.enableExtensionProcessSpawning()
Addons.extensionsProcessUiRetry.add()
store.dispatch(ExtensionProcessDisabledPopupAction(false))
onDismissDialog?.invoke()
}
findViewById<Button>(R.id.negative)?.setOnClickListener {
Addons.extensionsProcessUiDisable.add()
store.dispatch(ExtensionProcessDisabledPopupAction(false))
onDismissDialog?.invoke()
}
}
builder.apply {
setCancelable(false)
setView(layout)
setTitle(R.string.addon_process_crash_dialog_title)
}
val dialog = builder.show()
onDismissDialog = { dialog?.dismiss() }
}
/**
* Controller for showing the user a dialog when the the extension process spawning has been disabled.
*
* @param context to show the AlertDialog
* @param store The [BrowserStore] which holds the state for showing the dialog
* @param webExtensionController to call when a user enables the process spawning
* @param builder to use for creating the dialog which can be styled as needed
* @param appName to be added to the message. Optional and mainly relevant for testing
*/
class ExtensionProcessDisabledController(
@UiContext context: Context,
store: BrowserStore,
engine: Engine = context.components.core.engine,
builder: AlertDialog.Builder = AlertDialog.Builder(context),
appName: String = context.appName,
) : ExtensionProcessDisabledPopupFeature(
store,
{ presentDialog(context, store, engine, builder, appName) },
)

@ -104,6 +104,7 @@ import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.kotlin.getOrigin
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import mozilla.components.support.locale.ActivityContextWrapper
import mozilla.components.ui.widgets.withCenterAlignedButtons
import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.GleanMetrics.MediaState
@ -920,7 +921,7 @@ abstract class BaseBrowserFragment :
provideAddons = ::provideAddons,
context = requireContext(),
fragmentManager = parentFragmentManager,
view = view,
snackBarParentView = binding.dynamicSnackbarContainer,
),
owner = this,
view = view,
@ -1000,7 +1001,7 @@ abstract class BaseBrowserFragment :
}
create()
}.show().secure(activity)
}.show().withCenterAlignedButtons().secure(activity)
context.settings().incrementSecureWarningCount()
}

@ -50,6 +50,7 @@ import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.dialog.CookieBannerReEngagementDialogUtils
import org.mozilla.fenix.settings.quicksettings.protections.cookiebanners.getCookieBannerUIMode
import org.mozilla.fenix.shopping.DefaultShoppingExperienceFeature
import org.mozilla.fenix.shopping.ReviewQualityCheckFeature
import org.mozilla.fenix.shortcut.PwaOnboardingObserver
import org.mozilla.fenix.theme.ThemeManager
@ -225,6 +226,10 @@ class BrowserFragment : BaseBrowserFragment(), UserInteractionHandler {
reviewQualityCheckFeature.set(
feature = ReviewQualityCheckFeature(
browserStore = context.components.core.store,
shoppingExperienceFeature = DefaultShoppingExperienceFeature(
settings = requireContext().settings(),
),
onAvailabilityChange = { reviewQualityCheckAvailable = it },
),
owner = this,

@ -15,6 +15,7 @@ import kotlinx.coroutines.launch
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.feature.tab.collections.TabCollection
import mozilla.components.support.ktx.android.view.showKeyboard
import mozilla.components.ui.widgets.withCenterAlignedButtons
import org.mozilla.fenix.R
import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.ext.getDefaultCollectionNumber
@ -79,7 +80,7 @@ fun CollectionsDialog.show(
dialog.cancel()
}
val dialog = builder.create()
val dialog = builder.create().withCenterAlignedButtons()
val collectionNames =
arrayOf(context.getString(R.string.tab_tray_add_new_collection)) + collections
val collectionsListAdapter = CollectionsListAdapter(collectionNames) {
@ -126,7 +127,7 @@ internal fun CollectionsDialog.showAddNewDialog(
onNegativeButtonClick.invoke()
dialog.cancel()
}
.create()
.create().withCenterAlignedButtons()
.show()
collectionNameEditText.setSelection(0, collectionNameEditText.text.length)

@ -136,7 +136,7 @@ class Components(private val context: Context) {
}
val addonManager by lazyMonitored {
AddonManager(core.store, core.engine, addonCollectionProvider, addonUpdater)
AddonManager(core.store, core.engine, addonsProvider, addonUpdater)
}
val analytics by lazyMonitored { Analytics(context) }

@ -4,6 +4,8 @@
package org.mozilla.fenix.components
import androidx.annotation.MainThread
import androidx.annotation.VisibleForTesting
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStoreOwner
@ -33,6 +35,7 @@ class StoreProvider<T : Store<*, *>>(
*
* @param createStore Callback to create a new [Store], used when the [ViewModel] is first created.
*/
@VisibleForTesting
class StoreProviderFactory<T : Store<*, *>>(
private val createStore: () -> T,
) : ViewModelProvider.Factory {
@ -42,3 +45,17 @@ class StoreProviderFactory<T : Store<*, *>>(
return StoreProvider(createStore()) as VM
}
}
/**
* Helper function for lazy creation of a [Store] instance scoped to a [ViewModelStoreOwner].
*
* @param createStore Function that creates a [Store] instance.
*/
@MainThread
fun <T : Store<*, *>> ViewModelStoreOwner.lazyStore(
createStore: () -> T,
): Lazy<T> {
return lazy(mode = LazyThreadSafetyMode.NONE) {
StoreProvider.get(this, createStore)
}
}

@ -1,3 +1,7 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components.metrics
import android.content.Context

@ -29,6 +29,7 @@ import mozilla.components.feature.top.sites.PinnedSiteStorage
import mozilla.components.feature.top.sites.TopSite
import mozilla.components.service.glean.private.NoExtras
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.ui.widgets.withCenterAlignedButtons
import org.mozilla.fenix.GleanMetrics.AppMenu
import org.mozilla.fenix.GleanMetrics.Collections
import org.mozilla.fenix.GleanMetrics.Events
@ -266,7 +267,7 @@ class DefaultBrowserToolbarMenuController(
setPositiveButton(R.string.top_sites_max_limit_confirmation_button) { dialog, _ ->
dialog.dismiss()
}
create()
create().withCenterAlignedButtons()
}.show()
} else {
ioScope.launch {

@ -35,7 +35,6 @@ import mozilla.components.feature.webcompat.reporter.WebCompatReporterFeature
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.kotlinx.coroutines.flow.ifAnyChanged
import org.mozilla.fenix.FeatureFlags
import org.mozilla.fenix.R
import org.mozilla.fenix.components.accounts.FenixAccountManager
import org.mozilla.fenix.ext.components
@ -393,7 +392,7 @@ open class DefaultToolbarMenu(
installToHomescreen.apply { visible = ::canInstall },
if (shouldShowTopSites) addRemoveTopSitesItem else null,
saveToCollectionItem,
if (FeatureFlags.print && FxNimbus.features.print.value().browserPrintEnabled) printPageItem else null,
if (FxNimbus.features.print.value().browserPrintEnabled) printPageItem else null,
BrowserMenuDivider(),
settingsItem,
if (shouldDeleteDataOnQuit) deleteDataOnQuit else null,

@ -0,0 +1,83 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.button.TextButton
import org.mozilla.fenix.theme.FirefoxTheme
/**
* Default layout for a Banner messaging surface with two text buttons.
*
* @param message The primary text displayed to the user.
* @param button1Text The text of the first button.
* @param button2Text The text of the second button.
* @param onButton1Click Invoked when the first button is clicked.
* @param onButton2Click Invoked when the second button is clicked.
*/
@Composable
fun Banner(
message: String,
button1Text: String,
button2Text: String,
onButton1Click: () -> Unit,
onButton2Click: () -> Unit,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(FirefoxTheme.colors.layer1)
.padding(all = 16.dp),
) {
Text(
text = message,
color = FirefoxTheme.colors.textPrimary,
style = FirefoxTheme.typography.body2,
)
Spacer(modifier = Modifier.height(12.dp))
Row(modifier = Modifier.align(Alignment.End)) {
TextButton(
text = button1Text,
onClick = onButton2Click,
)
Spacer(modifier = Modifier.width(12.dp))
TextButton(
text = button2Text,
onClick = onButton1Click,
)
}
}
}
@LightDarkPreview
@Composable
private fun BannerPreview() {
FirefoxTheme {
Banner(
message = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sodales laoreet commodo.",
button1Text = "Button 1",
button2Text = "Button 2",
onButton1Click = {},
onButton2Click = {},
)
}
}

@ -12,10 +12,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import org.mozilla.fenix.theme.FirefoxTheme
/**
@ -29,9 +29,11 @@ import org.mozilla.fenix.theme.FirefoxTheme
* @param clickableEndIndex [text] index at which the URL substring ends.
* @param onClick Callback to be invoked only when the URL substring is clicked.
*/
@Deprecated("Use LinkText instead", ReplaceWith("LinkText", "org.mozilla.fenix.compose.LinkText"))
@Composable
fun ClickableSubstringLink(
text: String,
textStyle: TextStyle = FirefoxTheme.typography.caption,
textColor: Color = FirefoxTheme.colors.textPrimary,
linkTextColor: Color = FirefoxTheme.colors.textAccent,
linkTextDecoration: TextDecoration? = null,
@ -63,12 +65,6 @@ fun ClickableSubstringLink(
end = text.length,
)
addStyle(
SpanStyle(fontSize = 12.sp),
start = 0,
end = clickableEndIndex,
)
addStringAnnotation(
tag = "link",
annotation = "",
@ -79,6 +75,7 @@ fun ClickableSubstringLink(
ClickableText(
text = annotatedText,
style = textStyle,
onClick = {
annotatedText
.getStringAnnotations("link", it, it)
@ -90,6 +87,7 @@ fun ClickableSubstringLink(
}
@Composable
@Suppress("Deprecation")
@Preview
private fun ClickableSubstringTextPreview() {
val text = "This text contains a link"

@ -0,0 +1,181 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.compose
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.semantics.Role
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import mozilla.components.support.ktx.android.content.isScreenReaderEnabled
import org.mozilla.fenix.theme.FirefoxTheme
/**
* The tag used for links in the text for annotated strings.
*/
private const val URL_TAG = "URL_TAG"
/**
* Model containing link text, url and action.
*
* @property text Substring of the text passed to [LinkText] to be displayed as clickable link.
* @property url Url the link should point to.
* @property onClick Callback to be invoked when link is clicked.
*/
data class LinkTextState(
val text: String,
val url: String,
val onClick: (String) -> Unit,
)
/**
* A composable for displaying text that contains a clickable link text.
*
* @param text The complete text.
* @param linkTextState The clickable part of the text.
* @param style [TextStyle] applied to the text.
* @param linkTextColor [Color] applied to the clickable part of the text.
* @param linkTextDecoration [TextDecoration] applied to the clickable part of the text.
*/
@Composable
fun LinkText(
text: String,
linkTextState: LinkTextState,
style: TextStyle = FirefoxTheme.typography.body2.copy(
textAlign = TextAlign.Center,
color = FirefoxTheme.colors.textSecondary,
),
linkTextColor: Color = FirefoxTheme.colors.textAccent,
linkTextDecoration: TextDecoration? = null,
) {
val context = LocalContext.current
val annotatedString = buildAnnotatedString {
val startIndex = text.indexOf(linkTextState.text, ignoreCase = true)
val endIndex = startIndex + linkTextState.text.length
append(text)
addStyle(
style = SpanStyle(
color = linkTextColor,
textDecoration = linkTextDecoration,
),
start = startIndex,
end = endIndex,
)
addStringAnnotation(
tag = URL_TAG,
annotation = linkTextState.url,
start = startIndex,
end = endIndex,
)
}
// When using UrlAnnotation, talkback shows links in a separate dialog and
// opens them in the default browser. Since this component allows the caller to define the
// onClick behaviour - e.g. to open the link in in-app custom tab, here StringAnnotation is used
// and modifier is enabled with Role.Button when screen reader is enabled.
ClickableText(
text = annotatedString,
style = style,
modifier = Modifier.clickable(
enabled = context.isScreenReaderEnabled,
role = Role.Button,
onClickLabel = linkTextState.text,
onClick = { linkTextState.onClick(linkTextState.url) },
),
onClick = {
if (!context.isScreenReaderEnabled) {
val range: AnnotatedString.Range<String>? =
annotatedString.getStringAnnotations(URL_TAG, it, it).firstOrNull()
range?.let { stringAnnotation ->
linkTextState.onClick(stringAnnotation.item)
}
}
},
)
}
@Preview
@Composable
private fun LinkTextEndPreview() {
val state = LinkTextState(
text = "click here",
url = "www.mozilla.com",
onClick = {},
)
FirefoxTheme {
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
LinkText(text = "This is normal text, click here", linkTextState = state)
}
}
}
@Preview
@Composable
private fun LinkTextMiddlePreview() {
val state = LinkTextState(
text = "clickable text",
url = "www.mozilla.com",
onClick = {},
)
FirefoxTheme {
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
LinkText(text = "This is clickable text, followed by normal text", linkTextState = state)
}
}
}
@Preview
@Composable
private fun LinkTextStyledPreview() {
val state = LinkTextState(
text = "clickable text",
url = "www.mozilla.com",
onClick = {},
)
FirefoxTheme {
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
LinkText(
text = "This is clickable text, in a different style",
linkTextState = state,
style = FirefoxTheme.typography.headline5,
)
}
}
}
@Preview
@Composable
private fun LinkTextClickStyledPreview() {
val state = LinkTextState(
text = "clickable text",
url = "www.mozilla.com",
onClick = {},
)
FirefoxTheme {
Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) {
LinkText(
text = "This is clickable text, with underlined text",
linkTextState = state,
style = FirefoxTheme.typography.headline5,
linkTextColor = FirefoxTheme.colors.textOnColorSecondary,
linkTextDecoration = TextDecoration.Underline,
)
}
}
}

@ -26,6 +26,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.compose.ext.thenConditional
import org.mozilla.fenix.theme.FirefoxTheme
const val ITEM_WIDTH = 328
@ -141,8 +142,12 @@ fun ListItemTabSurface(
onClick: (() -> Unit)? = null,
tabDetails: @Composable () -> Unit,
) {
var modifier = Modifier.size(ITEM_WIDTH.dp, ITEM_HEIGHT.dp)
if (onClick != null) modifier = modifier.then(Modifier.clickable { onClick() })
val modifier = Modifier
.size(ITEM_WIDTH.dp, ITEM_HEIGHT.dp)
.thenConditional(
modifier = Modifier.clickable { onClick!!() },
predicate = { onClick != null },
)
Card(
modifier = modifier,

@ -16,14 +16,15 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import mozilla.components.browser.state.state.TabSessionState
import mozilla.components.browser.state.state.createTab
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.theme.FirefoxTheme
private const val THUMBNAIL_SIZE = 108
private const val FALLBACK_ICON_SIZE = 36
/**
@ -31,9 +32,10 @@ private const val FALLBACK_ICON_SIZE = 36
* will be displayed until the thumbnail is loaded.
*
* @param tab The given [TabSessionState] to render a thumbnail for.
* @param size [Dp] size of the thumbnail.
* @param backgroundColor [Color] used for the background of the favicon.
* @param storage [ThumbnailStorage] to obtain tab thumbnail bitmaps from.
* @param size Size of the thumbnail.
* @param modifier [Modifier] used to draw the image content.
* @param backgroundColor [Color] used for the background of the favicon.
* @param contentDescription Text used by accessibility services
* to describe what this image represents.
* @param contentScale [ContentScale] used to draw image content.
@ -43,8 +45,9 @@ private const val FALLBACK_ICON_SIZE = 36
@Suppress("LongParameterList")
fun TabThumbnail(
tab: TabSessionState,
storage: ThumbnailStorage,
size: Int,
modifier: Modifier = Modifier,
size: Dp = THUMBNAIL_SIZE.dp,
backgroundColor: Color = FirefoxTheme.colors.layer2,
contentDescription: String? = null,
contentScale: ContentScale = ContentScale.FillWidth,
@ -55,8 +58,11 @@ fun TabThumbnail(
backgroundColor = backgroundColor,
) {
ThumbnailImage(
key = tab.id,
size = size,
request = ImageLoadRequest(
id = tab.id,
size = size,
),
storage = storage,
modifier = modifier,
contentScale = contentScale,
alignment = alignment,
@ -93,8 +99,10 @@ private fun ThumbnailCardPreview() {
FirefoxTheme {
TabThumbnail(
tab = createTab(url = "www.mozilla.com", title = "Mozilla"),
size = 108,
storage = ThumbnailStorage(LocalContext.current),
modifier = Modifier
.size(THUMBNAIL_SIZE.dp, 80.dp)
.size(108.dp, 80.dp)
.clip(RoundedCornerShape(8.dp)),
)
}

@ -16,12 +16,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import mozilla.components.browser.icons.compose.Loader
import mozilla.components.browser.icons.compose.Placeholder
import mozilla.components.browser.icons.compose.WithIcon
import mozilla.components.browser.thumbnails.storage.ThumbnailStorage
import mozilla.components.concept.base.images.ImageLoadRequest
import org.mozilla.fenix.components.components
import org.mozilla.fenix.theme.FirefoxTheme
@ -33,10 +35,10 @@ private const val FALLBACK_ICON_SIZE = 36
* will be displayed until the thumbnail is loaded.
*
* @param url Url to display thumbnail for.
* @param key Key used to remember the thumbnail for future compositions.
* @param size [Dp] size of the thumbnail.
* @param backgroundColor [Color] used for the background of the favicon.
* @param request [ImageLoadRequest] used to fetch the thumbnail bitmap.
* @param storage [ThumbnailStorage] to obtain tab thumbnail bitmaps from.
* @param modifier [Modifier] used to draw the image content.
* @param backgroundColor [Color] used for the background of the favicon.
* @param contentDescription Text used by accessibility services
* to describe what this image represents.
* @param contentScale [ContentScale] used to draw image content.
@ -45,10 +47,10 @@ private const val FALLBACK_ICON_SIZE = 36
@Composable
fun ThumbnailCard(
url: String,
key: String,
size: Dp = THUMBNAIL_SIZE.dp,
backgroundColor: Color = FirefoxTheme.colors.layer2,
request: ImageLoadRequest,
storage: ThumbnailStorage,
modifier: Modifier = Modifier,
backgroundColor: Color = FirefoxTheme.colors.layer2,
contentDescription: String? = null,
contentScale: ContentScale = ContentScale.FillWidth,
alignment: Alignment = Alignment.TopCenter,
@ -58,8 +60,8 @@ fun ThumbnailCard(
backgroundColor = backgroundColor,
) {
ThumbnailImage(
key = key,
size = size,
request = request,
storage = storage,
modifier = modifier,
contentScale = contentScale,
alignment = alignment,
@ -95,7 +97,8 @@ private fun ThumbnailCardPreview() {
FirefoxTheme {
ThumbnailCard(
url = "https://mozilla.com",
key = "123",
request = ImageLoadRequest("123", THUMBNAIL_SIZE),
storage = ThumbnailStorage(LocalContext.current),
modifier = Modifier
.size(THUMBNAIL_SIZE.dp)
.clip(RoundedCornerShape(8.dp)),

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save