Skip to content
Newer
Older
100644 1171 lines (883 sloc) 31.8 KB
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
1 # SitePrism
2 _A Page Object Model DSL for Capybara_
3
208d281 @natritmeyer deleting redundant text
authored Apr 19, 2012
4 SitePrism gives you a simple, clean and semantic DSL for describing your site using the Page Object Model pattern, for use with Capybara in automated acceptance testing.
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
5
f9e205b @natritmeyer adding link to rdoc.info
authored Apr 19, 2012
6 Find the pretty documentation here: http://rdoc.info/gems/site_prism/frames
7
b4938e1 @natritmeyer adding synopsis section for the concentrationally challenged
authored Apr 18, 2012
8 ## Synopsis
9
10 Here's an overview of how SitePrism is designed to be used:
11
12 ```ruby
13 # define our site's pages
14
15 class Home < SitePrism::Page
16 set_url "http://www.google.com"
17 set_url_matcher /google.com\/?/
18
19 element :search_field, "input[name='q']"
20 element :search_button, "button[name='btnK']"
21 elements :footer_links, "#footer a"
22 section :menu, MenuSection, "#gbx3"
23 end
24
25 class SearchResults < SitePrism::Page
26 set_url_matcher /google.com\/results\?.*/
27
28 section :menu, MenuSection, "#gbx3"
29 sections :search_results, SearchResultSection, "#results li"
30 end
31
32 # define sections used on multiple pages or multiple times on one page
33
34 class MenuSection < SitePrism::Section
35 element :search, "a.search"
36 element :images, "a.image-search"
37 element :maps, "a.map-search"
38 end
39
40 class SearchResultSection < SitePrism::Section
41 element :title, "a.title"
42 element :blurb, "span.result-decription"
43 end
44
45 # now for some tests
46
47 When /^I navigate to the google home page$/ do
48 @home = Home.new
49 @home.load
50 end
51
52 Then /^the home page should contain the menu and the search form$/ do
53 @home.wait_for_menu # menu loads after a second or 2, give it time to arrive
54 @home.should have_menu
55 @home.should have_search_field
56 @home.should have_search_button
57 end
58
59 When /^I search for Sausages$/ do
60 @home.search_field.set "Sausages"
61 @home.search_button.click
62 end
63
64 Then /^the search results page is displayed$/ do
65 @results_page = SearchResults.new
66 @results_page.should be_displayed
67 end
68
69 Then /^the search results page contains 10 individual search results$/ do
70 @results_page.wait_for_search_results
71 @results_page.should have_search_results
72 @results_page.search_results.size.should == 10
73 end
74
75 Then /^the search results contain a link to the wikipedia sausages page$/ do
76 @results_page.search_results.map {|sr| sr.title['href']}.should include "http://en.wikipedia.org/wiki/Sausage"
77 end
78 ```
79
80 Now for the details...
81
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
82 ## Setup
83
c37cc36 @natritmeyer adding installation section
authored Apr 18, 2012
84 ### Installation
85
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
86 To install SitePrism:
87
88 ```bash
89 gem install site_prism
90 ```
91
a12ef33 @natritmeyer adding subheadings to the setup section
authored Apr 17, 2012
92 ### Using SitePrism with Cucumber
93
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
94 If you are using cucumber, here's what needs requiring:
95
96 ```ruby
97 require 'capybara'
98 require 'capybara/dsl'
99 require 'capybara/cucumber'
100 require 'selenium-webdriver'
101 require 'site_prism'
102 ```
103
a12ef33 @natritmeyer adding subheadings to the setup section
authored Apr 17, 2012
104 ### Using SitePrism with RSpec
105
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
106 If you're using rspec instead, here's what needs requiring:
107
108 ```ruby
109 require 'capybara'
110 require 'capybara/dsl'
111 require 'capybara/rspec'
112 require 'selenium-webdriver'
113 require 'site_prism'
114 ```
115
46eacb9 @natritmeyer adding blurb about the page object model pattern
authored Apr 18, 2012
116 ## Introduction to the Page Object Model
117
118 The Page Object Model is a test automation pattern that aims to create
119 an abstraction of your site's user interface that can be used in tests.
120 The most common way to do this is to model each page as a class, and
121 to then use instances of those classes in your tests.
122
123 If a class represents a page then each element of the page is
124 represented by a method that, when called, returns a reference to that
125 element that can then be acted upon (clicked, set text value), or
126 queried (is it enabled? visible?).
127
128 SitePrism is based around this concept, but goes further as you'll see
e78ce04 @natritmeyer adding set_url and load details
authored Apr 18, 2012
129 below by also allowing modelling of repeated sections that appear on
130 muliple pages, or many times on a page using the concept of sections.
46eacb9 @natritmeyer adding blurb about the page object model pattern
authored Apr 17, 2012
131
e78ce04 @natritmeyer adding set_url and load details
authored Apr 17, 2012
132 ## Pages
46eacb9 @natritmeyer adding blurb about the page object model pattern
authored Apr 17, 2012
133
e78ce04 @natritmeyer adding set_url and load details
authored Apr 17, 2012
134 As you might be able to guess from the name, pages are fairly central to
135 the Page Object Model. Here's how SitePrism models them:
136
137 ### Creating a Page Model
138
139 The simplest page is one that has nothing defined in it. Here's an
140 example of how to begin modelling a home page:
141
142 ```ruby
143 class Home < SitePrism::Page
144 end
145 ```
146
147 The above has nothing useful defined, only the name.
148
149 ### Adding a URL
150
151 A page usually has a URL. If you want to be able to navigate to a page,
152 you'll need to set its URL. Here's how:
153
154 ```ruby
155 class Home < SitePrism::Page
156 set_url "http://www.google.com"
157 end
158 ```
159
2082298 @natritmeyer adding examples of when you would want and not want to set a page url
authored Apr 18, 2012
160 Note that setting a URL is optional - you only need to set a url if you want to be able to navigate
161 directly to that page. It makes sense to set the URL for a page model of a home
863d0f1 @natritmeyer minor edit
authored Apr 19, 2012
162 page or a login page, but probably not a search results page.
2082298 @natritmeyer adding examples of when you would want and not want to set a page url
authored Apr 17, 2012
163
e78ce04 @natritmeyer adding set_url and load details
authored Apr 17, 2012
164 ### Navigating to the Page
165
166 Once the URL has been set (using `set_url`), you can navigate directly
167 to the page using `#load`:
168
169 ```ruby
a1d01a9 @natritmeyer converting local vars to instance vars in example code
authored Apr 18, 2012
170 @home_page = Home.new
171 @home_page.load
e78ce04 @natritmeyer adding set_url and load details
authored Apr 17, 2012
172 ```
173
174 This will tell which ever capybara driver you have configured to
175 navigate to the URL set against that page's class.
46c63d0 @natritmeyer first bit of the readme
authored Apr 17, 2012
176
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 18, 2012
177 ### Verifying that a particular page is displayed
178
179 Automated tests often need to verify that a particular page is
180 displayed. Intuitively you'd think that simply checking that the URL
364f9a1 @natritmeyer minor edit
authored Apr 19, 2012
181 defined using `set_url` is the current page in the browser would be enough, but experience shows that it's
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
182 not. It is far more robust to check to see if the browser's current url
ce3d506 @natritmeyer grammar fix
authored Apr 18, 2012
183 matches a regular expression. For example, though `account/1` and `account/2`
364f9a1 @natritmeyer minor edit
authored Apr 19, 2012
184 are the same page, their URLs are different. To deal with this,
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
185 SitePrism provides the ability to set a URL matcher.
186
187 ```ruby
188 class Account < SitePrism::Page
189 set_url_matcher /\account\/\d+/
190 end
191 ```
192
193 Once a URL matcher is set for a page, you can test to see if it is
194 displayed:
195
196 ```ruby
a1d01a9 @natritmeyer converting local vars to instance vars in example code
authored Apr 17, 2012
197 @account_page = Account.new
88a681d @natritmeyer making example clearer
authored Apr 18, 2012
198 #...
a1d01a9 @natritmeyer converting local vars to instance vars in example code
authored Apr 17, 2012
199 @account_page.displayed? #=> true or false
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
200 ```
201
202 Calling `#displayed?` will return true if the browser's current URL
364f9a1 @natritmeyer minor edit
authored Apr 19, 2012
203 matches the regular expression for the page and false if it doesn't. So
204 in the above example (`account/1` and `account/2`), calling
205 `@account_page.displayed?` will return true for both examples.
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
206
207 #### Testing for Page display
208
209 SitePrism's `#displayed?` predicate method allows for semantic code in
210 your test:
211
212 ```ruby
213 Then /^the account page is displayed$/ do
214 @account_page.should be_displayed
8164b5a @natritmeyer adding better example back in
authored Apr 18, 2012
215 @some_other_page.should_not be_displayed
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
216 end
217 ```
218
47f16a5 @natritmeyer adding another set_url_matcher example
authored Apr 18, 2012
219 Another example that demonstrates why using regex instead of string
220 comparison for URL checking is when you want to be able to run your
221 tests across multiple environments.
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
222
47f16a5 @natritmeyer adding another set_url_matcher example
authored Apr 18, 2012
223 ```ruby
224 class Login < SitePrism::Page
225 set_url "#{$test_environment}.example.com/login" #=> global var used for demonstration purposes only!!!
226 set_url_matcher /(?:dev|test|www)\.example\.com\/login/
227 end
228 ```
229
230 The above example would work for `dev.example.com/login`,
231 `test.example.com/login` and `www.example.com/login`; now your tests
232 aren't limited to one environment but can verify that they are on the
233 correct page regardless of the environment the tests are being executed
234 against.
c7ceab4 @natritmeyer adding set_url_matcher details
authored Apr 17, 2012
235
a1b8d01 @natritmeyer fixing formatting
authored Jun 14, 2012
236 ### Getting the Current Page's URL
15cd0f7 @natritmeyer adding details on how to get the current url to the readme
authored Jun 14, 2012
237
238 SitePrism allows you to get the current page's URL. Here's how it's
239 done:
240
241 ```ruby
242 class Account < SitePrism::Page
243 end
244
245 @account = Account.new
246 #...
247 @account.current_url #=> "http://www.example.com/account/123"
248 @account.current_url.should include "example.com/account/"
249 ```
250
a1b8d01 @natritmeyer fixing formatting
authored Jun 14, 2012
251 ### Page Title
5bcce47 @natritmeyer adding page title section
authored Apr 18, 2012
252
253 Getting a page's title isn't hard:
254
255 ```ruby
256 class Account < SitePrism::Page
257 end
258
259 @account = Account.new
260 #...
261 @account.title #=> "Welcome to Your Account"
262 ```
3d06fc0 @natritmeyer adding wip notice to readme
authored Apr 18, 2012
263
a1b8d01 @natritmeyer fixing formatting
authored Jun 14, 2012
264 ### HTTP vs. HTTPS
4fd9858 @natritmeyer adding #secure? details to the readme
authored Jun 14, 2012
265
266 You can easily tell if the page is secure or not by checking to see if
267 the current URL begins with 'https' or not. SitePrism provides the
268 `secure?` method that will return true if the current url begins with
269 'https' and false if it doesn't. For example:
270
271 ```ruby
272 class Account < SitePrism::Page
273 end
274
275 @account = Account.new
276 #...
277 @account.secure? #=> true/false
278 @account.should be_secure
279 ```
280
4fe521d @natritmeyer adding initial section about elements
authored Apr 18, 2012
281 ## Elements
282
283 Pages are made up of elements (text fields, buttons, combo boxes, etc),
284 either individual elements or groups of them. Examples of individual
285 elements would be a search field or a company logo image; examples of
286 element collections would be items in any sort of list, eg: menu items,
287 images in a carousel, etc.
288
289 ### Individual Elements
290
291 To interact with individual elements, they need to be defined as part of
292 the relevant page. SitePrism makes this easy:
293
294 ```ruby
295 class Home < SitePrism::Page
296 element :search_field, "input[name='q']"
297 end
fea6f84 @natritmeyer fixing typo
authored Apr 18, 2012
298 ```
4fe521d @natritmeyer adding initial section about elements
authored Apr 18, 2012
299
300 Here we're adding a search field to the Home page. The `element` method
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
301 takes 2 arguments: the name of the element as a symbol, and a css selector
4fe521d @natritmeyer adding initial section about elements
authored Apr 18, 2012
302 as a string.
303
27ca028 @natritmeyer adding has_element? section
authored Apr 18, 2012
304 #### Accessing the individual element
305
306 The `element` method will add a number of methods to instances of the
307 particular Page class. The first method to be added is the name of the
308 element. So using the following example:
309
310 ```ruby
311 class Home < SitePrism::Page
312 set_url "http://www.google.com"
313
314 element :search_field, "input[name='q']"
315 end
316 ```
317
318 ... the following shows how to get hold of the search field:
319
320 ```ruby
a4710af @natritmeyer fixing variable name used in example
authored Jul 6, 2012
321 @home = Home.new
27ca028 @natritmeyer adding has_element? section
authored Apr 18, 2012
322 @home.load
323
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
324 @home.search_field #=> will return the capybara element found using the selector
27ca028 @natritmeyer adding has_element? section
authored Apr 18, 2012
325 @home.search_field.set "the search string" #=> since search_field returns a capybara element, you can use the capybara API to deal with it
3c751db @natritmeyer minor edit
authored Apr 19, 2012
326 @home.search_field.text #=> standard method on a capybara element; returns a string
27ca028 @natritmeyer adding has_element? section
authored Apr 18, 2012
327 ```
328
329 #### Testing for the existence of the element
330
331 Another method added to the Page class by the `element` method is the
332 `has_<element name>?` method. Using the same example as above:
333
334 ```ruby
335 class Home < SitePrism::Page
336 set_url "http://www.google.com"
337
338 element :search_field, "input[name='q']"
339 end
340 ```
341
342 ... you can test for the existence of the element on the page like this:
343
344 ```ruby
7fe4199 @natritmeyer fixing a couple more instances of incorrect var names in example code
authored Jul 6, 2012
345 @home = Home.new
27ca028 @natritmeyer adding has_element? section
authored Apr 18, 2012
346 @home.load
347 @home.has_search_field? #=> returns true if it exists, false if it doesn't
348 ```
349
350 ...which makes for nice test code:
351
352 ```ruby
353 Then /^the search field exists$/ do
354 @home.should have_search_field
355 end
356 ```
357
21892f9 @natritmeyer Adding visible/invisible test to readme for elements
authored Jul 11, 2012
358 #### Waiting for an element to exist on a page
715444f @natritmeyer finishing section about elements
authored Apr 18, 2012
359
777d87d @natritmeyer adding details of visibility waiters to the readme
authored Jul 10, 2012
360 Another method added by calling `element` is the `wait_for_<element_name>` method.
715444f @natritmeyer finishing section about elements
authored Apr 18, 2012
361 Calling the method will cause the test to wait for the Capybara's
362 default wait time for the element to exist. It is also possible to use a
363 custom amount of time to wait. Using the same example as above:
364
365 ```ruby
366 class Home < SitePrism::Page
367 set_url "http://www.google.com"
368
369 element :search_field, "input[name='q']"
370 end
371 ```
372
373 ... you can wait for the search field to exist like this:
374
375 ```ruby
7fe4199 @natritmeyer fixing a couple more instances of incorrect var names in example code
authored Jul 6, 2012
376 @home = Home.new
715444f @natritmeyer finishing section about elements
authored Apr 18, 2012
377 @home.load
378 @home.wait_for_search_field
379 # or...
380 @home.wait_for_search_field(10) #will wait for 10 seconds for the search field to appear
381 ```
382
777d87d @natritmeyer adding details of visibility waiters to the readme
authored Jul 10, 2012
383 #### Waiting for an element to become visible
384
385 Another method added by calling `element` is the
386 `wait_until_<element_name>_visible` method. Calling this method will
387 cause the test to wait for Capybara's default wait time for the element
388 to become visible (*not* the same as existence!). You can customise the
389 wait time be supplying a number of seconds to wait. Using the above
390 example:
391
392 ```ruby
393 @home.wait_until_search_field_visible
394 # or...
395 @home.wait_until_search_field_visible(10)
396 ```
397
398 #### Waiting for an element to become invisible
399
400 Another method added by calling `element` is the
401 `wait_until_<element_name>_invisible` method. Calling this method will
402 cause the test to wait for Capybara's default wait time for the element
403 to become invisible. You can customise the wait time be supplying a number
404 of seconds to wait. Using the above example:
405
406 ```ruby
407 @home.wait_until_search_field_invisible
408 # or...
409 @home.wait_until_search_field_invisible(10)
410 ```
411
715444f @natritmeyer finishing section about elements
authored Apr 18, 2012
412 #### Summary of what the element method provides:
413
414 Given:
415
416 ```ruby
417 class Home < SitePrism::Page
418 element :search_field, "input[name='q']"
419 end
420 ```
421
62a9c21 @natritmeyer stylistic change
authored Apr 19, 2012
422 ...then the following methods are available:
715444f @natritmeyer finishing section about elements
authored Apr 18, 2012
423
424 ```ruby
425 @home.search_field
426 @home.has_search_field?
427 @home.wait_for_search_field
428 @home.wait_for_search_field(10)
777d87d @natritmeyer adding details of visibility waiters to the readme
authored Jul 10, 2012
429 @home.wait_until_search_field_visible
430 @home.wait_until_search_field_visible(10)
431 @home.wait_until_search_field_invisible
432 @home.wait_until_search_field_invisible(10)
433
715444f @natritmeyer finishing section about elements
authored Apr 18, 2012
434 ```
435
4fe521d @natritmeyer adding initial section about elements
authored Apr 18, 2012
436 ### Element Collections
437
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
438 Sometimes you don't want to deal with an individual element but rather
439 with a collection of similar elements, for example, a list of names. To
440 enable this, SitePrism provides the `elements` method on the Page class.
441 Here's how it works:
4fe521d @natritmeyer adding initial section about elements
authored Apr 18, 2012
442
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
443 ```ruby
444 class Friends < SitePrism::Page
445 elements :names, "ul#names li a"
446 end
447 ```
448
449 Just like the `element` method, the `elements` method takes 2 arguments:
450 the first being the name of the elements as a symbol, the second is the
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
451 css selector that would return the array of capybara elements.
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
452
453 #### Accessing the elements
454
455 Just like the `element` method, the `elements` method adds a few methods
456 to the Page class. The first one is of the name of the element
457 collection which returns an array of capybara elements that match the
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
458 css selector. Using the example above:
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
459
460 ```ruby
461 class Friends < SitePrism::Page
462 elements :names, "ul#names li a"
463 end
464 ```
465
466 You can access the element collection like this:
467
468 ```ruby
469 @friends_page = Friends.new
470 # ...
471 @friends_page.names #=> [<Capybara::Element>, <Capybara::Element>, <Capybara::Element>]
472 ```
473
474 With that you can do all the normal things that are possible with
475 arrays:
476
477
478 ```ruby
479 @friends_page.names.each {|name| puts name.text}
480 @friends_page.names.map {|name| name.text}.should == ["Alice", "Bob", "Fred"]
481 @friends_page.names.size.should == 3
efdaa24 @natritmeyer adding have(3).names code example
authored Jul 6, 2012
482 @friends_page.should have(3).names
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
483 ```
484
485 #### Testing for the existence of the element collection
486
487 Just like the `element` method, the `elements` method adds a method to
488 the page that will allow you to check for the existence of the
489 collection, called `has_<element collection name>?`. As long as there is
490 at least 1 element in the array, the method will return true, otherwise
491 false. For example, with the following page:
492
493 ```ruby
494 class Friends < SitePrism::Page
495 elements :names, "ul#names li a"
496 end
497 ```
498
499 ... the following method is available:
500
501 ```ruby
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
502 @friends_page.has_names? #=> returns true if at least one element is found using the relevant selector
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
503 ```
504
505 ...which allows for pretty test code:
506
507 ```ruby
508 Then /^there should be some names listed on the page$/ do
509 @friends_page.should have_names
510 end
511 ```
512
513 #### Waiting for the element collection
514
515 Just like for an individual element, the tests can be told to wait for
516 the existence of the element collection. The `elements` method adds a
517 `wait_for_<element collection name>` method that will wait for
518 Capybara's default wait time until at least 1 element is found that
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
519 matches the selector. For example, with the following page:
f7659b3 @natritmeyer adding elements section
authored Apr 18, 2012
520
521 ```ruby
522 class Friends < SitePrism::Page
523 elements :names, "ul#names li a"
524 end
525
526 ```
527
528 ... you can wait for the existence of a list of names like this:
529
530 ```ruby
531 @friends_page.wait_for_names
532 ```
533
534 Again, you can customise the wait time by supplying a number of seconds
535 to wait for:
536
537 ```ruby
538 @friends_page.wait_for_names(10)
539 ```
4fe521d @natritmeyer adding initial section about elements
authored Apr 18, 2012
540
21892f9 @natritmeyer Adding visible/invisible test to readme for elements
authored Jul 11, 2012
541 #### Waiting for the elements to be visible or invisible
542
543 Like the individual elements, calling the `elements` method will create
544 two methods: `wait_until_<elements_name>_visible` and
545 `wait_until_<elements_name>_invisible`. Calling these methods will cause
546 your test to wait for the elements to become visible or invisibe. Using
547 the above example:
548
549 ```ruby
550 @friends_page.wait_until_names_visible
551 # and...
552 @friends_page.wait_until_names_invisible
553 ```
554
555 It is possible to wait for a specific amount of time instead of using
556 the default Capybara wait time:
557
558 ```ruby
559 @friends_page.wait_until_names_visible(5)
560 # and...
561 @friends_page.wait_until_names_invisible(7)
562 ```
563
1fe06e0 @natritmeyer adding blurb about the all_there? method
authored Apr 19, 2012
564 ### Checking that all mapped elements are present on the page
565
ed31647 @natritmeyer spelling mistake...
authored Apr 19, 2012
566 Throughout my time in test automation I keep getting asked to provide the
1fe06e0 @natritmeyer adding blurb about the all_there? method
authored Apr 19, 2012
567 ability to check that all elements that should be on the page are on the
568 page. Why people would want to test this, I don't know. But if that's
569 what you want to do, SitePrism provides the `#all_there?` method that
570 will return true if all mapped elements (and sections... see below) are
571 present in the browser, false if they're not all there.
572
573 ```ruby
574 @friends_page.all_there? #=> true/false
575
576 # and...
577
578 Then /^the friends page contains all the expected elements$/ do
579 @friends_page.should be_all_there
580 end
581
582 ```
583
4d632d8 @natritmeyer adding sections title
authored Apr 18, 2012
584 ## Sections
585
82b0be3 @natritmeyer adding initial blurb about sections
authored Apr 18, 2012
586 SitePrism allows you to model sections of a page that appear on multiple
3eddfba @natritmeyer typo
authored Apr 19, 2012
587 pages or that appear a number of times on a page separately from Pages.
82b0be3 @natritmeyer adding initial blurb about sections
authored Apr 18, 2012
588 SitePrism provides the Section class for this task.
b4938e1 @natritmeyer adding synopsis section for the concentrationally challenged
authored Apr 18, 2012
589
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
590 ### Individual Sections
591
18c4c7f @natritmeyer adding introductory blurb about individual sections
authored Apr 19, 2012
592 In the same way that SitePrism provides `element` and `elements`, it
593 provides `section` and `sections`. The first returns an instance of a
594 page section, the secont returns an array of section instances, one for
595 each capybara element found by the supplied css selector. What follows
596 is an explanation of `section`.
597
598
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
599 #### Defining a Section
82b0be3 @natritmeyer adding initial blurb about sections
authored Apr 18, 2012
600
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
601 A section is similar to a page in that it inherits from a SitePrism
602 class:
603
604 ```ruby
605 class MenuSection < SitePrism::Section
606 end
607 ```
608
609 At the moment, this section does nothing.
610
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
611 #### Adding a section to a page
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
612
613 Pages include sections that's how SitePrism works. Here's a page that
614 includes the above `MenuSection` section:
615
616 ```ruby
617 class Home < SitePrism::Page
618 section :menu, MenuSection, "#gbx3"
619 end
620 ```
621
1251277 @natritmeyer clarifying some text
authored Apr 19, 2012
622 The way to add a section to a page (or another section -
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
623 SitePrism allows adding sections to sections) is to call the `section`
624 method. It takes 3 arguments: the first is the name of the section as
625 referred to on the page (sections that appear on multiple pages can be
626 named differently). The second argument is the class of which an
627 instance will be created to represent the page section, and the third
628 argument is a css selector that identifies the root node of the section
629 on this page (note that the css selector can be different for different
630 pages as the whole point of sections is that they can appear in
631 different places on different pages).
632
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
633 #### Accessing a page's section
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
634
635 The `section` method (like the `element` method) adds a few methods to
636 the page or section class it was called against. The first method that
637 is added is one that returns an instance of the section, the method name
638 being the first argument to the `section` method. Here's an example:
639
640 ```ruby
641 # the section:
642
643 class MenuSection < SitePrism::Section
644 end
645
646 # the page that includes the section:
647
648 class Home < SitePrism::Page
649 section :menu, MenuSection, "#gbx3"
650 end
651
652 # the page and section in action:
653
654 @home = Home.new
655 @home.menu #=> <MenuSection...>
656 ```
657
658 When the `menu` method is called against `@home`, an instance of
659 `MenuSection` (the second argument to the `section` method) is returned.
660 The third argument that is passed to the `section` method is the css
661 selector that will be used to find the root element of the section; this
662 root node becomes the 'scope' of the section.
663
664 The following shows that though the same section can appear on multiple
665 pages, it can take a different root node:
82b0be3 @natritmeyer adding initial blurb about sections
authored Apr 18, 2012
666
667 ```ruby
668 # define the section that appears on both pages
669
670 class MenuSection < SitePrism::Section
671 end
672
673 # define 2 pages, each containing the same section
674
675 class Home < SitePrism::Page
676 section :menu, MenuSection, "#gbx3"
677 end
678
679 class SearchResults < SitePrism::Page
680 section :menu, MenuSection, "#gbx48"
681 end
682 ```
683
684 You can see that the `MenuSection` is used in both the `Home` and
685 `SearchResults` pages, but each has slightly different root node. The
71cde9e @natritmeyer changing locator for selector
authored Apr 19, 2012
686 capybara element that is found by the css selector becomes the root node
82b0be3 @natritmeyer adding initial blurb about sections
authored Apr 18, 2012
687 for the relevant page's instance of the `MenuSection` section.
b4938e1 @natritmeyer adding synopsis section for the concentrationally challenged
authored Apr 18, 2012
688
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
689 #### Adding elements to a section
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
690
691 This works just the same as adding elements to a page:
692
693 ```ruby
694 class MenuSection < SitePrism::Section
695 element :search, "a.search"
696 element :images, "a.image-search"
697 element :maps, "a.map-search"
698 end
699 ```
700
303dce0 @natritmeyer adding some clarification around the scope of elements in sections
authored Apr 19, 2012
701 Note that the css selectors used to find elements are searched for
702 within the scope of the root element of that section. The search for the
703 element won't be page-wide but it will only look in the section.
704
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
705 When the section is added to a page...
706
707 ```ruby
708 class Home < SitePrism::Page
709 section :menu, MenuSection, "#gbx3"
710 end
711 ```
712
713 ...then the section's elements can be accessed like this:
714
715 ```ruby
716 @home = Home.new
717 @home.load
718
719 @home.menu.search #=> returns a capybara element representing the link to the search page
720 @home.menu.search.click #=> clicks the search link in the home page menu
721 @home.menu.search['href'] #=> returns the value for the href attribute of the capybara element representing the search link
722 @home.menu.has_images? #=> returns true or false based on whether the link is present in the section on the page
723 @home.menu.wait_for_images #=> waits for capybara's default wait time until the element appears in the page section
724
725 ```
726
727 ...which leads to some pretty test code:
728
729 ```ruby
730 Then /^the home page menu contains a link to the various search functions$/ do
731 @home.menu.should have_search
732 @home.menu.search['href'].should include "google.com"
733 @home.menu.should have_images
734 @home.menu.should have_maps
735 end
736 ```
737
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
738 #### Testing for the existence of a section
dd0da5f @natritmeyer adding blurb about testing for existence of section
authored Apr 19, 2012
739
740 Just like elements, it is possible to test for the existence of a
741 section. The `section` method adds a method called `has_<section name>?`
742 to the page or section it's been added to - same idea as what the
743 `has_<element name>?` method. Given the following setup:
744
745 ```ruby
746 class MenuSection < SitePrism::Section
747 element :search, "a.search"
748 element :images, "a.image-search"
749 element :maps, "a.map-search"
750 end
751
752 class Home < SitePrism::Page
753 section :menu, MenuSection, "#gbx3"
754 end
755 ```
756
757 ... you can check whether the section is present on the page or not:
758
759 ```ruby
760 @home = Home.new
761 #...
762 #home.has_menu? #=> returns true or false
763 ```
764
765 Again, this allows pretty test code:
766
767 ```ruby
768 @home.should have_menu
769 @home.should_not have_menu
770 ```
771
68a41aa @natritmeyer changing appear to exist in readme
authored Jul 11, 2012
772 #### Waiting for a section to exist
b16e831 @natritmeyer adding blurb around waiting for a section
authored Apr 19, 2012
773
b2fdca8 @natritmeyer adding visibility/invisibility text to readme for sections
authored Jul 11, 2012
774 Another method added to the page or section by the `section` method is
b16e831 @natritmeyer adding blurb around waiting for a section
authored Apr 19, 2012
775 `wait_for_<section name>`. Similar to what `element` does, this method
776 waits for the section to appear - the test will wait up to capybara's
777 default wait time until the root node of the element exists on the
778 page/section that our section was added to. Given the following setup:
779
780 ```ruby
781 class MenuSection < SitePrism::Section
782 element :search, "a.search"
783 element :images, "a.image-search"
784 element :maps, "a.map-search"
785 end
786
787 class Home < SitePrism::Page
788 section :menu, MenuSection, "#gbx3"
789 end
790 ```
791
792 ... we can wait for the menu section to appear on the page like this:
793
794 ```ruby
795 @home.wait_for_menu
796 @home.wait_for_menu(10) # waits for 10 seconds instead of capybara's default timeout
797 ```
798
b2fdca8 @natritmeyer adding visibility/invisibility text to readme for sections
authored Jul 11, 2012
799 #### Waiting for a section to become visible or invisible
800
801 Like an element, it is possible to wait for a section to become visible
802 or invisible. Calling the `section` method creates two methods on the
803 relevant page or section:
804 `wait_until_<section_name>_visible` and
805 `wait_until_<section_name>_invisible`. Using the above example, here's
806 how they're used:
807
808 ```ruby
809 @home = Home.new
810 @home.wait_for_menu_visible
811 # and...
812 @home.wait_for_menu_invisible
813 ```
814
815 Again, as for an element, it is possible to give a specific amount of
816 time to wait for visibility/invisibility of a section. Here's how:
817
818 ```ruby
819 @home = Home.new
820 @home.wait_for_menu_visible(5)
821 # and...
822 @home.wait_for_menu_invisible(3)
823 ```
824
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
825 #### Sections within sections
826
827 You are not limited to adding sections only to pages; you can nest
828 sections within sections within sections within sections!
829
830 ```ruby
831
832 # define a page that contains an area that contains a section for both logging in and registration, then modelling each of the sub sections seperately
833
834 class Login < SitePrism::Section
835 element :username, "#username"
836 element :password, "#password"
837 element :sign_in, "button"
838 end
839
840 class Registration < SitePrism::Section
841 element :first_name, "#first_name"
842 element :last_name, "#last_name"
843 element :next_step, "button.next-reg-step"
844 end
845
846 class LoginRegistrationForm < SitePrism::Section
847 section :login, Login, "div.login-area"
848 section :registration, Registration, "div.reg-area"
849 end
850
851 class Home < SitePrism::Page
852 section :login_and_registration, LoginRegistrationForm, "div.login-registration"
853 end
b16e831 @natritmeyer adding blurb around waiting for a section
authored Apr 19, 2012
854
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
855 # how to login (fatuous, but demonstrates the point):
b16e831 @natritmeyer adding blurb around waiting for a section
authored Apr 19, 2012
856
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
857 Then /^I sign in$/ do
858 @home = Home.new
859 @home.load
860 @home.wait_for_login_and_registration
861 @home.should have_login_and_registration
862 @home.login_and_registration.should have_username
863 @home.login_and_registration.login.username.set "bob"
864 @home.login_and_registration.login.password.set "p4ssw0rd"
865 @home.login_and_registration.login.sign_in.click
866 end
b16e831 @natritmeyer adding blurb around waiting for a section
authored Apr 19, 2012
867
c7ca816 @natritmeyer adding blurb around having sections within sections - mainly code exa…
authored Apr 19, 2012
868 # how to sign up:
869
870 When /^I enter my name into the home page's registration form$/ do
871 @home = Home.new
872 @home.load
873 @home.login_and_registration.should have_first_name
874 @home.login_and_registration.should have_last_name
875 @home.login_and_registration.first_name.set "Bob"
876 # ...
877 end
878 ```
b16e831 @natritmeyer adding blurb around waiting for a section
authored Apr 19, 2012
879
5268668 @natritmeyer adding epic sentence explaining the difference between section and se…
authored Apr 19, 2012
880 ### Section Collections
881
882 An individual section represents a discrete section of a page, but often
883 sections are repeated on a page, an example is a search result listing -
884 each listing contains a title, a url and a description of the content.
885 It makes sense to model this only once and then to be able to access
886 each instance of a search result on a page as an array of SitePrism
887 sections. To achieve this, SitePrism provides the `sections` method that
888 can be called in a page or a section.
889
890 The only difference between `section` and `sections` is that whereas the
891 first returns an instance of the supplied section class, the second
b215ffb @natritmeyer fixing text
authored Apr 19, 2012
892 returns an array containing as many instances of the section class as
57783ef @natritmeyer reducing epicness of sentence :(
authored Apr 19, 2012
893 there are capybara elements found by the supplied css selector. This is
894 better explained in code :)
5268668 @natritmeyer adding epic sentence explaining the difference between section and se…
authored Apr 19, 2012
895
5aa7d42 @natritmeyer adding blurb about sections
authored Apr 19, 2012
896 #### Adding a Section collection to a page (or other section)
897
898 Given the following setup:
899
900 ```ruby
901 class SearchResultSection < SitePrism::Section
902 element :title, "a.title"
903 element :blurb, "span.result-decription"
904 end
905
906 class SearchResults < SitePrism::Page
907 sections :search_results, SearchResultSection, "#results li"
908 end
909 ```
910
911 ... it is possible to access each of the search results:
912
913 ```ruby
914 @results_page = SearchResults.new
915 # ...
916 @results_page.search_results.each do |search_result|
917 puts search_result.title.text
918 end
919 ```
920
921 ... which allows for pretty tests:
922
923 ```ruby
924 Then /^there are lots of search_results$/ do
925 @results_page.search_results.size.should == 10
926 @results_page.search_results.each do |search_result|
927 search_result.should have_title
928 search_result.blurb.text.should_not be_nil
929 end
930 end
931 ```
932
933 The css selector that is passed as the 3rd argument to the
934 `sections` method ("#results li") is used to find a number of capybara
935 elements. Each capybara element found using the css selector is used to
936 create a new instance of the `SearchResultSection` and becomes its root
343bc77 @natritmeyer clarifying...
authored Apr 19, 2012
937 element. So if the css selector finds 3 `li` elements, calling
938 `search_results` will return an array containing 3 instances of
939 `SearchResultSection`, each with one of the `li` elements as it's root
5aa7d42 @natritmeyer adding blurb about sections
authored Apr 19, 2012
940 element.
941
942 #### Testing for existence of Sections
943
944 Using the example above, it is possible to test for the existence of the
945 sections. As long as there is at least one section in the array, the
946 sections exist. The `sections` method adds a `has_<sections name>?`
947 method to the page/section that our section has been added to. Given the
948 following example:
949
950 ```ruby
951 class SearchResultSection < SitePrism::Section
952 element :title, "a.title"
953 element :blurb, "span.result-decription"
954 end
955
956 class SearchResults < SitePrism::Page
957 sections :search_results, SearchResultSection, "#results li"
958 end
959 ```
960
961 ... here's how to test for the existence of the section:
962
963 ```ruby
964 @results_page = SearchResults.new
965 # ...
966 @results_page.has_search_results?
967 ```
968
969 ...which allows pretty tests:
970
971 ```ruby
972 Then /^there are search results on the page$/ do
973 @results.page.should have_search_results
974 end
975 ```
976
977 #### Waiting for sections to appear
978
979 The final method added by `sections` to the page/section we're adding
980 our sections to is `wait_for_<sections name>`. It will wait for
981 capybara's default wait time for there to be at least one instance of
982 the section in the array of sections. For example:
983
984 ```ruby
985 class SearchResultSection < SitePrism::Section
986 element :title, "a.title"
987 element :blurb, "span.result-decription"
988 end
989
990 class SearchResults < SitePrism::Page
991 sections :search_results, SearchResultSection, "#results li"
992 end
993 ```
994
995 ... here's how to wait for the section:
996
997 ```ruby
998 @results_page = SearchResults.new
999 # ...
1000 @results_page.wait_for_search_results
1001 @results_page.wait_for_search_results(10) #=> waits for 10 seconds instead of the default capybara timeout
1002 ```
1003
cbda501 @natritmeyer adding iframe related documentation
authored Jul 2, 2012
1004 ## IFrames
1005
1006 SitePrism allows you to interact with iframes. An iframe is declared as
1007 a `SitePrism::Page` class, and then referenced by the page or section it
1008 is embeded into. Like a section, it is possible to test for the
1009 existence of the iframe, wait for it to exist as well as interact with
1010 the page it contains.
1011
1012 ### Creating an iframe
1013
1014 An iframe is declared in the same way as a Page:
1015
1016 ```ruby
1017 class MyIframe < SitePrism::Page
1018 element :some_text_field, "input.username"
1019 end
1020 ```
1021
1022 To expose the iframe, reference it from another page or class using the `iframe`
1023 method. The `iframe` method takes 3 arguments; the name by which you
1024 would like to reference the iframe, the page class that represents the
1025 iframe, and an ID by which you can locate the iframe. For example:
1026
1027 ```ruby
1028 class PageContainingIframe < SitePrism::Page
1029 iframe :my_iframe, MyIframe, "#my_iframe_id"
1030 end
1031 ```
1032
1033 NB: An iframe can only be referenced by its ID. This is a limitation
1034 imposed by Capybara. The third argument to the `iframe` method must
1035 contain a selector that will locate the iframe node, and the portion of
1036 the selector that locates the iframe node must use the iframe node's ID.
1037
1038 ### Testing for an iframe's existence
1039
1040 Like an element or section, it is possible to test for an iframe's
1041 existence using the auto-generated `has_<iframe_name>?` method. Using
1042 the above example, here's how it's done:
1043
1044 ```ruby
1045 @page = PageContainingIframe.new
1046 # ...
1047 @page.has_my_iframe? #=> true
1048 @page.should have_my_iframe
1049 ```
1050
1051 ### Waiting for an iframe
1052
1053 Like an element or section, it is possible to wait for an iframe to
1054 exist by using the `wait_for_<iframe_name>` method. For example:
1055
1056 ```ruby
1057 @page = PageContainingIframe.new
1058 # ...
1059 @page.wait_for_my_iframe
1060 ```
1061
1062 ### Interacting with an iframe's contents:
1063
1064 Since an iframe contains a fully fledged SitePrism::Page, you are able
1065 to interact with the elements and sections defined within it. Due to
1066 capybara internals it is necessary to pass a block to the iframe instead
1067 of simply calling methods on it; the block argument is the
1068 SitePrism::Page that represents the iframe's contents. For example:
1069
1070 ```ruby
1071 # SitePrism::Page representing the iframe
1072 class Login < SitePrism::Page
1073 element :username "input.username"
1074 element :password "input.password"
1075 end
1076
1077 # SitePrism::Page representing the page that contains the iframe
1078 class Home < SitePrism::Page
1079 set_url "http://www.example.com"
1080
1081 iframe, :login_area, Login, "#login_and_registration"
1082 end
1083
1084 # cucumber step that performs login
1085 When /^I log in$/ do
1086 @home = Home.new
1087 @home.load
1088
1089 @home.login_area do |frame|
1090 #`frame` is an instance of the `Login` class
1091 frame.username.set "admin"
1092 frame.password.set "p4ssword"
1093 end
1094 end
1095 ```
5aa7d42 @natritmeyer adding blurb about sections
authored Apr 19, 2012
1096
9b9be54 @natritmeyer finishing readme - only 951 pages :(
authored Apr 19, 2012
1097 # Epilogue
1098
1099 So, we've seen how to use SitePrism to put together page objects made up
cbda501 @natritmeyer adding iframe related documentation
authored Jul 2, 2012
1100 of pages, elements, sections and iframes. But how to organise this stuff? There
9b9be54 @natritmeyer finishing readme - only 951 pages :(
authored Apr 19, 2012
1101 are a few ways of saving yourself having to create instances of pages
1102 all over the place. Here's an example of this common problem:
1103
1104 ```ruby
cbda501 @natritmeyer adding iframe related documentation
authored Jul 2, 2012
1105 @home = Home.new # <-- noise
9b9be54 @natritmeyer finishing readme - only 951 pages :(
authored Apr 19, 2012
1106 @home.load
1107 @home.search_field.set "Sausages"
1108 @home.search_field.search_button.click
cbda501 @natritmeyer adding iframe related documentation
authored Jul 2, 2012
1109 @results_page = SearchResults.new # <-- noise
9b9be54 @natritmeyer finishing readme - only 951 pages :(
authored Apr 19, 2012
1110 @results_page.should have_search_result_items
1111 ```
1112
1113 The annoyance (and, later, maintenance nightmare) is having to create
1114 `@home` and `@results_page`. It would be better to not have to create
4fe2fd7 @natritmeyer minor edit
authored Apr 19, 2012
1115 instances of pages all over your tests.
9b9be54 @natritmeyer finishing readme - only 951 pages :(
authored Apr 19, 2012
1116
1117 The way I've dealt with this problem is to create a class containing
1118 methods that return instances of the pages. Eg:
1119
1120 ```ruby
1121 # our pages
1122
1123 class Home < SitePrism::Page
1124 #...
1125 end
1126
1127 class SearchResults < SitePrism::Page
1128 #...
1129 end
1130
1131 class Maps < SitePrism::Page
1132 #...
1133 end
1134
1135 # here's the app class that represents our entire site:
1136
1137 class App
1138 def home
1139 Home.new
1140 end
1141
1142 def results_page
1143 SearchResults.new
1144 end
1145
1146 def maps
1147 Maps.new
1148 end
1149 end
1150
1151 # and here's how to use it:
1152
1153 #first line of the test...
1154 Given /^I start on the home page$/ do
1155 @app = App.new
1156 @app.home.load
1157 end
1158
1159 When /^I search for Sausages$/ do
1160 @app.home.search_field.set "sausages"
1161 @app.home.search_button.click
1162 end
1163
1164 # etc...
1165 ```
1166
1167 The only thing that needs instantiating is the App class - from then on
1168 pages don't need to be initialized, they are now returned by methods on
1169 @app. Maintenance win!
3d06fc0 @natritmeyer adding wip notice to readme
authored Apr 17, 2012
1170
Something went wrong with that request. Please try again.