Connect Java Selenium Tests to BrowserStack Grid with Local Tunnel
Run Java Selenium tests on BrowserStack's cloud grid with BrowserStackLocal for private URLs: store credentials in environment variables, start the tunnel, set capabilities, and verify the session in the dashboard.
16 Mar 2026, 08:34 UTC

Desired outcome
You have a Java Selenium suite that passes locally, and you want it to run against real browsers on BrowserStack while the application under test is reachable only from your machine or VPN. The takeaway: point a Selenium RemoteWebDriver at BrowserStack's hub, and when the app is private, open a BrowserStackLocal tunnel before the first session is created. Everything else — build grouping, video, logs — is metadata you attach to capabilities.
Two failure modes dominate this setup: a tunnel that is not running when the session starts, and capabilities that do not match a browser/OS combination BrowserStack currently offers. Both are diagnosable from the test log and the dashboard.
Prerequisites
- A BrowserStack account with a username and access key. The pair authenticates to
hub-cloud.browserstack.com. - A Java project using Selenium 4.x, which provides the
RemoteWebDriverandDesiredCapabilitiesAPI used below. Selenium 3 can talk to the same hub URL, but some capability names differ. - The
BrowserStackLocalbinary for your OS and CPU architecture, if you need private URLs. - A place to store secrets: CI secret store, environment variables, or a secrets manager. Not the repository.
Step 1 — Keep credentials out of the code
Export the pair in the shell or CI job that launches the tests. The values below are placeholders; substitute your own.
export BROWSERSTACK_USERNAME=<your-username>
export BROWSERSTACK_ACCESS_KEY=<your-access-key>
Check: printenv BROWSERSTACK_USERNAME prints the value in the same shell that will run Maven or Gradle. If it prints nothing, the test JVM will not see it either.
Step 2 — Start BrowserStackLocal only when the app is private
The tunnel forwards traffic from the BrowserStack browser back to a host reachable from your machine. It is unnecessary for public staging URLs and adds latency, so gate it on whether the test actually needs a private address.
# run from the directory containing the binary; the user needs execute permission
./BrowserStackLocal --key "$BROWSERSTACK_ACCESS_KEY"
Expected check: the process stays in the foreground and reports that the tunnel is up. If it exits immediately, the key is wrong, the binary lacks execute permission, or outbound connections are blocked.
Stop it when the run finishes, so the socket and the tunnel slot are released:
./BrowserStackLocal --key "$BROWSERSTACK_ACCESS_KEY" --stop
In CI, start it as a background process and capture its output to a file. A tunnel that dies mid-run usually surfaces as a test timeout, not as a tunnel error, so the captured output is the only place the real cause appears.
Step 3 — Capabilities and the remote session
Capabilities are the key/value pairs that tell the hub which browser to allocate and how to label the session. The ones worth setting deliberately:
| Capability | Purpose | Note |
|---|---|---|
browserName, browserVersion | Browser and version to allocate | latest is accepted but pins nothing; record the resolved version from the dashboard if you need reproducibility |
os, osVersion | Operating system of the remote machine | Must be a combination BrowserStack currently offers |
project, build, name | Grouping and labelling in the dashboard | Use a build id from CI so runs stay distinguishable |
browserstack.local | Routes the session through the tunnel | Set true only when the tunnel is running |
import java.net.URL;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("browserName", "chrome");
caps.setCapability("browserVersion", "latest");
caps.setCapability("os", "Windows");
caps.setCapability("osVersion", "11");
caps.setCapability("project", "Java Selenium");
caps.setCapability("build", "build-<build-id>");
caps.setCapability("name", "Login flow");
caps.setCapability("browserstack.local", true); // only with a running tunnel
String user = System.getenv("BROWSERSTACK_USERNAME");
String key = System.getenv("BROWSERSTACK_ACCESS_KEY");
URL hub = new URL("https://" + user + ":" + key + "@hub-cloud.browserstack.com/wd/hub");
RemoteWebDriver driver = new RemoteWebDriver(hub, caps);
Check: driver.getSessionId() returns a non-null id right after construction. That id is the handle you use to find the session in the dashboard. If construction throws, the hub rejected the request and the message usually names the offending capability.
Note that browserstack.local must be true at session creation. Setting it after the driver exists has no effect, because the routing decision is made when the session is allocated.
Step 4 — Run and verify
- Start the tunnel if the app is private, and confirm it is up.
- Run the suite locally or from CI (Jenkins, GitHub Actions, or any runner that can export the two environment variables).
- In the test log, capture the session id and the build name.
- Open the Automate dashboard, filter by the build name, and open the session. Confirm the browser and OS actually allocated, and that video and logs are attached.
A session with no video or logs usually means the session ended abnormally or the plan does not include them. Check the session status rather than assuming the test passed.
Recovery options
- Authentication failure at driver construction. Re-check that both environment variables are visible to the test JVM, and that the access key has not been rotated in the account.
- Private URL times out while a public URL works. The tunnel is not running, or
browserstack.localis missing or false. Verify the tunnel process is still alive at the moment of failure. - Session creation rejected. A capability names an unsupported browser, OS, or version combination. Reduce to a known-good pair first, then add values back one at a time.
- Sessions queue instead of starting. Concurrency limit reached. Lower parallel threads rather than retrying.
- Tunnel will not start. Check execute permission, architecture match, and outbound firewall rules; run the binary with verbose output and read the first error line.
Limitations and what to re-check
The tunnel adds a network hop, so timing-sensitive assertions may need looser waits than local runs, and tunnel concurrency is tied to your plan. Environment variables are safer than hard-coded keys but still depend on the CI secret store being access-controlled. Capability names and the set of available browser and OS versions change over time, so treat the values above as a shape rather than a fixed list, and confirm them against BrowserStack's current capability documentation before wiring this into a release gate. The code samples here are illustrative and were not executed as part of writing; run them against a throwaway build first.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.