capybara_page_object 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1b46c4f3b30e45f161b40fdba0269df229bc881f
4
+ data.tar.gz: d83d6348573ee3ec0476f6377f511a54dbdfa1f5
5
+ SHA512:
6
+ metadata.gz: 72c2e7c49d8e4dd1012210284dddbfb5ade11dc92e8ce39ebacd573258f5ed0ad2735f592b39c0c7ae1edf66e9c30e4ff5d532ad6f10032066c51e1143039d80
7
+ data.tar.gz: bb15be36933ebf78cd04ad19584e2cd1e953806b544de8a828032224aae35c93e57b1d70f41062fe367eb8f7cf5e1129405906acc0504d78b358f37bb7560ce8
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.1.5
4
+ before_install: gem install bundler -v 1.10.6
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in capybara_page_object.gemspec
4
+ gemspec
5
+
6
+
7
+ gem 'capybara'
8
+ gem 'cucumber'
9
+ gem 'selenium-webdriver'
10
+ gem 'pry'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 Ryan Stawarz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # CapybaraPageObject
2
+
3
+ This is a partial port of the [`page_object` gem](https://github.com/cheezy/page-object) to use Capybara
4
+ instead of directly relying upon the Selenium or Waitr drivers.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'capybara_page_object'
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install capybara_page_object
21
+
22
+ ## Usage
23
+
24
+ TODO: Write usage instructions here
25
+
26
+ ## Development
27
+
28
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+
30
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
31
+
32
+ ## Contributing
33
+
34
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/capybara_page_object. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
35
+
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
40
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "capybara/page_object"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'capybara_page_object/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "capybara_page_object"
8
+ spec.version = CapybaraPageObject::VERSION
9
+ spec.authors = ["Ryan Stawarz"]
10
+ spec.email = ["ryan@stawarz.com"]
11
+
12
+ spec.summary = %q{A Page Object Model to be used with capybara}
13
+ spec.description = %q{A simple library to abstract out page details from cucumber and browser tests}
14
+ spec.homepage = "http://github.com/rstawarz/capybara_page_object"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "capybara"
23
+ spec.add_development_dependency "bundler", "~> 1.10"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec"
26
+ end
data/cucumber.yml ADDED
@@ -0,0 +1,10 @@
1
+ <%
2
+ std_opts = "--no-source --color --format pretty" # Cucumber::Formatter::Fuubar"
3
+ %>
4
+
5
+ default: DRIVER=WATIR <%= std_opts %> --tags ~@selenium_only
6
+ watir_webdriver: DRIVER=WATIR <%= std_opts %> --tags ~@selenium_only
7
+ selenium_webdriver: DRIVER=SELENIUM <%= std_opts %> --tags ~@watir_only
8
+ focus: DRIVER=WATIR <%= std_opts %> --tags ~@selenium_only --tags @focus
9
+ #focus: DRIVER=SELENIUM <%= std_opts %> --tags ~@watir_only --tags @focus
10
+
@@ -0,0 +1,252 @@
1
+ module CapybaraPageObject
2
+ module ClassMethods
3
+ #
4
+ # Set some values that can be used within the class. This is
5
+ # typically used to provide values that help build dynamic urls in
6
+ # the page_url method
7
+ #
8
+ # @param [Hash] the value to set the params
9
+ #
10
+ def params=(the_params)
11
+ @params = the_params
12
+ end
13
+
14
+ #
15
+ # Return the params that exist on this page class
16
+ #
17
+ def params
18
+ @params ||= {}
19
+ end
20
+
21
+ #
22
+ # Specify the url for the page. A call to this method will generate a
23
+ # 'goto' method to take you to the page.
24
+ #
25
+ # @param [String] the url for the page.
26
+ # @param [Symbol] a method name to call to get the url
27
+ #
28
+ def page_url(url)
29
+ define_method("goto") do
30
+ visit self.page_url_value
31
+ end
32
+
33
+ define_method('page_url_value') do
34
+ url
35
+ end
36
+ end
37
+
38
+ #
39
+ # Creates a method that compares the expected_title of a page against the actual.
40
+ # @param [String] expected_title the literal expected title for the page
41
+ # @param [Regexp] expected_title the expected title pattern for the page
42
+ # @return [boolean]
43
+ # @raise An exception if expected_title does not match actual title
44
+ #
45
+ # @example Specify 'Google' as the expected title of a page
46
+ # expected_title "Google"
47
+ # page.has_expected_title?
48
+ #
49
+ def expected_title(expected_title)
50
+ define_method("has_expected_title?") do
51
+ page.has_title?(expected_title)
52
+ end
53
+ end
54
+
55
+ #
56
+ # Creates a method that provides a way to initialize a page based upon an expected element.
57
+ # This is useful for pages that load dynamic content.
58
+ # @param [Symbol] the name given to the element in the declaration
59
+ # @param [optional, Integer] timeout default value is 5 seconds
60
+ # @return [boolean]
61
+ #
62
+ # @example Specify a text box named :address expected on the page within 10 seconds
63
+ # expected_element(:address, 10)
64
+ # page.has_expected_element?
65
+ #
66
+ def expected_element(element_name)
67
+ define_method("has_expected_element?") do
68
+ args = self.send("#{element_name}_selector")
69
+ page.assert_selector(*args)
70
+ true
71
+ end
72
+ end
73
+
74
+ #
75
+ # Creates a method that provides a way to initialize a page based upon an expected element to become visible.
76
+ # This is useful for pages that load dynamic content and might have hidden elements that are not shown.
77
+ # @param [Symbol] the name given to the element in the declaration
78
+ # @param [optional, Integer] timeout default value is 5 seconds
79
+ # @param [optional, boolean] also check that element to be visible if set to true
80
+ # @return [boolean]
81
+ #
82
+ # @example Specify a text box named :address expected on the page within 10 seconds
83
+ # expected_element_visible(:address, 10)
84
+ # page.has_expected_element_visible?
85
+ #
86
+ def expected_element_visible(element_name, timeout=Capybara.default_wait_time)
87
+ define_method("has_expected_element_visible?") do
88
+ expect(self.send("#{element_name}_element")).to be_visible
89
+ end
90
+ end
91
+
92
+ #
93
+ # adds a method to return a collection of generic Element objects
94
+ # for a specific tag.
95
+ #
96
+ #def elements(name, tag=:element, identifier={:index => 0}, &block)
97
+ ## default tag to :element
98
+ ##
99
+ ## elements 'button', css: 'some css'
100
+ ##
101
+ ## is the same as
102
+ ##
103
+ ## elements 'button', :element, css: 'some css'
104
+ ##
105
+ #if tag.is_a?(Hash)
106
+ #identifier = tag
107
+ #tag = :element
108
+ #end
109
+
110
+ #define_method("#{name}_elements") do
111
+ #return call_block(&block) if block_given?
112
+ #platform.elements_for(tag, identifier.clone)
113
+ #end
114
+ #end
115
+
116
+ #
117
+ # adds a method to return a page object rooted at an element
118
+ #
119
+ # @example
120
+ # page_section(:navigation_bar, NavigationBar, :id => 'nav-bar')
121
+ # # will generate 'navigation_bar' and 'navigation_bar?'
122
+ #
123
+ #def page_section(name, section_class, identifier)
124
+ #define_method(name) do
125
+ #platform.page_for(identifier, section_class)
126
+ #end
127
+ #end
128
+
129
+ #
130
+ # adds a method to return a collection of page objects rooted at elements
131
+ #
132
+ # @example
133
+ # page_sections(:articles, Article, :class => 'article')
134
+ # # will generate 'articles'
135
+ #
136
+ #def page_sections(name, section_class, identifier)
137
+ #define_method(name) do
138
+ #platform.pages_for(identifier, section_class)
139
+ #end
140
+ #end
141
+
142
+ #
143
+ # adds a method that will return an indexed property. The property will respond to
144
+ # the [] method with an object that has a set of normal page_object properties that
145
+ # correspond to the definitions included in the identifier_list parameter, with the
146
+ # "what" of the "how and what" substituted based on the index provided to the []
147
+ # method.
148
+ #
149
+ # @example
150
+ # indexed_property(:title, [
151
+ # [:text_field, :field_1, :id => 'table[%s].field_1'],
152
+ # [:button, :button_1, :id => 'table[%s].button_1'],
153
+ # [:text_field, :field_2, :name => 'table[%s].field_2']
154
+ # ])
155
+ # # will generate a title method that responds to []. title['foo'] will return an object
156
+ # # that responds to the normal methods expected for two text_fields and a button with the
157
+ # # given names, using the given how and what with 'foo' substituted for the %s. title[123]
158
+ # # will do the same, using the integer 123 instead.
159
+ #
160
+ #
161
+ #def indexed_property (name, identifier_list)
162
+ #define_method("#{name}") do
163
+ #IndexedProperties::TableOfElements.new(@browser, identifier_list)
164
+ #end
165
+ #end
166
+
167
+ def add_element_accessor_method(name, element_klass, finder_options)
168
+ finder_options = finder_options.dup
169
+ finder_args = extract_finder_args(finder_options)
170
+ element_klass ||= CapybaraPageObject::Element
171
+ define_method("#{name}_element") do
172
+ e = find(*finder_args)
173
+ element_klass.new(e, self)
174
+ end
175
+ define_method("#{name}_selector") do |options={}|
176
+ [finder_args.first,
177
+ finder_args[1],
178
+ finder_args[2].merge(options)]
179
+ end
180
+ end
181
+
182
+ def add_element_query_method(name, finder_options)
183
+ finder_options = finder_options.dup
184
+ finder_args = extract_finder_args(finder_options)
185
+ define_method("has_#{name}?") do |options = {}|
186
+ args = self.send("#{name}_selector", options)
187
+ has_selector?(*args)
188
+ end
189
+ define_method("has_no_#{name}?") do |options = {}|
190
+ args = self.send("#{name}_selector", options)
191
+ has_no_selector?(*args)
192
+ end
193
+ define_method("assert_has_#{name}") do |options = {}|
194
+ args = self.send("#{name}_selector", options)
195
+ assert_selector(*args)
196
+ end
197
+ define_method("assert_has_no_#{name}") do |options = {}|
198
+ args = self.send("#{name}_selector", options)
199
+ assert_no_selector(*args)
200
+ end
201
+ end
202
+
203
+ def add_value_accessor_method(name)
204
+ define_method(name) do
205
+ self.send("#{name}_element").value
206
+ end
207
+ end
208
+
209
+ def add_text_accessor_method(name)
210
+ define_method(name) do
211
+ self.send("#{name}_element").text
212
+ end
213
+ end
214
+
215
+ def add_value_mutator_method(name)
216
+ define_method("#{name}=") do |value|
217
+ self.send("#{name}_element").set(value)
218
+ end
219
+ end
220
+
221
+ def add_click_method(name)
222
+ define_method(name) do
223
+ self.send("#{name}_element").click
224
+ end
225
+ end
226
+
227
+ def extract_finder_args(finder_options={})
228
+ finder_type, finder_param = extract_finder_type!(finder_options, :class, :css, :id, :xpath)
229
+ finder_options.delete(finder_type)
230
+ case finder_type
231
+ when :class
232
+ [:css, ".{finder_param}", finder_options]
233
+ when :id
234
+ [:css, "##{finder_param}", finder_options]
235
+ when :css
236
+ [finder_type, finder_param, finder_options]
237
+ when :xpath
238
+ [finder_type, finder_param, finder_options]
239
+ else
240
+ raise "oops"
241
+ end
242
+ end
243
+
244
+ def extract_finder_type!(options, *types)
245
+ found_keys = options.select{|k,v| types.include?(k)}
246
+ unless found_keys.length == 1
247
+ raise "Incorrect finder type specified '#{found_keys.keys.join(', ')}' - only one of #{types.join(', ')} can be specified at a time."
248
+ end
249
+ found_keys.to_a.first
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,255 @@
1
+ module CapybaraPageObject
2
+ module ElementClassMethods
3
+
4
+ def self.define_element_methods(element_name, options = {})
5
+ aliases = options.delete(:aliases) || []
6
+ aliases.each {|name| define_element_methods(name, options) }
7
+ element_klass = options.fetch(:element_class, CapybaraPageObject::Element)
8
+ define_method(element_name) do |name, finder_options|
9
+ add_element_accessor_method(name, element_klass, finder_options)
10
+ add_element_query_method(name, finder_options)
11
+ case options[:default]
12
+ when :text
13
+ add_text_accessor_method(name)
14
+ when :value
15
+ add_value_accessor_method(name)
16
+ when :click
17
+ add_click_method(name)
18
+ end
19
+ end
20
+ end
21
+
22
+ define_element_methods(:a, default: :click)
23
+ define_element_methods(:area, default: :click)
24
+ define_element_methods(:audio, element_class: CapybaraPageObject::MediaElement)
25
+ define_element_methods(:b, default: :text)
26
+ define_element_methods(:button, element_class: CapybaraPageObject::ButtonElement,
27
+ default: :click)
28
+ define_element_methods(:canvas)
29
+ define_element_methods(:div, default: :text)
30
+ define_element_methods(:element, default: :text)
31
+ define_element_methods(:form, element_class: CapybaraPageObject::FormElement)
32
+ define_element_methods(:h1, default: :text)
33
+ define_element_methods(:h2, default: :text)
34
+ define_element_methods(:h3, default: :text)
35
+ define_element_methods(:h4, default: :text)
36
+ define_element_methods(:h5, default: :text)
37
+ define_element_methods(:h6, default: :text)
38
+ define_element_methods(:hidden, default: :value,
39
+ aliases: [:hidden_field])
40
+ define_element_methods(:image, aliases: [:img])
41
+ define_element_methods(:label, default: :text)
42
+ define_element_methods(:link, default: :click)
43
+ define_element_methods(:list_item, default: :text)
44
+ define_element_methods(:list_item, default: :text, aliases: [:li])
45
+ define_element_methods(:ordered_list, default: :text,
46
+ element_class: ListElement,
47
+ aliases: [:ol])
48
+ define_element_methods(:paragraph, default: :text,
49
+ aliases: [:p])
50
+ define_element_methods(:span, default: :text)
51
+ define_element_methods(:svg, default: :text)
52
+ define_element_methods(:table, element_class: CapybaraPageObject::TableElement,
53
+ default: :text)
54
+ define_element_methods(:td, default: :text, aliases: [:cell])
55
+ define_element_methods(:row, element_class: CapybaraPageObject::TableRowElement,
56
+ default: :text)
57
+ define_element_methods(:video, element_class: CapybaraPageObject::MediaElement)
58
+ define_element_methods(:ul, default: :text,
59
+ element_class: CapybaraPageObject::ListElement,
60
+ aliases: [:unordered_list])
61
+
62
+ #
63
+ # adds five methods - one to check, another to uncheck, another
64
+ # to return the state of a checkbox, another to return
65
+ # an element representing the checkbox, and
66
+ # a final method to check the checkbox's existence.
67
+ #
68
+ # @example
69
+ # checkbox(:active, :name => "is_active")
70
+ # # will generate
71
+ # # * 'check_active',
72
+ # # * 'uncheck_active',
73
+ # # * 'active_checked?',
74
+ # # * 'active_element',
75
+ # # * 'active?'
76
+ #
77
+ def checkbox(name, finder_options)
78
+ add_element_accessor_method(name, CapybaraPageObject::Element, finder_options)
79
+ add_element_query_method(name, finder_options)
80
+ element_name = "#{name}_element"
81
+ define_method("check_#{name}") do
82
+ self.send(element_name).set(true)
83
+ end
84
+ define_method("uncheck_#{name}") do
85
+ self.send(element_name).set(false)
86
+ end
87
+ define_method("#{name}_checked?") do
88
+ self.send(element_name).checked? == true ||
89
+ self.send(element_name).checked? == 'checked'
90
+ end
91
+ end
92
+
93
+ #
94
+ # adds three methods - one to set the file for a file field, another to retrieve
95
+ # the file field element, and another to check it's existence.
96
+ #
97
+ # @example
98
+ # file_field(:the_file, :css, '#file_to_upload')
99
+ # # will generate
100
+ # # * 'the_file=',
101
+ # # * 'the_file_element',
102
+ # # * 'the_file?'
103
+ #
104
+ def file_field(name, finder_options)
105
+ add_element_accessor_method(name, CapybaraPageObject::Element, finder_options)
106
+ add_element_query_method(name, finder_options)
107
+ define_method("#{name}=") do |value|
108
+ self.send("#{name}_element").set(value)
109
+ end
110
+ define_method("#{name}") do
111
+ self.send("#{name}_element").value
112
+ end
113
+ end
114
+
115
+ #
116
+ # adds four methods - one to select, another to return if a radio button
117
+ # is selected, another method to return a PageObject::Elements::RadioButton
118
+ # object representing the radio button element, and another to check
119
+ # the radio button's existence.
120
+ #
121
+ # @example
122
+ # radio_button(:north, :css, "#north")
123
+ # # * will generate
124
+ # # * 'select_north',
125
+ # # * 'north_selected?',
126
+ # # * 'north_element',
127
+ # # * 'north?' methods
128
+ #
129
+ def radio_button(name, finder_options)
130
+ add_element_accessor_method(name, CapybaraPageObject::Element, finder_options)
131
+ add_element_query_method(name, finder_options)
132
+ define_method("select_#{name}") do
133
+ self.send("#{name}_element").set(true)
134
+ end
135
+ define_method("#{name}_selected?") do
136
+ self.send("#{name}_element").selected?
137
+ end
138
+ end
139
+ alias_method :radio, :radio_button
140
+
141
+ #
142
+ # adds five methods to help interact with a radio button group -
143
+ # a method to select a radio button in the group by given value/text,
144
+ # a method to return the values of all radio buttons in the group, a method
145
+ # to return if a radio button in the group is selected (will return
146
+ # the text of the selected radio button, if true), a method to return
147
+ # an array of PageObject::Elements::RadioButton objects representing
148
+ # the radio button group, and finally a method to check the existence
149
+ # of the radio button group.
150
+ #
151
+ # @example
152
+ # radio_button_group(:color, :css, "input[name='color']")
153
+ # will generate
154
+ # * 'select_color',
155
+ # * 'color_values',
156
+ # * 'color_selected?',
157
+ # * 'color_elements',
158
+ # * 'color?' methods
159
+ #
160
+ def radio_button_group(name, finder_options)
161
+ finder_options = finder_options.dup
162
+ finder_args = extract_finder_args(finder_options)
163
+
164
+ define_method("select_#{name}") do |value|
165
+ elements = self.send("#{name}_elements")
166
+ elements.find {|e| e.value == value }.set(true)
167
+ end
168
+ define_method("#{name}_values") do
169
+ self.send("#{name}_elements").map(&:value)
170
+ end
171
+ define_method("#{name}_selected?") do
172
+ elements = self.send("#{name}_elements")
173
+ selected = elements.select {|e| e.checked? }
174
+ selected.first ? selected.first.value : false
175
+ end
176
+ define_method("#{name}_elements") do
177
+ all(*finder_args)
178
+ end
179
+ define_method("#{name}?") do
180
+ assert_selector(*finder_args)
181
+ end
182
+ end
183
+ alias_method :radio_group, :radio_button_group
184
+
185
+
186
+ #
187
+ # adds five methods - one to select an item in a drop-down,
188
+ # another to fetch the currently selected item text, another
189
+ # to retrieve the select list element, another to check the
190
+ # drop down's existence and another to get all the available options
191
+ # to select from.
192
+ #
193
+ # @example
194
+ # select_list(:state, :css, "#state")
195
+ # # will generate 'state', 'state=', 'state_element', 'state?', "state_options" methods
196
+ #
197
+ def select_list(name, finder_options)
198
+ add_element_accessor_method(name, CapybaraPageObject::SelectListElement, finder_options)
199
+ add_element_query_method(name, finder_options)
200
+
201
+ element_name = "#{name}_element"
202
+ define_method(name) do
203
+ self.send(element_name).value
204
+ end
205
+ define_method("selected_#{name}") do
206
+ within(self.send(element_name)) do
207
+ find('option[selected]')
208
+ end
209
+ end
210
+ define_method("#{name}=") do |value|
211
+ element = self.send(element_name)
212
+ within(element) do
213
+ find(:option, value).select_option
214
+ end
215
+ end
216
+ define_method("#{name}_options") do
217
+ element = self.send(element_name)
218
+ within(element) do
219
+ all(:css, 'option')
220
+ end
221
+ end
222
+ end
223
+ alias_method :select, :select_list
224
+
225
+ # adds four methods to the page object - one to set text in a text area,
226
+ # another to retrieve text from a text area, another to return the text
227
+ # area element, and another to check the text area's existence.
228
+ #
229
+ # @example
230
+ # text_area(:address, :css, "#address")
231
+ # # will generate
232
+ # # * 'address',
233
+ # # * 'address=',
234
+ # # * 'address_element',
235
+ # # * 'address?'
236
+ #
237
+ #
238
+ def text_area(name, finder_options)
239
+ add_element_accessor_method(name, CapybaraPageObject::TextAreaElement, finder_options)
240
+ add_element_query_method(name, finder_options)
241
+ add_value_accessor_method(name)
242
+ add_value_mutator_method(name)
243
+ end
244
+ alias_method :textarea, :text_area
245
+
246
+ def text_field(name, finder_options)
247
+ add_element_accessor_method(name, CapybaraPageObject::TextFieldElement, finder_options)
248
+ add_element_query_method(name, finder_options)
249
+ add_value_accessor_method(name)
250
+ add_value_mutator_method(name)
251
+ end
252
+
253
+ end
254
+ end
255
+
@@ -0,0 +1,13 @@
1
+ module CapybaraPageObject
2
+ class ButtonElement < Element
3
+
4
+ def text
5
+ if element.tag_name == 'button'
6
+ element.text
7
+ else
8
+ element.value
9
+ end
10
+ end
11
+
12
+ end
13
+ end
@@ -0,0 +1,85 @@
1
+ module CapybaraPageObject
2
+ class Element
3
+ attr_accessor :element,
4
+ :page
5
+
6
+ def initialize(element, page)
7
+ @element = element
8
+ @page = page
9
+ end
10
+
11
+ def attribute(name)
12
+ element[name.to_s]
13
+ end
14
+
15
+ def enabled?
16
+ !element.disabled?
17
+ end
18
+
19
+ def class
20
+ attribute('class')
21
+ end
22
+ alias_method :class_name, :class
23
+
24
+ def focus
25
+ bridge.executeScript("return arguments[0].focus()", native)
26
+ end
27
+
28
+ def parent
29
+ raise Capybara::NotSupportedByDriverError unless javascript_enabled?
30
+ element.parent
31
+ end
32
+
33
+ def html
34
+ case native.class.to_s
35
+ when /Nokogiri/
36
+ native.to_html
37
+ when /Selenium/
38
+ native['outerHTML']
39
+ else
40
+ raise "This driver is not supported for retrieving the html of an element"
41
+ end
42
+ end
43
+
44
+ def native
45
+ element.native
46
+ end
47
+
48
+ def scroll_into_view
49
+ unless element.native.respond_to?(:location_once_scrolled_into_view)
50
+ raise "Selenium javascript driver required to use 'scroll_into_view'"
51
+ end
52
+ element.native.location_once_scrolled_into_view
53
+ end
54
+
55
+ def method_missing(method_name, *arguments, &block)
56
+ if element.respond_to?(method_name, true)
57
+ element.send(method_name, *arguments, &block)
58
+ elsif value = element[method_name.to_s]
59
+ value
60
+ else
61
+ super
62
+ end
63
+ end
64
+
65
+ def respond_to_missing?(method_name, include_private = false)
66
+ element.respond_to?(method_name, include_private) ||
67
+ element[method_name.to_s] != nil ||
68
+ super
69
+ end
70
+
71
+ def within(&block)
72
+ page.within(element, &block)
73
+ end
74
+
75
+ private
76
+
77
+ def bridge
78
+ native.instance_variable_get(:@bridge)
79
+ end
80
+
81
+ def javascript_enabled?
82
+ Capybara.current_driver == Capybara.javascript_driver
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ module CapybaraPageObject
2
+ class FormElement < Element
3
+
4
+ def submit
5
+ Capybara.current_session.within(element) do
6
+ first(:css, "input[type='submit']").click
7
+ end
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module CapybaraPageObject
2
+ class ListElement < Element
3
+
4
+ def items
5
+ @page.within(element) { all('li') }
6
+ end
7
+
8
+ def [](item_index)
9
+ items[item_index]
10
+ end
11
+
12
+ end
13
+ end
14
+
@@ -0,0 +1,47 @@
1
+ module CapybaraPageObject
2
+ class MediaElement < Element
3
+
4
+ def autoplay?
5
+ true?(attribute(:autoplay))
6
+ end
7
+
8
+ def has_controls?
9
+ true?(attribute(:controls))
10
+ end
11
+
12
+ def paused?
13
+ true?(attribute(:paused))
14
+ end
15
+
16
+ def duration
17
+ duration = attribute(:duration)
18
+ return duration.to_f if duration
19
+ end
20
+
21
+ def volume
22
+ volume = attribute(:volume)
23
+ return volume.to_i if volume
24
+ end
25
+
26
+ def ended?
27
+ true?(attribute(:ended))
28
+ end
29
+
30
+ def seeking?
31
+ true?(attribute(:seeking))
32
+ end
33
+
34
+ def loop?
35
+ true?(attribute(:loop))
36
+ end
37
+
38
+ def muted?
39
+ true?(attribute(:muted))
40
+ end
41
+
42
+ def true?(value)
43
+ value == true || value == 'true'
44
+ end
45
+
46
+ end
47
+ end
@@ -0,0 +1,38 @@
1
+ module CapybaraPageObject
2
+ class SelectListElement < Element
3
+
4
+ def selected?(value_or_text)
5
+ option = selected_option
6
+ option.value == value_or_text ||
7
+ option.text == value_or_text
8
+ end
9
+
10
+ def selected_option
11
+ within do
12
+ first('option[selected]')
13
+ end
14
+ end
15
+
16
+ def clear
17
+ selected_options.each(&:unselect_option)
18
+ end
19
+
20
+ def selected_options
21
+ within do
22
+ all('option[selected]')
23
+ end
24
+ end
25
+
26
+ def select_value(value)
27
+ within do
28
+ all(:css, "option[@value=#{value}]").each(&:select_option)
29
+ end
30
+ end
31
+
32
+ def selected_values
33
+ selected_options.map(&:value)
34
+ end
35
+
36
+ end
37
+ end
38
+
@@ -0,0 +1,50 @@
1
+ module CapybaraPageObject
2
+ class TableElement < Element
3
+ include Enumerable
4
+
5
+ def each
6
+ for index in 1..self.rows.length do
7
+ yield self[index-1]
8
+ end
9
+ end
10
+
11
+ def [](row_index)
12
+ if row_index.kind_of?(String)
13
+ rows.find do |row|
14
+ row.columns.any? {|col| col.text.include?(row_index) }
15
+ end
16
+ else
17
+ rows[row_index]
18
+ end
19
+ end
20
+
21
+ def as_hashes
22
+ rows[1..-1].map do |row|
23
+ Hash[headers.zip(row.map(&:text))]
24
+ end
25
+ end
26
+
27
+
28
+ def rows
29
+ within do
30
+ all(:xpath, ".//child::tr").map do |row|
31
+ TableRowElement.new(row, page, self)
32
+ end
33
+ end
34
+ end
35
+
36
+ def last_row
37
+ the_rows = rows
38
+ the_rows[the_rows.length - 1]
39
+ end
40
+
41
+ def first_row
42
+ rows[0]
43
+ end
44
+
45
+ def headers
46
+ rows[0].columns.map(&:text)
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,66 @@
1
+ module CapybaraPageObject
2
+ class TableRowElement < Element
3
+ include Enumerable
4
+
5
+ attr_reader :parent_table
6
+
7
+ def initialize(element, page, parent_table = nil)
8
+ super(element, page)
9
+ @parent_table = parent_table || find_parent_table
10
+ end
11
+
12
+ def each
13
+ for index in 1..self.columns.length do
14
+ yield self[index-1]
15
+ end
16
+ end
17
+
18
+ def [](column_index)
19
+ if column_index.kind_of?(String)
20
+ column_index = lookup_index_by_name(column_index)
21
+ end
22
+
23
+ return nil if column_index == nil
24
+ columns[column_index]
25
+ end
26
+
27
+ def columns
28
+ within do
29
+ all(:xpath, ".//child::td|th")
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def lookup_index_by_name(column_name)
36
+ parent_table.headers.find_index{|header_name| header_name.include?(column_name) }
37
+ end
38
+
39
+ # Find the parent table based on the current nodes xpath expression
40
+ # e.g.
41
+ # irb > path = "/html/body/table[1]/tbody/tr[1]"
42
+ # irb> paths = path.split("/")
43
+ # => ["", "html", "body", "table[1]", "tbody", "tr[1]"]
44
+ # irb> paths.reverse!
45
+ # => ["tr[1]", "tbody", "table[1]", "body", "html", ""]
46
+ # irb> index = paths.find_index{|s| s.include?('table') }
47
+ # => 2
48
+ # irb> paths = paths[index..-1]
49
+ # => ["table[1]", "body", "html", ""]
50
+ # irb> paths.reverse!
51
+ # => ["", "html", "body", "table[1]"]
52
+ # irb> paths.join("/")
53
+ # => "/html/body/table[1]"
54
+ #
55
+ def find_parent_table
56
+ path = element.path
57
+ paths = path.split("/")
58
+ paths.reverse!
59
+ index = paths.find_index{|s| s.include?('table') }
60
+ paths = paths[index..-1]
61
+ paths.reverse!
62
+ page.first(:xpath, paths.join("/"))
63
+ end
64
+ end
65
+ end
66
+
@@ -0,0 +1,10 @@
1
+ module CapybaraPageObject
2
+ class TextAreaElement < Element
3
+
4
+ def clear
5
+ element.set('')
6
+ end
7
+
8
+ end
9
+ end
10
+
@@ -0,0 +1,14 @@
1
+ module CapybaraPageObject
2
+ class TextFieldElement < Element
3
+
4
+ def clear
5
+ element.set('')
6
+ end
7
+
8
+ #def value
9
+ #element.text
10
+ #end
11
+
12
+ end
13
+ end
14
+
@@ -0,0 +1,54 @@
1
+ module CapybaraPageObject
2
+ #
3
+ # @example Making the PageFactory available to your step definitions
4
+ # World(CapybaraPageObject::PageFactory)
5
+ #
6
+ # @example Visiting a page for the first time in a Scenario
7
+ # goto_page MyPage do |page|
8
+ # ...
9
+ # end
10
+ #
11
+ # @example using a page that has already been visited in a Scenario
12
+ # on_page MyPage do |page|
13
+ # ....
14
+ # end
15
+ #
16
+ #
17
+ module PageFactory
18
+ #
19
+ # Create and navigate to a page object. The navigation will only work if the
20
+ # 'page_url' method was set on the page object.
21
+ #
22
+ # @param [PageObject, String] a class that has included the
23
+ # PageObject module or a string containing the name of the class
24
+ # @param Hash values that is pass through to page class a
25
+ # available in the @params instance variable.
26
+ # @param an optional block to be called
27
+ # @return [PageObject] the newly created page object
28
+ #
29
+ def goto_page(page_class, params={:using_params => {}}, &block)
30
+ new_page(page_class, params, true, &block)
31
+ end
32
+
33
+ #
34
+ # Create a page object.
35
+ #
36
+ def new_page(page_class, params={:using_params => {}}, visit=false, &block)
37
+ page_class = class_from_string(page_class) if page_class.is_a? String
38
+ merged = page_class.params.merge(params[:using_params])
39
+ page_class.instance_variable_set("@merged_params", merged) unless merged.empty?
40
+ @current_page = page_class.new(visit)
41
+ block.call @current_page if block
42
+ @current_page
43
+ end
44
+
45
+ private
46
+
47
+ def class_from_string(str)
48
+ str.split('::').inject(Object) do |mod, class_name|
49
+ mod.const_get(class_name)
50
+ end
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,72 @@
1
+ module CapybaraPageObject
2
+ # Module that when included adds functionality to a page object. This module
3
+ # will add numerous class and instance methods that you use to define and
4
+ # interact with web pages.
5
+ #
6
+ # If we have a login page with a username and password textfield and a login
7
+ # button we might define our page like the one below. We can then interact with
8
+ # the object using the generated methods.
9
+ #
10
+ # @example Login page example
11
+ # class LoginPage
12
+ # include PageObject
13
+ #
14
+ # text_field(:username, :id => 'user')
15
+ # text_field(:password, :id => 'pass')
16
+ # button(:login, :value => 'Login')
17
+ # end
18
+ #
19
+ # login_page = LoginPage.new
20
+ # login_page.username = 'ryan'
21
+ # login_page.password = 'starwars'
22
+ # login_page.login
23
+ #
24
+ # @see PageObject::ClassMethods to see what class level methods are added to this module at runtime.
25
+ #
26
+ module PageObject
27
+ include Capybara::DSL
28
+
29
+ alias_method :navigate_to, :visit
30
+ #
31
+ # Construct a new page object. Prior to browser initialization it will call
32
+ # a method named initialize_accessors if it exists. Upon initialization of
33
+ # the page it will call a method named initialize_page if it exists.
34
+ #
35
+ #
36
+ def initialize(visit=false)
37
+ initialize_accessors if respond_to?(:initialize_accessors)
38
+ if visit
39
+ raise "Page must specify `page_url` to use visit functionality" unless respond_to?(:goto)
40
+ goto
41
+ end
42
+ initialize_page if respond_to?(:initialize_page)
43
+ end
44
+
45
+ def self.included(base)
46
+ base.extend(CapybaraPageObject::ClassMethods)
47
+ base.extend(CapybaraPageObject::ElementClassMethods)
48
+ end
49
+
50
+ def within(element, &block)
51
+ if element.kind_of?(CapybaraPageObject::Element)
52
+ super(element.element, &block)
53
+ else
54
+ super(element, &block)
55
+ end
56
+ end
57
+
58
+ def go_back
59
+ page.evaluate_script('window.history.back()')
60
+ end
61
+
62
+ def go_forward
63
+ page.evaluate_script('window.history.forward()')
64
+ end
65
+
66
+ def reload_page
67
+ url = [ current_path, page.driver.request.env['QUERY_STRING'] ].reject{|s| s.nil? || s == '' }.join('?')
68
+ page.visit url
69
+ end
70
+
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module CapybaraPageObject
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'capybara'
2
+ require "capybara_page_object/version"
3
+
4
+ base = File.join(File.dirname(__FILE__), 'capybara_page_object')
5
+ require File.join(base, 'elements', 'element.rb')
6
+ Dir.glob(File.join(base, 'elements', '**', '*.rb'), &method(:require))
7
+
8
+ require 'capybara_page_object/class_methods'
9
+ require 'capybara_page_object/element_class_methods'
10
+ require 'capybara_page_object/page_object'
11
+ require 'capybara_page_object/page_factory'
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: capybara_page_object
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Ryan Stawarz
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-10-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: capybara
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.10'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: A simple library to abstract out page details from cucumber and browser
70
+ tests
71
+ email:
72
+ - ryan@stawarz.com
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - CODE_OF_CONDUCT.md
81
+ - Gemfile
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bin/console
86
+ - bin/setup
87
+ - capybara_page_object.gemspec
88
+ - cucumber.yml
89
+ - lib/capybara_page_object.rb
90
+ - lib/capybara_page_object/class_methods.rb
91
+ - lib/capybara_page_object/element_class_methods.rb
92
+ - lib/capybara_page_object/elements/button_element.rb
93
+ - lib/capybara_page_object/elements/element.rb
94
+ - lib/capybara_page_object/elements/form_element.rb
95
+ - lib/capybara_page_object/elements/list_element.rb
96
+ - lib/capybara_page_object/elements/media_element.rb
97
+ - lib/capybara_page_object/elements/select_list_element.rb
98
+ - lib/capybara_page_object/elements/table_element.rb
99
+ - lib/capybara_page_object/elements/table_row_element.rb
100
+ - lib/capybara_page_object/elements/text_area_element.rb
101
+ - lib/capybara_page_object/elements/text_field_element.rb
102
+ - lib/capybara_page_object/page_factory.rb
103
+ - lib/capybara_page_object/page_object.rb
104
+ - lib/capybara_page_object/version.rb
105
+ homepage: http://github.com/rstawarz/capybara_page_object
106
+ licenses:
107
+ - MIT
108
+ metadata: {}
109
+ post_install_message:
110
+ rdoc_options: []
111
+ require_paths:
112
+ - lib
113
+ required_ruby_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ required_rubygems_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 2.2.2
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: A Page Object Model to be used with capybara
129
+ test_files: []