diff --git a/.github/workflows/sdk-sample-test.yml b/.github/workflows/sdk-sample-test.yml
new file mode 100644
index 0000000..044c19e
--- /dev/null
+++ b/.github/workflows/sdk-sample-test.yml
@@ -0,0 +1,72 @@
+# Runs the BrowserStack SDK sample against a given commit and reports a status check.
+# Trigger: Actions tab -> "Vanilla Java SDK sample test" -> Run workflow -> paste the PR's full commit SHA.
+# Requires repo secrets: BROWSERSTACK_USERNAME, BROWSERSTACK_ACCESS_KEY.
+name: Vanilla Java SDK sample test
+
+on:
+ workflow_dispatch:
+ inputs:
+ commit_sha:
+ description: 'The full commit id to build'
+ required: true
+
+permissions:
+ contents: read # checkout
+ checks: write # github-script creates the status check
+
+jobs:
+ sdk-sample:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ max-parallel: 3
+ matrix:
+ os: [ubuntu-latest]
+ java: ['11', '17']
+ name: vanilla-java JDK ${{ matrix.java }} sample
+ env:
+ BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }}
+ BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }}
+ defaults:
+ run:
+ working-directory: .
+ 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 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 compile exec:exec
+ - name: Mark status check completed
+ if: always()
+ uses: actions/github-script@v7
+ env:
+ conclusion: ${{ job.status }}
+ job_name: vanilla-java 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/.gitignore b/.gitignore
new file mode 100644
index 0000000..7265af6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,21 @@
+# Maven
+target/
+
+# IDE
+.idea/
+*.iml
+.vscode/
+.project
+.classpath
+.settings/
+
+# OS
+.DS_Store
+
+# BrowserStack / logs
+local.log
+logs/
+log/
+
+# Credentials — never commit
+.env
diff --git a/README.md b/README.md
index 4521bcd..fd353e5 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,82 @@
-# vanilla-java-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 (Selenium) with BrowserStack
+
+Run Selenium WebDriver tests on [BrowserStack Automate](https://automate.browserstack.com/)
+using the **BrowserStack Java SDK** — no test framework required. The SDK attaches as a
+`-javaagent:`, reads `browserstack.yml`, and runs your tests across the configured platform
+matrix with zero code changes for credentials, platforms, Test Observability, and Local.
+
+This sample contains exactly two tests:
+
+- **Sample test** (`BStackDemoTest`) — an add-to-cart flow on [bstackdemo.com](https://bstackdemo.com/).
+- **Local test** (`BStackLocalTest`) — verifies the BrowserStack Local tunnel by opening
+ `http://bs-local.com:45454` and asserting the page title.
+
+## Prerequisites
+
+- A [BrowserStack account](https://www.browserstack.com/users/sign_up) (username + access key).
+- **JDK 11+** — Selenium 4.23 requires Java 11 (`maven.compiler.source/target = 11`).
+- **Maven 3.6+**.
+
+## Setup
+
+1. Clone this repository and change into it:
+ ```bash
+ git clone https://github.com/browserstack/vanilla-java-browserstack.git
+ cd vanilla-java-browserstack
+ ```
+2. Configure your BrowserStack credentials. Either edit `userName` / `accessKey` in
+ `browserstack.yml`, or export them as environment variables (env vars take precedence):
+ ```bash
+ export BROWSERSTACK_USERNAME="YOUR_USERNAME"
+ export BROWSERSTACK_ACCESS_KEY="YOUR_ACCESS_KEY"
+ ```
+3. Resolve dependencies (downloads Selenium and the BrowserStack Java SDK):
+ ```bash
+ mvn clean compile
+ ```
+
+The `-javaagent:` jar is wired automatically: the `maven-dependency-plugin` resolves the
+SDK jar path into the `${com.browserstack:browserstack-java-sdk:jar}` property, and the
+`exec-maven-plugin` passes it as `-javaagent:` when launching each test class.
+
+## Run Sample Test
+
+Runs the bstackdemo.com add-to-cart flow across the platforms in `browserstack.yml`:
+
+```bash
+mvn test
+```
+
+Or, equivalently, via the exec goal:
+
+```bash
+mvn compile exec:exec
+```
+
+## Run Local Test
+
+`browserstackLocal: true` in `browserstack.yml` tells the SDK to start a BrowserStack Local
+tunnel before the session, so the remote browser can reach `http://bs-local.com:45454`:
+
+```bash
+mvn compile exec:exec -P sample-local
+```
+
+To run both tests concurrently:
+
+```bash
+mvn compile exec:exec -P run-parallel
+```
+
+## Notes / Dashboard
+
+- View runs, video, logs, and network traffic at
+ [automate.browserstack.com](https://automate.browserstack.com/).
+- With `testObservability: true`, the same build also appears in
+ [Test Observability](https://observability.browserstack.com/).
+- Platforms, parallelism, Local, Observability, and debugging flags are all controlled from
+ `browserstack.yml` — no code changes needed to add browsers or devices.
+- Vanilla Java has no test runner, so `browserstack.yml` intentionally omits the `framework:`
+ field. The `-javaagent:` still instruments the Selenium `RemoteWebDriver` so sessions are
+ created and reported. Add `framework:` only when adopting a supported test framework
+ (TestNG / JUnit 4 / JUnit 5 / Cucumber).
diff --git a/browserstack.yml b/browserstack.yml
new file mode 100644
index 0000000..5cb43c9
--- /dev/null
+++ b/browserstack.yml
@@ -0,0 +1,59 @@
+# =============================
+# 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: browserstack build
+buildIdentifier: '#${BUILD_NUMBER}'
+# NOTE: no `framework:` field for vanilla Java — there is no test runner for the SDK to
+# instrument. The `-javaagent:` jar still instruments the Selenium RemoteWebDriver
+# constructor, so sessions are created and reported. Set `framework:` only when using a
+# supported test framework (testng / junit4 / junit5 / cucumber).
+
+# =======================================
+# Platforms (Browsers / Devices to test)
+# =======================================
+platforms:
+ - os: OS X
+ osVersion: Big Sur
+ browserName: Chrome
+ browserVersion: latest
+ - os: Windows
+ osVersion: 10
+ browserName: Edge
+ browserVersion: latest
+ - deviceName: Samsung Galaxy S22 Ultra
+ browserName: chrome
+ osVersion: 12.0
+
+# =======================
+# Parallels per Platform
+# =======================
+parallelsPerPlatform: 1
+
+# ==========================================
+# BrowserStack Automate + Local
+# ==========================================
+browserstackAutomation: true
+browserstackLocal: true
+
+source: java:sample-sdk:v1.0
+
+# ======================
+# Test Observability
+# ======================
+testObservability: true
+
+# ===================
+# Debugging features
+# ===================
+debug: false
+networkLogs: false
+consoleLogs: errors
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..5e254c0
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,150 @@
+
+ 4.0.0
+
+ com.browserstack
+ vanilla-java-browserstack
+ 1.0-SNAPSHOT
+ jar
+
+ vanilla-java-browserstack
+ https://github.com/browserstack/vanilla-java-browserstack
+
+
+ UTF-8
+ 11
+ 11
+ 4.23.0
+
+
+
+
+ org.seleniumhq.selenium
+ selenium-java
+ ${selenium.version}
+
+
+ com.browserstack
+ browserstack-java-sdk
+ LATEST
+ compile
+
+
+ io.github.bonigarcia
+ webdrivermanager
+ 5.9.2
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+ ${maven.compiler.source}
+ ${maven.compiler.target}
+
+
+
+
+
+ maven-dependency-plugin
+
+
+ getClasspathFilenames
+
+ properties
+
+
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.0.0
+
+
+ test
+
+ exec
+
+
+
+
+ java
+
+ -javaagent:${com.browserstack:browserstack-java-sdk:jar}
+ -cp
+
+ com.browserstack.tests.BStackDemoTest
+
+
+
+
+
+
+
+
+
+ sample-local
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.0.0
+
+
+
+ exec
+
+
+
+
+ java
+
+ -javaagent:${com.browserstack:browserstack-java-sdk:jar}
+ -cp
+
+ com.browserstack.tests.BStackLocalTest
+
+
+
+
+
+
+
+
+
+ run-parallel
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 3.0.0
+
+
+
+ exec
+
+
+
+
+ java
+
+ -javaagent:${com.browserstack:browserstack-java-sdk:jar}
+ -cp
+
+ com.browserstack.runner.ParallelTestRunner
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/browserstack/driver/DriverFactory.java b/src/main/java/com/browserstack/driver/DriverFactory.java
new file mode 100644
index 0000000..bdebaa3
--- /dev/null
+++ b/src/main/java/com/browserstack/driver/DriverFactory.java
@@ -0,0 +1,32 @@
+package com.browserstack.driver;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.remote.DesiredCapabilities;
+import org.openqa.selenium.remote.RemoteWebDriver;
+
+/**
+ * Creates a RemoteWebDriver pointed at the BrowserStack hub.
+ *
+ * No browser/OS capabilities are set in code: the BrowserStack Java SDK
+ * (attached via the {@code -javaagent:} jar) reads {@code browserstack.yml},
+ * injects the platform matrix, starts the session, and reports the result.
+ * Credentials are read from the BROWSERSTACK_USERNAME / BROWSERSTACK_ACCESS_KEY
+ * environment variables (or from browserstack.yml).
+ */
+public class DriverFactory {
+
+ private static final String HUB_URL = "https://hub.browserstack.com/wd/hub";
+
+ public static WebDriver createDriver() {
+ try {
+ // Empty capabilities — the SDK injects platform + credentials from browserstack.yml.
+ DesiredCapabilities capabilities = new DesiredCapabilities();
+ return new RemoteWebDriver(new URL(HUB_URL), capabilities);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Invalid BrowserStack hub URL: " + HUB_URL, e);
+ }
+ }
+}
diff --git a/src/main/java/com/browserstack/runner/ParallelTestRunner.java b/src/main/java/com/browserstack/runner/ParallelTestRunner.java
new file mode 100644
index 0000000..5aa5f88
--- /dev/null
+++ b/src/main/java/com/browserstack/runner/ParallelTestRunner.java
@@ -0,0 +1,30 @@
+package com.browserstack.runner;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import com.browserstack.tests.BStackDemoTest;
+import com.browserstack.tests.BStackLocalTest;
+
+/**
+ * Runs both sample tests concurrently. Each Runnable creates its own session
+ * against the BrowserStack hub; the SDK reports each one independently.
+ */
+public class ParallelTestRunner {
+
+ public static void main(String[] args) throws InterruptedException {
+ List tests = Arrays.asList(
+ new BStackDemoTest(),
+ new BStackLocalTest());
+
+ ExecutorService executor = Executors.newFixedThreadPool(tests.size());
+ for (Runnable test : tests) {
+ executor.submit(test);
+ }
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.MINUTES);
+ }
+}
diff --git a/src/main/java/com/browserstack/tests/BStackDemoTest.java b/src/main/java/com/browserstack/tests/BStackDemoTest.java
new file mode 100644
index 0000000..434f960
--- /dev/null
+++ b/src/main/java/com/browserstack/tests/BStackDemoTest.java
@@ -0,0 +1,64 @@
+package com.browserstack.tests;
+
+import org.openqa.selenium.By;
+import org.openqa.selenium.WebDriver;
+import org.openqa.selenium.WebElement;
+
+import com.browserstack.driver.DriverFactory;
+
+/**
+ * Sample test — e-commerce add-to-cart flow on https://bstackdemo.com/.
+ *
+ * Vanilla Java (no test framework): the flow runs inside {@code main()}.
+ * The BrowserStack Java SDK ({@code -javaagent:}) instruments the RemoteWebDriver
+ * constructor, starts the session, and marks it passed/failed automatically.
+ * It also implements {@link Runnable} so it can be driven by ParallelTestRunner.
+ */
+public class BStackDemoTest implements Runnable {
+
+ @Override
+ public void run() {
+ try {
+ main(null);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ WebDriver driver = DriverFactory.createDriver();
+ try {
+ driver.get("https://bstackdemo.com/");
+
+ // 1. Read the first product's name from the listing.
+ WebElement productElement = driver.findElement(By.xpath("//*[@id=\"1\"]/p"));
+ String productName = productElement.getText();
+ System.out.println("Product on screen: " + productName);
+
+ // 2. Click that product's "Add to cart".
+ driver.findElement(By.xpath("//*[@id=\"1\"]/div[4]")).click();
+
+ // 3. Wait for the cart pane to render.
+ Thread.sleep(3000);
+ WebElement cartContent = driver.findElement(By.cssSelector(".float-cart__content"));
+ if (!cartContent.isDisplayed()) {
+ throw new AssertionError("Cart pane (float-cart__content) is not displayed");
+ }
+
+ // 4. Read the product name shown in the cart and assert it matches.
+ WebElement productInCart = driver.findElement(By.xpath("//p[@class='title']"));
+ String cartProductName = productInCart.getText();
+ System.out.println("Product in cart: " + cartProductName);
+
+ if (!productName.equals(cartProductName)) {
+ throw new AssertionError(
+ "Product in cart '" + cartProductName + "' does not match product on screen '"
+ + productName + "'");
+ }
+
+ System.out.println("Sample test passed!");
+ } finally {
+ driver.quit();
+ }
+ }
+}
diff --git a/src/main/java/com/browserstack/tests/BStackLocalTest.java b/src/main/java/com/browserstack/tests/BStackLocalTest.java
new file mode 100644
index 0000000..156a329
--- /dev/null
+++ b/src/main/java/com/browserstack/tests/BStackLocalTest.java
@@ -0,0 +1,43 @@
+package com.browserstack.tests;
+
+import org.openqa.selenium.WebDriver;
+
+import com.browserstack.driver.DriverFactory;
+
+/**
+ * Local test — proves the BrowserStack Local tunnel is connected.
+ *
+ * With {@code browserstackLocal: true} in browserstack.yml, the SDK starts a
+ * Local tunnel, so the remote browser can reach http://bs-local.com:45454.
+ * The page served there has the title "BrowserStack Local".
+ */
+public class BStackLocalTest implements Runnable {
+
+ @Override
+ public void run() {
+ try {
+ main(null);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ WebDriver driver = DriverFactory.createDriver();
+ try {
+ driver.get("http://bs-local.com:45454/");
+
+ String title = driver.getTitle();
+ System.out.println("Local page title: " + title);
+
+ if (!"BrowserStack Local".equals(title)) {
+ throw new AssertionError(
+ "Title does not match 'BrowserStack Local' (got '" + title + "')");
+ }
+
+ System.out.println("Local test passed!");
+ } finally {
+ driver.quit();
+ }
+ }
+}