page_magic 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +0 -1
- data/Gemfile.lock +10 -10
- data/README.md +149 -104
- data/VERSION +1 -1
- data/lib/page_magic.rb +8 -0
- data/lib/page_magic/exceptions.rb +3 -0
- data/lib/page_magic/matcher.rb +117 -0
- data/lib/page_magic/session.rb +21 -24
- data/page_magic.gemspec +5 -6
- data/spec/page_magic/matcher_spec.rb +235 -0
- data/spec/page_magic/session_spec.rb +58 -20
- metadata +4 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4acdbbbed10c450234259833ff79fc6fe45ba7f8
|
4
|
+
data.tar.gz: 3167a8d0be893169dac91a1f449f9713a057189e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd8232c273a46b22c349e8819da69f3d12680443bf3493334f0ab549698656d27ab98ddbd0ec0d2957b93038e7ec7735c9dfbfcef831aa20823b0f7cbf39ca18
|
7
|
+
data.tar.gz: be986a4a47b5645fb9629a6c695d090de31c0c969a82bff6f23cbef482cca57ca504acc27002a3de98fe6eafd432726a1d3f41197855e8023a9362c65f718526
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -18,7 +18,7 @@ GEM
|
|
18
18
|
rack (>= 1.0.0)
|
19
19
|
rack-test (>= 0.5.4)
|
20
20
|
xpath (~> 2.0)
|
21
|
-
certifi (2015.
|
21
|
+
certifi (2015.11.20)
|
22
22
|
childprocess (0.5.8)
|
23
23
|
ffi (~> 1.0, >= 1.0.11)
|
24
24
|
cliver (0.3.2)
|
@@ -43,7 +43,7 @@ GEM
|
|
43
43
|
gherkin3 (3.1.2)
|
44
44
|
git (1.2.9.1)
|
45
45
|
github-markup (1.4.0)
|
46
|
-
github_api (0.
|
46
|
+
github_api (0.13.0)
|
47
47
|
addressable (~> 2.3)
|
48
48
|
descendants_tracker (~> 0.0.4)
|
49
49
|
faraday (~> 0.8, < 0.10)
|
@@ -65,14 +65,16 @@ GEM
|
|
65
65
|
rdoc
|
66
66
|
json (1.8.3)
|
67
67
|
jwt (1.5.2)
|
68
|
-
mime-types (
|
68
|
+
mime-types (3.0)
|
69
|
+
mime-types-data (~> 3.2015)
|
70
|
+
mime-types-data (3.2015.1120)
|
69
71
|
mini_portile (0.6.2)
|
70
|
-
minitest (5.8.
|
72
|
+
minitest (5.8.3)
|
71
73
|
multi_json (1.11.2)
|
72
74
|
multi_test (0.1.2)
|
73
75
|
multi_xml (0.5.5)
|
74
76
|
multipart-post (2.0.0)
|
75
|
-
nokogiri (1.6.6.
|
77
|
+
nokogiri (1.6.6.4)
|
76
78
|
mini_portile (~> 0.6.0)
|
77
79
|
oauth2 (1.0.0)
|
78
80
|
faraday (>= 0.8, < 0.10)
|
@@ -82,7 +84,7 @@ GEM
|
|
82
84
|
rack (~> 1.2)
|
83
85
|
parser (2.2.3.0)
|
84
86
|
ast (>= 1.1, < 3.0)
|
85
|
-
poltergeist (1.8.
|
87
|
+
poltergeist (1.8.1)
|
86
88
|
capybara (~> 2.1)
|
87
89
|
cliver (~> 0.3.1)
|
88
90
|
multi_json (~> 1.0)
|
@@ -105,7 +107,7 @@ GEM
|
|
105
107
|
rspec-core (~> 3.4.0)
|
106
108
|
rspec-expectations (~> 3.4.0)
|
107
109
|
rspec-mocks (~> 3.4.0)
|
108
|
-
rspec-core (3.4.
|
110
|
+
rspec-core (3.4.1)
|
109
111
|
rspec-support (~> 3.4.0)
|
110
112
|
rspec-expectations (3.4.0)
|
111
113
|
diff-lcs (>= 1.2.0, < 2.0)
|
@@ -113,7 +115,7 @@ GEM
|
|
113
115
|
rspec-mocks (3.4.0)
|
114
116
|
diff-lcs (>= 1.2.0, < 2.0)
|
115
117
|
rspec-support (~> 3.4.0)
|
116
|
-
rspec-support (3.4.
|
118
|
+
rspec-support (3.4.1)
|
117
119
|
rubocop (0.35.1)
|
118
120
|
astrolabe (~> 1.3)
|
119
121
|
parser (>= 2.2.3.0, < 3.0)
|
@@ -142,7 +144,6 @@ GEM
|
|
142
144
|
tins (1.6.0)
|
143
145
|
tzinfo (1.2.2)
|
144
146
|
thread_safe (~> 0.1)
|
145
|
-
wait (0.5.2)
|
146
147
|
watir-webdriver (0.9.1)
|
147
148
|
selenium-webdriver (>= 2.46.2)
|
148
149
|
websocket (1.2.2)
|
@@ -170,6 +171,5 @@ DEPENDENCIES
|
|
170
171
|
rubocop (~> 0.34)
|
171
172
|
simplecov
|
172
173
|
sinatra
|
173
|
-
wait (~> 0)
|
174
174
|
watir-webdriver
|
175
175
|
yard (~> 0.8)
|
data/README.md
CHANGED
@@ -1,4 +1,9 @@
|
|
1
|
-
[![Gem Version](https://badge.fury.io/rb/page_magic.svg)](https://badge.fury.io/rb/page_magic)
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/page_magic.svg)](https://badge.fury.io/rb/page_magic)
|
2
|
+
[![Dependency Status](https://gemnasium.com/Ladtech/page_magic.svg)](https://gemnasium.com/Ladtech/page_magic)
|
3
|
+
[![Circle CI](https://circleci.com/gh/Ladtech/page_magic.svg?style=shield&circle-token=49c8f6869c1e0dc6f3b368e6e22a11fcea3aab8a)](https://circleci.com/gh/Ladtech/page_magic)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/Ladtech/page_magic/badges/gpa.svg)](https://codeclimate.com/github/Ladtech/page_magic)
|
5
|
+
[![Test Coverage](https://codeclimate.com/github/Ladtech/page_magic/badges/coverage.svg)](https://codeclimate.com/github/Ladtech/page_magic/coverage)
|
6
|
+
[![PullReview stats](https://www.pullreview.com/github/Ladtech/page_magic/badges/master.svg?)](https://www.pullreview.com/github/Ladtech/page_magic/reviews/master)
|
2
7
|
#PageMagic
|
3
8
|
PageMagic is an API for testing web applications.
|
4
9
|
|
@@ -13,7 +18,8 @@ Wouldn't it be great if there was a framework that could:
|
|
13
18
|
|
14
19
|
Well PageMagic might just be the answer!
|
15
20
|
|
16
|
-
Give it a try and let us know what you think! There will undoubtedly be things that can be improved and issues that
|
21
|
+
Give it a try and let us know what you think! There will undoubtedly be things that can be improved and issues that
|
22
|
+
we are not aware of so your feedback/pull requests are greatly appreciated!
|
17
23
|
# Contents
|
18
24
|
|
19
25
|
- [Installation](#installation)
|
@@ -31,6 +37,8 @@ Give it a try and let us know what you think! There will undoubtedly be things t
|
|
31
37
|
- [Dynamic Selectors](#dynamic-selectors)
|
32
38
|
- [Starting a session](#starting-a-session)
|
33
39
|
- [Page mapping](#page-mapping)
|
40
|
+
- [Mapping against query string parameters](#mapping-against-query-string-parameters)
|
41
|
+
- [Mapping against fragment identifiers](#mapping-against-fragment-identifiers)
|
34
42
|
- [Watchers](#watchers)
|
35
43
|
- [Method watchers](#method-watchers)
|
36
44
|
- [Simple watchers](#simple-watchers)
|
@@ -43,210 +51,229 @@ Give it a try and let us know what you think! There will undoubtedly be things t
|
|
43
51
|
`gem install page_magic`
|
44
52
|
|
45
53
|
# Quick Start
|
46
|
-
Getting started with PageMagic is
|
54
|
+
Getting started with PageMagic is easy, try running this:
|
47
55
|
|
48
56
|
```ruby
|
49
57
|
require 'page_magic'
|
50
58
|
|
51
|
-
class
|
59
|
+
class Github
|
52
60
|
include PageMagic
|
53
|
-
|
61
|
+
|
62
|
+
url 'https://www.github.com'
|
54
63
|
|
55
|
-
text_field :search_field, name: 'q'
|
56
|
-
|
64
|
+
text_field :search_field, name: 'q' do
|
65
|
+
watch(:url)
|
66
|
+
|
67
|
+
after_events do
|
68
|
+
wait_until { changed?(:url) }
|
69
|
+
end
|
70
|
+
end
|
57
71
|
|
58
|
-
def search
|
59
|
-
search_field.set
|
60
|
-
search_button.click
|
72
|
+
def search(project_name)
|
73
|
+
search_field.set "#{project_name}\n"
|
61
74
|
end
|
62
75
|
end
|
63
|
-
|
64
|
-
|
65
|
-
google.search('page_magic')
|
76
|
+
github = Github.visit(browser: chrome)
|
77
|
+
github.search('page_magic')
|
66
78
|
```
|
67
79
|
|
68
|
-
This example defines a
|
80
|
+
This example defines a page to represent Github's home page, visits it and performs a search.
|
69
81
|
|
70
|
-
This code models a single page and will let you [interact](#interacting-with-elements) with the [elements](#elements)
|
82
|
+
This code models a single page and will let you [interact](#interacting-with-elements) with the [elements](#elements)
|
83
|
+
defined on it as well as use the [helper method](Helpers) we defined.
|
71
84
|
|
72
|
-
You can do lots with PageMagic including [mapping pages](#page-mapping) to a [session](#starting-a-session) so that
|
85
|
+
You can do lots with PageMagic including [mapping pages](#page-mapping) to a [session](#starting-a-session) so that
|
86
|
+
they are fluidly switched in for you. You can even define [hooks](#hooks) to run when ever a element is interacted
|
87
|
+
with. So what are you wating for? there's no place better to start than the [beginning](#defining-pages). Have fun! :)
|
73
88
|
|
74
89
|
# Defining Pages
|
75
90
|
To define something that PageMagic can work with, simply include PageMagic in to a class.
|
76
91
|
```ruby
|
77
|
-
class
|
92
|
+
class Github
|
78
93
|
include PageMagic
|
79
94
|
end
|
80
95
|
```
|
81
96
|
## Elements
|
82
|
-
Defining elements is easy
|
97
|
+
Defining elements is easy. The following example defines a text field called 'search_field' that can be found using its name which is 'q'
|
83
98
|
|
84
99
|
```ruby
|
85
|
-
class
|
100
|
+
class Github
|
86
101
|
include PageMagic
|
87
|
-
|
88
|
-
text_field
|
89
|
-
button(:login_button, text: 'login')
|
102
|
+
|
103
|
+
text_field :search_field, name: 'q'
|
90
104
|
end
|
91
105
|
```
|
92
106
|
|
93
107
|
### Interacting with elements
|
94
|
-
Elements are defined with an id which is the name of the method you will use to reference it. In the above example,
|
108
|
+
Elements are defined with an id which is the name of the method you will use to reference it. In the above example,
|
109
|
+
the text field was defined with the ide `:search_field`.
|
110
|
+
|
111
|
+
After visiting a page you are will get a `Session` object. Elements can be accessed through the session itself.
|
95
112
|
|
96
|
-
After visiting a page with a PageMagic session, you can access all of the elements of that page through the session itself.
|
97
113
|
```ruby
|
98
|
-
|
99
|
-
session.password.set 'passw0rd'
|
100
|
-
session.login_button.click
|
114
|
+
page.search_field.set 'page_magic'
|
101
115
|
```
|
116
|
+
|
102
117
|
#### Multple Results
|
103
|
-
Where an element has been scoped to return multple results, these will be returned in an array.
|
104
|
-
using all of the same features as described in this readme and behave in exactly the same way.
|
118
|
+
Where an element has been scoped to return multple results, these will be returned in an array.
|
105
119
|
```ruby
|
106
|
-
class
|
107
|
-
|
120
|
+
class ResultsPage
|
121
|
+
include PageMagic
|
122
|
+
element :results, css: '.repo-list-item'
|
108
123
|
end
|
109
124
|
|
110
|
-
|
125
|
+
page.results #=> Array<Element>
|
111
126
|
```
|
112
127
|
|
113
128
|
### Sub Elements
|
114
|
-
If your pages are complex you can use PageMagic to compose pages, their elements and subelements to as many levels as
|
129
|
+
If your pages are complex you can use PageMagic to compose pages, their elements and subelements to as many levels as
|
130
|
+
you need to.
|
115
131
|
|
116
132
|
```ruby
|
117
|
-
class
|
133
|
+
class ResultsPage
|
118
134
|
include PageMagic
|
119
|
-
|
120
|
-
element :
|
121
|
-
|
135
|
+
|
136
|
+
element :results, css: '.repo-list-item' do
|
137
|
+
element :stats, css: '.repo-list-stats'
|
138
|
+
element :meta_data, css: '.repo-list-meta'
|
139
|
+
link :repo_link, css: 'h3 a'
|
122
140
|
end
|
123
141
|
end
|
124
142
|
```
|
143
|
+
|
125
144
|
Sub elements can be accessed through their parent elements e.g:
|
126
|
-
```
|
127
|
-
|
145
|
+
```ruby
|
146
|
+
page.results.first.repo_link.click
|
128
147
|
```
|
129
148
|
|
130
149
|
### Custom elements
|
131
150
|
PageMagic allows you to define your own custom elements.
|
132
151
|
```ruby
|
133
|
-
class
|
134
|
-
selector
|
135
|
-
|
136
|
-
element :options, css: '.options' do
|
137
|
-
link(:link1, id: 'link1')
|
138
|
-
link(:link2, id: 'link2')
|
139
|
-
link(:link3, id: 'link3')
|
140
|
-
end
|
152
|
+
class SearchField < PageMagic::Element
|
153
|
+
selector name: 'q'
|
154
|
+
# custom stuff
|
141
155
|
end
|
142
156
|
|
143
|
-
class
|
157
|
+
class Github
|
144
158
|
include PageMagic
|
145
|
-
element
|
159
|
+
element SearchField
|
146
160
|
end
|
147
161
|
```
|
148
|
-
|
162
|
+
|
163
|
+
If an id is not specified then the name of the element class will be used. In the above example the name given to the element of type `SearchField` would be `search_field`. The selector for the element can bespecified on the class itself or overiden when defining the element. The custom element can also be extended as with other elements.
|
164
|
+
|
149
165
|
```ruby
|
150
166
|
class MyPage
|
151
167
|
include PageMagic
|
152
|
-
element
|
168
|
+
element SearchField, :search, selector: '.custom' do
|
153
169
|
link(:extr_link, id: 'extra-link')
|
154
170
|
do
|
155
171
|
end
|
156
172
|
```
|
157
173
|
|
158
174
|
## Hooks
|
159
|
-
PageMagic provides hooks to allow you to
|
175
|
+
PageMagic provides hooks to allow you to define actions that are executed when you pages and elements are interacted with.
|
160
176
|
|
161
177
|
**Note:**
|
162
|
-
-
|
163
|
-
- The following examples wait for actions to happen. You can of course write you own wait code or try out our
|
178
|
+
- You may well find PageMagic's [watchers](#watchers) useful.
|
179
|
+
- The following examples wait for actions to happen. You can of course write you own wait code or feel free try out our
|
180
|
+
[wait_until](#waiting) helper:)
|
164
181
|
|
165
182
|
### On load hook
|
166
|
-
PageMagic lets you define an on_load hook for your pages. This
|
167
|
-
|
183
|
+
PageMagic lets you define an on_load hook for your pages. This will be executed when the browser thinks the page has been loaded.
|
184
|
+
|
168
185
|
```ruby
|
169
|
-
class
|
186
|
+
class Github
|
170
187
|
# ... code defining elements as shown above
|
171
188
|
|
172
189
|
on_load do
|
173
|
-
|
190
|
+
# code that needs to run when the page has loaded
|
174
191
|
end
|
175
192
|
end
|
176
193
|
```
|
177
194
|
|
178
195
|
### Element event hooks
|
179
|
-
Frequently, you are going to have to work with pages that make heavy use of ajax.
|
196
|
+
Frequently, you are going to have to work with pages that make heavy use of ajax. For these occasions PageMagic provides `before_events`
|
197
|
+
and `after_events` hooks that you use to perform custom action.
|
198
|
+
|
199
|
+
In the following example we have added watchers and event hooks to the SearchField custom element we defined in the
|
200
|
+
[previous section](#custom-elements). Encapsulating the business logic here means that we can really add value to
|
201
|
+
the pages that reuse this custom element
|
180
202
|
|
181
203
|
```ruby
|
182
|
-
class
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
after_events do
|
188
|
-
wait_until{fancy_animation_has_disappeared?}
|
189
|
-
end
|
204
|
+
class SearchField < PageMagic::Element
|
205
|
+
selector name: 'q'
|
206
|
+
watch(:url)
|
207
|
+
after_events do
|
208
|
+
wait_until { changed?(:url) }
|
190
209
|
end
|
191
210
|
end
|
192
211
|
```
|
193
212
|
|
194
213
|
## Helper methods
|
195
|
-
|
214
|
+
Helper methods can be defined to avoid writing repetive page/element specific code outside of your pages and elements.
|
196
215
|
|
197
216
|
```ruby
|
198
|
-
class
|
217
|
+
class Github
|
199
218
|
# ... code defining elements as shown above
|
200
|
-
|
201
|
-
def
|
202
|
-
|
203
|
-
password.set pass
|
204
|
-
login_button.click
|
219
|
+
|
220
|
+
def search(project_name)
|
221
|
+
search_field.set "#{project_name}\n"
|
205
222
|
end
|
206
223
|
end
|
207
224
|
```
|
208
225
|
|
209
226
|
We can interact with helper in the same way as we did page elements.
|
227
|
+
|
210
228
|
```ruby
|
211
|
-
|
229
|
+
page.search('page_magic')
|
212
230
|
```
|
213
231
|
|
214
232
|
## Dynamic Selectors
|
215
|
-
In some cases you
|
233
|
+
In some cases you wont be able to specify the selector for an element until runtime. PageMagic allows you to handle
|
234
|
+
such situations with support for dynamic selectors. In the case of our Github example it would be nice to select a
|
235
|
+
particular result by supplying the owners organisation name.
|
216
236
|
|
217
237
|
```ruby
|
218
|
-
class
|
238
|
+
class ResultsPage
|
219
239
|
include PageMagic
|
220
|
-
|
221
|
-
element :
|
222
|
-
|
223
|
-
|
240
|
+
|
241
|
+
element :results do |organisation:|
|
242
|
+
|
243
|
+
selector xpath: "//h3/a[contains(text(), '#{organisation}')]/../.."
|
244
|
+
|
245
|
+
# code for sub elements
|
224
246
|
end
|
225
247
|
end
|
226
248
|
```
|
227
|
-
|
249
|
+
|
250
|
+
In the above example the selector looks for an element that has a link containing text that includes that organisation.
|
251
|
+
The example uses a named parameter and is invoked as follows.
|
228
252
|
```ruby
|
229
|
-
|
253
|
+
page.results(organisation: 'Ladtech')
|
230
254
|
```
|
231
255
|
|
232
256
|
# Starting a session
|
233
257
|
To start a PageMagic session simply decide what browser you want to use and pass it to PageMagic's `.session` method
|
234
258
|
```ruby
|
235
|
-
session = PageMagic.session(browser: :chrome, url: 'https://
|
259
|
+
session = PageMagic.session(browser: :chrome, url: 'https://www.github.com)
|
236
260
|
```
|
237
261
|
|
238
|
-
Your session won't
|
262
|
+
Your session won't do much besides navigating to the given url until you have [mapped pages](#page-mapping) to it, so
|
263
|
+
take a look at this next!
|
239
264
|
|
240
|
-
**Note** PageMagic supports having multiple sessions
|
265
|
+
**Note** PageMagic supports having multiple sessions using different browsers at the same time :)
|
241
266
|
|
242
267
|
## Rack applications and Rack::Test
|
243
|
-
To run a session against a rack application instead of a live site, simply supply the rack application when creating
|
268
|
+
To run a session against a rack application instead of a live site, simply supply the rack application when creating
|
269
|
+
the session
|
270
|
+
|
244
271
|
```ruby
|
245
272
|
session = PageMagic.session(application: YourRackApp, url: '/path_to_start_at')
|
246
273
|
```
|
247
274
|
|
248
|
-
By default PageMagic uses the Rack::Test driver for capybara however you are free to use any browser you like as long
|
249
|
-
the [driver is registered](#drivers) for it.
|
275
|
+
By default PageMagic uses the Rack::Test driver for capybara however you are free to use any browser you like as long
|
276
|
+
as the [driver is registered](#drivers) for it.
|
250
277
|
|
251
278
|
```ruby
|
252
279
|
session = PageMagic.session(application: YourRackApp, browser: :your_chosen_browser, url: '/path_to_start_at')
|
@@ -258,24 +285,39 @@ Out of the box, PageMagic supports the following as parameters to browser:
|
|
258
285
|
- :poltergeist
|
259
286
|
- :rack_test
|
260
287
|
|
261
|
-
Under the hood, PageMagic is using [Capybara](https://github.com/jnicklas/capybara) so you can register any Capybara
|
288
|
+
Under the hood, PageMagic is using [Capybara](https://github.com/jnicklas/capybara) so you can register any Capybara
|
289
|
+
compliant driver you want. See [below](#registering-a-custom-driver) for how to do this.
|
262
290
|
|
263
|
-
**Note:** We don't want to impose particular driver versions so PageMagic does not list any as dependencies. Therefore
|
291
|
+
**Note:** We don't want to impose particular driver versions so PageMagic does not list any as dependencies. Therefore
|
292
|
+
you will need add the requiste gems to your Gemfile.
|
264
293
|
|
265
294
|
# Page mapping
|
266
|
-
With PageMagic you can map which pages should be used to handle which
|
295
|
+
With PageMagic you can map which pages should be used to handle which resources. Meaning that when a the page in the browser changes, PageMagic loads the correct PageObject class to handle it. This feature removes a lot of the juggling and brings back fluency to your code!
|
296
|
+
|
267
297
|
```ruby
|
268
298
|
# define what pages map to what
|
269
|
-
|
270
|
-
|
271
|
-
|
299
|
+
session.define_page_mappings '/' => GitHub, '/search' => ResultsPage
|
300
|
+
```
|
301
|
+
You can use even use regular expressions and provide more than one mapping to the same page object class.
|
302
|
+
|
303
|
+
**Note:** By default mappings are matched against a URL's path. In addition, PageMagic supports mapping against both
|
304
|
+
query string parameters and the fragement identifer (see below). Any combination of these can be used to define a page mapping.
|
305
|
+
|
306
|
+
## Mapping against query string parameters
|
307
|
+
```ruby
|
308
|
+
browser.define_page_mappings PageMagic.mapping(parameters: {parameter_name: string_or_regex}) => ResultsPage
|
309
|
+
```
|
310
|
+
|
311
|
+
## Mapping against fragment identifiers
|
312
|
+
JavaScript MVC frameworks allow different resources to be mapped the fragment portion of URLs. That is the part of the URL that follows the [Fragement identififer](https://en.wikipedia.org/wiki/Fragment_identifier) (#). PageMagic supports mapping page_objects
|
313
|
+
against URL fragments.
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
browser.define_page_mappings PageMagic.mapping(fragment: string_or_regex) => ResultsPage
|
272
317
|
```
|
273
|
-
You can use even use regular expressions to map multiple paths to the same page. In the above example we are mapping paths that that starts with '/messages/' and are followed by one ore more digits to the `MessagePage` class.
|
274
318
|
|
275
319
|
# Watchers
|
276
|
-
PageMagic lets you set a watcher on any
|
277
|
-
things have changed. The `watch` method can be called from anywhere within an element definition. For PageObjects it can
|
278
|
-
only be called from within hooks and helper methods.
|
320
|
+
PageMagic lets you set a watcher on any element. Use watchers to decide when things have changed. The `watch` method can be called from anywhere within an element definition. For PageObjects it can only be called from within hooks and helper methods.
|
279
321
|
|
280
322
|
**Note**: Watchers are not inherited
|
281
323
|
|
@@ -292,8 +334,9 @@ end
|
|
292
334
|
```
|
293
335
|
|
294
336
|
## Simple watchers
|
295
|
-
|
296
|
-
eye and the second is the method that needs to be called to get the value that should be observed.
|
337
|
+
Use `watch` method passing two parameters, the first is the name of the element you want to keep
|
338
|
+
an eye on and the second is the method that needs to be called to get the value that should be observed.
|
339
|
+
|
297
340
|
```ruby
|
298
341
|
element :product_row, css '.cta' do
|
299
342
|
watch(:total, :text)
|
@@ -303,6 +346,7 @@ element :product_row, css '.cta' do
|
|
303
346
|
end
|
304
347
|
end
|
305
348
|
```
|
349
|
+
|
306
350
|
## Custom watchers
|
307
351
|
Custom watchers are defined by passing a name and block parameter to the `watch` method. The block returns the value
|
308
352
|
that needs to be observed. Use watch in this way if you need to do something non standard to obtain a value or to
|
@@ -320,7 +364,7 @@ end
|
|
320
364
|
```
|
321
365
|
|
322
366
|
# Waiting
|
323
|
-
It's inevitable that if there is JavaScript on the page that you are going to have to wait for things to happen
|
367
|
+
It's inevitable that if there is JavaScript on the page that you are going to have to wait for things to happen. PageMagic supplies the `wait_until` method that can be used anywhere you might need it. The wait_until method takes a block that it will execute until either that block returns true or the timeout occurs. See the method docs for details on configuring timeouts and retry intervals.
|
324
368
|
|
325
369
|
# Drivers
|
326
370
|
## Registering a custom driver
|
@@ -339,10 +383,11 @@ end
|
|
339
383
|
PageMagic.drivers.register Webkit
|
340
384
|
|
341
385
|
#3. Use registered driver
|
342
|
-
session = PageMagic.session(browser: webkit, url: 'https://
|
386
|
+
session = PageMagic.session(browser: webkit, url: 'https://www.github.com')
|
343
387
|
```
|
344
388
|
# Cucumber quick start
|
345
|
-
You can obviously use PageMagic anywhere you fancy but one of the places you might decide to use it is within a
|
389
|
+
You can obviously use PageMagic anywhere you fancy but one of the places you might decide to use it is within a
|
390
|
+
Cucumber test suite. If that's the case something like the following could prove useful.
|
346
391
|
|
347
392
|
## Helper methods
|
348
393
|
Put the following in to `features/support/page_magic.rb` to make these helpers available to all of your steps.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.0
|
data/lib/page_magic.rb
CHANGED
@@ -14,6 +14,14 @@ require 'page_magic/drivers'
|
|
14
14
|
|
15
15
|
# module PageMagic - PageMagic is an api for modelling pages in a website.
|
16
16
|
module PageMagic
|
17
|
+
extend SingleForwardable
|
18
|
+
|
19
|
+
# @!method matcher
|
20
|
+
# define match critera for loading a page object class
|
21
|
+
# @see Matcher#initialize
|
22
|
+
# @return [Matcher]
|
23
|
+
def_delegator Matcher, :new, :matcher
|
24
|
+
|
17
25
|
class << self
|
18
26
|
# @return [Drivers] registered drivers
|
19
27
|
def drivers
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require 'active_support/core_ext/object/to_query'
|
2
|
+
module PageMagic
|
3
|
+
# models mapping used to relate pages to uris
|
4
|
+
class Matcher
|
5
|
+
attr_reader :path, :parameters, :fragment
|
6
|
+
|
7
|
+
# @raise [MatcherInvalidException] if at least one component is not specified
|
8
|
+
def initialize(path = nil, parameters: nil, fragment: nil)
|
9
|
+
fail MatcherInvalidException unless path || parameters || fragment
|
10
|
+
@path = path
|
11
|
+
@parameters = parameters
|
12
|
+
@fragment = fragment
|
13
|
+
end
|
14
|
+
|
15
|
+
# @return [Boolean] true if no component contains a Regexp
|
16
|
+
def can_compute_uri?
|
17
|
+
!fuzzy?(fragment) && !fuzzy?(path) && !fuzzy?(parameters)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [String] uri represented by this mapping
|
21
|
+
def compute_uri
|
22
|
+
"#{path}".tap do |uri|
|
23
|
+
uri << "?#{parameters.to_query}" if parameters
|
24
|
+
uri << "##{fragment}" if fragment
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Fixnum] hash for instance
|
29
|
+
def hash
|
30
|
+
[path, parameters, fragment].hash
|
31
|
+
end
|
32
|
+
|
33
|
+
# @param [String] uri
|
34
|
+
# @return [Boolean] returns true if the uri is matched against this matcher
|
35
|
+
def match?(uri)
|
36
|
+
uri = URI(uri)
|
37
|
+
path_valid?(uri.path) && query_string_valid?(uri.query) && fragment_valid?(uri.fragment)
|
38
|
+
end
|
39
|
+
|
40
|
+
# compare this matcher against another
|
41
|
+
# @param [Matcher] other
|
42
|
+
# @return [Fixnum] -1 = smaller, 0 = equal to, 1 = greater than
|
43
|
+
def <=>(other)
|
44
|
+
[:path, :parameters, :fragment].inject(0) do |result, component|
|
45
|
+
result == 0 ? compare(send(component), other.send(component)) : result
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# check equality
|
50
|
+
# @param [Matcher] other
|
51
|
+
# @return [Boolean]
|
52
|
+
def ==(other)
|
53
|
+
return false unless other.is_a?(Matcher)
|
54
|
+
path == other.path && parameters == other.parameters && fragment == other.fragment
|
55
|
+
end
|
56
|
+
|
57
|
+
alias_method :eql?, :==
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def compare(this, other)
|
62
|
+
return presence_comparison(this, other) unless this && other
|
63
|
+
fuzzy_comparison(this, other)
|
64
|
+
end
|
65
|
+
|
66
|
+
def compatible?(string, comparitor)
|
67
|
+
return true if comparitor.nil?
|
68
|
+
if fuzzy?(comparitor)
|
69
|
+
string =~ comparitor ? true : false
|
70
|
+
else
|
71
|
+
string == comparitor
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def fragment_valid?(string)
|
76
|
+
compatible?(string, fragment)
|
77
|
+
end
|
78
|
+
|
79
|
+
def fuzzy?(component)
|
80
|
+
return false unless component
|
81
|
+
if component.is_a?(Hash)
|
82
|
+
component.values.any? { |o| fuzzy?(o) }
|
83
|
+
else
|
84
|
+
component.is_a?(Regexp)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def fuzzy_comparison(this, other)
|
89
|
+
if fuzzy?(this)
|
90
|
+
fuzzy?(other) ? 0 : 1
|
91
|
+
else
|
92
|
+
fuzzy?(other) ? -1 : 0
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def parameters_hash(string)
|
97
|
+
CGI.parse(string.to_s.downcase).collect { |key, value| [key.downcase, value.first] }.to_h
|
98
|
+
end
|
99
|
+
|
100
|
+
def path_valid?(string)
|
101
|
+
compatible?(string, path)
|
102
|
+
end
|
103
|
+
|
104
|
+
def presence_comparison(this, other)
|
105
|
+
return 0 if this.nil? && other.nil?
|
106
|
+
return 1 if this.nil? && other
|
107
|
+
-1
|
108
|
+
end
|
109
|
+
|
110
|
+
def query_string_valid?(string)
|
111
|
+
return true unless parameters
|
112
|
+
!parameters.any? do |key, value|
|
113
|
+
!compatible?(parameters_hash(string)[key.downcase.to_s], value)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
data/lib/page_magic/session.rb
CHANGED
@@ -1,29 +1,30 @@
|
|
1
|
-
require 'wait'
|
2
1
|
require 'forwardable'
|
2
|
+
require 'page_magic/matcher'
|
3
3
|
module PageMagic
|
4
4
|
# class Session - coordinates access to the browser though page objects.
|
5
5
|
class Session
|
6
6
|
URL_MISSING_MSG = 'a path must be mapped or a url supplied'
|
7
|
-
REGEXP_MAPPING_MSG = 'URL could not be derived because mapping
|
7
|
+
REGEXP_MAPPING_MSG = 'URL could not be derived because mapping contains Regexps'
|
8
8
|
INVALID_MAPPING_MSG = 'mapping must be a string or regexp'
|
9
9
|
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
-
attr_reader :raw_session, :transitions
|
12
|
+
attr_reader :raw_session, :transitions, :base_url
|
13
13
|
|
14
14
|
# Create a new session instance
|
15
15
|
# @param [Object] capybara_session an instance of a capybara session
|
16
|
-
# @param [String]
|
17
|
-
def initialize(capybara_session,
|
16
|
+
# @param [String] base_url url to start the session at.
|
17
|
+
def initialize(capybara_session, base_url = nil)
|
18
18
|
@raw_session = capybara_session
|
19
|
-
|
19
|
+
@base_url = base_url
|
20
|
+
visit(url: base_url) if base_url
|
20
21
|
@transitions = {}
|
21
22
|
end
|
22
23
|
|
23
24
|
# @return [Object] returns page object representing the currently loaded page on the browser. If no mapping
|
24
25
|
# is found then nil returned
|
25
26
|
def current_page
|
26
|
-
mapping = find_mapped_page(
|
27
|
+
mapping = find_mapped_page(current_url)
|
27
28
|
@current_page = initialize_page(mapping) if mapping
|
28
29
|
@current_page
|
29
30
|
end
|
@@ -47,7 +48,10 @@ module PageMagic
|
|
47
48
|
# @option transitions [String] path as literal
|
48
49
|
# @option transitions [Regexp] path as a regexp for dynamic matching.
|
49
50
|
def define_page_mappings(transitions)
|
50
|
-
@transitions = transitions
|
51
|
+
@transitions = transitions.collect do |key, value|
|
52
|
+
key = key.is_a?(Matcher) ? key : Matcher.new(key)
|
53
|
+
[key, value]
|
54
|
+
end.to_h
|
51
55
|
end
|
52
56
|
|
53
57
|
# @!method execute_script
|
@@ -84,9 +88,9 @@ module PageMagic
|
|
84
88
|
def visit(page = nil, url: nil)
|
85
89
|
if url
|
86
90
|
raw_session.visit(url)
|
87
|
-
elsif (
|
88
|
-
fail InvalidURLException, REGEXP_MAPPING_MSG
|
89
|
-
raw_session.visit(url(
|
91
|
+
elsif (mapping = transitions.key(page))
|
92
|
+
fail InvalidURLException, REGEXP_MAPPING_MSG unless mapping.can_compute_uri?
|
93
|
+
raw_session.visit(url(base_url, mapping.compute_uri))
|
90
94
|
else
|
91
95
|
fail InvalidURLException, URL_MISSING_MSG
|
92
96
|
end
|
@@ -96,23 +100,16 @@ module PageMagic
|
|
96
100
|
|
97
101
|
private
|
98
102
|
|
99
|
-
def find_mapped_page(
|
100
|
-
|
101
|
-
matches?(path, key)
|
102
|
-
end
|
103
|
-
transitions[mapping]
|
103
|
+
def find_mapped_page(url)
|
104
|
+
matches(url).first
|
104
105
|
end
|
105
106
|
|
106
|
-
def
|
107
|
-
|
107
|
+
def matches(url)
|
108
|
+
transitions.keys.find_all { |matcher| matcher.match?(url) }.sort.collect { |match| transitions[match] }
|
108
109
|
end
|
109
110
|
|
110
|
-
def
|
111
|
-
|
112
|
-
string =~ matcher
|
113
|
-
else
|
114
|
-
string == matcher
|
115
|
-
end
|
111
|
+
def initialize_page(page_class)
|
112
|
+
page_class.new(self).execute_on_load
|
116
113
|
end
|
117
114
|
|
118
115
|
def url(base_url, path)
|
data/page_magic.gemspec
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
4
|
# -*- encoding: utf-8 -*-
|
5
|
-
# stub: page_magic 1.0
|
5
|
+
# stub: page_magic 1.1.0 ruby lib
|
6
6
|
|
7
7
|
Gem::Specification.new do |s|
|
8
8
|
s.name = "page_magic"
|
9
|
-
s.version = "1.0
|
9
|
+
s.version = "1.1.0"
|
10
10
|
|
11
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
12
|
s.require_paths = ["lib"]
|
13
13
|
s.authors = ["Leon Davis"]
|
14
|
-
s.date = "2015-11-
|
14
|
+
s.date = "2015-11-25"
|
15
15
|
s.description = "Framework for modeling and interacting with webpages which wraps capybara"
|
16
16
|
s.email = "info@lad-tech.com"
|
17
17
|
s.extra_rdoc_files = [
|
@@ -47,6 +47,7 @@ Gem::Specification.new do |s|
|
|
47
47
|
"lib/page_magic/elements.rb",
|
48
48
|
"lib/page_magic/exceptions.rb",
|
49
49
|
"lib/page_magic/instance_methods.rb",
|
50
|
+
"lib/page_magic/matcher.rb",
|
50
51
|
"lib/page_magic/session.rb",
|
51
52
|
"lib/page_magic/session_methods.rb",
|
52
53
|
"lib/page_magic/wait_methods.rb",
|
@@ -67,6 +68,7 @@ Gem::Specification.new do |s|
|
|
67
68
|
"spec/page_magic/element_definition_builder_spec.rb",
|
68
69
|
"spec/page_magic/elements_spec.rb",
|
69
70
|
"spec/page_magic/instance_methods_spec.rb",
|
71
|
+
"spec/page_magic/matcher_spec.rb",
|
70
72
|
"spec/page_magic/session_methods_spec.rb",
|
71
73
|
"spec/page_magic/session_spec.rb",
|
72
74
|
"spec/page_magic/wait_methods_spec.rb",
|
@@ -93,7 +95,6 @@ Gem::Specification.new do |s|
|
|
93
95
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
94
96
|
s.add_runtime_dependency(%q<capybara>, [">= 2.5"])
|
95
97
|
s.add_runtime_dependency(%q<activesupport>, ["~> 4"])
|
96
|
-
s.add_runtime_dependency(%q<wait>, ["~> 0"])
|
97
98
|
s.add_development_dependency(%q<jeweler>, ["~> 2.0"])
|
98
99
|
s.add_development_dependency(%q<rubocop>, ["~> 0.34"])
|
99
100
|
s.add_development_dependency(%q<yard>, ["~> 0.8"])
|
@@ -102,7 +103,6 @@ Gem::Specification.new do |s|
|
|
102
103
|
else
|
103
104
|
s.add_dependency(%q<capybara>, [">= 2.5"])
|
104
105
|
s.add_dependency(%q<activesupport>, ["~> 4"])
|
105
|
-
s.add_dependency(%q<wait>, ["~> 0"])
|
106
106
|
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
107
107
|
s.add_dependency(%q<rubocop>, ["~> 0.34"])
|
108
108
|
s.add_dependency(%q<yard>, ["~> 0.8"])
|
@@ -112,7 +112,6 @@ Gem::Specification.new do |s|
|
|
112
112
|
else
|
113
113
|
s.add_dependency(%q<capybara>, [">= 2.5"])
|
114
114
|
s.add_dependency(%q<activesupport>, ["~> 4"])
|
115
|
-
s.add_dependency(%q<wait>, ["~> 0"])
|
116
115
|
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
117
116
|
s.add_dependency(%q<rubocop>, ["~> 0.34"])
|
118
117
|
s.add_dependency(%q<yard>, ["~> 0.8"])
|
@@ -0,0 +1,235 @@
|
|
1
|
+
# rubocop:disable Metrics/ModuleLength
|
2
|
+
module PageMagic
|
3
|
+
describe Matcher do
|
4
|
+
describe '#initialize' do
|
5
|
+
context 'no componentes specified' do
|
6
|
+
it 'raises an exeception' do
|
7
|
+
expect { described_class.new }.to raise_exception(MatcherInvalidException)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
describe '#can_compute_uri?' do
|
12
|
+
context 'regex in path' do
|
13
|
+
it 'returns false' do
|
14
|
+
expect(described_class.new(//).can_compute_uri?).to eq(false)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'regex in parameters' do
|
19
|
+
it 'returns false' do
|
20
|
+
expect(described_class.new(parameters: { param: // }).can_compute_uri?).to eq(false)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'regexp in fragment' do
|
25
|
+
it 'returns false' do
|
26
|
+
expect(described_class.new(fragment: //).can_compute_uri?).to eq(false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'regexp not present' do
|
31
|
+
it 'returns true' do
|
32
|
+
expect(described_class.new('/').can_compute_uri?).to eq(true)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'compare' do
|
38
|
+
subject { described_class.new('/') }
|
39
|
+
context 'param 1 not nil' do
|
40
|
+
context 'param 2 not nil' do
|
41
|
+
context 'literal to fuzzy' do
|
42
|
+
it 'returns -1' do
|
43
|
+
expect(subject.instance_eval { compare('/', //) }).to eq(-1)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
context 'literal to literal' do
|
48
|
+
it 'returns 0' do
|
49
|
+
expect(subject.instance_eval { compare('/', '/') }).to eq(0)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'fuzzy to fuzzy' do
|
54
|
+
it 'returns 0' do
|
55
|
+
expect(subject.instance_eval { compare(//, //) }).to eq(0)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'fuzzy to literal' do
|
60
|
+
it 'returns 1' do
|
61
|
+
expect(subject.instance_eval { compare(//, '/') }).to eq(1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context 'param2 is nil' do
|
67
|
+
it 'returns -1' do
|
68
|
+
expect(subject.instance_eval { compare('/', nil) }).to eq(-1)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'param1 is nil' do
|
74
|
+
context 'param2 not nil' do
|
75
|
+
it 'returns 1' do
|
76
|
+
expect(subject.instance_eval { compare(nil, '/') }).to eq(1)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context 'param2 nil' do
|
81
|
+
it 'returns 0' do
|
82
|
+
expect(subject.instance_eval { compare(nil, nil) }).to eq(0)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe 'compute_uri' do
|
89
|
+
context 'path present' do
|
90
|
+
it 'returns a uri' do
|
91
|
+
expect(described_class.new('/').compute_uri).to eq('/')
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'params present' do
|
96
|
+
context '1 param' do
|
97
|
+
it 'returns a uri' do
|
98
|
+
expect(described_class.new(parameters: { a: 1 }).compute_uri).to eq('?a=1')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'more than 1 param' do
|
103
|
+
it 'returns a uri' do
|
104
|
+
expect(described_class.new(parameters: { a: 1, b: 2 }).compute_uri).to eq('?a=1&b=2')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'fragment present' do
|
110
|
+
it 'returns a uri' do
|
111
|
+
expect(described_class.new(fragment: 'fragment').compute_uri).to eq('#fragment')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#matches?' do
|
117
|
+
let(:matching_url) { 'http://www.example.com/path?foo=bar#fragment' }
|
118
|
+
let(:incompatible_url) { 'http://www.example.com/mismatch?miss=match#mismatch' }
|
119
|
+
context 'path requirement exists' do
|
120
|
+
context 'path is literal' do
|
121
|
+
subject do
|
122
|
+
described_class.new('/path')
|
123
|
+
end
|
124
|
+
it 'returns true for an exact match' do
|
125
|
+
expect(subject.match?(matching_url)).to eq(true)
|
126
|
+
end
|
127
|
+
|
128
|
+
it 'returns false when not an exact match' do
|
129
|
+
expect(subject.match?(incompatible_url)).to eq(false)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context 'path is regexp' do
|
134
|
+
subject do
|
135
|
+
described_class.new(/\d/)
|
136
|
+
end
|
137
|
+
it 'returns true for a match on the regexp' do
|
138
|
+
expect(subject.match?('3')).to eq(true)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'returns false when regexp is not a match' do
|
142
|
+
expect(subject.match?('/mismatch')).to eq(false)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
context 'query string requirement exists' do
|
148
|
+
context 'parameter requirement is a literal' do
|
149
|
+
subject do
|
150
|
+
described_class.new(parameters: { foo: 'bar' })
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'returns true for a match' do
|
154
|
+
expect(subject.match?(matching_url)).to eq(true)
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'returns false when regexp is not a match' do
|
158
|
+
expect(subject.match?(incompatible_url)).to eq(false)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'parameter requirement is a regexp' do
|
163
|
+
subject do
|
164
|
+
described_class.new(parameters: { foo: /b[a]r/ })
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'returns true for a match on the regexp' do
|
168
|
+
expect(subject.match?(matching_url)).to eq(true)
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'returns false when regexp is not a match' do
|
172
|
+
expect(subject.match?(incompatible_url)).to eq(false)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context 'fragment requirement exists' do
|
178
|
+
context 'fragment requirement is a literal' do
|
179
|
+
subject do
|
180
|
+
described_class.new(fragment: 'fragment')
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'returns true for a match' do
|
184
|
+
expect(subject.match?(matching_url)).to eq(true)
|
185
|
+
end
|
186
|
+
|
187
|
+
it 'returns false when regexp is not a match' do
|
188
|
+
expect(subject.match?(incompatible_url)).to eq(false)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context 'fragment requirement is a regexp' do
|
193
|
+
subject do
|
194
|
+
described_class.new(fragment: /fragment/)
|
195
|
+
end
|
196
|
+
|
197
|
+
it 'returns true for a match on the regexp' do
|
198
|
+
expect(subject.match?(matching_url)).to eq(true)
|
199
|
+
end
|
200
|
+
|
201
|
+
it 'returns false when regexp is not a match' do
|
202
|
+
expect(subject.match?(incompatible_url)).to eq(false)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
describe '<=>' do
|
209
|
+
it 'compares paths' do
|
210
|
+
subject = described_class.new('/')
|
211
|
+
expect(subject).to receive(:compare).with('/', nil).and_return(:result)
|
212
|
+
expect(subject <=> described_class.new(parameters: {})).to eq(:result)
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'paths are equal' do
|
216
|
+
it 'compares parameters' do
|
217
|
+
subject = described_class.new('/', parameters: :params1)
|
218
|
+
expect(subject).to receive(:compare).with('/', '/').and_call_original
|
219
|
+
expect(subject).to receive(:compare).with(:params1, :params2).and_return(:params_result)
|
220
|
+
expect(subject <=> described_class.new('/', parameters: :params2)).to eq(:params_result)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
context 'parameters are equal' do
|
225
|
+
it 'compares fragments' do
|
226
|
+
subject = described_class.new('/', parameters: :params, fragment: :frag1)
|
227
|
+
expect(subject).to receive(:compare).with('/', '/').and_call_original
|
228
|
+
expect(subject).to receive(:compare).with(:params, :params).and_call_original
|
229
|
+
expect(subject).to receive(:compare).with(:frag1, :frag2).and_return(:fragment_result)
|
230
|
+
expect(subject <=> described_class.new('/', parameters: :params, fragment: :frag2)).to eq(:fragment_result)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
@@ -4,37 +4,38 @@ module PageMagic
|
|
4
4
|
let(:page) do
|
5
5
|
Class.new do
|
6
6
|
include PageMagic
|
7
|
+
url 'http://url.com'
|
7
8
|
end
|
8
9
|
end
|
9
10
|
|
10
11
|
subject { described_class.new(browser) }
|
11
12
|
|
12
|
-
let(:url) {
|
13
|
-
let(:browser) { double('browser', current_url: url, visit: nil, current_path: :current_path) }
|
13
|
+
let(:url) { page.url }
|
14
|
+
let(:browser) { double('browser', current_url: "#{url}/somepath", visit: nil, current_path: :current_path) }
|
14
15
|
|
15
16
|
describe '#current_page' do
|
16
17
|
let(:another_page_class) do
|
17
18
|
Class.new do
|
18
19
|
include PageMagic
|
19
|
-
url '/another_page1'
|
20
|
+
url 'http://www.example.com/another_page1'
|
20
21
|
end
|
21
22
|
end
|
22
23
|
|
23
24
|
before do
|
24
|
-
subject.define_page_mappings
|
25
|
+
subject.define_page_mappings '/another_page1' => another_page_class
|
25
26
|
subject.visit(page, url: url)
|
26
27
|
end
|
27
28
|
|
28
29
|
context 'page url has not changed' do
|
29
30
|
it 'returns the original page' do
|
30
|
-
allow(browser).to receive(:
|
31
|
+
allow(browser).to receive(:current_url).and_return(page.url)
|
31
32
|
expect(subject.current_page).to be_an_instance_of(page)
|
32
33
|
end
|
33
34
|
end
|
34
35
|
|
35
36
|
context 'page url has changed' do
|
36
37
|
it 'returns the mapped page object' do
|
37
|
-
allow(browser).to receive(:
|
38
|
+
allow(browser).to receive(:current_url).and_return(another_page_class.url)
|
38
39
|
expect(subject.current_page).to be_an_instance_of(another_page_class)
|
39
40
|
end
|
40
41
|
end
|
@@ -52,21 +53,46 @@ module PageMagic
|
|
52
53
|
end
|
53
54
|
end
|
54
55
|
|
56
|
+
describe '#define_page_mappings' do
|
57
|
+
context 'mapping includes a literal' do
|
58
|
+
it 'creates a matcher to contain the specification' do
|
59
|
+
subject.define_page_mappings path: :page
|
60
|
+
expect(subject.transitions).to eq(Matcher.new(:path) => :page)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context 'mapping is a matcher' do
|
65
|
+
it 'leaves it intact' do
|
66
|
+
expected_matcher = Matcher.new(:page)
|
67
|
+
subject.define_page_mappings expected_matcher => :page
|
68
|
+
expect(subject.transitions.key(:page)).to be(expected_matcher)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#execute_script' do
|
74
|
+
it 'calls the execute script method on the capybara session' do
|
75
|
+
expect(browser).to receive(:execute_script).with(:script).and_return(:result)
|
76
|
+
expect(subject.execute_script(:script)).to be(:result)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
55
80
|
describe '#find_mapped_page' do
|
56
81
|
subject do
|
57
|
-
described_class.new(nil)
|
58
|
-
session.define_page_mappings '/page' => :mapped_page_using_string, /page\d/ => :mapped_page_using_regex
|
59
|
-
end
|
82
|
+
described_class.new(nil)
|
60
83
|
end
|
61
84
|
|
62
|
-
context '
|
85
|
+
context 'match found' do
|
63
86
|
it 'returns the page class' do
|
87
|
+
subject.define_page_mappings '/page' => :mapped_page_using_string
|
64
88
|
expect(subject.instance_eval { find_mapped_page('/page') }).to be(:mapped_page_using_string)
|
65
89
|
end
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
90
|
+
|
91
|
+
context 'more than one match is found' do
|
92
|
+
it 'returns the most specific match' do
|
93
|
+
subject.define_page_mappings %r{/page} => :mapped_page_using_regex, '/page' => :mapped_page_using_string
|
94
|
+
expect(subject.instance_eval { find_mapped_page('/page') }).to eq(:mapped_page_using_string)
|
95
|
+
end
|
70
96
|
end
|
71
97
|
end
|
72
98
|
|
@@ -77,10 +103,22 @@ module PageMagic
|
|
77
103
|
end
|
78
104
|
end
|
79
105
|
|
80
|
-
describe '#
|
81
|
-
|
82
|
-
|
83
|
-
|
106
|
+
describe '#matches' do
|
107
|
+
subject do
|
108
|
+
described_class.new(nil)
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns matching page mappings' do
|
112
|
+
subject.define_page_mappings '/page' => :mapped_page_using_string
|
113
|
+
expect(subject.instance_eval { matches('/page') }).to eq([:mapped_page_using_string])
|
114
|
+
end
|
115
|
+
|
116
|
+
context 'more than one match on path' do
|
117
|
+
it 'orders the results by specificity ' do
|
118
|
+
subject.define_page_mappings %r{/page} => :mapped_page_using_regex, '/page' => :mapped_page_using_string
|
119
|
+
expected_result = [:mapped_page_using_string, :mapped_page_using_regex]
|
120
|
+
expect(subject.instance_eval { matches('/page') }).to eq(expected_result)
|
121
|
+
end
|
84
122
|
end
|
85
123
|
end
|
86
124
|
|
@@ -167,9 +205,9 @@ module PageMagic
|
|
167
205
|
expect(session.current_page).to be_a(page)
|
168
206
|
end
|
169
207
|
|
170
|
-
it 'uses the
|
208
|
+
it 'uses the base url and the path in the page mappings' do
|
171
209
|
session.define_page_mappings '/page' => page
|
172
|
-
expect(browser).to receive(:visit).with("#{
|
210
|
+
expect(browser).to receive(:visit).with("#{url}/page")
|
173
211
|
session.visit(page)
|
174
212
|
end
|
175
213
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: page_magic
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Leon Davis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-11-
|
11
|
+
date: 2015-11-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: capybara
|
@@ -38,20 +38,6 @@ dependencies:
|
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
40
|
version: '4'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: wait
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
48
|
-
type: :runtime
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
55
41
|
- !ruby/object:Gem::Dependency
|
56
42
|
name: jeweler
|
57
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -158,6 +144,7 @@ files:
|
|
158
144
|
- lib/page_magic/elements.rb
|
159
145
|
- lib/page_magic/exceptions.rb
|
160
146
|
- lib/page_magic/instance_methods.rb
|
147
|
+
- lib/page_magic/matcher.rb
|
161
148
|
- lib/page_magic/session.rb
|
162
149
|
- lib/page_magic/session_methods.rb
|
163
150
|
- lib/page_magic/wait_methods.rb
|
@@ -178,6 +165,7 @@ files:
|
|
178
165
|
- spec/page_magic/element_definition_builder_spec.rb
|
179
166
|
- spec/page_magic/elements_spec.rb
|
180
167
|
- spec/page_magic/instance_methods_spec.rb
|
168
|
+
- spec/page_magic/matcher_spec.rb
|
181
169
|
- spec/page_magic/session_methods_spec.rb
|
182
170
|
- spec/page_magic/session_spec.rb
|
183
171
|
- spec/page_magic/wait_methods_spec.rb
|