page_magic 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5996ae0a70973cd36ed8c14db0ae4bc6a2aa58b1
4
- data.tar.gz: 14fb91c0dfa3638d6604a6d77a8559f2f3bf7660
3
+ metadata.gz: 262a69afc9f016ea5dfb07cd11c90bf9efc29a33
4
+ data.tar.gz: 6a822ce3efb1abcf1dfc73d829a6b5604db5b518
5
5
  SHA512:
6
- metadata.gz: 66de0184bc4fbc4cb481a1591ced83ca45f807a2d26b1088d129ba1e5108eb2470a3ae68fc27dc986e7b52a38c816e3abfe0a7a947085b7ce1f7008513832291
7
- data.tar.gz: 8a3c2dc2a3997ebac5836620a525d0e7bf8372c9050e0c4b36c8a73a2e9b3afa46569ee12b5ac24ca420bb2ca7290c6ed10cb8da7e68bfd283dcf47fc66c1d29
6
+ metadata.gz: 17b6e79c778f473d8a4cc2ca83fc36e02bb31b0b2e6686204afd92f4298388d0914150c4e6002a3f6883b808ecab5161d1c6fe3dea6bd552a6be0cfb6b8611fc
7
+ data.tar.gz: e5dacc0762ff7ec21f303247460597dc201ddaf409c45a12e16ee57bdc7cfaa6c1659375335be10b840f45b852b523ade1557516367d43ca02aef6dded24c44c
data/.rubocop.yml CHANGED
@@ -7,6 +7,7 @@ AllCops:
7
7
  - 'vendor/**/*'
8
8
  - 'coverage/**/*'
9
9
  - '.idea/**/*'
10
+ - '*.gemspec'
10
11
 
11
12
  Metrics/ParameterLists:
12
13
  CountKeywordArgs: false
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'capybara', '2.5.0'
3
+ gem 'capybara', '>= 2.5'
4
4
  gem 'activesupport', '~> 4'
5
5
  gem 'wait', '~> 0'
6
6
 
@@ -12,6 +12,7 @@ group :test do
12
12
  gem 'poltergeist'
13
13
  gem 'codeclimate-test-reporter', group: :test, require: nil
14
14
  gem 'pullreview-coverage', require: false
15
+ gem 'cucumber'
15
16
  end
16
17
 
17
18
  group :development do
data/Gemfile.lock CHANGED
@@ -1,13 +1,13 @@
1
1
  GEM
2
2
  remote: https://rubygems.org/
3
3
  specs:
4
- activesupport (4.2.3)
4
+ activesupport (4.2.5)
5
5
  i18n (~> 0.7)
6
6
  json (~> 1.7, >= 1.7.7)
7
7
  minitest (~> 5.1)
8
8
  thread_safe (~> 0.3, >= 0.3.4)
9
9
  tzinfo (~> 1.1)
10
- addressable (2.3.6)
10
+ addressable (2.3.8)
11
11
  ast (2.1.0)
12
12
  astrolabe (1.3.1)
13
13
  parser (~> 2.2)
@@ -19,27 +19,40 @@ GEM
19
19
  rack-test (>= 0.5.4)
20
20
  xpath (~> 2.0)
21
21
  certifi (2015.08.10)
22
- childprocess (0.3.9)
22
+ childprocess (0.5.8)
23
23
  ffi (~> 1.0, >= 1.0.11)
24
24
  cliver (0.3.2)
25
25
  codeclimate-test-reporter (0.4.8)
26
26
  simplecov (>= 0.7.1, < 1.0.0)
27
+ cucumber (2.1.0)
28
+ builder (>= 2.1.2)
29
+ cucumber-core (~> 1.3.0)
30
+ diff-lcs (>= 1.1.3)
31
+ gherkin3 (~> 3.1.0)
32
+ multi_json (>= 1.7.5, < 2.0)
33
+ multi_test (>= 0.1.2)
34
+ cucumber-core (1.3.0)
35
+ gherkin3 (~> 3.1.0)
36
+ descendants_tracker (0.0.4)
37
+ thread_safe (~> 0.3, >= 0.3.1)
27
38
  diff-lcs (1.2.5)
28
39
  docile (1.1.5)
29
- faraday (0.8.9)
30
- multipart-post (~> 1.2.0)
31
- ffi (1.9.3)
32
- git (1.2.6)
40
+ faraday (0.9.2)
41
+ multipart-post (>= 1.2, < 3)
42
+ ffi (1.9.10)
43
+ gherkin3 (3.1.2)
44
+ git (1.2.9.1)
33
45
  github-markup (1.4.0)
34
- github_api (0.10.2)
35
- addressable
36
- faraday (~> 0.8.7)
37
- hashie (>= 1.2)
38
- multi_json (~> 1.4)
39
- nokogiri (~> 1.6.0)
46
+ github_api (0.12.4)
47
+ addressable (~> 2.3)
48
+ descendants_tracker (~> 0.0.4)
49
+ faraday (~> 0.8, < 0.10)
50
+ hashie (>= 3.4)
51
+ multi_json (>= 1.7.5, < 2.0)
52
+ nokogiri (~> 1.6.6)
40
53
  oauth2
41
- hashie (2.0.5)
42
- highline (1.6.21)
54
+ hashie (3.4.3)
55
+ highline (1.7.8)
43
56
  i18n (0.7.0)
44
57
  jeweler (2.0.1)
45
58
  builder
@@ -50,26 +63,26 @@ GEM
50
63
  nokogiri (>= 1.5.10)
51
64
  rake
52
65
  rdoc
53
- json (1.8.1)
54
- jwt (0.1.11)
55
- multi_json (>= 1.5)
66
+ json (1.8.3)
67
+ jwt (1.5.2)
56
68
  mime-types (2.6.2)
57
69
  mini_portile (0.6.2)
58
- minitest (5.7.0)
70
+ minitest (5.8.2)
59
71
  multi_json (1.11.2)
72
+ multi_test (0.1.2)
60
73
  multi_xml (0.5.5)
61
- multipart-post (1.2.0)
74
+ multipart-post (2.0.0)
62
75
  nokogiri (1.6.6.2)
63
76
  mini_portile (~> 0.6.0)
64
- oauth2 (0.9.3)
77
+ oauth2 (1.0.0)
65
78
  faraday (>= 0.8, < 0.10)
66
- jwt (~> 0.1.8)
79
+ jwt (~> 1.0)
67
80
  multi_json (~> 1.3)
68
81
  multi_xml (~> 0.5)
69
82
  rack (~> 1.2)
70
83
  parser (2.2.3.0)
71
84
  ast (>= 1.1, < 3.0)
72
- poltergeist (1.7.0)
85
+ poltergeist (1.8.0)
73
86
  capybara (~> 2.1)
74
87
  cliver (~> 0.3.1)
75
88
  multi_json (~> 1.0)
@@ -79,58 +92,60 @@ GEM
79
92
  certifi
80
93
  simplecov (>= 0.7.1, < 1.0.0)
81
94
  rack (1.6.4)
82
- rack-protection (1.5.0)
95
+ rack-protection (1.5.3)
83
96
  rack
84
97
  rack-test (0.6.3)
85
98
  rack (>= 1.0)
86
99
  rainbow (2.0.0)
87
- rake (10.2.2)
88
- rdoc (4.1.1)
100
+ rake (10.4.2)
101
+ rdoc (4.2.0)
89
102
  json (~> 1.4)
90
103
  redcarpet (3.3.3)
91
- rspec (3.3.0)
92
- rspec-core (~> 3.3.0)
93
- rspec-expectations (~> 3.3.0)
94
- rspec-mocks (~> 3.3.0)
95
- rspec-core (3.3.2)
96
- rspec-support (~> 3.3.0)
97
- rspec-expectations (3.3.1)
104
+ rspec (3.4.0)
105
+ rspec-core (~> 3.4.0)
106
+ rspec-expectations (~> 3.4.0)
107
+ rspec-mocks (~> 3.4.0)
108
+ rspec-core (3.4.0)
109
+ rspec-support (~> 3.4.0)
110
+ rspec-expectations (3.4.0)
98
111
  diff-lcs (>= 1.2.0, < 2.0)
99
- rspec-support (~> 3.3.0)
100
- rspec-mocks (3.3.2)
112
+ rspec-support (~> 3.4.0)
113
+ rspec-mocks (3.4.0)
101
114
  diff-lcs (>= 1.2.0, < 2.0)
102
- rspec-support (~> 3.3.0)
103
- rspec-support (3.3.0)
104
- rubocop (0.34.2)
115
+ rspec-support (~> 3.4.0)
116
+ rspec-support (3.4.0)
117
+ rubocop (0.35.1)
105
118
  astrolabe (~> 1.3)
106
- parser (>= 2.2.2.5, < 3.0)
119
+ parser (>= 2.2.3.0, < 3.0)
107
120
  powerpack (~> 0.1)
108
121
  rainbow (>= 1.99.1, < 3.0)
109
- ruby-progressbar (~> 1.4)
122
+ ruby-progressbar (~> 1.7)
123
+ tins (<= 1.6.0)
110
124
  ruby-progressbar (1.7.5)
111
- rubyzip (0.9.9)
112
- selenium-webdriver (2.35.1)
113
- childprocess (>= 0.2.5)
125
+ rubyzip (1.1.7)
126
+ selenium-webdriver (2.48.1)
127
+ childprocess (~> 0.5)
114
128
  multi_json (~> 1.0)
115
- rubyzip (< 1.0.0)
116
- websocket (~> 1.0.4)
129
+ rubyzip (~> 1.0)
130
+ websocket (~> 1.0)
117
131
  simplecov (0.10.0)
118
132
  docile (~> 1.1.0)
119
133
  json (~> 1.8)
120
134
  simplecov-html (~> 0.10.0)
121
135
  simplecov-html (0.10.0)
122
- sinatra (1.4.3)
136
+ sinatra (1.4.6)
123
137
  rack (~> 1.4)
124
138
  rack-protection (~> 1.4)
125
- tilt (~> 1.3, >= 1.3.4)
139
+ tilt (>= 1.3, < 3)
126
140
  thread_safe (0.3.5)
127
- tilt (1.4.1)
141
+ tilt (2.0.1)
142
+ tins (1.6.0)
128
143
  tzinfo (1.2.2)
129
144
  thread_safe (~> 0.1)
130
- wait (0.5.1)
131
- watir-webdriver (0.6.4)
132
- selenium-webdriver (>= 2.18.0)
133
- websocket (1.0.7)
145
+ wait (0.5.2)
146
+ watir-webdriver (0.9.1)
147
+ selenium-webdriver (>= 2.46.2)
148
+ websocket (1.2.2)
134
149
  websocket-driver (0.6.3)
135
150
  websocket-extensions (>= 0.1.0)
136
151
  websocket-extensions (0.1.2)
@@ -143,8 +158,9 @@ PLATFORMS
143
158
 
144
159
  DEPENDENCIES
145
160
  activesupport (~> 4)
146
- capybara (= 2.5.0)
161
+ capybara (>= 2.5)
147
162
  codeclimate-test-reporter
163
+ cucumber
148
164
  github-markup (~> 1.4)
149
165
  jeweler (~> 2.0)
150
166
  poltergeist
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Circle CI](https://circleci.com/gh/Ladtech/page_magic.svg?style=shield&circle-token=49c8f6869c1e0dc6f3b368e6e22a11fcea3aab8a)](https://circleci.com/gh/Ladtech/page_magic) [![Code Climate](https://codeclimate.com/github/Ladtech/page_magic/badges/gpa.svg)](https://codeclimate.com/github/Ladtech/page_magic) [![Test Coverage](https://codeclimate.com/github/Ladtech/page_magic/badges/coverage.svg)](https://codeclimate.com/github/Ladtech/page_magic/coverage) [![PullReview stats](https://www.pullreview.com/github/Ladtech/page_magic/badges/master.svg?)](https://www.pullreview.com/github/Ladtech/page_magic/reviews/master)
1
+ [![Gem Version](https://badge.fury.io/rb/page_magic.svg)](https://badge.fury.io/rb/page_magic) [![Dependency Status](https://gemnasium.com/Ladtech/page_magic.svg)](https://gemnasium.com/Ladtech/page_magic) [![Circle CI](https://circleci.com/gh/Ladtech/page_magic.svg?style=shield&circle-token=49c8f6869c1e0dc6f3b368e6e22a11fcea3aab8a)](https://circleci.com/gh/Ladtech/page_magic) [![Code Climate](https://codeclimate.com/github/Ladtech/page_magic/badges/gpa.svg)](https://codeclimate.com/github/Ladtech/page_magic) [![Test Coverage](https://codeclimate.com/github/Ladtech/page_magic/badges/coverage.svg)](https://codeclimate.com/github/Ladtech/page_magic/coverage) [![PullReview stats](https://www.pullreview.com/github/Ladtech/page_magic/badges/master.svg?)](https://www.pullreview.com/github/Ladtech/page_magic/reviews/master)
2
2
  #PageMagic
3
3
  PageMagic is an API for testing web applications.
4
4
 
@@ -15,7 +15,7 @@ Well PageMagic might just be the answer!
15
15
  Give it a try and let us know what you think! There will undoubtedly be things that can be improved and issues that we are not aware of so your feedback/pull requests are greatly appreciated!
16
16
  # Contents
17
17
  - [Installation](#installation)
18
- - [Starting a session](#starting-a-session)
18
+ - [Quick Start](#quick-start)
19
19
  - [Defining Pages](#defining-pages)
20
20
  - [Elements](#elements)
21
21
  - [Interacting with elements](#interacting-with-elements)
@@ -27,30 +27,45 @@ Give it a try and let us know what you think! There will undoubtedly be things t
27
27
  - [On load hook](#on-load-hook)
28
28
  - [Helper Methods](#helper-methods)
29
29
  - [Dynamic Selectors](#dynamic-selectors)
30
+ - [Starting a session](#starting-a-session)
31
+ - [Page mapping](#page-mapping)
30
32
  - [Watchers](#watchers)
31
33
  - [Method watchers](#method-watchers)
32
34
  - [Simple watchers](#simple-watchers)
33
35
  - [Custom watchers](#custom-watchers)
34
- - [Page mapping](#page-mapping)
36
+ - [Waiting](#waiting)
35
37
  - [Drivers](#drivers)
36
- - [Pulling it all together](#pulling-it-all-together)
38
+ - [Cucumber Quick Start](#cucumber-quick-start)
37
39
 
38
40
  # Installation
39
- `gem install page_magic --pre`
41
+ `gem install page_magic`
40
42
 
41
- # Starting a session
42
- To start a PageMagic session simply decide what browser you want to use and pass it to PageMagic's `.session` method
43
+ # Quick Start
44
+ Getting started with PageMagic is as easy, try running this:
43
45
  ```ruby
44
- session = PageMagic.session(browser: :chrome, url: 'https://21st-century-mail.com')
46
+ require 'page_magic'
47
+
48
+ class Google
49
+ include PageMagic
50
+ url 'https://www.google.com'
51
+
52
+ text_field :search_field, name: 'q'
53
+ button :search_button, name: 'btnG'
54
+
55
+ def search term
56
+ search_field.set term
57
+ search_button.click
58
+ end
59
+ end
60
+
61
+ google = Google.visit(browser: :chrome)
62
+ google.search('page_magic')
45
63
  ```
46
- Out of the box, PageMagic knows how to work with:
47
- - Chrome and Firefox
48
- - poltergeist
49
- - RackTest - Read more on testing rack compliant object's directly later on
64
+ This example defines a simple page to represent Google's search page, visits it and performs a search.
50
65
 
51
- Under the hood, PageMagic is using [Capybara](https://github.com/jnicklas/capybara) so you can register any Capybara specific driver you want. See [below](#registering-a-custom-driver) for how to do this.
66
+ This code models a single page and will let you [interact](#interacting-with-elements) with the [elements](#elements) defined on it as well as use the [helper method](Helpers) we defined.
52
67
 
53
- **Note:** We don't want to impose particular driver versions so PageMagic does not list any as dependencies. Therefore you will need add the requiste gem to your Gemfile.
68
+ You can do lots with PageMagic including [mapping pages](#page-mapping) to a [session](#starting-a-session) so that they are fluidly switched in for you. You can even define [hooks](#hooks) to run when ever a element is interacted with. So what are you wating for? there' no place better to start than the [beginning](#defining-pages). Have fun! :)
54
69
 
55
70
  # Defining Pages
56
71
  To define something that PageMagic can work with, simply include PageMagic in to a class.
@@ -112,7 +127,7 @@ session.message.read.click
112
127
  PageMagic allows you to define your own custom elements.
113
128
  ```ruby
114
129
  class Nav < PageMagic::Element
115
- selector css: '.nav
130
+ selector css: '.nav'
116
131
 
117
132
  element :options, css: '.options' do
118
133
  link(:link1, id: 'link1')
@@ -137,7 +152,11 @@ end
137
152
  ```
138
153
 
139
154
  ## Hooks
140
- PageMagic provides hooks to allow you to interact at the right moments with your pages
155
+ PageMagic provides hooks to allow you to interact at the right moments with your pages.
156
+
157
+ **Note:**
158
+ - with hooks you may well find PageMagic's [watchers](#watchers) useful.
159
+ - The following examples wait for actions to happen. You can of course write you own wait code or try out our [wait_until](#waiting) helper:)
141
160
 
142
161
  ### On load hook
143
162
  PageMagic lets you define an on_load hook for your pages. This lets you write any custom wait logic you might need
@@ -147,7 +166,7 @@ class LoginPage
147
166
  # ... code defining elements as shown above
148
167
 
149
168
  on_load do
150
- # wait code here
169
+ wait_until{login_fields_have_appeared?}
151
170
  end
152
171
  end
153
172
  ```
@@ -156,7 +175,7 @@ end
156
175
  Frequently, you are going to have to work with pages that make heavy use of ajax. This means that just because you've clicked something, it doesn't mean that the action is finished. For these occasions PageMagic provides `before_events` and `after_events` hooks that you use to perform custom actions and wait for things to happen.
157
176
 
158
177
  ```ruby
159
- class MessagePage
178
+ class EmailMessagePage
160
179
  include PageMagic
161
180
  ## code defining other elements, such as subject and body
162
181
 
@@ -206,9 +225,53 @@ Here we have defined the 'message' element using a block that takes subject argu
206
225
  session.message(subject: 'test message')
207
226
  ```
208
227
 
228
+ # Starting a session
229
+ To start a PageMagic session simply decide what browser you want to use and pass it to PageMagic's `.session` method
230
+ ```ruby
231
+ session = PageMagic.session(browser: :chrome, url: 'https://21st-century-mail.com')
232
+ ```
233
+
234
+ Your session won't you do much besides navigating to the given url until you have [mapped pages](#page-mapping) to it, so take a look at this next!
235
+
236
+ **Note** PageMagic supports having multiple sessions pointed at different urls using different browsers at the same time :)
237
+
238
+ ## Rack applications and Rack::Test
239
+ To run a session against a rack application instead of a live site, simply supply the rack application when creating the session
240
+ ```ruby
241
+ session = PageMagic.session(application: YourRackApp, url: '/path_to_start_at')
242
+ ```
243
+
244
+ By default PageMagic uses the Rack::Test driver for capybara however you are free to use any browser you like as long as
245
+ the [driver is registered](#drivers) for it.
246
+
247
+ ```ruby
248
+ session = PageMagic.session(application: YourRackApp, browser: :your_chosen_browser, url: '/path_to_start_at')
249
+ ```
250
+
251
+ Out of the box, PageMagic supports the following as parameters to browser:
252
+ - :chrome
253
+ - :firefox
254
+ - :poltergeist
255
+ - :rack_test
256
+
257
+ Under the hood, PageMagic is using [Capybara](https://github.com/jnicklas/capybara) so you can register any Capybara specific driver you want. See [below](#registering-a-custom-driver) for how to do this.
258
+
259
+ **Note:** We don't want to impose particular driver versions so PageMagic does not list any as dependencies. Therefore you will need add the requiste gem to your Gemfile.
260
+
261
+ # Page mapping
262
+ With PageMagic you can map which pages should be used to handle which URL paths. This is a pretty killer feature that will remove a lot of the juggling and bring back fluency to your code!
263
+ ```ruby
264
+ # define what pages map to what
265
+ browser.define_page_mappings %r{/messages/\d+} => MessagePage,
266
+ '/login' => LoginPage,
267
+ '/' => MailBox
268
+ ```
269
+ 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.
270
+
209
271
  # Watchers
210
272
  PageMagic lets you set a watcher on any of the elements that you have defined on your pages. Use watchers to decide when
211
- things have changed.
273
+ things have changed. The `watch` method can be called from anywhere within an element definition. For PageObjects it can
274
+ only be called from within hooks and helper methods.
212
275
 
213
276
  **Note**: Watchers are not inherited
214
277
 
@@ -216,9 +279,7 @@ things have changed.
216
279
  Method watchers watch the output of the given method name.
217
280
  ```ruby
218
281
  button :javascript_button, css: '.fancy_button' do
219
- before_events do
220
- watch(:url)
221
- end
282
+ watch(:url)
222
283
 
223
284
  after_events do
224
285
  wait_until{changed?(:url)}
@@ -231,9 +292,7 @@ Simple watchers use the `watch` method passing two parameters, the first is the
231
292
  eye and the second is the method that needs to be called to get the value that should be observed.
232
293
  ```ruby
233
294
  element :product_row, css '.cta' do
234
- before_events do
235
- element(:total, css: '.total')
236
- end
295
+ watch(:total, :text)
237
296
 
238
297
  after_events do
239
298
  wait_until{changed?(:total)}
@@ -246,10 +305,8 @@ that needs to be observed. Use watch in this way if you need to do something non
246
305
  access an element not located within the current element but elsewhere within the page.
247
306
  ```ruby
248
307
  element :product_row, css '.cta' do
249
- before_events do
250
- element(:total) do
251
- session.nav.total.text
252
- end
308
+ watch(:total) do
309
+ session.nav.total.text
253
310
  end
254
311
 
255
312
  after_events do
@@ -257,15 +314,9 @@ element :product_row, css '.cta' do
257
314
  end
258
315
  end
259
316
  ```
260
- # Page mapping
261
- With PageMagic you can map which pages should be used to handle which URL paths. This is a pretty killer feature that will remove a lot of the juggling and bring back fluency to your code!
262
- ```ruby
263
- # define what pages map to what
264
- browser.define_page_mappings %r{/messages/\d+} => MessagePage,
265
- '/login' => LoginPage,
266
- '/' => MailBox
267
- ```
268
- 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.
317
+
318
+ # Waiting
319
+ It's inevitable that if there is JavaScript on the page that you are going to have to wait for things to happen before you can move on. 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.
269
320
 
270
321
  # Drivers
271
322
  ## Registering a custom driver
@@ -286,34 +337,57 @@ PageMagic.drivers.register Webkit
286
337
  #3. Use registered driver
287
338
  session = PageMagic.session(browser: webkit, url: 'https://21st-century-mail.com')
288
339
  ```
340
+ # Cucumber quick start
341
+ You can obviously use PageMagic anywhere you fancy but one of the places you might decide to use it is within a Cucumber test suite. If that's the case something like the following could prove useful.
289
342
 
290
- # Pulling it all together
291
- Imagine the scene. You've written a web based mail client and now you want to test it...
292
- You have a scenario in mind that goes something along the lines of:
293
- - Send yourself an email with a unique subject
294
- - Go to the Login page and login
295
- - Find the message using it's unique subject and read it
296
- - delete the message
297
-
298
- You're mail client is totally 21st century so there is loads of lovely ajax etc...
299
-
300
- Using the PageMagic you can implement an API that might look something like the following to use:
343
+ ## Helper methods
344
+ Put the following in to `features/support/page_magic.rb` to make these helpers available to all of your steps.
301
345
 
302
346
  ```ruby
303
- test_subject = send_test_mail('test@21st-century-mail.com')
304
- #Visit your site using a PageMagic session we prepared earlier
305
- session.visit(LoginPage)
306
-
307
- #Login using some handy helper method on our page object
308
- session.login('username', 'password')
347
+ require 'page_magic'
348
+ require 'active_support/inflector'
349
+ require 'your_pages'
350
+
351
+ World(Module.new do
352
+ def page_class(string)
353
+ "#{string}Page".delete(' ').constantize
354
+ end
355
+
356
+ def snake_case(string)
357
+ string.delete(' ').underscore
358
+ end
359
+
360
+ def session
361
+ $session ||= begin
362
+ PageMagic.session(browser: :chrome, url: the_base_url).tap do |session|
363
+
364
+ session.define_page_mappings '/login' => LoginPage,
365
+ '/' => HomePage
366
+
367
+ end
368
+ end
369
+ end
370
+ end)
371
+ ```
372
+ ## Example steps
373
+ Use the [above](#helper-methods) helpers to navigate to pages with steps like the following.
309
374
 
310
- #Find the message amongst all the other messages that are on screen and read it
311
- session.message(subject: test_subject).read.click
375
+ ```ruby
376
+ Given /^I am on the '(.*)' page$/ do |page_name|
377
+ session.visit(page_class(page_name))
378
+ end
312
379
 
313
- #Now we are on the message screen lets delete it without having to worry about the ajax.
314
- session.delete_message
380
+ And /^I set '(.*)' to be '(.*)'$/ do |field, value|
381
+ session.send(snake_case(field)).set value
382
+ end
315
383
 
316
- fail "message is still there!" if session.message(subject: test_subject).exists?
384
+ When /^I click '(.*)'$/ do |element|
385
+ session.send(snake_case(element)).click
386
+ end
317
387
 
318
- # Sweet :)
388
+ Then /^I should be on the '(.*)' page$/ do |page_name|
389
+ current_page = session.current_page.class
390
+ expected_page = page_class(page_name)
391
+ fail "On #{current_page}, expected #{expected_page}" unless current_page == expected_page
392
+ end
319
393
  ```
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.0.2
@@ -1,4 +1,5 @@
1
1
  module PageMagic
2
+ # for the benefit of pull review :@
2
3
  class Element
3
4
  # contains method for finding element definitions
4
5
  module Locators
@@ -12,7 +13,7 @@ module PageMagic
12
13
  def element_by_name(name, *args)
13
14
  defintion = element_definitions[name]
14
15
  fail ElementMissingException, (ELEMENT_NOT_DEFINED_MSG % name) unless defintion
15
- defintion.call(*args.append(self))
16
+ defintion.call(self, *args)
16
17
  end
17
18
 
18
19
  # @return [Array] class level defined element definitions
@@ -12,21 +12,46 @@ module PageMagic
12
12
  include SelectorMethods, Watchers, SessionMethods, WaitMethods, Locators
13
13
  extend Elements, SelectorMethods, Forwardable
14
14
 
15
- attr_reader :type, :name, :parent_page_element, :browser_element, :before_events, :after_events
15
+ attr_reader :type, :name, :parent_element, :browser_element, :before_events, :after_events
16
16
 
17
17
  class << self
18
- # Get/Sets the block of code to be run after an event is triggered on an element. See {EVENT_TYPES} for the
19
- # list of events that this block will be triggered for. The block is run in the scope of the element object
20
- def after_events(&block)
21
- return @after_hook unless block
22
- @after_hook = block
18
+ # @!method after_events
19
+ # If a block is passed in, it adds it to be run after an event is triggered on an element.
20
+ # See {EVENT_TYPES} for the
21
+ # @return [Array] all registered blocks
22
+
23
+ # @!method before_events
24
+ # If a block is passed in, it adds it to be run before an event is triggered on an element.
25
+ # @see .after_events
26
+ %i(after_events before_events).each do |method|
27
+ define_method method do |&block|
28
+ instance_variable_name = "@#{method}".to_sym
29
+ instance_variable_value = instance_variable_get(instance_variable_name) || [DEFAULT_HOOK]
30
+ instance_variable_value << block if block
31
+ instance_variable_set(instance_variable_name, instance_variable_value)
32
+ end
33
+ end
34
+
35
+ # Get/Sets the parent element desribed by this class
36
+ # @param [Element] page_element parent page element
37
+ # @return [Element]
38
+ def parent_element(page_element = nil)
39
+ return @parent_page_element unless page_element
40
+ @parent_page_element = page_element
41
+ end
42
+
43
+ # called when class inherits this one
44
+ # @param [Class] clazz inheriting class
45
+ def inherited(clazz)
46
+ super
47
+ clazz.before_events.replace(before_events)
48
+ clazz.after_events.replace(after_events)
23
49
  end
24
50
 
25
- # Get/Sets the block of code to be run before an event is triggered on an element. See {EVENT_TYPES} for the
26
- # list of events that this block will be triggered for. The block is run in the scope of the element object
27
- def before_events(&block)
28
- return @before_hook unless block
29
- @before_hook = block
51
+ # Defines watchers to be used by instances
52
+ # @see Watchers#watch
53
+ def watch(name, method = nil, &block)
54
+ before_events { watch(name, method, &block) }
30
55
  end
31
56
 
32
57
  def ==(other)
@@ -34,11 +59,11 @@ module PageMagic
34
59
  end
35
60
  end
36
61
 
37
- def initialize(browser_element, parent_page_element)
62
+ def initialize(browser_element)
38
63
  @browser_element = browser_element
39
- @parent_page_element = parent_page_element
40
- @before_events = self.class.before_events || DEFAULT_HOOK
41
- @after_events = self.class.after_events || DEFAULT_HOOK
64
+ @parent_element = self.class.parent_element
65
+ @before_events = self.class.before_events
66
+ @after_events = self.class.after_events
42
67
  @element_definitions = self.class.element_definitions.dup
43
68
  wrap_events(browser_element)
44
69
  end
@@ -64,18 +89,18 @@ module PageMagic
64
89
  # get the current session
65
90
  # @return [Session] returns the session of the parent page element.
66
91
  # Capybara session
67
- def_delegator :parent_page_element, :session
92
+ def_delegator :parent_element, :session
68
93
 
69
94
  private
70
95
 
71
- def apply_hooks(raw_element:, capybara_method:, before_hook:, after_hook:)
96
+ def apply_hooks(raw_element:, capybara_method:, before_events:, after_events:)
72
97
  original_method = raw_element.method(capybara_method)
73
98
  this = self
74
99
 
75
100
  raw_element.define_singleton_method(capybara_method) do |*arguments, &block|
76
- this.instance_exec(&before_hook)
101
+ before_events.each { |event| this.instance_exec(&event) }
77
102
  original_method.call(*arguments, &block)
78
- this.instance_exec(&after_hook)
103
+ after_events.each { |event| this.instance_exec(&event) }
79
104
  end
80
105
  end
81
106
 
@@ -88,8 +113,8 @@ module PageMagic
88
113
  next unless raw_element.respond_to?(action_method)
89
114
  apply_hooks(raw_element: raw_element,
90
115
  capybara_method: action_method,
91
- before_hook: before_events,
92
- after_hook: after_events)
116
+ before_events: before_events,
117
+ after_events: after_events)
93
118
  end
94
119
  end
95
120
  end
@@ -20,7 +20,7 @@ module PageMagic
20
20
  builder = page_element.element_by_name(method, *args)
21
21
 
22
22
  prefecteched_element = builder.element
23
- return builder.build(prefecteched_element, page_element) if prefecteched_element
23
+ return builder.build(prefecteched_element) if prefecteched_element
24
24
 
25
25
  elements = find(builder)
26
26
  elements.size == 1 ? elements.first : elements
@@ -41,7 +41,7 @@ module PageMagic
41
41
  fail ElementMissingException, ELEMENT_NOT_FOUND_MSG % query.description
42
42
  end
43
43
 
44
- result.to_a.collect { |e| builder.build(e, page_element) }
44
+ result.to_a.collect { |e| builder.build(e) }
45
45
  end
46
46
  end
47
47
  end
@@ -23,11 +23,10 @@ module PageMagic
23
23
  end
24
24
 
25
25
  # Create new instance of the ElementDefinition modeled by this builder
26
- # @param [Element] parent_page_element element containing the element modelled by this builder
27
26
  # @param [Object] browser_element capybara browser element corresponding to the element modelled by this builder
28
27
  # @return [Element] element definition
29
- def build(browser_element, parent_page_element)
30
- definition_class.new(browser_element, parent_page_element)
28
+ def build(browser_element)
29
+ definition_class.new(browser_element)
31
30
  end
32
31
 
33
32
  def ==(other)
@@ -52,16 +52,16 @@ module PageMagic
52
52
  # @param [Hash] selector a key value pair defining the method for locating this element. See above for details
53
53
  def element(*args, &block)
54
54
  block ||= proc {}
55
- section_class = remove_argument(args, Class) || Element
56
- name = compute_name(args, section_class)
57
- options = { type: __callee__,
58
- selector: compute_selector(args, section_class),
59
- options: compute_argument(args, Hash),
60
- element: args.delete_at(0) }
61
-
62
- add_element_definition(name) do |*e_args|
63
- defintion_class = Class.new(section_class) { class_exec(*e_args, &(block)) }
64
- ElementDefinitionBuilder.new(options.merge(definition_class: defintion_class))
55
+ options = compute_options(args.dup)
56
+ options[:type] = __callee__
57
+ section_class = options.delete(:section_class)
58
+
59
+ add_element_definition(options.delete(:name)) do |parent_element, *e_args|
60
+ definition_class = Class.new(section_class) do
61
+ parent_element(parent_element)
62
+ class_exec(*e_args, &(block))
63
+ end
64
+ ElementDefinitionBuilder.new(options.merge(definition_class: definition_class))
65
65
  end
66
66
  end
67
67
 
@@ -75,6 +75,15 @@ module PageMagic
75
75
 
76
76
  private
77
77
 
78
+ def compute_options(args)
79
+ section_class = remove_argument(args, Class) || Element
80
+ { name: compute_name(args, section_class),
81
+ selector: compute_selector(args, section_class),
82
+ options: compute_argument(args, Hash),
83
+ element: args.delete_at(0),
84
+ section_class: section_class }
85
+ end
86
+
78
87
  def add_element_definition(name, &block)
79
88
  fail InvalidElementNameException, 'duplicate page element defined' if element_definitions[name]
80
89
 
@@ -24,7 +24,7 @@ module PageMagic
24
24
  # is found then nil returned
25
25
  def current_page
26
26
  mapping = find_mapped_page(current_path)
27
- @current_page = mapping.new(self) if mapping
27
+ @current_page = initialize_page(mapping) if mapping
28
28
  @current_page
29
29
  end
30
30
 
@@ -90,7 +90,7 @@ module PageMagic
90
90
  else
91
91
  fail InvalidURLException, URL_MISSING_MSG
92
92
  end
93
- @current_page = page.new(self).execute_on_load if page
93
+ @current_page = initialize_page(page) if page
94
94
  self
95
95
  end
96
96
 
@@ -103,6 +103,10 @@ module PageMagic
103
103
  transitions[mapping]
104
104
  end
105
105
 
106
+ def initialize_page(page_class)
107
+ page_class.new(self).execute_on_load
108
+ end
109
+
106
110
  def matches?(string, matcher)
107
111
  if matcher.is_a?(Regexp)
108
112
  string =~ matcher
@@ -25,6 +25,7 @@ module PageMagic
25
25
  def watch(name, method = nil, &block)
26
26
  fail ElementMissingException, (ELEMENT_MISSING_MSG % name) unless block || respond_to?(name)
27
27
  watched_element = block ? Watcher.new(name, &block) : Watcher.new(name, method)
28
+ watchers.delete_if { |w| w.name == name }
28
29
  watchers << watched_element.check(self)
29
30
  end
30
31
 
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.1 ruby lib
5
+ # stub: page_magic 1.0.2 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "page_magic"
9
- s.version = "1.0.1"
9
+ s.version = "1.0.2"
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-12"
14
+ s.date = "2015-11-16"
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 = [
data/spec/element_spec.rb CHANGED
@@ -14,10 +14,12 @@ module PageMagic
14
14
 
15
15
  let(:page) { session.current_page }
16
16
 
17
- # let!(:browser) { double('browser') }
17
+ let(:described_class) do
18
+ Class.new(Element).tap { |clazz| clazz.parent_element(page) }
19
+ end
18
20
 
19
21
  subject do
20
- described_class.new(:page_element, page)
22
+ described_class.new(:page_element)
21
23
  end
22
24
 
23
25
  it_behaves_like 'session accessor'
@@ -25,8 +27,60 @@ module PageMagic
25
27
  it_behaves_like 'waiter'
26
28
  it_behaves_like 'element locator'
27
29
 
28
- describe 'inheriting' do
29
- it 'lets you create custom elements' do
30
+ describe '.after_events' do
31
+ subject do
32
+ Class.new(described_class)
33
+ end
34
+
35
+ context 'hook set' do
36
+ it 'returns that hook' do
37
+ hook = proc {}
38
+ subject.after_events(&hook)
39
+ expect(subject.after_events).to eq([described_class::DEFAULT_HOOK, hook])
40
+ end
41
+ end
42
+ context 'hook not registered' do
43
+ it 'returns the default hook' do
44
+ expect(subject.after_events).to eq([described_class::DEFAULT_HOOK])
45
+ end
46
+ end
47
+ end
48
+
49
+ describe '.before_events' do
50
+ subject do
51
+ Class.new(described_class)
52
+ end
53
+
54
+ context 'hook set' do
55
+ it 'returns that hook' do
56
+ hook = proc {}
57
+ subject.before_events(&hook)
58
+ expect(subject.before_events).to eq([described_class::DEFAULT_HOOK, hook])
59
+ end
60
+ end
61
+ context 'hook not registered' do
62
+ it 'returns the default hook' do
63
+ expect(subject.before_events).to eq([described_class::DEFAULT_HOOK])
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '.inherited' do
69
+ it 'copies before hooks' do
70
+ before_hook = proc {}
71
+ described_class.before_events(&before_hook)
72
+ sub_class = Class.new(described_class)
73
+ expect(sub_class.before_events).to include(before_hook)
74
+ end
75
+
76
+ it 'copies after hooks' do
77
+ after_hook = proc {}
78
+ described_class.after_events(&after_hook)
79
+ sub_class = Class.new(described_class)
80
+ expect(sub_class.after_events).to include(after_hook)
81
+ end
82
+
83
+ it 'lets sub classes defined their own elements' do
30
84
  custom_element = Class.new(described_class) do
31
85
  text_field :form_field, id: 'field_id'
32
86
 
@@ -43,6 +97,18 @@ module PageMagic
43
97
  end
44
98
  end
45
99
 
100
+ describe '.watch' do
101
+ let(:described_class) { Class.new(Element) }
102
+ it 'adds a before hook with the watcher in it' do
103
+ described_class.watch(:object_id)
104
+ instance = described_class.new(:element)
105
+
106
+ watcher_block = instance.before_events.last
107
+ instance.instance_exec(&watcher_block)
108
+ expect(instance.watchers.first.last).to eq(instance.object_id)
109
+ end
110
+ end
111
+
46
112
  describe 'EVENT_TYPES' do
47
113
  context 'methods created' do
48
114
  it 'creates methods for each of the event types' do
@@ -53,7 +119,7 @@ module PageMagic
53
119
  context 'method called' do
54
120
  let(:browser_element) { instance_double(Capybara::Node::Element) }
55
121
  subject do
56
- described_class.new(browser_element, page)
122
+ described_class.new(browser_element)
57
123
  end
58
124
  it 'calls the browser_element passing on all args' do
59
125
  expect(browser_element).to receive(:select).with(:args)
@@ -63,44 +129,62 @@ module PageMagic
63
129
  end
64
130
  end
65
131
 
66
- describe '#initialize' do
67
- it 'sets the parent element' do
68
- instance = described_class.new(page, :parent_page_element)
69
- expect(instance.parent_page_element).to eq(:parent_page_element)
70
- end
71
- end
72
-
73
132
  describe 'hooks' do
74
133
  subject do
75
134
  Class.new(described_class) do
76
135
  before_events do
77
- call_in_before_hook
136
+ call_in_before_events
78
137
  end
79
- end.new(double('button', click: true), page)
138
+ end.new(double('button', click: true))
80
139
  end
81
- context 'method called in before hook' do
140
+ context 'method called in before_events' do
82
141
  it 'calls methods on the page element' do
83
- expect(subject).to receive(:call_in_before_hook)
142
+ expect(subject).to receive(:call_in_before_events)
84
143
  subject.click
85
144
  end
86
145
  end
87
146
 
88
- context 'method called in after hook' do
147
+ context 'method called in after_events' do
89
148
  subject do
90
149
  Class.new(described_class) do
91
150
  after_events do
92
- call_in_after_hook
151
+ call_in_after_events
93
152
  end
94
- end.new(double('button', click: true), page)
153
+ end.new(double('button', click: true))
95
154
  end
96
155
 
97
156
  it 'calls methods on the page element' do
98
- expect(subject).to receive(:call_in_after_hook)
157
+ expect(subject).to receive(:call_in_after_events)
99
158
  subject.click
100
159
  end
101
160
  end
102
161
  end
103
162
 
163
+ describe '#initialize' do
164
+ it 'sets the parent element' do
165
+ instance = described_class.new(:element)
166
+ expect(instance.parent_element).to eq(page)
167
+ end
168
+
169
+ context 'inherited items' do
170
+ let(:described_class) do
171
+ Class.new(Element)
172
+ end
173
+
174
+ it 'copies the event hooks from the class' do
175
+ before_hook = proc {}
176
+ after_hook = proc {}
177
+ described_class.before_events(&before_hook)
178
+ described_class.after_events(&after_hook)
179
+
180
+ instance = described_class.new(:element)
181
+
182
+ expect(instance.before_events).to include(before_hook)
183
+ expect(instance.after_events).to include(after_hook)
184
+ end
185
+ end
186
+ end
187
+
104
188
  describe '#method_missing' do
105
189
  before do
106
190
  page_class.class_eval do
@@ -125,7 +209,7 @@ module PageMagic
125
209
  subject do
126
210
  Class.new(described_class) do
127
211
  element :sub_element, css: '.sub-element'
128
- end.new(double(element_method: ''), :parent_page_element)
212
+ end.new(double(element_method: ''))
129
213
  end
130
214
  it 'checks for methods on self' do
131
215
  expect(subject.respond_to?(:session)).to eq(true)
@@ -73,7 +73,7 @@ module PageMagic
73
73
  context 'using a block' do
74
74
  it 'passes the parent element in as the last argument' do
75
75
  expected_element = instance
76
- subject.element :page_section, child_selector do |_arg1, parent_element|
76
+ subject.element :page_section, child_selector do |_arg1|
77
77
  extend RSpec::Matchers
78
78
  expect(parent_element).to eq(expected_element)
79
79
  end
@@ -56,6 +56,16 @@ module PageMagic
56
56
  end
57
57
  end
58
58
 
59
+ context 'watcher with the same name added' do
60
+ it 'replaces the watcher' do
61
+ subject.watch(:object_id)
62
+ original_watcher = subject.watchers.first
63
+ subject.watch(:object_id)
64
+ expect(subject.watchers.size).to eq(1)
65
+ expect(subject.watchers.first).to_not be(original_watcher)
66
+ end
67
+ end
68
+
59
69
  context 'watcher defined on element that does not exist' do
60
70
  it 'raises an error' do
61
71
  expected_message = described_class::ELEMENT_MISSING_MSG % :missing
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.1
4
+ version: 1.0.2
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-12 00:00:00.000000000 Z
11
+ date: 2015-11-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: capybara