Bug 1961377 - Adding POM Scaffolding r=ajoltan

This is the initial scaffolding for setting up the use of a Page Object Model design.

These intial helper modules prepare the creation of Test Factories leveraging data-driven testing patterns and test runtime configuration for dynamic test creation and execution.

This prepares the way for targeted test suites on an ad-hoc basis per the needs of the state and context-under-test.

Differential Revision: https://phabricator.services.mozilla.com/D246044
This commit is contained in:
Jackie Johnson
2025-05-19 09:19:57 +00:00
committed by ajoltan@mozilla.com
parent e10114d313
commit c1e4aedbd6
41 changed files with 1484 additions and 2 deletions

View File

@@ -0,0 +1,82 @@
# Android Test Automation Framework (POM-Based)
## 🚀 Introduction
This framework introduces a modern, scalable approach to Android UI testing using the Page Object Model (POM). It combines Espresso, UIAutomator2, and Jetpack Compose testing under a single unified abstraction, making test code clean, reusable, and easy to maintain.
Tests written with this system read like user journeys, not code. Teams can define pages once and reuse them throughout tests—encouraging ownership, modularity, and clarity across features.
## 🛠️ Getting Started
### Prerequisites
- Android Studio Arctic Fox or newer
- Kotlin 1.9+
- Compose UI Test library
- Enabled Test Orchestrator (optional for retries)
### Writing Your First Test
```kotlin
@Test
fun verifyHomeLoads() {
on.homePage.navigateToPage()
.mozVerifyElementsByGroup("requiredForPage")
}
```
### Structure Overview
- `BasePage.kt`: Common actions (clicks, verification, navigation)
- `Selector.kt`: Describes UI elements in a flexible, tool-agnostic way
- `PageContext.kt`: Entry point for test pages via `on.<Page>`
- `NavigationRegistry.kt`: Stores how to move between pages
## 🧪 Test Development Workflow
1. **Define Selectors**
```kotlin
val TITLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Welcome",
description = "Welcome title on Home Page",
groups = listOf("requiredForPage")
)
```
2. **Create a Page Object**
```kotlin
object HomePage : BasePage() {
override val pageName = "HomePage"
override fun mozGetSelectorsByGroup(group: String) = HomePageSelectors.all.filter {
it.groups.contains(group)
}
}
```
3. **Add to Context**
```kotlin
val homePage = HomePage
```
4. **Write a Test**
```kotlin
on.homePage.navigateToPage()
.mozVerifyElementsByGroup("requiredForPage")
```
## 📚 Additional Resources
- 📖 [Test Automation Strategy](./docs/TestAutomationStrategy.md): Roadmap for phases and long-term goals
- 💡 Example tests: See `ui/efficiency/tests/`
- 📎 Diagrams: (Coming soon)
## 👥 Contributing
When adding new pages or selectors:
- Follow the fluent interface pattern
- Group selectors meaningfully (e.g., `"requiredForPage"`, `"toolbar"`)
- Register navigation paths explicitly in each `Page`'s `init` block
## ✅ Best Practices
- Use clear, readable selector descriptions
- Avoid direct interaction with Espresso/UIAutomator in tests
- Build tests in Given/When/Then structure
---
This framework enables powerful, flexible testing—but starts simple. With it, we empower teams to own their features *and* their tests.

View File

@@ -0,0 +1,114 @@
# Test Automation as a Service: Multi-Phase Strategy
## Overview
This document outlines a multi-phase strategy for building a scalable, maintainable, and dynamic Android test automation system. The goal is to support testing at scale—ranging from dozens to tens of thousands of tests—while enabling individual development teams to own their test logic confidently.
The system is designed not just to enable automated UI tests, but to act as a test orchestration framework that can:
- Generate tests dynamically
- Support efficient test suite creation and execution
- Optimize coverage with minimal redundancy
- Allow runtime customization of test input, state, and configuration
## Summary of Phases
### **Phase 0: Ad-Hoc Testing**
- Common pattern seen in early automation efforts or tutorials
- Tests are brittle, hard to maintain
- Difficult to scale beyond ~100-200 tests
### **Phase 1: Standardization with Page Object Model (POM)**
- Introduce BasePage + Selector abstractions for cross-device/platform consistency
- Define navigation flows using a navigation graph (nodes = pages, edges = steps)
- Fluent interface pattern for clean and readable test flows
- Reuse components and improve stability
- Targets ~1,000 to ~2,000 tests
**Key Outcomes:**
- Reduced maintenance overhead
- Stable and composable test structure
- Foundation for dynamic navigation and grouped element validation
### **Phase 2: Data-Driven Tests & Dynamic Test Factories**
- Use metadata and reflection to define what each test requires
- Centralized test data service that dynamically prepares:
- Test data
- Test environments
- Targeted test suites based on code changes
- Enables composable, on-demand test generation
- Replaces tags and decorators with runtime configuration
- Coverage optimization using Orthogonal Arrays (e.g. pairwise testing)
**Key Outcomes:**
- Tests generated from config or runtime context
- Faster feedback cycles by running most relevant tests first
- Easier root cause analysis in CI
- Teams can define and manage their own test scope
### **Phase 3: Test Factory of Test Factories**
- Fully leverage standardized infrastructure from Phases 12
- Enable AI and/or rule-based helpers to:
- Propose tests based on heuristics or code changes
- Self-heal flaking or broken tests
- Minimize human intervention while scaling coverage
- Libraries for setup, steps, and assertions are highly structured
- Fluent interfaces make automation predictable and parseable
**Key Outcomes:**
- Automation becomes intelligent and context-aware
- Reduced cost of test maintenance and authoring
- Human involvement focused on novel or edge-case testing
## Current State (Phase 1 MVP)
### Helper Modules Implemented:
- `BasePage.kt`: Common logic for all screens (navigation, verification, interaction)
- `Selector.kt`: Declarative wrapper around UI selectors with grouping and description
- `NavigationRegistry.kt`: Graph structure for defining navigation steps between pages
- `PageContext.kt`: Central access point to all page objects
- `BaseTest.kt`: Test rule harness including Compose + Retry rules
**Why this matters:**
These modules abstract away repetitive test logic and create a clean DSL-like interface for writing readable, robust, and maintainable UI tests.
```kotlin
on.settingsPage.navigateToPage()
.mozVerifyElementsByGroup("requiredForPage")
```
## How This Enables the Future
- Pages describe their *structure* via selectors
- Navigation is described declaratively
- Test code focuses only on *what to test*, not *how to get there*
- Data-driven factories can then define *what to test* programmatically
## Low-Level Developer Guidelines
### Writing a New Test
- Extend `BaseTest`
- Use `on.<Page>.navigateToPage()` to transition
- Use `.mozVerifyElementsByGroup("...group...")` to check structure
- Use `.mozClick(selector)` or define `NavigationStep.Click()` to interact
### Adding a New Page
- Create new object that extends `BasePage`
- Implement `mozGetSelectorsByGroup()`
- Register all navigation edges in `init {}` block
### Creating a Dynamic Test
- Define input data and selectors declaratively
- Feed test data into a factory that builds navigation + verification steps
---
## Final Thoughts
This framework is not just about making UI tests easier. Its about enabling:
- Reusable automation primitives
- Team autonomy
- Data- and AI-assisted test generation
- Long-term maintainability
It positions test automation as a *strategic capability*, not just a tactical tool.
---
*For more implementation details, see internal documentation and examples in the `helpers/`, `navigation/`, and `pageObjects/` packages.*

View File

@@ -0,0 +1,285 @@
/* 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.efficiency.helpers
import android.util.Log
import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.compose.ui.test.onNodeWithTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.ViewInteraction
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.assertion.ViewAssertions.matches
import androidx.test.espresso.matcher.ViewMatchers.isDisplayed
import androidx.test.espresso.matcher.ViewMatchers.withContentDescription
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.UiSelector
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.TestHelper.mDevice
import org.mozilla.fenix.helpers.TestHelper.packageName
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
abstract class BasePage(
protected val composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>,
) {
abstract val pageName: String
open fun navigateToPage(): BasePage {
if (mozWaitForPageToLoad()) {
PageStateTracker.currentPageName = pageName
return this
}
val fromPage = PageStateTracker.currentPageName
Log.i("PageNavigation", "🔍 Trying to find path from '$fromPage' to '$pageName'")
val path = NavigationRegistry.findPath(fromPage, pageName)
if (path == null) {
NavigationRegistry.logGraph()
throw AssertionError("❌ No navigation path found from '$fromPage' to '$pageName'")
} else {
Log.i("PageNavigation", "✅ Navigation path found from '$fromPage' to '$pageName':")
path.forEachIndexed { i, step -> Log.i("PageNavigation", " Step ${i + 1}: $step") }
}
path.forEach { step ->
when (step) {
is NavigationStep.Click -> mozClick(step.selector)
is NavigationStep.Swipe -> mozSwipeTo(step.selector, step.direction)
}
}
if (!mozWaitForPageToLoad()) {
throw AssertionError("Failed to navigate to $pageName")
}
PageStateTracker.currentPageName = pageName
return this
}
private fun mozWaitForPageToLoad(timeout: Long = 10_000, interval: Long = 500): Boolean {
val requiredSelectors = mozGetSelectorsByGroup("requiredForPage")
val deadline = System.currentTimeMillis() + timeout
while (System.currentTimeMillis() < deadline) {
if (requiredSelectors.all { mozVerifyElement(it) }) {
return true
}
android.os.SystemClock.sleep(interval)
}
return false
}
abstract fun mozGetSelectorsByGroup(group: String = "requiredForPage"): List<Selector>
fun mozVerifyElementsByGroup(group: String = "requiredForPage"): BasePage {
val selectors = mozGetSelectorsByGroup(group)
val allPresent = selectors.all { mozVerifyElement(it) }
if (!allPresent) {
throw AssertionError("Not all elements in group '$group' are present")
}
return this
}
fun mozClick(selector: Selector): BasePage {
val element = mozGetElement(selector)
if (element == null) {
throw AssertionError("Element not found for selector: ${selector.description} (${selector.strategy} -> ${selector.value})")
}
when (element) {
is ViewInteraction -> {
try {
element.perform(click())
} catch (e: Exception) {
throw AssertionError("Failed to click on Espresso element for selector: ${selector.description}", e)
}
}
is UiObject -> {
if (!element.exists()) {
throw AssertionError("UiObject does not exist for selector: ${selector.description}")
}
if (!element.click()) {
throw AssertionError("Failed to click on UiObject for selector: ${selector.description}")
}
}
is SemanticsNodeInteraction -> {
try {
element.assertExists()
element.assertIsDisplayed()
element.performClick()
} catch (e: Exception) {
throw AssertionError("Failed to click on Compose node for selector: ${selector.description}", e)
}
}
else -> {
throw AssertionError("Unsupported element type (${element::class.simpleName}) for selector: ${selector.description}")
}
}
return this
}
fun mozSwipeTo(
selector: Selector,
direction: SwipeDirection = SwipeDirection.DOWN,
maxSwipes: Int = 5,
): BasePage {
repeat(maxSwipes) { attempt ->
val element = mozGetElement(selector)
val isVisible = when (element) {
is ViewInteraction -> try {
element.check(matches(isDisplayed()))
true
} catch (_: Exception) {
false
}
is UiObject -> element.exists()
is SemanticsNodeInteraction -> try {
element.assertExists()
element.assertIsDisplayed()
true
} catch (_: AssertionError) {
false
}
else -> false
}
if (isVisible) {
Log.i("MozSwipeTo", "✅ Element '${selector.description}' found after $attempt swipe(s)")
return this
}
Log.i("MozSwipeTo", "🔄 Swipe attempt ${attempt + 1} for selector '${selector.description}'")
performSwipe(direction)
Thread.sleep(500)
}
throw AssertionError("❌ Element '${selector.description}' not found after $maxSwipes swipe(s)")
}
private fun performSwipe(direction: SwipeDirection) {
val height = mDevice.displayHeight
val width = mDevice.displayWidth
val (startX, startY, endX, endY) = when (direction) {
SwipeDirection.UP -> listOf(width / 2, height / 2, width / 2, height / 4)
SwipeDirection.DOWN -> listOf(width / 2, height / 2, width / 2, height * 3 / 4)
SwipeDirection.LEFT -> listOf(width * 3 / 4, height / 2, width / 4, height / 2)
SwipeDirection.RIGHT -> listOf(width / 4, height / 2, width * 3 / 4, height / 2)
}
mDevice.swipe(startX, startY, endX, endY, 10)
}
private fun mozGetElement(selector: Selector): Any? {
if (selector.value.isBlank()) {
Log.i("mozGetElement", "Empty or blank selector value: ${selector.description}")
return null
}
return when (selector.strategy) {
SelectorStrategy.COMPOSE_BY_TAG -> {
try {
composeRule.onNodeWithTag(selector.value)
} catch (e: Exception) {
Log.i("mozGetElement", "Compose node not found for tag: ${selector.value}")
null
}
}
SelectorStrategy.COMPOSE_BY_TEXT -> {
try {
composeRule.onNodeWithText(selector.value, useUnmergedTree = true)
} catch (e: Exception) {
Log.i("mozGetElement", "Compose node not found for text: ${selector.value}")
null
}
}
SelectorStrategy.ESPRESSO_BY_ID -> {
val resId = selector.toResourceId()
if (resId == 0) {
Log.i("mozGetElement", "Invalid resource ID for: ${selector.value}")
null
} else {
onView(withId(resId))
}
}
SelectorStrategy.ESPRESSO_BY_TEXT -> onView(withText(selector.value))
SelectorStrategy.ESPRESSO_BY_CONTENT_DESC -> onView(withContentDescription(selector.value))
SelectorStrategy.UIAUTOMATOR2_BY_CLASS -> {
val obj = mDevice.findObject(UiSelector().className(selector.value))
if (!obj.exists()) null else obj
}
SelectorStrategy.UIAUTOMATOR2_BY_TEXT -> {
val obj = mDevice.findObject(UiSelector().text(selector.value))
if (!obj.exists()) null else obj
}
SelectorStrategy.UIAUTOMATOR2_BY_RES -> {
val obj = mDevice.findObject(By.res(selector.value))
if (obj == null) {
Log.i("MozGetElement", "mozGetElement: UIObject2 not found for res: ${selector.value}")
null
} else { obj }
}
SelectorStrategy.UIAUTOMATOR2_BY_RES_ID -> {
val fullResId = "$packageName:${selector.value}"
val obj = mDevice.findObject(UiSelector().resourceId(fullResId))
if (!obj.exists()) null else obj
}
}
}
private fun mozVerifyElement(selector: Selector): Boolean {
val element = mozGetElement(selector)
return when (element) {
is ViewInteraction -> {
try {
element.check(matches(isDisplayed()))
true
} catch (e: Exception) {
false
}
}
is UiObject -> element.exists()
is SemanticsNodeInteraction -> {
try {
element.assertExists()
element.assertIsDisplayed()
true
} catch (e: AssertionError) {
false
}
}
else -> false
}
}
}

View File

@@ -0,0 +1,44 @@
/* 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.efficiency.helpers
import android.util.Log
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.junit.Before
import org.junit.Rule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.helpers.RetryTestRule
import org.mozilla.fenix.helpers.TestSetup
abstract class BaseTest(
private val skipOnboarding: Boolean = true,
private val isMenuRedesignEnabled: Boolean = true,
private val isMenuRedesignCFREnabled: Boolean = false,
private val isPageLoadTranslationsPromptEnabled: Boolean = false,
) : TestSetup() {
@get:Rule(order = 0)
val composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *> =
AndroidComposeTestRule(
HomeActivityIntentTestRule(
skipOnboarding = skipOnboarding,
isMenuRedesignEnabled = isMenuRedesignEnabled,
isMenuRedesignCFREnabled = isMenuRedesignCFREnabled,
isPageLoadTranslationsPromptEnabled = isPageLoadTranslationsPromptEnabled,
),
) { it.activity }
protected val on: PageContext = PageContext(composeRule)
@get:Rule(order = 1)
val retryTestRule = RetryTestRule(3)
@Before
override fun setUp() {
super.setUp()
PageStateTracker.currentPageName = "AppEntry"
Log.i("BaseTest", "🚀 Starting test with page: AppEntry")
}
}

View File

@@ -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.efficiency.helpers
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.pageObjects.HomePage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsAccessibilityPage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsAutofillPage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsCustomizePage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsHomepagePage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsPage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsPasswordsPage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsSearchPage
import org.mozilla.fenix.ui.efficiency.pageObjects.SettingsTabsPage
class PageContext(val composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) {
val home = HomePage(composeRule)
val settings = SettingsPage(composeRule)
val settingsAccessibility = SettingsAccessibilityPage(composeRule)
val settingsAutofill = SettingsAutofillPage(composeRule)
val settingsCustomize = SettingsCustomizePage(composeRule)
val settingsHomepage = SettingsHomepagePage(composeRule)
val settingsPasswords = SettingsPasswordsPage(composeRule)
val settingsSearch = SettingsSearchPage(composeRule)
val settingsTabs = SettingsTabsPage(composeRule)
fun initTestRule(
skipOnboarding: Boolean = true,
isMenuRedesignEnabled: Boolean = true,
isMenuRedesignCFREnabled: Boolean = false,
isPageLoadTranslationsPromptEnabled: Boolean = false,
): AndroidComposeTestRule<HomeActivityIntentTestRule, *> {
return AndroidComposeTestRule(
HomeActivityIntentTestRule(
skipOnboarding = skipOnboarding,
isMenuRedesignEnabled = isMenuRedesignEnabled,
isMenuRedesignCFREnabled = isMenuRedesignCFREnabled,
isPageLoadTranslationsPromptEnabled = isPageLoadTranslationsPromptEnabled,
),
) { it.activity }
}
}

View File

@@ -0,0 +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.ui.efficiency.helpers
object PageStateTracker {
var currentPageName: String = "AppEntry"
}

View File

@@ -0,0 +1,39 @@
/* 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.efficiency.helpers
import org.mozilla.fenix.R
data class Selector(
val strategy: SelectorStrategy,
val value: String,
val description: String,
val groups: List<String> = listOf(),
) {
fun toResourceId(): Int {
return try {
val rClass = R.id::class.java
val field = rClass.getField(value)
field.getInt(null)
} catch (e: Exception) {
throw IllegalArgumentException("Could not resolve resource ID for selector value: '$value' using R.id", e)
}
}
}
enum class SelectorStrategy {
/**
* Supported strategies for locating UI elements.
*/
COMPOSE_BY_TAG,
COMPOSE_BY_TEXT,
ESPRESSO_BY_ID,
ESPRESSO_BY_TEXT,
ESPRESSO_BY_CONTENT_DESC,
UIAUTOMATOR2_BY_RES,
UIAUTOMATOR2_BY_CLASS,
UIAUTOMATOR2_BY_TEXT,
UIAUTOMATOR2_BY_RES_ID,
}

View File

@@ -0,0 +1,5 @@
package org.mozilla.fenix.ui.efficiency.helpers
enum class SwipeDirection {
UP, DOWN, LEFT, RIGHT
}

View File

@@ -0,0 +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.efficiency.navigation
data class NavigationEdge(
val from: String,
val to: String,
val steps: List<NavigationStep>,
)

View File

@@ -0,0 +1,51 @@
/* 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.efficiency.navigation
import android.util.Log
object NavigationRegistry {
private val graph = mutableMapOf<String, MutableList<NavigationEdge>>()
fun register(from: String, to: String, steps: List<NavigationStep>) {
val edge = NavigationEdge(from, to, steps)
graph.getOrPut(from) { mutableListOf() }.add(edge)
Log.i("NavigationRegistry", "📌 Registered navigation: $from -> $to with ${steps.size} step(s)")
steps.forEachIndexed { index, step ->
Log.i("NavigationRegistry", " Step ${index + 1}: $step")
}
}
fun findPath(from: String, to: String): List<NavigationStep>? {
val visited = mutableSetOf<String>()
val path = mutableListOf<NavigationStep>()
return dfs(from, to, visited, path)
}
private fun dfs(current: String, target: String, visited: MutableSet<String>, path: MutableList<NavigationStep>): List<NavigationStep>? {
if (current == target) return path.toList()
visited.add(current)
for (edge in graph[current].orEmpty()) {
if (edge.to !in visited) {
path.addAll(edge.steps)
val result = dfs(edge.to, target, visited, path)
if (result != null) return result
path.removeAll(edge.steps)
}
}
return null
}
fun logGraph() {
Log.i("NavigationRegistry", "🧭 Current navigation graph:")
for ((from, edges) in graph) {
for (edge in edges) {
Log.i("NavigationRegistry", " - $from -> ${edge.to} [${edge.steps.size} step(s)]")
}
}
}
}

View File

@@ -0,0 +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.efficiency.navigation
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SwipeDirection
sealed class NavigationStep {
data class Click(val selector: Selector) : NavigationStep()
data class Swipe(val selector: Selector, val direction: SwipeDirection = SwipeDirection.UP) : NavigationStep()
}

View File

@@ -0,0 +1,65 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.HomeSelectors
class HomePage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "HomePage"
init {
NavigationRegistry.register(
from = "AppEntry",
to = pageName,
steps = listOf(),
)
NavigationRegistry.register(
from = pageName,
to = "ThreeDotMenu",
steps = listOf(NavigationStep.Click(HomeSelectors.THREE_DOT_MENU)),
)
NavigationRegistry.register(
from = "ThreeDotMenu",
to = "BookmarksPage",
steps = listOf(NavigationStep.Click(HomeSelectors.TDM_BOOKMARKS_BUTTON)),
)
NavigationRegistry.register(
from = "ThreeDotMenu",
to = "SettingsPage",
steps = listOf(
NavigationStep.Swipe(HomeSelectors.TDM_SETTINGS_BUTTON_COMPOSE),
NavigationStep.Click(HomeSelectors.TDM_SETTINGS_BUTTON_COMPOSE),
),
)
NavigationRegistry.register(
from = "ThreeDotMenu",
to = "HistoryPage",
steps = listOf(NavigationStep.Click(HomeSelectors.TDM_HISTORY_BUTTON)),
)
NavigationRegistry.register(
from = "ThreeDotMenu",
to = "DownloadsPage",
steps = listOf(NavigationStep.Click(HomeSelectors.TDM_DOWNLOADS_BUTTON)),
)
NavigationRegistry.register(
from = "ThreeDotMenu",
to = "PasswordsPage",
steps = listOf(NavigationStep.Click(HomeSelectors.TDM_PASSWORDS_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return HomeSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsAccessibilitySelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsAccessibilityPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsAccessibilityPage"
init {
NavigationRegistry.register(
from = pageName,
to = "SettingsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsAccessibilitySelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsAutofillSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsAutofillPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsAutofillPage"
init {
NavigationRegistry.register(
from = pageName,
to = "SettingsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsAutofillSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsCustomizeSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsCustomizePage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsCustomizePage"
init {
NavigationRegistry.register(
from = pageName,
to = "SettingsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsCustomizeSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsHomepageSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsHomepagePage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsHomepagePage"
init {
NavigationRegistry.register(
from = "SettingsPage",
to = pageName,
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsHomepageSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,69 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.HomeSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsPage"
init {
NavigationRegistry.register(
from = "ThreeDotMenu",
to = pageName,
steps = listOf(NavigationStep.Click(HomeSelectors.TDM_SETTINGS_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "HomePage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsAccessibilityPage",
steps = listOf(
NavigationStep.Swipe(SettingsSelectors.ACCESSIBILITY_BUTTON),
NavigationStep.Click(SettingsSelectors.ACCESSIBILITY_BUTTON),
),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsAutofillPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.AUTOFILL_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsCustomizePage",
steps = listOf(NavigationStep.Click(SettingsSelectors.CUSTOMIZE_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsHomepagePage",
steps = listOf(NavigationStep.Click(SettingsSelectors.HOMEPAGE_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsPasswordsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.PASSWORDS_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsSearchPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.SEARCH_BUTTON)),
)
NavigationRegistry.register(
from = pageName,
to = "SettingsTabsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.TABS_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsPasswordsSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsPasswordsPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsPasswordsPage"
init {
NavigationRegistry.register(
from = pageName,
to = "SettingsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsPasswordsSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSearchSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
class SettingsSearchPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsSearchPage"
init {
NavigationRegistry.register(
from = pageName,
to = "SettingsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsSearchSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,26 @@
package org.mozilla.fenix.ui.efficiency.pageObjects
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import org.mozilla.fenix.helpers.HomeActivityIntentTestRule
import org.mozilla.fenix.ui.efficiency.helpers.BasePage
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.navigation.NavigationRegistry
import org.mozilla.fenix.ui.efficiency.navigation.NavigationStep
import org.mozilla.fenix.ui.efficiency.selectors.SettingsSelectors
import org.mozilla.fenix.ui.efficiency.selectors.SettingsTabsSelectors
class SettingsTabsPage(composeRule: AndroidComposeTestRule<HomeActivityIntentTestRule, *>) : BasePage(composeRule) {
override val pageName = "SettingsTabsPage"
init {
NavigationRegistry.register(
from = pageName,
to = "SettingsPage",
steps = listOf(NavigationStep.Click(SettingsSelectors.GO_BACK_BUTTON)),
)
}
override fun mozGetSelectorsByGroup(group: String): List<Selector> {
return SettingsTabsSelectors.all.filter { it.groups.contains(group) }
}
}

View File

@@ -0,0 +1,84 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object HomeSelectors {
val TOP_SITES_LIST = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_RES_ID,
value = "id/top_sites_list",
description = "Top Sites List",
groups = listOf("topSites"),
)
val TOP_SITES_LIST_COMPOSE = Selector(
strategy = SelectorStrategy.COMPOSE_BY_TAG,
value = "top_sites_list",
description = "Top Sites List",
groups = listOf("topSitesCompose"),
)
val THREE_DOT_MENU = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "menuButton",
description = "Three Dot Menu",
groups = listOf("requiredForPage"),
)
// TODO (Jackie J. 4/18/25): move THREE_DOT_MENU to component object file in next phase
// TODO (Jackie J. 4/18/25): add support for resId as text
val TDM_SETTINGS_BUTTON_COMPOSE = Selector(
strategy = SelectorStrategy.COMPOSE_BY_TEXT,
value = "Settings",
description = "Menu item with text 'Settings'",
groups = listOf("threeDotMenu", "compose"),
)
val TDM_SETTINGS_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = getStringResource(R.string.browser_menu_settings),
description = "the Settings Button",
groups = listOf("threeDotMenu"),
)
val TDM_HISTORY_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = getStringResource(R.string.library_history),
description = "the History button",
groups = listOf("threeDotMenu", "Bug-1234"),
)
val TDM_BOOKMARKS_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = getStringResource(R.string.library_bookmarks),
description = "the Bookmarks button",
groups = listOf("threeDotMenu"),
)
val TDM_DOWNLOADS_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = getStringResource(R.string.library_downloads),
description = "the Downloads button",
groups = listOf("threeDotMenu"),
)
val TDM_PASSWORDS_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = getStringResource(R.string.browser_menu_passwords),
description = "the Passwords button",
groups = listOf("threeDotMenu"),
)
val all = listOf(
THREE_DOT_MENU,
TDM_HISTORY_BUTTON,
TDM_BOOKMARKS_BUTTON,
TDM_DOWNLOADS_BUTTON,
TDM_PASSWORDS_BUTTON,
TDM_SETTINGS_BUTTON,
TOP_SITES_LIST,
)
}

View File

@@ -0,0 +1,27 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.DataGenerationHelper.getStringResource
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsAccessibilitySelectors {
val SETTINGS_ACCESSIBILITY_TITLE = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = getStringResource(R.string.preferences_accessibility),
description = "The Accessibility Settings header",
groups = listOf("requiredForPage"),
)
val USE_SYSTEM_FONT_SIZE_TOGGLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "use_system_font_size_toggle",
description = "Use System Font Size Toggle",
groups = listOf("accessibilitySettings"),
)
val all = listOf(
SETTINGS_ACCESSIBILITY_TITLE,
USE_SYSTEM_FONT_SIZE_TOGGLE,
)
}

View File

@@ -0,0 +1,25 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsAutofillSelectors {
val SETTINGS_AUTOFILL_TITLE = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Autofill",
description = "The Autofill Settings title",
groups = listOf("requiredForPage"),
)
val AUTOFILL_ADDRESSES_TOGGLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "autofill_addresses_toggle",
description = "Autofill Addresses Toggle",
groups = listOf("autofillSettings"),
)
val all = listOf(
SETTINGS_AUTOFILL_TITLE,
AUTOFILL_ADDRESSES_TOGGLE,
)
}

View File

@@ -0,0 +1,25 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsCustomizeSelectors {
val SETTINGS_CUSTOMIZE_TITLE = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Customize",
description = "The Customize Settings title",
groups = listOf("requiredForPage"),
)
val SHOW_TOOLBAR_TOGGLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "show_toolbar_toggle",
description = "Show Toolbar Toggle",
groups = listOf("customizeSettings"),
)
val all = listOf(
SETTINGS_CUSTOMIZE_TITLE,
SHOW_TOOLBAR_TOGGLE,
)
}

View File

@@ -0,0 +1,25 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsHomepageSelectors {
val SETTINGS_HOMEPAGE_TITLE = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Homepage",
description = "The Homepage Settings menu item",
groups = listOf("requiredForPage"),
)
val SHOW_TOP_SITES_TOGGLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "show_top_sites_toggle",
description = "Show Top Sites Toggle",
groups = listOf("homepageSettings"),
)
val all = listOf(
SETTINGS_HOMEPAGE_TITLE,
SHOW_TOP_SITES_TOGGLE,
)
}

View File

@@ -0,0 +1,25 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsPasswordsSelectors {
val SETTINGS_PASSWORDS_TITLE = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Passwords",
description = "The Passwords Settings title",
groups = listOf("requiredForPage"),
)
val SAVE_PASSWORDS_TOGGLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "save_passwords_toggle",
description = "Save Passwords Toggle",
groups = listOf("passwordSettings"),
)
val all = listOf(
SETTINGS_PASSWORDS_TITLE,
SAVE_PASSWORDS_TOGGLE,
)
}

View File

@@ -0,0 +1,15 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsSearchSelectors {
val SETTINGS_SEARCH_TITLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Search",
description = "the Settings Search title",
groups = listOf("requiredForPage"),
)
val all = listOf(SETTINGS_SEARCH_TITLE)
}

View File

@@ -0,0 +1,81 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsSelectors {
val GO_BACK_BUTTON = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_CONTENT_DESC,
value = "Navigate up",
description = "the Back Arrow button",
groups = listOf("requiredForPage"),
)
val GENERAL_HEADING = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "General",
description = "the General heading",
groups = listOf("requiredForPage"),
)
val SEARCH_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Search",
description = "the Search button",
groups = listOf("requiredForPage"),
)
val TABS_BUTTON = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Tabs",
description = "the Tabs button",
groups = listOf("requiredForPage"),
)
val ACCESSIBILITY_BUTTON = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Accessibility",
description = "the Accessibility button",
groups = listOf("requiredForPage"),
)
val AUTOFILL_BUTTON = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Autofill",
description = "the Autofill button",
groups = listOf("requiredForPage"),
)
val CUSTOMIZE_BUTTON = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Customize",
description = "the Customize button",
groups = listOf("requiredForPage"),
)
val HOMEPAGE_BUTTON = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Homepage",
description = "the Homepage button",
groups = listOf("requiredForPage"),
)
val PASSWORDS_BUTTON = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_TEXT,
value = "Passwords",
description = "the Passwords button",
groups = listOf("requiredForPage"),
)
val all = listOf(
GO_BACK_BUTTON,
GENERAL_HEADING,
SEARCH_BUTTON,
TABS_BUTTON,
ACCESSIBILITY_BUTTON,
AUTOFILL_BUTTON,
CUSTOMIZE_BUTTON,
HOMEPAGE_BUTTON,
PASSWORDS_BUTTON,
)
}

View File

@@ -0,0 +1,25 @@
package org.mozilla.fenix.ui.efficiency.selectors
import org.mozilla.fenix.ui.efficiency.helpers.Selector
import org.mozilla.fenix.ui.efficiency.helpers.SelectorStrategy
object SettingsTabsSelectors {
val SETTINGS_TABS_TITLE = Selector(
strategy = SelectorStrategy.UIAUTOMATOR2_BY_TEXT,
value = "Tabs",
description = "The Settings Tabs title",
groups = listOf("requiredForPage"),
)
val NEW_TAB_PAGE_TOGGLE = Selector(
strategy = SelectorStrategy.ESPRESSO_BY_ID,
value = "new_tab_page_toggle",
description = "New Tab Page Toggle Switch",
groups = listOf("tabSettings"),
)
val all = listOf(
SETTINGS_TABS_TITLE,
NEW_TAB_PAGE_TOGGLE,
)
}

View File

@@ -0,0 +1,20 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class HomeTest : BaseTest() {
// TestRail link: https://mozilla.testrail.io/index.php?/cases/view/235396
@Test
fun homeScreenItemsTest() {
// Given: App is loaded with default settings
// on = AndroidComposeTestRule<HomeActivityIntentTestRule, *> with app defaults
// When: We navigate to the Settings page and back to the Home page
on.settings.navigateToPage()
on.home.navigateToPage()
// Then: the browser chrome, page components, and elements should load
on.home.mozVerifyElementsByGroup("topSitesCompose")
}
}

View File

@@ -0,0 +1,12 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsAccessibilityTest : BaseTest() {
@Test
fun verifySettingsAccessibilityPageLoadsTest() {
on.settingsAccessibility.navigateToPage()
}
}

View File

@@ -0,0 +1,12 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsAutofillTest : BaseTest() {
@Test
fun verifySettingsAutofillLoadsTest() {
on.settingsAutofill.navigateToPage()
}
}

View File

@@ -0,0 +1,12 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsCustomizeTest : BaseTest() {
@Test
fun verifySettingsCustomizeLoadsTest() {
on.settingsCustomize.navigateToPage()
}
}

View File

@@ -0,0 +1,12 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsHomepageTest : BaseTest() {
@Test
fun verifySettingsHomepageLoadsTest() {
on.settingsHomepage.navigateToPage()
}
}

View File

@@ -0,0 +1,12 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsPasswordsTest : BaseTest() {
@Test
fun verifySettingsPasswordsLoadsTest() {
on.settingsPasswords.navigateToPage()
}
}

View File

@@ -0,0 +1,20 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsSearchTest : BaseTest() {
// TestRail link: https://mozilla.testrail.io/index.php?/cases/view/2203333
@Test
fun verifySearchSettingsMenuItemsTest() {
// Given: App is loaded with default settings
// on = AndroidComposeTestRule<HomeActivityIntentTestRule, *> with app defaults
// When: We navigate to the Settings 'Search' page
on.settingsSearch.navigateToPage()
// Then: all elements should load
// by default navigateToPage() asserts all 'requiredForPage' elements are present
}
}

View File

@@ -0,0 +1,12 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsTabsTest : BaseTest() {
@Test
fun verifySettingsTabsLoadsTest() {
on.settingsTabs.navigateToPage()
}
}

View File

@@ -0,0 +1,20 @@
package org.mozilla.fenix.ui.efficiency.tests
import org.junit.Test
import org.mozilla.fenix.ui.efficiency.helpers.BaseTest
class SettingsTest : BaseTest() {
// TestRail link: https://mozilla.testrail.io/index.php?/cases/view/2092697
@Test
fun verifyGeneralSettingsItemsTest() {
// Given: App is loaded with default settings
// on = AndroidComposeTestRule<HomeActivityIntentTestRule, *> with app defaults
// When: We navigate to the Settings page
on.settings.navigateToPage()
// Then: all elements should load
// by default navigateToPage() asserts all 'requiredForPage' elements are present
}
}

View File

@@ -17,10 +17,13 @@ gcloud:
directories-to-pull:
- /sdcard/screenshots
performance-metrics: true
other-files:
/data/local/tmp/org.mozilla.fenix.debug-geckoview-config.yaml: ./automation/taskcluster/androidTest/geckoview-configs/org.mozilla.fenix.debug-geckoview-config.yaml
test-targets:
- notPackage org.mozilla.fenix.syncintegration
- notPackage org.mozilla.fenix.experimentintegration
- package org.mozilla.fenix.ui.efficiency.tests
device:
- model: MediumPhone.arm

View File

@@ -23,6 +23,7 @@ gcloud:
test-targets:
- notPackage org.mozilla.fenix.syncintegration
- notPackage org.mozilla.fenix.experimentintegration
- notPackage org.mozilla.fenix.ui.efficiency.tests
device:
- model: MediumPhone.arm

View File

@@ -379,7 +379,7 @@ tasks:
attributes:
build-type: fenix-debug-firebase
shipping-product: fenix
description: Run Fenix tests on newer Android API levels (ARM64-v8a)
description: Run Fenix tests using experimental POM design
dependencies:
signed-apk-debug-apk: signing-apk-fenix-debug
signed-apk-android-test: signing-apk-fenix-android-test-debug
@@ -397,7 +397,7 @@ tasks:
- [wget, {artifact-reference: '<signed-apk-debug-apk/public/build/target.arm64-v8a.apk>'}, '-O', app.apk]
- [wget, {artifact-reference: '<signed-apk-android-test/public/build/target.noarch.apk>'}, '-O', android-test.apk]
- [python3, ../../../taskcluster/scripts/tests/test-lab.py, arm-experimental-api-tests, app.apk, --apk_test, android-test.apk]
run-on-projects: []
run-on-projects: [trunk]
treeherder:
platform: 'fenix-android-all/opt'
symbol: fenix-debug(experimental-arm)