GitHub - testingbot/testingbot-php: PHP API Client to interact with TestingBot.com
The official PHP client for the TestingBot API — cross-browser & device testing, screenshots, tunnels, app storage and codeless tests.
Requirements
- PHP 8.1 or newer
- The
curl,jsonandfileinfoextensions (all standard)
Installation
composer require testingbot/testingbot-php
Getting started
Grab your key and secret from your TestingBot account and create a client:
$client = new TestingBot\Client($key, $secret); $browsers = $client->browsers()->list(); $test = $client->tests()->get($webdriverSessionId); $client->tests()->update($webdriverSessionId, ['name' => 'login test', 'success' => true]);
The API is grouped into resources, accessed via methods on the client. Every call returns the decoded JSON response as an array, and throws a typed exception on failure (see Error handling).
Configuration options
$client = new TestingBot\Client($key, $secret, [ 'timeout' => 90, // request timeout in seconds 'connect_timeout' => 30, 'base_url' => 'https://api.testingbot.com/v1/', 'ssl_verify' => true, 'user_agent' => 'my-app/1.0', ]);
Resources
Tests
$client->tests()->list(0, 10, ['build' => 'ci-42']); // paginated, filterable $client->tests()->get($sessionId); $client->tests()->update($sessionId, ['name' => 'checkout', 'success' => false]); $client->tests()->create(['name' => 'external result', 'success' => true]); $client->tests()->stop($sessionId); $client->tests()->delete($sessionId);
Builds
$client->builds()->list(0, 10); $client->builds()->getTests($buildId); $client->builds()->delete($buildId);
Storage
$upload = $client->storage()->uploadFile('/path/to/app.apk'); // => ['app_url' => 'tb://...'] $client->storage()->uploadRemoteUrl('https://example.com/app.apk'); $client->storage()->replaceFile($upload['app_url'], '/path/to/new.apk'); $client->storage()->list(0, 10); $client->storage()->get($upload['app_url']); $client->storage()->delete($upload['app_url']);
Tunnels
$client->tunnels()->list(); $client->tunnels()->getActive(); $client->tunnels()->get($tunnelId); $client->tunnels()->create(); $client->tunnels()->delete($tunnelId);
User & team
$client->user()->get(); $client->user()->keys(); $client->user()->update(['first_name' => 'Jane']); $client->teamManagement()->get(); // concurrency snapshot $client->teamManagement()->listUsers(0, 25); $client->teamManagement()->createUser(['email' => 'dev@acme.com', 'password' => '…']); $client->teamManagement()->updateUser($userId, ['credits' => 100]); $client->teamManagement()->resetUserKeys($userId);
Browsers, devices & configuration
use TestingBot\Enum\BrowserType; $client->browsers()->list(BrowserType::Webdriver); $client->devices()->list('android'); $client->devices()->available(); $client->devices()->get($deviceId); $client->configuration()->ipRanges(); // no authentication required
Screenshots
$batch = $client->screenshots()->create('https://example.com', [1, 2, 3], [ 'resolution' => '1920x1080', 'fullpage' => true, ]); $client->screenshots()->get($batch['id']); $client->screenshots()->list(0, 10);
Codeless (Lab) tests & suites
$test = $client->lab()->createTest(['name' => 'smoke', 'url' => 'https://example.com']); $client->lab()->setSteps($test['lab_test_id'], [ ['order' => 0, 'cmd' => 'open', 'locator' => '/', 'value' => ''], ['order' => 1, 'cmd' => 'click', 'locator' => '#go', 'value' => ''], ]); $client->lab()->setBrowsers($test['lab_test_id'], [12, 34]); $job = $client->lab()->trigger($test['lab_test_id']); $client->labSuites()->create(['name' => 'Regression']); $client->labSuites()->addTests($suiteId, [1, 2, 3]); $client->labSuites()->trigger($suiteId);
Jobs (polling)
Trigger endpoints return a job_id you can poll:
$job = $client->lab()->trigger($labTestId); $final = $client->jobs()->waitForCompletion($job['job_id'], timeout: 300, interval: 5);
Sharing
$hash = $client->getAuthenticationHash($sessionId); // for building share links
Error handling
Any non-2xx response throws a typed exception; transport failures throw
NetworkException. All of them implement TestingBot\Exception\TestingBotExceptionInterface.
use TestingBot\Exception\AuthenticationException; use TestingBot\Exception\NotFoundException; use TestingBot\Exception\RateLimitException; use TestingBot\Exception\ApiException; use TestingBot\Exception\NetworkException; try { $test = $client->tests()->get($sessionId); } catch (NotFoundException $e) { // 404 — no such test } catch (AuthenticationException $e) { // 401 / 403 } catch (RateLimitException $e) { sleep($e->getRetryAfter() ?? 60); } catch (ApiException $e) { error_log($e->getStatusCode() . ': ' . $e->getResponseBody()); } catch (NetworkException $e) { // DNS / connection / timeout / TLS }
Migrating from 1.x
The 1.x class is still here and every method keeps its name and signature:
$api = new TestingBot\TestingBotAPI($key, $secret); $api->getJob($sessionId); $api->getTunnels();
The one behavioural change in 2.0: failures now throw instead of returning
an array with an error key. Wrap calls in try/catch as shown above. From the
facade you can reach any new resource via $api->client():
$api->client()->screenshots()->create('https://example.com', [1], ['resolution' => '1280x1024']);
See CHANGELOG.md for the full list of changes.
Development
composer install composer test # unit tests (no credentials needed) composer phpstan # static analysis (level 8) composer cs-fix # apply coding standards (PSR-12)
Integration tests hit the live API and are skipped unless TB_KEY and
TB_SECRET are set:
TB_KEY=… TB_SECRET=… composer test:integration
Releasing
Releases are tag-driven and require no tokens or secrets. Packagist syncs
new versions from the repository through its GitHub integration, and the
release workflow uses the built-in GITHUB_TOKEN.
-
Bump
Client::VERSIONand add a## [x.y.z]section toCHANGELOG.md. -
Tag and push:
git tag 2.0.0 git push origin 2.0.0
The Release workflow then verifies the tag
matches Client::VERSION, runs the checks, and publishes a GitHub Release with
notes taken from CHANGELOG.md. Packagist updates on its own once the tag exists.
License
Apache 2.0 — see LICENSE.APACHE2.