![]()
@bugbug-io/sdk is the TypeScript client for building integrations with the
BugBug API. SDK access requires a BugBug Business plan.
npm install @bugbug-io/sdk
import { createBugBug } from '@bugbug-io/sdk';
const bugbug = createBugBug({ apiToken: process.env.BUGBUG_API_TOKEN! });
const run = await bugbug.tests.startRun('test-id', {
watchProgress: true,
onProgress: (state) => console.log(state.status),
});
console.log(run.status);
Pass an API token when creating the client. You can also set BUGBUG_API_TOKEN
and optionally BUGBUG_API_URL; the production API base URL is
https://app.bugbug.io/api/v2.
const bugbug = createBugBug({
apiToken: process.env.BUGBUG_API_TOKEN!,
apiUrl: 'https://app.bugbug.io/api/v2',
});
const bugbug = createBugBug({
apiToken: 'your-api-token',
apiUrl: 'https://app.bugbug.io/api/v2', // Full API base URL. Default: https://app.bugbug.io/api/v2
projectId: 'project-id', // Active project for organization/user tokens
verbose: false, // Enable detailed logging
logLevel: 'info', // 'debug' | 'info' | 'warn' | 'error'
timeout: 30000, // Request timeout in milliseconds
// Rate limiting configuration
rateLimit: {
maxRequests: 100, // Max requests per window
windowMs: 60000, // Time window in milliseconds
retryServer429: true, // Retry server-side 429s. Default: true
exponentialBackoff: {
baseDelay: 1000, // Base delay for retries (ms)
maxDelay: 30000, // Maximum delay (ms)
maxRetries: 3, // Maximum retry attempts on rate-limit
},
},
});
Note on
apiUrl: The SDK treatsapiUrlas the full API base URL and does not auto-append/api/v2or rewrite the value in any way. Pass a complete URL likehttps://app.bugbug.io/api/v2. Host-only values will route to the wrong endpoint.
Note on
retryServer429: When enabled, a 429 returned mid-POST is retried transparently, which can create duplicate resources for non-idempotent calls such astests.startRunortests.create. Set it tofalsein integrations that mutate data and retry idempotently yourself.
Use bugbug.tests, bugbug.suites, bugbug.profiles, bugbug.groups, and
bugbug.config for the respective API areas.
// List all tests
const tests = await bugbug.tests.list({
page: 1,
pageSize: 50,
query: 'search-term',
ordering: 'name', // 'name' | '-name' | 'created' | '-created'
});
// Get a specific test
const test = await bugbug.tests.get('test-id');
// Run a test by name or UUID (fire-and-forget; returns the run state)
const runState = await bugbug.tests.startRun('test-id-or-name', {
profileName: 'Production',
variables: [
{ key: 'username', value: 'testuser' },
{ key: 'password', value: 'testpass' },
],
});
// Run a test and watch progress until completion (returns full TestRun)
const completedRun = await bugbug.tests.startRun('test-id-or-name', {
watchProgress: true,
pollInterval: 2000, // Check every 2 seconds
timeout: 300000, // 5 minute timeout
onProgress: (state) => console.log(`Status: ${state.status}`),
});
// Poll an existing run until it finishes
const run = await bugbug.tests.watchRunProgress(
runState.id,
(state) => console.log(`Status: ${state.status}`),
{ pollInterval: 2000, timeout: 300000 },
);
// Get run details / lightweight progress
const runDetails = await bugbug.tests.getRun('run-id');
const status = await bugbug.tests.getRunProgress('run-id');
// Recent runs, logs, screenshots, JUnit report
const recentTests = await bugbug.tests.getRecentRuns({ hours: 24 });
const logs = await bugbug.tests.getRunLogs('run-id');
const screenshots = await bugbug.tests.getRunScreenshots('run-id');
const junitXml = await bugbug.tests.downloadRunJunitReport('run-id');
// Stop a running test
await bugbug.tests.stopRun('run-id');
// List all suites
const suites = await bugbug.suites.list();
// Get a specific suite
const suite = await bugbug.suites.get('suite-id');
// Run a suite (fire-and-forget; returns the run state)
const runState = await bugbug.suites.startRun('suite-id', {
profileName: 'Production',
});
// Run a suite and watch progress until completion (returns full SuiteRun)
const completedRun = await bugbug.suites.startRun('suite-id', {
watchProgress: true,
onProgress: (state) => console.log(`Status: ${state.status}`),
});
// Poll an existing suite run until it finishes
const run = await bugbug.suites.watchRunProgress(runState.id, (state) =>
console.log(`Status: ${state.status}`),
);
// Run details, recent runs, and JUnit report
const runDetails = await bugbug.suites.getRun('run-id');
const recentSuites = await bugbug.suites.getRecentRuns({ hours: 24 });
const junitXml = await bugbug.suites.downloadRunJunitReport('run-id');
// Stop a running suite
await bugbug.suites.stopRun('run-id');
// List all profiles
const profiles = await bugbug.profiles.list();
// Get a specific profile
const profile = await bugbug.profiles.get('profile-id');
// Find profile by name
const profile = await bugbug.profiles.findByName('Production');
// Get all profiles (handles pagination)
const allProfiles = await bugbug.profiles.getAll();
// Get default profile
const defaultProfile = await bugbug.profiles.getDefault();
// Get IP addresses for whitelisting
const ips = await bugbug.config.getIpAddresses();
// Test connectivity
const isConnected = await bugbug.testConnection();
// Get system information (IP addresses + connectivity probe)
const systemInfo = await bugbug.config.getSystemInfo();
Groups represent reusable test components or test building blocks.
// List all groups
const groups = await bugbug.groups.list({
query: 'login',
page: 1,
pageSize: 50,
});
// Get a specific group
const group = await bugbug.groups.get('group-id');
// Create a new group
const newGroup = await bugbug.groups.create({
name: 'Login Component',
});
// Update a group
const updatedGroup = await bugbug.groups.update('group-id', {
name: 'Updated Login Component',
});
// Partially update a group
const patchedGroup = await bugbug.groups.partialUpdate('group-id', {
name: 'Patched Name',
});
// Delete a group
await bugbug.groups.delete('group-id');
Components are reusable test building blocks that can be shared across multiple tests.
// List all components
const components = await bugbug.components.list({
query: 'login',
page: 1,
pageSize: 50,
});
// The response has a nested structure
const { results } = components;
const componentsList = results.results; // Array of components
// See which tests use a component
const usage = await bugbug.components.getUsage('component-id');
Steps represent individual actions within tests or groups.
// ✅ NEW: Get steps through their parent group
const group = await bugbug.groups.get('group-id');
const steps = group.steps; // Array of steps in this group
// ✅ Get a specific step by ID
const step = await bugbug.steps.get('step-id');
// ✅ List groups to find steps
const groups = await bugbug.groups.list({ query: 'login' });
groups.results.forEach((group) => {
console.log(`Group: ${group.name}, Steps: ${group.steps?.length || 0}`);
});
// Create a new step
const newStep = await bugbug.steps.create({
type: 'click',
name: 'Click Login Button',
groupId: 'group-id',
isActive: true,
runTimeout: 30,
interactionPosition: 'center',
selectorsPresets: [],
});
// Update a step
const updatedStep = await bugbug.steps.update('step-id', {
type: 'click',
name: 'Updated Step',
groupId: 'group-id',
interactionPosition: 'center',
selectorsPresets: [],
});
// Partially update a step
const patchedStep = await bugbug.steps.partialUpdate('step-id', {
type: 'click',
name: 'Patched Step Name',
runTimeout: 60,
});
// Delete a step
await bugbug.steps.delete('step-id');
// Get details of a single step run (selectors, errors, screenshots)
const stepRun = await bugbug.stepRuns.get('step-run-id');
The project module operates on the single project the API token is scoped to.
// Get settings for the authenticated project
const settings = await bugbug.project.getSettings();
// Export the project as a ZIP archive (Uint8Array)
const zipBytes = await bugbug.project.export();
// Import a project from ZIP bytes
await bugbug.project.import(zipBytes);
// List the projects available to the current credentials
const projects = await bugbug.projects.list();
// List reference screenshots for a step
const refs = await bugbug.visualRegression.listReferenceScreenshots({
stepId: 'step-id',
page: 1,
pageSize: 50,
});
// Get a single reference screenshot
const ref = await bugbug.visualRegression.getReferenceScreenshot('ref-id');
// Create, update, and delete reference screenshots
const created = await bugbug.visualRegression.createReferenceScreenshot({
stepId: 'step-id',
screenshot: 'https://example.com/reference.png',
});
await bugbug.visualRegression.updateReferenceScreenshot('ref-id', { isActive: false });
await bugbug.visualRegression.deleteReferenceScreenshot('ref-id');
In addition to running tests, you can now create, update, and delete tests programmatically.
// Create a new test
const newTest = await bugbug.tests.create({
name: 'My New Test',
screenSizeType: 'desktop',
});
// Update a test
const updatedTest = await bugbug.tests.update('test-id', {
name: 'Updated Test Name',
isActive: true,
});
// Partially update a test
const patchedTest = await bugbug.tests.partialUpdate('test-id', {
name: 'Patched Test Name',
});
// Delete a test
await bugbug.tests.delete('test-id');
// Link a component (group) to a test
await bugbug.tests.linkComponent('test-id', {
groupId: 'group-id',
atIndex: 0, // Optional: insert at specific position
});
// Unlink a component from a test
await bugbug.tests.unlinkComponent('test-id', 'group-id');
// Get test run logs
const logs = await bugbug.tests.getRunLogs('run-id');
// Download JUnit report
const junitXml = await bugbug.tests.downloadRunJunitReport('run-id');
// Run test and watch progress until completion
const result = await bugbug.tests.startRun('test-name-or-id', {
watchProgress: true,
profileName: 'Production',
timeout: 600000, // 10 minutes
onProgress: (run) => {
console.log(`Test ${run.id}: ${run.status}`);
},
});
// Run suite and watch progress until completion
const result = await bugbug.suites.startRun('suite-id', {
watchProgress: true,
profileName: 'Production',
onProgress: (run) => {
console.log(`Suite ${run.id}: ${run.status} (${run.test_runs?.length || 0} tests)`);
},
});
The SDK provides comprehensive error types for different scenarios:
import {
BugBugError,
AuthenticationError,
AuthorizationError,
SubscriptionError,
ValidationError,
NotFoundError,
RateLimitError,
NetworkError,
CancellationError,
TimeoutError,
} from '@bugbug-io/sdk';
try {
const result = await bugbug.tests.startRun('test-id');
} catch (error) {
if (error instanceof AuthenticationError) {
console.error('Invalid API token');
} else if (error instanceof SubscriptionError) {
console.error(error.upgradeUrl ? `Upgrade at ${error.upgradeUrl}` : error.message);
} else if (error instanceof RateLimitError) {
console.error(`Rate limited. Retry after ${error.retryAfter}ms`);
} else if (error instanceof TimeoutError) {
console.error(`Request timed out after ${error.timeout}ms`);
} else if (error instanceof NetworkError) {
console.error('Network error occurred');
} else {
console.error('Unknown error:', error.message);
}
}
All requests support cancellation using AbortController:
const controller = new AbortController();
// Cancel the request after 5 seconds
setTimeout(() => controller.abort(), 5000);
try {
const result = await bugbug.tests.list({
signal: controller.signal,
});
} catch (error) {
if (error instanceof CancellationError) {
console.log('Request was cancelled');
}
}
The SDK applies rate limiting with exponential backoff automatically, based on the
rateLimit configuration (see Advanced Configuration).
On HTTP 429 responses it backs off and retries up to maxRetries, throwing a
RateLimitError once retries are exhausted.
The top-level client exposes a few helpers alongside the resource modules:
// Read the resolved config (apiToken, apiUrl, etc.)
const config = bugbug.getConfig();
// Update config at runtime
bugbug.updateConfig({ apiUrl: 'https://app.bugbug.io/api/v2' });
// Switch the active project (shorthand for updateConfig({ projectId }))
bugbug.setProject('project-id');
// Probe connectivity to the API
const connectivity = await bugbug.testConnection();
The SDK is built with TypeScript and provides comprehensive type definitions:
import type { Test, TestRun, Suite, SuiteRun } from '@bugbug-io/sdk';
// All API responses are properly typed
const test: Test = await bugbug.tests.get('test-id');
const run: TestRun = await bugbug.tests.startRun(test.id, { watchProgress: true });
You can also configure the SDK using environment variables:
BUGBUG_API_TOKEN=your-api-token
BUGBUG_API_URL=https://app.bugbug.io/api/v2
BUGBUG_VERBOSE=true
import { createBugBug } from '@bugbug-io/sdk';
const bugbug = createBugBug({
apiToken: process.env.BUGBUG_API_TOKEN!,
apiUrl: process.env.BUGBUG_API_URL,
verbose: process.env.BUGBUG_VERBOSE === 'true',
});
This project is licensed under the MIT License - see the LICENSE file for details.