eyes_selenium 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE.txt +13 -0
  5. data/README.md +82 -0
  6. data/Rakefile +1 -0
  7. data/eyes_selenium.gemspec +30 -0
  8. data/lib/eyes_selenium.rb +42 -0
  9. data/lib/eyes_selenium/capybara.rb +21 -0
  10. data/lib/eyes_selenium/eyes/agent_connecter.rb +39 -0
  11. data/lib/eyes_selenium/eyes/batch_info.rb +14 -0
  12. data/lib/eyes_selenium/eyes/dimension.rb +15 -0
  13. data/lib/eyes_selenium/eyes/driver.rb +146 -0
  14. data/lib/eyes_selenium/eyes/element.rb +78 -0
  15. data/lib/eyes_selenium/eyes/environment.rb +14 -0
  16. data/lib/eyes_selenium/eyes/eyes.rb +182 -0
  17. data/lib/eyes_selenium/eyes/eyes_keyboard.rb +25 -0
  18. data/lib/eyes_selenium/eyes/eyes_mouse.rb +60 -0
  19. data/lib/eyes_selenium/eyes/failure_reports.rb +4 -0
  20. data/lib/eyes_selenium/eyes/match_level.rb +7 -0
  21. data/lib/eyes_selenium/eyes/match_window_data.rb +18 -0
  22. data/lib/eyes_selenium/eyes/match_window_task.rb +71 -0
  23. data/lib/eyes_selenium/eyes/mouse_trigger.rb +19 -0
  24. data/lib/eyes_selenium/eyes/region.rb +22 -0
  25. data/lib/eyes_selenium/eyes/screenshot_taker.rb +18 -0
  26. data/lib/eyes_selenium/eyes/session.rb +14 -0
  27. data/lib/eyes_selenium/eyes/start_info.rb +34 -0
  28. data/lib/eyes_selenium/eyes/target_app.rb +17 -0
  29. data/lib/eyes_selenium/eyes/test_results.rb +21 -0
  30. data/lib/eyes_selenium/eyes/text_trigger.rb +15 -0
  31. data/lib/eyes_selenium/eyes/viewport_size.rb +104 -0
  32. data/lib/eyes_selenium/eyes_logger.rb +18 -0
  33. data/lib/eyes_selenium/utils.rb +5 -0
  34. data/lib/eyes_selenium/utils/image_delta_compressor.rb +147 -0
  35. data/lib/eyes_selenium/version.rb +3 -0
  36. data/spec/capybara_spec.rb +34 -0
  37. data/spec/driver_spec.rb +10 -0
  38. data/spec/eyes_spec.rb +157 -0
  39. data/spec/spec_helper.rb +29 -0
  40. data/spec/test_app.rb +11 -0
  41. data/test_script.rb +21 -0
  42. 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,5 @@
1
+ module Applitools
2
+ module Utils
3
+ require 'eyes_selenium/utils/image_delta_compressor'
4
+ end
5
+ 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,3 @@
1
+ module Applitools
2
+ VERSION = "1.6.0"
3
+ 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
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe Applitools::Driver do
4
+ describe "#driver" do
5
+ let(:driver) { Applitools::Driver.new }
6
+
7
+ it "has a selenium driver" do
8
+ end
9
+ end
10
+ end
@@ -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