dkastner-capybara-wheel 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +260 -0
- data/Rakefile +1 -0
- data/capybara-wheel.gemspec +29 -0
- data/lib/capybara/wheel.rb +34 -0
- data/lib/capybara/wheel/element.rb +96 -0
- data/lib/capybara/wheel/element_factory.rb +33 -0
- data/lib/capybara/wheel/includes.rb +32 -0
- data/lib/capybara/wheel/page.rb +59 -0
- data/lib/capybara/wheel/version.rb +5 -0
- data/spec/element_factory_spec.rb +25 -0
- data/spec/element_spec.rb +50 -0
- data/spec/feature_spec.rb +21 -0
- data/spec/page_spec.rb +30 -0
- data/spec/spec_helper.rb +7 -0
- metadata +165 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 48f3c56080572f38abc650588314ebc0335646c9
|
4
|
+
data.tar.gz: 86b2298ebc7e77c2970c0c0d7c4d7d9ab91aa2e4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a090305573a3ae190140824d8596274db2c9fe94d71c253cbaff6ab22062ff002624d561a189b939ceb49bf73ecc770941e36ed3c6653795fed4391a5e4bf507
|
7
|
+
data.tar.gz: daf6887577eb28636cd9afc594bb2ed8057c7710d4e036b351a8e39a607e852ad455548dfd5df8afb6f17c3b49541546868b32c4cc306874557febc514d97c35
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Gabriel Rotbart
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,260 @@
|
|
1
|
+
# Capybara::Wheel
|
2
|
+
> Keeping the rodent on track
|
3
|
+
|
4
|
+
Capybara wheel is a page model framework which natively and (hopefully) easily enforces stability coventions:
|
5
|
+
|
6
|
+
- No memoization. Every action or query relating to the page should make a new find callback to the actual page.
|
7
|
+
- Enforces native Capybara wait time when interacting with the page if the element is not there (yet) or changed.
|
8
|
+
|
9
|
+
- No access to capybara directly from the specs.
|
10
|
+
- Enforces single point of reference to selectors / elements.
|
11
|
+
- Also helps reducing memoizations.
|
12
|
+
|
13
|
+
- Element hierarchy structure to scope finds to a specific section of the page.
|
14
|
+
- Reduces ambiguity.
|
15
|
+
|
16
|
+
*A special thank you to @woollyams for the initial concept*
|
17
|
+
|
18
|
+
## Why Wheel?
|
19
|
+
|
20
|
+
Browser driven acceptance tests are notorious for being unstable and hard to maintain. The main culprits are:
|
21
|
+
|
22
|
+
1. Timing issues which result in unstable waits and "is the page loaded?" queries accross the specs.
|
23
|
+
|
24
|
+
2. Memoizing the state of the page (e.g. `search_result = Capybara.find('li')` ) at a certain time. Each spec run might memoize a different state, leading to unstable tests.
|
25
|
+
|
26
|
+
3. No convention around page interactions (e.g. sometimes calling a page model, sometimes a native find with a selector).
|
27
|
+
|
28
|
+
Wheel solves all these by forcing the spec to always act or query the currect state of the page.
|
29
|
+
If the page is not in a state that the spec expects, the native Capybara wait will be used until it is; removing the need to `sleep` or build mechanics to test for page loads.
|
30
|
+
|
31
|
+
Specs are always written in one uniform, clean, way - always calling the same model when dealing with the same page / element. No more hunting multiple specs to change a selector.
|
32
|
+
|
33
|
+
The Element model DSL is still eaily customisable just like "normal" page model classes so domain specfic applications are just as easy, resulting in descriptive, easy to read specs.
|
34
|
+
|
35
|
+
## Example spec:
|
36
|
+
|
37
|
+
feature "Supervillan Console" do
|
38
|
+
|
39
|
+
let(:supervillan_console) { SuperVillanConsole.new }
|
40
|
+
|
41
|
+
it 'can destroy the world with the push of a button' do
|
42
|
+
supervillan_console.visit do |page|
|
43
|
+
page.button_of_doom.should be_present
|
44
|
+
page.button_of_doom.click
|
45
|
+
|
46
|
+
# assert that world was destroyed
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
More spec goodness below.
|
53
|
+
|
54
|
+
## Installation
|
55
|
+
|
56
|
+
Add this line to your application's Gemfile:
|
57
|
+
|
58
|
+
gem 'capybara-wheel'
|
59
|
+
|
60
|
+
And then execute:
|
61
|
+
|
62
|
+
$ bundle
|
63
|
+
|
64
|
+
Or install it yourself as:
|
65
|
+
|
66
|
+
$ gem install capybara-wheel
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
In spec_helper.rb:
|
71
|
+
|
72
|
+
$ require 'capybara/wheel'
|
73
|
+
|
74
|
+
**Important: Capybara Wheel overrides Capybara native `feature` method to inject new behavior. This means it should be required instead or Capybara**
|
75
|
+
|
76
|
+
## Modeling
|
77
|
+
|
78
|
+
### Page Model
|
79
|
+
|
80
|
+
|
81
|
+
To model a new page:
|
82
|
+
|
83
|
+
class NewPage < Capybara::Wheel::Page
|
84
|
+
|
85
|
+
A page needs to implement two methods:
|
86
|
+
|
87
|
+
def path
|
88
|
+
# implament to support visiting the page directly
|
89
|
+
# Rails apps can include Rails.application.routes.url_helpers to access the url helpers
|
90
|
+
# Other applications should implament a relative url
|
91
|
+
end
|
92
|
+
|
93
|
+
and
|
94
|
+
|
95
|
+
def on_page?
|
96
|
+
# Capybara Wheel calls this method before excuting the page block (see Specs section below below)
|
97
|
+
# Recommended: Use Wheel native Page method has_title?('Title')
|
98
|
+
# e.g. has_title?('Login')
|
99
|
+
|
100
|
+
# Or: Use Capybara.find(any selector you expect would be on a loaded page)
|
101
|
+
# e.g. Capybara.find('h1', text: 'Login Page')
|
102
|
+
end
|
103
|
+
|
104
|
+
**_Example:_**
|
105
|
+
|
106
|
+
class SuperVillanConsole << Capybara::Wheel::Page
|
107
|
+
|
108
|
+
def path
|
109
|
+
super_villan_console_path
|
110
|
+
end
|
111
|
+
|
112
|
+
def on_page?
|
113
|
+
has_title?('Destroy all humans')
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
***
|
118
|
+
|
119
|
+
### Element model
|
120
|
+
|
121
|
+
#### Element
|
122
|
+
|
123
|
+
Once inside a Page model, it has access to the `element` method.
|
124
|
+
|
125
|
+
element 'ElementName', 'selector' *optional block*
|
126
|
+
|
127
|
+
# Wheel recommends using a name that follows the class constant name convention (i.e CamelCase)
|
128
|
+
# as an object will be created and assigned this name (see below)
|
129
|
+
|
130
|
+
The `element` method does several important things:
|
131
|
+
|
132
|
+
1. It defines a Page method element_name which allows access and initializes...
|
133
|
+
2. an Element model
|
134
|
+
|
135
|
+
Out of the box, Element accepts all the old familar Capybara Element actions / queries (e.g. click, set, text, visible?). Once an action or query is sent to a Wheel element it then finds the native Capybara element and passes it on. This ensures that each method call is executed on the newset version of the element.
|
136
|
+
|
137
|
+
Passing a block to element gives access to the Element object for the purpose of implamenting a subelement (see below) or rolling your own customised methods:
|
138
|
+
|
139
|
+
**The following messages are delegated to the native Capybara element callback.**
|
140
|
+
|
141
|
+
```
|
142
|
+
#find, #click, #has_content?, #checked?, #text, #visible?, #present?, #selected?, #disabled?, #tag_name, #value, #set, #select_option, #unselect_option, #hover
|
143
|
+
```
|
144
|
+
|
145
|
+
**_Example:_**
|
146
|
+
|
147
|
+
element 'ButtonOfDoom', '#doom-button' do
|
148
|
+
|
149
|
+
def armed?
|
150
|
+
capybara_element.text == 'Armed'
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
element 'MissleTracker', '.missle-tracker'
|
156
|
+
|
157
|
+
|
158
|
+
#=> SuperVillanConsole.new.button_of_doom.armed?
|
159
|
+
|
160
|
+
#=> SuperVillanConsole.new.missle_tracker.visible?
|
161
|
+
|
162
|
+
|
163
|
+
|
164
|
+
#### (Sub)Element
|
165
|
+
|
166
|
+
An element block also accepts the `element` method.
|
167
|
+
|
168
|
+
element 'SubElementName', 'selector' *optional block*
|
169
|
+
|
170
|
+
When called inside an element block, the element behaves like an Element but is now scoped to the containing (or parent) element, which reduces ambiguity.
|
171
|
+
|
172
|
+
**_Example:_**
|
173
|
+
|
174
|
+
Two buttons have an `li` element with the `.key` class. We want to be able to find one and turn it without accidently causing world peace:
|
175
|
+
|
176
|
+
element 'ButtonOfDoom', '#doom-button' do
|
177
|
+
|
178
|
+
# reference to this key is scoped inside the #doom-button element
|
179
|
+
element 'ArmingKey', 'li.key' do
|
180
|
+
|
181
|
+
def turn
|
182
|
+
click
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
element 'ButtonOfWorldPeace', '#peace-button' do
|
189
|
+
|
190
|
+
# reference to this key is scoped inside the #peace-button element
|
191
|
+
element 'ArmingKey', 'li.key' do
|
192
|
+
|
193
|
+
def turn
|
194
|
+
click
|
195
|
+
end
|
196
|
+
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#=> SuperVillanConsole.new.button_of_world_peace.arming_key.turn
|
201
|
+
#=> SuperVillanConsole.new.button_of_doom.arming_key.turn
|
202
|
+
|
203
|
+
## Specs
|
204
|
+
|
205
|
+
Wheel specs are written to always refer to the models and to never refer to Capybara directly. The page methods we implemented above allows us the do:
|
206
|
+
|
207
|
+
let(:some_page) { SomePageModel.new }
|
208
|
+
|
209
|
+
it 'can visit a page' do
|
210
|
+
some_page.visit
|
211
|
+
end
|
212
|
+
|
213
|
+
The visit page would use the `on_page?` method to determine if we are indeed on the page. It will use Capyara's internal wait to poll the page until it is ready. We can then even excute within the scope of the page:
|
214
|
+
|
215
|
+
it 'can visit a page and pass a block to execute' do
|
216
|
+
some_page.visit do | page |
|
217
|
+
page.some_element.should be_visible
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'can also just check if we're on the page without visiting it'
|
222
|
+
some_page.on do | page |
|
223
|
+
page.some_element.should be_visible
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
|
228
|
+
**_Example:_**
|
229
|
+
|
230
|
+
feature "Supervillan Console" do
|
231
|
+
|
232
|
+
let(:supervillan_console) { SuperVillanConsole.new }
|
233
|
+
|
234
|
+
before :each do
|
235
|
+
supervillan_console.visit
|
236
|
+
end
|
237
|
+
|
238
|
+
scenario 'can arm doom button' do
|
239
|
+
supervillan_console.on do | con |
|
240
|
+
con.button_of_doom.arming_key.turn
|
241
|
+
con.button_of_doom.armed?.should be_true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
|
246
|
+
end
|
247
|
+
|
248
|
+
***
|
249
|
+
***
|
250
|
+
***
|
251
|
+
|
252
|
+
|
253
|
+
|
254
|
+
## Contributing
|
255
|
+
|
256
|
+
1. Fork it
|
257
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
258
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
259
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
260
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'capybara/wheel/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dkastner-capybara-wheel"
|
8
|
+
spec.version = Capybara::Wheel::VERSION
|
9
|
+
spec.authors = ["@gabrielrotbart"]
|
10
|
+
spec.email = ["gabe@hooroo.com"]
|
11
|
+
spec.description = %q{Keeping the rodent on track}
|
12
|
+
spec.summary = %q{Creating (yet another) page model gem based around making capybara tests more stable with no need for waits}
|
13
|
+
spec.homepage = "http://github.com/hooroo/capybara-wheel"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency "capybara", "~> 2.1"
|
22
|
+
spec.add_dependency "rspec"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
25
|
+
spec.add_development_dependency "rake"
|
26
|
+
spec.add_development_dependency "selenium-webdriver", "~> 2.0"
|
27
|
+
spec.add_development_dependency "sinatra", "~> 1.0"
|
28
|
+
spec.add_development_dependency "pry"
|
29
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Dir.glob(File.join(File.dirname(__FILE__), 'wheel', '*.rb')).each {|file| require file}
|
2
|
+
require 'capybara'
|
3
|
+
require 'capybara/dsl'
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
module Capybara
|
7
|
+
module Wheel
|
8
|
+
# main mixin to access wheel
|
9
|
+
|
10
|
+
def self.included(base)
|
11
|
+
base.instance_eval do
|
12
|
+
alias :background :before
|
13
|
+
alias :scenario :it
|
14
|
+
alias :given :let
|
15
|
+
alias :given! :let!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module FeatureOverride
|
20
|
+
def feature(*args, &block)
|
21
|
+
options = {
|
22
|
+
type: :wheel_feature,
|
23
|
+
caller: caller
|
24
|
+
}
|
25
|
+
options.merge!(args.pop) if args.last.is_a?(Hash)
|
26
|
+
describe(*args, options, &block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
extend Capybara::Wheel::FeatureOverride
|
34
|
+
RSpec.configuration.include Capybara::Wheel, wheel_feature: true
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'capybara/wheel/includes'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Capybara
|
5
|
+
module Wheel
|
6
|
+
class Element
|
7
|
+
|
8
|
+
extend Forwardable
|
9
|
+
include Capybara::Wheel::Includes
|
10
|
+
extend Capybara::Wheel::Includes::ClassIncludes
|
11
|
+
|
12
|
+
attr_reader :scope
|
13
|
+
|
14
|
+
def initialize(selector = nil, scope = nil)
|
15
|
+
@selector = selector if selector
|
16
|
+
@scope = scope
|
17
|
+
end
|
18
|
+
|
19
|
+
def_delegators :capybara_element,
|
20
|
+
:find,
|
21
|
+
:click,
|
22
|
+
:has_content?,
|
23
|
+
:checked?,
|
24
|
+
:text,
|
25
|
+
:visible?,
|
26
|
+
:present?,
|
27
|
+
:selected?,
|
28
|
+
:disabled?,
|
29
|
+
:tag_name,
|
30
|
+
:value,
|
31
|
+
:set,
|
32
|
+
:select_option,
|
33
|
+
:unselect_option,
|
34
|
+
:hover,
|
35
|
+
:[]
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
def present?
|
40
|
+
capybara_element.visible?
|
41
|
+
rescue Capybara::ElementNotFound => e
|
42
|
+
puts "#{e} on #{Time.now.strftime("%H:%I:%S:%L")}"
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def visible?
|
47
|
+
capybara_element.visible?
|
48
|
+
rescue Capybara::ElementNotFound => e
|
49
|
+
puts "#{e} on #{Time.now.strftime("%H:%I:%S:%L")}"
|
50
|
+
false
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.element(name, selector, block = nil)
|
54
|
+
subelement_factory = lambda do |parent_element|
|
55
|
+
Capybara::Wheel::ElementFactory.create_element(selector, parent_element, block)
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method(underscore(name).to_sym) { subelement_factory.call(self) }
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def element(name, selector, &block)
|
63
|
+
self.class.element(name, selector, block)
|
64
|
+
end
|
65
|
+
|
66
|
+
#TODO: deprecated in 0.0.5
|
67
|
+
def subelement(name, selector, &block)
|
68
|
+
puts "subelement will be deprecated in future versions."
|
69
|
+
puts "Calling element inside an element block will scope it to the parent element"
|
70
|
+
element(name, selector, block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.subelement(name, selector, &block)
|
74
|
+
puts "subelement will be deprecated in future versions."
|
75
|
+
puts "Calling element inside an element block will scope it to the parent element"
|
76
|
+
element(name, selector, &block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def selector
|
80
|
+
@selector
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
# Finds a capybara element representing this thing
|
86
|
+
def capybara_element
|
87
|
+
scope_capybara.find(selector, options)
|
88
|
+
end
|
89
|
+
|
90
|
+
def scope_capybara
|
91
|
+
scope.nil? ? capybara : scope.send(:capybara_element)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'capybara/wheel'
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Wheel
|
5
|
+
class ElementFactory
|
6
|
+
|
7
|
+
def self.create_element_klass(selector, options = {}, block = nil)
|
8
|
+
subclass = Class.new(Capybara::Wheel::Element)
|
9
|
+
|
10
|
+
_selector = selector
|
11
|
+
_options = options
|
12
|
+
|
13
|
+
subclass.class_exec do
|
14
|
+
define_method(:selector) { @selector = _selector }
|
15
|
+
define_method(:options) { @options = _options }
|
16
|
+
end
|
17
|
+
|
18
|
+
subclass.class_eval(&block) if block
|
19
|
+
|
20
|
+
subclass
|
21
|
+
end
|
22
|
+
|
23
|
+
#TODO: Pass object not an instance
|
24
|
+
def self.create_element(selector, parent_element, block = nil)
|
25
|
+
subelement = Capybara::Wheel::Element.new(selector, parent_element)
|
26
|
+
subelement.instance_eval(&block) if block
|
27
|
+
|
28
|
+
subelement
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'capybara/wheel'
|
2
|
+
require 'capybara'
|
3
|
+
require 'capybara/dsl'
|
4
|
+
require 'rspec'
|
5
|
+
|
6
|
+
module Capybara
|
7
|
+
module Wheel
|
8
|
+
module Includes
|
9
|
+
|
10
|
+
include Capybara::DSL
|
11
|
+
|
12
|
+
def capybara
|
13
|
+
Capybara.current_session
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassIncludes
|
17
|
+
def underscore(string)
|
18
|
+
string.gsub(/::/, '/').
|
19
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
20
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
21
|
+
tr("-", "_").
|
22
|
+
downcase
|
23
|
+
end
|
24
|
+
|
25
|
+
def make_const(string)
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'capybara/wheel/includes'
|
2
|
+
|
3
|
+
module Capybara
|
4
|
+
module Wheel
|
5
|
+
class Page
|
6
|
+
|
7
|
+
include Capybara::Wheel::Includes
|
8
|
+
extend Capybara::Wheel::Includes::ClassIncludes
|
9
|
+
|
10
|
+
# actively visits page and executes block in context
|
11
|
+
def visit(&block)
|
12
|
+
capybara.visit(path)
|
13
|
+
if block_given?
|
14
|
+
on(&block)
|
15
|
+
else
|
16
|
+
on_page?
|
17
|
+
end
|
18
|
+
return self
|
19
|
+
end
|
20
|
+
|
21
|
+
# execute a block in the context of this page
|
22
|
+
def on
|
23
|
+
unless on_page?
|
24
|
+
raise "We don't appear to be on the #{self.class}"
|
25
|
+
end
|
26
|
+
yield self if block_given?
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the path (relative URI) of this page
|
31
|
+
def path
|
32
|
+
raise NotImplementedError, "implement to support page.visit"
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return true if the browser is on this page
|
36
|
+
def on_page?
|
37
|
+
raise NotImplementedError, "implement me, e.g. using #has_title?"
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_title?(expected_title)
|
41
|
+
capybara.has_css?("head title", text: expected_title)
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.element(name, selector, options = {}, &block)
|
45
|
+
begin
|
46
|
+
element_klass = const_set("#{name}", Capybara::Wheel::ElementFactory.create_element_klass(selector, options, block))
|
47
|
+
rescue NameError
|
48
|
+
puts "We recommend using capitalized element and subelement names"
|
49
|
+
name = name.capitalize!
|
50
|
+
retry
|
51
|
+
end
|
52
|
+
|
53
|
+
define_method(underscore(name).to_sym) { element_klass.new(selector) }
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'ElementFactory' do
|
4
|
+
let(:subject) { Capybara::Wheel::ElementFactory }
|
5
|
+
let(:selector) { '#rad-selector'}
|
6
|
+
let(:created_element_klass) { subject.create_element_klass(selector) }
|
7
|
+
|
8
|
+
|
9
|
+
it 'creates an element' do
|
10
|
+
created_element_klass.superclass.should == Capybara::Wheel::Element
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'allows access to the selector' do
|
14
|
+
created_element_klass.new.selector.should == selector
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'generated instance evalutes block' do
|
18
|
+
test_block = Proc.new do
|
19
|
+
def evaled_method
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
subject.create_element_klass(selector, test_block).new.should respond_to(:evaled_method)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'Element' do
|
4
|
+
let(:element_selector) { '#some-selector' }
|
5
|
+
let(:element) { Capybara::Wheel::Element }
|
6
|
+
let(:element_instance) { element.new(element_selector) }
|
7
|
+
|
8
|
+
it 'has access to capybara' do
|
9
|
+
element_instance.methods.include?(:capybara).should be_true
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'has delegators for Capybara actions' do
|
13
|
+
pending 'grab all the actions from Capybara and ensure delegators are implemented'
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'self.element' do
|
17
|
+
let(:element_selector) { '#rad-parent-selector' }
|
18
|
+
|
19
|
+
let(:subelement_name) { 'RadSubElement' }
|
20
|
+
let(:subelement_selector) { '#rad-sub-selector' }
|
21
|
+
|
22
|
+
before do
|
23
|
+
element_instance.instance_eval do
|
24
|
+
element('RadSubElement', '#rad-sub-selector')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'created a method for calling the element' do
|
29
|
+
element_instance.should respond_to(:rad_sub_element)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'create a (sub)element with parent element context' do
|
33
|
+
element_instance.send(:rad_sub_element).scope.should == element_instance
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'the find would be scoped to parent' do
|
37
|
+
subelement = element_instance.send(:rad_sub_element)
|
38
|
+
mock_capybara_session = mock(Capybara::Session)
|
39
|
+
mock_capybara_element = mock('Capybara::Element')
|
40
|
+
Capybara.stub!(:current_session).and_return(mock_capybara_session)
|
41
|
+
mock_capybara_session.stub(:find).with(element_selector).and_return(mock_capybara_element)
|
42
|
+
mock_capybara_element.stub(:find).with(subelement_selector).and_return(mock_capybara_element)
|
43
|
+
|
44
|
+
element_instance.should_receive(:capybara_element).once.and_return(mock_capybara_element)
|
45
|
+
|
46
|
+
subelement.send(:capybara_element)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.configure do |config|
|
4
|
+
config.before(:each, :some_hook) do
|
5
|
+
@hook_passed_on = true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
feature 'runs as a wheel feature', :some_hook do
|
10
|
+
|
11
|
+
it 'should pass' do
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should be set as a wheel feature' do
|
15
|
+
example.metadata[:type].should == :wheel_feature
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should pass the right hook' do
|
19
|
+
@hook_passed_on.should be_true
|
20
|
+
end
|
21
|
+
end
|
data/spec/page_spec.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
feature 'Page' do
|
4
|
+
let(:page) { Capybara::Wheel::Page }
|
5
|
+
|
6
|
+
subject { page.new }
|
7
|
+
it { is_expected.to respond_to(:capybara) }
|
8
|
+
|
9
|
+
describe '.element' do
|
10
|
+
|
11
|
+
context 'element with no options' do
|
12
|
+
subject { page.element('RadElement', '#rad-selector').new }
|
13
|
+
|
14
|
+
it { is_expected.to respond_to(:rad_element) }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'element with extra options' do
|
18
|
+
subject { page.element('OptionalElement', '#opt-selector', visible: false).new }
|
19
|
+
|
20
|
+
it { is_expected.to respond_to(:optional_element) }
|
21
|
+
|
22
|
+
it 'has options' do
|
23
|
+
expect(subject.optional_element.options).to eq(visible: false)
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dkastner-capybara-wheel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- "@gabrielrotbart"
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-12-16 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: '2.1'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.1'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rake
|
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
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: selenium-webdriver
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '2.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '2.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: sinatra
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description: Keeping the rodent on track
|
112
|
+
email:
|
113
|
+
- gabe@hooroo.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- Gemfile
|
120
|
+
- LICENSE.txt
|
121
|
+
- README.md
|
122
|
+
- Rakefile
|
123
|
+
- capybara-wheel.gemspec
|
124
|
+
- lib/capybara/wheel.rb
|
125
|
+
- lib/capybara/wheel/element.rb
|
126
|
+
- lib/capybara/wheel/element_factory.rb
|
127
|
+
- lib/capybara/wheel/includes.rb
|
128
|
+
- lib/capybara/wheel/page.rb
|
129
|
+
- lib/capybara/wheel/version.rb
|
130
|
+
- spec/element_factory_spec.rb
|
131
|
+
- spec/element_spec.rb
|
132
|
+
- spec/feature_spec.rb
|
133
|
+
- spec/page_spec.rb
|
134
|
+
- spec/spec_helper.rb
|
135
|
+
homepage: http://github.com/hooroo/capybara-wheel
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata: {}
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubyforge_project:
|
155
|
+
rubygems_version: 2.2.2
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Creating (yet another) page model gem based around making capybara tests
|
159
|
+
more stable with no need for waits
|
160
|
+
test_files:
|
161
|
+
- spec/element_factory_spec.rb
|
162
|
+
- spec/element_spec.rb
|
163
|
+
- spec/feature_spec.rb
|
164
|
+
- spec/page_spec.rb
|
165
|
+
- spec/spec_helper.rb
|