@bugbug-io/sdk
v14.6.0
Published
TypeScript SDK for building integrations with the BugBug API
Readme
![]()
BugBug SDK
@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/sdkQuick start
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);What it supports
- Tests, suites, runs, profiles, project settings, and reusable components.
- Typed API responses and Zod-backed validation.
- Rate-limit retries, request cancellation, timeout handling, and run-progress watching.
Configuration
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',
});Advanced configuration
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.
API Reference
Tests
// 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');Suites
// 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');Profiles
// 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();Configuration & System Info
// 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 (Components)
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
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
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');Step Runs
// Get details of a single step run (selectors, errors, screenshots)
const stepRun = await bugbug.stepRuns.get('step-run-id');Project
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);Projects
// List the projects available to the current credentials
const projects = await bugbug.projects.list();Visual Regression
// 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');Tests - Extended CRUD Operations
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');High-Level Methods
Running Tests with Progress Watching
// 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}`);
},
});Running Suites with Progress Watching
// 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)`);
},
});Error Handling
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);
}
}Request Cancellation
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');
}
}Rate Limiting
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.
Client Methods
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();TypeScript Support
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 });Environment Variables
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=trueimport { 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',
});License
This project is licensed under the MIT License - see the LICENSE file for details.
