eyes_selenium 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +82 -0
- data/Rakefile +1 -0
- data/eyes_selenium.gemspec +30 -0
- data/lib/eyes_selenium.rb +42 -0
- data/lib/eyes_selenium/capybara.rb +21 -0
- data/lib/eyes_selenium/eyes/agent_connecter.rb +39 -0
- data/lib/eyes_selenium/eyes/batch_info.rb +14 -0
- data/lib/eyes_selenium/eyes/dimension.rb +15 -0
- data/lib/eyes_selenium/eyes/driver.rb +146 -0
- data/lib/eyes_selenium/eyes/element.rb +78 -0
- data/lib/eyes_selenium/eyes/environment.rb +14 -0
- data/lib/eyes_selenium/eyes/eyes.rb +182 -0
- data/lib/eyes_selenium/eyes/eyes_keyboard.rb +25 -0
- data/lib/eyes_selenium/eyes/eyes_mouse.rb +60 -0
- data/lib/eyes_selenium/eyes/failure_reports.rb +4 -0
- data/lib/eyes_selenium/eyes/match_level.rb +7 -0
- data/lib/eyes_selenium/eyes/match_window_data.rb +18 -0
- data/lib/eyes_selenium/eyes/match_window_task.rb +71 -0
- data/lib/eyes_selenium/eyes/mouse_trigger.rb +19 -0
- data/lib/eyes_selenium/eyes/region.rb +22 -0
- data/lib/eyes_selenium/eyes/screenshot_taker.rb +18 -0
- data/lib/eyes_selenium/eyes/session.rb +14 -0
- data/lib/eyes_selenium/eyes/start_info.rb +34 -0
- data/lib/eyes_selenium/eyes/target_app.rb +17 -0
- data/lib/eyes_selenium/eyes/test_results.rb +21 -0
- data/lib/eyes_selenium/eyes/text_trigger.rb +15 -0
- data/lib/eyes_selenium/eyes/viewport_size.rb +104 -0
- data/lib/eyes_selenium/eyes_logger.rb +18 -0
- data/lib/eyes_selenium/utils.rb +5 -0
- data/lib/eyes_selenium/utils/image_delta_compressor.rb +147 -0
- data/lib/eyes_selenium/version.rb +3 -0
- data/spec/capybara_spec.rb +34 -0
- data/spec/driver_spec.rb +10 -0
- data/spec/eyes_spec.rb +157 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/test_app.rb +11 -0
- data/test_script.rb +21 -0
- metadata +226 -0
@@ -0,0 +1,14 @@
|
|
1
|
+
class Applitools::Session
|
2
|
+
attr_reader :eyes, :id, :url
|
3
|
+
attr_accessor :new_session
|
4
|
+
def initialize(session_id, session_url, new_session)
|
5
|
+
@new_session = new_session
|
6
|
+
@id = session_id
|
7
|
+
@url = session_url
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_session?
|
11
|
+
self.new_session
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class Applitools::StartInfo
|
2
|
+
|
3
|
+
ATTRIBUTES = %w[ agent_id app_id_or_name ver_id scenario_id_or_name batch_info
|
4
|
+
environment application match_level branch_name parent_branch_name ]
|
5
|
+
|
6
|
+
ATTRIBUTES.each do |attr|
|
7
|
+
attr_accessor attr
|
8
|
+
end
|
9
|
+
|
10
|
+
## add a config file with this stuff, and use hash arg
|
11
|
+
def initialize(agent_id, app_id_or_name, scenario_id_or_name, batch_info, environment,
|
12
|
+
application, match_level, ver_id=nil, branch_name=nil,
|
13
|
+
parent_branch_name=nil)
|
14
|
+
@agent_id = agent_id
|
15
|
+
@app_id_or_name = app_id_or_name
|
16
|
+
@ver_id = ver_id
|
17
|
+
@scenario_id_or_name = scenario_id_or_name
|
18
|
+
@batch_info = batch_info
|
19
|
+
@environment = environment
|
20
|
+
@application = application
|
21
|
+
@match_level = match_level
|
22
|
+
@branch_name = branch_name
|
23
|
+
@parent_branch_name = parent_branch_name
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_hash
|
27
|
+
{
|
28
|
+
AgentId: agent_id, AppIdOrName: app_id_or_name, VerId: ver_id, ScenarioIdOrName: scenario_id_or_name,
|
29
|
+
BatchInfo: batch_info.to_hash, Environment: environment.to_hash,
|
30
|
+
Application: application.to_hash, matchLevel: match_level, branchName: branch_name,
|
31
|
+
parentBranchName: parent_branch_name
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Applitools::TargetApp
|
2
|
+
|
3
|
+
attr_reader :url, :session_id, :user_agent
|
4
|
+
|
5
|
+
def initialize(url, session_id, user_agent)
|
6
|
+
@url = url
|
7
|
+
@session_id = session_id
|
8
|
+
@user_agent = user_agent
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_hash
|
12
|
+
{
|
13
|
+
"$type" => "Applitools.Framework.TargetWebDriverApplication, Core",
|
14
|
+
url: URI.encode(url.to_s), sessionId: session_id, userAgent: user_agent
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Applitools::TestResults
|
2
|
+
attr_reader :steps, :matches, :mismatches, :missing, :exact_matches, :strict_matches,
|
3
|
+
:context_matches, :layout_matches, :none_matches
|
4
|
+
def initialize(steps=0, matches=0, mismatches=0, missing=0,
|
5
|
+
exact_matches=0, strict_matches=0, content_matches=0,
|
6
|
+
layout_matches=0, none_matches=0)
|
7
|
+
@steps = steps
|
8
|
+
@matches = matches
|
9
|
+
@mismatches = mismatches
|
10
|
+
@missing = missing
|
11
|
+
@exact_matches = exact_matches
|
12
|
+
@strict_matches = strict_matches
|
13
|
+
@content_matches = content_matches
|
14
|
+
@layout_matches = layout_matches
|
15
|
+
@none_matches = none_matches
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"[ steps: #{steps}, matches: #{matches}, mismatches: #{mismatches}, missing: #{missing} ]"
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,15 @@
|
|
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
|
+
"$type" => "Applitools.Models.TextTrigger, Core", text: text, control: control.to_hash
|
13
|
+
}
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
class Applitools::ViewportSize
|
2
|
+
|
3
|
+
GET_VIEWPORT_HEIGHT_JAVASCRIPT_FOR_NORMAL_BROWSER = "return window.innerHeight"
|
4
|
+
GET_VIEWPORT_WIDTH_JAVASCRIPT_FOR_NORMAL_BROWSER = "return window.innerWidth"
|
5
|
+
|
6
|
+
DOCUMENT_CLEAR_SCROLL_BARS_JAVASCRIPT = "var doc = document.documentElement;" +
|
7
|
+
"var previousOverflow = doc.style.overflow;"
|
8
|
+
DOCUMENT_RESET_SCROLL_BARS_JAVASCRIPT = "doc.style.overflow = previousOverflow;"
|
9
|
+
DOCUMENT_RETURN_JAVASCRIPT = "return __applitools_result;"
|
10
|
+
|
11
|
+
GET_VIEWPORT_WIDTH_JAVASCRIPT_FOR_BAD_BROWSERS =
|
12
|
+
DOCUMENT_CLEAR_SCROLL_BARS_JAVASCRIPT +
|
13
|
+
"var __applitools_result = doc.clientWidth;" +
|
14
|
+
DOCUMENT_RESET_SCROLL_BARS_JAVASCRIPT +
|
15
|
+
DOCUMENT_RETURN_JAVASCRIPT
|
16
|
+
|
17
|
+
GET_VIEWPORT_HEIGHT_JAVASCRIPT_FOR_BAD_BROWSERS =
|
18
|
+
DOCUMENT_CLEAR_SCROLL_BARS_JAVASCRIPT +
|
19
|
+
"var __applitools_result = doc.clientHeight;" +
|
20
|
+
DOCUMENT_RESET_SCROLL_BARS_JAVASCRIPT +
|
21
|
+
DOCUMENT_RETURN_JAVASCRIPT
|
22
|
+
|
23
|
+
attr_reader :driver
|
24
|
+
attr_accessor :dimension
|
25
|
+
def initialize(driver, dimension=nil)
|
26
|
+
@driver = driver
|
27
|
+
@dimension = dimension
|
28
|
+
end
|
29
|
+
|
30
|
+
def extract_viewport_width
|
31
|
+
begin
|
32
|
+
return driver.execute_script(GET_VIEWPORT_WIDTH_JAVASCRIPT_FOR_NORMAL_BROWSER)
|
33
|
+
rescue => e
|
34
|
+
EyesLogger.info "getViewportSize(): Browser does not support innerWidth (#{e.message})"
|
35
|
+
end
|
36
|
+
|
37
|
+
driver.execute_script(GET_VIEWPORT_WIDTH_JAVASCRIPT_FOR_BAD_BROWSERS)
|
38
|
+
end
|
39
|
+
|
40
|
+
def extract_viewport_height
|
41
|
+
begin
|
42
|
+
return driver.execute_script(GET_VIEWPORT_HEIGHT_JAVASCRIPT_FOR_NORMAL_BROWSER)
|
43
|
+
rescue => e
|
44
|
+
EyesLogger.info "getViewportSize(): Browser does not support innerHeight (#{e.message})"
|
45
|
+
end
|
46
|
+
|
47
|
+
driver.execute_script(GET_VIEWPORT_WIDTH_JAVASCRIPT_FOR_BAD_BROWSERS)
|
48
|
+
end
|
49
|
+
|
50
|
+
def extract_viewport_from_browser!
|
51
|
+
self.dimension = extract_viewport_from_browser
|
52
|
+
end
|
53
|
+
|
54
|
+
def extract_viewport_from_browser
|
55
|
+
width = extract_viewport_width
|
56
|
+
height = extract_viewport_height
|
57
|
+
Applitools::Dimension.new(width,height)
|
58
|
+
rescue => e
|
59
|
+
EyesLogger.info "getViewportSize(): only window size is available (#{e.message})"
|
60
|
+
width, height = *browser_size.values
|
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
|
+
retries.times do
|
85
|
+
sleep(sleep_time)
|
86
|
+
cur_size = send(to_verify)
|
87
|
+
return if cur_size.values == dimension.values
|
88
|
+
end
|
89
|
+
EyesLogger.info(err_msg = "Failed setting #{to_verify} to #{dimension.values}")
|
90
|
+
raise Applitools::TestFailedError.new(err_msg)
|
91
|
+
end
|
92
|
+
|
93
|
+
def browser_size
|
94
|
+
driver.manage.window.size
|
95
|
+
end
|
96
|
+
|
97
|
+
def browser_size=(other)
|
98
|
+
self.driver.manage.window.size = other
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_hash
|
102
|
+
Hash[dimension.each_pair.to_a]
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
module EyesLogger
|
4
|
+
def self.logger(to=STDOUT, level=Logger::INFO)
|
5
|
+
return @@log if defined?(@@log)
|
6
|
+
@@log = Logger.new(to)
|
7
|
+
@@log.level = level
|
8
|
+
@@log
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.info(msg)
|
12
|
+
logger.info(msg)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.debug(msg)
|
16
|
+
logger.debug(msg)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
=begin
|
2
|
+
Applitools SDK class.
|
3
|
+
|
4
|
+
Provides image compression based on image sequences and deflate
|
5
|
+
=end
|
6
|
+
require 'oily_png'
|
7
|
+
require 'base64'
|
8
|
+
|
9
|
+
class Applitools::Utils::ImageDeltaCompressor
|
10
|
+
|
11
|
+
# Compresses the target image based on the source image.
|
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
|
+
compression_result += compressor.deflate(string_to_compress)
|
61
|
+
|
62
|
+
# If the compressed data so far is greater than the uncompressed
|
63
|
+
# representation of the target, just return the target.
|
64
|
+
if compression_result.length > target_encoded.length
|
65
|
+
compressor.close
|
66
|
+
# Returning a COPY of the target bytes
|
67
|
+
return String.new(target_encoded)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
block_number += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
# Compress and flush any remaining uncompressed data in the input buffer.
|
75
|
+
compression_result += compressor.finish
|
76
|
+
compressor.close
|
77
|
+
# Returning the compressed result as a byte array
|
78
|
+
return compression_result
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
### PRIVATE
|
83
|
+
private
|
84
|
+
|
85
|
+
@@PREAMBLE = "applitools"
|
86
|
+
@@FORMAT_RAW_BLOCKS = 3
|
87
|
+
|
88
|
+
Dimension = Struct.new(:width, :height)
|
89
|
+
CompareAndCopyBlockChannelDataResult = Struct.new(:identical, :channel_bytes)
|
90
|
+
|
91
|
+
|
92
|
+
# Computes the width and height of the image data contained in the block
|
93
|
+
# at the input column and row.
|
94
|
+
# +image_size+:: +Dimension+ The image size in pixels.
|
95
|
+
# +block_size+:: The block size for which we would like to compute the image data width and height.
|
96
|
+
# +block_column+:: The block column index.
|
97
|
+
# +block_row+:: The block row index.
|
98
|
+
# ++
|
99
|
+
# Returns the width and height of the image data contained in the block are returned as a +Dimension+.
|
100
|
+
def self.get_actual_block_size(image_size, block_size, block_column, block_row)
|
101
|
+
actual_width = [image_size.width - (block_column * block_size), block_size].min
|
102
|
+
actual_height = [image_size.height - (block_row * block_size), block_size].min
|
103
|
+
Dimension.new(actual_width, actual_height)
|
104
|
+
end
|
105
|
+
|
106
|
+
# Compares a block of pixels between the source and target and copies the target's block bytes to the result.
|
107
|
+
# +source_pixels+:: +Array+ of bytes, representing the pixels of the source image.
|
108
|
+
# +target_pixels+:: +Array+ of bytes, representing the pixels of the target image.
|
109
|
+
# +image_size+:: +Dimension+ The size of the source/target image (remember they must be the same size).
|
110
|
+
# +pixel_length+:: +Integer+ The number of bytes composing a pixel
|
111
|
+
# +block_size+:: +Integer+ The width/height of the block (block is a square, theoretically).
|
112
|
+
# +block_column+:: +Integer+ The block column index (when looking at the images as a grid of blocks).
|
113
|
+
# +block_row+:: +Integer+ The block row index (when looking at the images as a grid of blocks).
|
114
|
+
# +channel+:: +Integer+ The index of the channel we're comparing.
|
115
|
+
# ++
|
116
|
+
# Returns +CompareAndCopyBlockChannelDataResult+ object containing a flag saying whether the blocks are identical
|
117
|
+
# and a copy of the target block's bytes.
|
118
|
+
def self.compare_and_copy_block_channel_data(source_pixels, target_pixels, image_size, pixel_length, block_size,
|
119
|
+
block_column, block_row, channel)
|
120
|
+
identical = true
|
121
|
+
|
122
|
+
actual_block_size = get_actual_block_size(image_size, block_size, block_column, block_row)
|
123
|
+
|
124
|
+
# Getting the actual amount of data in the block we wish to copy
|
125
|
+
actual_block_height = actual_block_size.height
|
126
|
+
actual_block_width = actual_block_size.width
|
127
|
+
|
128
|
+
stride = image_size.width * pixel_length
|
129
|
+
|
130
|
+
# Iterating the block's pixels and comparing the source and target
|
131
|
+
channel_bytes = []
|
132
|
+
actual_block_height.times do |h|
|
133
|
+
offset = (((block_size * block_row) + h) * stride) + (block_size * block_column * pixel_length) + channel
|
134
|
+
actual_block_width.times do |w|
|
135
|
+
source_byte = source_pixels[offset]
|
136
|
+
target_byte = target_pixels[offset]
|
137
|
+
if source_byte != target_byte
|
138
|
+
identical = false
|
139
|
+
end
|
140
|
+
channel_bytes << target_byte
|
141
|
+
offset += pixel_length
|
142
|
+
end
|
143
|
+
end
|
144
|
+
# Returning the compare-and-copy result
|
145
|
+
CompareAndCopyBlockChannelDataResult.new(identical, channel_bytes)
|
146
|
+
end
|
147
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'capybara/rspec'
|
3
|
+
require 'eyes_selenium/capybara'
|
4
|
+
require 'test_app'
|
5
|
+
|
6
|
+
Capybara.app = TestApp
|
7
|
+
|
8
|
+
describe Applitools::Eyes do
|
9
|
+
init_eyes
|
10
|
+
describe 'capybara' do
|
11
|
+
before do
|
12
|
+
eyes.agent_connector.stub(:stop_session).and_return(double("TestResults", mismatches: 0, missing: 0))
|
13
|
+
eyes.agent_connector.stub(:match_window).and_return(true)
|
14
|
+
@navigation = double("navigation")
|
15
|
+
@navigation.stub(:to)
|
16
|
+
end
|
17
|
+
it 'should invoke eyes driver when calling capybara visit inside of eyes.open' do
|
18
|
+
Capybara.current_driver.should == :rack_test
|
19
|
+
|
20
|
+
eyes.driver.should_receive(:navigate).and_return(@navigation)
|
21
|
+
eyes.test(app_name: 'test', test_name: 'test') do |eyes|
|
22
|
+
Capybara.current_driver.should == :selenium
|
23
|
+
eyes.driver.should == Capybara.current_session.driver.instance_variable_get(:@browser)
|
24
|
+
Capybara.current_session.visit '/'
|
25
|
+
eyes.check_window('test')
|
26
|
+
end
|
27
|
+
Capybara.current_driver.should == :rack_test
|
28
|
+
end
|
29
|
+
it 'should not invoke eyes driver when calling capybara visit outside of eyes.open' do
|
30
|
+
eyes.driver.should_not_receive(:navigate)
|
31
|
+
Capybara.current_session.visit '/'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/spec/driver_spec.rb
ADDED
data/spec/eyes_spec.rb
ADDED
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Applitools::Eyes do
|
4
|
+
init_eyes
|
5
|
+
|
6
|
+
before do
|
7
|
+
eyes.open(app_name: 'test_app', test_name: 'test_app_test')
|
8
|
+
fake_session = Applitools::Session.new(1,"http://fake_url.com", false)
|
9
|
+
eyes.session = fake_session
|
10
|
+
eyes.agent_connector.stub(:stop_session).and_return(double("TestResults", mismatches: 0, missing: 0))
|
11
|
+
eyes.agent_connector.stub(:match_window).and_return(true)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "#new" do
|
15
|
+
context "doesn't have an apikey" do
|
16
|
+
before do
|
17
|
+
Applitools::Eyes.config[:apikey] = nil
|
18
|
+
end
|
19
|
+
it {expect {Applitools::Eyes.new }.to raise_error }
|
20
|
+
after do
|
21
|
+
Applitools::Eyes.config[:apikey] = 'YOUR_API_KEY_HERE'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
context "has an apikey url" do
|
25
|
+
it {expect {Applitools::Eyes.new }.to_not raise_error }
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".open" do
|
30
|
+
context "already open" do
|
31
|
+
it "aborts session" do
|
32
|
+
eyes.should_receive(:abort_if_not_closed)
|
33
|
+
expect {eyes.open}.to raise_error
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
describe ".abort_if_not_closed" do
|
40
|
+
it "closes eyes" do
|
41
|
+
expect(eyes).to be_open
|
42
|
+
eyes.send(:abort_if_not_closed)
|
43
|
+
expect(eyes).to_not be_open
|
44
|
+
end
|
45
|
+
|
46
|
+
it "clears session" do
|
47
|
+
eyes.send(:abort_if_not_closed)
|
48
|
+
expect(eyes.session).to be_nil
|
49
|
+
end
|
50
|
+
|
51
|
+
it "stops session" do
|
52
|
+
expect(eyes.agent_connector).to receive(:stop_session)
|
53
|
+
eyes.send(:abort_if_not_closed)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe ".close" do
|
58
|
+
it "stops the session" do
|
59
|
+
eyes.agent_connector.stub(:stop_session).and_return(double("TestResults", mismatches: 0, missing: 0))
|
60
|
+
expect(eyes.agent_connector).to receive(:stop_session)
|
61
|
+
expect(eyes.close)
|
62
|
+
end
|
63
|
+
|
64
|
+
context "new_session" do
|
65
|
+
before { eyes.session.stub(:new_session?).and_return(true) }
|
66
|
+
it "raises NewTestError" do
|
67
|
+
eyes.session_start_info = double().as_null_object
|
68
|
+
expect { eyes.close }.to raise_error(Applitools::NewTestError)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "not new_session" do
|
73
|
+
before do
|
74
|
+
eyes.stub(:session_start_info).and_return double().as_null_object
|
75
|
+
eyes.session.new_session = false
|
76
|
+
end
|
77
|
+
|
78
|
+
it "raises TestFailedError if results have mismatches" do
|
79
|
+
eyes.agent_connector.stub(:stop_session).and_return(double("TestResults", mismatches: 2))
|
80
|
+
expect { eyes.close }.to raise_error(Applitools::TestFailedError)
|
81
|
+
end
|
82
|
+
|
83
|
+
it "raises TestFailedError if results have missing" do
|
84
|
+
eyes.agent_connector.stub(:stop_session).and_return(double("TestResults", mismatches: 0, missing: 2))
|
85
|
+
expect { eyes.close }.to raise_error(Applitools::TestFailedError)
|
86
|
+
end
|
87
|
+
|
88
|
+
it "doesn't raise if old session without mismatches" do
|
89
|
+
eyes.stub_chain(:agent_connector, :stop_session).and_return(double("TestResults", mismatches: 0, missing: 0))
|
90
|
+
expect(eyes.close).to_not raise_error
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ".check_window" do
|
96
|
+
it "raises EyesError if eyes aren't open"
|
97
|
+
context "no session" do
|
98
|
+
it "creates new session if no session exists"
|
99
|
+
it "creates new MatchWindowTask"
|
100
|
+
end
|
101
|
+
|
102
|
+
context "results are as expected" do
|
103
|
+
it "doesn't raise"
|
104
|
+
end
|
105
|
+
context "results are not as expected" do
|
106
|
+
context "new session" do
|
107
|
+
specify "next check_window will run once on time out"
|
108
|
+
end
|
109
|
+
context "not new session" do
|
110
|
+
context "immediate failreports" do
|
111
|
+
it "raises TestFailedError"
|
112
|
+
end
|
113
|
+
|
114
|
+
context "not immediate failreports" do
|
115
|
+
it "doesn't raise"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe ".test" do
|
122
|
+
it "opens and closes the eyes around the block execution" do
|
123
|
+
eyes.should_receive(:open).ordered
|
124
|
+
eyes.should_receive(:close).ordered
|
125
|
+
eyes.test { "foo" }
|
126
|
+
end
|
127
|
+
|
128
|
+
it "always closes eyes even if there were exceptions" do
|
129
|
+
eyes.stub(:open)
|
130
|
+
eyes.should_receive(:abort_if_not_closed)
|
131
|
+
eyes.test { raise Applitools::EyesError }
|
132
|
+
end
|
133
|
+
|
134
|
+
context "testing .test" do
|
135
|
+
let!(:eyes) do
|
136
|
+
Applitools::Eyes.new
|
137
|
+
end
|
138
|
+
it "catches triggers" do
|
139
|
+
eyes.send(:abort_if_not_closed)
|
140
|
+
eyes.test(app_name: 'my app1', test_name: 'my test') do |eyes, driver|
|
141
|
+
driver.get "http://www.applitools.com"
|
142
|
+
eyes.check_window("initial")
|
143
|
+
driver.find_element(:css, "li.pricing a").click
|
144
|
+
# return false unless user_inputs.map(&:to_hash) == [{"$type"=>"Applitools.Models.MouseTrigger, Core", :mouseAction=>1, :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>555, :top=>0, :height=>69, :width=>50}, :location=>{:latitude=>25, :longitude=>34, :altitude=>nil}}]
|
145
|
+
eyes.check_window("pricing page")
|
146
|
+
el = driver.find_elements(:css, ".footer-row a").first
|
147
|
+
driver.action.double_click(el).perform
|
148
|
+
# return false unless user_inputs.map(&:to_hash) == [{"$type"=>"Applitools.Models.MouseTrigger, Core", :mouseAction=>3, :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>0, :top=>0, :height=>115, :width=>376}, :location=>{:x=>115, :y=>376}}]
|
149
|
+
eyes.check_window("in forms")
|
150
|
+
other_el = driver.find_elements(:css, ".s2member-pro-paypal-registration-email").first
|
151
|
+
driver.action.move_to(other_el).click(other_el).send_keys("text1",:space,"text2").perform
|
152
|
+
# return false unless user_inputs.map(&:to_hash) == [{"$type"=>"Applitools.Models.MouseTrigger, Core", :mouseAction=>4, :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>0, :top=>0, :height=>230, :width=>331}, :location=>{:x=>230, :y=>331}}, {"$type"=>"Applitools.Models.MouseTrigger, Core", :mouseAction=>1, :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>0, :top=>0, :height=>230, :width=>331}, :location=>{:x=>230, :y=>331}}, {"$type"=>"Applitools.Models.TextTrigger, Core", :text=>"text1", :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>230, :top=>331, :height=>367, :width=>35}}, {"$type"=>"Applitools.Models.TextTrigger, Core", :text=>"", :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>230, :top=>331, :height=>367, :width=>35}}, {"$type"=>"Applitools.Models.TextTrigger, Core", :text=>"text2", :control=>{"$type"=>"Applitools.Utils.Geometry.MutableRegion, Core", :left=>230, :top=>331, :height=>367, :width=>35}}]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|