diff --git a/.github/workflows/appium-mobile-examples.yml b/.github/workflows/appium-mobile-examples.yml
new file mode 100644
index 000000000..d712f6be9
--- /dev/null
+++ b/.github/workflows/appium-mobile-examples.yml
@@ -0,0 +1,35 @@
+name: Real Devices
+
+on:
+ schedule:
+ - cron: '35 20 * * *'
+ workflow_dispatch:
+ push:
+ branches: [ main ]
+ paths:
+ - '**/appium/**'
+ pull_request:
+ paths:
+ - '**/appium/**'
+
+env:
+ SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}
+ SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ strategy:
+ matrix:
+ cloud: [ RDC, VDC ]
+ browser: [ chrome, safari ]
+ steps:
+ - uses: actions/checkout@v4
+ - name: Set up Java
+ uses: actions/setup-java@v4
+ with:
+ java-version: 11
+ distribution: "temurin"
+ - name: Mobile Web ${{ matrix.cloud }} {{ matrix.browser }}
+ working-directory: ./appium/mobile-web
+ run: mvn test -Dsauce.cloud=${{ matrix.cloud}} -Dsauce.browser=${{ matrix.browser}}
diff --git a/appium/appium-web/appium-web-examples/pom.xml b/appium/appium-web/appium-web-examples/pom.xml
deleted file mode 100644
index ad3d39090..000000000
--- a/appium/appium-web/appium-web-examples/pom.xml
+++ /dev/null
@@ -1,73 +0,0 @@
-
-
-
- 4.0.0
-
- appium-web-examples
- com.saucelabs
- 1.0-SNAPSHOT
-
- Sauce Labs Appium Web
-
-
- 11
- 11
- UTF-8
- 3.5.3
- 9.4.0
- 4.28.1
-
-
-
-
-
-
- commons-logging
- commons-logging
- 1.3.5
-
-
- junit
- junit
- 4.13.2
-
-
- org.assertj
- assertj-core
- 3.27.3
-
-
- io.appium
- java-client
- ${appium.version}
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-compiler-plugin
- 3.14.0
-
- ${maven.compiler.source}
- ${maven.compiler.source}
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- ${maven.surefire.version}
-
- all
- 30
- true
- false
-
-
-
-
-
-
\ No newline at end of file
diff --git a/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/AndroidWebAppTest.java b/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/AndroidWebAppTest.java
deleted file mode 100644
index e87e487df..000000000
--- a/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/AndroidWebAppTest.java
+++ /dev/null
@@ -1,109 +0,0 @@
-package com.examples.simple_example;
-
-import com.helpers.SauceAppiumTestWatcher;
-import io.appium.java_client.AppiumDriver;
-import io.appium.java_client.android.AndroidDriver;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.openqa.selenium.By;
-import org.openqa.selenium.MutableCapabilities;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.List;
-
-import static com.helpers.Constants.*;
-
-/**
- * Android Native App Tests
- */
-public class AndroidWebAppTest {
- @Rule
- public TestName name = new TestName();
-
- //This rule allows us to set test status with Junit
- @Rule
- public SauceAppiumTestWatcher resultReportingTestWatcher = new SauceAppiumTestWatcher();
-
- private AndroidDriver driver;
-
- By usernameInput = By.id("user-name");
- By passwordInput = By.id("password");
- By submitButton = By.className("btn_action");
- By inventoryList = By.className("inventory_list");
-
- @Before
- public void setup() throws MalformedURLException {
- System.out.println("Sauce Android Web App - Before hook");
-
- MutableCapabilities capabilities = new MutableCapabilities();
- MutableCapabilities sauceOptions = new MutableCapabilities();
- URL url;
-
- url = switch (region) {
- case "us" -> new URL(SAUCE_US_URL);
- default -> new URL(SAUCE_EU_URL);
- };
-
- // For all capabilities please check
- // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities
- // Use the platform configuration https://saucelabs.com/platform/platform-configurator#/
- // to find the emulators/real devices names, OS versions and appium versions you can use for your testings
- capabilities.setCapability("platformName", "android");
- capabilities.setCapability("appium:automationName", "UiAutomator2");
- if (rdc.equals("true")) {
- capabilities.setCapability("appium:deviceName", "samsung.*");
- sauceOptions.setCapability("appiumVersion", "latest");
- }
- else {
- capabilities.setCapability("appium:deviceName", "Android GoogleAPI Emulator");
- sauceOptions.setCapability("appiumVersion", "latest");
- }
-
- capabilities.setCapability("appium:platformVersion", "13");
- capabilities.setCapability("browserName", "chrome");
-
- // Sauce capabilities
- sauceOptions.setCapability("name", name.getMethodName());
- sauceOptions.setCapability("build", "myWebApp-job-1");
- List tags = Arrays.asList("sauceDemo", "Android", "Demo", "Web");
- sauceOptions.setCapability("tags", tags);
- sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
- sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
-
- capabilities.setCapability("sauce:options", sauceOptions);
-
- driver = new AndroidDriver(url, capabilities);
-
- resultReportingTestWatcher.setDriver(driver);
- }
-
- @Test
- public void loginToSwagLabsWebTestValid() {
- System.out.println("Sauce - Start loginToSwagLabsWebTestValid test");
- String urlToTest = "https://www.saucedemo.com/";
- driver.get(urlToTest);
-
- login("standard_user", "secret_sauce");
-
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Assert.assertTrue(isOnProductsPage());
- }
- public void login(String user, String pass){
- driver.findElement(usernameInput).sendKeys(user);
- driver.findElement(passwordInput).sendKeys(pass);
- driver.findElement(submitButton).click();
- }
-
- public boolean isOnProductsPage() {
- return driver.findElement(inventoryList).isDisplayed();
- }
-}
diff --git a/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/IOSWebAppTest.java b/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/IOSWebAppTest.java
deleted file mode 100644
index 991d90876..000000000
--- a/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/IOSWebAppTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.examples.simple_example;
-
-import com.helpers.SauceAppiumTestWatcher;
-import io.appium.java_client.ios.IOSDriver;
-import org.junit.Assert;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-import org.openqa.selenium.By;
-import org.openqa.selenium.MutableCapabilities;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.util.Arrays;
-import java.util.List;
-
-import static com.helpers.Constants.*;
-
-public class IOSWebAppTest {
-
- @Rule
- public TestName name = new TestName();
-
- //This rule allows us to set test status with Junit
- @Rule
- public SauceAppiumTestWatcher resultReportingTestWatcher = new SauceAppiumTestWatcher();
-
- private IOSDriver driver;
- By usernameInput = By.id("user-name");
- By passwordInput = By.id("password");
- By submitButton = By.className("btn_action");
- By inventoryList = By.className("inventory_list");
-
- @Before
- public void setUp() throws MalformedURLException {
- System.out.println("Sauce iOS Native App - Before hook");
-
- MutableCapabilities capabilities = new MutableCapabilities();
- MutableCapabilities sauceOptions = new MutableCapabilities();
- URL url;
- String appName;
-
- url = switch (region) {
- case "us" -> new URL(SAUCE_US_URL);
- default -> new URL(SAUCE_EU_URL);
- };
-
- // For all capabilities please check
- // http://appium.io/docs/en/writing-running-appium/caps/#general-capabilities
- // Use the platform configuration https://saucelabs.com/platform/platform-configurator#/
- // to find the simulators/real device names, OS versions and appium versions you can use for your testings
-
- capabilities.setCapability("platformName", "iOS");
- capabilities.setCapability("appium:automationName", "XCuiTest");
- if (rdc.equals("true")) {
- //Allocate any avilable iPhone device with version 14
- capabilities.setCapability("appium:deviceName", "iPhone.*");
- capabilities.setCapability("appium:platformVersion", "14");
- }
- else {
- capabilities.setCapability("appium:deviceName", "iPhone Instant Simulator");
- capabilities.setCapability("appium:platformVersion", "current_major");
- }
- capabilities.setCapability("browserName", "safari");
-
- sauceOptions.setCapability("name", name.getMethodName());
- sauceOptions.setCapability("build", "myWebApp-job-1");
- List tags = Arrays.asList("sauceDemo_ios", "iOS", "Demo", "Web");
- sauceOptions.setCapability("tags", tags);
- sauceOptions.setCapability("username", System.getenv("SAUCE_USERNAME"));
- sauceOptions.setCapability("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
- capabilities.setCapability("sauce:options", sauceOptions);
-
- driver = new IOSDriver(url, capabilities);
-
- resultReportingTestWatcher.setDriver(driver);
- }
-
- @Test
- public void loginToSwagLabsWebTestValid() {
- System.out.println("Sauce - Start loginToSwagLabsWebTestValid test");
- String urlToTest = "https://www.saucedemo.com/";
- driver.get(urlToTest);
-
- login("standard_user", "secret_sauce");
-
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- Assert.assertTrue(isOnProductsPage());
- }
-
- public void login(String user, String pass){
- driver.findElement(usernameInput).sendKeys(user);
- driver.findElement(passwordInput).sendKeys(pass);
- driver.findElement(submitButton).click();
- }
-
- public boolean isOnProductsPage() {
- return driver.findElement(inventoryList).isDisplayed();
- }
-
-}
diff --git a/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/README.md b/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/README.md
deleted file mode 100644
index 0b472d7de..000000000
--- a/appium/appium-web/appium-web-examples/src/test/java/com/examples/simple_example/README.md
+++ /dev/null
@@ -1,78 +0,0 @@
-# Java Web App Simple Example on Sauce Labs
-This folder is a very simple example to get you started with your appium testing on Web App.
-This folder contains examples for:
-
-- [Android Real Devices in the Sauce Labs Cloud](#run-tests-on-sauce-labs-android-real-devices)
-- [Android Emulators in the Sauce Labs Cloud](#run-tests-on-sauce-labs-android-emulators)
-- [iOS Real Devices in the Sauce Labs Cloud](#run-tests-on-sauce-labs-ios-real-devices)
-- [iOS Simulators in the Sauce Labs Cloud](#run-tests-on-sauce-labs-ios-simulators)
-
-## Important information
-### Environment variables for Sauce Labs
-The examples in this repository use environment variables, make sure you've added the following
-
- # For Sauce Labs Emulators/Simulators/Real devices
- export SAUCE_USERNAME=********
- export SAUCE_ACCESS_KEY=*******
-
-
-## Run tests on Sauce Labs Android real devices
-If you want to run the tests on Sauce Labs Android Real Devices then you can run the Android test with
-
- // If using the US DC
- mvn clean test -Dtest=AndroidWebAppTest -Dregion=us
-
- // If using the EU DC
- mvn clean test -Dtest=AndroidWebAppTest -Dregion=eu
-
-The tests, which can be found [here](AndroidWebAppTest.java), will be executed on:
-- Any available Samsung device with OS Android 12
-
-> The devices use *dynamic* allocation, meaning they will try to find an available device that matches a regular expression.
-> NOTE: Make sure you are in the folder `appium-app-examples` when you execute this command
-
-## Run tests on Sauce Labs Android Emulators
-If you want to run the tests on Sauce Labs Android emulators then you can run the Android test with
-
- // If using the US DC
- mvn clean test -Dtest=AndroidWebAppTest -Dregion=us -Drdc=false
-
- // If using the EU DC
- mvn clean test -Dtest=AndroidWebAppTest -Dregion=eu -Drdc=false
-
-The tests, which can be found [here](AndroidWebAppTest.java), will be executed on:
-
-- Android GoogleAPI Emulator with OS Android 12
-> NOTE: Make sure you are in the folder `appium-web-examples` when you execute this command
-
-## Run tests on Sauce Labs iOS real devices
-If you want to run the tests on iOS Sauce Labs Real Devices then you can run the iOS test with
-
- // If using the US DC
- mvn clean install -Dtest=IOSWebAppTest -Dregion=us
-
- // If using the EU DC
- mvn clean install -Dtest=IOSWebAppTest -Dregion=eu
-
-The tests, which can be found [here](IOSWebAppTest.java), will be executed on:
-
-- Any available iPhone with OS 14
-
-> The devices use *dynamic* allocation, meaning they will try to find an available device that matches a regular
-expression.
-> NOTE: Make sure you are in the folder `appium-web-examples` when you execute this command
-
-## Run tests on Sauce Labs iOS Simulators
-If you want to run the tests on Sauce Labs iOS simulators then you can run the Android test with
-
- // If using the US DC
- mvn clean install -Dtest=IOSWebAppTest -Dregion=us -Drdc=false
-
- // If using the EU DC
- mvn clean install -Dtest=IOSWebAppTest -Dregion=eu -Drdc=false
-
-The tests, which can be found [here](IOSWebAppTest.java), will be executed on:
-
-- iPhone 11 Simulator with OS 14
-> NOTE: Make sure you are in the folder `appium-web-examples` when you execute this command
-
diff --git a/appium/appium-web/appium-web-examples/src/test/java/com/helpers/Constants.java b/appium/appium-web/appium-web-examples/src/test/java/com/helpers/Constants.java
deleted file mode 100644
index 9b5aaae6d..000000000
--- a/appium/appium-web/appium-web-examples/src/test/java/com/helpers/Constants.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.helpers;
-
-public class Constants {
- public static final String region = System.getProperty("region", "eu");
- public static final String host = System.getProperty("host", "saucelabs");
- public static final String rdc = System.getProperty("rdc", "true");
-
- public static final String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
- public static final String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
-
-}
diff --git a/appium/appium-web/appium-web-examples/src/test/java/com/helpers/SauceAppiumTestWatcher.java b/appium/appium-web/appium-web-examples/src/test/java/com/helpers/SauceAppiumTestWatcher.java
deleted file mode 100644
index fc431012b..000000000
--- a/appium/appium-web/appium-web-examples/src/test/java/com/helpers/SauceAppiumTestWatcher.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package com.helpers;
-
-import io.appium.java_client.AppiumDriver;
-import org.junit.rules.TestWatcher;
-import org.junit.runner.Description;
-
-public class SauceAppiumTestWatcher extends TestWatcher {
- private AppiumDriver driver;
-
- public void setDriver(AppiumDriver driver)
- {
- this.driver = driver;
- }
-
- @Override
- protected void succeeded(Description description) {
- if(driver != null)
- {
- driver.executeScript("sauce:job-result=passed");
- driver.quit();
- }
- }
-
- @Override
- public void failed(Throwable e, Description description) {
- if(driver != null)
- {
- driver.executeScript("sauce:job-result=failed");
- driver.quit();
- }
- }
-}
diff --git a/appium/mobile-web/README.md b/appium/mobile-web/README.md
new file mode 100644
index 000000000..2d3dca322
--- /dev/null
+++ b/appium/mobile-web/README.md
@@ -0,0 +1,80 @@
+# Testing a Mobile Website
+
+If you need to test your website on mobile devices with Appium, you have 5 choices.
+* Chrome on Android Device on Sauce Labs Real Device Cloud
+* Safari on iOS Device on Sauce Labs Real Device Cloud
+* Chrome on Emulator in Sauce Labs Virtual Device Cloud
+* Safari on Simulator in Sauce Labs Virtual Device Cloud
+* [Chrome Mobile Emulation](../../selenium-junit5-examples/README.md#chrome-mobile-emulation)
+
+[Setup Instructions](https://github.com/saucelabs-training/demo-java/blob/main/README.md#%EF%B8%8Fsetupprerequisites)
+and
+[Contribution Information](https://github.com/saucelabs-training/demo-java/blob/main/README.md#contributing)
+can be found on the [Main README](https://github.com/saucelabs-training/demo-java/blob/main/README.md).
+
+## Executing Examples
+The Website being tested is [Swag Labs](https://www.saucedemo.com/).
+
+1. After cloning this repo from instructions, change to this subdirectory:
+ ```
+ $ cd appium/mobile-web
+ ```
+
+2. Run the following command to update any package dependencies:
+ ```
+ $ mvn dependency:resolve
+ ```
+3. Then run the following command to compile your test code:
+ ```
+ $ mvn test-compile
+ ```
+4. Finally, run the following test to see if you've properly configured the test environment:
+ ```
+ $ mvn clean test
+ ```
+
+ See passing tests on [GitHub Actions](https://github.com/saucelabs-training/demo-java/actions/workflows/appium-examples.yml)
+
+## Configurations
+This code allows toggling what cloud the tests will target.
+The capabilities for these configurations can be copied from the
+[Platform Configurator](https://app.saucelabs.com/platform-configurator),
+and can be viewed in the [Test Configurations](src/test/java/com/saucedemo/TestConfigurations.java) file.
+
+### Chrome on Virtual Device Cloud (default)
+Tests will execute on an Android Emulator:
+ ```
+ $ mvn clean test -Dsauce.browser=chrome -Dsauce.cloud=vdc
+ ```
+
+### Safari on Virtual Device Cloud
+Tests will execute on an iOS Simulator:
+ ```
+ $ mvn clean test -Dsauce.browser=safari -Dsauce.cloud=vdc
+ ```
+
+### Chrome on Real Device Cloud
+Tests will execute on a real Android device:
+ ```
+ $ mvn clean test -Dsauce.browser=chrome -Dsauce.cloud=rdc
+ ```
+
+### Safari on Real Device Cloud
+Tests will execute on a real iOS device:
+ ```
+ $ mvn clean test -Dsauce.browser=safari -Dsauce.cloud=rdc
+ ```
+
+## Disclaimer
+
+> The code in these scripts is provided on an "AS-IS" basis without warranty of any kind, either
+> express or implied, including without limitation any implied warranties of condition,
+> uninterrupted
+> use, merchantability, fitness for a particular purpose, or non-infringement. These scripts are
+> provided for educational and demonstration purposes only and should not be used in production.
+> Issues regarding these scripts should be submitted through GitHub. These scripts are maintained by
+> the Technical Services team at Sauce Labs.
+>
+> Some examples in this repository, such as `appium-example`, `parallel-testing`, and `headless`,
+> may require a different account tier beyond free trial. Please contact
+> the [Sauce Labs Sales Team](https://saucelabs.com/contact) for support and information.
diff --git a/appium/mobile-web/pom.xml b/appium/mobile-web/pom.xml
new file mode 100644
index 000000000..4f896eab3
--- /dev/null
+++ b/appium/mobile-web/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+
+ com.saucelabs
+ mobile-web
+ 1.0-SNAPSHOT
+
+
+ 11
+ 11
+ UTF-8
+ 10
+
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.10.3
+ test
+
+
+ io.appium
+ java-client
+ 9.3.0
+ test
+
+
+ com.titusfortner
+ selenium-logger
+ 2.4.0
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 3.5.1
+
+
+
+ junit.jupiter.execution.parallel.enabled = true
+ junit.jupiter.execution.parallel.mode.default = concurrent
+ junit.jupiter.execution.parallel.mode.classes.default = concurrent
+ junit.jupiter.execution.parallel.config.strategy = fixed
+ junit.jupiter.execution.parallel.config.fixed.parallelism = ${surefire.parallel}
+ junit.jupiter.execution.parallel.config.fixed.max-pool-size = ${surefire.parallel}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/appium/mobile-web/src/test/java/com/saucedemo/AuthenticationTest.java b/appium/mobile-web/src/test/java/com/saucedemo/AuthenticationTest.java
new file mode 100644
index 000000000..b69ca3efc
--- /dev/null
+++ b/appium/mobile-web/src/test/java/com/saucedemo/AuthenticationTest.java
@@ -0,0 +1,49 @@
+package com.saucedemo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebElement;
+
+public class AuthenticationTest extends TestBase {
+
+ @Test
+ public void signInUnsuccessful() {
+ driver.get("https://www.saucedemo.com/");
+
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("locked_out_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+
+ WebElement errorElement = driver.findElement(By.cssSelector("[data-test='error']"));
+ Assertions.assertTrue(
+ errorElement.getText().contains("Sorry, this user has been locked out"), "Error Not Found");
+ }
+
+ @Test
+ public void signInSuccessful() {
+ driver.get("https://www.saucedemo.com/");
+
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+
+ Assertions.assertEquals(
+ "https://www.saucedemo.com/inventory.html", driver.getCurrentUrl(), "Login Not Successful");
+ }
+
+ @Test
+ public void logout() throws InterruptedException {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.id("react-burger-menu-btn")).click();
+ Thread.sleep(1000);
+
+ driver.findElement(By.id("logout_sidebar_link")).click();
+
+ Assertions.assertEquals(
+ "https://www.saucedemo.com/", driver.getCurrentUrl(), "Logout Not Successful");
+ }
+}
diff --git a/appium/mobile-web/src/test/java/com/saucedemo/CartTest.java b/appium/mobile-web/src/test/java/com/saucedemo/CartTest.java
new file mode 100644
index 000000000..1bc92a98e
--- /dev/null
+++ b/appium/mobile-web/src/test/java/com/saucedemo/CartTest.java
@@ -0,0 +1,91 @@
+package com.saucedemo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+
+public class CartTest extends TestBase {
+
+ @Test
+ public void addFromProductPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+
+ driver
+ .findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-bolt-t-shirt']"))
+ .click();
+
+ Assertions.assertEquals(
+ "1",
+ driver.findElement(By.className("shopping_cart_badge")).getText(),
+ "Item not correctly added to cart");
+ }
+
+ @Test
+ public void removeFromProductPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver
+ .findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-bolt-t-shirt']"))
+ .click();
+
+ driver
+ .findElement(By.cssSelector("button[data-test='remove-sauce-labs-bolt-t-shirt']"))
+ .click();
+
+ Assertions.assertTrue(
+ driver.findElements(By.className("shopping_cart_badge")).isEmpty(),
+ "Item not correctly removed from cart");
+ }
+
+ @Test
+ public void addFromInventoryPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+
+ Assertions.assertEquals("1", driver.findElement(By.className("shopping_cart_badge")).getText());
+ }
+
+ @Test
+ public void removeFromInventoryPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver
+ .findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-bike-light']"))
+ .click();
+
+ driver.findElement(By.cssSelector("button[data-test='remove-sauce-labs-bike-light']")).click();
+
+ Assertions.assertTrue(
+ driver.findElements(By.className("shopping_cart_badge")).isEmpty(),
+ "Shopping Cart is not empty");
+ }
+
+ @Test
+ public void removeFromCartPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver
+ .findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-backpack']"))
+ .click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='remove-sauce-labs-backpack']")).click();
+
+ Assertions.assertTrue(
+ driver.findElements(By.className("shopping_cart_badge")).isEmpty(),
+ "Shopping Cart is not empty");
+ }
+}
diff --git a/appium/mobile-web/src/test/java/com/saucedemo/CheckoutTest.java b/appium/mobile-web/src/test/java/com/saucedemo/CheckoutTest.java
new file mode 100644
index 000000000..b8b9f1ae5
--- /dev/null
+++ b/appium/mobile-web/src/test/java/com/saucedemo/CheckoutTest.java
@@ -0,0 +1,72 @@
+package com.saucedemo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+
+public class CheckoutTest extends TestBase {
+
+ @Test
+ public void badInfo() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+ driver.findElement(By.cssSelector("button[data-test='checkout']")).click();
+
+ driver.findElement(By.cssSelector("input[data-test='continue']")).click();
+
+ Assertions.assertTrue(
+ driver
+ .findElement(By.cssSelector("input[data-test='firstName']"))
+ .getAttribute("class")
+ .contains("error"),
+ "Expected error not found on page");
+ }
+
+ @Test
+ public void goodInfo() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+ driver.findElement(By.cssSelector("button[data-test='checkout']")).click();
+
+ driver.findElement(By.cssSelector("input[data-test='firstName']")).sendKeys("Luke");
+ driver.findElement(By.cssSelector("input[data-test='lastName']")).sendKeys("Perry");
+ driver.findElement(By.cssSelector("input[data-test='postalCode']")).sendKeys("90210");
+
+ driver.findElement(By.cssSelector("input[data-test='continue']")).click();
+
+ Assertions.assertEquals(
+ "https://www.saucedemo.com/checkout-step-two.html",
+ driver.getCurrentUrl(),
+ "Information Submission Unsuccessful");
+ }
+
+ @Test
+ public void completeCheckout() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+ driver.findElement(By.cssSelector("button[data-test='checkout']")).click();
+ driver.findElement(By.cssSelector("input[data-test='firstName']")).sendKeys("Luke");
+ driver.findElement(By.cssSelector("input[data-test='lastName']")).sendKeys("Perry");
+ driver.findElement(By.cssSelector("input[data-test='postalCode']")).sendKeys("90210");
+ driver.findElement(By.cssSelector("input[data-test='continue']")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='finish']")).click();
+
+ Assertions.assertEquals(
+ "https://www.saucedemo.com/checkout-complete.html", driver.getCurrentUrl());
+
+ Assertions.assertTrue(driver.findElement(By.className("complete-text")).isDisplayed());
+ }
+}
diff --git a/appium/mobile-web/src/test/java/com/saucedemo/NavigationTest.java b/appium/mobile-web/src/test/java/com/saucedemo/NavigationTest.java
new file mode 100644
index 000000000..be1acdb82
--- /dev/null
+++ b/appium/mobile-web/src/test/java/com/saucedemo/NavigationTest.java
@@ -0,0 +1,70 @@
+package com.saucedemo;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.openqa.selenium.By;
+
+public class NavigationTest extends TestBase {
+
+ @Test
+ public void cancelFromCart() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='continue-shopping']")).click();
+
+ Assertions.assertEquals("https://www.saucedemo.com/inventory.html", driver.getCurrentUrl());
+ }
+
+ @Test
+ public void cancelFromInfoPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+ driver.findElement(By.cssSelector("button[data-test='checkout']")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='cancel']")).click();
+
+ Assertions.assertEquals("https://www.saucedemo.com/cart.html", driver.getCurrentUrl());
+ }
+
+ @Test
+ public void cancelFromCheckoutPage() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+ driver.findElement(By.cssSelector("button[data-test='checkout']")).click();
+ driver.findElement(By.cssSelector("input[data-test='firstName']")).sendKeys("Luke");
+ driver.findElement(By.cssSelector("input[data-test='lastName']")).sendKeys("Perry");
+ driver.findElement(By.cssSelector("input[data-test='postalCode']")).sendKeys("90210");
+ driver.findElement(By.cssSelector("input[data-test='continue']")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='cancel']")).click();
+
+ Assertions.assertEquals("https://www.saucedemo.com/inventory.html", driver.getCurrentUrl());
+ }
+
+ @Test
+ public void startCheckout() {
+ driver.get("https://www.saucedemo.com/");
+ driver.findElement(By.cssSelector("input[data-test='username']")).sendKeys("standard_user");
+ driver.findElement(By.cssSelector("input[data-test='password']")).sendKeys("secret_sauce");
+ driver.findElement(By.cssSelector("input[data-test='login-button']")).click();
+ driver.findElement(By.cssSelector("button[data-test='add-to-cart-sauce-labs-onesie']")).click();
+ driver.findElement(By.className("shopping_cart_link")).click();
+
+ driver.findElement(By.cssSelector("button[data-test='checkout']")).click();
+
+ Assertions.assertEquals(
+ "https://www.saucedemo.com/checkout-step-one.html", driver.getCurrentUrl());
+ }
+}
diff --git a/appium/mobile-web/src/test/java/com/saucedemo/TestBase.java b/appium/mobile-web/src/test/java/com/saucedemo/TestBase.java
new file mode 100644
index 000000000..e380cca23
--- /dev/null
+++ b/appium/mobile-web/src/test/java/com/saucedemo/TestBase.java
@@ -0,0 +1,85 @@
+package com.saucedemo;
+
+import io.appium.java_client.AppiumDriver;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.TestInfo;
+import org.junit.jupiter.api.extension.ExtensionContext;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.junit.jupiter.api.extension.TestWatcher;
+import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.JavascriptExecutor;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+public class TestBase {
+
+ static {
+ String buildName = "Default Build Name";
+ String buildNumber = String.valueOf(System.currentTimeMillis());
+ System.setProperty("build.name", buildName + ": " + buildNumber);
+ }
+
+ public WebDriver driver;
+ @RegisterExtension public TestBase.SauceTestWatcher watcher = new SauceTestWatcher();
+ public static final String DATA_CENTER = System.getProperty("sauce.region", "us");
+ public static final String SAUCE_EU_URL = "https://ondemand.eu-central-1.saucelabs.com/wd/hub";
+ public static final String SAUCE_US_URL = "https://ondemand.us-west-1.saucelabs.com/wd/hub";
+ public static final String SAUCE_URL = DATA_CENTER.equals("us") ? SAUCE_US_URL : SAUCE_EU_URL;
+
+ @BeforeEach
+ public void setup(TestInfo testInfo) throws MalformedURLException {
+ Capabilities capabilities = TestConfigurations.getCapabilities(testInfo);
+
+ this.driver = new AppiumDriver(new URL(SAUCE_URL), capabilities);
+ this.driver.manage().timeouts().implicitlyWait(Duration.of(5, ChronoUnit.SECONDS));
+ }
+
+ public class SauceTestWatcher implements TestWatcher {
+ @Override
+ public void testSuccessful(ExtensionContext context) {
+ if (driver != null) {
+ printResults(context.getDisplayName());
+ try {
+ ((JavascriptExecutor) driver).executeScript("sauce:job-result=passed");
+ driver.quit();
+ } catch (Exception e) {
+ System.out.println("problem with using driver: " + e);
+ }
+ }
+ }
+
+ @Override
+ public void testFailed(ExtensionContext context, Throwable cause) {
+ if (driver != null) {
+ printResults(context.getDisplayName());
+
+ try {
+ ((JavascriptExecutor) driver).executeScript("sauce:job-result=failed");
+ driver.quit();
+ } catch (Exception e) {
+ System.out.println("problem with using driver: " + e);
+ }
+ }
+ }
+
+ public void printResults(String name) {
+ String sessionId;
+ String sauceCloud = System.getProperty("sauce.cloud", "vdc");
+ if (sauceCloud.equalsIgnoreCase("rdc")) {
+ sessionId = (String) ((RemoteWebDriver) driver).getCapabilities().getCapability("appium:jobUuid");
+ } else {
+ sessionId = String.valueOf(((RemoteWebDriver) driver).getSessionId());
+ }
+
+ String sauceReporter =
+ String.format("SauceOnDemandSessionID=%s job-name=%s", sessionId, name);
+ String sauceTestLink =
+ String.format("Test Job Link: https://app.saucelabs.com/tests/%s", sessionId);
+ System.out.print(sauceReporter + "\n" + sauceTestLink + "\n");
+ }
+ }
+}
diff --git a/appium/mobile-web/src/test/java/com/saucedemo/TestConfigurations.java b/appium/mobile-web/src/test/java/com/saucedemo/TestConfigurations.java
new file mode 100644
index 000000000..77cb40d92
--- /dev/null
+++ b/appium/mobile-web/src/test/java/com/saucedemo/TestConfigurations.java
@@ -0,0 +1,106 @@
+package com.saucedemo;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.jupiter.api.TestInfo;
+import org.openqa.selenium.Capabilities;
+import org.openqa.selenium.MutableCapabilities;
+
+public class TestConfigurations {
+ static final String SAUCE_CLOUD = System.getProperty("sauce.cloud", "vdc");
+ static final String SAUCE_PLATFORM = System.getProperty("sauce.browser", "chrome");
+ static final String BUILD_TIME = String.valueOf(System.currentTimeMillis());
+
+ // Best practice would put these constants in a config file and dynamically pull them.
+ public static Capabilities getCapabilities(TestInfo testInfo) {
+ if (SAUCE_PLATFORM.equalsIgnoreCase("chrome")) {
+ if (SAUCE_CLOUD.equalsIgnoreCase("vdc")) {
+ return androidVDC(testInfo);
+ } else if (SAUCE_CLOUD.equalsIgnoreCase("rdc")) {
+ return androidRDC(testInfo);
+ }
+ }
+
+ if (SAUCE_PLATFORM.equalsIgnoreCase("safari")) {
+ if (SAUCE_CLOUD.equalsIgnoreCase("vdc")) {
+ return iosVDC(testInfo);
+ } else if (SAUCE_CLOUD.equalsIgnoreCase("rdc")) {
+ return iosRDC(testInfo);
+ }
+ }
+
+ throw new RuntimeException("Invalid platform/cloud combination. Browser must be chrome or safari and cloud must be vdc or rdc.");
+ }
+
+ private static Capabilities androidVDC(TestInfo testInfo) {
+ Map caps = new HashMap<>();
+ caps.put("platformName", "Android");
+ caps.put("browserName", "Chrome");
+ caps.put("appium:deviceName", "Android GoogleAPI Emulator");
+ caps.put("appium:platformVersion", "current_major");
+ caps.put("appium:automationName", "UiAutomator2");
+
+ Map sauceOptions = new HashMap<>();
+ sauceOptions.put("username", System.getenv("SAUCE_USERNAME"));
+ sauceOptions.put("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
+ sauceOptions.put("name", testInfo.getDisplayName());
+ sauceOptions.put("build", "Android Web VDC: " + BUILD_TIME);
+ caps.put("sauce:options", sauceOptions);
+
+ return new MutableCapabilities(caps);
+ }
+
+ private static Capabilities androidRDC(TestInfo testInfo) {
+ Map caps = new HashMap<>();
+ caps.put("platformName", "Android");
+ caps.put("browserName", "Chrome");
+ caps.put("appium:deviceName", "Google.*");
+ caps.put("appium:automationName", "UiAutomator2");
+
+ Map sauceOptions = new HashMap<>();
+ sauceOptions.put("username", System.getenv("SAUCE_USERNAME"));
+ sauceOptions.put("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
+ sauceOptions.put("name", testInfo.getDisplayName());
+ sauceOptions.put("build", "Android Web RDC: " + BUILD_TIME);
+ sauceOptions.put("appiumVersion", "latest");
+ caps.put("sauce:options", sauceOptions);
+
+ return new MutableCapabilities(caps);
+ }
+
+ private static Capabilities iosVDC(TestInfo testInfo) {
+ Map caps = new HashMap<>();
+ caps.put("platformName", "iOS");
+ caps.put("browserName", "Safari");
+ caps.put("appium:deviceName", "iPhone Simulator");
+ caps.put("appium:platformVersion", "current_major");
+ caps.put("appium:automationName", "XCUITest");
+
+ Map sauceOptions = new HashMap<>();
+ sauceOptions.put("username", System.getenv("SAUCE_USERNAME"));
+ sauceOptions.put("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
+ sauceOptions.put("name", testInfo.getDisplayName());
+ sauceOptions.put("build", "iOS Web VDC: " + BUILD_TIME);
+ caps.put("sauce:options", sauceOptions);
+
+ return new MutableCapabilities(caps);
+ }
+
+ private static Capabilities iosRDC(TestInfo testInfo) {
+ Map caps = new HashMap<>();
+ caps.put("platformName", "iOS");
+ caps.put("browserName", "Safari");
+ caps.put("appium:deviceName", "iPhone.*");
+ caps.put("appium:automationName", "XCUITest");
+
+ Map sauceOptions = new HashMap<>();
+ sauceOptions.put("username", System.getenv("SAUCE_USERNAME"));
+ sauceOptions.put("accessKey", System.getenv("SAUCE_ACCESS_KEY"));
+ sauceOptions.put("appiumVersion", "latest");
+ sauceOptions.put("name", testInfo.getDisplayName());
+ sauceOptions.put("build", "iOS Web RDC: " + BUILD_TIME);
+ caps.put("sauce:options", sauceOptions);
+
+ return new MutableCapabilities(caps);
+ }
+}