capybara-screenshot-diff 1.9.0 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/capybara-screenshot-diff.gemspec +1 -1
 - data/lib/capybara/screenshot/diff/browser_helpers.rb +4 -4
 - data/lib/capybara/screenshot/diff/drivers/base_driver.rb +0 -1
 - data/lib/capybara/screenshot/diff/screenshot_matcher.rb +27 -36
 - data/lib/capybara/screenshot/diff/screenshoter.rb +7 -34
 - data/lib/capybara/screenshot/diff/stable_screenshoter.rb +34 -54
 - data/lib/capybara/screenshot/diff/test_methods.rb +3 -3
 - data/lib/capybara/screenshot/diff/vcs.rb +24 -22
 - data/lib/capybara/screenshot/diff/version.rb +1 -1
 - data/lib/capybara_screenshot_diff/attempts_reporter.rb +49 -0
 - data/lib/capybara_screenshot_diff/dsl.rb +1 -0
 - data/lib/capybara_screenshot_diff/rspec.rb +1 -0
 - data/lib/capybara_screenshot_diff/snap.rb +55 -0
 - data/lib/capybara_screenshot_diff/snap_manager.rb +76 -0
 - data/lib/capybara_screenshot_diff.rb +3 -2
 - metadata +8 -5
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 87328c5152c18c006da12b39c30f0389ac0f7b71174530473681ac98e035b884
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: ec54883ea6de4b75fa5be917fee79e5098a18cd1c5ea3ebbfceb567661e148d3
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 7e5f89855ddaf008feab8705c6e13f495b654a7dd382fe209ac5e5917fca00553765dfcf411b90cd83aecd931fec6055502dcf3cf7ee0d69c21e8edaa891cbdc
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 9f402d5ff1ed20d02c0e6fd3d7d025fc7e82287e7d159a6fb892c21cf0f8bf6db74257ba22560631ef179f191cce2de78f099b4a47b637a9d6f91eaededa7ddd
         
     | 
| 
         @@ -23,6 +23,6 @@ Gem::Specification.new do |spec| 
     | 
|
| 
       23 
23 
     | 
    
         
             
              spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
         
     | 
| 
       24 
24 
     | 
    
         
             
              spec.require_paths = ["lib"]
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
              spec.add_runtime_dependency "actionpack", ">= 6.1", "<  
     | 
| 
      
 26 
     | 
    
         
            +
              spec.add_runtime_dependency "actionpack", ">= 6.1", "< 9"
         
     | 
| 
       27 
27 
     | 
    
         
             
              spec.add_runtime_dependency "capybara", ">= 2", "< 4"
         
     | 
| 
       28 
28 
     | 
    
         
             
            end
         
     | 
| 
         @@ -30,13 +30,13 @@ module Capybara 
     | 
|
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
                  IMAGE_WAIT_SCRIPT = <<~JS
         
     | 
| 
       32 
32 
     | 
    
         
             
                    function pending_image() {
         
     | 
| 
       33 
     | 
    
         
            -
                       
     | 
| 
      
 33 
     | 
    
         
            +
                      const images = document.images
         
     | 
| 
       34 
34 
     | 
    
         
             
                      for (var i = 0; i < images.length; i++) {
         
     | 
| 
       35 
     | 
    
         
            -
                        if (!images[i].complete) {
         
     | 
| 
       36 
     | 
    
         
            -
                            return images[i].src 
     | 
| 
      
 35 
     | 
    
         
            +
                        if (!images[i].complete && images[i].loading !== "lazy") {
         
     | 
| 
      
 36 
     | 
    
         
            +
                            return images[i].src
         
     | 
| 
       37 
37 
     | 
    
         
             
                        }
         
     | 
| 
       38 
38 
     | 
    
         
             
                      }
         
     | 
| 
       39 
     | 
    
         
            -
                      return false 
     | 
| 
      
 39 
     | 
    
         
            +
                      return false
         
     | 
| 
       40 
40 
     | 
    
         
             
                    }(window)
         
     | 
| 
       41 
41 
     | 
    
         
             
                  JS
         
     | 
| 
       42 
42 
     | 
    
         | 
| 
         @@ -1,5 +1,6 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            require "capybara_screenshot_diff/snap_manager"
         
     | 
| 
       3 
4 
     | 
    
         
             
            require_relative "screenshoter"
         
     | 
| 
       4 
5 
     | 
    
         
             
            require_relative "stable_screenshoter"
         
     | 
| 
       5 
6 
     | 
    
         
             
            require_relative "browser_helpers"
         
     | 
| 
         @@ -10,15 +11,14 @@ module Capybara 
     | 
|
| 
       10 
11 
     | 
    
         
             
              module Screenshot
         
     | 
| 
       11 
12 
     | 
    
         
             
                module Diff
         
     | 
| 
       12 
13 
     | 
    
         
             
                  class ScreenshotMatcher
         
     | 
| 
       13 
     | 
    
         
            -
                    attr_reader :screenshot_full_name, :driver_options, : 
     | 
| 
      
 14 
     | 
    
         
            +
                    attr_reader :screenshot_full_name, :driver_options, :screenshot_format
         
     | 
| 
       14 
15 
     | 
    
         | 
| 
       15 
16 
     | 
    
         
             
                    def initialize(screenshot_full_name, options = {})
         
     | 
| 
       16 
17 
     | 
    
         
             
                      @screenshot_full_name = screenshot_full_name
         
     | 
| 
       17 
18 
     | 
    
         
             
                      @driver_options = Diff.default_options.merge(options)
         
     | 
| 
       18 
19 
     | 
    
         | 
| 
       19 
20 
     | 
    
         
             
                      @screenshot_format = @driver_options[:screenshot_format]
         
     | 
| 
       20 
     | 
    
         
            -
                      @ 
     | 
| 
       21 
     | 
    
         
            -
                      @base_screenshot_path = ScreenshotMatcher.base_image_path_from(@screenshot_path)
         
     | 
| 
      
 21 
     | 
    
         
            +
                      @snapshot = CapybaraScreenshotDiff::SnapManager.snapshot(screenshot_full_name, @screenshot_format)
         
     | 
| 
       22 
22 
     | 
    
         
             
                    end
         
     | 
| 
       23 
23 
     | 
    
         | 
| 
       24 
24 
     | 
    
         
             
                    def build_screenshot_matches_job
         
     | 
| 
         @@ -32,58 +32,49 @@ module Capybara 
     | 
|
| 
       32 
32 
     | 
    
         
             
                      # TODO: Move this into screenshot stage, in order to re-evaluate coordinates after page updates
         
     | 
| 
       33 
33 
     | 
    
         
             
                      # Allow nil or single or multiple areas
         
     | 
| 
       34 
34 
     | 
    
         
             
                      driver_options[:skip_area] = area_calculator.calculate_skip_area
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
35 
     | 
    
         
             
                      driver_options[:driver] = Drivers.for(driver_options[:driver])
         
     | 
| 
       37 
36 
     | 
    
         | 
| 
       38 
     | 
    
         
            -
                       
     | 
| 
       39 
     | 
    
         
            -
                      create_output_directory_for(screenshot_path) unless screenshot_path.exist?
         
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
                      checkout_base_screenshot
         
     | 
| 
      
 37 
     | 
    
         
            +
                      @snapshot.checkout_base_screenshot
         
     | 
| 
       42 
38 
     | 
    
         | 
| 
       43 
39 
     | 
    
         
             
                      # When fail_if_new is true no need to create screenshot if base screenshot is missing
         
     | 
| 
       44 
     | 
    
         
            -
                      return if Capybara::Screenshot::Diff.fail_if_new &&  
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                      capture_options =  
     | 
| 
       47 
     | 
    
         
            -
                        # screenshot options
         
     | 
| 
       48 
     | 
    
         
            -
                        capybara_screenshot_options: driver_options[:capybara_screenshot_options],
         
     | 
| 
       49 
     | 
    
         
            -
                        crop: driver_options.delete(:crop),
         
     | 
| 
       50 
     | 
    
         
            -
                        # delivery options
         
     | 
| 
       51 
     | 
    
         
            -
                        screenshot_format: driver_options[:screenshot_format],
         
     | 
| 
       52 
     | 
    
         
            -
                        # stability options
         
     | 
| 
       53 
     | 
    
         
            -
                        stability_time_limit: driver_options.delete(:stability_time_limit),
         
     | 
| 
       54 
     | 
    
         
            -
                        wait: driver_options.delete(:wait)
         
     | 
| 
       55 
     | 
    
         
            -
                      }
         
     | 
| 
      
 40 
     | 
    
         
            +
                      return if Capybara::Screenshot::Diff.fail_if_new && !@snapshot.base_path.exist?
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                      capture_options, comparison_options = extract_capture_and_comparison_options!(driver_options)
         
     | 
| 
       56 
43 
     | 
    
         | 
| 
       57 
44 
     | 
    
         
             
                      # Load new screenshot from Browser
         
     | 
| 
       58 
     | 
    
         
            -
                      take_comparison_screenshot(capture_options,  
     | 
| 
      
 45 
     | 
    
         
            +
                      take_comparison_screenshot(capture_options, comparison_options, @snapshot)
         
     | 
| 
       59 
46 
     | 
    
         | 
| 
       60 
47 
     | 
    
         
             
                      # Pre-computation: No need to compare without base screenshot
         
     | 
| 
       61 
     | 
    
         
            -
                      return unless  
     | 
| 
      
 48 
     | 
    
         
            +
                      return unless @snapshot.base_path.exist?
         
     | 
| 
       62 
49 
     | 
    
         | 
| 
       63 
50 
     | 
    
         
             
                      # Add comparison job in the queue
         
     | 
| 
       64 
     | 
    
         
            -
                      [screenshot_full_name, ImageCompare.new( 
     | 
| 
       65 
     | 
    
         
            -
                    end
         
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
                    def self.base_image_path_from(screenshot_path)
         
     | 
| 
       68 
     | 
    
         
            -
                      screenshot_path.sub_ext(".base#{screenshot_path.extname}")
         
     | 
| 
      
 51 
     | 
    
         
            +
                      [screenshot_full_name, ImageCompare.new(@snapshot.path, @snapshot.base_path, comparison_options)]
         
     | 
| 
       69 
52 
     | 
    
         
             
                    end
         
     | 
| 
       70 
53 
     | 
    
         | 
| 
       71 
54 
     | 
    
         
             
                    private
         
     | 
| 
       72 
55 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
                    def  
     | 
| 
       74 
     | 
    
         
            -
                       
     | 
| 
       75 
     | 
    
         
            -
             
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
             
     | 
| 
      
 56 
     | 
    
         
            +
                    def extract_capture_and_comparison_options!(driver_options = {})
         
     | 
| 
      
 57 
     | 
    
         
            +
                      [
         
     | 
| 
      
 58 
     | 
    
         
            +
                        {
         
     | 
| 
      
 59 
     | 
    
         
            +
                          # screenshot options
         
     | 
| 
      
 60 
     | 
    
         
            +
                          capybara_screenshot_options: driver_options[:capybara_screenshot_options],
         
     | 
| 
      
 61 
     | 
    
         
            +
                          crop: driver_options.delete(:crop),
         
     | 
| 
      
 62 
     | 
    
         
            +
                          # delivery options
         
     | 
| 
      
 63 
     | 
    
         
            +
                          screenshot_format: driver_options[:screenshot_format],
         
     | 
| 
      
 64 
     | 
    
         
            +
                          # stability options
         
     | 
| 
      
 65 
     | 
    
         
            +
                          stability_time_limit: driver_options.delete(:stability_time_limit),
         
     | 
| 
      
 66 
     | 
    
         
            +
                          wait: driver_options.delete(:wait)
         
     | 
| 
      
 67 
     | 
    
         
            +
                        },
         
     | 
| 
      
 68 
     | 
    
         
            +
                        driver_options
         
     | 
| 
      
 69 
     | 
    
         
            +
                      ]
         
     | 
| 
       79 
70 
     | 
    
         
             
                    end
         
     | 
| 
       80 
71 
     | 
    
         | 
| 
       81 
72 
     | 
    
         
             
                    # Try to get screenshot from browser.
         
     | 
| 
       82 
73 
     | 
    
         
             
                    # On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
         
     | 
| 
       83 
74 
     | 
    
         
             
                    # On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
         
     | 
| 
       84 
     | 
    
         
            -
                    def take_comparison_screenshot(capture_options,  
     | 
| 
       85 
     | 
    
         
            -
                      screenshoter = build_screenshoter_for(capture_options,  
     | 
| 
       86 
     | 
    
         
            -
                      screenshoter.take_comparison_screenshot( 
     | 
| 
      
 75 
     | 
    
         
            +
                    def take_comparison_screenshot(capture_options, comparison_options, snapshot = nil)
         
     | 
| 
      
 76 
     | 
    
         
            +
                      screenshoter = build_screenshoter_for(capture_options, comparison_options)
         
     | 
| 
      
 77 
     | 
    
         
            +
                      screenshoter.take_comparison_screenshot(snapshot)
         
     | 
| 
       87 
78 
     | 
    
         
             
                    end
         
     | 
| 
       88 
79 
     | 
    
         | 
| 
       89 
80 
     | 
    
         
             
                    def build_screenshoter_for(capture_options, comparison_options = {})
         
     | 
| 
         @@ -6,11 +6,10 @@ require_relative "browser_helpers" 
     | 
|
| 
       6 
6 
     | 
    
         
             
            module Capybara
         
     | 
| 
       7 
7 
     | 
    
         
             
              module Screenshot
         
     | 
| 
       8 
8 
     | 
    
         
             
                class Screenshoter
         
     | 
| 
       9 
     | 
    
         
            -
                  attr_reader :capture_options, : 
     | 
| 
      
 9 
     | 
    
         
            +
                  attr_reader :capture_options, :driver
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
                  def initialize(capture_options, driver)
         
     | 
| 
       12 
12 
     | 
    
         
             
                    @capture_options = capture_options
         
     | 
| 
       13 
     | 
    
         
            -
                    @comparison_options = comparison_options
         
     | 
| 
       14 
13 
     | 
    
         
             
                    @driver = driver
         
     | 
| 
       15 
14 
     | 
    
         
             
                  end
         
     | 
| 
       16 
15 
     | 
    
         | 
| 
         @@ -22,34 +21,16 @@ module Capybara 
     | 
|
| 
       22 
21 
     | 
    
         
             
                    @capture_options[:wait]
         
     | 
| 
       23 
22 
     | 
    
         
             
                  end
         
     | 
| 
       24 
23 
     | 
    
         | 
| 
       25 
     | 
    
         
            -
                  def screenshot_format
         
     | 
| 
       26 
     | 
    
         
            -
                    @capture_options[:screenshot_format] || "png"
         
     | 
| 
       27 
     | 
    
         
            -
                  end
         
     | 
| 
       28 
     | 
    
         
            -
             
     | 
| 
       29 
24 
     | 
    
         
             
                  def capybara_screenshot_options
         
     | 
| 
       30 
25 
     | 
    
         
             
                    @capture_options[:capybara_screenshot_options] || {}
         
     | 
| 
       31 
26 
     | 
    
         
             
                  end
         
     | 
| 
       32 
27 
     | 
    
         | 
| 
       33 
     | 
    
         
            -
                  def self.attempts_screenshot_paths(base_file)
         
     | 
| 
       34 
     | 
    
         
            -
                    extname = Pathname.new(base_file).extname
         
     | 
| 
       35 
     | 
    
         
            -
                    Dir["#{base_file.to_s.chomp(extname)}.attempt_*#{extname}"].sort
         
     | 
| 
       36 
     | 
    
         
            -
                  end
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                  def self.cleanup_attempts_screenshots(base_file)
         
     | 
| 
       39 
     | 
    
         
            -
                    FileUtils.rm_rf attempts_screenshot_paths(base_file)
         
     | 
| 
       40 
     | 
    
         
            -
                  end
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
28 
     | 
    
         
             
                  # Try to get screenshot from browser.
         
     | 
| 
       43 
29 
     | 
    
         
             
                  # On `stability_time_limit` it checks that page stop updating by comparison several screenshot attempts
         
     | 
| 
       44 
30 
     | 
    
         
             
                  # On reaching `wait` limit then it has been failed. On failing we annotate screenshot attempts to help to debug
         
     | 
| 
       45 
     | 
    
         
            -
                  def take_comparison_screenshot( 
     | 
| 
       46 
     | 
    
         
            -
                    capture_screenshot_at( 
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
                    Screenshoter.cleanup_attempts_screenshots(screenshot_path)
         
     | 
| 
       49 
     | 
    
         
            -
                  end
         
     | 
| 
       50 
     | 
    
         
            -
             
     | 
| 
       51 
     | 
    
         
            -
                  def self.gen_next_attempt_path(screenshot_path, iteration)
         
     | 
| 
       52 
     | 
    
         
            -
                    screenshot_path.sub_ext(format(".attempt_%02i#{screenshot_path.extname}", iteration))
         
     | 
| 
      
 31 
     | 
    
         
            +
                  def take_comparison_screenshot(snapshot)
         
     | 
| 
      
 32 
     | 
    
         
            +
                    capture_screenshot_at(snapshot)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    snapshot.cleanup_attempts
         
     | 
| 
       53 
34 
     | 
    
         
             
                  end
         
     | 
| 
       54 
35 
     | 
    
         | 
| 
       55 
36 
     | 
    
         
             
                  PNG_EXTENSION = ".png"
         
     | 
| 
         @@ -123,18 +104,10 @@ module Capybara 
     | 
|
| 
       123 
104 
     | 
    
         
             
                    File.unlink(tmpfile) if tmpfile
         
     | 
| 
       124 
105 
     | 
    
         
             
                  end
         
     | 
| 
       125 
106 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
                  def capture_screenshot_at( 
     | 
| 
       127 
     | 
    
         
            -
                     
     | 
| 
       128 
     | 
    
         
            -
                    take_and_process_screenshot(new_screenshot_path, screenshot_path)
         
     | 
| 
       129 
     | 
    
         
            -
                  end
         
     | 
| 
       130 
     | 
    
         
            -
             
     | 
| 
       131 
     | 
    
         
            -
                  def take_and_process_screenshot(new_screenshot_path, screenshot_path)
         
     | 
| 
       132 
     | 
    
         
            -
                    take_screenshot(new_screenshot_path)
         
     | 
| 
       133 
     | 
    
         
            -
                    move_screenshot_to(new_screenshot_path, screenshot_path)
         
     | 
| 
       134 
     | 
    
         
            -
                  end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  def capture_screenshot_at(snapshot)
         
     | 
| 
      
 108 
     | 
    
         
            +
                    take_screenshot(snapshot.next_attempt_path!)
         
     | 
| 
       135 
109 
     | 
    
         | 
| 
       136 
     | 
    
         
            -
             
     | 
| 
       137 
     | 
    
         
            -
                    FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
         
     | 
| 
      
 110 
     | 
    
         
            +
                    snapshot.commit_last_attempt
         
     | 
| 
       138 
111 
     | 
    
         
             
                  end
         
     | 
| 
       139 
112 
     | 
    
         | 
| 
       140 
113 
     | 
    
         
             
                  def resize_if_needed(saved_image)
         
     | 
| 
         @@ -16,17 +16,17 @@ module Capybara 
     | 
|
| 
       16 
16 
     | 
    
         
             
                    # @param capture_options [Hash] The options for capturing screenshots, must include `:stability_time_limit` and `:wait`.
         
     | 
| 
       17 
17 
     | 
    
         
             
                    # @param comparison_options [Hash, nil] The options for comparing screenshots, defaults to `nil` which uses `Diff.default_options`.
         
     | 
| 
       18 
18 
     | 
    
         
             
                    # @raise [ArgumentError] If `:wait` or `:stability_time_limit` are not provided, or if `:stability_time_limit` is greater than `:wait`.
         
     | 
| 
       19 
     | 
    
         
            -
                    def initialize(capture_options, comparison_options =  
     | 
| 
       20 
     | 
    
         
            -
                      @stability_time_limit, @wait = capture_options.fetch_values( 
     | 
| 
      
 19 
     | 
    
         
            +
                    def initialize(capture_options, comparison_options = {})
         
     | 
| 
      
 20 
     | 
    
         
            +
                      @stability_time_limit, @wait = capture_options.fetch_values(*STABILITY_OPTIONS)
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
                      raise ArgumentError, "wait should be provided for stable screenshots" unless wait
         
     | 
| 
       23 
23 
     | 
    
         
             
                      raise ArgumentError, "stability_time_limit should be provided for stable screenshots" unless stability_time_limit
         
     | 
| 
       24 
24 
     | 
    
         
             
                      raise ArgumentError, "stability_time_limit (#{stability_time_limit}) should be less or equal than wait (#{wait}) for stable screenshots" unless stability_time_limit <= wait
         
     | 
| 
       25 
25 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
                      @comparison_options = comparison_options 
     | 
| 
      
 26 
     | 
    
         
            +
                      @comparison_options = comparison_options
         
     | 
| 
       27 
27 
     | 
    
         | 
| 
       28 
28 
     | 
    
         
             
                      driver = Diff::Drivers.for(@comparison_options)
         
     | 
| 
       29 
     | 
    
         
            -
                      @screenshoter = Diff.screenshoter.new(capture_options.except( 
     | 
| 
      
 29 
     | 
    
         
            +
                      @screenshoter = Diff.screenshoter.new(capture_options.except(:stability_time_limit), driver)
         
     | 
| 
       30 
30 
     | 
    
         
             
                    end
         
     | 
| 
       31 
31 
     | 
    
         | 
| 
       32 
32 
     | 
    
         
             
                    # Takes a comparison screenshot ensuring page stability
         
     | 
| 
         @@ -35,92 +35,72 @@ module Capybara 
     | 
|
| 
       35 
35 
     | 
    
         
             
                    # or the `:wait` limit is reached. If unable to achieve a stable state within the time limit, it annotates the attempts
         
     | 
| 
       36 
36 
     | 
    
         
             
                    # to aid debugging.
         
     | 
| 
       37 
37 
     | 
    
         
             
                    #
         
     | 
| 
       38 
     | 
    
         
            -
                    # @param  
     | 
| 
      
 38 
     | 
    
         
            +
                    # @param snapshot Snap The snapshot details to take a stable screenshot of.
         
     | 
| 
       39 
39 
     | 
    
         
             
                    # @return [void]
         
     | 
| 
       40 
40 
     | 
    
         
             
                    # @raise [RuntimeError] If a stable screenshot cannot be obtained within the specified `:wait` time.
         
     | 
| 
       41 
     | 
    
         
            -
                    def take_comparison_screenshot( 
     | 
| 
       42 
     | 
    
         
            -
                       
     | 
| 
      
 41 
     | 
    
         
            +
                    def take_comparison_screenshot(snapshot)
         
     | 
| 
      
 42 
     | 
    
         
            +
                      result = take_stable_screenshot(snapshot)
         
     | 
| 
       43 
43 
     | 
    
         | 
| 
       44 
44 
     | 
    
         
             
                      # We failed to get stable browser state! Generate difference between attempts to overview moving parts!
         
     | 
| 
       45 
     | 
    
         
            -
                      unless  
     | 
| 
      
 45 
     | 
    
         
            +
                      unless result
         
     | 
| 
       46 
46 
     | 
    
         
             
                        # FIXME(uwe): Change to store the failure and only report if the test succeeds functionally.
         
     | 
| 
       47 
     | 
    
         
            -
                        annotate_attempts_and_fail!( 
     | 
| 
      
 47 
     | 
    
         
            +
                        annotate_attempts_and_fail!(snapshot)
         
     | 
| 
       48 
48 
     | 
    
         
             
                      end
         
     | 
| 
       49 
49 
     | 
    
         | 
| 
       50 
     | 
    
         
            -
                       
     | 
| 
       51 
     | 
    
         
            -
                       
     | 
| 
      
 50 
     | 
    
         
            +
                      # store success attempt as actual screenshot
         
     | 
| 
      
 51 
     | 
    
         
            +
                      snapshot.commit_last_attempt
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                      # cleanup all previous attempts
         
     | 
| 
      
 54 
     | 
    
         
            +
                      snapshot.cleanup_attempts
         
     | 
| 
       52 
55 
     | 
    
         
             
                    end
         
     | 
| 
       53 
56 
     | 
    
         | 
| 
       54 
     | 
    
         
            -
                    def take_stable_screenshot( 
     | 
| 
       55 
     | 
    
         
            -
                      screenshot_path = screenshot_path.is_a?(String) ? Pathname.new(screenshot_path) : screenshot_path
         
     | 
| 
      
 57 
     | 
    
         
            +
                    def take_stable_screenshot(snapshot)
         
     | 
| 
       56 
58 
     | 
    
         
             
                      # We try to compare first attempt with checkout version, in order to not run next screenshots
         
     | 
| 
       57 
     | 
    
         
            -
                      attempt_path = nil
         
     | 
| 
       58 
59 
     | 
    
         
             
                      deadline_at = Process.clock_gettime(Process::CLOCK_MONOTONIC) + wait
         
     | 
| 
       59 
60 
     | 
    
         | 
| 
       60 
61 
     | 
    
         
             
                      # Cleanup all previous attempts for sure
         
     | 
| 
       61 
     | 
    
         
            -
                       
     | 
| 
      
 62 
     | 
    
         
            +
                      snapshot.cleanup_attempts
         
     | 
| 
       62 
63 
     | 
    
         | 
| 
       63 
64 
     | 
    
         
             
                      0.step do |i|
         
     | 
| 
       64 
65 
     | 
    
         
             
                        # FIXME: it should be wait, and wait should be replaced with stability_time_limit
         
     | 
| 
       65 
     | 
    
         
            -
                        sleep(stability_time_limit) unless i == 0
         
     | 
| 
       66 
     | 
    
         
            -
             
     | 
| 
       67 
     | 
    
         
            -
                         
     | 
| 
       68 
     | 
    
         
            -
             
     | 
| 
      
 66 
     | 
    
         
            +
                        sleep(stability_time_limit) unless i == 0 # test prev_attempt_path is nil
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                        attempt_next_screenshot(snapshot)
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                        return true if attempt_successful?(snapshot)
         
     | 
| 
      
 71 
     | 
    
         
            +
                        return false if timeout?(deadline_at)
         
     | 
| 
       69 
72 
     | 
    
         
             
                      end
         
     | 
| 
       70 
73 
     | 
    
         
             
                    end
         
     | 
| 
       71 
74 
     | 
    
         | 
| 
       72 
75 
     | 
    
         
             
                    private
         
     | 
| 
       73 
76 
     | 
    
         | 
| 
       74 
     | 
    
         
            -
                    def attempt_successful?( 
     | 
| 
       75 
     | 
    
         
            -
                      return false unless prev_attempt_path
         
     | 
| 
       76 
     | 
    
         
            -
             
     | 
| 
      
 77 
     | 
    
         
            +
                    def attempt_successful?(snapshot)
         
     | 
| 
      
 78 
     | 
    
         
            +
                      return false unless snapshot.prev_attempt_path
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                      build_last_attempts_comparison_for(snapshot).quick_equal?
         
     | 
| 
       77 
81 
     | 
    
         
             
                    rescue ArgumentError
         
     | 
| 
       78 
82 
     | 
    
         
             
                      false
         
     | 
| 
       79 
83 
     | 
    
         
             
                    end
         
     | 
| 
       80 
84 
     | 
    
         | 
| 
       81 
     | 
    
         
            -
                    def attempt_next_screenshot( 
     | 
| 
       82 
     | 
    
         
            -
                       
     | 
| 
       83 
     | 
    
         
            -
                      @screenshoter.take_screenshot(new_attempt_path)
         
     | 
| 
       84 
     | 
    
         
            -
                      [new_attempt_path, prev_attempt_path]
         
     | 
| 
      
 85 
     | 
    
         
            +
                    def attempt_next_screenshot(snapshot)
         
     | 
| 
      
 86 
     | 
    
         
            +
                      @screenshoter.take_screenshot(snapshot.next_attempt_path!)
         
     | 
| 
       85 
87 
     | 
    
         
             
                    end
         
     | 
| 
       86 
88 
     | 
    
         | 
| 
       87 
89 
     | 
    
         
             
                    def timeout?(deadline_at)
         
     | 
| 
       88 
90 
     | 
    
         
             
                      Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline_at
         
     | 
| 
       89 
91 
     | 
    
         
             
                    end
         
     | 
| 
       90 
92 
     | 
    
         | 
| 
       91 
     | 
    
         
            -
                    def  
     | 
| 
       92 
     | 
    
         
            -
                      ImageCompare.new(attempt_path,  
     | 
| 
      
 93 
     | 
    
         
            +
                    def build_last_attempts_comparison_for(snapshot)
         
     | 
| 
      
 94 
     | 
    
         
            +
                      ImageCompare.new(snapshot.attempt_path, snapshot.prev_attempt_path, @comparison_options)
         
     | 
| 
       93 
95 
     | 
    
         
             
                    end
         
     | 
| 
       94 
96 
     | 
    
         | 
| 
       95 
97 
     | 
    
         
             
                    # TODO: Move to the HistoricalReporter
         
     | 
| 
       96 
     | 
    
         
            -
                    def annotate_attempts_and_fail!( 
     | 
| 
       97 
     | 
    
         
            -
                       
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
                      annotate_stabilization_images(screenshot_attempts)
         
     | 
| 
      
 98 
     | 
    
         
            +
                    def annotate_attempts_and_fail!(snapshot)
         
     | 
| 
      
 99 
     | 
    
         
            +
                      require "capybara_screenshot_diff/attempts_reporter"
         
     | 
| 
      
 100 
     | 
    
         
            +
                      attempts_reporter = CapybaraScreenshotDiff::AttemptsReporter.new(snapshot, @comparison_options, {wait: wait, stability_time_limit: stability_time_limit})
         
     | 
| 
       100 
101 
     | 
    
         | 
| 
       101 
102 
     | 
    
         
             
                      # TODO: Move fail to the queue after tests passed
         
     | 
| 
       102 
     | 
    
         
            -
                      fail( 
     | 
| 
       103 
     | 
    
         
            -
                    end
         
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
       105 
     | 
    
         
            -
                    # TODO: Add tests that we annotate all files except first one
         
     | 
| 
       106 
     | 
    
         
            -
                    def annotate_stabilization_images(attempts_screenshot_paths)
         
     | 
| 
       107 
     | 
    
         
            -
                      previous_file = nil
         
     | 
| 
       108 
     | 
    
         
            -
                      attempts_screenshot_paths.reverse_each do |file_name|
         
     | 
| 
       109 
     | 
    
         
            -
                        if previous_file && File.exist?(previous_file)
         
     | 
| 
       110 
     | 
    
         
            -
                          attempts_comparison = build_comparison_for(file_name, previous_file)
         
     | 
| 
       111 
     | 
    
         
            -
             
     | 
| 
       112 
     | 
    
         
            -
                          if attempts_comparison.different?
         
     | 
| 
       113 
     | 
    
         
            -
                            FileUtils.mv(attempts_comparison.reporter.annotated_base_image_path, previous_file, force: true)
         
     | 
| 
       114 
     | 
    
         
            -
                          else
         
     | 
| 
       115 
     | 
    
         
            -
                            warn "[capybara-screenshot-diff] Some attempts was stable, but mistakenly marked as not: " \
         
     | 
| 
       116 
     | 
    
         
            -
                                   "#{previous_file} and #{file_name} are equal"
         
     | 
| 
       117 
     | 
    
         
            -
                          end
         
     | 
| 
       118 
     | 
    
         
            -
             
     | 
| 
       119 
     | 
    
         
            -
                          FileUtils.rm(attempts_comparison.reporter.annotated_image_path, force: true)
         
     | 
| 
       120 
     | 
    
         
            -
                        end
         
     | 
| 
       121 
     | 
    
         
            -
             
     | 
| 
       122 
     | 
    
         
            -
                        previous_file = file_name
         
     | 
| 
       123 
     | 
    
         
            -
                      end
         
     | 
| 
      
 103 
     | 
    
         
            +
                      fail(attempts_reporter.generate)
         
     | 
| 
       124 
104 
     | 
    
         
             
                    end
         
     | 
| 
       125 
105 
     | 
    
         
             
                  end
         
     | 
| 
       126 
106 
     | 
    
         
             
                end
         
     | 
| 
         @@ -26,7 +26,7 @@ module Capybara 
     | 
|
| 
       26 
26 
     | 
    
         
             
                module Diff
         
     | 
| 
       27 
27 
     | 
    
         
             
                  module TestMethods
         
     | 
| 
       28 
28 
     | 
    
         
             
                    # @!attribute [rw] test_screenshots
         
     | 
| 
       29 
     | 
    
         
            -
                    #   @return [Array(Array(Array(String), String, ImageCompare))] An array where each element is an array containing the caller context,
         
     | 
| 
      
 29 
     | 
    
         
            +
                    #   @return [Array(Array(Array(String), String, ImageCompare | Minitest::Mock))] An array where each element is an array containing the caller context,
         
     | 
| 
       30 
30 
     | 
    
         
             
                    #     the name of the screenshot, and the comparison object. This attribute stores information about each screenshot
         
     | 
| 
       31 
31 
     | 
    
         
             
                    #     scheduled for comparison to ensure they do not show any unintended differences.
         
     | 
| 
       32 
32 
     | 
    
         
             
                    def initialize(*)
         
     | 
| 
         @@ -146,7 +146,7 @@ module Capybara 
     | 
|
| 
       146 
146 
     | 
    
         | 
| 
       147 
147 
     | 
    
         
             
                    # Asserts that an image has not changed compared to its baseline.
         
     | 
| 
       148 
148 
     | 
    
         
             
                    #
         
     | 
| 
       149 
     | 
    
         
            -
                    # @param caller [Array] The caller context, used for error reporting.
         
     | 
| 
      
 149 
     | 
    
         
            +
                    # @param caller [Array(String)] The caller context, used for error reporting.
         
     | 
| 
       150 
150 
     | 
    
         
             
                    # @param name [String] The name of the screenshot being verified.
         
     | 
| 
       151 
151 
     | 
    
         
             
                    # @param comparison [Object] The comparison object containing the result and details of the comparison.
         
     | 
| 
       152 
152 
     | 
    
         
             
                    # @return [String, nil] Returns an error message if the screenshot differs from the baseline, otherwise nil.
         
     | 
| 
         @@ -163,7 +163,7 @@ module Capybara 
     | 
|
| 
       163 
163 
     | 
    
         | 
| 
       164 
164 
     | 
    
         
             
                      return unless result
         
     | 
| 
       165 
165 
     | 
    
         | 
| 
       166 
     | 
    
         
            -
                      "Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller}"
         
     | 
| 
      
 166 
     | 
    
         
            +
                      "Screenshot does not match for '#{name}' #{comparison.error_message}\n#{caller.join(", ")}"
         
     | 
| 
       167 
167 
     | 
    
         
             
                    end
         
     | 
| 
       168 
168 
     | 
    
         | 
| 
       169 
169 
     | 
    
         
             
                    private
         
     | 
| 
         @@ -6,21 +6,35 @@ module Capybara 
     | 
|
| 
       6 
6 
     | 
    
         
             
              module Screenshot
         
     | 
| 
       7 
7 
     | 
    
         
             
                module Diff
         
     | 
| 
       8 
8 
     | 
    
         
             
                  module Vcs
         
     | 
| 
       9 
     | 
    
         
            -
                     
     | 
| 
      
 9 
     | 
    
         
            +
                    def self.checkout_vcs(root, screenshot_path, checkout_path)
         
     | 
| 
      
 10 
     | 
    
         
            +
                      if svn?(root)
         
     | 
| 
      
 11 
     | 
    
         
            +
                        restore_svn_revision(screenshot_path, checkout_path)
         
     | 
| 
      
 12 
     | 
    
         
            +
                      else
         
     | 
| 
      
 13 
     | 
    
         
            +
                        restore_git_revision(screenshot_path, checkout_path, root: root)
         
     | 
| 
      
 14 
     | 
    
         
            +
                      end
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
       10 
16 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
                    def self. 
     | 
| 
       12 
     | 
    
         
            -
                       
     | 
| 
      
 17 
     | 
    
         
            +
                    def self.svn?(root)
         
     | 
| 
      
 18 
     | 
    
         
            +
                      (root / ".svn").exist?
         
     | 
| 
      
 19 
     | 
    
         
            +
                    end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                    SILENCE_ERRORS = Os::ON_WINDOWS ? "2>nul" : "2>/dev/null"
         
     | 
| 
       13 
22 
     | 
    
         | 
| 
      
 23 
     | 
    
         
            +
                    def self.restore_git_revision(screenshot_path, checkout_path = screenshot_path, root:)
         
     | 
| 
      
 24 
     | 
    
         
            +
                      vcs_file_path = screenshot_path.relative_path_from(root)
         
     | 
| 
       14 
25 
     | 
    
         
             
                      redirect_target = "#{checkout_path} #{SILENCE_ERRORS}"
         
     | 
| 
       15 
26 
     | 
    
         
             
                      show_command = "git show HEAD~0:./#{vcs_file_path}"
         
     | 
| 
       16 
     | 
    
         
            -
             
     | 
| 
       17 
     | 
    
         
            -
             
     | 
| 
       18 
     | 
    
         
            -
                        if  
     | 
| 
       19 
     | 
    
         
            -
                           
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                      Dir.chdir(root) do
         
     | 
| 
      
 29 
     | 
    
         
            +
                        if Screenshot.use_lfs
         
     | 
| 
      
 30 
     | 
    
         
            +
                          system("#{show_command} > #{checkout_path}.tmp #{SILENCE_ERRORS}", exception: !!ENV["DEBUG"])
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                          `git lfs smudge < #{checkout_path}.tmp > #{redirect_target}` if $CHILD_STATUS == 0
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                          File.delete "#{checkout_path}.tmp"
         
     | 
| 
      
 35 
     | 
    
         
            +
                        else
         
     | 
| 
      
 36 
     | 
    
         
            +
                          system("#{show_command} > #{redirect_target}", exception: !!ENV["DEBUG"])
         
     | 
| 
       20 
37 
     | 
    
         
             
                        end
         
     | 
| 
       21 
     | 
    
         
            -
                        File.delete "#{checkout_path}.tmp"
         
     | 
| 
       22 
     | 
    
         
            -
                      else
         
     | 
| 
       23 
     | 
    
         
            -
                        `#{show_command} > #{redirect_target}`
         
     | 
| 
       24 
38 
     | 
    
         
             
                      end
         
     | 
| 
       25 
39 
     | 
    
         | 
| 
       26 
40 
     | 
    
         
             
                      if $CHILD_STATUS != 0
         
     | 
| 
         @@ -31,14 +45,6 @@ module Capybara 
     | 
|
| 
       31 
45 
     | 
    
         
             
                      end
         
     | 
| 
       32 
46 
     | 
    
         
             
                    end
         
     | 
| 
       33 
47 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                    def self.checkout_vcs(screenshot_path, checkout_path)
         
     | 
| 
       35 
     | 
    
         
            -
                      if svn?
         
     | 
| 
       36 
     | 
    
         
            -
                        restore_svn_revision(screenshot_path, checkout_path)
         
     | 
| 
       37 
     | 
    
         
            -
                      else
         
     | 
| 
       38 
     | 
    
         
            -
                        restore_git_revision(screenshot_path, checkout_path)
         
     | 
| 
       39 
     | 
    
         
            -
                      end
         
     | 
| 
       40 
     | 
    
         
            -
                    end
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
48 
     | 
    
         
             
                    def self.restore_svn_revision(screenshot_path, checkout_path)
         
     | 
| 
       43 
49 
     | 
    
         
             
                      committed_file_name = screenshot_path + "../.svn/text-base/" + "#{screenshot_path.basename}.svn-base"
         
     | 
| 
       44 
50 
     | 
    
         
             
                      if committed_file_name.exist?
         
     | 
| 
         @@ -60,10 +66,6 @@ module Capybara 
     | 
|
| 
       60 
66 
     | 
    
         | 
| 
       61 
67 
     | 
    
         
             
                      false
         
     | 
| 
       62 
68 
     | 
    
         
             
                    end
         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       64 
     | 
    
         
            -
                    def self.svn?
         
     | 
| 
       65 
     | 
    
         
            -
                      (Screenshot.screenshot_area_abs / ".svn").exist?
         
     | 
| 
       66 
     | 
    
         
            -
                    end
         
     | 
| 
       67 
69 
     | 
    
         
             
                  end
         
     | 
| 
       68 
70 
     | 
    
         
             
                end
         
     | 
| 
       69 
71 
     | 
    
         
             
              end
         
     | 
| 
         @@ -0,0 +1,49 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "capybara/screenshot/diff/image_compare"
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module CapybaraScreenshotDiff
         
     | 
| 
      
 6 
     | 
    
         
            +
              class AttemptsReporter
         
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(snapshot, comparison_options, stability_options = {})
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @snapshot = snapshot
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @comparison_options = comparison_options
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @wait = stability_options[:wait]
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def generate
         
     | 
| 
      
 14 
     | 
    
         
            +
                  attempts_screenshot_paths = @snapshot.find_attempts_paths
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  annotate_attempts(attempts_screenshot_paths)
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  "Could not get stable screenshot within #{@wait}s:\n#{attempts_screenshot_paths.join("\n")}"
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
                def build_comparison_for(attempt_path, previous_attempt_path)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  Capybara::Screenshot::Diff::ImageCompare.new(attempt_path, previous_attempt_path, @comparison_options)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                private
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def annotate_attempts(attempts_screenshot_paths)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  previous_file = nil
         
     | 
| 
      
 29 
     | 
    
         
            +
                  attempts_screenshot_paths.reverse_each do |file_name|
         
     | 
| 
      
 30 
     | 
    
         
            +
                    if previous_file && File.exist?(previous_file)
         
     | 
| 
      
 31 
     | 
    
         
            +
                      attempts_comparison = build_comparison_for(file_name, previous_file)
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                      if attempts_comparison.different?
         
     | 
| 
      
 34 
     | 
    
         
            +
                        FileUtils.mv(attempts_comparison.reporter.annotated_base_image_path, previous_file, force: true)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      else
         
     | 
| 
      
 36 
     | 
    
         
            +
                        warn "[capybara-screenshot-diff] Some attempts was stable, but mistakenly marked as not: " \
         
     | 
| 
      
 37 
     | 
    
         
            +
                               "#{previous_file} and #{file_name} are equal"
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                      FileUtils.rm(attempts_comparison.reporter.annotated_image_path, force: true)
         
     | 
| 
      
 41 
     | 
    
         
            +
                    end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                    previous_file = file_name
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  previous_file
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -14,6 +14,7 @@ end 
     | 
|
| 
       14 
14 
     | 
    
         | 
| 
       15 
15 
     | 
    
         
             
            RSpec.configure do |config|
         
     | 
| 
       16 
16 
     | 
    
         
             
              config.include ::CapybaraScreenshotDiff::DSL, type: :feature
         
     | 
| 
      
 17 
     | 
    
         
            +
              config.include ::CapybaraScreenshotDiff::DSL, type: :system
         
     | 
| 
       17 
18 
     | 
    
         | 
| 
       18 
19 
     | 
    
         
             
              config.after do
         
     | 
| 
       19 
20 
     | 
    
         
             
                if self.class.include?(::CapybaraScreenshotDiff::DSL) && ::Capybara::Screenshot.active?
         
     | 
| 
         @@ -0,0 +1,55 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module CapybaraScreenshotDiff
         
     | 
| 
      
 4 
     | 
    
         
            +
              class Snap
         
     | 
| 
      
 5 
     | 
    
         
            +
                attr_reader :full_name, :format, :path, :base_path, :manager, :attempt_path, :prev_attempt_path, :attempts_count
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                def initialize(full_name, format, manager: SnapManager.instance)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @full_name = full_name
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @format = format
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @path = manager.abs_path_for(Pathname.new(@full_name).sub_ext(".#{@format}"))
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @base_path = @path.sub_ext(".base.#{@format}")
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @manager = manager
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @attempts_count = 0
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def delete!
         
     | 
| 
      
 17 
     | 
    
         
            +
                  path.delete if path.exist?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  base_path.delete if base_path.exist?
         
     | 
| 
      
 19 
     | 
    
         
            +
                  cleanup_attempts
         
     | 
| 
      
 20 
     | 
    
         
            +
                end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                def checkout_base_screenshot
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @manager.checkout_file(path, base_path)
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def path_for(version = :actual)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  case version
         
     | 
| 
      
 28 
     | 
    
         
            +
                  when :base
         
     | 
| 
      
 29 
     | 
    
         
            +
                    base_path
         
     | 
| 
      
 30 
     | 
    
         
            +
                  else
         
     | 
| 
      
 31 
     | 
    
         
            +
                    path
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def next_attempt_path!
         
     | 
| 
      
 36 
     | 
    
         
            +
                  @prev_attempt_path = @attempt_path
         
     | 
| 
      
 37 
     | 
    
         
            +
                  @attempt_path = path.sub_ext(sprintf(".attempt_%02i.#{format}", @attempts_count))
         
     | 
| 
      
 38 
     | 
    
         
            +
                ensure
         
     | 
| 
      
 39 
     | 
    
         
            +
                  @attempts_count += 1
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                def commit_last_attempt
         
     | 
| 
      
 43 
     | 
    
         
            +
                  @manager.move(attempt_path, path)
         
     | 
| 
      
 44 
     | 
    
         
            +
                end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                def cleanup_attempts
         
     | 
| 
      
 47 
     | 
    
         
            +
                  @manager.cleanup_attempts!(self)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @attempts_count = 0
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def find_attempts_paths
         
     | 
| 
      
 52 
     | 
    
         
            +
                  Dir[@manager.abs_path_for "**/#{full_name}.attempt_*.#{format}"]
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,76 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "capybara/screenshot/diff/vcs"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "active_support/core_ext/module/attribute_accessors"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            require "capybara_screenshot_diff/snap"
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module CapybaraScreenshotDiff
         
     | 
| 
      
 9 
     | 
    
         
            +
              class SnapManager
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :root
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                def initialize(root)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @root = Pathname.new(root)
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                def snapshot(screenshot_full_name, screenshot_format = "png")
         
     | 
| 
      
 17 
     | 
    
         
            +
                  Snap.new(screenshot_full_name, screenshot_format, manager: self)
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                def self.snapshot(screenshot_full_name, screenshot_format = "png")
         
     | 
| 
      
 21 
     | 
    
         
            +
                  instance.snapshot(screenshot_full_name, screenshot_format)
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def abs_path_for(relative_path)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @root / relative_path
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def checkout_file(path, as_path)
         
     | 
| 
      
 29 
     | 
    
         
            +
                  create_output_directory_for(as_path) unless as_path.exist?
         
     | 
| 
      
 30 
     | 
    
         
            +
                  Capybara::Screenshot::Diff::Vcs.checkout_vcs(root, path, as_path)
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                def provision_snap_with(snap, path, version: :actual)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  managed_path = snap.path_for(version)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  create_output_directory_for(managed_path) unless managed_path.exist?
         
     | 
| 
      
 36 
     | 
    
         
            +
                  FileUtils.cp(path, managed_path)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def create_output_directory_for(path = nil)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  path ? path.dirname.mkpath : root.mkpath
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                # TODO: rename to delete!
         
     | 
| 
      
 44 
     | 
    
         
            +
                def cleanup!
         
     | 
| 
      
 45 
     | 
    
         
            +
                  FileUtils.rm_rf root, secure: true
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                def self.cleanup!
         
     | 
| 
      
 49 
     | 
    
         
            +
                  instance.cleanup!
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                def cleanup_attempts!(snapshot)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  FileUtils.rm_rf snapshot.find_attempts_paths, secure: true
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def move(new_screenshot_path, screenshot_path)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  FileUtils.mv(new_screenshot_path, screenshot_path, force: true)
         
     | 
| 
      
 58 
     | 
    
         
            +
                end
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                def screenshots
         
     | 
| 
      
 61 
     | 
    
         
            +
                  root.children.map { |f| f.basename.to_s }
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                def self.screenshots
         
     | 
| 
      
 65 
     | 
    
         
            +
                  instance.screenshots
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                def self.root
         
     | 
| 
      
 69 
     | 
    
         
            +
                  instance.root
         
     | 
| 
      
 70 
     | 
    
         
            +
                end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                def self.instance
         
     | 
| 
      
 73 
     | 
    
         
            +
                  Capybara::Screenshot::Diff.manager.new(Capybara::Screenshot.screenshot_area_abs)
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
              end
         
     | 
| 
      
 76 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -4,9 +4,9 @@ require "capybara/dsl" 
     | 
|
| 
       4 
4 
     | 
    
         
             
            require "capybara/screenshot/diff/version"
         
     | 
| 
       5 
5 
     | 
    
         
             
            require "capybara/screenshot/diff/utils"
         
     | 
| 
       6 
6 
     | 
    
         
             
            require "capybara/screenshot/diff/image_compare"
         
     | 
| 
      
 7 
     | 
    
         
            +
            require "capybara_screenshot_diff/snap_manager"
         
     | 
| 
       7 
8 
     | 
    
         
             
            require "capybara/screenshot/diff/test_methods"
         
     | 
| 
       8 
9 
     | 
    
         
             
            require "capybara/screenshot/diff/screenshoter"
         
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
10 
     | 
    
         
             
            require "capybara/screenshot/diff/reporters/default"
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
       12 
12 
     | 
    
         
             
            module CapybaraScreenshotDiff
         
     | 
| 
         @@ -20,7 +20,7 @@ module Capybara 
     | 
|
| 
       20 
20 
     | 
    
         
             
                mattr_accessor :blur_active_element
         
     | 
| 
       21 
21 
     | 
    
         
             
                mattr_accessor :enabled
         
     | 
| 
       22 
22 
     | 
    
         
             
                mattr_accessor :hide_caret
         
     | 
| 
       23 
     | 
    
         
            -
                mattr_reader(:root) { (defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
         
     | 
| 
      
 23 
     | 
    
         
            +
                mattr_reader(:root) { (defined?(Rails) && defined?(Rails.root) && Rails.root) || Pathname(".").expand_path }
         
     | 
| 
       24 
24 
     | 
    
         
             
                mattr_accessor :stability_time_limit
         
     | 
| 
       25 
25 
     | 
    
         
             
                mattr_accessor :window_size
         
     | 
| 
       26 
26 
     | 
    
         
             
                mattr_accessor(:save_path) { "doc/screenshots" }
         
     | 
| 
         @@ -63,6 +63,7 @@ module Capybara 
     | 
|
| 
       63 
63 
     | 
    
         
             
                  mattr_accessor :tolerance
         
     | 
| 
       64 
64 
     | 
    
         | 
| 
       65 
65 
     | 
    
         
             
                  mattr_accessor(:screenshoter) { Screenshoter }
         
     | 
| 
      
 66 
     | 
    
         
            +
                  mattr_accessor(:manager) { CapybaraScreenshotDiff::SnapManager }
         
     | 
| 
       66 
67 
     | 
    
         | 
| 
       67 
68 
     | 
    
         
             
                  AVAILABLE_DRIVERS = Utils.detect_available_drivers.freeze
         
     | 
| 
       68 
69 
     | 
    
         | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: capybara-screenshot-diff
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 1.9. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.9.2
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Uwe Kubosch
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2024- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2024-11-20 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: actionpack
         
     | 
| 
         @@ -19,7 +19,7 @@ dependencies: 
     | 
|
| 
       19 
19 
     | 
    
         
             
                    version: '6.1'
         
     | 
| 
       20 
20 
     | 
    
         
             
                - - "<"
         
     | 
| 
       21 
21 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       22 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 22 
     | 
    
         
            +
                    version: '9'
         
     | 
| 
       23 
23 
     | 
    
         
             
              type: :runtime
         
     | 
| 
       24 
24 
     | 
    
         
             
              prerelease: false
         
     | 
| 
       25 
25 
     | 
    
         
             
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -29,7 +29,7 @@ dependencies: 
     | 
|
| 
       29 
29 
     | 
    
         
             
                    version: '6.1'
         
     | 
| 
       30 
30 
     | 
    
         
             
                - - "<"
         
     | 
| 
       31 
31 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       32 
     | 
    
         
            -
                    version: ' 
     | 
| 
      
 32 
     | 
    
         
            +
                    version: '9'
         
     | 
| 
       33 
33 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       34 
34 
     | 
    
         
             
              name: capybara
         
     | 
| 
       35 
35 
     | 
    
         
             
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
         @@ -84,10 +84,13 @@ files: 
     | 
|
| 
       84 
84 
     | 
    
         
             
            - lib/capybara/screenshot/diff/vcs.rb
         
     | 
| 
       85 
85 
     | 
    
         
             
            - lib/capybara/screenshot/diff/version.rb
         
     | 
| 
       86 
86 
     | 
    
         
             
            - lib/capybara_screenshot_diff.rb
         
     | 
| 
      
 87 
     | 
    
         
            +
            - lib/capybara_screenshot_diff/attempts_reporter.rb
         
     | 
| 
       87 
88 
     | 
    
         
             
            - lib/capybara_screenshot_diff/cucumber.rb
         
     | 
| 
       88 
89 
     | 
    
         
             
            - lib/capybara_screenshot_diff/dsl.rb
         
     | 
| 
       89 
90 
     | 
    
         
             
            - lib/capybara_screenshot_diff/minitest.rb
         
     | 
| 
       90 
91 
     | 
    
         
             
            - lib/capybara_screenshot_diff/rspec.rb
         
     | 
| 
      
 92 
     | 
    
         
            +
            - lib/capybara_screenshot_diff/snap.rb
         
     | 
| 
      
 93 
     | 
    
         
            +
            - lib/capybara_screenshot_diff/snap_manager.rb
         
     | 
| 
       91 
94 
     | 
    
         
             
            homepage: https://github.com/donv/capybara-screenshot-diff
         
     | 
| 
       92 
95 
     | 
    
         
             
            licenses:
         
     | 
| 
       93 
96 
     | 
    
         
             
            - MIT
         
     | 
| 
         @@ -108,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement 
     | 
|
| 
       108 
111 
     | 
    
         
             
                - !ruby/object:Gem::Version
         
     | 
| 
       109 
112 
     | 
    
         
             
                  version: '0'
         
     | 
| 
       110 
113 
     | 
    
         
             
            requirements: []
         
     | 
| 
       111 
     | 
    
         
            -
            rubygems_version: 3.5. 
     | 
| 
      
 114 
     | 
    
         
            +
            rubygems_version: 3.5.22
         
     | 
| 
       112 
115 
     | 
    
         
             
            signing_key: 
         
     | 
| 
       113 
116 
     | 
    
         
             
            specification_version: 4
         
     | 
| 
       114 
117 
     | 
    
         
             
            summary: Track your GUI changes with diff assertions
         
     |