dotdiff 2.0.3-java → 3.0.0-java

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
- SHA1:
3
- metadata.gz: c38dc01d49c4b88d625c3c481829f9b321e20a1d
4
- data.tar.gz: 62e3b2b13fa9706f03415c4368fa18dc467d5b7b
2
+ SHA256:
3
+ metadata.gz: 3c84511842aad634a5b0a37f06b70a5cd00ff007443e0517493530172907ccae
4
+ data.tar.gz: a0b6676992de893aec44592fff36b9f74d40d0844b6a8c7c2c02636b793f23c8
5
5
  SHA512:
6
- metadata.gz: 7d7afdc360442d8ea26aab7aa1f03037a25c8560be2240b700a313b8b5b5049d7aadc7c26124f4e27a874ae6a65ecb35b4d4adf3fa0a736bd439b17fd7db842e
7
- data.tar.gz: d3c7b775973d966aaa069d29c77c254a94dbcceb6207e0dd195e247925f256424123dfce88b52b6d072a4ab9e3c7a7ba9fc306dcfc4f5d4918a3e5556cab189a
6
+ metadata.gz: 07357ed91bcddd8863dd9e7543b27ebdbec1ad5d44a243ad00e1f4243473006b18ad1ac50c78860a6846494de4a4b8bc3d8c238930f36453764fb66603337369
7
+ data.tar.gz: a5bff90cf808780185d4f7759ca283731074c4e40d5efc4ceb2bcbcd1bf18197e2df8b043671ee1423a0a1cefc5b32315832e40781048afa2717cc06a549a09b
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,38 @@
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
7
  is_java = RUBY_PLATFORM == 'java'
7
8
 
8
9
  Gem::Specification.new do |spec|
9
- spec.name = "dotdiff"
10
+ spec.name = 'dotdiff'
10
11
  spec.version = DotDiff::VERSION
11
- spec.authors = ["Jon Normington"]
12
- spec.email = ["jnormington@users.noreply.github.com"]
12
+ spec.authors = ['Jon Normington']
13
+ spec.email = ['jnormington@users.noreply.github.com']
13
14
  spec.platform = 'java' if is_java
14
15
 
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"
16
+ spec.summary = 'Image regression wrapper for Capybara and RSpec using image'\
17
+ 'magick supporting both MRI and JRuby versions'
18
+ spec.description = [spec.summary, 'which supports snap shoting both full page and'\
19
+ "specific elements on a page where text checks isn't enough"].join(' ')
20
+ spec.homepage = 'https://github.com/jnormington/dotdiff'
21
+ spec.license = 'MIT'
19
22
 
20
23
  spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
21
- spec.bindir = "exe"
24
+ spec.bindir = 'exe'
22
25
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
- spec.require_paths = ["lib"]
26
+ spec.require_paths = ['lib']
24
27
 
25
28
  if is_java
26
- spec.add_runtime_dependency "rmagick4j", '~> 0.4.0'
29
+ spec.add_runtime_dependency 'rmagick4j', '>= 0.4.0'
27
30
  else
28
- spec.add_runtime_dependency "rmagick", '~> 2.15'
31
+ spec.add_runtime_dependency 'rmagick', '>= 2.15'
29
32
  end
30
33
 
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"
34
+ spec.add_development_dependency 'bundler', '>= 2'
35
+ spec.add_development_dependency 'capybara', '>= 2.6'
36
+ spec.add_development_dependency 'rake', '>= 12.3.3'
37
+ spec.add_development_dependency 'rspec', '>= 3.0'
35
38
  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
@@ -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
@@ -10,49 +12,12 @@ module DotDiff
10
12
 
11
13
  def result
12
14
  if element.is_a?(Capybara::Session)
13
- compare_page
15
+ DotDiff::Comparible::PageComparer.run(snapshot, nil)
14
16
  elsif element.is_a?(Capybara::Node::Base)
15
- compare_element
17
+ DotDiff::Comparible::ElementComparer.run(snapshot, ElementMeta.new(page, element))
16
18
  else
17
19
  raise ArgumentError, "Unknown element class received: #{element.class.name}"
18
20
  end
19
21
  end
20
-
21
- private
22
-
23
- def compare_element(element_meta = ElementMeta.new(page, element))
24
- snapshot.capture_from_browser(false, nil)
25
- snapshot.crop_and_resave(element_meta)
26
-
27
- if !File.exists?(snapshot.basefile)
28
- snapshot.resave_cropped_file
29
- [true, snapshot.basefile]
30
- else
31
- compare(snapshot.cropped_file)
32
- end
33
- end
34
-
35
- def compare_page
36
- snapshot.capture_from_browser
37
-
38
- if !File.exists?(snapshot.basefile)
39
- snapshot.resave_fullscreen_file
40
- [true, snapshot.basefile]
41
- else
42
- compare(snapshot.fullscreen_file)
43
- end
44
- end
45
-
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)
53
- end
54
-
55
- [result.passed?, result.message]
56
- end
57
22
  end
58
23
  end
@@ -0,0 +1,56 @@
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, :new_image
18
+
19
+ def compare(compare_to_image)
20
+ @new_image = compare_to_image
21
+ return [false, img_container.dimensions_mismatch_msg] unless img_container.both_images_same_dimensions?
22
+
23
+ cmd = CommandWrapper.new
24
+ cmd.run(snapshot.basefile, new_image, snapshot.diff_file)
25
+ return [cmd.passed?, cmd.message] if cmd.failed?
26
+
27
+ calculate_result(cmd.pixels)
28
+ end
29
+
30
+ def calculate_result(diff_pixels)
31
+ calc = DotDiff::ThresholdCalculator.new(
32
+ DotDiff.pixel_threshold,
33
+ img_container.total_pixels,
34
+ diff_pixels
35
+ )
36
+
37
+ passed = calc.under_threshold?
38
+ write_failure_imgs if !passed && DotDiff.failure_image_path
39
+
40
+ [passed, calc.message]
41
+ end
42
+
43
+ def img_container
44
+ @img_container ||= DotDiff::Image::Container.new(
45
+ snapshot.basefile,
46
+ new_image
47
+ )
48
+ end
49
+
50
+ def write_failure_imgs
51
+ FileUtils.mkdir_p(snapshot.failure_path)
52
+ FileUtils.mv(new_image, snapshot.new_file, force: true)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,25 @@
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
+ private
18
+
19
+ def take_snapshot_and_crop
20
+ snapshot.capture_from_browser(DotDiff.hide_elements_on_non_full_screen_screenshot)
21
+ snapshot.crop_and_resave(element_meta)
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,18 @@
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
+ end
17
+ end
18
+ 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.rows == new_image.rows &&
13
+ base_image.columns == new_image.columns
14
+ end
15
+
16
+ def total_pixels
17
+ base_image.rows * base_image.columns
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.columns}x#{base_image.rows}
24
+ New file: #{new_image.columns}x#{new_image.rows}
25
+ MSG
26
+ end
27
+
28
+ private
29
+
30
+ attr_reader :baseimg_file, :newimg_file
31
+
32
+ def base_image
33
+ @base_image ||= Magick::Image.read(baseimg_file).first
34
+ end
35
+
36
+ def new_image
37
+ @new_image ||= Magick::Image.read(newimg_file).first
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rmagick'
2
4
 
3
5
  module DotDiff
@@ -6,8 +8,8 @@ module DotDiff
6
8
  def crop_and_resave(element)
7
9
  image = load_image(fullscreen_file)
8
10
  image.crop!(
9
- element.rectangle.x,
10
- element.rectangle.y,
11
+ element.rectangle.x.floor,
12
+ element.rectangle.y.floor,
11
13
  width(element, image),
12
14
  height(element, image)
13
15
  )
@@ -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 = '3.0.0'
3
5
  end
metadata CHANGED
@@ -1,19 +1,19 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dotdiff
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.3
4
+ version: 3.0.0
5
5
  platform: java
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: 2021-03-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
15
15
  requirements:
16
- - - "~>"
16
+ - - ">="
17
17
  - !ruby/object:Gem::Version
18
18
  version: 0.4.0
19
19
  name: rmagick4j
@@ -21,66 +21,68 @@ dependencies:
21
21
  type: :runtime
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.4.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
- - - "~>"
30
+ - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: '1.11'
32
+ version: '2'
33
33
  name: bundler
34
34
  prerelease: false
35
35
  type: :development
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
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '10.0'
47
- name: rake
46
+ version: '2.6'
47
+ name: capybara
48
48
  prerelease: false
49
49
  type: :development
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
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
- - - "~>"
58
+ - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '3.0'
61
- name: rspec
60
+ version: 12.3.3
61
+ name: rake
62
62
  prerelease: false
63
63
  type: :development
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
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
- - - "~>"
72
+ - - ">="
73
73
  - !ruby/object:Gem::Version
74
- version: '2.6'
75
- name: capybara
74
+ version: '3.0'
75
+ name: rspec
76
76
  prerelease: false
77
77
  type: :development
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 both MRI and JRuby versions which supports snap shoting both full page and specific elements on a page where text checks isn't enough
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
+ elements on a page where text checks isn't enough
84
86
  email:
85
87
  - jnormington@users.noreply.github.com
86
88
  executables: []
@@ -89,6 +91,8 @@ extra_rdoc_files: []
89
91
  files:
90
92
  - ".gitignore"
91
93
  - ".rspec"
94
+ - ".rubocop.yml"
95
+ - ".rubocop_todo.yml"
92
96
  - Gemfile
93
97
  - LICENSE
94
98
  - README.md
@@ -99,11 +103,16 @@ files:
99
103
  - lib/dotdiff.rb
100
104
  - lib/dotdiff/command_wrapper.rb
101
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
102
109
  - lib/dotdiff/element_handler.rb
103
110
  - lib/dotdiff/element_meta.rb
111
+ - lib/dotdiff/image/container.rb
104
112
  - lib/dotdiff/image/cropper.rb
105
113
  - lib/dotdiff/rspec_matcher.rb
106
114
  - lib/dotdiff/snapshot.rb
115
+ - lib/dotdiff/threshold_calculator.rb
107
116
  - lib/dotdiff/version.rb
108
117
  homepage: https://github.com/jnormington/dotdiff
109
118
  licenses:
@@ -125,8 +134,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
125
134
  version: '0'
126
135
  requirements: []
127
136
  rubyforge_project:
128
- rubygems_version: 2.6.8
137
+ rubygems_version: 2.7.10
129
138
  signing_key:
130
139
  specification_version: 4
131
- summary: Image regression wrapper for Capybara and RSpec using image magick supporting both MRI and JRuby versions
140
+ summary: Image regression wrapper for Capybara and RSpec using imagemagick supporting
141
+ both MRI and JRuby versions
132
142
  test_files: []