page_magic 1.0.0.alpha21 → 1.0.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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +4 -1
  3. data/Gemfile +1 -1
  4. data/Gemfile.lock +9 -7
  5. data/README.md +181 -78
  6. data/VERSION +1 -1
  7. data/lib/page_magic/class_methods.rb +8 -7
  8. data/lib/page_magic/driver.rb +7 -7
  9. data/lib/page_magic/drivers.rb +6 -6
  10. data/lib/page_magic/element/locators.rb +24 -0
  11. data/lib/page_magic/element/query.rb +1 -0
  12. data/lib/page_magic/element/selector.rb +11 -9
  13. data/lib/page_magic/element.rb +47 -85
  14. data/lib/page_magic/element_context.rb +25 -5
  15. data/lib/page_magic/element_definition_builder.rb +39 -0
  16. data/lib/page_magic/elements.rb +24 -27
  17. data/lib/page_magic/exceptions.rb +11 -8
  18. data/lib/page_magic/instance_methods.rb +23 -21
  19. data/lib/page_magic/session.rb +43 -44
  20. data/lib/page_magic/session_methods.rb +28 -0
  21. data/lib/page_magic/wait_methods.rb +18 -0
  22. data/lib/page_magic/watcher.rb +41 -0
  23. data/lib/page_magic/watchers.rb +43 -0
  24. data/lib/page_magic.rb +13 -9
  25. data/spec/element_spec.rb +58 -160
  26. data/spec/page_magic/class_methods_spec.rb +7 -6
  27. data/spec/page_magic/driver_spec.rb +14 -13
  28. data/spec/page_magic/drivers_spec.rb +7 -6
  29. data/spec/page_magic/element/locators_spec.rb +33 -0
  30. data/spec/page_magic/element/query_spec.rb +44 -19
  31. data/spec/page_magic/element/selector_spec.rb +45 -3
  32. data/spec/page_magic/element_context_spec.rb +41 -11
  33. data/spec/page_magic/element_definition_builder_spec.rb +76 -0
  34. data/spec/page_magic/elements_spec.rb +94 -141
  35. data/spec/page_magic/instance_methods_spec.rb +15 -10
  36. data/spec/page_magic/session_methods_spec.rb +36 -0
  37. data/spec/page_magic/session_spec.rb +78 -79
  38. data/spec/page_magic/wait_methods_spec.rb +41 -0
  39. data/spec/page_magic/watchers_spec.rb +74 -0
  40. data/spec/page_magic_spec.rb +9 -9
  41. data/spec/spec_helper.rb +1 -0
  42. data/spec/support/shared_contexts/nested_elements_html_context.rb +16 -0
  43. data/spec/support/shared_contexts/{webapp_context.rb → webapp_fixture_context.rb} +0 -0
  44. data/spec/support/shared_examples.rb +25 -0
  45. data/spec/watcher_spec.rb +49 -0
  46. metadata +23 -11
  47. data/lib/page_magic/element/method_observer.rb +0 -20
  48. data/spec/member_methods_spec.rb +0 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 115f37789b407f0f922654cec028ab74268132c4
4
- data.tar.gz: ad52bf172b7e0ebd649f738c86e418c47c57a70f
3
+ metadata.gz: 72362e511bc0f363179017fa7363ce20995b28b1
4
+ data.tar.gz: a7c4b1ba4c85213592217702bedcb0f76080da8e
5
5
  SHA512:
6
- metadata.gz: 1a3d128c9dbf70e10c7aae8ddef2c48603714b625634639b8b559af1870655a114c9f706447add67e906ed4cd087e905dc827492798b8b71166455e1cade255b
7
- data.tar.gz: 6428d9dc270bc4768556e102a68bf0014817c05e3c947e467fc7cad3868b3cf7ad0007cf34a16cab24fc9cb8d23502192509103d6ca9799efe4f8c514c352337
6
+ metadata.gz: 6c5646258cadec859bfa0923db62334d055ba0ede606d6f62c05e73b21524832a0b4da4be9be2f6dc20f5be47e2c041cf3a1224693b7549ec6db91e45fa9041d
7
+ data.tar.gz: 6a82e9164bdb65b49cb892ea83f67334ae512b91305974329fa041bf884c63d0145dfde2ac503de133d12c51a8504b85e82d3ce06d851764763bda6631a5d6b5
data/.rubocop.yml CHANGED
@@ -9,4 +9,7 @@ AllCops:
9
9
  - '.idea/**/*'
10
10
 
11
11
  Metrics/ParameterLists:
12
- CountKeywordArgs: false
12
+ CountKeywordArgs: false
13
+
14
+ Metrics/MethodLength:
15
+ Max: 11
data/Gemfile CHANGED
@@ -1,6 +1,6 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gem 'capybara', '~> 2'
3
+ gem 'capybara', '2.5.0'
4
4
  gem 'activesupport', '~> 4'
5
5
  gem 'wait', '~> 0'
6
6
 
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GEM
12
12
  astrolabe (1.3.1)
13
13
  parser (~> 2.2)
14
14
  builder (3.2.2)
15
- capybara (2.1.0)
15
+ capybara (2.5.0)
16
16
  mime-types (>= 1.16)
17
17
  nokogiri (>= 1.3.3)
18
18
  rack (>= 1.0.0)
@@ -21,7 +21,7 @@ GEM
21
21
  certifi (2015.08.10)
22
22
  childprocess (0.3.9)
23
23
  ffi (~> 1.0, >= 1.0.11)
24
- cliver (0.2.2)
24
+ cliver (0.3.2)
25
25
  codeclimate-test-reporter (0.4.8)
26
26
  simplecov (>= 0.7.1, < 1.0.0)
27
27
  diff-lcs (1.2.5)
@@ -69,9 +69,9 @@ GEM
69
69
  rack (~> 1.2)
70
70
  parser (2.2.3.0)
71
71
  ast (>= 1.1, < 3.0)
72
- poltergeist (1.4.1)
73
- capybara (~> 2.1.0)
74
- cliver (~> 0.2.1)
72
+ poltergeist (1.7.0)
73
+ capybara (~> 2.1)
74
+ cliver (~> 0.3.1)
75
75
  multi_json (~> 1.0)
76
76
  websocket-driver (>= 0.2.0)
77
77
  powerpack (0.1.1)
@@ -131,7 +131,9 @@ GEM
131
131
  watir-webdriver (0.6.4)
132
132
  selenium-webdriver (>= 2.18.0)
133
133
  websocket (1.0.7)
134
- websocket-driver (0.3.0)
134
+ websocket-driver (0.6.3)
135
+ websocket-extensions (>= 0.1.0)
136
+ websocket-extensions (0.1.2)
135
137
  xpath (2.0.0)
136
138
  nokogiri (~> 1.3)
137
139
  yard (0.8.7.6)
@@ -141,7 +143,7 @@ PLATFORMS
141
143
 
142
144
  DEPENDENCIES
143
145
  activesupport (~> 4)
144
- capybara (~> 2)
146
+ capybara (= 2.5.0)
145
147
  codeclimate-test-reporter
146
148
  github-markup (~> 1.4)
147
149
  jeweler (~> 2.0)
data/README.md CHANGED
@@ -5,51 +5,40 @@ PageMagic is an API for testing web applications.
5
5
  It has a simple but powerful DSL which makes modelling and interacting with your pages easy.
6
6
 
7
7
  Wouldn't it be great if there was a framework that could:
8
- - [Model your pages](#modelling-pages)
9
- - [Fluently define interaction hooks / waiters on page elements](#interaction-hooks)
8
+ - [Model your pages](#defining-pages)
9
+ - [Fluently define event hooks / waiters on page elements](#hooks)
10
10
  - [Map paths to pages so that the correct page object is loaded as you navigate](#page-mapping)
11
11
  - [Be super dynamic](#dynamic-selectors)
12
12
 
13
13
  Well PageMagic might just be the answer!
14
14
 
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
+ # Contents
17
+ - [Installation](#installation)
18
+ - [Starting a session](#starting-a-session)
19
+ - [Defining Pages](#defining-pages)
20
+ - [Elements](#elements)
21
+ - [Interacting with elements](#interacting-with-elements)
22
+ - [Multple Results](#multiple-results)
23
+ - [Sub elements](#sub-elements)
24
+ - [Custom elements](#custom-elements)
25
+ - [Hooks](#hooks)
26
+ - [Element event hooks](#element-event-hooks)
27
+ - [On load hook](#on-load-hook)
28
+ - [Helper Methods](#helper-methods)
29
+ - [Dynamic Selectors](#dynamic-selectors)
30
+ - [Watchers](#watchers)
31
+ - [Method watchers](#method-watchers)
32
+ - [Simple watchers](#simple-watchers)
33
+ - [Custom watchers](#custom-watchers)
34
+ - [Page mapping](#page-mapping)
35
+ - [Drivers](#drivers)
36
+ - [Pulling it all together](#pulling-it-all-together)
16
37
 
17
38
  # Installation
18
39
  `gem install page_magic --pre`
19
40
 
20
- # An Example
21
- Imagine the scene. You've written a web based mail client and now you want to test it...
22
- You have a scenario in mind that goes something along the lines of:
23
- - Send yourself an email with a unique subject
24
- - Go to the Login page and login
25
- - Find the message using it's unique subject and read it
26
- - delete the message
27
-
28
- You're mail client is total 21st century so there is loads of lovely ajax etc...
29
-
30
- Writing robust, nice looking code for this could be a real pain...
31
- Here's how you might do it with PageMagic (note that the following code would work if we you had a compatible mail web app but in this case is purely illustrative)
32
-
33
- What we really want to write is something like
34
- ```ruby
35
- test_subject = send_test_mail('test@21st-century-mail.com')
36
- #Visit your site using a PageMagic session we prepared earlier
37
- session.visit(LoginPage)
38
-
39
- #Login using some handy helper method on our page object
40
- session.login('username', 'password')
41
-
42
- #Find the message amongst all the other messages that are on screen and read it
43
- session.message(subject: test_subject).read.click
44
-
45
- #Now we are on the message screen lets delete it without having to worry about the ajax.
46
- session.delete_message
47
-
48
- fail "message is still there!" if session.message(subject: test_subject).exists?
49
-
50
- # Sweet :)
51
- ```
52
- ## Starting a session
41
+ # Starting a session
53
42
  To start a PageMagic session simply decide what browser you want to use and pass it to PageMagic's `.session` method
54
43
  ```ruby
55
44
  session = PageMagic.session(browser: :chrome, url: 'https://21st-century-mail.com')
@@ -63,8 +52,15 @@ Under the hood, PageMagic is using [Capybara](https://github.com/jnicklas/capyba
63
52
 
64
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.
65
54
 
66
- ## Modeling pages
67
- To define something that PageMagic can work with, simply include PageMagic in to a class. Your you will also want to model the elements on your page so that you can interact with them. Here are we are modelling the login page we would need for the example above
55
+ # Defining Pages
56
+ To define something that PageMagic can work with, simply include PageMagic in to a class.
57
+ ```ruby
58
+ class LoginPage
59
+ include PageMagic
60
+ end
61
+ ```
62
+ ## Elements
63
+ Defining elements is easy see the example below.
68
64
 
69
65
  ```ruby
70
66
  class LoginPage
@@ -74,10 +70,9 @@ class LoginPage
74
70
  button(:login_button, text: 'login')
75
71
  end
76
72
  ```
77
- In the case of the Login page, it's easy to imagine that it will have text fields for a username and password and a button to login in with.
78
73
 
79
- ##Interacting with elements
80
- Elements are defined with a id which is the name of the method you will use to reference it. In the above example, the textfields and button were defined with the id's, `:username`, `:password`, and `:login_button`
74
+ ### Interacting with elements
75
+ Elements are defined with an id which is the name of the method you will use to reference it. In the above example, the textfields and button were defined with the id's, `:username`, `:password`, and `:login_button`
81
76
 
82
77
  After visiting a page with a PageMagic session, you can access all of the elements of that page through the session itself.
83
78
  ```ruby
@@ -85,24 +80,67 @@ session.username.set 'joe@blogs.com'
85
80
  session.password.set 'passw0rd'
86
81
  session.login_button.click
87
82
  ```
88
-
89
- ## Defining Pages
90
- To define something that PageMagic can work with, simply include PageMagic in to a class. Here are the classes we would need for the example above.
83
+ #### Multple Results
84
+ Where an element has been scoped to return multple results, these will be returned in an array. These elements can be defined
85
+ using all of the same features as described in this readme and behave in exactly the same way.
91
86
  ```ruby
92
87
  class LoginPage
93
- include PageMagic
88
+ element :links, css: 'a'
94
89
  end
95
90
 
91
+ session.link => Array<Element>
92
+ ```
93
+
94
+ ### Sub Elements
95
+ If your pages are complex you can use PageMagic to compose pages, their elements and subelements to as many levels as you need to.
96
+
97
+ ```ruby
96
98
  class MailBox
97
99
  include PageMagic
100
+
101
+ element :message, id: 'message_id' do
102
+ link(:read, text: 'read')
103
+ end
104
+ end
105
+ ```
106
+ Sub elements can be accessed through their parent elements e.g:
107
+ ```
108
+ session.message.read.click
109
+ ```
110
+
111
+ ### Custom elements
112
+ PageMagic allows you to define your own custom elements.
113
+ ```ruby
114
+ class Nav < PageMagic::Element
115
+ selector css: '.nav
116
+
117
+ element :options, css: '.options' do
118
+ link(:link1, id: 'link1')
119
+ link(:link2, id: 'link2')
120
+ link(:link3, id: 'link3')
121
+ end
98
122
  end
99
123
 
100
- class MessageView
124
+ class MyPage
101
125
  include PageMagic
126
+ element Nav
102
127
  end
103
128
  ```
104
- ### Hooks
105
- PageMagic lets you define an on_load hook for your pages. This lets you right any custom wait logic you might need
129
+ If an id is not specified then the name of the element class will be used. The selector for the element can be specified on the class itself or overiden when defining the element on the page. The custom element can also be extended as with other elements.
130
+ ```ruby
131
+ class MyPage
132
+ include PageMagic
133
+ element Nav, :navigation, selector: '.custom' do
134
+ link(:extr_link, id: 'extra-link')
135
+ do
136
+ end
137
+ ```
138
+
139
+ ## Hooks
140
+ PageMagic provides hooks to allow you to interact at the right moments with your pages
141
+
142
+ ### On load hook
143
+ PageMagic lets you define an on_load hook for your pages. This lets you write any custom wait logic you might need
106
144
  before letting execution continue.
107
145
  ```ruby
108
146
  class LoginPage
@@ -114,10 +152,25 @@ class LoginPage
114
152
  end
115
153
  ```
116
154
 
117
- ### Helper methods
155
+ ### Element event hooks
156
+ 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
+
158
+ ```ruby
159
+ class MessagePage
160
+ include PageMagic
161
+ ## code defining other elements, such as subject and body
162
+
163
+ link(:delete id: 'delete-message') do
164
+ after_events do
165
+ wait_until{fancy_animation_has_disappeared?}
166
+ end
167
+ end
168
+ end
169
+ ```
170
+
171
+ ## Helper methods
118
172
  Using elements that are defined on a page is great, but if you are enacting a procedure through interacting with a few of them then your code could end up with some pretty repetitive code. In this case you can define helper methods instead.
119
173
 
120
- In the above [example](#an example) we used a helper called `login`.
121
174
  ```ruby
122
175
  class LoginPage
123
176
  # ... code defining elements as shown above
@@ -134,25 +187,9 @@ We can interact with helper in the same way as we did page elements.
134
187
  ```ruby
135
188
  session.login('joe', 'blogs')
136
189
  ```
137
- ##Defining sub elements
138
- If your pages are complex you can use PageMagic to compose pages, their elements and subelements to as many levels as you need to.
139
190
 
140
- In the example we accessed a read link that resided with a particular message
141
- ```ruby
142
- class MailBox
143
- include PageMagic
144
-
145
- element :message, id: 'message_id' do
146
- link(:read, text: 'read')
147
- end
148
- end
149
- ```
150
- Sub elements can be accessed through their parent elements e.g:
151
- ```
152
- session.message.read.click
153
- ```
154
191
  ## Dynamic Selectors
155
- In our scenario we actually selected a message based on a subject that was randomly generated. In this case we would not be able to hard code the selector for our message but instead would need to set the selector dynamically.
192
+ In some cases you will able to specify the selector for an element until runtime. PageMagic allows you to handle such situations with support for dynamic selectors.
156
193
 
157
194
  ```ruby
158
195
  class MailBox
@@ -168,23 +205,60 @@ Here we have defined the 'message' element using a block that takes subject argu
168
205
  ```ruby
169
206
  session.message(subject: 'test message')
170
207
  ```
171
- ## Interaction hooks
172
- 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. In the case of our web based mail client, we could imagine that when deleting the email, a fancy spinner is displayed whilst the application sends an ajax request to have the message deleted. In this case we wouldn't want to proceed until this has disappeared.
173
208
 
209
+ # Watchers
210
+ 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.
212
+
213
+ **Note**: Watchers are not inherited
214
+
215
+ ## Method watchers
216
+ Method watchers watch the output of the given method name.
174
217
  ```ruby
175
- class MessagePage
176
- include PageMagic
177
- ## code defining other elements, such as subject and body
218
+ button :javascript_button, css: '.fancy_button' do
219
+ before_events do
220
+ watch(:url)
221
+ end
178
222
 
179
- link(:delete id: 'delete-message') do
180
- after_events do
181
- wait_until{fancy_animation_has_disappeared?}
223
+ after_events do
224
+ wait_until{changed?(:url)}
225
+ end
226
+ end
227
+ ```
228
+
229
+ ## Simple watchers
230
+ Simple watchers use the `watch` method passing two parameters, the first is the name of the element you want to keep an
231
+ eye and the second is the method that needs to be called to get the value that should be observed.
232
+ ```ruby
233
+ element :product_row, css '.cta' do
234
+ before_events do
235
+ element(:total, css: '.total')
236
+ end
237
+
238
+ after_events do
239
+ wait_until{changed?(:total)}
240
+ end
241
+ end
242
+ ```
243
+ ## Custom watchers
244
+ Custom watchers are defined by passing a name and block parameter to the `watch` method. The block returns the value
245
+ that needs to be observed. Use watch in this way if you need to do something non standard to obtain a value or to
246
+ access an element not located within the current element but elsewhere within the page.
247
+ ```ruby
248
+ element :product_row, css '.cta' do
249
+ before_events do
250
+ element(:total) do
251
+ session.nav.total.text
182
252
  end
183
253
  end
254
+
255
+ after_events do
256
+ wait_until{changed?(:total)}
257
+ end
184
258
  end
185
259
  ```
186
- ## Page Mapping
187
- You will have noticed that, that we have been performing actions that would move us from page to page but have not done anything to tell PageMagic to use the `MailBox` or `MessagePage`. 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!
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!
188
262
  ```ruby
189
263
  # define what pages map to what
190
264
  browser.define_page_mappings %r{/messages/\d+} => MessagePage,
@@ -193,6 +267,7 @@ browser.define_page_mappings %r{/messages/\d+} => MessagePage,
193
267
  ```
194
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.
195
269
 
270
+ # Drivers
196
271
  ## Registering a custom driver
197
272
  You can register any Capybara compliant driver as follows
198
273
 
@@ -211,6 +286,34 @@ PageMagic.drivers.register Webkit
211
286
  #3. Use registered driver
212
287
  session = PageMagic.session(browser: webkit, url: 'https://21st-century-mail.com')
213
288
  ```
214
- ##What else can you do with PageMagic?
215
- PageMagic has lots of other useful features. I'm writing up the documentation so check back here soon!
216
289
 
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:
301
+
302
+ ```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')
309
+
310
+ #Find the message amongst all the other messages that are on screen and read it
311
+ session.message(subject: test_subject).read.click
312
+
313
+ #Now we are on the message screen lets delete it without having to worry about the ajax.
314
+ session.delete_message
315
+
316
+ fail "message is still there!" if session.message(subject: test_subject).exists?
317
+
318
+ # Sweet :)
319
+ ```
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0.alpha21
1
+ 1.0.0
@@ -1,15 +1,9 @@
1
1
  module PageMagic
2
2
  # module ClassMethods - contains class level methods for PageObjects
3
3
  module ClassMethods
4
+ # Default block to be run when a page is loaded. This is used if a specific handler is not registered
4
5
  DEFAULT_ON_LOAD = proc {}
5
6
 
6
- # getter setter for storing the page url
7
- # @param [String] url the url of the page
8
- def url(url = nil)
9
- @url = url if url
10
- @url
11
- end
12
-
13
7
  # sets block to run when page has loaded
14
8
  # if one has not been set on the page object class it will return a default block that does nothing
15
9
  def on_load(&block)
@@ -17,6 +11,13 @@ module PageMagic
17
11
  @on_load = block
18
12
  end
19
13
 
14
+ # getter setter for storing the page url
15
+ # @param [String] url the url of the page
16
+ def url(url = nil)
17
+ @url = url if url
18
+ @url
19
+ end
20
+
20
21
  # Visit this page based on the class level registered url
21
22
  # @param [Object] application rack application (optional)
22
23
  # @param [Symbol] browser name of browser
@@ -20,13 +20,6 @@ module PageMagic
20
20
  @supported_browsers = supported_browsers
21
21
  end
22
22
 
23
- # Determines if the given browser name is supported by this driver definition
24
- # @param [Symbol] browser name of browser
25
- # @return [Boolean] true if definition supports the given driver name
26
- def support?(browser)
27
- supported_browsers.include?(browser)
28
- end
29
-
30
23
  # Build a new driver instance based on this definition
31
24
  # @param [Object] app - rack compatible application
32
25
  # @param [Symbol] browser name of required browser
@@ -35,5 +28,12 @@ module PageMagic
35
28
  def build(app, browser:, options:{})
36
29
  handler.call(app, options, browser)
37
30
  end
31
+
32
+ # Determines if the given browser name is supported by this driver definition
33
+ # @param [Symbol] browser name of browser
34
+ # @return [Boolean] true if definition supports the given driver name
35
+ def support?(browser)
36
+ supported_browsers.include?(browser)
37
+ end
38
38
  end
39
39
  end
@@ -7,12 +7,6 @@ module PageMagic
7
7
  @all ||= []
8
8
  end
9
9
 
10
- # Make a driver available for selection when creating calling {PageMagic.session}
11
- # @param [Driver] driver driver definition
12
- def register(driver)
13
- all << driver
14
- end
15
-
16
10
  # Find a driver definition based on its registered name
17
11
  # @param [Symbol] browser registered name of the required browser
18
12
  def find(browser)
@@ -31,6 +25,12 @@ module PageMagic
31
25
  end
32
26
  end
33
27
 
28
+ # Make a driver available for selection when creating calling {PageMagic.session}
29
+ # @param [Driver] driver driver definition
30
+ def register(driver)
31
+ all << driver
32
+ end
33
+
34
34
  # returns true if this driver instance is equal to the supplied object
35
35
  # @param [Object] other subject of equality check
36
36
  # @return [Boolean] true if the object is a match
@@ -0,0 +1,24 @@
1
+ module PageMagic
2
+ class Element
3
+ # contains method for finding element definitions
4
+ module Locators
5
+ # message used when raising {ElementMissingException} from methods within this module
6
+ ELEMENT_NOT_DEFINED_MSG = 'Element not defined: %s'
7
+
8
+ # find an element definition based on its name
9
+ # @param [Symbol] name name of the element
10
+ # @return [Element] element definition with the given name
11
+ # @raise [ElementMissingException] raised when element with the given name is not found
12
+ def element_by_name(name, *args)
13
+ defintion = element_definitions[name]
14
+ fail ElementMissingException, (ELEMENT_NOT_DEFINED_MSG % name) unless defintion
15
+ defintion.call(*args.append(self))
16
+ end
17
+
18
+ # @return [Array] class level defined element definitions
19
+ def element_definitions
20
+ self.class.element_definitions
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,3 +1,4 @@
1
+ require 'capybara/query'
1
2
  module PageMagic
2
3
  class Element
3
4
  # class Query - models overall queries for Capybara, queries can include:
@@ -14,6 +14,15 @@ module PageMagic
14
14
  end
15
15
  end
16
16
 
17
+ attr_reader :name, :formatter, :exact, :supports_type
18
+
19
+ def initialize(selector = nil, supports_type: false, exact: false, &formatter)
20
+ @name = selector
21
+ @formatter = formatter || proc { |arg| arg }
22
+ @supports_type = supports_type
23
+ @exact = exact
24
+ end
25
+
17
26
  # Build selector query parameters for Capybara's find method
18
27
  # @param [Symbol] element_type the type of browser element being found. e.g :link
19
28
  # @param [Hash] locator the selection method and its parameter. E.g. text: 'click me'
@@ -22,20 +31,13 @@ module PageMagic
22
31
  array << element_type if supports_type
23
32
  array << name if name
24
33
  array << formatter.call(locator)
34
+ array << { exact: true } if exact
25
35
  end
26
36
  end
27
37
 
28
- attr_reader :name, :formatter, :supports_type
29
-
30
- def initialize(selector = nil, supports_type: false, &formatter)
31
- @name = selector
32
- @formatter = formatter || proc { |arg| arg }
33
- @supports_type = supports_type
34
- end
35
-
36
38
  XPATH = Selector.new(:xpath, supports_type: false)
37
39
  ID = Selector.new(:id, supports_type: false)
38
- LABEL = Selector.new(:field, supports_type: false)
40
+ LABEL = Selector.new(:field, supports_type: false, exact: true)
39
41
 
40
42
  CSS = Selector.new(supports_type: false)
41
43
  TEXT = Selector.new(supports_type: true)