npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bugbug-io/sdk

v14.6.0

Published

TypeScript SDK for building integrations with the BugBug API

Readme

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

Quick 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 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.

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=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',
});

License

This project is licensed under the MIT License - see the LICENSE file for details.