or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

index.mdinteraction-testing.mdlifecycle-testing.mdtest-generation.mdtesting-utilities.mdvisual-testing.md
tile.json

visual-testing.mddocs/

Visual Regression Testing

Screenshot-based testing that renders deck.gl scenes and compares against golden images for visual regression detection. Includes platform-specific overrides, diff image generation, and automated test orchestration.

Capabilities

Snapshot Test Runner

Primary class for managing visual regression test execution.

/**
 * Test runner that renders scenes and compares against golden images
 * Extends the base TestRunner with image comparison capabilities
 */
class SnapshotTestRunner extends TestRunner<SnapshotTestCase, DiffImageResult, { imageDiffOptions: ImageDiffOptions }> {
  /**
   * Create a new snapshot test runner
   * @param props - Deck.gl props for rendering configuration
   * @param options - Image comparison options
   */
  constructor(props: DeckProps, options?: { imageDiffOptions: ImageDiffOptions });
  
  /**
   * Add test cases to the runner
   * @param testCases - Array of snapshot test cases
   * @returns This runner for method chaining
   */
  add(testCases: SnapshotTestCase[]): this;
  
  /**
   * Execute all test cases
   * @param options - Runtime options for test execution
   * @returns Promise that resolves when all tests complete
   */
  run(options?: Partial<TestOptions<SnapshotTestCase, DiffImageResult> & { imageDiffOptions: ImageDiffOptions }>): Promise<void>;
}

Snapshot Test Case Configuration

Structure for defining individual visual regression test cases.

interface SnapshotTestCase {
  /** Descriptive name for the test case */
  name: string;
  /** Deck.gl props for rendering the scene */
  props: DeckProps;
  /** Path to the golden reference image */
  goldenImage: string;
  /** Called before rendering begins */
  onBeforeRender?: (params: { deck: Deck; layers: Layer[] }) => void;
  /** Called after each render frame - call done() when ready to capture */
  onAfterRender?: (params: { deck: Deck; layers: Layer[]; done: () => void }) => void;
  /** Maximum time to wait for this test case (milliseconds) */
  timeout?: number;
  /** Image comparison options specific to this test case */
  imageDiffOptions?: ImageDiffOptions;
}

Image Comparison Options

Configuration for controlling image comparison behavior.

interface ImageDiffOptions {
  /** Save failed comparison images to disk */
  saveOnFail?: boolean;
  /** Custom filename for saved images */
  saveAs?: string;  
  /** Similarity threshold (0-1, where 1 is exact match) */
  threshold?: number;
  /** Generate diff image highlighting differences */
  createDiffImage?: boolean;
  /** Color tolerance for pixel comparison (0-1) */
  tolerance?: number;
  /** Include anti-aliasing in comparison */
  includeAA?: boolean;
  /** Include empty/transparent areas in comparison */
  includeEmpty?: boolean;
  /** Platform identifier for platform-specific golden images */
  platform?: string;
}

interface DiffImageResult {
  /** Whether running in headless browser mode */
  headless: boolean;
  /** Similarity score or match status */
  match: string | number;
  /** Human-readable match percentage */
  matchPercentage: string;
  /** Whether the comparison passed */
  success: boolean;
  /** Error information if comparison failed */
  error: Error | string | null;
}

Usage Examples:

import { SnapshotTestRunner } from "@deck.gl/test-utils";
import { ScatterplotLayer } from "@deck.gl/layers";

// Create test runner
const testRunner = new SnapshotTestRunner({
  width: 800,
  height: 600,
  views: [new MapView()],
  initialViewState: {
    longitude: -122.4,
    latitude: 37.8,
    zoom: 12
  }
}, {
  imageDiffOptions: {
    threshold: 0.99,
    tolerance: 0.1,
    saveOnFail: true
  }
});

// Add test cases
testRunner.add([
  {
    name: "Scatterplot basic rendering",
    props: {
      layers: [
        new ScatterplotLayer({
          id: 'scatter',
          data: [
            { position: [-122.4, 37.8], size: 100, color: [255, 0, 0] },
            { position: [-122.41, 37.81], size: 150, color: [0, 255, 0] }
          ],
          getPosition: d => d.position,
          getRadius: d => d.size,
          getFillColor: d => d.color
        })
      ]
    },
    goldenImage: './test/golden-images/scatterplot-basic.png',
    onAfterRender: ({ deck, layers, done }) => {
      // Wait for all layers to be loaded
      if (layers.every(layer => layer.isLoaded)) {
        done();
      }
    }
  },
  {
    name: "Animated scatterplot",
    props: {
      layers: [
        new ScatterplotLayer({
          id: 'animated-scatter',
          data: generateAnimatedData(),
          getPosition: d => d.position,
          getRadius: d => d.size,
          transitions: {
            getRadius: 1000
          }
        })
      ]
    },
    goldenImage: './test/golden-images/scatterplot-animated.png',
    timeout: 5000,
    onBeforeRender: ({ deck }) => {
      console.log('Starting animation test');
    },
    onAfterRender: ({ deck, layers, done }) => {
      // Wait for transitions to complete
      const layer = layers[0];
      if (layer.isLoaded && !layer.state.transitions?.getRadius) {
        done();
      }
    }
  }
]);

// Run tests
await testRunner.run({
  timeout: 10000,
  onTestStart: testCase => console.log(`Starting: ${testCase.name}`),
  onTestPass: testCase => console.log(`✓ ${testCase.name}`),
  onTestFail: (testCase, result) => console.error(`✗ ${testCase.name}:`, result.error)
});

Platform-Specific Golden Images

The test runner supports platform-specific golden images for handling rendering differences across operating systems and GPU vendors:

  1. Primary golden image: Standard reference stored in golden-images/ directory
  2. Platform override: Alternative reference in golden-images/platform-overrides/{platform}/ directory

When a test fails against the primary golden image, the runner automatically attempts comparison against the platform-specific override if available.

// Platform detection is automatic
const testRunner = new SnapshotTestRunner({}, {
  imageDiffOptions: {
    platform: 'windows' // Detected automatically in browser
  }
});

// Golden image paths:
// Primary: ./golden-images/my-test.png  
// Override: ./golden-images/platform-overrides/windows/my-test.png

Render Lifecycle Control

Test cases have fine-grained control over the render cycle:

onBeforeRender

Called once before rendering begins. Use for:

  • Initial setup and configuration
  • Logging and debugging
  • Setting up test data

onAfterRender

Called after each render frame with a done callback. Use for:

  • Waiting for layers to load (layer.isLoaded)
  • Waiting for animations/transitions to complete
  • Checking render state before capture
  • Must call done() when ready for screenshot
{
  name: "Complex scene with loading states",
  props: { /* ... */ },
  onAfterRender: ({ deck, layers, done }) => {
    // Check multiple conditions
    const allLoaded = layers.every(layer => layer.isLoaded);
    const noTransitions = layers.every(layer => 
      !layer.state.transitions || 
      Object.keys(layer.state.transitions).length === 0
    );
    
    if (allLoaded && noTransitions) {
      // Additional wait for GPU to finish rendering
      setTimeout(done, 100);
    }
    // If conditions not met, onAfterRender will be called again next frame
  }
}

Error Handling and Debugging

Test Failure Information

Failed tests provide detailed information:

  • match: Similarity score or error code
  • matchPercentage: Human-readable percentage
  • error: Specific failure reason
  • headless: Whether running in headless mode (affects some rendering)

Image Saving Options

Configure automatic saving of failed comparison images:

{
  imageDiffOptions: {
    saveOnFail: true,
    saveAs: 'custom-failure-name.png',
    createDiffImage: true  // Also save highlighted diff
  }
}

Debugging Render Issues

Common patterns for debugging visual tests:

  • Use onBeforeRender to log layer configurations
  • Use onAfterRender to inspect layer states and loading status
  • Enable createDiffImage to see exactly what pixels differ
  • Check result.headless flag for browser mode differences