eyes_selenium 2.15.0 → 2.16.0

Sign up to get free protection for your applications and to get access to all the features.
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