browsery 0.1.0 → 0.2.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.
@@ -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