@bugbug-io/sdk
    Preparing search index...

    @bugbug-io/sdk

    BugBug logo

    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/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);
    • 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.

    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 treats apiUrl as the full API base URL and does not auto-append /api/v2 or rewrite the value in any way. Pass a complete URL like https://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 as tests.startRun or tests.create. Set it to false in 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.