AWS CodePipeline Automated Testing
AWS CodePipeline is a continuous delivery service that models your release process as a set of stages: a source stage (GitHub, Bitbucket, CodeCommit or Amazon S3), a build stage and one or more deploy stages.
Your tests run in the build stage, which is usually handled by
AWS CodeBuild. CodeBuild reads a
buildspec.yml file from the root of your repository: this is where you install dependencies,
start a TestingBot Tunnel if needed, and run your Selenium or Appium tests on the
TestingBot browser and device cloud.
If you already have tests running locally, only two things are needed: make your TestingBot key and secret available
to the build, and point your tests at https://hub.testingbot.com/wd/hub.
Prerequisites
- An AWS account with permission to create CodePipeline and CodeBuild projects.
- A repository with your tests, connected to CodePipeline as a source.
- A
buildspec.ymlfile in the root of that repository. - A TestingBot key and secret, available in the TestingBot member area.
Credentials
Your tests read the TestingBot key and secret from the TESTINGBOT_KEY and
TESTINGBOT_SECRET environment variables.
There are three ways to provide them to CodeBuild, in order of preference:
1. AWS Secrets Manager (recommended)
Store your credentials as a secret, for example a secret named testingbot with the keys
key and secret, then reference it from your
buildspec.yml:
env:
secrets-manager:
TESTINGBOT_KEY: testingbot:key
TESTINGBOT_SECRET: testingbot:secret
The CodeBuild service role needs secretsmanager:GetSecretValue permission on that secret.
The values never appear in your repository and CodeBuild masks them in the build log.
2. AWS Systems Manager Parameter Store
env:
parameter-store:
TESTINGBOT_KEY: /testingbot/key
TESTINGBOT_SECRET: /testingbot/secret
Store both as SecureString parameters and grant the build role
ssm:GetParameters.
3. Plain environment variables
You can also set the variables directly on the CodeBuild project, under Environment → Additional configuration → Environment variables, or in the build stage of the Create Pipeline wizard.
env:
variables:
TESTINGBOT_KEY: "YOUR_KEY"
TESTINGBOT_SECRET: "YOUR_SECRET"
Do not commit your TestingBot secret to your repository. Plain env.variables entries in
buildspec.yml are visible to everyone with access to the repository and to the build logs.
Use Secrets Manager or Parameter Store instead.
Create a Pipeline
In the AWS console, go to CodePipeline and choose Create pipeline:
- Pipeline settings: give the pipeline a name and let AWS create a new service role.
- Source stage: pick your source provider (GitHub, Bitbucket, CodeCommit or S3), the repository and the branch that should trigger the pipeline.
- Build stage: choose AWS CodeBuild as the build provider and select your build project. It helps to create the CodeBuild project beforehand, because its name is what you will recognize in your build logs. Add the TestingBot environment variables here if you are not using Secrets Manager.
- Deploy stage: optional, select a deploy provider if your pipeline also deploys.
- Review the configuration and choose Create pipeline.
From now on, every commit to the configured branch triggers the pipeline and runs your tests on TestingBot.
Buildspec Example
Below is a buildspec.yml that installs the project dependencies and runs a test suite
against the TestingBot cloud:
version: 0.2
env:
secrets-manager:
TESTINGBOT_KEY: testingbot:key
TESTINGBOT_SECRET: testingbot:secret
phases:
install:
runtime-versions:
nodejs: 20
commands:
- npm ci
build:
commands:
- npm run test:e2e
reports:
e2e:
files:
- "junit/*.xml"
The optional reports section makes CodeBuild display your JUnit results in the AWS console,
next to the full test reports, videos and logs on TestingBot.
Locally Hosted Websites
If the website you want to test is not publicly reachable (a staging environment inside a VPC, or a server started by
the build itself), start a TestingBot Tunnel in the
pre_build phase. The tunnel is a Java application, so add a Java runtime to the build image.
version: 0.2
env:
secrets-manager:
TESTINGBOT_KEY: testingbot:key
TESTINGBOT_SECRET: testingbot:secret
phases:
install:
runtime-versions:
nodejs: 20
java: corretto17
commands:
- npm ci
pre_build:
commands:
- curl -sS -O https://testingbot.com/downloads/testingbot-tunnel.zip
- unzip -o -q testingbot-tunnel.zip
- |
java -jar testingbot-tunnel-*.jar $TESTINGBOT_KEY $TESTINGBOT_SECRET \
--tunnel-identifier codebuild-$CODEBUILD_BUILD_NUMBER \
--readyfile /tmp/tunnel.ready \
--logfile /tmp/tunnel.log &
- timeout 180 bash -c 'until [ -f /tmp/tunnel.ready ]; do sleep 1; done'
build:
commands:
- npm run start:staging &
- npm run test:e2e
post_build:
commands:
- pkill -f testingbot-tunnel || true
artifacts:
files:
- /tmp/tunnel.log
A few things worth pointing out:
-
The
--readyfileflag touches a file as soon as the tunnel is ready. Waiting for that file is more reliable than a fixedsleep, because your tests never start before the tunnel is up. -
The
--tunnel-identifierflag tags the tunnel, so parallel builds each get their own tunnel. Pass the same value as thetunnel-identifiercapability in your tests.CODEBUILD_BUILD_NUMBERis set by CodeBuild for every build. -
The
post_buildphase always runs, also when your tests fail, which makes it the right place to stop the tunnel. -
Uploading
/tmp/tunnel.logas an artifact makes it easy to debug connection problems afterwards.
If your staging environment lives in a private VPC, configure the CodeBuild project to run inside that VPC. The tunnel then reaches your internal hosts the same way the build does.
Instead of the tunnel identifier, you can also point your tests at the local relay the tunnel exposes on
http://localhost:4445/wd/hub. See the
tunnel command line options for all available flags.
Example Test
Your test reads the credentials from the environment and sends the TestingBot capabilities in the
tb:options object:
#!/usr/bin/env ruby
require 'rubygems'
require 'selenium-webdriver'
options = Selenium::WebDriver::Chrome::Options.new
options.add_option('platformName', 'WIN11')
options.add_option('browserVersion', 'latest')
options.add_option('tb:options', {
'key' => ENV['TESTINGBOT_KEY'],
'secret' => ENV['TESTINGBOT_SECRET'],
'name' => 'CodePipeline Test',
'build' => "codebuild-#{ENV['CODEBUILD_BUILD_NUMBER']}",
'tunnel-identifier' => "codebuild-#{ENV['CODEBUILD_BUILD_NUMBER']}"
})
driver = Selenium::WebDriver.for(
:remote,
url: "https://hub.testingbot.com/wd/hub",
options: options)
driver.navigate.to "http://localhost:8080"
puts driver.title
driver.quit
import os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
build = "codebuild-%s" % os.environ.get('CODEBUILD_BUILD_NUMBER')
options = Options()
options.set_capability('platformName', 'WIN11')
options.set_capability('browserVersion', 'latest')
options.set_capability('tb:options', {
'key': os.environ['TESTINGBOT_KEY'],
'secret': os.environ['TESTINGBOT_SECRET'],
'name': 'CodePipeline Test',
'build': build,
'tunnel-identifier': build
})
driver = webdriver.Remote(
command_executor='https://hub.testingbot.com/wd/hub',
options=options
)
driver.get("http://localhost:8080")
print(driver.title)
driver.quit()
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class CodePipelineTest {
public static final String KEY = System.getenv("TESTINGBOT_KEY");
public static final String SECRET = System.getenv("TESTINGBOT_SECRET");
public static final String URL = "https://hub.testingbot.com/wd/hub";
public static void main(String[] args) throws Exception {
String build = "codebuild-" + System.getenv("CODEBUILD_BUILD_NUMBER");
ChromeOptions options = new ChromeOptions();
options.setCapability("browserVersion", "latest");
options.setCapability("platformName", "WIN11");
Map<String, Object> tbOptions = new HashMap<>();
tbOptions.put("key", KEY);
tbOptions.put("secret", SECRET);
tbOptions.put("name", "CodePipeline Test");
tbOptions.put("build", build);
tbOptions.put("tunnel-identifier", build);
options.setCapability("tb:options", tbOptions);
WebDriver driver = new RemoteWebDriver(new URL(URL), options);
driver.get("http://localhost:8080");
System.out.println(driver.getTitle());
driver.quit();
}
}
const webdriver = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const build = `codebuild-${process.env.CODEBUILD_BUILD_NUMBER}`;
async function runCodePipelineTest () {
let options = new chrome.Options();
options.set('platformName', 'WIN11');
options.set('browserVersion', 'latest');
options.set('tb:options', {
'key': process.env.TESTINGBOT_KEY,
'secret': process.env.TESTINGBOT_SECRET,
'name': 'CodePipeline Test',
'build': build,
'tunnel-identifier': build
});
let driver = new webdriver.Builder()
.usingServer('https://hub.testingbot.com/wd/hub')
.withCapabilities(options)
.build();
await driver.get("http://localhost:8080");
const title = await driver.getTitle();
console.log(title);
await driver.quit();
}
runCodePipelineTest();
Leave out the tunnel-identifier capability when you are testing a public website without a tunnel.
Grouping Builds
By passing the build capability, all sessions started by one CodeBuild run are grouped into a
single TestingBot build, which gives you one combined pass or fail
result for the pipeline execution.
CodeBuild exposes several variables you can use for this:
| Variable | Description |
|---|---|
CODEBUILD_BUILD_NUMBER |
Incrementing build number of the CodeBuild project. Short and readable, ideal as a build name. |
CODEBUILD_BUILD_ID |
Unique id of the build, in the form project-name:uuid. |
CODEBUILD_RESOLVED_SOURCE_VERSION |
The commit that triggered the pipeline. Useful as part of the test name. |
CODEBUILD_WEBHOOK_HEAD_REF |
The branch of the commit, when the build was started by a webhook. |
You can report the outcome of your build back to TestingBot with the TestingBot REST API, or fetch the status of a build to gate a deploy stage on the test results.
Verify the Results
In the AWS console, open CodePipeline, select your pipeline and choose Details on the build stage to follow the CodeBuild log.
At the same time, your sessions appear in the TestingBot member area, with video, screenshots, WebDriver logs and network traffic for every test.
Troubleshooting
- Authentication failed: the build could not read your credentials. Check that the CodeBuild service role is allowed to read the secret or parameter, and that the key and secret are not swapped.
-
The tunnel never becomes ready: make sure a Java runtime is present in the
installphase (java: corretto17) and inspect/tmp/tunnel.log. If your VPC blocks outbound traffic, allow port 443 tohub.testingbot.comand*.testingbot.com. -
Tests time out on a local URL: verify that the
tunnel-identifiercapability matches the--tunnel-identifiervalue passed to the tunnel, and that your local server is already listening when the tests start. -
The build hangs after the tests: the tunnel is still running in the background. Stop it in the
post_buildphase as shown above.