Official NodeJS client for the TestingBot REST API.
TestingBot provides a cloud-based test infrastructure for automated cross-browser testing. This client library allows you to interact with the TestingBot API to manage tests, retrieve browser information, handle test artifacts, and more.
- Features
- Requirements
- Installation
- Quick Start
- API Reference
- Responses & Pagination
- CLI Usage
- Complete Examples
- Testing
- Contributing
- License
- Support
- More Documentation
- Full support for all TestingBot REST API endpoints
- Both callback and async/await patterns supported
- Manage browser sessions and tests
- Upload and manage test artifacts
- Team management capabilities
- Screenshot generation across multiple browsers
- Tunnel management for local testing
- Codeless test management (create, schedule, trigger, alerts, reports)
- Built-in workflow helpers (auto-pagination, polling, batch operations, statistics)
- Typed errors (
AuthenticationError,RateLimitError,ValidationError, …)
- NodeJS >= 18.0.0
- A TestingBot account with API credentials
npm install testingbot-api
Sign up for a TestingBot account and obtain your API key and secret from the account settings.
You can authenticate in three ways:
export TESTINGBOT_KEY="your-api-key"
export TESTINGBOT_SECRET="your-api-secret"
const tb = new TestingBot({
api_key: 'your-api-key',
api_secret: 'your-api-secret'
});
Create a ~/.testingbot file with your credentials
const TestingBot = require('testingbot-api');
const tb = new TestingBot({
api_key: process.env.TESTINGBOT_KEY,
api_secret: process.env.TESTINGBOT_SECRET
});
All methods support both callback style and async/await patterns. When using async/await, simply omit the callback parameter.
List responses are paginated. Endpoints that return a list (
getTests,getBuilds,getStorageFiles,getScreenshotList,getCodelessTests,getUsersInTeam) resolve to a{ data: [...], meta: { offset, count, total } }object — read the array from.data. See Responses & Pagination. (getBrowsers,getDevicesandgetTunnelListreturn a plain array.)Errors are typed. A failed request rejects (or calls back) with a
TestingBotErrorsubclass carrying.statusCodeand.response. See Error Handling.
Gets a list of browsers you can test on. Returns a plain array.
// Callback style
tb.getBrowsers(type, function(error, browsers) {});
// Async/await style
const browsers = await tb.getBrowsers();
// With optional type parameter ('webdriver' (default) or 'rc' for legacy Selenium RC)
const rcBrowsers = await tb.getBrowsers('rc');
Gets a list of physical mobile devices you can test on. Returns a plain array.
// Callback style
tb.getDevices(function(error, devices) {});
// Async/await style
const devices = await tb.getDevices();
// Filter by OS family: Android, iOS, REAL_ANDROID or REAL_IOS
const androidDevices = await tb.getDevices({ platform: 'Android' });
Gets the list of TestingBot IP ranges, useful for firewall allow-lists. No authentication required.
// Callback style
tb.getIpRanges(function(error, ipRanges) {});
// Async/await style
const ipRanges = await tb.getIpRanges();
Gets a list of available physical mobile devices for your account
// Callback style
tb.getAvailableDevices(function(error, availableDevices) {});
// Async/await style
const availableDevices = await tb.getAvailableDevices();
Gets details for a specific physical device
// Callback style
tb.getDevice(deviceId, function(error, deviceDetails) {});
// Async/await style
const deviceDetails = await tb.getDevice(deviceId);
Creates a remote browser on TestingBot and returns its CDP URL, which can be used to interface with the remote browser.
const options = {
capabilities: {
browserName: 'chrome',
browserVersion: 'latest',
platform: 'WIN11'
}
};
// Callback style
tb.createSession(options, function(error, data) {});
// Async/await style
const session = await tb.createSession(options);
This will return a response with this structure:
{
"session_id": "cb8aaba38ddb-88fd98fca537-a0070d8f1815-175888519321-14646637",
"cdp_url": "wss://cloud.testingbot.com/session/cb8aaba38ddb-88fd98fca537-a0070d8f1815-175888519321-14646637"
}
You can now connect to the cdp_url with a CDP client to instruct the remote browser.
Gets your user information
// Callback style
tb.getUserInfo(function(error, userInfo) {});
// Async/await style
const userInfo = await tb.getUserInfo();
Updates your user information. Only first_name and last_name are mutable. Pass the fields
directly — they are wrapped in a user object for you.
// Callback style
tb.updateUserInfo({ first_name: 'Jane', last_name: 'Doe' }, function(error, userInfo) {});
// Async/await style
const userInfo = await tb.updateUserInfo({ first_name: 'Jane', last_name: 'Doe' });
Returns the account's API key and secret ({ key, secret }).
// Callback style
tb.getUserKeys(function(error, keys) {});
// Async/await style
const keys = await tb.getUserKeys();
Gets a list of your latest tests. Resolves to { data, meta } — the tests are in .data.
// Callback style
tb.getTests(offset, limit, function(error, result) {});
// Async/await style
const result = await tb.getTests();
console.log(result.data, result.meta);
// With pagination
const page = await tb.getTests(10, 20); // offset: 10, count: 20
// With filters (options form) — since (UNIX ts), group, build, browser_id, skip_fields
const filtered = await tb.getTests({ offset: 0, count: 50, build: 'ci-42', skip_fields: 'logs' });
Gets details for a single test, pass the WebDriver's SessionID
// Callback style
tb.getTestDetails(sessionId, function(error, testDetails) {});
// Async/await style
const testDetails = await tb.getTestDetails(sessionId);
// Omit heavy fields from the response
const lean = await tb.getTestDetails(sessionId, { skip_fields: 'logs,thumbs' });
Updates a single test. For example, update the passed state of a test (whether it was successful or not).
const testData = { "test[success]" : "1", "test[status_message]" : "test" };
// Callback style
tb.updateTest(testData, sessionId, function(error, testDetails) {});
// Async/await style
const testDetails = await tb.updateTest(testData, sessionId);
// Array values (e.g. multiple groups) are encoded correctly and all preserved
await tb.updateTest({ groups: ['regression', 'smoke'] }, sessionId);
Deletes a single test, pass the WebDriver's SessionID
// Callback style
tb.deleteTest(sessionId, function(error, success) {});
// Async/await style
const success = await tb.deleteTest(sessionId);
Stops a single test, pass the WebDriver's SessionID
// Callback style
tb.stopTest(sessionId, function(error, success) {});
// Async/await style
const success = await tb.stopTest(sessionId);
Gets the account's currently active tunnel (handy when you only run one).
// Callback style
tb.getTunnel(function(error, tunnel) {});
// Async/await style
const tunnel = await tb.getTunnel();
Gets list of active tunnels. Returns a plain array.
// Callback style
tb.getTunnelList(function(error, tunnelList) {});
// Async/await style
const tunnelList = await tb.getTunnelList();
Gets a single tunnel by its ID.
// Callback style
tb.getTunnelById(tunnelId, function(error, tunnel) {});
// Async/await style
const tunnel = await tb.getTunnelById(tunnelId);
Deletes a single Tunnel by ID
// Callback style
tb.deleteTunnel(tunnelId, function(error, success) {});
// Async/await style
const success = await tb.deleteTunnel(tunnelId);
Tears down whichever tunnel is currently active, without needing an ID.
// Callback style
tb.deleteActiveTunnel(function(error, success) {});
// Async/await style
const success = await tb.deleteActiveTunnel();
Retrieves the latest builds
// Callback style
tb.getBuilds(offset, limit, function(error, builds) {});
// Async/await style
const builds = await tb.getBuilds();
// With pagination
const builds = await tb.getBuilds(10, 20); // offset: 10, limit: 20
Retrieves the tests for a single build
// Callback style
tb.getTestsForBuild(buildId, function(error, tests) {});
// Async/await style
const tests = await tb.getTestsForBuild(buildId);
Deletes a single build
// Callback style
tb.deleteBuild(buildId, function(error, success) {});
// Async/await style
const success = await tb.deleteBuild(buildId);
Uploads a local file to TestingBot Storage
// Callback style
tb.uploadFile(localFilePath, function(error, appUrl) {});
// Async/await style
const appUrl = await tb.uploadFile(localFilePath);
Uploads a remote file to TestingBot Storage
// Callback style
tb.uploadRemoteFile(remoteFileUrl, function(error, appUrl) {});
// Async/await style
const appUrl = await tb.uploadRemoteFile(remoteFileUrl);
Retrieve data from a previously uploaded file
// Callback style
tb.getStorageFile(appUrl, function(error, fileDetails) {});
// Async/await style
const fileDetails = await tb.getStorageFile(appUrl);
Retrieve list of previously uploaded files
// Callback style
tb.getStorageFiles(offset, limit, function(error, fileDetails) {});
// Async/await style
const fileDetails = await tb.getStorageFiles();
// With pagination
const fileDetails = await tb.getStorageFiles(10, 20); // offset: 10, limit: 20
Delete a previously uploaded file
// Callback style
tb.deleteStorageFile(appUrl, function(error, success) {});
// Async/await style
const success = await tb.deleteStorageFile(appUrl);
Calculates the authentication hash for sharing, pass the WebDriver's SessionID. This is used to share a test's detail page on TestingBot
// This method is synchronous and returns the hash directly
const hash = tb.getAuthenticationHashForSharing(sessionId);
Takes screenshots for the specific browsers
// Callback style — note the parameter order: url, browsers, resolution, waitTime, fullPage, callbackURL
tb.takeScreenshot(url, browsers, resolution, waitTime, fullPage, callbackURL, function(error, screenshots) {});
// Async/await style
const screenshots = await tb.takeScreenshot(
'https://example.com', // url - required
[{ browserName: 'chrome', version: 'latest', os: 'WIN11' }], // browsers - required
'1920x1080', // resolution - required
5, // waitTime (seconds) - optional
true, // fullPage - optional
'https://your-callback.com' // callbackURL - optional
);
Once a screenshot job is running, you can use retrieveScreenshots to poll for the results.
Retrieves screenshots for a specific takeScreenshot call
// Callback style
tb.retrieveScreenshots(screenshotId, function(error, screenshots) {});
// Async/await style
const screenshots = await tb.retrieveScreenshots(screenshotId);
// Delta-fetch: exclude screenshot IDs you already have
const delta = await tb.retrieveScreenshots(screenshotId, { excludeIds: '101,102' });
Retrieves all screenshots previously generated with your account
// Callback style
tb.getScreenshotList(offset, limit, function(error, screenshots) {});
// Async/await style
const screenshots = await tb.getScreenshotList();
// With pagination
const screenshots = await tb.getScreenshotList(10, 20); // offset: 10, limit: 20
Retrieves team settings
// Callback style
tb.getTeam(function(error, data) {});
// Async/await style
const teamInfo = await tb.getTeam();
Get all users in your team
// Callback style
tb.getUsersInTeam(function(error, users) {});
// Async/await style
const users = await tb.getUsersInTeam();
Retrieves information about a specific user in your team
// Callback style
tb.getUserFromTeam(userId, function(error, user) {});
// Async/await style
const user = await tb.getUserFromTeam(userId);
Add a user to your team. You need ADMIN rights for this. Pass the fields at the top level;
email and password are required.
const userData = {
email: 'john@example.com',
password: 'a-strong-password',
first_name: 'John',
last_name: 'Doe'
};
// Callback style
tb.createUserInTeam(userData, function(error, result) {});
// Async/await style
const result = await tb.createUserInTeam(userData);
Update a user in your team. You need ADMIN rights for this. Pass the fields at the top level
(first_name, last_name, email, password, credits, device_credits, concurrency,
concurrencyPhysical).
const userData = {
first_name: 'Jane',
last_name: 'Smith'
};
// Callback style
tb.updateUserInTeam(userId, userData, function(error, result) {});
// Async/await style
const result = await tb.updateUserInTeam(userId, userData);
Gets a sub-account's client key ({ client_key }). You need ADMIN rights for this.
// Callback style
tb.getUserClientKey(userId, function(error, result) {});
// Async/await style
const result = await tb.getUserClientKey(userId);
Resets credentials for a specific user in your team. You need ADMIN rights for this.
// Callback style
tb.resetCredentials(userId, function(error, result) {});
// Async/await style
const result = await tb.resetCredentials(userId);
Codeless tests allow you to create automated tests without writing code. These tests can be configured to run on a schedule and include AI-powered test generation.
Retrieves a list of codeless tests
// Callback style
tb.getCodelessTests(offset, limit, function(error, tests) {});
// Async/await style
const tests = await tb.getCodelessTests(0, 10); // offset: 0, limit: 10
Creates a new codeless test
const testData = {
name: 'My Codeless Test', // Required: Test name
url: 'https://example.com', // Required: URL to test
cron: '0 0 * * *', // Optional: Cron schedule
screenshot: true, // Optional: Take screenshots
video: false, // Optional: Record video
idletimeout: 60, // Optional: Idle timeout in seconds
screenresolution: '1920x1080', // Optional: Screen resolution
ai_prompt: 'Test the login flow' // Optional: AI test agent prompt
};
// Callback style
tb.createCodelessTest(testData, function(error, result) {});
// Async/await style
const result = await tb.createCodelessTest(testData);
Updates an existing codeless test
const updateData = {
test: {
name: 'Updated Test Name',
cron: '0 12 * * *'
}
};
// Callback style
tb.updateCodelessTest(updateData, testId, function(error, result) {});
// Async/await style
const result = await tb.updateCodelessTest(updateData, testId);
Gets a single codeless test by ID
const test = await tb.getCodelessTest(testId);
Deletes a codeless test
// Callback style
tb.deleteCodelessTest(testId, function(error, result) {});
// Async/await style
const result = await tb.deleteCodelessTest(testId);
await tb.triggerCodelessTest(testId); // run now
await tb.triggerAllCodelessTests(); // run every codeless test
await tb.stopCodelessTest(testId); // stop a running test
// Schedule: once / daily / weekly preset, or a raw cron expression
await tb.scheduleCodelessTest(testId, { type: 'daily', hour: '09:00' });
await tb.scheduleCodelessTest(testId, { type: 'custom', cronFormat: '0 9 * * 1' });
const steps = await tb.getCodelessSteps(testId);
await tb.addCodelessStep(testId, { /* step definition */ });
const browsers = await tb.getCodelessBrowsers(testId);
await tb.setCodelessBrowsers(testId, { /* browser selection */ });
await tb.createCodelessAlert(testId, { /* alert config */ });
await tb.updateCodelessAlert(testId, { /* alert config */ });
await tb.createCodelessReport(testId, { /* report config */ });
await tb.updateCodelessReport(testId, { /* report config */ });
Promise-based conveniences built on the methods above. These always return promises.
// Fetch every test, paging automatically (returns a flat array)
const allTests = await tb.getAllTests();
// Poll a test until it reaches a terminal state (or the timeout elapses)
const finished = await tb.waitForTestCompletion(sessionId, 300000, 5000);
// Create a session and wait for it to finish
const result = await tb.runTestAndWait({ capabilities: { browserName: 'chrome' } });
// Fetch details for many tests at once
const { results, errors } = await tb.batchGetTestDetails([id1, id2, id3]);
// Aggregate pass/fail/state and browser/platform counts over the last N days
const stats = await tb.getTestStatistics(7);
// Bulk uploads / screenshots
const uploads = await tb.uploadFiles(['/path/a.apk', '/path/b.ipa']);
const shots = await tb.takeMultipleScreenshots(
['https://example.com', 'https://example.com/pricing'],
[{ browserName: 'chrome', version: 'latest', os: 'WIN11' }],
'1920x1080',
{ waitTime: 3, fullPage: true }
);
// Cleanup helpers (destructive)
await tb.cleanupOldTests(30); // delete tests older than 30 days
await tb.smartCleanup({ keepDays: 30, keepFailed: true, keepMax: 1000 });
Every failed request rejects (or calls back) with a typed error that extends TestingBotError
and carries .statusCode and .response (the raw response body). The classes are exported from
the package:
const TestingBot = require('testingbot-api');
const { TestingBotError, AuthenticationError, RateLimitError, ValidationError, NotFoundError, NetworkError } = TestingBot;
try {
await tb.getTestDetails('does-not-exist');
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Bad credentials');
} else if (error instanceof RateLimitError) {
console.error('Slow down, retry after', error.retryAfter);
} else {
console.error(error.name, error.statusCode, error.message);
}
}
| Status | Error class |
|---|---|
| 400 | ValidationError (has .fields) |
| 401 | AuthenticationError |
| 429 | RateLimitError (has .retryAfter) |
| other non-2xx | TestingBotError |
| no response (transport failure) | NetworkError |
List endpoints return a { data, meta } envelope rather than a bare array:
const result = await tb.getTests(0, 20);
// {
// data: [ /* tests */ ],
// meta: { offset: 0, count: 20, total: 1234 }
// }
result.data.forEach(test => console.log(test.id, test.state));
This applies to getTests, getBuilds, getStorageFiles, getScreenshotList, getCodelessTests
and getUsersInTeam. The getAllTests() workflow helper unwraps and concatenates these pages for
you. getBrowsers, getDevices and getTunnelList return a plain array.
The TestingBot API package includes a command-line interface for quick access to API functionality.
# Install globally for CLI access
npm install -g testingbot-api
# Or use npx with local installation
npx testingbot <command>
The CLI uses the same authentication methods as the API client:
- Environment variables:
TB_KEYandTB_SECRETorTESTINGBOT_KEYandTESTINGBOT_SECRET - Configuration file:
~/.testingbotwithapi_keyandapi_secret
# User management
testingbot user info # Get user information
testingbot user keys # Get your API key and secret
testingbot user update <json> # Update user information
# Test management
testingbot tests list [offset] [limit] # List tests
testingbot tests get <id> # Get test details
testingbot tests delete <id> # Delete a test
testingbot tests stop <id> # Stop a running test
# Codeless tests (Lab)
testingbot lab list [offset] [limit] # List codeless tests
testingbot lab get <id> # Get a codeless test
testingbot lab create <json> # Create a codeless test
testingbot lab update <id> <json> # Update a codeless test
testingbot lab delete <id> # Delete a codeless test
testingbot lab trigger <id> # Trigger a run
testingbot lab trigger-all # Trigger all codeless tests
testingbot lab stop <id> # Stop a running test
testingbot lab schedule <id> <json> # Set a schedule
testingbot lab steps <id> # List steps
testingbot lab add-step <id> <json> # Add a step
testingbot lab browsers <id> # List browsers
testingbot lab set-browsers <id> <json> # Set browsers
testingbot lab alert <id> <json> # Create an alert
testingbot lab update-alert <id> <json> # Update an alert
testingbot lab report <id> <json> # Create a report
testingbot lab update-report <id> <json> # Update a report
# Browsers and devices
testingbot browsers list [type] # List browsers (type: webdriver|rc)
testingbot devices list [platform] # List devices (platform: Android|iOS|REAL_ANDROID|REAL_IOS)
testingbot devices available # List available devices
testingbot devices get <id> # Get a device
testingbot config ip-ranges # List TestingBot IP ranges
# Tunnels
testingbot tunnel info # Get the active tunnel
testingbot tunnel list # List tunnels
testingbot tunnel get <id> # Get a tunnel by ID
testingbot tunnel delete <id> # Delete a tunnel
testingbot tunnel delete-active # Delete the active tunnel
# Team
testingbot team info # Get team info
testingbot team users # List team users
testingbot team get-user <id> # Get a team user
testingbot team client-key <id> # Get a user's client key
testingbot team create-user <json> # Create a team user
# Storage
testingbot storage upload <file> # Upload a file
testingbot storage list [offset] [limit] # List stored files
testingbot storage get <id> # Get a stored file
testingbot storage delete <id> # Delete a stored file
# Help
testingbot --help # Show help
testingbot --version # Show version
# Create a codeless test
testingbot lab create '{
"name": "Homepage Test",
"url": "https://example.com",
"cron": "0 0 * * *",
"screenshot": true,
"ai_prompt": "Test the homepage loads correctly"
}'
# List recent tests
testingbot tests list 0 20
# Get browser list
testingbot browsers list webdriver
# Upload a file to storage
testingbot storage upload ./test-app.zip
const TestingBot = require('testingbot-api');
const tb = new TestingBot({
api_key: "your-tb-key",
api_secret: "your-tb-secret"
});
async function runTests() {
try {
// Get user information
const userInfo = await tb.getUserInfo();
console.log('User:', userInfo);
// Get available browsers (plain array)
const browsers = await tb.getBrowsers();
console.log('Available browsers:', browsers.length);
// Get recent tests (paginated: { data, meta })
const tests = await tb.getTests(0, 10);
console.log('Recent tests:', tests.data.length, 'of', tests.meta.total);
// Get test details for the first test
if (tests.data.length > 0) {
const testDetails = await tb.getTestDetails(tests.data[0].id);
console.log('Test details:', testDetails);
}
// Upload a file
const appUrl = await tb.uploadFile('/path/to/your/app.apk');
console.log('Uploaded file:', appUrl);
// Take screenshots
const screenshots = await tb.takeScreenshot(
'https://testingbot.com',
[{ browserName: 'chrome', version: 'latest', os: 'WIN11' }],
'1280x1024'
);
console.log('Screenshots:', screenshots);
} catch (error) {
console.error('Error:', error);
}
}
runTests();
Run lint + the full test suite:
npm test
Most suites are integration tests that hit the live API and require valid TestingBot credentials set
as environment variables (TB_KEY and TB_SECRET). A subset are pure unit tests that need no
credentials (typed-error mapping, parameter validation, request shaping, and workflow helpers):
npx mocha --grep "Typed errors|Parameter validation|Request shaping|Workflow helpers"
Contributions are welcome! Please feel free to submit a Pull Request.
See the LICENSE file for details.
- Documentation: TestingBot REST API
- Issues: GitHub Issues
Check out the TestingBot REST API for more information.