diff --git a/README.md b/README.md index 2a068dd..0872b13 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,61 @@ -# selfheal-demo-browserstack -Self Heal Demo Scripts +# Self Heal Demo (TestNG) + +## Overview +This repository contains demo scripts and configuration for testing the self-healing feature on BrowserStack using Appium and TestNG. It includes two main flows: + +### 1. Base App Flow +- Uses the **BaseApp.apk** for standard automation tests without self-healing. +- Located at: `android/testng-examples/BaseApp.apk` +- Demonstrates normal automation behavior and serves as a reference for comparison. + +### 2. Self-Heal App Flow +- Uses the **SelfHealApp.apk** to showcase BrowserStack's self-heal capability. +- Located at: `android/testng-examples/SelfHealApp.apk` +- **Run sessions with and without self-heal enabled** to observe how the feature recovers from locator changes or UI modifications: + - **Without self-heal:** Tests may fail if locators change. + - **With self-heal:** Tests should pass even if some locators have changed, as self-heal attempts to find the correct elements automatically. + +## How to Compare +- Run your test suite on the Self-Heal App twice: once with `selfHeal: false` and once with `selfHeal: true` in your `browserstack.yml`. +- Compare the results and logs to see the impact of self-healing. + +## About the Self-Heal Feature +- The self-heal feature in BrowserStack automatically detects and recovers from locator changes in your app's UI during automated tests. +- If a locator fails (e.g., due to a UI update), self-heal attempts to find the correct element using alternative strategies, allowing your tests to continue without manual intervention. +- This reduces test flakiness and maintenance effort, especially in agile environments with frequent UI changes. +- **To enable self-healing, set `selfHeal: true` in your `browserstack.yml` configuration file.** + +#### Example `browserstack.yml` snippet: +```yaml +selfHeal: true +``` + +## Usage +- Build and upload the APKs to BrowserStack as needed. +- Run your automation scripts using the provided configuration files. +- Make sure `selfHeal: true` is set in your `browserstack.yml` to enable the self-heal feature. +- Review the test results and logs to observe self-healing in action. + +### Running Tests with Maven or Gradle +- **For Maven:** + - To run the Base App test suite: + ```sh + mvn test -P sampleBaseAppTest + ``` + - To run the Self-Heal App test suite: + ```sh + mvn test -P sampleSelfHealAppTest + ``` +- **For Gradle:** + - To run the Base App test suite: + ```sh + gradle clean sampleBaseAppTest + ``` + - To run the Self-Heal App test suite: + ```sh + gradle clean sampleSelfHealAppTest + ``` + +## Getting Help + +If you are running into any issues or have any queries, please check [Browserstack Support page](https://www.browserstack.com/support/app-automate) or [get in touch with us](https://www.browserstack.com/contact?ref=help). diff --git a/android/testng-examples/BaseApp.apk b/android/testng-examples/BaseApp.apk new file mode 100644 index 0000000..6898d8c Binary files /dev/null and b/android/testng-examples/BaseApp.apk differ diff --git a/android/testng-examples/SelfHealApp.apk b/android/testng-examples/SelfHealApp.apk new file mode 100644 index 0000000..edf3f08 Binary files /dev/null and b/android/testng-examples/SelfHealApp.apk differ diff --git a/android/testng-examples/browserstack.yml b/android/testng-examples/browserstack.yml new file mode 100644 index 0000000..e761495 --- /dev/null +++ b/android/testng-examples/browserstack.yml @@ -0,0 +1,81 @@ +# ============================= +# Set BrowserStack Credentials +# ============================= +# Add your BrowserStack userName and acccessKey here or set BROWSERSTACK_USERNAME and +# BROWSERSTACK_ACCESS_KEY as env variables +userName: BROWSERSTACK_USERNAME +accessKey: BROWSERSTACK_ACCESS_KEY + +# ====================== +# BrowserStack Reporting +# ====================== +# The following capabilities are used to set up reporting on BrowserStack: +# Set 'projectName' to the name of your project. Example, Marketing Website +projectName: BrowserStack Samples +# Set `buildName` as the name of the job / testsuite being run +buildName: browserstack build +# `buildIdentifier` is a unique id to differentiate every execution that gets appended to +# buildName. Choose your buildIdentifier format from the available expressions: +# ${BUILD_NUMBER} (Default): Generates an incremental counter with every execution +# ${DATE_TIME}: Generates a Timestamp with every execution. Eg. 05-Nov-19:30 +# Read more about buildIdentifiers here -> https://www.browserstack.com/docs/automate/selenium/organize-tests +buildIdentifier: '#${BUILD_NUMBER}' # Supports strings along with either/both ${expression} +# Set `framework` of your test suite. Example, `testng`, `cucumber`, `cucumber-testng` +# This property is needed to send test context to BrowserStack (test name, status) +framework: testng + +source: testng:appium-sample-sdk:v1.1 + +app: ./BaseApp.apk +# app: ./SelfHealApp.apk # For testing with the Self Heal App + +# ======================================= +# Platforms (Browsers / Devices to test) +# ======================================= +# Platforms object contains all the browser / device combinations you want to test on. +# Entire list available here -> (https://www.browserstack.com/list-of-browsers-and-platforms/automate) + +platforms: + - deviceName: Samsung Galaxy S22 Ultra + osVersion: 12.0 + platformName: android + - deviceName: Samsung Galaxy S21 + osVersion: 11.0 + platformName: android + - deviceName: Google Pixel 6 Pro + osVersion: 12.0 + platformName: android + +# ======================= +# Parallels per Platform +# ======================= +# The number of parallel threads to be used for each platform set. +# BrowserStack's SDK runner will select the best strategy based on the configured value +# +# Example 1 - If you have configured 3 platforms and set `parallelsPerPlatform` as 2, a total of 6 (2 * 3) parallel threads will be used on BrowserStack +# +# Example 2 - If you have configured 1 platform and set `parallelsPerPlatform` as 5, a total of 5 (1 * 5) parallel threads will be used on BrowserStack +parallelsPerPlatform: 1 + +# ========================================== +# BrowserStack Local +# (For localhost, staging/private websites) +# ========================================== +# Set browserStackLocal to true if your website under test is not accessible publicly over the internet +# Learn more about how BrowserStack Local works here -> https://www.browserstack.com/docs/automate/selenium/local-testing-introduction +browserstackLocal: true # (Default false) +#browserStackLocalOptions: +#Options to be passed to BrowserStack local in-case of advanced configurations +# localIdentifier: # (Default: null) Needed if you need to run multiple instances of local. +# forceLocal: true # (Default: false) Set to true if you need to resolve all your traffic via BrowserStack Local tunnel. +# Entire list of arguments available here -> https://www.browserstack.com/docs/automate/selenium/manage-incoming-connections + +# =================== +# Debugging features +# =================== +debug: false # # Set to true if you need screenshots for every selenium command ran +networkLogs: false # Set to true to enable HAR logs capturing +consoleLogs: errors # Remote browser's console debug levels to be printed (Default: errors) +# Available options are `disable`, `errors`, `warnings`, `info`, `verbose` (Default: errors) + +selfHeal: false # Set to true if you want to enable self-healing capabilities in your tests diff --git a/android/testng-examples/build.gradle b/android/testng-examples/build.gradle new file mode 100644 index 0000000..f53072a --- /dev/null +++ b/android/testng-examples/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'java' +} + +repositories { mavenCentral() } + +dependencies { + testImplementation 'org.testng:testng:7.5' + implementation 'org.seleniumhq.selenium:selenium-java:4.13.0' + implementation 'io.appium:java-client:8.6.0' + implementation 'commons-io:commons-io:2.11.0' + implementation 'com.googlecode.json-simple:json-simple:1.1.1' + implementation 'com.browserstack:browserstack-java-sdk:latest.release' +} + +group = 'com.browserstack' +version = '1.0-SNAPSHOT' +description = 'testng-browserstack' +sourceCompatibility = '1.8' + +def browserstackSDKArtifact = configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.find { it.name == 'browserstack-java-sdk' } + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +tasks.withType(Test) { + systemProperties = System.properties +} + +task sampleBaseAppTest(type: Test) { + useTestNG() { + dependsOn cleanTest + useDefaultListeners = true + suites "src/test/resources/com/browserstack/sample-base-app-test.testng.xml" + jvmArgs "-javaagent:${browserstackSDKArtifact.file}" + } +} + +task sampleSelfHealAppTest(type: Test) { + useTestNG() { + dependsOn cleanTest + useDefaultListeners = true + suites "src/test/resources/com/browserstack/sample-self-heal-app-test.testng.xml" + jvmArgs "-javaagent:${browserstackSDKArtifact.file}" + } +} diff --git a/android/testng-examples/pom.xml b/android/testng-examples/pom.xml new file mode 100644 index 0000000..cb1c973 --- /dev/null +++ b/android/testng-examples/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + com.browserstack + testng-browserstack-android + 1.0-SNAPSHOT + jar + + testng-appium-app + https://github.com/browserstack/selfheal-demo-browserstack + + + UTF-8 + 1.8 + 1.8 + 2.19.1 + 3.0.0-M5 + default + + + + + org.testng + testng + 7.5 + + + org.seleniumhq.selenium + selenium-java + 4.13.0 + + + io.appium + java-client + 8.6.0 + + + commons-io + commons-io + 2.11.0 + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + com.browserstack + browserstack-java-sdk + LATEST + compile + + + + + + + maven-dependency-plugin + 3.3.0 + + + getClasspathFilenames + + properties + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + src/test/resources/com/browserstack/sample-base-app-test.testng.xml + + -javaagent:${com.browserstack:browserstack-java-sdk:jar} + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + + + + + sampleBaseAppTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/com/browserstack/sample-base-app-test.testng.xml + + + + + + + + sampleSelfHealAppTest + + + + org.apache.maven.plugins + maven-surefire-plugin + + + src/test/resources/com/browserstack/sample-self-heal-app-test.testng.xml + + + + + + + + diff --git a/android/testng-examples/settings.gradle b/android/testng-examples/settings.gradle new file mode 100644 index 0000000..55607bd --- /dev/null +++ b/android/testng-examples/settings.gradle @@ -0,0 +1,16 @@ +pluginManagement { + repositories { + mavenCentral() + mavenLocal() + gradlePluginPortal() + } + + resolutionStrategy { + eachPlugin { + if (requested.id.id == "com.browserstack.gradle-sdk") { + useModule("com.browserstack:gradle-sdk:1.1.2") + } + } + } +} +rootProject.name = 'testng-browserstack-android' diff --git a/android/testng-examples/src/test/java/com/browserstack/AppiumTest.java b/android/testng-examples/src/test/java/com/browserstack/AppiumTest.java new file mode 100644 index 0000000..72708fc --- /dev/null +++ b/android/testng-examples/src/test/java/com/browserstack/AppiumTest.java @@ -0,0 +1,27 @@ +package com.browserstack; + +import java.net.URL; + +import org.openqa.selenium.MutableCapabilities; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; + +import io.appium.java_client.android.AndroidDriver; +import io.appium.java_client.android.options.UiAutomator2Options; + + +public class AppiumTest { + + public AndroidDriver driver; + + @BeforeMethod(alwaysRun=true) + public void setUp() throws Exception { + MutableCapabilities capabilities = new UiAutomator2Options(); + driver = new AndroidDriver(new URL("http://127.0.0.1:4723/wd/hub"),capabilities); + } + + @AfterMethod(alwaysRun=true) + public void tearDown() throws Exception { + driver.quit(); + } +} diff --git a/android/testng-examples/src/test/java/com/browserstack/BaseAppTest.java b/android/testng-examples/src/test/java/com/browserstack/BaseAppTest.java new file mode 100644 index 0000000..d742c44 --- /dev/null +++ b/android/testng-examples/src/test/java/com/browserstack/BaseAppTest.java @@ -0,0 +1,122 @@ +package com.browserstack; + +import java.time.Duration; +import java.util.List; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.testng.Assert; +import org.testng.annotations.Test; + +import io.appium.java_client.AppiumBy; + +public class BaseAppTest extends AppiumTest { + + /** + * This test suite demonstrates standard Appium automation on the base app. + * All locators are expected to match the original app UI. If the UI changes, + * these tests may fail, showing the limitations of brittle locators. + */ + + @Test + public void shouldDisplayDemoScreenElements() { + // Switch to Demo tab + WebElement demoTab = driver.findElement(AppiumBy.id("navigation_demo")); + demoTab.click(); + // Main title (Inspector-style XPath) + WebElement demoTitle = new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated( + AppiumBy.xpath("//android.widget.TextView[@resource-id='com.example.selfhealingtestapp:id/demo_text_title' and @text='Demonstration App']"))); + Assert.assertNotNull(demoTitle); + // About section header (Inspector-style XPath) + Assert.assertNotNull(driver.findElement(AppiumBy.xpath( + "//android.widget.TextView[@text='About This Demo App']"))); + } + + @Test + public void shouldFindProductCardWithOriginalXpath() throws Exception { + // Verifies product card and info panel using original XPath (no UI change) + WebElement productsTab = driver.findElement(AppiumBy.id("navigation_products")); + productsTab.click(); + WebElement firstProductCard = new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated( + AppiumBy.xpath("//androidx.recyclerview.widget.RecyclerView[@resource-id='com.example.selfhealingtestapp:id/recycler_products']/androidx.cardview.widget.CardView[1]/android.view.ViewGroup"))); + WebElement firstProductName = firstProductCard.findElement(AppiumBy.id("product_name")); + Assert.assertNotNull(firstProductName); + WebElement infoIcon = firstProductCard.findElement(AppiumBy.id("product_info_icon")); + infoIcon.click(); + WebElement infoPanel = new WebDriverWait(driver, Duration.ofSeconds(5)) + .until(ExpectedConditions.visibilityOf( + firstProductCard.findElement(AppiumBy.id("info_card_product_panel")))); + Assert.assertNotNull(infoPanel); + Assert.assertNotNull(firstProductCard.findElement(AppiumBy.id("info_card_product_id"))); + Assert.assertNotNull(firstProductCard.findElement(AppiumBy.id("info_card_product_class"))); + Assert.assertNotNull(firstProductCard.findElement(AppiumBy.id("info_card_product_xpath"))); + infoPanel.click(); + } + + @Test + public void shouldToggleSwitchAndCheckboxWithOriginalId() throws Exception { + // Verifies switch and checkbox using original IDs (no UI change) + WebElement interactiveTab = driver.findElement(AppiumBy.id("navigation_interactive")); + interactiveTab.click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("demo_button_primary_action"))); + WebElement featureBtn = driver.findElement(AppiumBy.id("demo_switch_example")); + featureBtn.click(); + Assert.assertEquals(featureBtn.getAttribute("checked"), "true"); + WebElement acceptTermBtn = driver.findElement(AppiumBy.id("demo_checkbox_example")); + acceptTermBtn.click(); + Assert.assertEquals(acceptTermBtn.getAttribute("checked"), "true"); + acceptTermBtn.click(); + Assert.assertEquals(acceptTermBtn.getAttribute("checked"), "false"); + } + + @Test + public void shouldFindSwitchInfoIconWithOriginalContentDesc() throws Exception { + // Verifies info icon using original content-desc (no UI change) + WebElement interactiveTab = driver.findElement(AppiumBy.id("navigation_interactive")); + interactiveTab.click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("demo_button_primary_action"))); + WebElement infoIconSwitch = driver.findElement(AppiumBy.xpath("//android.widget.ImageView[@content-desc='Switch Info Icon']")); + infoIconSwitch.click(); + } + + @Test + public void shouldFindSecondaryActionWithOriginalText() throws Exception { + // Verifies secondary action using original text (no UI change) + WebElement interactiveTab = driver.findElement(AppiumBy.id("navigation_interactive")); + interactiveTab.click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("demo_button_primary_action"))); + WebElement secondaryActionBtn = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Secondary Action\")")); + secondaryActionBtn.click(); + WebElement infoIconSecondary = driver.findElement(AppiumBy.id("info_icon_secondary")); + infoIconSecondary.click(); + Thread.sleep(2000); // Wait for info panel to appear + infoIconSecondary.click(); + } + + @Test + public void shouldNavigateBetweenTabsAndVerifyHeadings() { + driver.findElement(AppiumBy.id("navigation_demo")).click(); + Assert.assertEquals(driver.findElement(AppiumBy.id("demo_text_title")).getText(), "Demonstration App"); + + driver.findElement(AppiumBy.id("navigation_interactive")).click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("label_interactive_elements"))); + Assert.assertEquals(driver.findElement(AppiumBy.id("label_interactive_elements")).getText(), "Interactive Elements"); + + driver.findElement(AppiumBy.id("navigation_products")).click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("product_list_heading"))); + Assert.assertNotNull(driver.findElement(AppiumBy.id("product_list_heading"))); + + driver.findElement(AppiumBy.id("navigation_demo")).click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("navigation_demo"))); + Assert.assertEquals(driver.findElement(AppiumBy.id("demo_text_title")).getText(), "Demonstration App"); + } +} diff --git a/android/testng-examples/src/test/java/com/browserstack/SelfHealAppTest.java b/android/testng-examples/src/test/java/com/browserstack/SelfHealAppTest.java new file mode 100644 index 0000000..fadff12 --- /dev/null +++ b/android/testng-examples/src/test/java/com/browserstack/SelfHealAppTest.java @@ -0,0 +1,122 @@ +package com.browserstack; + +import java.time.Duration; +import java.util.List; + +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.testng.Assert; +import org.testng.annotations.Test; + +import io.appium.java_client.AppiumBy; + +/** + * This test suite demonstrates the self-healing feature of BrowserStack. + * If the app UI changes and locators break, self-heal will attempt to recover + * and find the correct elements, allowing tests to pass even with locator changes. + * These tests are intentionally similar to BaseAppTest to show the difference + * in behavior when self-heal is enabled. + */ +public class SelfHealAppTest extends AppiumTest { + /** + * This test suite demonstrates BrowserStack's self-healing feature. + * Test names are updated to clearly describe the scenario and intent. + */ + + @Test + public void shouldDisplayDemoScreenElements() { + // Verifies main demo screen elements are present and visible + WebElement demoTab = driver.findElement(AppiumBy.id("navigation_demo")); + demoTab.click(); + WebElement demoTitle = new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated( + AppiumBy.xpath("//android.widget.TextView[@resource-id='com.example.selfhealingtestapp:id/demo_text_title' and @text='Demonstration App']"))); + Assert.assertNotNull(demoTitle); + Assert.assertNotNull(driver.findElement(AppiumBy.xpath( + "//android.widget.TextView[@text='About This Demo App']"))); + } + + @Test + public void shouldSelfHealOnProductCardXpathChange() { + // Demonstrates self-heal for product card XPath change + WebElement productsTab = driver.findElement(AppiumBy.id("navigation_products")); + productsTab.click(); + WebElement firstProductCard = new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated( + AppiumBy.xpath("//androidx.recyclerview.widget.RecyclerView[@resource-id='com.example.selfhealingtestapp:id/recycler_products']/androidx.cardview.widget.CardView[1]/android.view.ViewGroup"))); + WebElement firstProductName = firstProductCard.findElement(AppiumBy.id("product_name")); + Assert.assertNotNull(firstProductName); + WebElement infoIcon = firstProductCard.findElement(AppiumBy.id("product_info_icon")); + infoIcon.click(); + WebElement infoPanel = new WebDriverWait(driver, Duration.ofSeconds(5)) + .until(ExpectedConditions.visibilityOf( + firstProductCard.findElement(AppiumBy.id("info_card_product_panel")))); + Assert.assertNotNull(infoPanel); + Assert.assertNotNull(firstProductCard.findElement(AppiumBy.id("info_card_product_id"))); + Assert.assertNotNull(firstProductCard.findElement(AppiumBy.id("info_card_product_class"))); + Assert.assertNotNull(firstProductCard.findElement(AppiumBy.id("info_card_product_xpath"))); + infoPanel.click(); + } + + @Test + public void shouldSelfHealOnCheckboxIdChange() { + // Demonstrates self-heal for checkbox ID change + WebElement interactiveTab = driver.findElement(AppiumBy.id("navigation_interactive")); + interactiveTab.click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("demo_button_primary_action"))); + WebElement featureBtn = driver.findElement(AppiumBy.id("demo_switch_example")); + featureBtn.click(); + Assert.assertEquals(featureBtn.getAttribute("checked"), "true"); + WebElement acceptTermBtn = driver.findElement(AppiumBy.id("demo_checkbox_example")); + acceptTermBtn.click(); + Assert.assertEquals(acceptTermBtn.getAttribute("checked"), "true"); + acceptTermBtn.click(); + Assert.assertEquals(acceptTermBtn.getAttribute("checked"), "false"); + } + + @Test + public void shouldSelfHealOnSwitchInfoIconContentDescChange() { + // Demonstrates self-heal for info icon content-desc change + WebElement interactiveTab = driver.findElement(AppiumBy.id("navigation_interactive")); + interactiveTab.click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("demo_button_primary_action"))); + WebElement infoIconSwitch = driver.findElement(AppiumBy.xpath("//android.widget.ImageView[@content-desc='Switch Info Icon']")); + infoIconSwitch.click(); + } + + @Test + public void shouldSelfHealOnSecondaryActionTextChange() throws Exception { + // Demonstrates self-heal for secondary action text change + WebElement interactiveTab = driver.findElement(AppiumBy.id("navigation_interactive")); + interactiveTab.click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("demo_button_primary_action"))); + WebElement secondaryActionBtn = driver.findElement(AppiumBy.androidUIAutomator("new UiSelector().text(\"Secondary Action\")")); + secondaryActionBtn.click(); + WebElement infoIconSecondary = driver.findElement(AppiumBy.id("info_icon_secondary")); + Thread.sleep(2000); // Wait for info panel to appear + infoIconSecondary.click(); + } + + @Test + public void shouldNavigateBetweenTabsAndVerifyHeadings() { + // Navigates between tabs and checks headings + driver.findElement(AppiumBy.id("navigation_demo")).click(); + Assert.assertEquals(driver.findElement(AppiumBy.id("demo_text_title")).getText(), "Demonstration App"); + driver.findElement(AppiumBy.id("navigation_interactive")).click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("label_interactive_elements"))); + Assert.assertEquals(driver.findElement(AppiumBy.id("label_interactive_elements")).getText(), "Interactive Elements"); + driver.findElement(AppiumBy.id("navigation_products")).click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("product_list_heading"))); + Assert.assertNotNull(driver.findElement(AppiumBy.id("product_list_heading"))); + driver.findElement(AppiumBy.id("navigation_demo")).click(); + new WebDriverWait(driver, Duration.ofSeconds(10)) + .until(ExpectedConditions.visibilityOfElementLocated(AppiumBy.id("navigation_demo"))); + Assert.assertEquals(driver.findElement(AppiumBy.id("demo_text_title")).getText(), "Demonstration App"); + } +} diff --git a/android/testng-examples/src/test/resources/com/browserstack/sample-base-app-test.testng.xml b/android/testng-examples/src/test/resources/com/browserstack/sample-base-app-test.testng.xml new file mode 100644 index 0000000..9e48a02 --- /dev/null +++ b/android/testng-examples/src/test/resources/com/browserstack/sample-base-app-test.testng.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/android/testng-examples/src/test/resources/com/browserstack/sample-self-heal-app-test.testng.xml b/android/testng-examples/src/test/resources/com/browserstack/sample-self-heal-app-test.testng.xml new file mode 100644 index 0000000..c78222d --- /dev/null +++ b/android/testng-examples/src/test/resources/com/browserstack/sample-self-heal-app-test.testng.xml @@ -0,0 +1,9 @@ + + + + + + + + +