From 1ab3c8254c56abd39b14792cfdbb464e66433139 Mon Sep 17 00:00:00 2001 From: Aakash Hotchandani Date: Wed, 10 Jun 2026 18:31:55 +0530 Subject: [PATCH 1/3] Add vanilla-java-appium App Automate sample (Appium, Android) --- .github/workflows/Semgrep.yml | 49 ---------- .npmrc | 7 -- CODEOWNERS | 1 - README.md | 84 +++++++++++++++- android/.gitignore | 7 ++ android/browserstack.yml | 50 ++++++++++ android/pom.xml | 95 +++++++++++++++++++ .../com/browserstack/BStackLocalTest.java | 82 ++++++++++++++++ .../com/browserstack/BStackSampleTest.java | 78 +++++++++++++++ 9 files changed, 394 insertions(+), 59 deletions(-) delete mode 100644 .github/workflows/Semgrep.yml delete mode 100644 .npmrc delete mode 100644 CODEOWNERS create mode 100644 android/.gitignore create mode 100644 android/browserstack.yml create mode 100644 android/pom.xml create mode 100644 android/src/test/java/com/browserstack/BStackLocalTest.java create mode 100644 android/src/test/java/com/browserstack/BStackSampleTest.java diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml deleted file mode 100644 index 5398af9..0000000 --- a/.github/workflows/Semgrep.yml +++ /dev/null @@ -1,49 +0,0 @@ -# Name of this GitHub Actions workflow. -name: Semgrep - -on: - # Scan changed files in PRs (diff-aware scanning): - # The branches below must be a subset of the branches above - pull_request: - branches: ["master", "main"] - push: - branches: ["master", "main"] - schedule: - - cron: '0 6 * * *' - - -permissions: - contents: read - -jobs: - semgrep: - # User definable name of this GitHub Actions job. - permissions: - contents: read # for actions/checkout to fetch code - security-events: write # for github/codeql-action/upload-sarif to upload SARIF results - name: semgrep/ci - # If you are self-hosting, change the following `runs-on` value: - runs-on: ubuntu-latest - - container: - # A Docker image with Semgrep installed. Do not change this. - image: returntocorp/semgrep - - # Skip any PR created by dependabot to avoid permission issues: - if: (github.actor != 'dependabot[bot]') - - steps: - # Fetch project source with GitHub Actions Checkout. - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - # Run the "semgrep ci" command on the command line of the docker image. - - run: semgrep ci --sarif --output=semgrep.sarif - env: - # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. - SEMGREP_RULES: p/default # more at semgrep.dev/explore - - - name: Upload SARIF file for GitHub Advanced Security Dashboard - uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 - with: - sarif_file: semgrep.sarif - if: always() - diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 33911e3..0000000 --- a/.npmrc +++ /dev/null @@ -1,7 +0,0 @@ -ignore-scripts=true -strict-ssl=true -save-exact=true -engine-strict=true -legacy-peer-deps=false -audit-level=high -access=public diff --git a/CODEOWNERS b/CODEOWNERS deleted file mode 100644 index c4a6041..0000000 --- a/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @browserstack/automate-public-repos \ No newline at end of file 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(); + } + } +} From 8fced6f520bcfc3d27989423aff9cbce08423fe8 Mon Sep 17 00:00:00 2001 From: Aakash Hotchandani Date: Thu, 11 Jun 2026 14:16:57 +0530 Subject: [PATCH 2/3] Restore scaffold files dropped by import (Semgrep CI workflow, CODEOWNERS, .npmrc) --- .github/workflows/Semgrep.yml | 49 +++++++++++++++++++++++++++++++++++ .npmrc | 7 +++++ CODEOWNERS | 1 + 3 files changed, 57 insertions(+) create mode 100644 .github/workflows/Semgrep.yml create mode 100644 .npmrc create mode 100644 CODEOWNERS diff --git a/.github/workflows/Semgrep.yml b/.github/workflows/Semgrep.yml new file mode 100644 index 0000000..5398af9 --- /dev/null +++ b/.github/workflows/Semgrep.yml @@ -0,0 +1,49 @@ +# Name of this GitHub Actions workflow. +name: Semgrep + +on: + # Scan changed files in PRs (diff-aware scanning): + # The branches below must be a subset of the branches above + pull_request: + branches: ["master", "main"] + push: + branches: ["master", "main"] + schedule: + - cron: '0 6 * * *' + + +permissions: + contents: read + +jobs: + semgrep: + # User definable name of this GitHub Actions job. + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + name: semgrep/ci + # If you are self-hosting, change the following `runs-on` value: + runs-on: ubuntu-latest + + container: + # A Docker image with Semgrep installed. Do not change this. + image: returntocorp/semgrep + + # Skip any PR created by dependabot to avoid permission issues: + if: (github.actor != 'dependabot[bot]') + + steps: + # Fetch project source with GitHub Actions Checkout. + - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # Run the "semgrep ci" command on the command line of the docker image. + - run: semgrep ci --sarif --output=semgrep.sarif + env: + # Add the rules that Semgrep uses by setting the SEMGREP_RULES environment variable. + SEMGREP_RULES: p/default # more at semgrep.dev/explore + + - name: Upload SARIF file for GitHub Advanced Security Dashboard + uses: github/codeql-action/upload-sarif@6c089f53dd51dc3fc7e599c3cb5356453a52ca9e # v2.20.0 + with: + sarif_file: semgrep.sarif + if: always() + diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..33911e3 --- /dev/null +++ b/.npmrc @@ -0,0 +1,7 @@ +ignore-scripts=true +strict-ssl=true +save-exact=true +engine-strict=true +legacy-peer-deps=false +audit-level=high +access=public diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000..c4a6041 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @browserstack/automate-public-repos \ No newline at end of file From 811174575b59b47f78dc79816a6dd5daa52b0a3b Mon Sep 17 00:00:00 2001 From: Aakash Hotchandani Date: Fri, 12 Jun 2026 11:38:41 +0530 Subject: [PATCH 3/3] Add BrowserStack SDK sample-test GitHub Actions workflow (workflow_dispatch) --- .github/workflows/sdk-sample-test.yml | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 .github/workflows/sdk-sample-test.yml 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));