eyes_selenium 2.15.0 → 2.16.0

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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +0 -2
  3. data/.travis.yml +16 -0
  4. data/Gemfile +1 -1
  5. data/README.md +14 -4
  6. data/Rakefile +8 -1
  7. data/eyes_selenium.gemspec +26 -17
  8. data/lib/applitools/base/batch_info.rb +19 -0
  9. data/lib/applitools/base/dimension.rb +21 -0
  10. data/lib/applitools/base/environment.rb +19 -0
  11. data/lib/applitools/base/mouse_trigger.rb +33 -0
  12. data/lib/applitools/base/point.rb +21 -0
  13. data/lib/applitools/base/region.rb +77 -0
  14. data/lib/applitools/base/server_connector.rb +113 -0
  15. data/lib/applitools/base/session.rb +15 -0
  16. data/lib/applitools/base/start_info.rb +34 -0
  17. data/lib/applitools/base/test_results.rb +36 -0
  18. data/lib/applitools/base/text_trigger.rb +22 -0
  19. data/lib/applitools/eyes.rb +393 -0
  20. data/lib/applitools/eyes_logger.rb +40 -0
  21. data/lib/applitools/method_tracer.rb +22 -0
  22. data/lib/applitools/selenium/driver.rb +194 -0
  23. data/lib/applitools/selenium/element.rb +66 -0
  24. data/lib/applitools/selenium/keyboard.rb +27 -0
  25. data/lib/applitools/selenium/match_window_data.rb +24 -0
  26. data/lib/applitools/selenium/match_window_task.rb +190 -0
  27. data/lib/applitools/selenium/mouse.rb +62 -0
  28. data/lib/applitools/selenium/viewport_size.rb +128 -0
  29. data/lib/applitools/utils/image_delta_compressor.rb +150 -0
  30. data/lib/applitools/utils/image_utils.rb +63 -0
  31. data/lib/applitools/utils/utils.rb +52 -0
  32. data/lib/applitools/version.rb +3 -0
  33. data/lib/eyes_selenium.rb +9 -29
  34. data/spec/driver_passthrough_spec.rb +25 -25
  35. data/spec/spec_helper.rb +5 -8
  36. data/test/appium_example_script.rb +57 -0
  37. data/{test_script.rb → test/test_script.rb} +7 -9
  38. data/{watir_test.rb → test/watir_test_script.rb} +6 -4
  39. metadata +120 -48
  40. data/appium_eyes_example.rb +0 -56
  41. data/lib/eyes_selenium/capybara.rb +0 -21
  42. data/lib/eyes_selenium/eyes/agent_connector.rb +0 -76
  43. data/lib/eyes_selenium/eyes/batch_info.rb +0 -19
  44. data/lib/eyes_selenium/eyes/dimension.rb +0 -15
  45. data/lib/eyes_selenium/eyes/driver.rb +0 -266
  46. data/lib/eyes_selenium/eyes/element.rb +0 -78
  47. data/lib/eyes_selenium/eyes/environment.rb +0 -15
  48. data/lib/eyes_selenium/eyes/eyes.rb +0 -396
  49. data/lib/eyes_selenium/eyes/eyes_keyboard.rb +0 -25
  50. data/lib/eyes_selenium/eyes/eyes_mouse.rb +0 -60
  51. data/lib/eyes_selenium/eyes/failure_reports.rb +0 -4
  52. data/lib/eyes_selenium/eyes/match_level.rb +0 -8
  53. data/lib/eyes_selenium/eyes/match_window_data.rb +0 -18
  54. data/lib/eyes_selenium/eyes/match_window_task.rb +0 -182
  55. data/lib/eyes_selenium/eyes/mouse_trigger.rb +0 -23
  56. data/lib/eyes_selenium/eyes/region.rb +0 -72
  57. data/lib/eyes_selenium/eyes/screenshot_taker.rb +0 -18
  58. data/lib/eyes_selenium/eyes/session.rb +0 -14
  59. data/lib/eyes_selenium/eyes/start_info.rb +0 -32
  60. data/lib/eyes_selenium/eyes/test_results.rb +0 -32
  61. data/lib/eyes_selenium/eyes/text_trigger.rb +0 -19
  62. data/lib/eyes_selenium/eyes/viewport_size.rb +0 -105
  63. data/lib/eyes_selenium/eyes_logger.rb +0 -47
  64. data/lib/eyes_selenium/utils.rb +0 -6
  65. data/lib/eyes_selenium/utils/image_delta_compressor.rb +0 -149
  66. data/lib/eyes_selenium/utils/image_utils.rb +0 -76
  67. data/lib/eyes_selenium/version.rb +0 -3
@@ -1,32 +0,0 @@
1
- class Applitools::TestResults
2
- attr_accessor :is_new, :url
3
- attr_reader :steps, :matches, :mismatches, :missing, :exact_matches, :strict_matches,
4
- :content_matches, :layout_matches, :none_matches, :is_passed
5
- def initialize(steps=0, matches=0, mismatches=0, missing=0,
6
- exact_matches=0, strict_matches=0, content_matches=0,
7
- layout_matches=0, none_matches=0)
8
- @steps = steps
9
- @matches = matches
10
- @mismatches = mismatches
11
- @missing = missing
12
- @exact_matches = exact_matches
13
- @strict_matches = strict_matches
14
- @content_matches = content_matches
15
- @layout_matches = layout_matches
16
- @none_matches = none_matches
17
- @is_new = nil
18
- @url = nil
19
- end
20
-
21
- def is_passed
22
- !is_new && mismatches == 0 && missing ==0
23
- end
24
-
25
- def to_s
26
- is_new_str = ""
27
- unless is_new.nil?
28
- is_new_str = is_new ? "New test" : "Existing test"
29
- end
30
- "#{is_new_str} [ steps: #{steps}, matches: #{matches}, mismatches: #{mismatches}, missing: #{missing} ], URL: #{url}"
31
- end
32
- end
@@ -1,19 +0,0 @@
1
- class Applitools::TextTrigger
2
-
3
- attr_reader :text, :control
4
-
5
- def initialize(text, control)
6
- @text = text
7
- @control = control
8
- end
9
-
10
- def to_hash
11
- {
12
- triggetType: 'Text', text: text, control: control.to_hash
13
- }
14
- end
15
-
16
- def to_s
17
- "Text [#{@control}] #{@text}"
18
- end
19
- end
@@ -1,105 +0,0 @@
1
- class Applitools::ViewportSize
2
-
3
- JS_GET_VIEWPORT_HEIGHT =
4
- 'var height = undefined;' +
5
- ' if (window.innerHeight) {height = window.innerHeight;}' +
6
- ' else if (document.documentElement ' +
7
- '&& document.documentElement.clientHeight) ' +
8
- '{height = document.documentElement.clientHeight;}' +
9
- ' else { var b = document.getElementsByTagName("body")[0]; ' +
10
- 'if (b.clientHeight) {height = b.clientHeight;}' +
11
- '};' +
12
- 'return height;'
13
-
14
- JS_GET_VIEWPORT_WIDTH =
15
- 'var width = undefined;' +
16
- ' if (window.innerWidth) {width = window.innerWidth;}' +
17
- ' else if (document.documentElement ' +
18
- '&& document.documentElement.clientWidth) ' +
19
- '{width = document.documentElement.clientWidth;}' +
20
- ' else { var b = document.getElementsByTagName("body")[0]; ' +
21
- 'if (b.clientWidth) {' +
22
- 'width = b.clientWidth;}' +
23
- '};' +
24
- 'return width;'
25
-
26
- attr_reader :driver
27
- attr_accessor :dimension
28
- def initialize(driver, dimension=nil)
29
- @driver = driver
30
- @dimension = dimension
31
- end
32
-
33
- def extract_viewport_width
34
- driver.execute_script(JS_GET_VIEWPORT_WIDTH)
35
- end
36
-
37
- def extract_viewport_height
38
- driver.execute_script(JS_GET_VIEWPORT_HEIGHT)
39
- end
40
-
41
- def extract_viewport_from_browser!
42
- self.dimension = extract_viewport_from_browser
43
- end
44
-
45
- def extract_viewport_from_browser
46
- width, height = nil, nil
47
- begin
48
- width = extract_viewport_width
49
- height = extract_viewport_height
50
- rescue => e
51
- EyesLogger.info "#{__method__}(): Failed to extract viewport size using Javascript: (#{e.message})"
52
- end
53
- if width.nil? || height.nil?
54
- EyesLogger.info "#{__method__}(): Using window size as viewport size."
55
- width, height = *browser_size.values
56
- width, height = width.ceil, height.ceil
57
- if driver.landscape_orientation? && height > width
58
- width, height = height, width
59
- end
60
- end
61
- Applitools::Dimension.new(width,height)
62
- end
63
- alias_method :viewport_size, :extract_viewport_from_browser
64
-
65
- def set
66
- if dimension.is_a?(Hash) && dimension.has_key?(:width) && dimension.has_key?(:height)
67
- # If dimension is hash of width/height, we convert it to a struct with width/height properties.
68
- self.dimension = Struct.new(:width, :height).new(dimension[:width], dimension[:height])
69
- elsif !dimension.respond_to?(:width) || !dimension.respond_to?(:height)
70
- raise ArgumentError, "expected #{dimension.inspect}:#{dimension.class}" +
71
- ' to respond to #width and #height, or be a hash with these keys.'
72
- end
73
- self.browser_size = dimension
74
- verify_size(:browser_size)
75
- cur_viewport_size = extract_viewport_from_browser
76
- self.browser_size = Applitools::Dimension.new(
77
- (2 * browser_size.width) - cur_viewport_size.width,
78
- (2 * browser_size.height) - cur_viewport_size.height
79
- )
80
- verify_size(:viewport_size)
81
- end
82
-
83
- def verify_size(to_verify, sleep_time=1, retries=3)
84
- cur_size = nil
85
- retries.times do
86
- sleep(sleep_time)
87
- cur_size = send(to_verify)
88
- return if cur_size.values == dimension.values
89
- end
90
- EyesLogger.info(err_msg = "Failed setting #{to_verify} to #{dimension.values} (current size: #{cur_size.values})")
91
- raise Applitools::TestFailedError.new(err_msg)
92
- end
93
-
94
- def browser_size
95
- driver.manage.window.size
96
- end
97
-
98
- def browser_size=(other)
99
- self.driver.manage.window.size = other
100
- end
101
-
102
- def to_hash
103
- Hash[dimension.each_pair.to_a]
104
- end
105
- end
@@ -1,47 +0,0 @@
1
- require 'logger'
2
-
3
- module EyesLogger
4
-
5
- class NullLogger
6
- attr_accessor :level
7
-
8
- def info(msg)
9
- # do nothing
10
- end
11
- def debug(msg)
12
- #do nothing
13
- end
14
- end
15
-
16
- NULL_LOGGER = NullLogger.new
17
-
18
- @@log_handler = NULL_LOGGER
19
-
20
- def self.log_handler=(log_handler)
21
- if !log_handler.respond_to?(:info) || !log_handler.respond_to?(:debug)
22
- raise Applitools::EyesError.new('log handler must respond to "info" and "debug"!')
23
- end
24
- @@log_handler = log_handler
25
- end
26
-
27
- def self.info(msg)
28
- @@log_handler.info(msg)
29
- end
30
-
31
- def self.debug(msg)
32
- @@log_handler.debug(msg)
33
- end
34
-
35
- def self.open
36
- if @@log_handler.respond_to?(:open)
37
- @@log_handler.open
38
- end
39
- end
40
-
41
- def self.close
42
- if @@log_handler.respond_to?(:close)
43
- @@log_handler.close
44
- end
45
- end
46
-
47
- end
@@ -1,6 +0,0 @@
1
- module Applitools
2
- module Utils
3
- require 'eyes_selenium/utils/image_delta_compressor'
4
- require 'eyes_selenium/utils/image_utils'
5
- end
6
- end
@@ -1,149 +0,0 @@
1
- =begin
2
- Applitools SDK class.
3
-
4
- Provides image compression based on image sequences and deflate.
5
- =end
6
- require 'oily_png'
7
-
8
- class Applitools::Utils::ImageDeltaCompressor
9
-
10
- # Compresses the target image based on the source image.
11
- #
12
- # +target+:: +ChunkyPNG::Canvas+ The image to compress based on the source image.
13
- # +target_encoded+:: +Array+ The uncompressed image as binary string.
14
- # +source+:: +ChunkyPNG::Canvas+ The source image used as a base for compressing the target image.
15
- # +block_size+:: +Integer+ The width/height of each block.
16
- #
17
- # Returns +String+ The binary result (either the compressed image, or the uncompressed image if the compression
18
- # is greater in length).
19
- def self.compress_by_raw_blocks(target, target_encoded, source, block_size = 10)
20
- # If we can't compress for any reason, return the target image as is.
21
- if source.nil? || (source.height != target.height) || (source.width != target.width)
22
- # Returning a COPY of the target binary string
23
- return String.new(target_encoded)
24
- end
25
-
26
- # Preparing the variables we need
27
- target_pixels = target.to_rgb_stream.unpack('C*')
28
- source_pixels = source.to_rgb_stream.unpack('C*')
29
- image_size = Dimension.new(target.width, target.height)
30
- block_columns_count = (target.width / block_size) + ((target.width % block_size) == 0 ? 0 : 1)
31
- block_rows_count = (target.height / block_size) + ((target.height % block_size) == 0 ? 0 : 1)
32
-
33
- # IMPORTANT: The "-Zlib::MAX_WBITS" tells ZLib to create raw deflate compression, without the
34
- # "Zlib headers" (this isn't documented in the Zlib page, I found this in some internet forum).
35
- compressor = Zlib::Deflate.new(Zlib::BEST_COMPRESSION, -Zlib::MAX_WBITS)
36
-
37
- compression_result = ''
38
-
39
- # Writing the data header
40
- compression_result += @@PREAMBLE.encode("UTF-8")
41
- compression_result += [@@FORMAT_RAW_BLOCKS].pack("C")
42
- compression_result += [0].pack("S>") #Source id, Big Endian
43
- compression_result += [block_size].pack("S>") #Big Endian
44
-
45
-
46
- # We perform the compression for each channel
47
- 3.times do |channel|
48
- block_number = 0
49
- block_rows_count.times do |block_row|
50
- block_columns_count.times do |block_column|
51
- actual_channel_index = 2 - channel # Since the image bytes are BGR and the server expects RGB...
52
- compare_result = compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, 3, block_size,
53
- block_column, block_row, actual_channel_index)
54
-
55
- if !compare_result.identical
56
- channel_bytes = compare_result.channel_bytes
57
- string_to_compress = [channel].pack('C')
58
- string_to_compress += [block_number].pack('L>')
59
- string_to_compress += channel_bytes.pack('C*')
60
-
61
- compression_result += compressor.deflate(string_to_compress)
62
-
63
- # If the compressed data so far is greater than the uncompressed
64
- # representation of the target, just return the target.
65
- if compression_result.length > target_encoded.length
66
- compressor.finish
67
- compressor.close
68
- # Returning a COPY of the target bytes
69
- return String.new(target_encoded)
70
- end
71
- end
72
- block_number += 1
73
- end
74
- end
75
- end
76
- # Compress and flush any remaining uncompressed data in the input buffer.
77
- compression_result += compressor.finish
78
- compressor.close
79
- # Returning the compressed result as a byte array
80
- return compression_result
81
- end
82
-
83
-
84
- ### PRIVATE
85
- private
86
-
87
- @@PREAMBLE = 'applitools'
88
- @@FORMAT_RAW_BLOCKS = 3
89
-
90
- Dimension = Struct.new(:width, :height)
91
- CompareAndCopyBlockChannelDataResult = Struct.new(:identical, :channel_bytes)
92
-
93
-
94
- # Computes the width and height of the image data contained in the block
95
- # at the input column and row.
96
- # +image_size+:: +Dimension+ The image size in pixels.
97
- # +block_size+:: The block size for which we would like to compute the image data width and height.
98
- # +block_column+:: The block column index.
99
- # +block_row+:: The block row index.
100
- # ++
101
- # Returns the width and height of the image data contained in the block are returned as a +Dimension+.
102
- def self.get_actual_block_size(image_size, block_size, block_column, block_row)
103
- actual_width = [image_size.width - (block_column * block_size), block_size].min
104
- actual_height = [image_size.height - (block_row * block_size), block_size].min
105
- Dimension.new(actual_width, actual_height)
106
- end
107
-
108
- # Compares a block of pixels between the source and target and copies the target's block bytes to the result.
109
- # +source_pixels+:: +Array+ of bytes, representing the pixels of the source image.
110
- # +target_pixels+:: +Array+ of bytes, representing the pixels of the target image.
111
- # +image_size+:: +Dimension+ The size of the source/target image (remember they must be the same size).
112
- # +pixel_length+:: +Integer+ The number of bytes composing a pixel
113
- # +block_size+:: +Integer+ The width/height of the block (block is a square, theoretically).
114
- # +block_column+:: +Integer+ The block column index (when looking at the images as a grid of blocks).
115
- # +block_row+:: +Integer+ The block row index (when looking at the images as a grid of blocks).
116
- # +channel+:: +Integer+ The index of the channel we're comparing.
117
- # ++
118
- # Returns +CompareAndCopyBlockChannelDataResult+ object containing a flag saying whether the blocks are identical
119
- # and a copy of the target block's bytes.
120
- def self.compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, pixel_length, block_size,
121
- block_column, block_row, channel)
122
- identical = true
123
-
124
- actual_block_size = get_actual_block_size(image_size, block_size, block_column, block_row)
125
-
126
- # Getting the actual amount of data in the block we wish to copy
127
- actual_block_height = actual_block_size.height
128
- actual_block_width = actual_block_size.width
129
-
130
- stride = image_size.width * pixel_length
131
-
132
- # Iterating the block's pixels and comparing the source and target
133
- channel_bytes = []
134
- actual_block_height.times do |h|
135
- offset = (((block_size * block_row) + h) * stride) + (block_size * block_column * pixel_length) + channel
136
- actual_block_width.times do |w|
137
- source_byte = source_pixels[offset]
138
- target_byte = target_pixels[offset]
139
- if source_byte != target_byte
140
- identical = false
141
- end
142
- channel_bytes << target_byte
143
- offset += pixel_length
144
- end
145
- end
146
- # Returning the compare-and-copy result
147
- CompareAndCopyBlockChannelDataResult.new(identical, channel_bytes)
148
- end
149
- end
@@ -1,76 +0,0 @@
1
- =begin
2
- Applitools SDK class.
3
-
4
- Provides images manipulation functionality.
5
- =end
6
- require 'oily_png'
7
- require 'base64'
8
-
9
- module Applitools::Utils::ImageUtils
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 self.png_image_from_bytes(png_bytes)
17
- EyesLogger.debug "#{__method__}()"
18
- image = ChunkyPNG::Image.from_blob(png_bytes)
19
- EyesLogger.debug 'Done!'
20
- return image
21
- end
22
-
23
- # Creates an image instance from a base64 representation of its PNG encoding.
24
- #
25
- # +png_bytes64+:: +String+ The Base64 representation of a PNG image.
26
- #
27
- # Returns:
28
- # +ChunkyPNG::Canvas+ An image object.
29
- def self.png_image_from_base64(png_bytes64)
30
- EyesLogger.debug "#{__method__}()"
31
- png_bytes = Base64.decode64(png_bytes64)
32
- EyesLogger.debug 'Done!'
33
- return png_image_from_bytes(png_bytes)
34
- end
35
-
36
- # Get the raw PNG bytes of an image.
37
- #
38
- # +ChunkyPNG::Canvas+ The image object for which to get the PNG bytes.
39
- #
40
- # Returns:
41
- # +String+ The PNG bytes of the image.
42
- def self.bytes_from_png_image(image)
43
- EyesLogger.debug "#{__method__}()"
44
- png_bytes = image.to_blob
45
- EyesLogger.debug 'Done!'
46
- return png_bytes
47
- end
48
-
49
- # Get the Base64 representation of the raw PNG bytes of an image.
50
- #
51
- # +ChunkyPNG::Canvas+ The image object for which to get the PNG bytes.
52
- #
53
- # Returns:
54
- # +String+ the Base64 representation of the raw PNG bytes of an image.
55
- def self.base64_from_png_image(image)
56
- EyesLogger.debug "#{__method__}()"
57
- png_bytes = bytes_from_png_image(image)
58
- EyesLogger.debug 'Encoding as base64...'
59
- image64 = Base64.encode64(png_bytes)
60
- EyesLogger.debug 'Done!'
61
- return image64
62
- end
63
-
64
- # Rotates a matrix 90 deg clockwise or counter clockwise (depending whether num_quadrants is positive or negative,
65
- # respectively).
66
- #
67
- # +image+:: +ChunkyPNG::Canvas+ The image to rotate.
68
- # +num_quadrants+:: +Integer+ The number of rotations to perform. Positive values are used for clockwise rotation
69
- # and negative values are used for counter-clockwise rotation.
70
- #
71
- def self.quadrant_rotate!(image, num_quadrants)
72
- rotate_method = num_quadrants > 0 ? image.method('rotate_right!'.to_sym) : image.method('rotate_left!'.to_sym)
73
- (0..(num_quadrants.abs-1)).each { rotate_method.call }
74
- return image
75
- end
76
- end
@@ -1,3 +0,0 @@
1
- module Applitools
2
- VERSION = '2.15.0'
3
- end