browsery 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,266 @@
1
+ require 'minitest/assertions'
2
+
3
+ module Browsery
4
+ module PageObjects
5
+
6
+ # The base page object. All page objects should be a subclass of this.
7
+ # Every subclass must implement the following class methods:
8
+ #
9
+ # expected_path
10
+ #
11
+ # All methods added here will be available to all subclasses, so do so
12
+ # sparingly. This class has access to assertions, which should only be
13
+ # used to validate the page.
14
+ class Base
15
+ include Minitest::Assertions
16
+ include Utils::Castable
17
+ include Utils::Loggable
18
+ include Utils::PageObjectHelper
19
+ include Utils::OverlayAndWidgetHelper
20
+ extend ElementContainer
21
+
22
+ attr_accessor :assertions
23
+ attr_accessor :failures
24
+ attr_reader :driver
25
+
26
+ # Given a set of arguments (no arguments by default), return the expected
27
+ # path to the page, which must only have file path and query-string.
28
+ #
29
+ # @param args [String] one or more arguments to be used in calculating
30
+ # the expected path, if any.
31
+ # @return [String] the expected path.
32
+ def self.expected_path(*args)
33
+ raise NotImplementedError, "expected_path is not defined for #{self}"
34
+ end
35
+
36
+ # Initializes a new page object from the driver. When a page is initialized,
37
+ # no validation occurs. As such, do not call this method directly. Rather,
38
+ # use PageObjectHelper#page in a test case, or #cast in another page object.
39
+ #
40
+ # @param driver [Selenium::WebDriver] The WebDriver instance.
41
+ def initialize(driver)
42
+ @driver = driver
43
+
44
+ @assertions = 0
45
+ @failures = []
46
+ end
47
+
48
+ def find_first(how, what)
49
+ driver.find_element(how, what)
50
+ end
51
+
52
+ def find_all(how, what)
53
+ driver.all(how, what)
54
+ end
55
+
56
+ # Returns the current path loaded in the driver.
57
+ #
58
+ # @return [String] The current path, without hostname.
59
+ def current_path
60
+ current_url.path
61
+ end
62
+
63
+ # Returns the current URL loaded in the driver.
64
+ #
65
+ # @return [String] The current URL, including hostname.
66
+ def current_url
67
+ URI.parse(driver.current_url)
68
+ end
69
+
70
+ ## interface for Overlay And Widget Helper version of get_widgets! and get_overlay!
71
+ def page_object
72
+ self
73
+ end
74
+
75
+ # Instructs the driver to visit the {expected_path}.
76
+ #
77
+ # @param args [*Object] optional parameters to pass into {expected_path}.
78
+ def go!(*args)
79
+ driver.get(driver.url_for(self.class.expected_path(*args)))
80
+ end
81
+
82
+ # Check that the page includes a certain string.
83
+ #
84
+ # @param value [String] the string to search
85
+ # @return [Boolean]
86
+ def include?(value)
87
+ driver.page_source.include?(value)
88
+ end
89
+
90
+ # Retrieves all META tags with a `name` attribute on the current page.
91
+ def meta
92
+ tags = driver.all(:css, 'meta[name]')
93
+ tags.inject(Hash.new) do |vals, tag|
94
+ vals[tag.attribute(:name)] = tag.attribute(:content) if tag.attribute(:name)
95
+ vals
96
+ end
97
+ end
98
+
99
+ def headline
100
+ driver.find_element(:css, 'body div.site-content h1').text
101
+ end
102
+
103
+ # Get page title from any page
104
+ def title
105
+ driver.title
106
+ end
107
+
108
+ # By default, any driver state is accepted for any page. This method
109
+ # should be overridden in subclasses.
110
+ def validate!
111
+ true
112
+ end
113
+
114
+ # Wait for all dom events to load
115
+ def wait_for_dom(timeout = 15)
116
+ uuid = SecureRandom.uuid
117
+ # make sure body is loaded before appending anything to it
118
+ wait(timeout: timeout, msg: "Timeout after waiting #{timeout} for body to load").until do
119
+ is_element_present?(:css, 'body')
120
+ end
121
+ driver.execute_script <<-EOS
122
+ _.defer(function() {
123
+ $('body').append("<div id='#{uuid}'></div>");
124
+ });
125
+ EOS
126
+ wait(timeout: timeout, msg: "Timeout after waiting #{timeout} for all dom events to finish").until do
127
+ is_element_present?(:css, "div[id='#{uuid}']")
128
+ end
129
+ end
130
+
131
+ # Wait on all AJAX requests to finish
132
+ def wait_for_ajax(timeout = 15)
133
+ wait(timeout: timeout, msg: "Timeout after waiting #{timeout} for all ajax requests to finish").until do
134
+ driver.execute_script 'return window.jQuery != undefined && jQuery.active == 0'
135
+ end
136
+ end
137
+
138
+ # Explicitly wait for a certain condition to be true:
139
+ # wait.until { driver.find_element(:css, 'body.tmpl-srp') }
140
+ # when timeout is not specified, default timeout 5 sec will be used
141
+ # when timeout is larger than 15, max timeout 15 sec will be used
142
+ def wait(opts = {})
143
+ if !opts[:timeout].nil? && opts[:timeout] > 15
144
+ puts "WARNING: #{opts[:timeout]} sec timeout is NOT supported by wait method,
145
+ max timeout 15 sec will be used instead"
146
+ opts[:timeout] = 15
147
+ end
148
+ Selenium::WebDriver::Wait.new(opts)
149
+ end
150
+
151
+ # Wrap blocks acting on Selenium elements and catch errors they
152
+ # raise. This probably qualifies as a Dumb LISPer Trick. If there's a
153
+ # better Ruby-ish way to do this, I welcome it. [~jacord]
154
+ def with_rescue(lbl, &blk)
155
+ yield ## run the block
156
+ ## rescue errors. Rerunning may help, but we can also test for specific
157
+ ## problems.
158
+ rescue Selenium::WebDriver::Error::ElementNotVisibleError => e
159
+ ## The element is in the DOM but e.visible? is 'false'. Retry may help.
160
+ logger.debug "Retrying #{lbl}: #{e.class}"
161
+ yield
162
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError => e
163
+ ## The page has changed and invalidated your element. Retry may help.
164
+ logger.debug "Retrying #{lbl}: #{e.class}"
165
+ yield
166
+ rescue Selenium::WebDriver::Error::NoSuchElementError => e
167
+ ## Raised by get_element(s). Retry MAY help, but check first for HTTP
168
+ ## 500, which may be best handled higher up the stack.
169
+ logger.debug "Recovering from NoSuchElementError during #{lbl}"
170
+ raise_on_error_page
171
+ ## If we got past the above, retry the block.
172
+ logger.debug "Retrying #{lbl}: #{e.class}"
173
+ yield
174
+ end
175
+
176
+ ## Wrap an action, wait for page title change. This function eliminates
177
+ ## some error-prone boilerplate around fetching page titles
178
+ def with_page_title_wait(&blk)
179
+ title = driver.title
180
+ yield
181
+ wait_for_title_change(title)
182
+ end
183
+
184
+ # returns the all the page source of a page, useful for debugging
185
+ #
186
+ def page_source
187
+ driver.page_source
188
+ end
189
+
190
+ ## PageObject validate! helper. Raises RuntimeError if one of our error
191
+ ## pages is displaying. This can prevent a test from taking the entire
192
+ ## implicit_wait before announcing error. [~jacord]
193
+ def raise_on_error_page
194
+ logger.debug "raise_on_error_page"
195
+ title = ''
196
+ begin
197
+ title = driver.title
198
+ rescue ReadTimeout
199
+ logger.debug 'ReadTimeout exception was thrown while trying to execute driver.title'
200
+ logger.debug 'ignore exception and proceed'
201
+ end
202
+ title = driver.title
203
+ logger.debug "Page Title: '#{title}'"
204
+ raise "HTTP 500 Error" if %r/Internal Server Error/ =~ title
205
+ raise "HTTP 503 Error" if %r/503 Service Temporarily Unavailable/ =~ title
206
+ raise "HTTP 404 Error" if %r/Error 404: Page Not Found/ =~ title
207
+
208
+ header = driver.find_element('body h1') rescue nil
209
+
210
+ unless header.nil?
211
+ raise "HTTP 500 Error" if header.text == 'Internal Server Error'
212
+ end
213
+
214
+ end
215
+
216
+ # click on a link on any page, cast a new page and return it
217
+ def click_on_link!(link_text, page_name)
218
+ driver.find_element(:link, link_text).location_once_scrolled_into_view
219
+ driver.find_element(:link, link_text).click
220
+ # check user angent, if it's on IE, wait 2sec for the title change
221
+ sleep 2 if driver.browser == :ie # todo remove this if every page has wait for title change in validate!
222
+ #sleep 5 #wait for 5 secs
223
+ logger.debug "click_on_link '#{link_text}'"
224
+ cast(page_name)
225
+ end
226
+
227
+ def wait_for_title_change(title)
228
+ title = driver.title if title.nil?
229
+ logger.debug("Waiting for title change from '#{title}'")
230
+ wait(timeout: 15, message: "Waited 15 sec for page transition")
231
+ .until { driver.title != title }
232
+ logger.debug("Arrived at #{driver.title}")
233
+ end
234
+
235
+ def wait_for_link(link_text)
236
+ message = "waited 15 sec, can't find link #{link_text} on page"
237
+ wait(timeout: 15, message: message).until{ driver.find_element(:link, link_text) }
238
+
239
+ unless driver.find_element(:link, link_text).displayed?
240
+ driver.navigate.refresh
241
+ end
242
+ end
243
+
244
+ # example usage:
245
+ # original_url = driver.current_url
246
+ # driver.find_element(*LINK_REGISTER).click # do some action that should cause url to change
247
+ # wait_for_url_change(original_url)
248
+ def wait_for_url_change(original_url)
249
+ time = 15
250
+ message = "waited #{time} sec, url is still #{original_url}, was expecting it to change"
251
+ wait(timeout: time, message: message).until { driver.current_url != original_url }
252
+ end
253
+
254
+ def go_to_page!(url, page_type = :base)
255
+ driver.navigate.to(url)
256
+ cast(page_type)
257
+ end
258
+
259
+ def go_to_subpage!(url_path, page_type = :base)
260
+ driver.navigate.to(driver.url_for(url_path))
261
+ cast(page_type)
262
+ end
263
+
264
+ end
265
+ end
266
+ end
@@ -0,0 +1,50 @@
1
+ module Browsery
2
+ module PageObjects
3
+ module ElementContainer
4
+
5
+ def element(element_name, *find_args)
6
+ build element_name, *find_args do |how, what|
7
+ define_method element_name.to_s do
8
+ find_first(how, what)
9
+ end
10
+ end
11
+ end
12
+
13
+ def elements(collection_name, *find_args)
14
+ build collection_name, *find_args do |how, what|
15
+ define_method collection_name.to_s do
16
+ find_all(how, what)
17
+ end
18
+ end
19
+ end
20
+ alias_method :collection, :elements
21
+
22
+ def add_to_mapped_items(item)
23
+ @mapped_items ||= []
24
+ @mapped_items << item.to_s
25
+ end
26
+
27
+ private
28
+
29
+ def build(name, *find_args)
30
+ if find_args.empty?
31
+ create_no_selector name
32
+ else
33
+ add_to_mapped_items name
34
+ if find_args.size == 1
35
+ yield(:css, *find_args)
36
+ else
37
+ yield(*find_args)
38
+ end
39
+ end
40
+ end
41
+
42
+ def create_no_selector(method_name)
43
+ define_method method_name do
44
+ fail Browsery::NoSelectorForElement.new, "#{self.class.name} => :#{method_name} needs a selector"
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,85 @@
1
+ module Browsery
2
+ module PageObjects
3
+ module Overlay
4
+
5
+ # A Overlay represents a portion (an element) of a page that is repeated
6
+ # or reproduced multiple times, either on the same page, or across multiple
7
+ # page objects or page modules.
8
+ class Base
9
+ include Utils::Castable
10
+ include Utils::PageObjectHelper
11
+ include Utils::OverlayAndWidgetHelper
12
+ extend ElementContainer
13
+
14
+ attr_reader :driver
15
+
16
+ def initialize(page)
17
+ @driver = page.driver
18
+ @page = page
19
+
20
+ # works here but not in initialize of base of page objects
21
+ # because a page instance is already present when opening an overlay
22
+ end
23
+
24
+ ## for overlay that include Utils::OverlayAndWidgetHelper
25
+ def page_object
26
+ @page
27
+ end
28
+
29
+ def find_first(how, what)
30
+ driver.find_element(how, what)
31
+ end
32
+
33
+ def find_all(how, what)
34
+ driver.all(how, what)
35
+ end
36
+
37
+ # By default, any driver state is accepted for any page. This method
38
+ # should be overridden in subclasses.
39
+ def validate!
40
+ true
41
+ end
42
+
43
+ # Wait for all dom events to load
44
+ def wait_for_dom(timeout = 15)
45
+ uuid = SecureRandom.uuid
46
+ # make sure body is loaded before appending anything to it
47
+ wait(timeout: timeout, msg: "Timeout after waiting #{timeout} for body to load").until do
48
+ is_element_present?(:css, 'body')
49
+ end
50
+ driver.execute_script <<-EOS
51
+ _.defer(function() {
52
+ $('body').append("<div id='#{uuid}'></div>");
53
+ });
54
+ EOS
55
+ wait(timeout: timeout, msg: "Timeout after waiting #{timeout} for all dom events to finish").until do
56
+ is_element_present?(:css, "div[id='#{uuid}']")
57
+ end
58
+ end
59
+
60
+ # Wait on all AJAX requests to finish
61
+ def wait_for_ajax(timeout = 15)
62
+ wait(timeout: timeout, msg: "Timeout after waiting #{timeout} for all ajax requests to finish").until do
63
+ driver.execute_script 'return window.jQuery != undefined && jQuery.active == 0'
64
+ end
65
+ end
66
+
67
+ # Explicitly wait for a certain condition to be true:
68
+ # wait.until { driver.find_element(:css, 'body.tmpl-srp') }
69
+ # when timeout is not specified, default timeout 5 sec will be used
70
+ # when timeout is larger than 15, max timeout 15 sec will be used
71
+ def wait(opts = {})
72
+ if !opts[:timeout].nil? && opts[:timeout] > 15
73
+ puts "WARNING: #{opts[:timeout]} sec timeout is NOT supported by wait method,
74
+ max timeout 15 sec will be used instead"
75
+ opts[:timeout] = 15
76
+ end
77
+ Selenium::WebDriver::Wait.new(opts)
78
+ end
79
+
80
+ end
81
+
82
+ end
83
+ end
84
+ end
85
+
@@ -0,0 +1,52 @@
1
+ module Browsery
2
+ module PageObjects
3
+ module Widgets
4
+
5
+ # A widget represents a portion (an element) of a page that is repeated
6
+ # or reproduced multiple times, either on the same page, or across multiple
7
+ # page objects or page modules.
8
+ class Base
9
+ include Utils::Castable
10
+ include Utils::PageObjectHelper
11
+ include Utils::OverlayAndWidgetHelper
12
+ extend ElementContainer
13
+
14
+ attr_reader :driver, :element, :page
15
+
16
+ def initialize(page, element)
17
+ @driver = page.driver
18
+ @page = page
19
+ @element = element
20
+ end
21
+
22
+ ## for widgets that include Utils::OverlayAndWidgetHelper
23
+ def page_object
24
+ @page
25
+ end
26
+
27
+ def find_first(how, what)
28
+ element.find_element(how, what)
29
+ end
30
+
31
+ def find_all(how, what)
32
+ element.all(how, what)
33
+ end
34
+
35
+ # Explicitly wait for a certain condition to be true:
36
+ # wait.until { driver.find_element(:css, 'body.tmpl-srp') }
37
+ # when timeout is not specified, default timeout 5 sec will be used
38
+ # when timeout is larger than 15, max timeout 15 sec will be used
39
+ def wait(opts = {})
40
+ if !opts[:timeout].nil? && opts[:timeout] > 15
41
+ puts "WARNING: #{opts[:timeout]} sec timeout is NOT supported by wait method,
42
+ max timeout 15 sec will be used instead"
43
+ opts[:timeout] = 15
44
+ end
45
+ Selenium::WebDriver::Wait.new(opts)
46
+ end
47
+
48
+ end
49
+
50
+ end
51
+ end
52
+ end