Module: SnapDiff::DSL

Includes:
Capybara::DSL
Included in:
Minitest::Assertions
Defined in:
lib/snap_diff/dsl.rb

Overview

DSL for taking screenshots and making assertions in Capybara tests. This module provides methods for taking screenshots, comparing them against baselines, and managing the comparison process with various configuration options.

The DSL is designed to be included in your test context (e.g., RSpec, Minitest) to provide screenshot comparison capabilities.

Instance Method Summary collapse

Instance Method Details

#assert_matches_screenshot(name, skip_stack_frames: 0, **options) { ... } ⇒ Boolean

Takes a screenshot and compares it against a baseline image.

The method follows a layered optimization strategy for comparison:

  1. First checks if screenshot functionality is active
  2. Builds a full screenshot name using the current context
  3. Creates a screenshot assertion object
  4. Either validates immediately or defers validation based on options

Parameters:

  • name (String)

    The base name of the screenshot, used to generate the filename.

  • skip_stack_frames (Integer) (defaults to: 0)

    The number of stack frames to skip when reporting errors.

  • options (Hash)

    Additional options for taking the screenshot and comparison.

Options Hash (**options):

  • :delayed (Boolean) — default: SnapDiff.config.delayed

    Whether to validate the screenshot immediately or delay validation.

  • :crop (Array<Integer>)

    [left, top, right, bottom] Edge coordinates to crop the screenshot to.

  • :skip_area (Array<Array<Integer>>)

    Array of [left, top, right, bottom] edge coordinates to ignore.

  • :tolerance (Numeric) — default: 0.001 for :vips driver

    Color tolerance for comparison. Represents the maximum allowed ratio of different pixels (0.0-1.0 scale).

  • :color_distance_limit (Numeric)

    Maximum allowed color distance between pixels. Uses Euclidean RGBA distance (0-510 scale). Mutually exclusive with :perceptual_threshold.

  • :perceptual_threshold (Numeric)

    Maximum perceptual color difference (CIE dE00). Uses human perception-based scale (0-100+). VIPS only. Takes priority over :color_distance_limit if both set.

  • :shift_distance_limit (Numeric)

    Maximum allowed shift distance for pixels.

  • :area_size_limit (Numeric)

    Maximum allowed difference area size in pixels.

  • :driver (Symbol) — default: :auto

    The image processing driver to use (:auto, :chunky_png, :vips).

Yields:

  • Optional readiness block: work that must happen before the page is captured -- settling lazy-loaded images, document.fonts.ready, waiting on a widget. Runs AFTER the active? guard and before the capture, exactly once per assertion (not once per stability attempt), and an error raised inside it propagates unchanged.

    The point is not ergonomics -- while screenshots are on, the block does nothing a line above the call could not. The point is what happens when they are OFF: this method returns at the guard above, so a preceding preload_all_images still costs its browser round-trips while the block costs nothing. Readiness work belongs inside the same switch as the capture it serves.

Returns:

  • (Boolean)

    True if the screenshot was successfully captured and processed.

Raises:



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/snap_diff/dsl.rb', line 83

def assert_matches_screenshot(name, skip_stack_frames: 0, **options)
  return false unless SnapDiff.config.active?

  yield if block_given?

  # Get the full name with section and group information
  full_name = SnapDiff.session.screenshot_namer.full_name(name)

  # Build the screenshot assertion; the actual comparison is deferred
  # until ScreenshotAssertion#validate! runs.
  assertion = SnapDiff::ScreenshotMatcher
    .new(full_name, options)
    .build_screenshot_assertion(skip_stack_frames: skip_stack_frames + 1)

  return false unless assertion

  # Determine if validation should be delayed or immediate
  delayed = options.fetch(:delayed, SnapDiff.config.delayed)

  if delayed
    SnapDiff.session.add_assertion(assertion)
  else
    assertion.validate!
  end

  true
end

#assert_no_screenshot_changes(name, skip_stack_frames: 0, **opts, &readiness) ⇒ Object

Asserts the current page has no visual changes from the baseline. Override in your base test class to add project-specific behavior (e.g., waiting for Turbo, default skip areas).



145
146
147
# File 'lib/snap_diff/dsl.rb', line 145

def assert_no_screenshot_changes(name, skip_stack_frames: 0, **opts, &readiness)
  assert_matches_screenshot(name, skip_stack_frames: skip_stack_frames + 1, **opts, &readiness)
end

#capture_screenshot(name, **options) { ... } ⇒ Boolean

Captures a screenshot without comparing it to a baseline.

Parameters:

  • name (String)

    The base name of the screenshot, used to generate the filename.

  • options (Hash)

    Additional options for taking the screenshot. See #assert_matches_screenshot.

Yields:

Returns:

  • (Boolean)

    True if the screenshot was successfully captured.



131
132
133
134
135
136
137
138
139
140
# File 'lib/snap_diff/dsl.rb', line 131

def capture_screenshot(name, **options)
  return false unless SnapDiff.config.active?

  yield if block_given?

  full_name = SnapDiff.session.screenshot_namer.full_name(name)
  SnapDiff::ScreenshotMatcher.new(full_name, options).capture

  true
end

#screenshot(name, skip_stack_frames: 0, compare: true, **options) { ... } ⇒ Object

Convenience wrapper around #assert_matches_screenshot and #capture_screenshot.

Parameters:

  • compare (Boolean) (defaults to: true)

    When false, only captures the screenshot without comparing it to a baseline.

Yields:

  • Forwarded to whichever of the two it delegates to. A delegator that swallowed the block would give the user a readiness block that silently never runs.

See Also:



118
119
120
121
122
123
124
# File 'lib/snap_diff/dsl.rb', line 118

def screenshot(name, skip_stack_frames: 0, compare: true, **options, &readiness)
  if compare
    assert_matches_screenshot(name, skip_stack_frames: skip_stack_frames + 1, **options, &readiness)
  else
    capture_screenshot(name, **options, &readiness)
  end
end

#screenshot_group(name) ⇒ Object



39
40
41
# File 'lib/snap_diff/dsl.rb', line 39

def screenshot_group(name)
  SnapDiff.session.screenshot_namer.group = name
end

#screenshot_section(name) ⇒ Object



35
36
37
# File 'lib/snap_diff/dsl.rb', line 35

def screenshot_section(name)
  SnapDiff.session.screenshot_namer.section = name
end