lib/capybara/screenshot/diff/test_methods.rb in capybara-screenshot-diff-1.8.3 vs lib/capybara/screenshot/diff/test_methods.rb in capybara-screenshot-diff-1.9.0
- old
+ new
@@ -13,33 +13,68 @@
require_relative "browser_helpers"
require_relative "region"
require_relative "screenshot_matcher"
-# Add the `screenshot` method to ActionDispatch::IntegrationTest
+# == Capybara::Screenshot::Diff::TestMethods
+#
+# This module provides methods for capturing screenshots and verifying them against
+# baseline images to detect visual changes. It's designed to be included in test
+# classes to add visual regression testing capabilities.
+
module Capybara
module Screenshot
module Diff
module TestMethods
+ # @!attribute [rw] test_screenshots
+ # @return [Array(Array(Array(String), String, ImageCompare))] An array where each element is an array containing the caller context,
+ # the name of the screenshot, and the comparison object. This attribute stores information about each screenshot
+ # scheduled for comparison to ensure they do not show any unintended differences.
def initialize(*)
super
@screenshot_counter = nil
@screenshot_group = nil
@screenshot_section = nil
@test_screenshot_errors = nil
@test_screenshots = []
end
+ # Verifies that all scheduled screenshots do not show any unintended differences.
+ #
+ # @param screenshots [Array(Array(Array(String), String, ImageCompare))] The list of match screenshots jobs. Defaults to all screenshots taken during the test.
+ # @return [Array, nil] Returns an array of error messages if there are screenshot differences, otherwise nil.
+ # @note This method is typically called at the end of a test to assert all screenshots are as expected.
+ def verify_screenshots!(screenshots = @test_screenshots)
+ return unless ::Capybara::Screenshot.active? && ::Capybara::Screenshot::Diff.fail_on_difference
+
+ test_screenshot_errors = screenshots.map do |caller, name, compare|
+ assert_image_not_changed(caller, name, compare)
+ end
+
+ test_screenshot_errors.compact!
+
+ test_screenshot_errors.presence
+ ensure
+ screenshots.clear
+ end
+
+ # Builds the full name for a screenshot, incorporating counters and group names for uniqueness.
+ #
+ # @param name [String] The base name for the screenshot.
+ # @return [String] The full, unique name for the screenshot.
def build_full_name(name)
if @screenshot_counter
name = format("%02i_#{name}", @screenshot_counter)
@screenshot_counter += 1
end
File.join(*group_parts.push(name.to_s))
end
+ # Determines the directory path for saving screenshots.
+ #
+ # @return [String] The full path to the directory where screenshots are saved.
def screenshot_dir
File.join(*([Screenshot.screenshot_area] + group_parts))
end
def screenshot_section(name)
@@ -52,10 +87,17 @@
return unless Screenshot.active? && name.present?
FileUtils.rm_rf screenshot_dir
end
+ # Schedules a screenshot comparison job for later execution.
+ #
+ # This method adds a job to the queue of screenshots to be matched. It's used when `Capybara::Screenshot::Diff.delayed`
+ # is set to true, allowing for batch processing of screenshot comparisons at a later point, typically at the end of a test.
+ #
+ # @param job [Array(Array(String), String, ImageCompare)] The job to be scheduled, consisting of the caller context, screenshot name, and comparison object.
+ # @return [Boolean] Always returns true, indicating the job was successfully scheduled.
def schedule_match_job(job)
(@test_screenshots ||= []) << job
true
end
@@ -64,32 +106,53 @@
parts << @screenshot_section if @screenshot_section.present?
parts << @screenshot_group if @screenshot_group.present?
parts
end
+ # Takes a screenshot and optionally compares it against a baseline image.
+ #
+ # @param name [String] The name of the screenshot, used to generate the filename.
+ # @param skip_stack_frames [Integer] The number of stack frames to skip when reporting errors, for cleaner error messages.
+ # @param options [Hash] Additional options for taking the screenshot, such as custom dimensions or selectors.
+ # @return [Boolean] Returns true if the screenshot was successfully captured and matches the baseline, false otherwise.
+ # @raise [CapybaraScreenshotDiff::ExpectationNotMet] If the screenshot does not match the baseline image and fail_if_new is set to true.
+ # @example Capture a full-page screenshot named 'login_page'
+ # screenshot('login_page', skip_stack_frames: 1, full: true)
def screenshot(name, skip_stack_frames: 0, **options)
return false unless Screenshot.active?
screenshot_full_name = build_full_name(name)
job = build_screenshot_matches_job(screenshot_full_name, options)
- return false unless job
+ unless job
+ if Screenshot::Diff.fail_if_new
+ raise_error(<<-ERROR.strip_heredoc, caller(2))
+ No existing screenshot found for #{screenshot_full_name}!
+ To stop seeing this error disable by `Capybara::Screenshot::Diff.fail_if_new=false`
+ ERROR
+ end
- job.prepend(caller[skip_stack_frames])
+ return false
+ end
+ job.prepend(caller(skip_stack_frames))
+
if Screenshot::Diff.delayed
schedule_match_job(job)
else
error_msg = assert_image_not_changed(*job)
- if error_msg
- error = ASSERTION.new(error_msg)
- error.set_backtrace(caller(2))
- raise error
- end
+ raise_error(error_msg, caller(2)) if error_msg
end
end
+ # Asserts that an image has not changed compared to its baseline.
+ #
+ # @param caller [Array] The caller context, used for error reporting.
+ # @param name [String] The name of the screenshot being verified.
+ # @param comparison [Object] The comparison object containing the result and details of the comparison.
+ # @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
+ # @note This method is used internally to verify individual screenshots.
def assert_image_not_changed(caller, name, comparison)
result = comparison.different?
# Cleanup after comparisons
if !result && comparison.base_image_path.exist?
@@ -102,9 +165,13 @@
"Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller}"
end
private
+
+ def raise_error(error_msg, backtrace)
+ raise CapybaraScreenshotDiff::ExpectationNotMet.new(error_msg).tap { _1.set_backtrace(backtrace) }
+ end
def build_screenshot_matches_job(screenshot_full_name, options)
ScreenshotMatcher
.new(screenshot_full_name, options)
.build_screenshot_matches_job