Skip to main content

Overview

BrowserTest SDK provides comprehensive TypeScript support with full type definitions, IntelliSense support, and type safety for all operations.

Installation

npm install browsertest-sdk
# TypeScript definitions are included automatically

Basic TypeScript Usage

Initialization with Types

import { BrowserTest } from 'browsertest-sdk';

const bt = new BrowserTest({
  apiKey: process.env.BROWSERTEST_API_KEY!
});

Typed Operations

// Screenshots with full type safety
const screenshot = await bt.screenshot.take({
  url: 'https://example.com',
  fullPage: true,
  format: 'png'
});
// Type: ScreenshotResult

// Agentic testing with structured output
const testResult = await bt.testing.execute({
  instructions: 'Test login functionality',
  url: 'https://example.com/login',
  outputSchema: {
    type: 'object',
    properties: {
      loginSuccessful: { type: 'boolean' },
      userName: { type: 'string' }
    }
  }
});
// Type: TestResult

Type Definitions

Core Types

// Configuration
interface BrowserTestConfig {
  apiKey: string;
  baseUrl?: string;
  timeout?: number;
  retries?: number;
}

// Usage information
interface QuotaInfo {
  plan: string;
  usage: {
    screenshot: {
      used: number;
      limit: number;
      remaining: number;
    };
    agentic: {
      used: number;
      limit: number;
      remaining: number;
    };
    total: {
      screenshot: number;
      agentic: number;
    };
  };
}

Screenshot Types

interface ScreenshotOptions {
  url: string;
  fullPage?: boolean;
  width?: number;
  height?: number;
  format?: 'png' | 'jpeg';
  quality?: number;
  waitFor?: number;
  selector?: string | null;
  timeout?: number;
  blockAds?: boolean;
}

interface ScreenshotResult {
  success: boolean;
  data: {
    screenshot: string;
    url: string;
    title: string;
    width: number;
    height: number;
    format: string;
    fullPage: boolean;
  };
  meta: {
    timeMs: number;
    size: number;
  };
}

Testing Types

interface TestOptions {
  instructions: string;
  url: string;
  outputSchema?: object;
  config?: TestConfig;
}

interface TestResult {
  success: boolean;
  results: {
    actions: Array<{
      type: string;
      description: string;
      timestamp: string;
    }>;
    structuredData?: any;
    output: string;
  };
  meta: {
    timeMs: number;
    url: string;
  };
}

Advanced TypeScript Patterns

Generic Types for Structured Output

// Define your expected output types
interface LoginTestOutput {
  loginSuccessful: boolean;
  userName?: string;
  errorMessage?: string;
  redirectUrl?: string;
}

interface EcommerceTestOutput {
  checkoutSuccessful: boolean;
  orderNumber?: string;
  totalAmount?: string;
  errors: string[];
}

// Typed test functions
async function testLogin(url: string): Promise<LoginTestOutput> {
  const result = await bt.testing.execute({
    instructions: 'Test login with valid credentials',
    url,
    outputSchema: {
      type: 'object',
      properties: {
        loginSuccessful: { type: 'boolean' },
        userName: { type: 'string' },
        errorMessage: { type: 'string' },
        redirectUrl: { type: 'string' }
      }
    }
  });

  return result.results.structuredData as LoginTestOutput;
}

async function testCheckout(url: string): Promise<EcommerceTestOutput> {
  const result = await bt.testing.execute({
    instructions: 'Complete checkout process',
    url,
    outputSchema: {
      type: 'object',
      properties: {
        checkoutSuccessful: { type: 'boolean' },
        orderNumber: { type: 'string' },
        totalAmount: { type: 'string' },
        errors: { type: 'array', items: { type: 'string' } }
      }
    }
  });

  return result.results.structuredData as EcommerceTestOutput;
}

// Usage with type safety
const loginResult = await testLogin('https://example.com/login');
if (loginResult.loginSuccessful) {
  console.log('Login successful for user:', loginResult.userName);
}

const checkoutResult = await testCheckout('https://shop.com/checkout');
if (checkoutResult.checkoutSuccessful) {
  console.log('Order placed:', checkoutResult.orderNumber);
}

Utility Types

// Extract successful results only
type SuccessfulScreenshot = ScreenshotResult & { success: true };

// Create a type guard
function isSuccessfulScreenshot(result: ScreenshotResult): result is SuccessfulScreenshot {
  return result.success;
}

// Usage
const results = await bt.screenshot.takeBatch({ urls: ['https://site1.com', 'https://site2.com'] });

const successfulResults = results.results.filter(isSuccessfulScreenshot);
// Type: SuccessfulScreenshot[]

successfulResults.forEach(result => {
  // TypeScript knows result.success is true and data exists
  console.log('Success:', result.data.url, result.data.width);
});

Template Types

interface TemplateConfig<TOutput = any> {
  name: string;
  description: string;
  instructions: string;
  outputSchema: object;
  tags?: string[];
}

interface TemplateInstance<TOutput = any> {
  id: string;
  config: TemplateConfig<TOutput>;
}

// Typed template creation
function createTypedTemplate<TOutput>(
  config: TemplateConfig<TOutput>
): Promise<TemplateInstance<TOutput>> {
  return bt.template.create(config);
}

// Typed template invocation
async function invokeTypedTemplate<TOutput>(
  templateId: string,
  params: { url: string; [key: string]: any }
): Promise<TOutput> {
  const result = await bt.template.invoke(templateId, params);
  return result.results.structuredData as TOutput;
}

// Usage
interface UserRegistrationOutput {
  registrationSuccessful: boolean;
  userId: string;
  emailVerified: boolean;
}

const registrationTemplate = await createTypedTemplate<UserRegistrationOutput>({
  name: 'User Registration Test',
  instructions: 'Test user registration flow',
  outputSchema: {
    type: 'object',
    properties: {
      registrationSuccessful: { type: 'boolean' },
      userId: { type: 'string' },
      emailVerified: { type: 'boolean' }
    }
  }
});

const registrationResult = await invokeTypedTemplate<UserRegistrationOutput>(
  registrationTemplate.id,
  { url: 'https://app.com/register' }
);
// Type: UserRegistrationOutput

Error Handling with Types

Typed Error Classes

// Custom error types
class BrowserTestError extends Error {
  constructor(message: string, public status?: number) {
    super(message);
    this.name = 'BrowserTestError';
  }
}

class BrowserTestAuthError extends BrowserTestError {
  constructor(message = 'Authentication failed') {
    super(message);
    this.name = 'BrowserTestAuthError';
  }
}

class BrowserTestQuotaError extends BrowserTestError {
  constructor(message = 'Quota exceeded', public quotaInfo?: QuotaInfo) {
    super(message);
    this.name = 'BrowserTestQuotaError';
  }
}

// Type guard functions
function isBrowserTestAuthError(error: unknown): error is BrowserTestAuthError {
  return error instanceof Error && error.name === 'BrowserTestAuthError';
}

function isBrowserTestQuotaError(error: unknown): error is BrowserTestQuotaError {
  return error instanceof Error && error.name === 'BrowserTestQuotaError';
}

// Typed error handling
async function safeScreenshot(url: string): Promise<ScreenshotResult | null> {
  try {
    return await bt.screenshot.take({ url });
  } catch (error) {
    if (isBrowserTestAuthError(error)) {
      console.error('Auth error:', error.message);
      // Handle auth error
      return null;
    }

    if (isBrowserTestQuotaError(error)) {
      console.error('Quota exceeded:', error.quotaInfo);
      // Handle quota error
      return null;
    }

    throw error; // Re-throw unknown errors
  }
}

Result Types with Discriminated Unions

// Result types
type OperationResult<TData> =
  | { success: true; data: TData }
  | { success: false; error: string; code?: string };

// Generic operation wrapper
async function executeOperation<TData>(
  operation: () => Promise<TData>
): Promise<OperationResult<TData>> {
  try {
    const data = await operation();
    return { success: true, data };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error',
      code: error instanceof Error ? error.name : undefined
    };
  }
}

// Usage with type safety
const screenshotResult = await executeOperation(() =>
  bt.screenshot.take({ url: 'https://example.com' })
);

if (screenshotResult.success) {
  // TypeScript knows screenshotResult.data is ScreenshotResult
  console.log('Screenshot size:', screenshotResult.data.meta.size);
} else {
  // TypeScript knows screenshotResult.error exists
  console.error('Screenshot failed:', screenshotResult.error);
}

Class-Based SDK Wrapper

class TypedBrowserTest {
  constructor(private bt: BrowserTest) {}

  async screenshot(options: ScreenshotOptions): Promise<ScreenshotResult> {
    return this.bt.screenshot.take(options);
  }

  async test<TOutput = any>(
    instructions: string,
    url: string,
    schema?: object
  ): Promise<TestResult & { structuredData: TOutput }> {
    const result = await this.bt.testing.execute({
      instructions,
      url,
      outputSchema: schema
    });

    return {
      ...result,
      results: {
        ...result.results,
        structuredData: result.results.structuredData as TOutput
      }
    };
  }

  async batchScreenshots(urls: string[]): Promise<ScreenshotResult[]> {
    const batch = await this.bt.screenshot.takeBatch({ urls });
    return batch.results.filter(r => r.success).map(r => r.data!);
  }

  async getUsage(): Promise<QuotaInfo> {
    return this.bt.getUsage();
  }
}

// Usage
const typedBt = new TypedBrowserTest(bt);

// Type-safe operations
const screenshot = await typedBt.screenshot({
  url: 'https://example.com',
  fullPage: true
});

interface LoginResult {
  success: boolean;
  username?: string;
}

const loginTest = await typedBt.test<LoginResult>(
  'Test login functionality',
  'https://example.com/login',
  {
    type: 'object',
    properties: {
      success: { type: 'boolean' },
      username: { type: 'string' }
    }
  }
);

// TypeScript knows loginTest.results.structuredData is LoginResult
if (loginTest.results.structuredData.success) {
  console.log('Logged in as:', loginTest.results.structuredData.username);
}

Configuration Types

Environment-Based Configuration

type Environment = 'development' | 'staging' | 'production';

interface EnvironmentConfig {
  apiKey: string;
  baseUrl: string;
  timeout: number;
  retries: number;
}

const environmentConfigs: Record<Environment, EnvironmentConfig> = {
  development: {
    apiKey: process.env.BROWSERTEST_DEV_KEY!,
    baseUrl: 'https://api.browsertest.in',
    timeout: 30000,
    retries: 1
  },
  staging: {
    apiKey: process.env.BROWSERTEST_STAGING_KEY!,
    baseUrl: 'https://staging-api.browsertest.in',
    timeout: 45000,
    retries: 3
  },
  production: {
    apiKey: process.env.BROWSERTEST_PROD_KEY!,
    baseUrl: 'https://api.browsertest.in',
    timeout: 60000,
    retries: 5
  }
};

function createBrowserTestForEnvironment(env: Environment): BrowserTest {
  const config = environmentConfigs[env];

  // Validate required environment variables
  if (!config.apiKey) {
    throw new Error(`Missing API key for environment: ${env}`);
  }

  return new BrowserTest(config);
}

// Usage
const env = (process.env.NODE_ENV as Environment) || 'development';
const bt = createBrowserTestForEnvironment(env);

Configuration Validation

import { z } from 'zod'; // Using zod for runtime validation

const BrowserTestConfigSchema = z.object({
  apiKey: z.string().min(10, 'API key too short'),
  baseUrl: z.string().url().optional(),
  timeout: z.number().min(1000).max(300000).optional(),
  retries: z.number().min(0).max(10).optional()
});

type ValidatedBrowserTestConfig = z.infer<typeof BrowserTestConfigSchema>;

function createValidatedBrowserTest(config: unknown): BrowserTest {
  const validatedConfig = BrowserTestConfigSchema.parse(config);
  return new BrowserTest(validatedConfig);
}

// Usage with runtime validation
try {
  const config = {
    apiKey: process.env.BROWSERTEST_API_KEY!,
    timeout: 30000,
    retries: 3
  };

  const bt = createValidatedBrowserTest(config);
  // Config is validated at runtime
} catch (error) {
  console.error('Invalid configuration:', error);
}

Utility Types

Extract and Transform Types

// Extract successful results
type SuccessfulResults<T> = T extends { success: true; data: infer D } ? D : never;

// Extract error types
type ErrorResults<T> = T extends { success: false; error: infer E } ? E : never;

// Transform batch results
type BatchResults<T> = Array<
  | { success: true; data: T }
  | { success: false; error: string }
>;

type SuccessfulBatchResults<T> = Extract<BatchResults<T>, { success: true }>['data'];

// Usage
type ScreenshotBatch = BatchResults<ScreenshotResult['data']>;
type SuccessfulScreenshots = SuccessfulBatchResults<ScreenshotResult['data']>;

API Response Types

// Generic API response wrapper
interface ApiResponse<TData = unknown, TError = string> {
  success: boolean;
  data?: TData;
  error?: TError;
  meta?: {
    timeMs: number;
    requestId?: string;
  };
}

// Specific response types
type ScreenshotApiResponse = ApiResponse<ScreenshotResult['data']>;
type TestApiResponse = ApiResponse<TestResult>;
type UsageApiResponse = ApiResponse<QuotaInfo>;

// Generic API call wrapper
async function apiCall<TData>(
  operation: () => Promise<TData>
): Promise<ApiResponse<TData>> {
  const startTime = Date.now();

  try {
    const data = await operation();
    return {
      success: true,
      data,
      meta: {
        timeMs: Date.now() - startTime
      }
    };
  } catch (error) {
    return {
      success: false,
      error: error instanceof Error ? error.message : 'Unknown error',
      meta: {
        timeMs: Date.now() - startTime
      }
    };
  }
}

// Usage
const screenshotResponse = await apiCall(() =>
  bt.screenshot.take({ url: 'https://example.com' })
);

if (screenshotResponse.success) {
  // TypeScript knows screenshotResponse.data is ScreenshotResult
  console.log('Screenshot taken:', screenshotResponse.data?.meta.size);
}

Best Practices

Type Safety

  1. Always define interfaces for structured output schemas
  2. Use strict null checks with TypeScript’s strictNullChecks
  3. Leverage discriminated unions for result types
  4. Create type guards for error handling

Development Experience

// Enable strict TypeScript settings in tsconfig.json
{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true
  }
}

IntelliSense and Auto-completion

// Use JSDoc comments for better IntelliSense
/**
 * Takes a screenshot of the specified URL with optional configuration
 * @param url - The URL to screenshot
 * @param options - Screenshot configuration options
 * @returns Promise resolving to screenshot result
 */
async function takeScreenshot(
  url: string,
  options: Partial<ScreenshotOptions> = {}
): Promise<ScreenshotResult> {
  return bt.screenshot.take({ url, ...options });
}

// IntelliSense will show parameter descriptions and types
const result = await takeScreenshot('https://example.com', {
  fullPage: true, // IntelliSense shows: "Capture full page (default: false)"
  format: 'png'   // IntelliSense shows: "'png' | 'jpeg'"
});

Testing with Types

// Typed test utilities
interface TestCase<TInput, TOutput> {
  name: string;
  input: TInput;
  expectedOutput: TOutput;
  schema?: object;
}

class TypedTester {
  constructor(private bt: BrowserTest) {}

  async runTest<TOutput>(
    testCase: TestCase<any, TOutput>
  ): Promise<{ success: boolean; result?: TOutput; error?: string }> {
    try {
      const result = await this.bt.testing.execute({
        instructions: `Test: ${testCase.name}`,
        url: testCase.input.url || 'https://example.com',
        outputSchema: testCase.schema
      });

      return {
        success: true,
        result: result.results.structuredData as TOutput
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : 'Unknown error'
      };
    }
  }
}

// Usage
interface LoginOutput {
  success: boolean;
  username?: string;
}

const tester = new TypedTester(bt);

const loginTest: TestCase<{ url: string }, LoginOutput> = {
  name: 'User Login',
  input: { url: 'https://app.com/login' },
  expectedOutput: { success: true },
  schema: {
    type: 'object',
    properties: {
      success: { type: 'boolean' },
      username: { type: 'string' }
    }
  }
};

const testResult = await tester.runTest(loginTest);
if (testResult.success) {
  // TypeScript knows testResult.result is LoginOutput
  console.log('Test passed:', testResult.result);
}