DevOps 4 min read

Selenium Framework + Azure DevOps Integration

How to integrate a Selenium test automation framework with Azure DevOps. Covers Maven/Gradle builds, TestNG/JUnit XML output, ChromeDriver configuration.

I
InnovateBits
InnovateBits

A Selenium framework integrated with Azure DevOps runs automatically on every commit, publishes results to Azure Test Plans, and flags regressions before they reach production. This guide covers a complete, production-ready integration.


Framework structure

selenium-framework/
├── pom.xml                          # Maven build file
├── azure-pipelines.yml              # Pipeline definition
├── testng.xml                       # TestNG suite configuration
├── src/
│   ├── main/java/
│   │   └── framework/
│   │       ├── BaseTest.java        # WebDriver lifecycle
│   │       ├── DriverFactory.java   # Driver initialisation
│   │       └── pages/              # Page Object Model
│   │           ├── LoginPage.java
│   │           └── DashboardPage.java
│   └── test/java/
│       └── tests/
│           ├── AuthTests.java
│           └── CheckoutTests.java
└── src/test/resources/
    ├── config.properties            # Environment config
    └── test-data.json               # Test data

Maven configuration (pom.xml)

XML
1<project> 2 <properties> 3 <selenium.version>4.21.0</selenium.version> 4 <testng.version>7.10.2</testng.version> 5 <wdm.version>5.8.0</wdm.version> 6 <maven.surefire.version>3.2.5</maven.surefire.version> 7 </properties> 8 9 <dependencies> 10 <dependency> 11 <groupId>org.seleniumhq.selenium</groupId> 12 <artifactId>selenium-java</artifactId> 13 <version>${selenium.version}</version> 14 </dependency> 15 <dependency> 16 <groupId>io.github.bonigarcia</groupId> 17 <artifactId>webdrivermanager</artifactId> 18 <version>${wdm.version}</version> 19 <scope>test</scope> 20 </dependency> 21 <dependency> 22 <groupId>org.testng</groupId> 23 <artifactId>testng</artifactId> 24 <version>${testng.version}</version> 25 <scope>test</scope> 26 </dependency> 27 </dependencies> 28 29 <build> 30 <plugins> 31 <plugin> 32 <groupId>org.apache.maven.plugins</groupId> 33 <artifactId>maven-surefire-plugin</artifactId> 34 <version>${maven.surefire.version}</version> 35 <configuration> 36 <suiteXmlFiles> 37 <suiteXmlFile>testng.xml</suiteXmlFile> 38 </suiteXmlFiles> 39 <rerunFailingTestsCount>1</rerunFailingTestsCount> 40 </configuration> 41 </plugin> 42 </plugins> 43 </build> 44</project>

BaseTest with CI-aware configuration

JAVA
1public class BaseTest { 2 protected WebDriver driver; 3 protected String baseUrl; 4 5 @BeforeMethod 6 @Parameters({"browser"}) 7 public void setUp(@Optional("chrome") String browser) { 8 baseUrl = System.getenv("BASE_URL") != null 9 ? System.getenv("BASE_URL") 10 : "http://localhost:3000"; 11 12 boolean isCI = System.getenv("TF_BUILD") != null; // Azure Pipelines sets TF_BUILD 13 14 if (browser.equals("chrome")) { 15 WebDriverManager.chromedriver().setup(); 16 ChromeOptions options = new ChromeOptions(); 17 if (isCI) { 18 options.addArguments("--headless=new", "--no-sandbox", 19 "--disable-dev-shm-usage", "--window-size=1920,1080"); 20 } 21 driver = new ChromeDriver(options); 22 } else if (browser.equals("firefox")) { 23 WebDriverManager.firefoxdriver().setup(); 24 FirefoxOptions ffOptions = new FirefoxOptions(); 25 if (isCI) ffOptions.addArguments("--headless"); 26 driver = new FirefoxDriver(ffOptions); 27 } 28 29 driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10)); 30 } 31 32 @AfterMethod(alwaysRun = true) 33 public void tearDown(ITestResult result) { 34 if (result.getStatus() == ITestResult.FAILURE && driver != null) { 35 captureScreenshot(result.getName()); 36 } 37 if (driver != null) driver.quit(); 38 } 39 40 private void captureScreenshot(String testName) { 41 TakesScreenshot ts = (TakesScreenshot) driver; 42 File src = ts.getScreenshotAs(OutputType.FILE); 43 try { 44 FileUtils.copyFile(src, new File("test-screenshots/" + testName + ".png")); 45 } catch (IOException e) { 46 System.err.println("Screenshot failed: " + e.getMessage()); 47 } 48 } 49}

TestNG suite configuration

XML
1<!-- testng.xml --> 2<!DOCTYPE suite SYSTEM "https://testng.org/testng-1.0.dtd"> 3<suite name="Regression Suite" parallel="classes" thread-count="3"> 4 <listeners> 5 <listener class-name="org.testng.reporters.JUnitXMLReporter"/> 6 </listeners> 7 <test name="Auth Tests"> 8 <parameter name="browser" value="chrome"/> 9 <classes> 10 <class name="tests.AuthTests"/> 11 </classes> 12 </test> 13 <test name="Checkout Tests"> 14 <parameter name="browser" value="chrome"/> 15 <classes> 16 <class name="tests.CheckoutTests"/> 17 </classes> 18 </test> 19</suite>

Azure Pipelines YAML

YAML
1trigger: 2 branches: 3 include: [main, release/*] 4 5pool: 6 vmImage: ubuntu-latest 7 8variables: 9 - group: selenium-env-vars 10 - name: MAVEN_CACHE_FOLDER 11 value: $(Pipeline.Workspace)/.m2/repository 12 - name: MAVEN_OPTS 13 value: -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER) 14 15stages: 16 - stage: SeleniumRegression 17 displayName: Selenium Regression Suite 18 jobs: 19 - job: TestNG 20 displayName: TestNG Selenium Tests 21 timeoutInMinutes: 45 22 steps: 23 - task: Cache@2 24 displayName: Cache Maven packages 25 inputs: 26 key: 'maven | "$(Agent.OS)" | **/pom.xml' 27 restoreKeys: 'maven | "$(Agent.OS)"' 28 path: $(MAVEN_CACHE_FOLDER) 29 30 - task: JavaToolInstaller@0 31 displayName: Set up Java 17 32 inputs: 33 versionSpec: '17' 34 jdkArchitectureOption: x64 35 jdkSourceOption: PreInstalled 36 37 - script: mkdir -p test-screenshots test-results 38 displayName: Create output directories 39 40 - script: | 41 mvn test \ 42 -Dsurefire.rerunFailingTestsCount=1 \ 43 -Dmaven.test.failure.ignore=true \ 44 $(MAVEN_OPTS) 45 displayName: Run Selenium tests 46 env: 47 BASE_URL: $(STAGING_URL) 48 TF_BUILD: $(TF_BUILD) 49 50 - task: PublishTestResults@2 51 displayName: Publish TestNG results 52 inputs: 53 testResultsFormat: JUnit 54 testResultsFiles: '**/surefire-reports/TEST-*.xml' 55 testRunTitle: Selenium TestNG — $(Build.BuildNumber) 56 mergeTestResults: true 57 condition: always() 58 59 - task: PublishPipelineArtifact@1 60 displayName: Upload screenshots 61 inputs: 62 targetPath: test-screenshots 63 artifact: selenium-screenshots 64 condition: always()

Common errors and fixes

Error: java.lang.UnsatisfiedLinkError for ChromeDriver on Linux Fix: Add --no-sandbox and --disable-dev-shm-usage Chrome options. On Azure Linux agents, the sandbox requires privileges that CI environments don't have.

Error: Maven downloads all dependencies on every pipeline run (slow) Fix: Add the Cache@2 task to cache the .m2 repository. The cache key based on pom.xml means it only invalidates when dependencies change.

Error: TestNG reports show 0 tests even though tests ran Fix: The JUnit XML reporter in TestNG generates to surefire-reports by default. Verify the path with find . -name "TEST-*.xml" in a script step.

Error: WebDriverManager failed to download ChromeDriver Fix: Azure agents have Chrome pre-installed. Use WebDriverManager.chromedriver().browserVersion("installed").setup() to use the agent's installed Chrome version without downloading.

Tags
#selenium#azure-devops#test-automation-framework#maven#testng#java#webdrivermanager

Share this article

Follow for more

Follow me on social media for more developer tips, tricks, and tutorials. Let's connect and build something great together!