Running Appium as a separate process that you start manually before tests is fine for local development. For CI and for clean test isolation, starting the Appium server programmatically inside the test suite is better — no pre-running process to forget, no port conflicts, automatic cleanup on suite exit.
AppiumServiceBuilder basics
AppiumServiceBuilder is part of io.appium.java_client.service.local. It constructs an AppiumDriverLocalService that wraps the Appium Node process.
import io.appium.java_client.service.local.AppiumDriverLocalService;
import io.appium.java_client.service.local.AppiumServiceBuilder;
import io.appium.java_client.service.local.flags.GeneralServerFlag;
import java.io.File;
public class AppiumServer {
private static AppiumDriverLocalService service;
public static void start() {
service = new AppiumServiceBuilder()
.withIPAddress("127.0.0.1")
.usingPort(4723)
.withArgument(GeneralServerFlag.BASEPATH, "/")
.withLogFile(new File("target/appium.log"))
.build();
service.start();
}
public static void stop() {
if (service != null && service.isRunning()) {
service.stop();
}
}
public static String getServiceUrl() {
return service.getUrl().toString();
}
}The withLogFile call redirects Appium's verbose output to target/appium.log — grep this file first when a session fails to start.
Hooking into TestNG lifecycle
Start the server in @BeforeSuite and stop it in @AfterSuite. These run once per suite, not once per test:
package com.example.base;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
public abstract class BaseTest {
@BeforeSuite(alwaysRun = true)
public void startServer() {
AppiumServer.start();
}
@AfterSuite(alwaysRun = true)
public void stopServer() {
AppiumServer.stop();
}
@BeforeTest
@Parameters("platform")
public void setUp(String platform) {
DriverManager.initDriver(platform, AppiumServer.getServiceUrl());
}
@AfterTest
public void tearDown() {
DriverManager.quitDriver();
}
}alwaysRun = true ensures server cleanup runs even when tests fail.
Pointing DriverManager at the service URL
Pass the service URL into DriverManager.initDriver so it uses the programmatic server rather than a hardcoded localhost URL:
public static void initDriver(String platform, String serverUrl) {
try {
URL url = new URL(serverUrl);
if ("Android".equalsIgnoreCase(platform)) {
UiAutomator2Options options = buildAndroidOptions();
driverThreadLocal.set(new AndroidDriver(url, options));
} else {
XCUITestOptions options = buildIosOptions();
driverThreadLocal.set(new IOSDriver(url, options));
}
} catch (MalformedURLException e) {
throw new RuntimeException("Bad server URL: " + serverUrl, e);
}
}Handling port conflicts
If port 4723 is already in use (another Appium process, another test run), service.start() throws. Two approaches:
Use a random free port:
service = new AppiumServiceBuilder()
.usingAnyFreePort()
.build();
service.start();
String url = service.getUrl().toString(); // e.g., http://127.0.0.1:49215usingAnyFreePort() asks the OS for an available port. This is the right default for CI where multiple builds may run in parallel.
Explicitly check before starting:
public static void start() {
if (service != null && service.isRunning()) {
return; // already up
}
// ...build and start
}Environment requirements
AppiumServiceBuilder needs Node.js and the Appium binary on the system PATH. In CI, install them in the setup step:
- run: npm install -g appium
- run: appium driver install uiautomator2
- run: appium driver install xcuitestOn macOS, verify with which appium and appium --version. If AppiumServiceBuilder can't find Appium, it throws AppiumServerHasNotBeenStartedLocallyException — the message includes the resolved path it tried, which tells you exactly what's missing from PATH.
Reading the Appium log
target/appium.log is your first debug resource. Look for:
Appium REST http interface listener started— server is upCould not find a connected Android device— capability or ADB issueWebDriverAgent installation failed— iOS provisioning issue- Session ID appearing then disappearing — driver quit before test finished