diff --git a/.github/workflows/sdk-sample-test.yml b/.github/workflows/sdk-sample-test.yml new file mode 100644 index 0000000..0a8ab2a --- /dev/null +++ b/.github/workflows/sdk-sample-test.yml @@ -0,0 +1,70 @@ +# Runs the BrowserStack SDK sample against a given commit and reports a status check. +# Trigger: Actions tab -> "Vanilla Java Appium App Automate SDK sample test" -> Run workflow -> paste the PR's full commit SHA. +# Requires repo secrets: BROWSERSTACK_USERNAME, BROWSERSTACK_ACCESS_KEY. +# NOTE (App Automate): the app under test is referenced via `app: bs://...` in browserstack.yml; +# ensure that uploaded app exists on the account whose secrets are used (re-upload + update if expired). +name: Vanilla Java Appium App Automate SDK sample test + +on: + workflow_dispatch: + inputs: + commit_sha: + description: 'The full commit id to build' + required: true + +jobs: + sdk-sample: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + max-parallel: 3 + matrix: + os: [ubuntu-latest] + java: ['11', '17'] + name: vanilla-java-appium JDK ${{ matrix.java }} sample + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + defaults: + run: + working-directory: android + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.commit_sha }} + - name: Mark status check in_progress + uses: actions/github-script@v7 + env: + job_name: vanilla-java-appium JDK ${{ matrix.java }} sample + commit_sha: ${{ github.event.inputs.commit_sha }} + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, repo: context.repo.repo, + name: process.env.job_name, head_sha: process.env.commit_sha, + status: 'in_progress' + }).catch(e => console.log('check create failed:', e.status)); + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java }} + - name: Run sample test + run: | + mvn compile + mvn test + - name: Mark status check completed + if: always() + uses: actions/github-script@v7 + env: + conclusion: ${{ job.status }} + job_name: vanilla-java-appium JDK ${{ matrix.java }} sample + commit_sha: ${{ github.event.inputs.commit_sha }} + with: + github-token: ${{ github.token }} + script: | + await github.rest.checks.create({ + owner: context.repo.owner, repo: context.repo.repo, + name: process.env.job_name, head_sha: process.env.commit_sha, + status: 'completed', conclusion: process.env.conclusion + }).catch(e => console.log('check create failed:', e.status)); diff --git a/README.md b/README.md index 0a2c534..528eb61 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,82 @@ -# vanilla-java-appium-app-browserstack -We require the following new public repositories under the browserstack GitHub organization to host customer-facing sample projects for the BrowserStack SDK. +# Vanilla Java Appium with BrowserStack App Automate + +Run a vanilla Java Appium (Android) test on real devices via +[BrowserStack App Automate](https://app-automate.browserstack.com/) using the +[BrowserStack Java SDK](https://www.browserstack.com/docs/app-automate/appium/getting-started/java). + +The SDK is attached as a `-javaagent`, reads `browserstack.yml`, selects the +pre-uploaded app, starts the Appium session on the chosen device, and reports +results back to BrowserStack — your test code stays plain `io.appium:java-client` +driving JUnit 5. + +## Prerequisites + +- A [BrowserStack](https://www.browserstack.com/) account (username + access key). +- JDK 8+ and Maven 3.6+. + +## Setup + +```bash +git clone +cd vanilla-java-appium/android +mvn -DskipTests compile +``` + +Configure credentials either in `browserstack.yml` (`userName` / `accessKey`) +or as environment variables: + +```bash +export BROWSERSTACK_USERNAME="YOUR_USERNAME" +export BROWSERSTACK_ACCESS_KEY="YOUR_ACCESS_KEY" +``` + +The sample app is already referenced in `android/browserstack.yml` as a +pre-uploaded `bs://` app id (WikipediaSample.apk). To use your own build, set +`app:` to a local `.apk` path and the SDK will upload it for you. + +## Run Sample Test + +```bash +cd android +mvn test -Dtest=BStackSampleTest +``` + +The `-javaagent` is wired into the Maven Surefire plugin's `argLine` (the jar +path resolves from `~/.m2` via the `maven-dependency-plugin` `properties` goal), +so a plain `mvn test` attaches the SDK automatically. The sample test: + +1. taps **Search Wikipedia** (accessibility id), +2. types `BrowserStack` into the search field, and +3. asserts the results list rendered at least one entry. + +## Run Local Test + +`BStackLocalTest` drives `LocalSample.apk` (pkg +`com.example.android.basicnetworking`) and asserts the in-app network check +reports **Up and running**, proving the BrowserStack Local tunnel is connected. +It is `@Disabled` by default because this `android/` dir ships wired to the +Wikipedia sample app. To run it: + +1. point `browserstack.yml` `app:` at `LocalSample.apk`, +2. set `browserstackLocal: true`, +3. remove the `@Disabled` annotation, then: + +```bash +cd android +mvn test -Dtest=BStackLocalTest +``` + +## Notes / Dashboard + +- Watch sessions live and review video/logs at + [app-automate.browserstack.com](https://app-automate.browserstack.com/). +- `testObservability: true` also surfaces this build in + [BrowserStack Test Observability](https://observability.browserstack.com/). +- The SDK dependency `com.browserstack:browserstack-java-sdk` uses `LATEST`, + matching the published-sample convention (resolved to `1.59.7` at validation + time). +- `io.appium:java-client` is pinned to `8.2.1` with `selenium-java` `4.5.0`. + Newer java-client (8.6.0) trips the SDK 1.59.7 javaagent on an empty/injected + capability set (`IllegalArgumentException: Capabilities must be set`, session + never starts); the 8.2.1 + Selenium 4.5.0 combo is the known-good pairing and + passes a live device session. diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..783ca71 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,7 @@ +target/ +.idea/ +*.iml +log/ +local.log +*.log +.DS_Store diff --git a/android/browserstack.yml b/android/browserstack.yml new file mode 100644 index 0000000..ad692f5 --- /dev/null +++ b/android/browserstack.yml @@ -0,0 +1,50 @@ +# ============================= +# Set BrowserStack Credentials +# ============================= +# Add your BrowserStack userName and accessKey here or set BROWSERSTACK_USERNAME and +# BROWSERSTACK_ACCESS_KEY as env variables +userName: YOUR_USERNAME +accessKey: YOUR_ACCESS_KEY + +# ====================== +# BrowserStack Reporting +# ====================== +projectName: BrowserStack Samples +buildName: appauto-vanilla-java-appium +buildIdentifier: '#${BUILD_NUMBER}' +# `framework` lets the Java SDK instrument the JUnit 5 runner so it can report +# per-test context (name, status) to BrowserStack. Unlike the Node App Automate +# samples (where `framework` is injected into bstack:options and the Appium hub +# rejects it), the Java SDK reports test context out-of-band via the -javaagent +# and does NOT push `framework` into the W3C session caps, so it is safe here. +framework: junit5 + +# Set `app` to the pre-uploaded BrowserStack app id (bs://...) or a local path. +app: bs://92d48b416632f2b1734259565ceab61b05ad0b24 + +# ======================================= +# Platforms (Devices to test) +# ======================================= +platforms: + - deviceName: Samsung Galaxy S22 Ultra + osVersion: "12.0" + platformName: android + +# ======================= +# Parallels per Platform +# ======================= +parallelsPerPlatform: 1 + +source: vanilla-java-appium-app-browserstack:sample-sdk:v1.0 + +# ====================== +# Test Observability +# ====================== +testObservability: true + +# =================== +# Debugging features +# =================== +debug: true +networkLogs: true +consoleLogs: errors diff --git a/android/pom.xml b/android/pom.xml new file mode 100644 index 0000000..88eafe4 --- /dev/null +++ b/android/pom.xml @@ -0,0 +1,95 @@ + + + 4.0.0 + + com.browserstack + vanilla-java-appium + 1.0-SNAPSHOT + jar + + vanilla-java-appium + https://www.github.com/browserstack/vanilla-java-appium-app-browserstack + + + UTF-8 + 1.8 + 1.8 + 5.10.2 + + + + + + org.seleniumhq.selenium + selenium-java + 4.5.0 + + + + + io.appium + java-client + 8.2.1 + + + + + org.junit.jupiter + junit-jupiter + ${junit.jupiter.version} + test + + + + + com.browserstack + browserstack-java-sdk + LATEST + compile + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + maven-dependency-plugin + + + getClasspathFilenames + + properties + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + -javaagent:${com.browserstack:browserstack-java-sdk:jar} + + + + + diff --git a/android/src/test/java/com/browserstack/BStackLocalTest.java b/android/src/test/java/com/browserstack/BStackLocalTest.java new file mode 100644 index 0000000..acc4fb4 --- /dev/null +++ b/android/src/test/java/com/browserstack/BStackLocalTest.java @@ -0,0 +1,82 @@ +package com.browserstack; + +import io.appium.java_client.AppiumBy; +import io.appium.java_client.android.AndroidDriver; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Disabled; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.net.URL; +import java.time.Duration; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Local App Automate test against the LocalSample.apk app + * (pkg com.example.android.basicnetworking). + * + * Proves the BrowserStack Local tunnel is connected: triggers the in-app network + * test action and asserts the app reports it is "Up and running". + * + * Requires browserstackLocal: true and the LocalSample.apk app in browserstack.yml. + * Disabled by default because this android/ dir is wired to the Wikipedia sample + * app + a single platform; enable it once you point browserstack.yml at + * LocalSample.apk and set browserstackLocal: true. + */ +@Disabled("Enable after configuring browserstack.yml with LocalSample.apk + browserstackLocal: true") +public class BStackLocalTest { + + private AndroidDriver driver; + + @BeforeEach + public void setUp() throws Exception { + String userName = System.getenv("BROWSERSTACK_USERNAME"); + String accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY"); + + // Minimal capabilities — the SDK injects the app + device caps from + // browserstack.yml on top. platformName satisfies the Appium java-client + // 8.x W3C validation (it rejects a fully-empty capability set). + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability("platformName", "android"); + + driver = new AndroidDriver( + new URL("http://" + userName + ":" + accessKey + "@hub.browserstack.com/wd/hub"), + capabilities); + } + + @Test + public void localTunnelUpAndRunning() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); + + // Trigger the in-app network test action. + wait.until(ExpectedConditions.elementToBeClickable( + AppiumBy.id("com.example.android.basicnetworking:id/test_action"))).click(); + + wait.until(ExpectedConditions.presenceOfElementLocated( + AppiumBy.className("android.widget.TextView"))); + + // Assert a TextView reports the tunnel is up. + List textViews = driver.findElements( + AppiumBy.className("android.widget.TextView")); + + boolean upAndRunning = textViews.stream() + .map(WebElement::getText) + .filter(t -> t != null) + .anyMatch(t -> t.contains("Up and running")); + + assertTrue(upAndRunning, "Expected 'Up and running' text confirming the Local tunnel"); + } + + @AfterEach + public void tearDown() { + if (driver != null) { + driver.quit(); + } + } +} diff --git a/android/src/test/java/com/browserstack/BStackSampleTest.java b/android/src/test/java/com/browserstack/BStackSampleTest.java new file mode 100644 index 0000000..56f8009 --- /dev/null +++ b/android/src/test/java/com/browserstack/BStackSampleTest.java @@ -0,0 +1,78 @@ +package com.browserstack; + +import io.appium.java_client.AppiumBy; +import io.appium.java_client.android.AndroidDriver; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.support.ui.WebDriverWait; + +import java.net.URL; +import java.time.Duration; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertFalse; + +/** + * Sample App Automate test against the WikipediaSample.apk app. + * + * The BrowserStack Java SDK (attached via -javaagent) reads browserstack.yml and + * injects the app + device capabilities into the Appium session, so the driver is + * created with an empty options object pointed at the BrowserStack hub. + * + * Flow: tap "Search Wikipedia" (accessibility id) -> type "BrowserStack" -> + * assert the results list rendered at least one entry. + */ +public class BStackSampleTest { + + private AndroidDriver driver; + + @BeforeEach + public void setUp() throws Exception { + String userName = System.getenv("BROWSERSTACK_USERNAME"); + String accessKey = System.getenv("BROWSERSTACK_ACCESS_KEY"); + + // Minimal capabilities — the SDK injects the app + device caps from + // browserstack.yml on top. platformName satisfies the Appium java-client + // 8.x W3C validation (it rejects a fully-empty capability set). + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setCapability("platformName", "android"); + + driver = new AndroidDriver( + new URL("http://" + userName + ":" + accessKey + "@hub.browserstack.com/wd/hub"), + capabilities); + } + + @Test + public void searchWikipedia() { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(30)); + + // 1. Tap "Search Wikipedia" (accessibility id) to open the search field. + wait.until(ExpectedConditions.elementToBeClickable( + AppiumBy.accessibilityId("Search Wikipedia"))).click(); + + // 2. Type "BrowserStack" into the search input. + WebElement searchInput = wait.until(ExpectedConditions.elementToBeClickable( + AppiumBy.id("org.wikipedia.alpha:id/search_src_text"))); + searchInput.sendKeys("BrowserStack"); + + // Give the results list a moment to populate. + wait.until(ExpectedConditions.presenceOfElementLocated( + AppiumBy.className("android.widget.TextView"))); + + // 3. Assert at least one search result rendered. + List results = driver.findElements( + AppiumBy.className("android.widget.TextView")); + assertFalse(results.isEmpty(), "Expected Wikipedia search to return results"); + } + + @AfterEach + public void tearDown() { + if (driver != null) { + driver.quit(); + } + } +}