site-object 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 300dba6834ffdc9679787bcc0a044eec07ba4578
4
- data.tar.gz: 294f6bd2a9b0a11bf6ee4b9c9aebe95158890f3b
3
+ metadata.gz: dcc551b0162213d441310f6c959457d62b70eaa0
4
+ data.tar.gz: 6fa848b3b71572a6ccdfc18190009de1a1fa5d0c
5
5
  SHA512:
6
- metadata.gz: 4ba1cafa9a61574ddcf61e1d22aac4f28b4776e85c2d7dfb5750b089435a032ca5078b38ceb59766e3b490b9e6044b3925e681965de947ca32a54070ce8d2be4
7
- data.tar.gz: 0ca164228e971c736627f0d45c9fa0af320048ea978d8ce71930d1c96ee8a1a2091f7510510af5a5c635b91fa0b99b89b304921a6bc25effa612e53520193ca9
6
+ metadata.gz: 5f0031ea59e20d8bff920e95995dc87c0e7d268989d746ec36ec1ce5915db4735b76cd64b32360875255f7e467558900a62253f9ee38e6ba2f7794b4e36742df
7
+ data.tar.gz: df0e8793178c932d4a91efc8f06265931d0dd42b624404625cefcd265b89e742d9d39b1ba4fd076b35009c7e1f319857b100e1c35570d62428c4903407546944
@@ -3,8 +3,10 @@ class ElementContainer
3
3
 
4
4
  def initialize(element)
5
5
  @element = element
6
+ binding.pry
7
+ @page = parent.site.page
6
8
  end
7
-
9
+
8
10
  def method_missing(sym, *args, &block)
9
11
  @element.send(sym, *args, &block)
10
12
  end
@@ -2,6 +2,9 @@ module SiteObjectExceptions
2
2
  class BrowserLibraryNotSupportedError < RuntimeError
3
3
  end
4
4
 
5
+ class PageConfigError < RuntimeError
6
+ end
7
+
5
8
  class PageInitError < RuntimeError
6
9
  end
7
10
 
@@ -1,14 +1,14 @@
1
1
  # Page objects are containers for all of the functionality of a page that you want to expose for testing
2
- # purposes. When you create a page object you define a URL to access it, elements for all of the page
2
+ # purposes. When you create a page object you define a URL to access it, elements for all of the page
3
3
  # elements that you want to work with as well as higher level methods that use those elements to perform
4
4
  # page operations.
5
5
  #
6
6
  # Here's a very simple account edit page example that has two fields and one button and assumes
7
7
  # that you've defined a site object called 'ExampleSite.'
8
8
  #
9
- # class AccountDetailsEditPage < ExampleSite::Page
9
+ # class AccountDetailsEditPage < ExampleSite::Page
10
10
  # set_url "/accounts/{account_code}/edit" # Parameterized URL.
11
- #
11
+ #
12
12
  # element(:first_name) {|b| b.text_field(:id, 'fname') } # text_field is a Watir method.
13
13
  # element(:last_name) {|b| b.text_field(:id, 'fname') } # text_field is a Watir method.
14
14
  # element(:save) {|b| b.button(:id, 'fname') } # text_field is a Watir method.
@@ -20,26 +20,26 @@
20
20
  # end
21
21
  # end
22
22
  #
23
- # The URL defined in the example above is "parameterized" ({account_code} is a placeholder.)
23
+ # The URL defined in the example above is "parameterized" ({account_code} is a placeholder.)
24
24
  # You don't need to specify parameters for a URL, but if you do you need to call the page with a hash
25
25
  # argument. To use the page after initializing an instance of the site object:
26
26
  #
27
- # site.account_details_edit_page(account_code: 12345)
27
+ # site.account_details_edit_page(account_code: 12345)
28
28
  #
29
- # Pages only take arguments if the URL is parameterized.
29
+ # Pages only take arguments if the URL is parameterized.
30
30
  #
31
- # Note that in the example above that there's no explicit navigation call. This is because the site will
31
+ # Note that in the example above that there's no explicit navigation call. This is because the site will
32
32
  #look at its current URL and automatically navigate to the page if it's not already on it.
33
33
  #
34
- # Here's a simple page object for the rubygems.org search page. Note that no page URL is
34
+ # Here's a simple page object for the rubygems.org search page. Note that no page URL is
35
35
  # defined using the PageObject#set_url method. This is because the page URL for the landing page is
36
- # the same as the base URL for the site. When a page URL isn't explicitly defined the base URL is used
36
+ # the same as the base URL for the site. When a page URL isn't explicitly defined the base URL is used
37
37
  # in its place:
38
38
  #
39
39
  # class LandingPage < RubyGems::Page
40
40
  # element(:search_field) { |b| b.browser.text_field(:id, 'home_query') }
41
41
  # element(:search_submit) { |b| b.browser.input(:id, 'search_submit') }
42
- #
42
+ #
43
43
  # def search(criteria)
44
44
  # search_field.set('rails')
45
45
  # search_submit.click
@@ -48,8 +48,8 @@
48
48
  # end
49
49
  #
50
50
  # Page objects aren't initialized outside of the context of a site object. When a site object is initialized
51
- # it creates accessor methods for each page object that inherits from the site's page class. In the
52
- # example above, the LandingPage class inherits from the RubyGems site object's page class so you'd
51
+ # it creates accessor methods for each page object that inherits from the site's page class. In the
52
+ # example above, the LandingPage class inherits from the RubyGems site object's page class so you'd
53
53
  # be able to use it once you've initialized a RubyGems site:
54
54
  #
55
55
  # site.landing_page.search("rails") # Returns an instance of the landing page after performing a search.
@@ -70,9 +70,9 @@ module PageObject
70
70
  ObjectSpace.each_object(Class).select { |klass| klass < self }
71
71
  end
72
72
 
73
- # This method can be used to disable page navigation when defining a page class (it sets an
73
+ # This method can be used to disable page navigation when defining a page class (it sets an
74
74
  # instance variable called @navigation during initialization.) The use case for this is a page
75
- # that can't be accessed directly and requires some level of browser interaction to reach.
75
+ # that can't be accessed directly and requires some level of browser interaction to reach.
76
76
  # To disable navigation:
77
77
  #
78
78
  # class SomePage < SomeSite::Page
@@ -80,16 +80,16 @@ module PageObject
80
80
  # end
81
81
  #
82
82
  # When navigation is disabled there will be no automatic navigation when the page is called.
83
- # If the current page is not the page that you want a SiteObject::WrongPageError will
83
+ # If the current page is not the page that you want a SiteObject::WrongPageError will
84
84
  # be raised.
85
- # If the visit method is called on the page a SiteObject::PageNavigationNotAllowedError
86
- # will be raised.
85
+ # If the visit method is called on the page a SiteObject::PageNavigationNotAllowedError
86
+ # will be raised.
87
87
  def disable_automatic_navigation
88
88
  @navigation_disabled = true
89
89
  end
90
90
 
91
91
  # Used to define access to a single HTML element on a page. This method takes two arguments:
92
- # * A symbol representing the element you are defining. This symbol is used to create an accessor
92
+ # * A symbol representing the element you are defining. This symbol is used to create an accessor
93
93
  # method on the page object.
94
94
  # * A block where access to the HTML element gets defined.
95
95
  #
@@ -97,8 +97,8 @@ module PageObject
97
97
  #
98
98
  # element(:first_name) { |b| b.text_field(:id 'signup-first-name') }
99
99
  #
100
- # In the example above, the block argument 'b' is the browser object that will get passed down from
101
- # the site to the page and used when the page needs to access the element. You can actually use any
100
+ # In the example above, the block argument 'b' is the browser object that will get passed down from
101
+ # the site to the page and used when the page needs to access the element. You can actually use any
102
102
  # label for the block argument but it's recommended that you use something like 'b' or 'browser'
103
103
  # consistently here because it's always going to be some sort of browser object.
104
104
  #
@@ -110,7 +110,7 @@ module PageObject
110
110
  # el(:first_name) { |b| b.text_field(:id 'signup-first-name') }
111
111
  def element(name, &block)
112
112
  @page_elements ||= []
113
- @page_elements << name.to_sym
113
+ @page_elements << name.to_sym
114
114
  define_method(name) do
115
115
  block.call(@browser)
116
116
  end
@@ -122,26 +122,26 @@ module PageObject
122
122
  @arguments ||= @url_template.keys.map { |k| k.to_sym }
123
123
  end
124
124
 
125
- # Used to define the full or relative URL to the page. Typically, you will *almost* *always* want to use
126
- # this method when defining a page object (but see notes below.) The URL can be defined in a number
125
+ # Used to define the full or relative URL to the page. Typically, you will *almost* *always* want to use
126
+ # this method when defining a page object (but see notes below.) The URL can be defined in a number
127
127
  # of different ways. Here are some examples using Google News:
128
128
  #
129
129
  # *Relative* *URL*
130
130
  #
131
131
  # set_url "/nwshp?hl=en"
132
132
  #
133
- # Relative URLs are most commonly used when defining page objects. The idea here is that you can
134
- # change the base_url when calling the site object, which allows you to use the same code across
133
+ # Relative URLs are most commonly used when defining page objects. The idea here is that you can
134
+ # change the base_url when calling the site object, which allows you to use the same code across
135
135
  # multiple test environments by changing the base_url as you initialize a site object.
136
136
  #
137
137
  # *Relative* *URL* *with* *URL* *Templating*
138
138
  # set_url "/nwshp?hl={language}"
139
139
  #
140
140
  # This takes the relative URL example one step further, allowing you to set the page's parameters.
141
- # Note that the the language specified in the first relative URL example ('en') was replaced by
142
- # '{language}' in this one. Siteobject uses the Addressable library, which supports this kind of
143
- # templating. When you template a value in the URL, the page object will allow you to specify the
144
- # templated value when it's being initialized. Here's an example of how this works using a news site.
141
+ # Note that the the language specified in the first relative URL example ('en') was replaced by
142
+ # '{language}' in this one. Siteobject uses the Addressable library, which supports this kind of
143
+ # templating. When you template a value in the URL, the page object will allow you to specify the
144
+ # templated value when it's being initialized. Here's an example of how this works using a news site.
145
145
  # Here's the base site object class:
146
146
  #
147
147
  # class NewsSite
@@ -154,14 +154,14 @@ module PageObject
154
154
  # set_url "/news?l={language}"
155
155
  # end
156
156
  #
157
- # After you've initialized the site object you can load the Spanish or French versions of the
157
+ # After you've initialized the site object you can load the Spanish or French versions of the
158
158
  # page by changing the hash argument used to call the page from the site object:
159
159
  #
160
160
  # site = NewsSite.new(base_url: "http://news.somesite.com")
161
161
  # site.news_page(language: 'es')
162
162
  # site.news_page(language: 'fr')
163
163
  #
164
- # In addition to providing a hash of templated values when initializing a page you can also use
164
+ # In addition to providing a hash of templated values when initializing a page you can also use
165
165
  # an object, as long as that object responds to all of the templated arguments in the page's
166
166
  # URL definition. Here's a simple class that has a language method that we can use for the news
167
167
  # page described above:
@@ -175,8 +175,8 @@ module PageObject
175
175
  # end
176
176
  #
177
177
  # In the example below, the Country class is used to create a new new country object called 'c'.
178
- # This object has been initialized with a Spanish language code and the news page
179
- # will load the spanish version of the page when it's called with the country object.
178
+ # This object has been initialized with a Spanish language code and the news page
179
+ # will load the spanish version of the page when it's called with the country object.
180
180
  #
181
181
  # site = NewsSite.new(base_url: "http://news.somesite.com")
182
182
  # c = Country.new('es')
@@ -188,7 +188,7 @@ module PageObject
188
188
  #
189
189
  # If one or more URL parameters are missing when the page is getting initialized then the page
190
190
  # will look at the hash arguments used to initialize the site. If the argument the page needs is
191
- # defined in the site's initialization arguments it will use that. For example, if the site
191
+ # defined in the site's initialization arguments it will use that. For example, if the site
192
192
  # object is initialized with a port, subdomain, or any other argument you can use those values
193
193
  # when defining a page URL. Example:
194
194
  #
@@ -199,7 +199,7 @@ module PageObject
199
199
  # site = MySite.new(subdomain: 'foo')
200
200
  # => <MySite:0x005434546511>
201
201
  # site.configuration_page # No need to provide a subdomain here as long as the site object has it.
202
- # => <ConfigPage:0x705434546541>
202
+ # => <ConfigPage:0x705434546541>
203
203
  #
204
204
  # *Full* *URL*
205
205
  # set_url "http://news.google.com/nwshp?hl=en"
@@ -208,18 +208,18 @@ module PageObject
208
208
  # to do that. Just define a complete URL for that page object and that's what will get used; the
209
209
  # base_url will be ignored.
210
210
  #
211
- # *No* *URL*
211
+ # *No* *URL*
212
212
  #
213
- # The set_url method is not mandatory. when defining a page. If you don't use set_url in the page
214
- # definition then the page will defined the base_url as the page's URL.
213
+ # The set_url method is not mandatory. when defining a page. If you don't use set_url in the page
214
+ # definition then the page will defined the base_url as the page's URL.
215
215
  def set_url(url)
216
216
  url ? @page_url = url : nil
217
- end
217
+ end
218
218
 
219
- def set_url_template(base_url)
219
+ def set_url_template(base_url)
220
220
  begin
221
221
  case @page_url.to_s
222
- when '' # There's no page URL so just assume the base URL
222
+ when '' # There's no page URL so just assume the base URL
223
223
  @url_template = Addressable::Template.new(base_url)
224
224
  when /(http:\/\/|https:\/\/)/i
225
225
  @url_template = Addressable::Template.new(@page_url)
@@ -227,15 +227,15 @@ module PageObject
227
227
  @url_template = Addressable::Template.new(Addressable::URI.parse("#{base_url}#{@page_url}"))
228
228
  end
229
229
  rescue Addressable::URI::InvalidURIError => e
230
- raise SiteObject::PageInitError, "Unable to initialize #{self.class} because there's no base_url defined for the site and the page object URL that was defined was a URL fragment (#{@page_url})\n\n#{caller.join("\n")}"
230
+ raise SiteObject::PageInitError, "Unable to initialize #{self.class} because there's no base_url defined for the site and the page object URL that was defined was a URL fragment (#{@page_url})\n\n#{caller.join("\n")}"
231
231
  end
232
232
  end
233
233
 
234
234
  # Optional. Allows you to specify a fallback mechanism for checking to see if the correct page is
235
235
  # being displayed. This only gets used in cases where the primary mechanism for checking a page
236
- # (the URL template defined by Page#set_url) fails to match the current browser URL. When that
236
+ # (the URL template defined by Page#set_url) fails to match the current browser URL. When that
237
237
  # happens the regular expression defined here will be applied and the navigation check will pass
238
- # if the regular expression matches the current browser URL.
238
+ # if the regular expression matches the current browser URL.
239
239
  #
240
240
  # In most cases, you won't need to define a URL matcher and should just rely on the default page
241
241
  # matching that uses the page's URL template. The default matching should work fine for most cases.
@@ -246,7 +246,7 @@ module PageObject
246
246
  # Used to import page features for use within the page. Example:
247
247
  #
248
248
  # class ConfigPage < MySite::Page
249
- # use_features :footer, :sidebar
249
+ # use_features :footer, :sidebar
250
250
  # end
251
251
  #
252
252
  # Then, once the page object has been initialized:
@@ -264,27 +264,27 @@ module PageObject
264
264
 
265
265
  # Takes the name of a page class. If the current page is of that class then it returns a page
266
266
  # object for the page. Raises a SiteObject::WrongPageError if that's not the case.
267
- # It's generally not a good idea to put error checking inside a page object. This should only be
267
+ # It's generally not a good idea to put error checking inside a page object. This should only be
268
268
  # used in cases where there is a page transition and that transition is always expected to work.
269
269
  def expect_page(page)
270
270
  @site.expect_page(page)
271
271
  end
272
272
 
273
- # There's no need to ever call this directly. Initializes a page object within the context of a
274
- # site object. Takes a site object and a hash of configuration arguments. The site object will
275
- # handle all of this for you.
273
+ # There's no need to ever call this directly. Initializes a page object within the context of a
274
+ # site object. Takes a site object and a hash of configuration arguments. The site object will
275
+ # handle all of this for you.
276
276
  def initialize(site, args={})
277
- @browser = site.browser
277
+ @browser = site.browser
278
278
  @navigation_disabled = self.class.navigation_disabled
279
279
  @page_url = self.class.page_url
280
280
  @page_elements = self.class.page_elements
281
281
  @page_features = self.class.page_features
282
282
  @required_arguments = self.class.required_arguments
283
283
  @site = site
284
- @url_matcher = self.class.url_matcher
285
- @url_template = self.class.url_template
284
+ @url_matcher = self.class.url_matcher
285
+ @url_template = self.class.url_template
286
286
 
287
- # Try to expand the URL template if the URL has parameters.
287
+ # Try to expand the URL template if the URL has parameters.
288
288
  @arguments = {}.with_indifferent_access # Stores the param list that will expand the url_template after examining the arguments used to initialize the page.
289
289
  if @required_arguments.length > 0
290
290
  @required_arguments.each do |arg| # Try to extract each URL argument from the hash or object provided, OR from the site object.
@@ -336,7 +336,7 @@ module PageObject
336
336
  end
337
337
  end
338
338
  end
339
-
339
+
340
340
  @site.most_recent_page = self
341
341
  unless on_page?
342
342
  if @navigation_disabled
@@ -345,8 +345,8 @@ module PageObject
345
345
  else
346
346
  raise SiteObject::PageNavigationNotAllowedError, "The #{self.class.name} page could not be accessed. Navigation is intentionally disabled for this page and the page that the browser was displaying could not be recognized.\n\nPAGE URL:\n------------\n#{@site.browser.url}\n\nPAGE TEXT:\n------------\n#{@site.browser.text}\n\n#{caller.join("\n")}"
347
347
  end
348
- end
349
- visit
348
+ end
349
+ visit
350
350
  end
351
351
  end
352
352
 
@@ -354,32 +354,31 @@ module PageObject
354
354
  def inspect
355
355
  "#<#{self.class.name}:#{object_id} @url_template=#{@url_template.inspect}>"
356
356
  end
357
-
357
+
358
358
  # Returns true if the page defined by the page object is currently being displayed in the browser,
359
359
  # false if not. It does this in two different ways, which are described below.
360
360
  #
361
361
  # A page always has a URL defined for it. This is typically done by using the Page.set_url method
362
362
  # to specify a URL when defining the page. You can skip using the set_url method but in that case
363
- # the page URL defaults to the base URL defined for the site object.
363
+ # the page URL defaults to the base URL defined for the site object.
364
364
  #
365
365
  # The default approach for determining if the page is being displayed relies on the URL defined for
366
366
  # the page. This method first does a general match against the current browser URL page's URL template.
367
367
  # If a match occurs here, and there are no required arguments for the page the method returns true.
368
- # If the page's URL template does require arguments the method performs an additional check to
369
- # verify that each of the arguments defined for the page match what's in the current browser URL.
370
- # If all of the arguments match then the method will return true.
368
+ # If the page's URL template does require arguments the method performs an additional check to
369
+ # verify that each of the arguments defined for the page match what's in the current browser URL.
370
+ # If all of the arguments match then the method will return true.
371
371
  #
372
372
  # This should work for most cases but may not always be enough. For example, there may be a redirect
373
- # and the URL used to navigate to the page may not be the final page URL. There's a fallback
373
+ # and the URL used to navigate to the page may not be the final page URL. There's a fallback
374
374
  # mechanism for these sorts of situations. You can use the Page.set_url_matcher method to define a
375
375
  # regular expression that the method will use in place of the URL template. If the regular expression
376
- # matches, then the method will return true even if the URL wouldn't match the URL template.
376
+ # matches, then the method will return true even if the URL wouldn't match the URL template.
377
377
  #
378
378
  # It's better to use the default URL matching if possible. But if for some reason it's not feasible
379
379
  # you can use the alternate method to specify how to match the page.
380
380
  def on_page?
381
381
  url = @browser.url
382
-
383
382
  if @url_matcher && @url_matcher =~ url
384
383
  return true
385
384
  elsif @url_template.match(url)
@@ -395,9 +394,9 @@ module PageObject
395
394
  false
396
395
  end
397
396
 
398
- # Refreshes the page.
397
+ # Refreshes the page.
399
398
  def refresh # TODO: Isolate browser library-specific code so that the adding new browser
400
- if @browser.is_a?(Watir::Browser)
399
+ if @browser.is_a?(Watir::Browser)
401
400
  @browser.refresh
402
401
  elsif @browser.is_a?(Selenium::WebDriver::Driver)
403
402
  @browser.navigate.refresh
@@ -407,28 +406,28 @@ module PageObject
407
406
  self
408
407
  end
409
408
 
410
- # Navigates to the page that it's called on. Raises a SiteObject::PageNavigationNotAllowedError when
411
- # navigation has been disabled for the page. Raises a SiteObject::WrongPageError if the
409
+ # Navigates to the page that it's called on. Raises a SiteObject::PageNavigationNotAllowedError when
410
+ # navigation has been disabled for the page. Raises a SiteObject::WrongPageError if the
412
411
  # specified page isn't getting displayed after navigation.
413
412
  def visit
414
413
  if @navigation_disabled
415
414
  raise SiteObject::PageNavigationNotAllowedError, "Navigation has been disabled for the #{self.class.name} page. This was done when defining the page class and usually means that the page can't be reached directly through a URL and requires some additional work to access."
416
- end
415
+ end
417
416
  if @browser.is_a?(Watir::Browser)
418
417
  @browser.goto(@url)
419
418
  elsif @browser.is_a?(Selenium::WebDriver::Driver)
420
419
  @browser.get(@url)
421
420
  else
422
421
  raise SiteObject::BrowserLibraryNotSupportedError, "Only Watir-Webdriver and Selenium Webdriver are currently supported. Class of browser object: #{@browser.class.name}"
423
- end
424
-
422
+ end
423
+
425
424
  if @url_matcher
426
425
  raise SiteObject::WrongPageError, "Navigation check failed after attempting to access the #{self.class.name} page. Current URL #{@browser.url} did not match #{@url_template.pattern}. A URL matcher was also defined for the page and the secondary check against the URL matcher also failed. URL matcher: #{@url_matcher}" unless on_page?
427
426
  else
428
427
  raise SiteObject::WrongPageError, "Navigation check failed after attempting to access the #{self.class.name} page. Current URL #{@browser.url} did not match #{@url_template.pattern}" unless on_page?
429
428
  end
430
429
 
431
- @site.most_recent_page = self
430
+ @site.most_recent_page = self
432
431
  self
433
432
  end
434
433
  end
@@ -83,6 +83,12 @@ module SiteObject
83
83
  @pages.each do |current_page|
84
84
  current_page.set_url_template(@base_url)
85
85
 
86
+ if current_page.url_matcher
87
+ unless current_page.url_matcher.is_a? Regexp
88
+ raise SiteObject::PageConfigError, "A url_matcher was defined for the #{current_page} page but it was not a regular expression. Check the value provided to the set_url_matcher method in the class definition for this page. Object provided was a #{current_page.url_matcher.class.name}"
89
+ end
90
+ end
91
+
86
92
  self.class.class_eval do
87
93
  define_method(current_page.to_s.underscore) do |args={}, block=nil|
88
94
  current_page.new(self, args)
@@ -1,3 +1,3 @@
1
1
  module SiteObject
2
- VERSION = '0.2.0'
2
+ VERSION = '0.2.1'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: site-object
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - John Fitisoff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-21 00:00:00.000000000 Z
11
+ date: 2015-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -80,11 +80,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
80
  version: '0'
81
81
  requirements: []
82
82
  rubyforge_project: site-object
83
- rubygems_version: 2.2.3
83
+ rubygems_version: 2.2.2
84
84
  signing_key:
85
85
  specification_version: 4
86
86
  summary: Wraps page objects up into a site object, which provides some introspection
87
87
  and navigation capabilities that page objects don't provide. Works with Watir and
88
88
  Selenium.
89
89
  test_files: []
90
- has_rdoc: