page_magic 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
-
[](https://badge.fury.io/rb/page_magic)
|
|
1
|
+
[](https://badge.fury.io/rb/page_magic)
|
|
2
|
+
[](https://gemnasium.com/Ladtech/page_magic)
|
|
3
|
+
[](https://circleci.com/gh/Ladtech/page_magic)
|
|
4
|
+
[](https://codeclimate.com/github/Ladtech/page_magic)
|
|
5
|
+
[](https://codeclimate.com/github/Ladtech/page_magic/coverage)
|
|
6
|
+
[](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
|