page-object 2.2.2 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +5 -5
  2. data/.coveralls.yml +1 -1
  3. data/.gitignore +8 -8
  4. data/.rspec +2 -2
  5. data/.ruby-gemset +1 -1
  6. data/.ruby-version +1 -1
  7. data/.travis.yml +17 -17
  8. data/ChangeLog +923 -888
  9. data/Gemfile +13 -13
  10. data/Guardfile +20 -20
  11. data/LICENSE +20 -20
  12. data/README.md +114 -114
  13. data/Rakefile +29 -29
  14. data/cucumber.yml +8 -8
  15. data/lib/page-object.rb +431 -420
  16. data/lib/page-object/accessors.rb +1201 -1175
  17. data/lib/page-object/element_locators.rb +21 -21
  18. data/lib/page-object/elements.rb +62 -61
  19. data/lib/page-object/elements/area.rb +9 -9
  20. data/lib/page-object/elements/audio.rb +9 -9
  21. data/lib/page-object/elements/bold.rb +9 -9
  22. data/lib/page-object/elements/button.rb +12 -12
  23. data/lib/page-object/elements/canvas.rb +10 -10
  24. data/lib/page-object/elements/check_box.rb +9 -9
  25. data/lib/page-object/elements/date_field.rb +10 -0
  26. data/lib/page-object/elements/div.rb +9 -9
  27. data/lib/page-object/elements/element.rb +212 -159
  28. data/lib/page-object/elements/file_field.rb +9 -9
  29. data/lib/page-object/elements/form.rb +9 -9
  30. data/lib/page-object/elements/heading.rb +14 -14
  31. data/lib/page-object/elements/hidden_field.rb +9 -9
  32. data/lib/page-object/elements/image.rb +10 -10
  33. data/lib/page-object/elements/italic.rb +9 -9
  34. data/lib/page-object/elements/label.rb +9 -9
  35. data/lib/page-object/elements/link.rb +9 -9
  36. data/lib/page-object/elements/list_item.rb +9 -9
  37. data/lib/page-object/elements/media.rb +11 -11
  38. data/lib/page-object/elements/option.rb +9 -9
  39. data/lib/page-object/elements/ordered_list.rb +43 -45
  40. data/lib/page-object/elements/paragraph.rb +9 -9
  41. data/lib/page-object/elements/radio_button.rb +9 -9
  42. data/lib/page-object/elements/select_list.rb +42 -42
  43. data/lib/page-object/elements/span.rb +9 -9
  44. data/lib/page-object/elements/table.rb +85 -68
  45. data/lib/page-object/elements/table_cell.rb +10 -10
  46. data/lib/page-object/elements/table_row.rb +52 -52
  47. data/lib/page-object/elements/text_area.rb +9 -9
  48. data/lib/page-object/elements/text_field.rb +10 -10
  49. data/lib/page-object/elements/unordered_list.rb +42 -44
  50. data/lib/page-object/elements/video.rb +9 -9
  51. data/lib/page-object/indexed_properties.rb +41 -41
  52. data/lib/page-object/javascript/angularjs.rb +14 -14
  53. data/lib/page-object/javascript/jquery.rb +14 -14
  54. data/lib/page-object/javascript/prototype.rb +14 -14
  55. data/lib/page-object/javascript/yui.rb +18 -18
  56. data/lib/page-object/javascript_framework_facade.rb +80 -80
  57. data/lib/page-object/locator_generator.rb +183 -182
  58. data/lib/page-object/nested_elements.rb +17 -17
  59. data/lib/page-object/page_factory.rb +108 -108
  60. data/lib/page-object/page_populator.rb +105 -95
  61. data/lib/page-object/platforms/watir.rb +50 -50
  62. data/lib/page-object/platforms/watir/page_object.rb +1155 -1124
  63. data/lib/page-object/section_collection.rb +16 -16
  64. data/lib/page-object/version.rb +4 -4
  65. data/lib/page-object/widgets.rb +98 -98
  66. data/page-object.gemspec +32 -32
  67. metadata +12 -12
data/Gemfile CHANGED
@@ -1,13 +1,13 @@
1
- source "https://rubygems.org"
2
-
3
- # adding rake so travis-ci will build properly
4
- gem 'rake'
5
- gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
6
- gem 'growl'
7
- gem 'guard-rspec'
8
- gem 'listen', '3.0.8' #Last version that supports ruby 2.0
9
- gem 'guard-cucumber'
10
- gem 'coveralls', require: false
11
-
12
-
13
- gemspec
1
+ source "https://rubygems.org"
2
+
3
+ # adding rake so travis-ci will build properly
4
+ gem 'rake'
5
+ gem 'rb-fsevent', :require => false if RUBY_PLATFORM =~ /darwin/i
6
+ gem 'growl'
7
+ gem 'guard-rspec'
8
+ gem 'listen', '3.0.8' #Last version that supports ruby 2.0
9
+ gem 'guard-cucumber'
10
+ gem 'coveralls', require: false
11
+
12
+
13
+ gemspec
data/Guardfile CHANGED
@@ -1,20 +1,20 @@
1
- # A sample Guardfile
2
- # More info at https://github.com/guard/guard#readme
3
-
4
-
5
- guard :rspec, cmd: 'rspec --color --format documentation' do
6
- watch(%r{^spec/.+_spec\.rb$})
7
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
- watch(%r{^lib/page-object/platforms/(.+)/.+\.rb$}) do |m|
9
- ["spec/page-object/platforms/#{m[1]}/", "spec/page-object/page-object_spec.rb"]
10
- end
11
- watch('spec/spec_helper.rb') { "spec" }
12
- end
13
-
14
- guard 'cucumber', notification: true, all_after_pass: false, cli: '--profile focus' do
15
- watch(%r{^features/.+\.feature$})
16
- watch(%r{^features/support/.+$}) { "features" }
17
- watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
18
- watch(%r{^lib/.+\.rb$}) { "features" }
19
- watch(%r{^cucumber.yml$}) { "features" }
20
- end
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+
5
+ guard :rspec, cmd: 'rspec --color --format documentation' do
6
+ watch(%r{^spec/.+_spec\.rb$})
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
+ watch(%r{^lib/page-object/platforms/(.+)/.+\.rb$}) do |m|
9
+ ["spec/page-object/platforms/#{m[1]}/", "spec/page-object/page-object_spec.rb"]
10
+ end
11
+ watch('spec/spec_helper.rb') { "spec" }
12
+ end
13
+
14
+ guard 'cucumber', notification: true, all_after_pass: false, cli: '--profile focus' do
15
+ watch(%r{^features/.+\.feature$})
16
+ watch(%r{^features/support/.+$}) { "features" }
17
+ watch(%r{^features/step_definitions/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'features' }
18
+ watch(%r{^lib/.+\.rb$}) { "features" }
19
+ watch(%r{^cucumber.yml$}) { "features" }
20
+ end
data/LICENSE CHANGED
@@ -1,20 +1,20 @@
1
- Copyright (c) 2011-2012 Jeff Morgan
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
1
+ Copyright (c) 2011-2012 Jeff Morgan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -1,114 +1,114 @@
1
- # page-object
2
-
3
- [![Gem Version](https://badge.fury.io/rb/page-object.svg)](https://rubygems.org/gems/page-object)
4
- [![Build Status](https://travis-ci.org/cheezy/page-object.svg)](https://travis-ci.org/cheezy/page-object)
5
- [![Coverage Status](https://coveralls.io/repos/cheezy/page-object/badge.svg?nocache)](https://coveralls.io/r/cheezy/page-object)
6
-
7
-
8
- A simple gem that assists in creating flexible page objects for testing browser based applications. The goal is to facilitate creating abstraction layers in your tests to decouple the tests from the item they are testing and to provide a simple interface to the elements on a page. It works with both watir and selenium-webdriver.
9
-
10
- ## Documentation
11
-
12
- The project [wiki](https://github.com/cheezy/page-object/wiki/page-object) is the first place to go to learn about how to use page-object.
13
-
14
- The rdocs for this project can be found at [rubydoc.info](http://rubydoc.info/gems/page-object/frames).
15
-
16
- To see the changes from release to release please look at the [ChangeLog](https://raw.github.com/cheezy/page-object/master/ChangeLog)
17
-
18
- To read about the motivation for this gem please read this [blog entry](http://www.cheezyworld.com/2010/11/19/ui-tests-introducing-a-simple-dsl/)
19
-
20
- There is a book that describes in detail how to use this gem and others to create a complete view of testing web and other types of applications. The book is named [Cucumber & Cheese](http://leanpub.com/cucumber_and_cheese)
21
-
22
- ## Support
23
-
24
- If you need help using the page-object gem please ask your questions on [Stack Overflow](http://stackoverflow.com). Please be sure to use the [page-object-gem](http://stackoverflow.com/questions/tagged/page-object-gem) tag. If you wish to report an issue or request a new feature use the [github issue tracking page](http://github.com/cheezy/page-object/issues).
25
-
26
- ## Basic Usage
27
-
28
- ### Defining your page object
29
-
30
- You define a new page object by including the PageObject module:
31
-
32
- ````ruby
33
- class LoginPage
34
- include PageObject
35
- end
36
- ````
37
-
38
- When you include this module numerous methods are added to your class that allow you to easily define your page. For the login page you might add the following:
39
-
40
- ````ruby
41
- class LoginPage
42
- include PageObject
43
-
44
- text_field(:username, :id => 'username')
45
- text_field(:password, :id => 'password')
46
- button(:login, :id => 'login')
47
- end
48
- ````
49
-
50
- Calling the _text_field_ and _button_ methods adds several methods to our page object that allow us to interact with the items on the page. To login using this page we could simply write the following code:
51
-
52
- ````ruby
53
- login_page.username = 'cheezy'
54
- login_page.password = 'secret'
55
- login_page.login
56
- ````
57
-
58
- Another approach might be to create higher level methods on our page object that hide the implementation details even further. Our page object might look like this:
59
-
60
- ````ruby
61
- class LoginPage
62
- include PageObject
63
-
64
- text_field(:username, :id => 'username')
65
- text_field(:password, :id => 'password')
66
- button(:login, :id => 'login')
67
-
68
- def login_with(username, password)
69
- self.username = username
70
- self.password = password
71
- login
72
- end
73
- end
74
- ````
75
-
76
- and your usage of the page would become:
77
-
78
- ````ruby
79
- login_page.login_with 'cheezy', 'secret'
80
- ````
81
-
82
- ### Creating your page object
83
- page-object supports both [watir](https://github.com/watir/watir) and [selenium-webdriver](http://seleniumhq.org/docs/03_webdriver.html). The one used will be determined by which driver you pass into the constructor of your page object. The page object can be created like this:
84
-
85
- ````ruby
86
- browser = Watir::Browser.new :firefox
87
- my_page_object = MyPageObject.new(browser)
88
- ````
89
-
90
- or
91
-
92
- ````ruby
93
- browser = Selenium::WebDriver.for :firefox
94
- my_page_object = MyPageObject.new(browser)
95
- ````
96
-
97
-
98
- ## Known Issues
99
-
100
- See [http://github.com/cheezy/page-object/issues](http://github.com/cheezy/page-object/issues)
101
-
102
- ## Contribute
103
-
104
- * Fork the project.
105
- * Test drive your feature addition or bug fix. Adding specs is important and I will not accept a pull request that does not have tests.
106
- * Make sure you describe your new feature with a cucumber scenario.
107
- * Make sure you provide RDoc comments for any new public method you add. Remember, others will be using this gem.
108
- * Commit, do not mess with Rakefile, version, or ChangeLog.
109
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
110
- * Send me a pull request. Bonus points for topic branches.
111
-
112
- ## Copyright
113
-
114
- Copyright (c) 2011-2012 Jeffrey S. Morgan. See LICENSE for details.
1
+ # page-object
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/page-object.svg)](https://rubygems.org/gems/page-object)
4
+ [![Build Status](https://travis-ci.org/cheezy/page-object.svg)](https://travis-ci.org/cheezy/page-object)
5
+ [![Coverage Status](https://coveralls.io/repos/cheezy/page-object/badge.svg?nocache)](https://coveralls.io/r/cheezy/page-object)
6
+
7
+
8
+ A simple gem that assists in creating flexible page objects for testing browser based applications. The goal is to facilitate creating abstraction layers in your tests to decouple the tests from the item they are testing and to provide a simple interface to the elements on a page. It works with both watir and selenium-webdriver.
9
+
10
+ ## Documentation
11
+
12
+ The project [wiki](https://github.com/cheezy/page-object/wiki/page-object) is the first place to go to learn about how to use page-object.
13
+
14
+ The rdocs for this project can be found at [rubydoc.info](http://rubydoc.info/gems/page-object/frames).
15
+
16
+ To see the changes from release to release please look at the [ChangeLog](https://raw.github.com/cheezy/page-object/master/ChangeLog)
17
+
18
+ To read about the motivation for this gem please read this [blog entry](http://www.cheezyworld.com/2010/11/19/ui-tests-introducing-a-simple-dsl/)
19
+
20
+ There is a book that describes in detail how to use this gem and others to create a complete view of testing web and other types of applications. The book is named [Cucumber & Cheese](http://leanpub.com/cucumber_and_cheese)
21
+
22
+ ## Support
23
+
24
+ If you need help using the page-object gem please ask your questions on [Stack Overflow](http://stackoverflow.com). Please be sure to use the [page-object-gem](http://stackoverflow.com/questions/tagged/page-object-gem) tag. If you wish to report an issue or request a new feature use the [github issue tracking page](http://github.com/cheezy/page-object/issues).
25
+
26
+ ## Basic Usage
27
+
28
+ ### Defining your page object
29
+
30
+ You define a new page object by including the PageObject module:
31
+
32
+ ````ruby
33
+ class LoginPage
34
+ include PageObject
35
+ end
36
+ ````
37
+
38
+ When you include this module numerous methods are added to your class that allow you to easily define your page. For the login page you might add the following:
39
+
40
+ ````ruby
41
+ class LoginPage
42
+ include PageObject
43
+
44
+ text_field(:username, :id => 'username')
45
+ text_field(:password, :id => 'password')
46
+ button(:login, :id => 'login')
47
+ end
48
+ ````
49
+
50
+ Calling the _text_field_ and _button_ methods adds several methods to our page object that allow us to interact with the items on the page. To login using this page we could simply write the following code:
51
+
52
+ ````ruby
53
+ login_page.username = 'cheezy'
54
+ login_page.password = 'secret'
55
+ login_page.login
56
+ ````
57
+
58
+ Another approach might be to create higher level methods on our page object that hide the implementation details even further. Our page object might look like this:
59
+
60
+ ````ruby
61
+ class LoginPage
62
+ include PageObject
63
+
64
+ text_field(:username, :id => 'username')
65
+ text_field(:password, :id => 'password')
66
+ button(:login, :id => 'login')
67
+
68
+ def login_with(username, password)
69
+ self.username = username
70
+ self.password = password
71
+ login
72
+ end
73
+ end
74
+ ````
75
+
76
+ and your usage of the page would become:
77
+
78
+ ````ruby
79
+ login_page.login_with 'cheezy', 'secret'
80
+ ````
81
+
82
+ ### Creating your page object
83
+ page-object supports both [watir](https://github.com/watir/watir) and [selenium-webdriver](http://seleniumhq.org/docs/03_webdriver.html). The one used will be determined by which driver you pass into the constructor of your page object. The page object can be created like this:
84
+
85
+ ````ruby
86
+ browser = Watir::Browser.new :firefox
87
+ my_page_object = MyPageObject.new(browser)
88
+ ````
89
+
90
+ or
91
+
92
+ ````ruby
93
+ browser = Selenium::WebDriver.for :firefox
94
+ my_page_object = MyPageObject.new(browser)
95
+ ````
96
+
97
+
98
+ ## Known Issues
99
+
100
+ See [http://github.com/cheezy/page-object/issues](http://github.com/cheezy/page-object/issues)
101
+
102
+ ## Contribute
103
+
104
+ * Fork the project.
105
+ * Test drive your feature addition or bug fix. Adding specs is important and I will not accept a pull request that does not have tests.
106
+ * Make sure you describe your new feature with a cucumber scenario.
107
+ * Make sure you provide RDoc comments for any new public method you add. Remember, others will be using this gem.
108
+ * Commit, do not mess with Rakefile, version, or ChangeLog.
109
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
110
+ * Send me a pull request. Bonus points for topic branches.
111
+
112
+ ## Copyright
113
+
114
+ Copyright (c) 2011-2012 Jeffrey S. Morgan. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,29 +1,29 @@
1
- require 'rubygems'
2
- require 'bundler'
3
- require 'rspec/core/rake_task'
4
- require 'cucumber'
5
- require 'cucumber/rake/task'
6
- require 'coveralls/rake/task'
7
-
8
- Coveralls::RakeTask.new
9
- Bundler::GemHelper.install_tasks
10
-
11
- RSpec::Core::RakeTask.new(:spec) do |spec|
12
- spec.ruby_opts = "-I lib:spec"
13
- spec.pattern = 'spec/**/*_spec.rb'
14
- end
15
- task :spec
16
-
17
- Cucumber::Rake::Task.new(:features, "Run the cucumber features")
18
-
19
-
20
- desc 'Run all specs and cukes'
21
- task :test => ['spec', 'features']
22
-
23
- task :lib do
24
- $LOAD_PATH.unshift(File.expand_path("lib", File.dirname(__FILE__)))
25
- end
26
-
27
- task :test_with_coveralls => [:test, 'coveralls:push']
28
-
29
- task :default => :test_with_coveralls
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'rspec/core/rake_task'
4
+ require 'cucumber'
5
+ require 'cucumber/rake/task'
6
+ require 'coveralls/rake/task'
7
+
8
+ Coveralls::RakeTask.new
9
+ Bundler::GemHelper.install_tasks
10
+
11
+ RSpec::Core::RakeTask.new(:spec) do |spec|
12
+ spec.ruby_opts = "-I lib:spec"
13
+ spec.pattern = 'spec/**/*_spec.rb'
14
+ end
15
+ task :spec
16
+
17
+ Cucumber::Rake::Task.new(:features, "Run the cucumber features")
18
+
19
+
20
+ desc 'Run all specs and cukes'
21
+ task :test => ['spec', 'features']
22
+
23
+ task :lib do
24
+ $LOAD_PATH.unshift(File.expand_path("lib", File.dirname(__FILE__)))
25
+ end
26
+
27
+ task :test_with_coveralls => [:test, 'coveralls:push']
28
+
29
+ task :default => :test_with_coveralls
data/cucumber.yml CHANGED
@@ -1,8 +1,8 @@
1
- <%
2
- std_opts = "--no-source --color --format pretty" # Cucumber::Formatter::Fuubar"
3
- %>
4
-
5
- default: DRIVER=WATIR <%= std_opts %>
6
- selenium: DRIVER=SELENIUM <%= std_opts %>
7
- focus: DRIVER=WATIR <%= std_opts %> --tags ~@selenium_only --tags @focus
8
-
1
+ <%
2
+ std_opts = "--no-source --color --format pretty" # Cucumber::Formatter::Fuubar"
3
+ %>
4
+
5
+ default: DRIVER=WATIR <%= std_opts %>
6
+ selenium: DRIVER=SELENIUM <%= std_opts %>
7
+ focus: DRIVER=WATIR <%= std_opts %> --tags ~@selenium_only --tags @focus
8
+
data/lib/page-object.rb CHANGED
@@ -1,420 +1,431 @@
1
- require 'watir'
2
- require 'page-object/version'
3
- require 'page-object/accessors'
4
- require 'page-object/element_locators'
5
- require 'page-object/nested_elements'
6
- require 'page-object/page_factory'
7
- require 'page-object/page_populator'
8
- require 'page-object/javascript_framework_facade'
9
- require 'page-object/indexed_properties'
10
- require 'page-object/section_collection'
11
- require 'page-object/widgets'
12
-
13
- require 'page-object/platforms/watir'
14
- require 'page-object/platforms/watir/page_object'
15
-
16
- #
17
- # Module that when included adds functionality to a page object. This module
18
- # will add numerous class and instance methods that you use to define and
19
- # interact with web pages.
20
- #
21
- # If we have a login page with a username and password textfield and a login
22
- # button we might define our page like the one below. We can then interact with
23
- # the object using the generated methods.
24
- #
25
- # @example Login page example
26
- # class LoginPage
27
- # include PageObject
28
- #
29
- # text_field(:username, :id => 'user')
30
- # text_field(:password, :id => 'pass')
31
- # button(:login, :value => 'Login')
32
- # end
33
- #
34
- # ...
35
- #
36
- # browser = Watir::Browser.new :firefox
37
- # login_page = LoginPage.new(browser)
38
- # login_page.username = 'cheezy'
39
- # login_page.password = 'secret'
40
- # login_page.login
41
- #
42
- # @see PageObject::Accessors to see what class level methods are added to this module at runtime.
43
- #
44
- module PageObject
45
- include ElementLocators
46
- include PagePopulator
47
-
48
- def method_missing(method, *args, &block)
49
- @root_element.send(method, *args, &block)
50
- end
51
-
52
- def respond_to_missing?(method, include_all = false)
53
- @root_element && @root_element.respond_to?(method) || super
54
- end
55
-
56
- # @return the platform browser passed to the constructor
57
- attr_reader :browser
58
- # @return [PageObject::WatirPageObject] the platform page object
59
- attr_reader :platform
60
-
61
- #
62
- # Construct a new page object. Prior to browser initialization it will call
63
- # a method named initialize_accessors if it exists. Upon initialization of
64
- # the page it will call a method named initialize_page if it exists.
65
- #
66
- # @param [Watir::Browser, Watir::HTMLElement or Selenium::WebDriver::Driver, Selenium::WebDriver::Element] the platform browser/element to use
67
- # @param [bool] open the page if page_url is set
68
- #
69
- def initialize(root, visit=false)
70
- initialize_accessors if respond_to?(:initialize_accessors)
71
- initialize_browser(root)
72
- goto if visit && self.class.instance_methods(false).include?(:goto)
73
- initialize_page if respond_to?(:initialize_page)
74
- end
75
-
76
- def initialize_browser(root)
77
- @root_element = PageObject::Platforms::Watir.root_element_for root
78
- @browser = root
79
- @platform = PageObject::Platforms::Watir.create_page_object @browser
80
- end
81
-
82
- # @private
83
- def self.included(cls)
84
- cls.extend PageObject::Accessors
85
- end
86
-
87
- #
88
- # Set the default timeout for page level waits
89
- #
90
- def self.default_page_wait=(timeout)
91
- @page_wait = timeout
92
- end
93
-
94
- #
95
- # Returns the default timeout for page lavel waits
96
- #
97
- def self.default_page_wait
98
- @page_wait ||= 30
99
- end
100
-
101
- #
102
- # Sets the default timeout for element level waits
103
- #
104
- def self.default_element_wait=(timeout)
105
- @element_wait = timeout
106
- end
107
-
108
- #
109
- # Returns the default timeout for element level waits
110
- #
111
- def self.default_element_wait
112
- @element_wait ||= 5
113
- end
114
-
115
- #
116
- # Set the javascript framework to use when determining number of
117
- # ajax requests. Valid frameworks are :jquery, :prototype, :yui,
118
- # and :angularjs
119
- #
120
- def self.javascript_framework=(framework)
121
- PageObject::JavascriptFrameworkFacade.framework = framework
122
- end
123
-
124
- #
125
- # Add a new javascript framework to page-object. The module passed
126
- # in must adhere to the same prototype as the JQuery and Prototype
127
- # modules.
128
- #
129
- # @param [Symbol] the name used to reference the framework in
130
- # subsequent calls
131
- # @param [Module] a module that has the necessary methods to perform
132
- # the required actions.
133
- #
134
- def self.add_framework(key, framework)
135
- PageObject::JavascriptFrameworkFacade.add_framework(key, framework)
136
- end
137
-
138
- #
139
- # get the current page url
140
- #
141
- def current_url
142
- platform.current_url
143
- end
144
-
145
- #
146
- # navigate to the provided url
147
- #
148
- # @param [String] the full url to navigate to
149
- #
150
- def navigate_to(url)
151
- platform.navigate_to(url)
152
- end
153
-
154
- #
155
- # Returns the text of the current page
156
- #
157
- def text
158
- platform.text
159
- end
160
-
161
- #
162
- # Returns the html of the current page
163
- #
164
- def html
165
- platform.html
166
- end
167
-
168
- #
169
- # Returns the title of the current page
170
- #
171
- def title
172
- platform.title
173
- end
174
-
175
- #
176
- # Wait until the block returns true or times out
177
- #
178
- # @example
179
- # @page.wait_until(5, 'Success not found on page') do
180
- # @page.text.include? 'Success'
181
- # end
182
- #
183
- # @param [Numeric] the amount of time to wait for the block to return true.
184
- # @param [String] the message to include with the error if we exceed the timeout duration.
185
- # @param block the block to execute. It should return true when successful.
186
- #
187
- def wait_until(timeout = PageObject.default_page_wait, message = nil, &block)
188
- platform.wait_until(timeout, message, &block)
189
- end
190
-
191
-
192
- #
193
- # Wait until there are no pending ajax requests. This requires you
194
- # to set the javascript framework in advance.
195
- #
196
- # @param [Numeric] the amount of time to wait for the block to return true.
197
- # @param [String] the message to include with the error if we exceed
198
- # the timeout duration.
199
- #
200
- def wait_for_ajax(timeout = 30, message = nil)
201
- end_time = ::Time.now + timeout
202
- until ::Time.now > end_time
203
- return if browser.execute_script(::PageObject::JavascriptFrameworkFacade.pending_requests) == 0
204
- sleep 0.5
205
- end
206
- message = "Timed out waiting for ajax requests to complete" unless message
207
- raise message
208
- end
209
-
210
- #
211
- # Override the normal alert popup so it does not occur.
212
- #
213
- # @example
214
- # message = @page.alert do
215
- # @page.button_that_causes_alert
216
- # end
217
- #
218
- # @param frame optional parameter used when alert is nested within a frame
219
- # @param block a block that has the call that will cause the alert to display
220
- # @return [String] the message that was contained in the alert
221
- #
222
- def alert(frame=nil, &block)
223
- platform.alert(frame, &block)
224
- end
225
-
226
- #
227
- # Override the normal confirm popup so it does not occur.
228
- #
229
- # @example
230
- # message = @popup.confirm(true) do
231
- # @page.button_that_causes_confirm
232
- # end
233
- #
234
- # @param [bool] what response you want to return back from the confirm popup
235
- # @param frame optional parameter used when the confirm is nested within a frame
236
- # @param block a block that has the call that will cause the confirm to display
237
- # @return [String] the message that was prompted in the confirm
238
- #
239
- def confirm(response, frame=nil, &block)
240
- platform.confirm(response, frame, &block)
241
- end
242
-
243
- #
244
- # Override the normal prompt popup so it does not occur.
245
- #
246
- # @example
247
- # message = @popup.prompt("Some Value") do
248
- # @page.button_that_causes_prompt
249
- # end
250
- #
251
- # @param [string] the value returned to the caller of the prompt
252
- # @param frame optional parameter used with the prompt is nested within a frame
253
- # @param block a block that has the call that will cause the prompt to display
254
- # @return [Hash] A has containing two keys - :message contains the prompt message and
255
- # :default_value contains the default value for the prompt if provided
256
- #
257
- def prompt(answer, frame=nil, &block)
258
- platform.prompt(answer, frame, &block)
259
- end
260
-
261
- #
262
- # Execute javascript on the browser
263
- #
264
- # @example Get inner HTML of element
265
- # span = @page.span_element
266
- # @page.execute_script "return arguments[0].innerHTML", span
267
- # #=> "Span innerHTML"
268
- #
269
- def execute_script(script, *args)
270
- args.map! { |e| e.kind_of?(PageObject::Elements::Element) ? e.element : e }
271
- platform.execute_script(script, *args)
272
- end
273
-
274
- #
275
- # Identify an element as existing within a frame. A frame parameter
276
- # is passed to the block and must be passed to the other calls to PageObject.
277
- # You can nest calls to in_frame by passing the frame to the next level.
278
- #
279
- # @example
280
- # in_frame(:id => 'frame_id') do |frame|
281
- # text_field_element(:id => 'fname', :frame => frame)
282
- # end
283
- #
284
- # @param [Hash] identifier how we find the frame. The valid keys are:
285
- # * :id
286
- # * :index
287
- # * :name
288
- # * :class
289
- # @param frame passed from a previous call to in_frame. Used to nest calls
290
- # @param block that contains the calls to elements that exist inside the frame.
291
- #
292
- def in_frame(identifier, frame=nil, &block)
293
- platform.in_frame(identifier, frame, &block)
294
- end
295
-
296
- # Identify an element as existing within an iframe. A frame parameter
297
- # is passed to the block and must be passed to the other calls to PageObject.
298
- # You can nest calls to in_iframe by passing the frame to the next level.
299
- #
300
- # @example
301
- # in_iframe(:id => 'iframe_id') do |iframe|
302
- # text_field_element(:id => 'ifname', :frame => iframe)
303
- # end
304
- #
305
- # @param [Hash] identifier how we find the iframe. The valid keys are:
306
- # * :id
307
- # * :index
308
- # * :name
309
- # * :class
310
- # @param frame passed from a previous call to in_iframe. Used to nest calls
311
- # @param block that contains the calls to elements that exist inside the iframe.
312
- #
313
- def in_iframe(identifier, frame=nil, &block)
314
- platform.in_iframe(identifier, frame, &block)
315
- end
316
-
317
- #
318
- # Override the normal showModalDialog call is it opens a window instead
319
- # of a dialog. You will need to attach to the new window in order to
320
- # continue.
321
- #
322
- # @example
323
- # @page.modal_dialog do
324
- # @page.action_that_spawns_the_modal
325
- # end
326
- #
327
- # @param block a block that contains the call that will cause the modal dialog.
328
- #
329
- def modal_dialog(&block)
330
- script =
331
- %Q{
332
- window.showModalDialog = function(sURL, vArguments, sFeatures) {
333
- window.dialogArguments = vArguments;
334
- modalWin = window.open(sURL, 'modal', sFeatures);
335
- return modalWin;
336
- }
337
- }
338
- browser.execute_script script
339
- yield if block_given?
340
- end
341
-
342
- #
343
- # Attach to a running window. You can locate the window using either
344
- # the window's title or url. If it fails to connect to a window it will
345
- # pause for 1 second and try again.
346
- #
347
- # @example
348
- # page.attach_to_window(:title => "other window's title")
349
- #
350
- # @param [Hash] either :title or :url of the other window. The url does not need to
351
- # be the entire url - it can just be the page name like index.html
352
- # @param block if present the block is executed and then execution is returned to the
353
- # calling window
354
- #
355
- def attach_to_window(identifier, &block)
356
- begin
357
- platform.attach_to_window(identifier, &block)
358
- rescue
359
- sleep 1
360
- platform.attach_to_window(identifier, &block)
361
- end
362
- end
363
-
364
- #
365
- # Find the element that has focus on the page
366
- #
367
- def element_with_focus
368
- platform.element_with_focus
369
- end
370
-
371
- #
372
- # Refresh to current page
373
- #
374
- def refresh
375
- platform.refresh
376
- end
377
-
378
- #
379
- # Go back to the previous page
380
- #
381
- def back
382
- platform.back
383
- end
384
-
385
- #
386
- # Go forward to the next page
387
- #
388
- def forward
389
- platform.forward
390
- end
391
-
392
- #
393
- # Clear the cookies from the browser
394
- #
395
- def clear_cookies
396
- platform.clear_cookies
397
- end
398
-
399
- #
400
- # Save the current screenshot to the provided url. File
401
- # is saved as a png file.
402
- #
403
- def save_screenshot(file_name)
404
- platform.save_screenshot file_name
405
- end
406
-
407
- def self.register_widget(widget_tag, widget_class, base_element_tag)
408
- Widgets.register_widget(widget_tag, widget_class, base_element_tag)
409
- end
410
-
411
- private
412
-
413
- def root
414
- @root_element || PageObject::Platforms::Watir.browser_root_for(browser)
415
- end
416
-
417
- def call_block(&block)
418
- block.arity == 1 ? block.call(self) : self.instance_eval(&block)
419
- end
420
- end
1
+ require 'watir'
2
+ require 'page-object/version'
3
+ require 'page-object/accessors'
4
+ require 'page-object/element_locators'
5
+ require 'page-object/nested_elements'
6
+ require 'page-object/page_factory'
7
+ require 'page-object/page_populator'
8
+ require 'page-object/javascript_framework_facade'
9
+ require 'page-object/indexed_properties'
10
+ require 'page-object/section_collection'
11
+ require 'page-object/widgets'
12
+
13
+ require 'page-object/platforms/watir'
14
+ require 'page-object/platforms/watir/page_object'
15
+
16
+ #
17
+ # Module that when included adds functionality to a page object. This module
18
+ # will add numerous class and instance methods that you use to define and
19
+ # interact with web pages.
20
+ #
21
+ # If we have a login page with a username and password textfield and a login
22
+ # button we might define our page like the one below. We can then interact with
23
+ # the object using the generated methods.
24
+ #
25
+ # @example Login page example
26
+ # class LoginPage
27
+ # include PageObject
28
+ #
29
+ # text_field(:username, :id => 'user')
30
+ # text_field(:password, :id => 'pass')
31
+ # button(:login, :value => 'Login')
32
+ # end
33
+ #
34
+ # ...
35
+ #
36
+ # browser = Watir::Browser.new :firefox
37
+ # login_page = LoginPage.new(browser)
38
+ # login_page.username = 'cheezy'
39
+ # login_page.password = 'secret'
40
+ # login_page.login
41
+ #
42
+ # @see PageObject::Accessors to see what class level methods are added to this module at runtime.
43
+ #
44
+ module PageObject
45
+ include ElementLocators
46
+ include PagePopulator
47
+
48
+ def method_missing(method, *args, &block)
49
+ if @root_element.respond_to?(method)
50
+ @root_element.send(method, *args, &block)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ def respond_to_missing?(method, include_all = false)
57
+ @root_element && @root_element.respond_to?(method) || super
58
+ end
59
+
60
+ # @return the platform browser passed to the constructor
61
+ attr_reader :browser
62
+ # @return [PageObject::WatirPageObject] the platform page object
63
+ attr_reader :platform
64
+
65
+ #
66
+ # Construct a new page object. Prior to browser initialization it will call
67
+ # a method named initialize_accessors if it exists. Upon initialization of
68
+ # the page it will call a method named initialize_page if it exists.
69
+ #
70
+ # @param [Watir::Browser, Watir::HTMLElement or Selenium::WebDriver::Driver, Selenium::WebDriver::Element] the platform browser/element to use
71
+ # @param [bool] open the page if page_url is set
72
+ #
73
+ def initialize(root, visit=false)
74
+ initialize_accessors if respond_to?(:initialize_accessors)
75
+ initialize_browser(root)
76
+ goto if visit && self.class.instance_methods(false).include?(:goto)
77
+ initialize_page if respond_to?(:initialize_page)
78
+ end
79
+
80
+ def initialize_browser(root)
81
+ @root_element = PageObject::Platforms::Watir.root_element_for root
82
+ @browser = root
83
+ @platform = PageObject::Platforms::Watir.create_page_object @browser
84
+ end
85
+
86
+ # @private
87
+ def self.included(cls)
88
+ cls.extend PageObject::Accessors
89
+ end
90
+
91
+ #
92
+ # Set the default timeout for page level waits
93
+ #
94
+ def self.default_page_wait=(timeout)
95
+ @page_wait = timeout
96
+ end
97
+
98
+ #
99
+ # Returns the default timeout for page lavel waits
100
+ #
101
+ def self.default_page_wait
102
+ @page_wait ||= 30
103
+ end
104
+
105
+ #
106
+ # Sets the default timeout for element level waits
107
+ #
108
+ def self.default_element_wait=(timeout)
109
+ @element_wait = timeout
110
+ end
111
+
112
+ #
113
+ # Returns the default timeout for element level waits
114
+ #
115
+ def self.default_element_wait
116
+ @element_wait ||= 5
117
+ end
118
+
119
+ #
120
+ # Set the javascript framework to use when determining number of
121
+ # ajax requests. Valid frameworks are :jquery, :prototype, :yui,
122
+ # and :angularjs
123
+ #
124
+ def self.javascript_framework=(framework)
125
+ PageObject::JavascriptFrameworkFacade.framework = framework
126
+ end
127
+
128
+ #
129
+ # Add a new javascript framework to page-object. The module passed
130
+ # in must adhere to the same prototype as the JQuery and Prototype
131
+ # modules.
132
+ #
133
+ # @param [Symbol] the name used to reference the framework in
134
+ # subsequent calls
135
+ # @param [Module] a module that has the necessary methods to perform
136
+ # the required actions.
137
+ #
138
+ def self.add_framework(key, framework)
139
+ PageObject::JavascriptFrameworkFacade.add_framework(key, framework)
140
+ end
141
+
142
+ #
143
+ # get the current page url
144
+ #
145
+ def current_url
146
+ platform.current_url
147
+ end
148
+
149
+ #
150
+ # navigate to the provided url
151
+ #
152
+ # @param [String] the full url to navigate to
153
+ #
154
+ def navigate_to(url)
155
+ platform.navigate_to(url)
156
+ end
157
+
158
+ #
159
+ # Returns the text of the current page
160
+ #
161
+ def text
162
+ platform.text
163
+ end
164
+
165
+ #
166
+ # Returns the html of the current page
167
+ #
168
+ def html
169
+ platform.html
170
+ end
171
+
172
+ #
173
+ # Returns the title of the current page
174
+ #
175
+ def title
176
+ platform.title
177
+ end
178
+
179
+ #
180
+ # Wait until the block returns true or times out
181
+ #
182
+ # @example
183
+ # @page.wait_until(5, 'Success not found on page') do
184
+ # @page.text.include? 'Success'
185
+ # end
186
+ #
187
+ # @param [Numeric] the amount of time to wait for the block to return true.
188
+ # @param [String] the message to include with the error if we exceed the timeout duration.
189
+ # @param block the block to execute. It should return true when successful.
190
+ #
191
+ def wait_until(timeout = PageObject.default_page_wait, message = nil, &block)
192
+ platform.wait_until(timeout, message, &block)
193
+ end
194
+
195
+
196
+ #
197
+ # Wait until there are no pending ajax requests. This requires you
198
+ # to set the javascript framework in advance.
199
+ #
200
+ # @param [Numeric] the amount of time to wait for the block to return true.
201
+ # @param [String] the message to include with the error if we exceed
202
+ # the timeout duration.
203
+ #
204
+ def wait_for_ajax(timeout = 30, message = nil)
205
+ end_time = ::Time.now + timeout
206
+ until ::Time.now > end_time
207
+ return if browser.execute_script(::PageObject::JavascriptFrameworkFacade.pending_requests) == 0
208
+ sleep 0.5
209
+ end
210
+ message = "Timed out waiting for ajax requests to complete" unless message
211
+ raise message
212
+ end
213
+
214
+ #
215
+ # Override the normal alert popup so it does not occur.
216
+ #
217
+ # @example
218
+ # message = @page.alert do
219
+ # @page.button_that_causes_alert
220
+ # end
221
+ #
222
+ # @param frame optional parameter used when alert is nested within a frame
223
+ # @param block a block that has the call that will cause the alert to display
224
+ # @return [String] the message that was contained in the alert
225
+ #
226
+ def alert(frame=nil, &block)
227
+ platform.alert(frame, &block)
228
+ end
229
+
230
+ #
231
+ # Override the normal confirm popup so it does not occur.
232
+ #
233
+ # @example
234
+ # message = @popup.confirm(true) do
235
+ # @page.button_that_causes_confirm
236
+ # end
237
+ #
238
+ # @param [bool] what response you want to return back from the confirm popup
239
+ # @param frame optional parameter used when the confirm is nested within a frame
240
+ # @param block a block that has the call that will cause the confirm to display
241
+ # @return [String] the message that was prompted in the confirm
242
+ #
243
+ def confirm(response, frame=nil, &block)
244
+ platform.confirm(response, frame, &block)
245
+ end
246
+
247
+ #
248
+ # Override the normal prompt popup so it does not occur.
249
+ #
250
+ # @example
251
+ # message = @popup.prompt("Some Value") do
252
+ # @page.button_that_causes_prompt
253
+ # end
254
+ #
255
+ # @param [string] the value returned to the caller of the prompt
256
+ # @param frame optional parameter used with the prompt is nested within a frame
257
+ # @param block a block that has the call that will cause the prompt to display
258
+ # @return [Hash] A has containing two keys - :message contains the prompt message and
259
+ # :default_value contains the default value for the prompt if provided
260
+ #
261
+ def prompt(answer, frame=nil, &block)
262
+ platform.prompt(answer, frame, &block)
263
+ end
264
+
265
+ #
266
+ # Execute javascript on the browser
267
+ #
268
+ # @example Get inner HTML of element
269
+ # span = @page.span_element
270
+ # @page.execute_script "return arguments[0].innerHTML", span
271
+ # #=> "Span innerHTML"
272
+ #
273
+ def execute_script(script, *args)
274
+ args.map! { |e| e.kind_of?(PageObject::Elements::Element) ? e.element : e }
275
+ platform.execute_script(script, *args)
276
+ end
277
+
278
+ #
279
+ # Identify an element as existing within a frame. A frame parameter
280
+ # is passed to the block and must be passed to the other calls to PageObject.
281
+ # You can nest calls to in_frame by passing the frame to the next level.
282
+ #
283
+ # @example
284
+ # in_frame(:id => 'frame_id') do |frame|
285
+ # text_field_element(:id => 'fname', :frame => frame)
286
+ # end
287
+ #
288
+ # @param [Hash] identifier how we find the frame. The valid keys are:
289
+ # * :id
290
+ # * :index
291
+ # * :name
292
+ # * :class
293
+ # @param frame passed from a previous call to in_frame. Used to nest calls
294
+ # @param block that contains the calls to elements that exist inside the frame.
295
+ #
296
+ def in_frame(identifier, frame=nil, &block)
297
+ platform.in_frame(identifier, frame, &block)
298
+ end
299
+
300
+ # Identify an element as existing within an iframe. A frame parameter
301
+ # is passed to the block and must be passed to the other calls to PageObject.
302
+ # You can nest calls to in_iframe by passing the frame to the next level.
303
+ #
304
+ # @example
305
+ # in_iframe(:id => 'iframe_id') do |iframe|
306
+ # text_field_element(:id => 'ifname', :frame => iframe)
307
+ # end
308
+ #
309
+ # @param [Hash] identifier how we find the iframe. The valid keys are:
310
+ # * :id
311
+ # * :index
312
+ # * :name
313
+ # * :class
314
+ # @param frame passed from a previous call to in_iframe. Used to nest calls
315
+ # @param block that contains the calls to elements that exist inside the iframe.
316
+ #
317
+ def in_iframe(identifier, frame=nil, &block)
318
+ platform.in_iframe(identifier, frame, &block)
319
+ end
320
+
321
+ #
322
+ # Override the normal showModalDialog call is it opens a window instead
323
+ # of a dialog. You will need to attach to the new window in order to
324
+ # continue.
325
+ #
326
+ # @example
327
+ # @page.modal_dialog do
328
+ # @page.action_that_spawns_the_modal
329
+ # end
330
+ #
331
+ # @param block a block that contains the call that will cause the modal dialog.
332
+ #
333
+ def modal_dialog(&block)
334
+ script =
335
+ %Q{
336
+ window.showModalDialog = function(sURL, vArguments, sFeatures) {
337
+ window.dialogArguments = vArguments;
338
+ modalWin = window.open(sURL, 'modal', sFeatures);
339
+ return modalWin;
340
+ }
341
+ }
342
+ browser.execute_script script
343
+ yield if block_given?
344
+ end
345
+
346
+ #
347
+ # Attach to a running window. You can locate the window using either
348
+ # the window's title or url. If it fails to connect to a window it will
349
+ # pause for 1 second and try again.
350
+ #
351
+ # @example
352
+ # page.attach_to_window(:title => "other window's title")
353
+ #
354
+ # @param [Hash] either :title or :url of the other window. The url does not need to
355
+ # be the entire url - it can just be the page name like index.html
356
+ # @param block if present the block is executed and then execution is returned to the
357
+ # calling window
358
+ #
359
+ def attach_to_window(identifier, &block)
360
+ begin
361
+ platform.attach_to_window(identifier, &block)
362
+ rescue
363
+ sleep 1
364
+ platform.attach_to_window(identifier, &block)
365
+ end
366
+ end
367
+
368
+ #
369
+ # Find the element that has focus on the page
370
+ #
371
+ def element_with_focus
372
+ platform.element_with_focus
373
+ end
374
+
375
+ #
376
+ # Refresh to current page
377
+ #
378
+ def refresh
379
+ platform.refresh
380
+ end
381
+
382
+ #
383
+ # Go back to the previous page
384
+ #
385
+ def back
386
+ platform.back
387
+ end
388
+
389
+ #
390
+ # Go forward to the next page
391
+ #
392
+ def forward
393
+ platform.forward
394
+ end
395
+
396
+ #
397
+ # Clear the cookies from the browser
398
+ #
399
+ def clear_cookies
400
+ platform.clear_cookies
401
+ end
402
+
403
+ #
404
+ # Save the current screenshot to the provided url. File
405
+ # is saved as a png file.
406
+ #
407
+ def save_screenshot(file_name)
408
+ platform.save_screenshot file_name
409
+ end
410
+
411
+ #
412
+ # Check if root element exists and is visible
413
+ #
414
+ def present?
415
+ root.present?
416
+ end
417
+
418
+ def self.register_widget(widget_tag, widget_class, base_element_tag)
419
+ Widgets.register_widget(widget_tag, widget_class, base_element_tag)
420
+ end
421
+
422
+ private
423
+
424
+ def root
425
+ @root_element || PageObject::Platforms::Watir.browser_root_for(browser)
426
+ end
427
+
428
+ def call_block(&block)
429
+ block.arity == 1 ? block.call(self) : self.instance_eval(&block)
430
+ end
431
+ end