eyes_selenium 2.39.1 → 3.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/applitools/capybara.rb +1 -1
  3. data/lib/applitools/selenium/border_aware_element_content_location_provider.rb +41 -0
  4. data/lib/applitools/selenium/browser.rb +1 -0
  5. data/lib/applitools/selenium/capybara/capybara_settings.rb +8 -0
  6. data/lib/applitools/selenium/capybara/driver.rb +1 -0
  7. data/lib/applitools/selenium/context_based_scale_provider.rb +36 -0
  8. data/lib/applitools/selenium/css_translate_position_provider.rb +66 -0
  9. data/lib/applitools/selenium/driver.rb +159 -44
  10. data/lib/applitools/selenium/element.rb +100 -8
  11. data/lib/applitools/selenium/element_position_provider.rb +48 -0
  12. data/lib/applitools/selenium/eyes.rb +922 -0
  13. data/lib/applitools/selenium/eyes_target_locator.rb +191 -0
  14. data/lib/applitools/selenium/eyes_web_driver_screenshot.rb +274 -0
  15. data/lib/applitools/selenium/frame.rb +24 -0
  16. data/lib/applitools/selenium/frame_chain.rb +68 -0
  17. data/lib/applitools/selenium/full_page_capture_algorithm.rb +162 -0
  18. data/lib/applitools/selenium/keyboard.rb +1 -0
  19. data/lib/applitools/selenium/match_window_task.rb +0 -197
  20. data/lib/applitools/selenium/mouse.rb +1 -0
  21. data/lib/applitools/selenium/move_to_region_visibility_strategy.rb +33 -0
  22. data/lib/applitools/selenium/nop_region_visibility_strategy.rb +16 -0
  23. data/lib/applitools/selenium/scroll_position_provider.rb +52 -0
  24. data/lib/applitools/selenium/takes_screenshot_image_provider.rb +36 -0
  25. data/lib/applitools/version.rb +1 -1
  26. data/lib/eyes_selenium.rb +19 -30
  27. metadata +24 -254
  28. data/.gitignore +0 -18
  29. data/.rspec +0 -2
  30. data/.rubocop.yml +0 -57
  31. data/.travis.yml +0 -17
  32. data/Gemfile +0 -4
  33. data/LICENSE.txt +0 -13
  34. data/README.md +0 -38
  35. data/Rakefile +0 -11
  36. data/certs/cacert.pem +0 -3557
  37. data/examples/appium_example_script.rb +0 -59
  38. data/examples/capybara_example.rb +0 -82
  39. data/examples/sauce_capybara_example.rb +0 -41
  40. data/examples/sauce_example.rb +0 -34
  41. data/examples/simple_test_script.rb +0 -23
  42. data/examples/watir_test_script.rb +0 -26
  43. data/eyes_selenium.gemspec +0 -89
  44. data/lib/applitools/appium_driver.rb +0 -7
  45. data/lib/applitools/base/batch_info.rb +0 -21
  46. data/lib/applitools/base/dimension.rb +0 -36
  47. data/lib/applitools/base/environment.rb +0 -19
  48. data/lib/applitools/base/image_position.rb +0 -10
  49. data/lib/applitools/base/mouse_trigger.rb +0 -33
  50. data/lib/applitools/base/point.rb +0 -34
  51. data/lib/applitools/base/region.rb +0 -112
  52. data/lib/applitools/base/server_connector.rb +0 -120
  53. data/lib/applitools/base/session.rb +0 -15
  54. data/lib/applitools/base/start_info.rb +0 -34
  55. data/lib/applitools/base/test_results.rb +0 -28
  56. data/lib/applitools/base/text_trigger.rb +0 -22
  57. data/lib/applitools/extensions.rb +0 -17
  58. data/lib/applitools/eyes.rb +0 -460
  59. data/lib/applitools/eyes_logger.rb +0 -38
  60. data/lib/applitools/method_tracer.rb +0 -22
  61. data/lib/applitools/poltergeist/applitools_compatible.rb +0 -28
  62. data/lib/applitools/poltergeist/driver.rb +0 -11
  63. data/lib/applitools/sauce.rb +0 -2
  64. data/lib/applitools/selenium/match_window_data.rb +0 -28
  65. data/lib/applitools/selenium_webdriver.rb +0 -8
  66. data/lib/applitools/utils/image_delta_compressor.rb +0 -148
  67. data/lib/applitools/utils/image_utils.rb +0 -143
  68. data/lib/applitools/utils/utils.rb +0 -49
  69. data/lib/applitools/watir_browser.rb +0 -8
  70. data/spec/driver_passthrough_spec.rb +0 -68
  71. data/spec/fixtures/static_test_file.html +0 -15
  72. data/spec/spec_helper.rb +0 -17
@@ -1,38 +0,0 @@
1
- require 'logger'
2
- require 'forwardable'
3
-
4
- module Applitools::EyesLogger
5
- class NullLogger < Logger
6
- def initialize(*_args) end
7
-
8
- def add(*_args, &_block) end
9
- end
10
-
11
- extend Forwardable
12
- extend self
13
-
14
- MANDATORY_METHODS = [:debug, :info, :close].freeze
15
- OPTIONAL_METHODS = [:warn, :error, :fatal].freeze
16
-
17
- def_delegators :@log_handler, *MANDATORY_METHODS
18
-
19
- @log_handler = NullLogger.new
20
-
21
- def log_handler=(log_handler)
22
- raise Applitools::EyesError.new('log_handler must implement Logger!') unless valid?(log_handler)
23
-
24
- @log_handler = log_handler
25
- end
26
-
27
- OPTIONAL_METHODS.each do |method|
28
- define_singleton_method(method) do |msg|
29
- @log_handler.respond_to?(method) ? @log_handler.send(method, msg) : @log_handler.info(msg)
30
- end
31
- end
32
-
33
- private
34
-
35
- def valid?(log_handler)
36
- MANDATORY_METHODS.all? { |method| log_handler.respond_to?(method) }
37
- end
38
- end
@@ -1,22 +0,0 @@
1
- module Applitools::MethodTracer
2
- def self.included(base)
3
- instance_methods = base.instance_methods(false) + base.private_instance_methods(false)
4
- class_methods = base.methods(false)
5
-
6
- base.class_eval do
7
- def self.trace_method(base, method_name, instance = true)
8
- original_method = instance ? instance_method(method_name) : method(method_name)
9
-
10
- send(instance ? :define_method : :define_singleton_method, method_name) do |*args, &block|
11
- Applitools::EyesLogger.debug "-> #{base}##{method_name}"
12
- return_value = (instance ? original_method.bind(self) : original_method).call(*args, &block)
13
- Applitools::EyesLogger.debug "<- #{base}##{method_name}"
14
- return_value
15
- end
16
- end
17
-
18
- instance_methods.each { |method_name| trace_method(base, method_name) }
19
- class_methods.each { |method_name| trace_method(base, method_name, false) }
20
- end
21
- end
22
- end
@@ -1,28 +0,0 @@
1
- # This module is used for compatibility with Applitools API.
2
- # Should be extended by Poltergeist driver instance.
3
- module Applitools::Poltergeist
4
- module ApplitoolsCompatible
5
- # Implementation of `screenshot_as` method for PhantomJS.
6
- # Realisation uses Poltergeist binding to `renderBase64` PhantomJS method.
7
- def screenshot_as(fmt)
8
- Base64.decode64(browser.render_base64(fmt))
9
- end
10
-
11
- # Poltergeist driver does not have `manage` and `window` methods.
12
- # In Applitools these methods are used in a chain to get size by `size` method call.
13
- %w(manage window).each do |method_name|
14
- define_method(method_name) { self }
15
- end
16
-
17
- # Method provides opened window size in Applitools format.
18
- def size
19
- size = window_size(current_window_handle)
20
- Applitools::Base::Dimension.new(size[0], size[1])
21
- end
22
-
23
- # Method changes opened window size in a way how original Applitools::Selenium::Driver does.
24
- def size=(new_size)
25
- resize(new_size.width, new_size.height)
26
- end
27
- end
28
- end
@@ -1,11 +0,0 @@
1
- # Applitools::Poltergeist::Driver is a small class implemented
2
- # for compatibility with Applitools API.
3
- # It gives required for Applitools methods to Poltergeist driver.
4
- module Applitools::Poltergeist
5
- class Driver < Applitools::Selenium::Driver
6
- def initialize(eyes, options)
7
- options[:driver].extend Applitools::Poltergeist::ApplitoolsCompatible
8
- super(eyes, options)
9
- end
10
- end
11
- end
@@ -1,2 +0,0 @@
1
- require_relative 'capybara' if defined? Capybara
2
- Applitools.require_dir 'selenium/sauce'
@@ -1,28 +0,0 @@
1
- module Applitools::Selenium
2
- class MatchWindowData
3
- attr_reader :user_inputs, :app_output, :tag, :ignore_mismatch, :screenshot
4
-
5
- def initialize(app_output, tag, ignore_mismatch, screenshot, user_inputs = [])
6
- @user_inputs = user_inputs
7
- @app_output = app_output
8
- @tag = tag
9
- @ignore_mismatch = ignore_mismatch
10
- @screenshot = screenshot
11
- end
12
-
13
- # IMPORTANT This method returns a hash WITHOUT the screenshot property. This is on purpose! The screenshot should
14
- # not be included as part of the json.
15
- def to_hash
16
- {
17
- user_inputs: user_inputs.map(&:to_hash),
18
- app_output: Hash[app_output.each_pair.to_a],
19
- tag: @tag,
20
- ignore_mismatch: @ignore_mismatch
21
- }
22
- end
23
-
24
- def screenshot
25
- @screenshot.to_blob.force_encoding('BINARY')
26
- end
27
- end
28
- end
@@ -1,8 +0,0 @@
1
- if defined? Selenium::WebDriver::Driver
2
- Selenium::WebDriver::Driver.class_eval do
3
- def driver_for_eyes(eyes)
4
- is_mobile_device = capabilities['platformName'] ? true : false
5
- Applitools::Selenium::Driver.new(eyes, driver: self, is_mobile_device: is_mobile_device)
6
- end
7
- end
8
- end
@@ -1,148 +0,0 @@
1
- require 'oily_png'
2
-
3
- module Applitools::Utils
4
- module ImageDeltaCompressor
5
- extend self
6
-
7
- BLOCK_SIZE = 10
8
-
9
- # Compresses the target image based on the source image.
10
- #
11
- # +target+:: +ChunkyPNG::Canvas+ The image to compress based on the source image.
12
- # +target_encoded+:: +Array+ The uncompressed image as binary string.
13
- # +source+:: +ChunkyPNG::Canvas+ The source image used as a base for compressing the target image.
14
- # +block_size+:: +Integer+ The width/height of each block.
15
- #
16
- # Returns +String+ The binary result (either the compressed image, or the uncompressed image if the compression
17
- # is greater in length).
18
- def compress_by_raw_blocks(target, target_encoded, source, block_size = BLOCK_SIZE)
19
- # If we can't compress for any reason, return the target image as is.
20
- if source.nil? || (source.height != target.height) || (source.width != target.width)
21
- # Returning a COPY of the target binary string.
22
- return String.new(target_encoded)
23
- end
24
-
25
- # Preparing the variables we need.
26
- target_pixels = target.to_rgb_stream.unpack('C*')
27
- source_pixels = source.to_rgb_stream.unpack('C*')
28
- image_size = Dimension.new(target.width, target.height)
29
- block_columns_count = (target.width / block_size) + ((target.width % block_size).zero? ? 0 : 1)
30
- block_rows_count = (target.height / block_size) + ((target.height % block_size).zero? ? 0 : 1)
31
-
32
- # IMPORTANT: The "-Zlib::MAX_WBITS" tells ZLib to create raw deflate compression, without the
33
- # "Zlib headers" (this isn't documented in the Zlib page, I found this in some internet forum).
34
- compressor = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS)
35
-
36
- compression_result = ''
37
-
38
- # Writing the data header.
39
- compression_result += PREAMBLE.encode('UTF-8')
40
- compression_result += [FORMAT_RAW_BLOCKS].pack('C')
41
- compression_result += [0].pack('S>') # Source id, Big Endian
42
- compression_result += [block_size].pack('S>') # Big Endian
43
-
44
- # We perform the compression for each channel.
45
- 3.times do |channel|
46
- block_number = 0
47
- block_rows_count.times do |block_row|
48
- block_columns_count.times do |block_column|
49
- actual_channel_index = 2 - channel # Since the image bytes are BGR and the server expects RGB...
50
- compare_result = compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, 3,
51
- block_size, block_column, block_row, actual_channel_index)
52
-
53
- unless compare_result.identical
54
- channel_bytes = compare_result.channel_bytes
55
- string_to_compress = [channel].pack('C')
56
- string_to_compress += [block_number].pack('L>')
57
- string_to_compress += channel_bytes.pack('C*')
58
-
59
- compression_result += compressor.deflate(string_to_compress)
60
-
61
- # If the compressed data so far is greater than the uncompressed representation of the target, just return
62
- # the target.
63
- if compression_result.length > target_encoded.length
64
- compressor.finish
65
- compressor.close
66
- # Returning a copy of the target bytes.
67
- return String.new(target_encoded)
68
- end
69
- end
70
-
71
- block_number += 1
72
- end
73
- end
74
- end
75
-
76
- # Compress and flush any remaining uncompressed data in the input buffer.
77
- compression_result += compressor.finish
78
- compressor.close
79
-
80
- # Returning the compressed result as a byte array.
81
- compression_result
82
- end
83
-
84
- private
85
-
86
- PREAMBLE = 'applitools'.freeze
87
- FORMAT_RAW_BLOCKS = 3
88
-
89
- Dimension = Struct.new(:width, :height)
90
- CompareAndCopyBlockChannelDataResult = Struct.new(:identical, :channel_bytes)
91
-
92
- # Computes the width and height of the image data contained in the block at the input column and row.
93
- # +image_size+:: +Dimension+ The image size in pixels.
94
- # +block_size+:: The block size for which we would like to compute the image data width and height.
95
- # +block_column+:: The block column index.
96
- # +block_row+:: The block row index.
97
- # ++
98
- # Returns the width and height of the image data contained in the block are returned as a +Dimension+.
99
- def get_actual_block_size(image_size, block_size, block_column, block_row)
100
- actual_width = [image_size.width - (block_column * block_size), block_size].min
101
- actual_height = [image_size.height - (block_row * block_size), block_size].min
102
- Dimension.new(actual_width, actual_height)
103
- end
104
-
105
- # Compares a block of pixels between the source and target and copies the target's block bytes to the result.
106
- # +source_pixels+:: +Array+ of bytes, representing the pixels of the source image.
107
- # +target_pixels+:: +Array+ of bytes, representing the pixels of the target image.
108
- # +image_size+:: +Dimension+ The size of the source/target image (remember they must be the same size).
109
- # +pixel_length+:: +Integer+ The number of bytes composing a pixel
110
- # +block_size+:: +Integer+ The width/height of the block (block is a square, theoretically).
111
- # +block_column+:: +Integer+ The block column index (when looking at the images as a grid of blocks).
112
- # +block_row+:: +Integer+ The block row index (when looking at the images as a grid of blocks).
113
- # +channel+:: +Integer+ The index of the channel we're comparing.
114
- # ++
115
- # Returns +CompareAndCopyBlockChannelDataResult+ object containing a flag saying whether the blocks are identical
116
- # and a copy of the target block's bytes.
117
- def compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, pixel_length, block_size,
118
- block_column, block_row, channel)
119
- identical = true
120
-
121
- actual_block_size = get_actual_block_size(image_size, block_size, block_column, block_row)
122
-
123
- # Getting the actual amount of data in the block we wish to copy.
124
- actual_block_height = actual_block_size.height
125
- actual_block_width = actual_block_size.width
126
-
127
- stride = image_size.width * pixel_length
128
-
129
- # Iterating the block's pixels and comparing the source and target.
130
- channel_bytes = []
131
- actual_block_height.times do |h|
132
- offset = (((block_size * block_row) + h) * stride) + (block_size * block_column * pixel_length) + channel
133
- actual_block_width.times do |_w|
134
- source_byte = source_pixels[offset]
135
- target_byte = target_pixels[offset]
136
- identical = false if source_byte != target_byte
137
- channel_bytes << target_byte
138
- offset += pixel_length
139
- end
140
- end
141
-
142
- # Returning the compare-and-copy result.
143
- CompareAndCopyBlockChannelDataResult.new(identical, channel_bytes)
144
- end
145
-
146
- include Applitools::MethodTracer
147
- end
148
- end
@@ -1,143 +0,0 @@
1
- require 'oily_png'
2
- require 'base64'
3
- require 'tempfile'
4
-
5
- module Applitools::Utils
6
- QUADRANTS_COUNT = 4
7
-
8
- module ImageUtils
9
- extend self
10
-
11
- # Creates an image object from the PNG bytes.
12
- # +png_bytes+:: +String+ A binary string of the PNG bytes of the image.
13
- #
14
- # Returns:
15
- # +ChunkyPNG::Canvas+ An image object.
16
- def png_image_from_bytes(png_bytes)
17
- ChunkyPNG::Image.from_blob(png_bytes)
18
- end
19
-
20
- # Creates an image instance from a base64 representation of its PNG encoding.
21
- #
22
- # +png_bytes64+:: +String+ The Base64 representation of a PNG image.
23
- #
24
- # Returns:
25
- # +ChunkyPNG::Canvas+ An image object.
26
- def png_image_from_base64(png_bytes)
27
- png_image_from_bytes(Base64.decode64(png_bytes))
28
- end
29
-
30
- # Get the raw PNG bytes of an image.
31
- #
32
- # +ChunkyPNG::Canvas+ The image object for which to get the PNG bytes.
33
- #
34
- # Returns:
35
- # +String+ The PNG bytes of the image.
36
- def bytes_from_png_image(image)
37
- image.to_blob(:fast_rgb)
38
- end
39
-
40
- # Get the Base64 representation of the raw PNG bytes of an image.
41
- #
42
- # +ChunkyPNG::Canvas+ The image object for which to get the PNG bytes.
43
- #
44
- # Returns:
45
- # +String+ the Base64 representation of the raw PNG bytes of an image.
46
- def base64_from_png_image(image)
47
- Base64.encode64(bytes_from_png_image(image))
48
- end
49
-
50
- # Rotates a matrix 90 deg clockwise or counter clockwise (depending whether num_quadrants is positive or negative,
51
- # respectively).
52
- #
53
- # +image+:: +ChunkyPNG::Canvas+ The image to rotate.
54
- # +num_quadrants+:: +Integer+ The number of rotations to perform. Positive values are used for clockwise rotation
55
- # and negative values are used for counter-clockwise rotation.
56
- #
57
- def quadrant_rotate!(image, num_quadrants)
58
- num_quadrants %= QUADRANTS_COUNT
59
-
60
- case num_quadrants
61
- when 0
62
- image
63
- when 1
64
- image.rotate_right!
65
- when 2
66
- image.rotate_180!
67
- when 3
68
- image.rotate_left!
69
- end
70
- end
71
-
72
- def scale!(image, factor)
73
- image.resample_nearest_neighbor!(image.width.to_f * factor, image.height.to_f * factor)
74
- end
75
-
76
- def stitch_images(size, images_data)
77
- stitched_screenshot = ChunkyPNG::Image.new(size.width, size.height, ChunkyPNG::Color::TRANSPARENT).tap do |res|
78
- images_data.each do |image_data|
79
- # Crop out of bounds images.
80
- image = image_data.image
81
- position = image_data.position
82
-
83
- new_width = position.left + image.width > size.width ? size.width - position.left : image.width
84
- new_height = position.top + image.height > size.height ? size.height - position.top : image.height
85
-
86
- if new_width != image.width || new_height != image.height
87
- image = image.crop!(0, 0, new_width, new_height)
88
- end
89
-
90
- res.replace!(image.restore, position.left, position.top)
91
- GC.start
92
- end
93
- end
94
- result = Applitools::Utils::ImageUtils::Screenshot.new stitched_screenshot.to_blob.dup
95
- GC.start
96
- result
97
- end
98
-
99
- class Screenshot < Delegator
100
- extend Forwardable
101
- def_delegators :header, :width, :height
102
-
103
- def initialize(image)
104
- @datastream = ChunkyPNG::Datastream.from_string image
105
- end
106
-
107
- def to_blob
108
- @datastream.to_blob
109
- end
110
-
111
- def __getobj__
112
- restore
113
- end
114
-
115
- def header
116
- @datastream.header_chunk
117
- end
118
-
119
- def __setobj__(obj)
120
- @datastream = obj.to_datastream
121
- self
122
- end
123
-
124
- def method_missing(method, *args, &block)
125
- if method =~ /^.+!$/
126
- __setobj__ super
127
- else
128
- super
129
- end
130
- end
131
-
132
- def respond_to_missing?(method_name, include_private = false)
133
- super
134
- end
135
-
136
- def restore
137
- ChunkyPNG::Image.from_datastream @datastream
138
- end
139
- end
140
-
141
- include Applitools::MethodTracer
142
- end
143
- end
@@ -1,49 +0,0 @@
1
- require 'bigdecimal'
2
-
3
- module Applitools::Utils
4
- extend self
5
-
6
- def underscore(str)
7
- str.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').tr('-', '_').downcase
8
- end
9
-
10
- def uncapitalize(str)
11
- str[0, 1].downcase + str[1..-1]
12
- end
13
-
14
- def camelcase(str)
15
- tokens = str.split('_')
16
- uncapitalize(tokens.shift) + tokens.map(&:capitalize).join
17
- end
18
-
19
- def wrap(object)
20
- if object.nil?
21
- []
22
- elsif object.respond_to?(:to_ary)
23
- object.to_ary || [object]
24
- else
25
- [object]
26
- end
27
- end
28
-
29
- def underscore_hash_keys(hash)
30
- convert_hash_keys(hash, :underscore)
31
- end
32
-
33
- def camelcase_hash_keys(hash)
34
- convert_hash_keys(hash, :camelcase)
35
- end
36
-
37
- private
38
-
39
- def convert_hash_keys(value, method)
40
- case value
41
- when Array
42
- value.map { |v| convert_hash_keys(v, method) }
43
- when Hash
44
- Hash[value.map { |k, v| [send(method, k.to_s).to_sym, convert_hash_keys(v, method)] }]
45
- else
46
- value
47
- end
48
- end
49
- end