dotdiff 2.0.3 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b02f39b117e8c2c17d6b4aef801e89d41275d87d
4
- data.tar.gz: 9a9b9ecc0d6e210514aa551adc2a06727d9d6bf6
3
+ metadata.gz: c869fc9b85965913b4ba033845431a45f5a32f0b
4
+ data.tar.gz: db555b8adab6b02ac434cd6d490acf905a02b765
5
5
  SHA512:
6
- metadata.gz: d2b4b0a9ba06188098cd616794bcb090cfd09019b8258371dda16103cf6f24d63ffc27374acfeef389225b82afe16623ca1ffb96ff154461003a894fa91b6e08
7
- data.tar.gz: fdb81120916a27326dc3b22ae5c6b27fcf6e2fa054f07076822a6171a1b57334a97fb38b1ea40c58de47763591ac63309431d3229f671952278313dc8ff40eea
6
+ metadata.gz: a7f2639b01603bfa39c611bbc04b464880b0e3332d510ea369c9b9b42da0f2cf3ca722d8612e4390f7d4e7d75674069a64773198fe41acf75b86cd2eb6962bad
7
+ data.tar.gz: b4a57a11512cf4ee9fa8f03f8bbedd8af8f729beb4d92fd0c27800c8a9ffc977bc7673397eb24c359273d2e76ee6a369f20de284006c2abd7cba18963102ac67
data/.rubocop.yml ADDED
@@ -0,0 +1,31 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
3
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
4
+ # configuration file. It makes it possible to enable/disable
5
+ # certain cops (checks) and to alter their behavior if they accept
6
+ # any parameters. The file can be placed either in your home
7
+ # directory or in some project directory.
8
+ #
9
+ # RuboCop will start looking for the configuration file in the directory
10
+ # where the inspected file is and continue its way up to the root directory.
11
+ #
12
+ # See https://github.com/rubocop-hq/rubocop/blob/master/manual/configuration.md
13
+ AllCops:
14
+ NewCops: enable
15
+ TargetRubyVersion: 2.4
16
+ SuggestExtensions: false
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+
21
+ Metrics/BlockLength:
22
+ ExcludedMethods:
23
+ - describe
24
+ - context
25
+ Exclude:
26
+ - 'spec/unit/snapshot_spec.rb'
27
+
28
+ Layout/LineLength:
29
+ Max: 120
30
+ IgnoredPatterns:
31
+ - !ruby/regexp /\A +(it|describe|context|shared_examples|include_examples|it_behaves_like) ["']/
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,17 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2021-03-13 18:24:12 +0000 using RuboCop version 0.84.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: IgnoredMethods.
11
+ Metrics/AbcSize:
12
+ Max: 17
13
+
14
+ # Offense count: 1
15
+ # Configuration parameters: CountComments, ExcludedMethods.
16
+ Metrics/MethodLength:
17
+ Max: 14
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in dotdiff.gemspec
data/README.md CHANGED
@@ -51,12 +51,12 @@ In an initializer you can configure certain options example shown below within D
51
51
  ```ruby
52
52
  DotDiff.configure do |config|
53
53
  config.image_magick_diff_bin = `which compare`
54
- config.pixel_threshold = 120
54
+ config.pixel_threshold = { type: 'percent', value: 0.04 }
55
55
  config.image_magick_options = '-metric AE'
56
56
  config.image_store_path = File.join('/home', 'user', 'images')
57
57
  config.xpath_elements_to_hide = ["id('main')"]
58
+ config.hide_elements_on_non_full_screen_screenshot = true
58
59
  config.failure_image_path = File.join('/home', 'user', 'failure_comparisions')
59
- config.max_wait_time = 2
60
60
  end
61
61
  ```
62
62
 
@@ -83,9 +83,9 @@ The only difference for the element specific is passing a specific element in th
83
83
  | image_magick_diff_bin | Location of the image magick compare binary file | `which compare` | N/A | Yes |
84
84
  | image_store_path | The root path to store the base images | File.join('/home', 'user','images') | nil | Yes |
85
85
  | xpath_elements_to_hide | When taking full page screenshots it will hide elements specified that might change each time and re-shows them after the screenshot. It doesn't use this option for taking screenshots of specific elements. | ["id('main')", "//div[contains(@class, 'formy'])[1]"] | [] | No |
86
+ | hide_elements_on_non_full_screen_screenshot | When taking non full page screenshots whether to also hide elements using `xpath_elements_to_hide` or not hide anything at all | true | false | No |
86
87
  | failure_image_path | When a comparison occurs and the perceptual_diff binary returns a failure with the message. It will dump the new image taken for comparison to this directory. If not supplied it will not move the images from the temporary location that it is generated at. | File.join('/home', 'user','failures') | nil | No |
87
- | max_wait_time | This is similar to the Capybara#default_max_wait_time if you have a high value such as 10, as its possible that the global xpath_elements_to_hide might not always exist it will wait the full time - therefore you can drop it for the hiding and showing of the elements. In this example it would wait up 20 seconds in total 10 for hiding and 10 seconds for re-showing - that is if the element isn't even going to be present on the page. | 2 | Capybara#default_max_wait_time | No |
88
- | pixel_threshold | This validates the output from compare is within your specified threshold | 120 | 100 | No |
88
+ | pixel_threshold | This validates the output from compare is within your specified threshold_config which supports pixel or percent value | { type: 'percent', value: 0.03 } | { type: 'pixel', value: 100 } | No |
89
89
  | image_magick_options | This allows you to pass some custom options to image magick | '-fuzz 10% -metric RSME' | '-fuzz 5% -metric AE' | No |
90
90
 
91
91
  ## Contributing
data/Rakefile CHANGED
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "dotdiff"
4
+ require 'bundler/setup'
5
+ require 'dotdiff'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "dotdiff"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start
data/dotdiff.gemspec CHANGED
@@ -1,35 +1,31 @@
1
- # coding: utf-8
2
- lib = File.expand_path('../lib', __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
5
  require 'dotdiff/version'
5
6
 
6
- is_java = RUBY_PLATFORM == 'java'
7
-
8
7
  Gem::Specification.new do |spec|
9
- spec.name = "dotdiff"
8
+ spec.name = 'dotdiff'
10
9
  spec.version = DotDiff::VERSION
11
- spec.authors = ["Jon Normington"]
12
- spec.email = ["jnormington@users.noreply.github.com"]
13
- spec.platform = 'java' if is_java
10
+ spec.authors = ['Jon Normington']
11
+ spec.email = ['jnormington@users.noreply.github.com']
14
12
 
15
- spec.summary = "Image regression wrapper for Capybara and RSpec using image magick supporting both MRI and JRuby versions"
16
- spec.description = [spec.summary, "which supports snap shoting both full page and specific elements on a page where text checks isn't enough"].join(' ')
17
- spec.homepage = "https://github.com/jnormington/dotdiff"
18
- spec.license = "MIT"
13
+ spec.summary = 'Image regression wrapper for Capybara and RSpec using image'\
14
+ 'magick supporting both MRI and JRuby versions'
15
+ spec.description = [spec.summary, 'which supports snap shoting both full page and'\
16
+ "specific elements on a page where text checks isn't enough"].join(' ')
17
+ spec.homepage = 'https://github.com/jnormington/dotdiff'
18
+ spec.license = 'MIT'
19
19
 
20
20
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
- spec.bindir = "exe"
21
+ spec.bindir = 'exe'
22
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
23
+ spec.require_paths = ['lib']
24
24
 
25
- if is_java
26
- spec.add_runtime_dependency "rmagick4j", '~> 0.4.0'
27
- else
28
- spec.add_runtime_dependency "rmagick", '~> 2.15'
29
- end
25
+ spec.add_runtime_dependency 'mini_magick', '>= 4.11.0'
30
26
 
31
- spec.add_development_dependency "bundler", "~> 1.11"
32
- spec.add_development_dependency "rake", "~> 10.0"
33
- spec.add_development_dependency "rspec", "~> 3.0"
34
- spec.add_development_dependency "capybara", "~> 2.6"
27
+ spec.add_development_dependency 'bundler', '>= 2'
28
+ spec.add_development_dependency 'capybara', '>= 2.6'
29
+ spec.add_development_dependency 'rake', '>= 12.3.3'
30
+ spec.add_development_dependency 'rspec', '>= 3.0'
35
31
  end
@@ -1,26 +1,20 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'shellwords'
2
4
 
3
5
  module DotDiff
4
6
  class CommandWrapper
5
- attr_reader :message
7
+ attr_reader :message, :pixels
6
8
 
7
9
  def run(base_image, new_image, diff_image_path)
8
10
  output = run_command(base_image, new_image, diff_image_path)
9
-
10
- @ran_checks = true
11
+ @message = output
11
12
 
12
13
  begin
13
- pixels = Float(output)
14
-
15
- if pixels && pixels <= DotDiff.pixel_threshold
16
- @failed = false
17
- else
18
- @failed = true
19
- @message = "Images are #{pixels} pixels different"
20
- end
21
- rescue ArgumentError => e
14
+ @pixels = Float(output)
15
+ @failed = false
16
+ rescue ArgumentError
22
17
  @failed = true
23
- @message = output
24
18
  end
25
19
  end
26
20
 
@@ -29,16 +23,11 @@ module DotDiff
29
23
  end
30
24
 
31
25
  def failed?
32
- @ran_checks && @failed
33
- end
34
-
35
- def ran_checks
36
- @ran_checks
26
+ @failed
37
27
  end
38
28
 
39
29
  private
40
30
 
41
- # For the tests
42
31
  def run_command(base_image, new_image, diff_image_path)
43
32
  `#{command(base_image, new_image, diff_image_path)}`.strip
44
33
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DotDiff
2
4
  class Comparer
3
5
  attr_reader :element, :page, :snapshot
@@ -9,50 +11,31 @@ module DotDiff
9
11
  end
10
12
 
11
13
  def result
12
- if element.is_a?(Capybara::Session)
13
- compare_page
14
- elsif element.is_a?(Capybara::Node::Base)
15
- compare_element
16
- else
14
+ comparer = build_comparer
15
+
16
+ if comparer.nil?
17
17
  raise ArgumentError, "Unknown element class received: #{element.class.name}"
18
18
  end
19
- end
20
-
21
- private
22
19
 
23
- def compare_element(element_meta = ElementMeta.new(page, element))
24
- snapshot.capture_from_browser(false, nil)
25
- snapshot.crop_and_resave(element_meta)
20
+ passed, msg = comparer.run
21
+ write_failure_imgs(comparer.new_image_path) if !passed && DotDiff.failure_image_path
26
22
 
27
- if !File.exists?(snapshot.basefile)
28
- snapshot.resave_cropped_file
29
- [true, snapshot.basefile]
30
- else
31
- compare(snapshot.cropped_file)
32
- end
23
+ [passed, msg]
33
24
  end
34
25
 
35
- def compare_page
36
- snapshot.capture_from_browser
26
+ private
37
27
 
38
- if !File.exists?(snapshot.basefile)
39
- snapshot.resave_fullscreen_file
40
- [true, snapshot.basefile]
41
- else
42
- compare(snapshot.fullscreen_file)
43
- end
28
+ def write_failure_imgs(new_image_path)
29
+ FileUtils.mkdir_p(snapshot.failure_path)
30
+ FileUtils.mv(new_image_path, snapshot.new_file, force: true)
44
31
  end
45
32
 
46
- def compare(compare_to_image)
47
- result = CommandWrapper.new
48
- result.run(snapshot.basefile, compare_to_image, snapshot.diff_file)
49
-
50
- if result.failed? && DotDiff.failure_image_path
51
- FileUtils.mkdir_p(snapshot.failure_path)
52
- FileUtils.mv(compare_to_image, snapshot.new_file, force: true)
33
+ def build_comparer
34
+ if element.is_a?(Capybara::Session)
35
+ DotDiff::Comparible::PageComparer.new(snapshot, nil)
36
+ elsif element.is_a?(Capybara::Node::Base)
37
+ DotDiff::Comparible::ElementComparer.new(snapshot, ElementMeta.new(page, element))
53
38
  end
54
-
55
- [result.passed?, result.message]
56
39
  end
57
40
  end
58
41
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DotDiff
4
+ module Comparible
5
+ class Base
6
+ def initialize(snapshot, element_meta)
7
+ @snapshot = snapshot
8
+ @element_meta = element_meta
9
+ end
10
+
11
+ def self.run(snapshot, element_meta)
12
+ new(snapshot, element_meta).run
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :snapshot, :element_meta
18
+
19
+ def compare(compare_to_image)
20
+ return [false, img_container.dimensions_mismatch_msg] unless img_container.both_images_same_dimensions?
21
+
22
+ cmd = CommandWrapper.new
23
+ cmd.run(snapshot.basefile, new_image_path, snapshot.diff_file)
24
+ return [cmd.passed?, cmd.message] if cmd.failed?
25
+
26
+ calculate_result(cmd.pixels)
27
+ end
28
+
29
+ def calculate_result(diff_pixels)
30
+ calc = DotDiff::ThresholdCalculator.new(
31
+ DotDiff.pixel_threshold,
32
+ img_container.total_pixels,
33
+ diff_pixels
34
+ )
35
+
36
+ [calc.under_threshold?, calc.message]
37
+ end
38
+
39
+ def img_container
40
+ @img_container ||= DotDiff::Image::Container.new(
41
+ snapshot.basefile,
42
+ new_image_path
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DotDiff
4
+ module Comparible
5
+ class ElementComparer < Base
6
+ def run
7
+ take_snapshot_and_crop
8
+
9
+ if !File.exist?(snapshot.basefile)
10
+ snapshot.resave_cropped_file
11
+ [true, snapshot.basefile]
12
+ else
13
+ compare(snapshot.cropped_file)
14
+ end
15
+ end
16
+
17
+ def new_image_path
18
+ snapshot.cropped_file
19
+ end
20
+
21
+ private
22
+
23
+ def take_snapshot_and_crop
24
+ snapshot.capture_from_browser(DotDiff.hide_elements_on_non_full_screen_screenshot)
25
+ snapshot.crop_and_resave(element_meta)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DotDiff
4
+ module Comparible
5
+ class PageComparer < Base
6
+ def run
7
+ snapshot.capture_from_browser(true)
8
+
9
+ if !File.exist?(snapshot.basefile)
10
+ snapshot.resave_fullscreen_file
11
+ [true, snapshot.basefile]
12
+ else
13
+ compare(snapshot.fullscreen_file)
14
+ end
15
+ end
16
+
17
+ def new_image_path
18
+ snapshot.fullscreen_file
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DotDiff
2
4
  class ElementHandler
3
5
  attr_accessor :driver
@@ -9,27 +11,28 @@ module DotDiff
9
11
 
10
12
  def hide
11
13
  elements.each do |xpath|
12
- if element_exists?(xpath)
13
- driver.execute_script("#{js_element(xpath)}.style.visibility = 'hidden'")
14
- end
14
+ driver.execute_script(script(xpath, :hidden))
15
15
  end
16
16
  end
17
17
 
18
18
  def show
19
19
  elements.each do |xpath|
20
- if element_exists?(xpath)
21
- driver.execute_script("#{js_element(xpath)}.style.visibility = ''")
22
- end
20
+ driver.execute_script(script(xpath, :''))
23
21
  end
24
22
  end
25
23
 
26
- def js_element(xpath)
27
- "document.evaluate(\"#{xpath}\", "\
28
- "document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue"
29
- end
24
+ def script(xpath, visibility)
25
+ xpath += if visibility == :hidden
26
+ "[not(contains(@style, 'visibility'))]"
27
+ else
28
+ "[contains(@style, 'visibility: hidden')]"
29
+ end
30
30
 
31
- def element_exists?(xpath)
32
- driver.find(:xpath, xpath, wait: DotDiff.max_wait_time, visible: :all) rescue nil
31
+ # this is done like so instead of a single pass over all elements due to a bug in Firefox:
32
+ # https://greasyfork.org/en/forum/discussion/12223/xpath-iteratenext-fails-in-firefox
33
+ "var elem; while (elem = document.evaluate(\"#{xpath}\", document, "\
34
+ 'null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null).iterateNext()) '\
35
+ "{ elem.style.visibility = '#{visibility}'; }"
33
36
  end
34
37
 
35
38
  def elements
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DotDiff
2
4
  class ElementMeta
3
5
  attr_reader :page, :element_xpath
@@ -19,22 +21,26 @@ module DotDiff
19
21
  end
20
22
 
21
23
  def method_missing(name, *args, &block)
22
- if %w(x y width height).include?(name.to_s)
24
+ if %w[x y width height].include?(name.to_s)
23
25
  case name
24
- when :x then rect['left']
25
- when :y then rect['top']
26
- else rect[name.to_s]
26
+ when :x then rect['left']
27
+ when :y then rect['top']
28
+ else rect[name.to_s]
27
29
  end
28
30
  else
29
31
  super
30
32
  end
31
33
  end
32
34
 
35
+ def respond_to_missing?(name, _include_private = false)
36
+ %w[x y width height].include?(name.to_s) || super
37
+ end
38
+
33
39
  private
34
40
 
35
41
  def js_query(xpath)
36
42
  "document.evaluate(\"#{xpath}\", document, null, XPathResult."\
37
- "FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect()"
43
+ 'FIRST_ORDERED_NODE_TYPE, null).singleNodeValue.getBoundingClientRect()'
38
44
  end
39
45
 
40
46
  def get_rect(page, xpath)
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DotDiff
4
+ module Image
5
+ class Container
6
+ def initialize(baseimg_file, newimg_file)
7
+ @baseimg_file = baseimg_file
8
+ @newimg_file = newimg_file
9
+ end
10
+
11
+ def both_images_same_dimensions?
12
+ base_image.width == new_image.width &&
13
+ base_image.height == new_image.height
14
+ end
15
+
16
+ def total_pixels
17
+ base_image.width * base_image.height
18
+ end
19
+
20
+ def dimensions_mismatch_msg
21
+ <<~MSG
22
+ Images are not the same dimensions to be compared
23
+ Base file: #{base_image.width}x#{base_image.height}
24
+ New file: #{new_image.width}x#{new_image.height}
25
+ MSG
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :baseimg_file, :newimg_file
31
+
32
+ def base_image
33
+ @base_image ||= MiniMagick::Image.open(baseimg_file)
34
+ end
35
+
36
+ def new_image
37
+ @new_image ||= MiniMagick::Image.open(newimg_file)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,27 +1,31 @@
1
- require 'rmagick'
1
+ # frozen_string_literal: true
2
+
3
+ require 'mini_magick'
2
4
 
3
5
  module DotDiff
4
6
  module Image
5
7
  module Cropper
6
8
  def crop_and_resave(element)
7
9
  image = load_image(fullscreen_file)
8
- image.crop!(
9
- element.rectangle.x,
10
- element.rectangle.y,
11
- width(element, image),
12
- height(element, image)
13
- )
14
10
 
11
+ # @see http://www.imagemagick.org/script/command-line-options.php?#crop
12
+ crop_area =
13
+ '' + width(element, image).to_s +
14
+ 'x' + height(element, image).to_s +
15
+ '+' + element.rectangle.x.floor.to_s +
16
+ '+' + element.rectangle.y.floor.to_s
17
+
18
+ image.crop crop_area
15
19
  image.write(cropped_file)
16
20
  end
17
21
 
18
22
  def load_image(file)
19
- Magick::Image.read(file).first
23
+ MiniMagick::Image.open(file)
20
24
  end
21
25
 
22
26
  def height(element, image)
23
27
  element_height = element.rectangle.height + element.rectangle.y
24
- image_height = image.rows
28
+ image_height = image.height
25
29
 
26
30
  if element_height > image_height
27
31
  image_height - element.rectangle.y
@@ -32,7 +36,7 @@ module DotDiff
32
36
 
33
37
  def width(element, image)
34
38
  element_width = element.rectangle.width + element.rectangle.x
35
- image_width = image.columns
39
+ image_width = image.width
36
40
 
37
41
  if element_width > image_width
38
42
  image_width - element.rectangle.x
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rspec/expectations'
2
4
 
3
5
  RSpec::Matchers.define :match_image do |filename, opts = {}|
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DotDiff
2
4
  class Snapshot
3
5
  include Image::Cropper
4
6
 
5
- attr_reader :base_filename, :subdir, :rootdir, :page, :use_custom_screenshot
7
+ attr_reader :subdir, :rootdir, :page, :use_custom_screenshot
6
8
 
7
- IMAGE_EXT = 'png'.freeze
9
+ IMAGE_EXT = 'png'
8
10
 
9
11
  def initialize(options = {})
10
12
  opts = { rootdir: DotDiff.image_store_path }.merge(Hash(options))
@@ -12,23 +14,23 @@ module DotDiff
12
14
  @subdir = opts[:subdir].to_s
13
15
  @rootdir = opts[:rootdir].to_s
14
16
  @page = opts[:page]
15
- @fullscreen = opts[:fullscreen_file]
17
+ @fullscreen_file = opts[:fullscreen_file]
16
18
  @use_custom_screenshot = opts[:use_custom_screenshot]
17
19
  end
18
20
 
19
21
  def fullscreen_file
20
- @fullscreen ||= File.join(Dir.tmpdir, subdir, base_filename)
22
+ @fullscreen_file ||= File.join(Dir.tmpdir, subdir, base_filename)
21
23
  end
22
24
 
23
25
  def cropped_file
24
- @cropped ||= File.join(Dir.tmpdir, subdir, "#{base_filename(false)}_cropped.#{IMAGE_EXT}")
26
+ @cropped_file ||= File.join(Dir.tmpdir, subdir, "#{base_filename(false)}_cropped.#{IMAGE_EXT}")
25
27
  end
26
28
 
27
29
  def basefile
28
30
  File.join(rootdir, subdir.to_s, base_filename)
29
31
  end
30
32
 
31
- def base_filename(with_extension=true)
33
+ def base_filename(with_extension = true)
32
34
  filename = File.basename(@base_filename)
33
35
  extension = File.extname(filename)
34
36
  rtn_file = @base_filename
@@ -47,17 +49,19 @@ module DotDiff
47
49
  end
48
50
 
49
51
  def new_file
50
- File.join(failure_path, "#{base_filename(false)}.new.#{IMAGE_EXT}" )
52
+ File.join(failure_path, "#{base_filename(false)}.new.#{IMAGE_EXT}")
51
53
  end
52
54
 
53
55
  def diff_file
54
56
  File.join(failure_path, "#{base_filename(false)}.diff.#{IMAGE_EXT}")
55
57
  end
56
58
 
57
- def capture_from_browser(hide_and_show = true, element_handler = ElementHandler.new(page))
59
+ def capture_from_browser(hide_and_show)
58
60
  return fullscreen_file if use_custom_screenshot
59
61
 
60
62
  if hide_and_show
63
+ element_handler = ElementHandler.new(page)
64
+
61
65
  element_handler.hide
62
66
  page.save_screenshot(fullscreen_file)
63
67
  element_handler.show
@@ -79,10 +83,10 @@ module DotDiff
79
83
  def resave_base_file(version)
80
84
  FileUtils.mkdir_p(File.join(DotDiff.image_store_path, subdir))
81
85
 
82
- if !File.exists?(basefile) || DotDiff.overwrite_on_resave
83
- FileUtils.mv(self.send("#{version}_file"), basefile, force: true)
86
+ if !File.exist?(basefile) || DotDiff.overwrite_on_resave
87
+ FileUtils.mv(send("#{version}_file"), basefile, force: true)
84
88
  else
85
- FileUtils.mv(self.send("#{version}_file"), "#{basefile}.r2", force: true)
89
+ FileUtils.mv(send("#{version}_file"), "#{basefile}.r2", force: true)
86
90
  end
87
91
  end
88
92
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DotDiff
4
+ class ThresholdCalculator
5
+ PIXEL = 'pixel'
6
+ PERCENT = 'percent'
7
+
8
+ def initialize(threshold_config, total_pixels, pixel_diff)
9
+ @threshold_config = threshold_config
10
+ @total_pixels = total_pixels
11
+ @pixel_diff = pixel_diff
12
+ end
13
+
14
+ def under_threshold?
15
+ return false if total_pixels.nil? || pixel_diff.nil?
16
+
17
+ case threshold_type
18
+ when PIXEL
19
+ @value = pixel_diff
20
+ when PERCENT
21
+ @value = pixel_diff / total_pixels.to_f
22
+ else
23
+ raise UnknownTypeError, "Unable to handle threshold type: #{threshold_type}"
24
+ end
25
+
26
+ value <= threshold_value
27
+ end
28
+
29
+ def message
30
+ "Outcome was '#{value}' difference for type '#{threshold_type}'"
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :threshold_config, :pixel_diff, :total_pixels, :value
36
+
37
+ def threshold_value
38
+ return threshold_config if threshold_config.class == Integer
39
+
40
+ threshold_config[:value]
41
+ end
42
+
43
+ def threshold_type
44
+ return PIXEL if threshold_config.class != Hash
45
+
46
+ threshold_config[:type].to_s
47
+ end
48
+ end
49
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DotDiff
2
- VERSION = "2.0.3"
4
+ VERSION = '4.0.0'
3
5
  end
data/lib/dotdiff.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'dotdiff/version'
2
4
 
3
5
  require 'shellwords'
@@ -9,16 +11,28 @@ require 'dotdiff/element_handler'
9
11
 
10
12
  require 'dotdiff/element_meta'
11
13
  require 'dotdiff/image/cropper'
14
+ require 'dotdiff/image/container'
12
15
  require 'dotdiff/snapshot'
16
+
17
+ require 'dotdiff/threshold_calculator'
18
+
19
+ require 'dotdiff/comparible/base'
20
+ require 'dotdiff/comparible/page_comparer'
21
+ require 'dotdiff/comparible/element_comparer'
22
+
13
23
  require 'dotdiff/comparer'
14
24
 
15
25
  module DotDiff
26
+ class UnknownTypeError < StandardError; end
27
+ class InvalidValueError < StandardError; end
28
+
29
+ SUPPORTED_THRESHOLD_TYPES = %w[pixel percent].freeze
30
+
16
31
  class << self
17
- attr_accessor :resave_base_image, :failure_image_path,
18
- :image_store_path, :overwrite_on_resave, :xpath_elements_to_hide,
19
- :max_wait_time
32
+ attr_accessor :failure_image_path, :image_store_path, :overwrite_on_resave
20
33
 
21
- attr_writer :image_magick_options, :pixel_threshold, :image_magick_diff_bin
34
+ attr_writer :image_magick_options, :image_magick_diff_bin,
35
+ :resave_base_image, :xpath_elements_to_hide, :hide_elements_on_non_full_screen_screenshot
22
36
 
23
37
  def configure
24
38
  yield self
@@ -32,6 +46,10 @@ module DotDiff
32
46
  @xpath_elements_to_hide ||= []
33
47
  end
34
48
 
49
+ def hide_elements_on_non_full_screen_screenshot
50
+ @hide_elements_on_non_full_screen_screenshot ||= false
51
+ end
52
+
35
53
  def image_magick_options
36
54
  @image_magick_options ||= '-fuzz 5% -metric AE'
37
55
  end
@@ -41,11 +59,25 @@ module DotDiff
41
59
  end
42
60
 
43
61
  def pixel_threshold
44
- @pixel_threshold ||= 100
62
+ @pixel_threshold ||= { type: 'pixel', value: 100 }
45
63
  end
46
64
 
47
- def max_wait_time
48
- @max_wait_time || Capybara.default_max_wait_time
65
+ def pixel_threshold=(config)
66
+ unless config.class == Hash
67
+ Kernel.warn '[Dotdiff deprecation] Pass a hash options instead of integer to support pixel/percentage threshold'
68
+ @pixel_threshold = config
69
+ return
70
+ end
71
+
72
+ unless SUPPORTED_THRESHOLD_TYPES.include?(config.fetch(:type))
73
+ raise UnknownTypeError, "Unknown threshold type supports only: #{SUPPORTED_THRESHOLD_TYPES.join(',')}"
74
+ end
75
+
76
+ if config.fetch(:type) == 'percent' && config.fetch(:value) > 1
77
+ raise InvalidValueError, 'Percent value should be a float between 0 and 1'
78
+ end
79
+
80
+ @pixel_threshold = config
49
81
  end
50
82
  end
51
83
  end
metadata CHANGED
@@ -1,87 +1,87 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotdiff
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Normington
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-07 00:00:00.000000000 Z
11
+ date: 2022-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rmagick
14
+ name: mini_magick
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '2.15'
19
+ version: 4.11.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '2.15'
26
+ version: 4.11.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1.11'
33
+ version: '2'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1.11'
40
+ version: '2'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: capybara
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '10.0'
47
+ version: '2.6'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '10.0'
54
+ version: '2.6'
55
55
  - !ruby/object:Gem::Dependency
56
- name: rspec
56
+ name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '3.0'
61
+ version: 12.3.3
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '3.0'
68
+ version: 12.3.3
69
69
  - !ruby/object:Gem::Dependency
70
- name: capybara
70
+ name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '2.6'
75
+ version: '3.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '2.6'
83
- description: Image regression wrapper for Capybara and RSpec using image magick supporting
84
- both MRI and JRuby versions which supports snap shoting both full page and specific
82
+ version: '3.0'
83
+ description: Image regression wrapper for Capybara and RSpec using imagemagick supporting
84
+ both MRI and JRuby versions which supports snap shoting both full page andspecific
85
85
  elements on a page where text checks isn't enough
86
86
  email:
87
87
  - jnormington@users.noreply.github.com
@@ -91,6 +91,8 @@ extra_rdoc_files: []
91
91
  files:
92
92
  - ".gitignore"
93
93
  - ".rspec"
94
+ - ".rubocop.yml"
95
+ - ".rubocop_todo.yml"
94
96
  - Gemfile
95
97
  - LICENSE
96
98
  - README.md
@@ -101,11 +103,16 @@ files:
101
103
  - lib/dotdiff.rb
102
104
  - lib/dotdiff/command_wrapper.rb
103
105
  - lib/dotdiff/comparer.rb
106
+ - lib/dotdiff/comparible/base.rb
107
+ - lib/dotdiff/comparible/element_comparer.rb
108
+ - lib/dotdiff/comparible/page_comparer.rb
104
109
  - lib/dotdiff/element_handler.rb
105
110
  - lib/dotdiff/element_meta.rb
111
+ - lib/dotdiff/image/container.rb
106
112
  - lib/dotdiff/image/cropper.rb
107
113
  - lib/dotdiff/rspec_matcher.rb
108
114
  - lib/dotdiff/snapshot.rb
115
+ - lib/dotdiff/threshold_calculator.rb
109
116
  - lib/dotdiff/version.rb
110
117
  homepage: https://github.com/jnormington/dotdiff
111
118
  licenses:
@@ -127,9 +134,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
134
  version: '0'
128
135
  requirements: []
129
136
  rubyforge_project:
130
- rubygems_version: 2.6.12
137
+ rubygems_version: 2.6.14.4
131
138
  signing_key:
132
139
  specification_version: 4
133
- summary: Image regression wrapper for Capybara and RSpec using image magick supporting
140
+ summary: Image regression wrapper for Capybara and RSpec using imagemagick supporting
134
141
  both MRI and JRuby versions
135
142
  test_files: []