site_prism 0.9.9 → 1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,962 @@
1
+ # SitePrism
2
+ _A Page Object Model DSL for Capybara_
3
+
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.
5
+
6
+ Find the pretty documentation here: http://rdoc.info/gems/site_prism/frames
7
+
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
+
82
+ ## Setup
83
+
84
+ ### Installation
85
+
86
+ To install SitePrism:
87
+
88
+ ```bash
89
+ gem install site_prism
90
+ ```
91
+
92
+ ### Using SitePrism with Cucumber
93
+
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
+
104
+ ### Using SitePrism with RSpec
105
+
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
+
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
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.
131
+
132
+ ## Pages
133
+
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
+
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
162
+ page or a login page, but probably not a search results page.
163
+
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
170
+ @home_page = Home.new
171
+ @home_page.load
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.
176
+
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
181
+ defined using `set_url` is the current page in the browser would be enough, but experience shows that it's
182
+ not. It is far more robust to check to see if the browser's current url
183
+ matches a regular expression. For example, though `account/1` and `account/2`
184
+ are the same page, their URLs are different. To deal with this,
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
197
+ @account_page = Account.new
198
+ #...
199
+ @account_page.displayed? #=> true or false
200
+ ```
201
+
202
+ Calling `#displayed?` will return true if the browser's current URL
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.
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
215
+ @some_other_page.should_not be_displayed
216
+ end
217
+ ```
218
+
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.
222
+
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.
235
+
236
+ #### Page Title
237
+
238
+ Getting a page's title isn't hard:
239
+
240
+ ```ruby
241
+ class Account < SitePrism::Page
242
+ end
243
+
244
+ @account = Account.new
245
+ #...
246
+ @account.title #=> "Welcome to Your Account"
247
+ ```
248
+
249
+ ## Elements
250
+
251
+ Pages are made up of elements (text fields, buttons, combo boxes, etc),
252
+ either individual elements or groups of them. Examples of individual
253
+ elements would be a search field or a company logo image; examples of
254
+ element collections would be items in any sort of list, eg: menu items,
255
+ images in a carousel, etc.
256
+
257
+ ### Individual Elements
258
+
259
+ To interact with individual elements, they need to be defined as part of
260
+ the relevant page. SitePrism makes this easy:
261
+
262
+ ```ruby
263
+ class Home < SitePrism::Page
264
+ element :search_field, "input[name='q']"
265
+ end
266
+ ```
267
+
268
+ Here we're adding a search field to the Home page. The `element` method
269
+ takes 2 arguments: the name of the element as a symbol, and a css selector
270
+ as a string.
271
+
272
+ #### Accessing the individual element
273
+
274
+ The `element` method will add a number of methods to instances of the
275
+ particular Page class. The first method to be added is the name of the
276
+ element. So using the following example:
277
+
278
+ ```ruby
279
+ class Home < SitePrism::Page
280
+ set_url "http://www.google.com"
281
+
282
+ element :search_field, "input[name='q']"
283
+ end
284
+ ```
285
+
286
+ ... the following shows how to get hold of the search field:
287
+
288
+ ```ruby
289
+ @home_page = Home.new
290
+ @home.load
291
+
292
+ @home.search_field #=> will return the capybara element found using the selector
293
+ @home.search_field.set "the search string" #=> since search_field returns a capybara element, you can use the capybara API to deal with it
294
+ @home.search_field.text #=> standard method on a capybara element; returns a string
295
+ ```
296
+
297
+ #### Testing for the existence of the element
298
+
299
+ Another method added to the Page class by the `element` method is the
300
+ `has_<element name>?` method. Using the same example as above:
301
+
302
+ ```ruby
303
+ class Home < SitePrism::Page
304
+ set_url "http://www.google.com"
305
+
306
+ element :search_field, "input[name='q']"
307
+ end
308
+ ```
309
+
310
+ ... you can test for the existence of the element on the page like this:
311
+
312
+ ```ruby
313
+ @home_page = Home.new
314
+ @home.load
315
+ @home.has_search_field? #=> returns true if it exists, false if it doesn't
316
+ ```
317
+
318
+ ...which makes for nice test code:
319
+
320
+ ```ruby
321
+ Then /^the search field exists$/ do
322
+ @home.should have_search_field
323
+ end
324
+ ```
325
+
326
+ #### Waiting for an element to appear on a page
327
+
328
+ The final method added by calling `element` is the `wait_for_<element_name>` method.
329
+ Calling the method will cause the test to wait for the Capybara's
330
+ default wait time for the element to exist. It is also possible to use a
331
+ custom amount of time to wait. Using the same example as above:
332
+
333
+ ```ruby
334
+ class Home < SitePrism::Page
335
+ set_url "http://www.google.com"
336
+
337
+ element :search_field, "input[name='q']"
338
+ end
339
+ ```
340
+
341
+ ... you can wait for the search field to exist like this:
342
+
343
+ ```ruby
344
+ @home_page = Home.new
345
+ @home.load
346
+ @home.wait_for_search_field
347
+ # or...
348
+ @home.wait_for_search_field(10) #will wait for 10 seconds for the search field to appear
349
+ ```
350
+
351
+ #### Summary of what the element method provides:
352
+
353
+ Given:
354
+
355
+ ```ruby
356
+ class Home < SitePrism::Page
357
+ element :search_field, "input[name='q']"
358
+ end
359
+ ```
360
+
361
+ ...then the following methods are available:
362
+
363
+ ```ruby
364
+ @home.search_field
365
+ @home.has_search_field?
366
+ @home.wait_for_search_field
367
+ @home.wait_for_search_field(10)
368
+ ```
369
+
370
+ ### Element Collections
371
+
372
+ Sometimes you don't want to deal with an individual element but rather
373
+ with a collection of similar elements, for example, a list of names. To
374
+ enable this, SitePrism provides the `elements` method on the Page class.
375
+ Here's how it works:
376
+
377
+ ```ruby
378
+ class Friends < SitePrism::Page
379
+ elements :names, "ul#names li a"
380
+ end
381
+ ```
382
+
383
+ Just like the `element` method, the `elements` method takes 2 arguments:
384
+ the first being the name of the elements as a symbol, the second is the
385
+ css selector that would return the array of capybara elements.
386
+
387
+ #### Accessing the elements
388
+
389
+ Just like the `element` method, the `elements` method adds a few methods
390
+ to the Page class. The first one is of the name of the element
391
+ collection which returns an array of capybara elements that match the
392
+ css selector. Using the example above:
393
+
394
+ ```ruby
395
+ class Friends < SitePrism::Page
396
+ elements :names, "ul#names li a"
397
+ end
398
+ ```
399
+
400
+ You can access the element collection like this:
401
+
402
+ ```ruby
403
+ @friends_page = Friends.new
404
+ # ...
405
+ @friends_page.names #=> [<Capybara::Element>, <Capybara::Element>, <Capybara::Element>]
406
+ ```
407
+
408
+ With that you can do all the normal things that are possible with
409
+ arrays:
410
+
411
+
412
+ ```ruby
413
+ @friends_page.names.each {|name| puts name.text}
414
+ @friends_page.names.map {|name| name.text}.should == ["Alice", "Bob", "Fred"]
415
+ @friends_page.names.size.should == 3
416
+ ```
417
+
418
+ #### Testing for the existence of the element collection
419
+
420
+ Just like the `element` method, the `elements` method adds a method to
421
+ the page that will allow you to check for the existence of the
422
+ collection, called `has_<element collection name>?`. As long as there is
423
+ at least 1 element in the array, the method will return true, otherwise
424
+ false. For example, with the following page:
425
+
426
+ ```ruby
427
+ class Friends < SitePrism::Page
428
+ elements :names, "ul#names li a"
429
+ end
430
+ ```
431
+
432
+ ... the following method is available:
433
+
434
+ ```ruby
435
+ @friends_page.has_names? #=> returns true if at least one element is found using the relevant selector
436
+ ```
437
+
438
+ ...which allows for pretty test code:
439
+
440
+ ```ruby
441
+ Then /^there should be some names listed on the page$/ do
442
+ @friends_page.should have_names
443
+ end
444
+ ```
445
+
446
+ #### Waiting for the element collection
447
+
448
+ Just like for an individual element, the tests can be told to wait for
449
+ the existence of the element collection. The `elements` method adds a
450
+ `wait_for_<element collection name>` method that will wait for
451
+ Capybara's default wait time until at least 1 element is found that
452
+ matches the selector. For example, with the following page:
453
+
454
+ ```ruby
455
+ class Friends < SitePrism::Page
456
+ elements :names, "ul#names li a"
457
+ end
458
+
459
+ ```
460
+
461
+ ... you can wait for the existence of a list of names like this:
462
+
463
+ ```ruby
464
+ @friends_page.wait_for_names
465
+ ```
466
+
467
+ Again, you can customise the wait time by supplying a number of seconds
468
+ to wait for:
469
+
470
+ ```ruby
471
+ @friends_page.wait_for_names(10)
472
+ ```
473
+
474
+ ### Checking that all mapped elements are present on the page
475
+
476
+ Throughout my time in test automation I keep getting asked to provide the
477
+ ability to check that all elements that should be on the page are on the
478
+ page. Why people would want to test this, I don't know. But if that's
479
+ what you want to do, SitePrism provides the `#all_there?` method that
480
+ will return true if all mapped elements (and sections... see below) are
481
+ present in the browser, false if they're not all there.
482
+
483
+ ```ruby
484
+ @friends_page.all_there? #=> true/false
485
+
486
+ # and...
487
+
488
+ Then /^the friends page contains all the expected elements$/ do
489
+ @friends_page.should be_all_there
490
+ end
491
+
492
+ ```
493
+
494
+ ## Sections
495
+
496
+ SitePrism allows you to model sections of a page that appear on multiple
497
+ pages or that appear a number of times on a page separately from Pages.
498
+ SitePrism provides the Section class for this task.
499
+
500
+ ### Individual Sections
501
+
502
+ In the same way that SitePrism provides `element` and `elements`, it
503
+ provides `section` and `sections`. The first returns an instance of a
504
+ page section, the secont returns an array of section instances, one for
505
+ each capybara element found by the supplied css selector. What follows
506
+ is an explanation of `section`.
507
+
508
+
509
+ #### Defining a Section
510
+
511
+ A section is similar to a page in that it inherits from a SitePrism
512
+ class:
513
+
514
+ ```ruby
515
+ class MenuSection < SitePrism::Section
516
+ end
517
+ ```
518
+
519
+ At the moment, this section does nothing.
520
+
521
+ #### Adding a section to a page
522
+
523
+ Pages include sections that's how SitePrism works. Here's a page that
524
+ includes the above `MenuSection` section:
525
+
526
+ ```ruby
527
+ class Home < SitePrism::Page
528
+ section :menu, MenuSection, "#gbx3"
529
+ end
530
+ ```
531
+
532
+ The way to add a section to a page (or another section -
533
+ SitePrism allows adding sections to sections) is to call the `section`
534
+ method. It takes 3 arguments: the first is the name of the section as
535
+ referred to on the page (sections that appear on multiple pages can be
536
+ named differently). The second argument is the class of which an
537
+ instance will be created to represent the page section, and the third
538
+ argument is a css selector that identifies the root node of the section
539
+ on this page (note that the css selector can be different for different
540
+ pages as the whole point of sections is that they can appear in
541
+ different places on different pages).
542
+
543
+ #### Accessing a page's section
544
+
545
+ The `section` method (like the `element` method) adds a few methods to
546
+ the page or section class it was called against. The first method that
547
+ is added is one that returns an instance of the section, the method name
548
+ being the first argument to the `section` method. Here's an example:
549
+
550
+ ```ruby
551
+ # the section:
552
+
553
+ class MenuSection < SitePrism::Section
554
+ end
555
+
556
+ # the page that includes the section:
557
+
558
+ class Home < SitePrism::Page
559
+ section :menu, MenuSection, "#gbx3"
560
+ end
561
+
562
+ # the page and section in action:
563
+
564
+ @home = Home.new
565
+ @home.menu #=> <MenuSection...>
566
+ ```
567
+
568
+ When the `menu` method is called against `@home`, an instance of
569
+ `MenuSection` (the second argument to the `section` method) is returned.
570
+ The third argument that is passed to the `section` method is the css
571
+ selector that will be used to find the root element of the section; this
572
+ root node becomes the 'scope' of the section.
573
+
574
+ The following shows that though the same section can appear on multiple
575
+ pages, it can take a different root node:
576
+
577
+ ```ruby
578
+ # define the section that appears on both pages
579
+
580
+ class MenuSection < SitePrism::Section
581
+ end
582
+
583
+ # define 2 pages, each containing the same section
584
+
585
+ class Home < SitePrism::Page
586
+ section :menu, MenuSection, "#gbx3"
587
+ end
588
+
589
+ class SearchResults < SitePrism::Page
590
+ section :menu, MenuSection, "#gbx48"
591
+ end
592
+ ```
593
+
594
+ You can see that the `MenuSection` is used in both the `Home` and
595
+ `SearchResults` pages, but each has slightly different root node. The
596
+ capybara element that is found by the css selector becomes the root node
597
+ for the relevant page's instance of the `MenuSection` section.
598
+
599
+ #### Adding elements to a section
600
+
601
+ This works just the same as adding elements to a page:
602
+
603
+ ```ruby
604
+ class MenuSection < SitePrism::Section
605
+ element :search, "a.search"
606
+ element :images, "a.image-search"
607
+ element :maps, "a.map-search"
608
+ end
609
+ ```
610
+
611
+ Note that the css selectors used to find elements are searched for
612
+ within the scope of the root element of that section. The search for the
613
+ element won't be page-wide but it will only look in the section.
614
+
615
+ When the section is added to a page...
616
+
617
+ ```ruby
618
+ class Home < SitePrism::Page
619
+ section :menu, MenuSection, "#gbx3"
620
+ end
621
+ ```
622
+
623
+ ...then the section's elements can be accessed like this:
624
+
625
+ ```ruby
626
+ @home = Home.new
627
+ @home.load
628
+
629
+ @home.menu.search #=> returns a capybara element representing the link to the search page
630
+ @home.menu.search.click #=> clicks the search link in the home page menu
631
+ @home.menu.search['href'] #=> returns the value for the href attribute of the capybara element representing the search link
632
+ @home.menu.has_images? #=> returns true or false based on whether the link is present in the section on the page
633
+ @home.menu.wait_for_images #=> waits for capybara's default wait time until the element appears in the page section
634
+
635
+ ```
636
+
637
+ ...which leads to some pretty test code:
638
+
639
+ ```ruby
640
+ Then /^the home page menu contains a link to the various search functions$/ do
641
+ @home.menu.should have_search
642
+ @home.menu.search['href'].should include "google.com"
643
+ @home.menu.should have_images
644
+ @home.menu.should have_maps
645
+ end
646
+ ```
647
+
648
+ #### Testing for the existence of a section
649
+
650
+ Just like elements, it is possible to test for the existence of a
651
+ section. The `section` method adds a method called `has_<section name>?`
652
+ to the page or section it's been added to - same idea as what the
653
+ `has_<element name>?` method. Given the following setup:
654
+
655
+ ```ruby
656
+ class MenuSection < SitePrism::Section
657
+ element :search, "a.search"
658
+ element :images, "a.image-search"
659
+ element :maps, "a.map-search"
660
+ end
661
+
662
+ class Home < SitePrism::Page
663
+ section :menu, MenuSection, "#gbx3"
664
+ end
665
+ ```
666
+
667
+ ... you can check whether the section is present on the page or not:
668
+
669
+ ```ruby
670
+ @home = Home.new
671
+ #...
672
+ #home.has_menu? #=> returns true or false
673
+ ```
674
+
675
+ Again, this allows pretty test code:
676
+
677
+ ```ruby
678
+ @home.should have_menu
679
+ @home.should_not have_menu
680
+ ```
681
+
682
+ #### Waiting for a section to appear
683
+
684
+ The final method added to the page or section by the `section` method is
685
+ `wait_for_<section name>`. Similar to what `element` does, this method
686
+ waits for the section to appear - the test will wait up to capybara's
687
+ default wait time until the root node of the element exists on the
688
+ page/section that our section was added to. Given the following setup:
689
+
690
+ ```ruby
691
+ class MenuSection < SitePrism::Section
692
+ element :search, "a.search"
693
+ element :images, "a.image-search"
694
+ element :maps, "a.map-search"
695
+ end
696
+
697
+ class Home < SitePrism::Page
698
+ section :menu, MenuSection, "#gbx3"
699
+ end
700
+ ```
701
+
702
+ ... we can wait for the menu section to appear on the page like this:
703
+
704
+ ```ruby
705
+ @home.wait_for_menu
706
+ @home.wait_for_menu(10) # waits for 10 seconds instead of capybara's default timeout
707
+ ```
708
+
709
+ #### Sections within sections
710
+
711
+ You are not limited to adding sections only to pages; you can nest
712
+ sections within sections within sections within sections!
713
+
714
+ ```ruby
715
+
716
+ # 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
717
+
718
+ class Login < SitePrism::Section
719
+ element :username, "#username"
720
+ element :password, "#password"
721
+ element :sign_in, "button"
722
+ end
723
+
724
+ class Registration < SitePrism::Section
725
+ element :first_name, "#first_name"
726
+ element :last_name, "#last_name"
727
+ element :next_step, "button.next-reg-step"
728
+ end
729
+
730
+ class LoginRegistrationForm < SitePrism::Section
731
+ section :login, Login, "div.login-area"
732
+ section :registration, Registration, "div.reg-area"
733
+ end
734
+
735
+ class Home < SitePrism::Page
736
+ section :login_and_registration, LoginRegistrationForm, "div.login-registration"
737
+ end
738
+
739
+ # how to login (fatuous, but demonstrates the point):
740
+
741
+ Then /^I sign in$/ do
742
+ @home = Home.new
743
+ @home.load
744
+ @home.wait_for_login_and_registration
745
+ @home.should have_login_and_registration
746
+ @home.login_and_registration.should have_username
747
+ @home.login_and_registration.login.username.set "bob"
748
+ @home.login_and_registration.login.password.set "p4ssw0rd"
749
+ @home.login_and_registration.login.sign_in.click
750
+ end
751
+
752
+ # how to sign up:
753
+
754
+ When /^I enter my name into the home page's registration form$/ do
755
+ @home = Home.new
756
+ @home.load
757
+ @home.login_and_registration.should have_first_name
758
+ @home.login_and_registration.should have_last_name
759
+ @home.login_and_registration.first_name.set "Bob"
760
+ # ...
761
+ end
762
+ ```
763
+
764
+ ### Section Collections
765
+
766
+ An individual section represents a discrete section of a page, but often
767
+ sections are repeated on a page, an example is a search result listing -
768
+ each listing contains a title, a url and a description of the content.
769
+ It makes sense to model this only once and then to be able to access
770
+ each instance of a search result on a page as an array of SitePrism
771
+ sections. To achieve this, SitePrism provides the `sections` method that
772
+ can be called in a page or a section.
773
+
774
+ The only difference between `section` and `sections` is that whereas the
775
+ first returns an instance of the supplied section class, the second
776
+ returns an array containing as many instances of the section class as
777
+ there are capybara elements found by the supplied css selector. This is
778
+ better explained in code :)
779
+
780
+ #### Adding a Section collection to a page (or other section)
781
+
782
+ Given the following setup:
783
+
784
+ ```ruby
785
+ class SearchResultSection < SitePrism::Section
786
+ element :title, "a.title"
787
+ element :blurb, "span.result-decription"
788
+ end
789
+
790
+ class SearchResults < SitePrism::Page
791
+ sections :search_results, SearchResultSection, "#results li"
792
+ end
793
+ ```
794
+
795
+ ... it is possible to access each of the search results:
796
+
797
+ ```ruby
798
+ @results_page = SearchResults.new
799
+ # ...
800
+ @results_page.search_results.each do |search_result|
801
+ puts search_result.title.text
802
+ end
803
+ ```
804
+
805
+ ... which allows for pretty tests:
806
+
807
+ ```ruby
808
+ Then /^there are lots of search_results$/ do
809
+ @results_page.search_results.size.should == 10
810
+ @results_page.search_results.each do |search_result|
811
+ search_result.should have_title
812
+ search_result.blurb.text.should_not be_nil
813
+ end
814
+ end
815
+ ```
816
+
817
+ The css selector that is passed as the 3rd argument to the
818
+ `sections` method ("#results li") is used to find a number of capybara
819
+ elements. Each capybara element found using the css selector is used to
820
+ create a new instance of the `SearchResultSection` and becomes its root
821
+ element. So if the css selector finds 3 `li` elements, calling
822
+ `search_results` will return an array containing 3 instances of
823
+ `SearchResultSection`, each with one of the `li` elements as it's root
824
+ element.
825
+
826
+ #### Testing for existence of Sections
827
+
828
+ Using the example above, it is possible to test for the existence of the
829
+ sections. As long as there is at least one section in the array, the
830
+ sections exist. The `sections` method adds a `has_<sections name>?`
831
+ method to the page/section that our section has been added to. Given the
832
+ following example:
833
+
834
+ ```ruby
835
+ class SearchResultSection < SitePrism::Section
836
+ element :title, "a.title"
837
+ element :blurb, "span.result-decription"
838
+ end
839
+
840
+ class SearchResults < SitePrism::Page
841
+ sections :search_results, SearchResultSection, "#results li"
842
+ end
843
+ ```
844
+
845
+ ... here's how to test for the existence of the section:
846
+
847
+ ```ruby
848
+ @results_page = SearchResults.new
849
+ # ...
850
+ @results_page.has_search_results?
851
+ ```
852
+
853
+ ...which allows pretty tests:
854
+
855
+ ```ruby
856
+ Then /^there are search results on the page$/ do
857
+ @results.page.should have_search_results
858
+ end
859
+ ```
860
+
861
+ #### Waiting for sections to appear
862
+
863
+ The final method added by `sections` to the page/section we're adding
864
+ our sections to is `wait_for_<sections name>`. It will wait for
865
+ capybara's default wait time for there to be at least one instance of
866
+ the section in the array of sections. For example:
867
+
868
+ ```ruby
869
+ class SearchResultSection < SitePrism::Section
870
+ element :title, "a.title"
871
+ element :blurb, "span.result-decription"
872
+ end
873
+
874
+ class SearchResults < SitePrism::Page
875
+ sections :search_results, SearchResultSection, "#results li"
876
+ end
877
+ ```
878
+
879
+ ... here's how to wait for the section:
880
+
881
+ ```ruby
882
+ @results_page = SearchResults.new
883
+ # ...
884
+ @results_page.wait_for_search_results
885
+ @results_page.wait_for_search_results(10) #=> waits for 10 seconds instead of the default capybara timeout
886
+ ```
887
+
888
+
889
+ # Epilogue
890
+
891
+ So, we've seen how to use SitePrism to put together page objects made up
892
+ of pages, elements and sections. But how to organise this stuff? There
893
+ are a few ways of saving yourself having to create instances of pages
894
+ all over the place. Here's an example of this common problem:
895
+
896
+ ```ruby
897
+ @home = Home.new
898
+ @home.load
899
+ @home.search_field.set "Sausages"
900
+ @home.search_field.search_button.click
901
+ @results_page = SearchResults.new
902
+ @results_page.should have_search_result_items
903
+ ```
904
+
905
+ The annoyance (and, later, maintenance nightmare) is having to create
906
+ `@home` and `@results_page`. It would be better to not have to create
907
+ instances of pages all over your tests.
908
+
909
+ The way I've dealt with this problem is to create a class containing
910
+ methods that return instances of the pages. Eg:
911
+
912
+ ```ruby
913
+ # our pages
914
+
915
+ class Home < SitePrism::Page
916
+ #...
917
+ end
918
+
919
+ class SearchResults < SitePrism::Page
920
+ #...
921
+ end
922
+
923
+ class Maps < SitePrism::Page
924
+ #...
925
+ end
926
+
927
+ # here's the app class that represents our entire site:
928
+
929
+ class App
930
+ def home
931
+ Home.new
932
+ end
933
+
934
+ def results_page
935
+ SearchResults.new
936
+ end
937
+
938
+ def maps
939
+ Maps.new
940
+ end
941
+ end
942
+
943
+ # and here's how to use it:
944
+
945
+ #first line of the test...
946
+ Given /^I start on the home page$/ do
947
+ @app = App.new
948
+ @app.home.load
949
+ end
950
+
951
+ When /^I search for Sausages$/ do
952
+ @app.home.search_field.set "sausages"
953
+ @app.home.search_button.click
954
+ end
955
+
956
+ # etc...
957
+ ```
958
+
959
+ The only thing that needs instantiating is the App class - from then on
960
+ pages don't need to be initialized, they are now returned by methods on
961
+ @app. Maintenance win!
962
+
@@ -1,26 +1,5 @@
1
- # Contains methods applicable to both {SitePrism::Page}s and {SitePrism::Section}s. Note that they are mixed into the {SitePrism::Page}
2
- # and {SitePrism::Section} classes so the methods below are used as class methods.
3
1
  module SitePrism::ElementContainer
4
2
 
5
- # Creates two methods; the first method has the same name as the element_name parameter and returns the capybara element
6
- # located by the element_locator parameter when the method is called. The second method generated has a name with a format
7
- # of: 'has_#\{element_name}?' which returns true if the element as located by the element_locator parameter exists, false
8
- # if it doesn't
9
- # @param [Symbol] element_name The name of the element
10
- # @param [String] element_locator The CSS locator to find the element
11
- # @example
12
- # class HomePage < SitePrism::Page
13
- # element :search_link, 'div.search > a'
14
- # end
15
- # home = HomePage.new
16
- #
17
- # #the element method created 2 methods...
18
- # home.search_link #=> returns the capybara element located by the element_locator parameter
19
- # home.has_search_link? #=> returns true if the capybara element as located by the element_locator exists, false if it doesn't
20
- #
21
- # #The has_search_link? method allows use of magic matchers in rspec/cucumber:
22
- # home.should have_search_link
23
- # home.should_not have_search_link
24
3
  def element element_name, element_locator = nil
25
4
  if element_locator.nil?
26
5
  define_method element_name.to_s do
@@ -36,20 +15,6 @@ module SitePrism::ElementContainer
36
15
  create_waiter element_name, element_locator
37
16
  end
38
17
 
39
- # Works in the same way as {SitePrism::Page.element} in that it will generate two methods; one to check existence of
40
- # the element (in the format 'has_#\{element_name}?'), and another to return not a single element, but an array of elements
41
- # found by the css locator
42
- # @param [Symbol] collection_name The name of the collection
43
- # @param [String] collection_locator The CSS locator that returns the list of elements in the collection
44
- # @example
45
- # class HomePage < SitePrism::Page
46
- # elements :app_links, '.title-links > a'
47
- # end
48
- # home = HomePage.new
49
- #
50
- # home.should have_app_links
51
- # home.app_links #=> [#<Capybara::Element tag="a">, #<Capybara::Element tag="a">, #<Capybara::Element tag="a">]
52
- # home.app_links.map {|link| link.text}.should == ['Finance', 'Maps', 'Blogs']
53
18
  def elements collection_name, collection_locator = nil
54
19
  if collection_locator.nil?
55
20
  define_method collection_name.to_s do
@@ -66,31 +31,6 @@ module SitePrism::ElementContainer
66
31
  end
67
32
  alias :collection :elements
68
33
 
69
- # Creates a method that returns an instance of a {SitePrism::Section}. If a page contains a common section (eg: a search area) that
70
- # appears on many pages, create a {SitePrism::Section} for it and then expose it in each {SitePrism::Page} that contains the section.
71
- # Say a search engine website displays the search field and search button on each page and they always have the same IDs, they should
72
- # be extracted into a {SitePrism::Section} that would look something like this:
73
- #
74
- # class SearchArea < SitePrism::Section
75
- # element :search_field, '.q'
76
- # element :search_button, '.btnK'
77
- # end
78
- #
79
- # ...then that section could be added to any page as follows:
80
- #
81
- # class SearchPage < SitePrism::Page
82
- # section :search_area, SearchArea, '.tsf-p'
83
- # end
84
- #
85
- # class SearchResultsPage < SitePrism::Page
86
- # section :search_again, SearchArea, '.tsf-p table'
87
- # end
88
- #
89
- # The SearchArea section appears on both pages, but can be invoked by methods specific to the page (eg: 'search_area' and 'search_again')
90
- # and the root element for the section can be different on the page (eg: '.tsf-p' and '.tsf-p table').
91
- # @param [Symbol] the method name to be called against this page or section to return an instance of the {SitePrism::Section} class
92
- # @param [Class] the class that models this area of the page
93
- # @param [String] the CSS locator for the root element of the section on this page/section
94
34
  def section section_name, section_class, section_locator
95
35
  add_element_name section_name
96
36
  create_existence_checker section_name, section_locator
@@ -100,7 +40,6 @@ module SitePrism::ElementContainer
100
40
  end
101
41
  end
102
42
 
103
- # Works in the same way as {SitePrism::Page.section} but instead of it returning one section, it returns an array of them.
104
43
  def sections section_collection_name, section_class, section_collection_locator
105
44
  add_element_name section_collection_name
106
45
  create_existence_checker section_collection_name, section_collection_locator
@@ -112,21 +51,17 @@ module SitePrism::ElementContainer
112
51
  end
113
52
  end
114
53
 
115
- # Adds the element name to the list of known elements
116
54
  def add_element_name element_name
117
55
  @element_names ||= []
118
56
  @element_names << element_name
119
57
  end
120
58
 
121
- # Returns list of known element names
122
59
  def element_names
123
60
  @element_names
124
61
  end
125
62
 
126
63
  private
127
64
 
128
- # Creates a method used to check for the existence of the element whose details are passed to it
129
- # @param
130
65
  def create_existence_checker element_name, element_locator
131
66
  method_name = "has_#{element_name.to_s}?"
132
67
  if element_locator.nil?
@@ -142,7 +77,6 @@ module SitePrism::ElementContainer
142
77
  end
143
78
  end
144
79
 
145
- # Creates a method used to wait for an element to appear - uses the default capybara wait time
146
80
  def create_waiter element_name, element_locator
147
81
  method_name = "wait_for_#{element_name.to_s}"
148
82
  if element_locator.nil?
@@ -1,10 +1,6 @@
1
- # SitePrism's exceptions...
2
1
  module SitePrism
3
- # Raised if you ask a page to load but it hasn't had its url set
4
2
  class NoUrlForPage < StandardError; end
5
- # Raised if you check to see if a page is displayed but it hasn't had its url matcher set
6
3
  class NoUrlMatcherForPage < StandardError; end
7
- # Raised if you ask for an element that hasn't got a locator (i.e. a pending element)
8
4
  class NoLocatorForElement < StandardError; end
9
5
  end
10
6
 
@@ -1,90 +1,43 @@
1
1
  module SitePrism
2
- # Subclasses of {SitePrism::Page} represent pages in your app.
3
- # class Home < SitePrism::Page
4
- # end
5
- #
6
- # The above is an example of how to make a class representing the home page. There are a number of properties that can be
7
- # set on a page - here is an example of a more fully spec'ed out page:
8
- # class Home < SitePrism::Page
9
- # set_url "/"
10
- # set_url_matcher /\/home.htm$/
11
- # end
12
2
  class Page
13
3
  include Capybara::DSL
14
4
  include ElementChecker
15
5
  extend ElementContainer
16
6
 
17
- # Visits the url associated with this page
18
- # @raise [SitePrism::NoUrlForPage] To load a page the url must be set using {.set_url}
19
7
  def load
20
8
  raise SitePrism::NoUrlForPage if url.nil?
21
9
  visit url
22
10
  end
23
11
 
24
- # Checks to see if we're on this page or not
25
- # @return true if the browser's current url matches the {.url_matcher} that has been set, false if it doesn't
26
- # @raise [SitePrism::NoUrlMatcherForPage] To check whether we're on this page or not the url matcher must be set using {.set_url_matcher}
27
- # @example
28
- # class SearchPage < SitePrism::Page
29
- # set_url_matcher /\/search.htm$/
30
- # end
31
- # search_page = SearchPage.new
32
- # search_page.load
33
- # puts "We're on the search page" if search_page.displayed?
34
- # search_page.should be_displayed
35
12
  def displayed?
36
13
  raise SitePrism::NoUrlMatcherForPage if url_matcher.nil?
37
14
  !(page.current_url =~ url_matcher).nil?
38
15
  end
39
16
 
40
- # Set the url associated with this page
41
- # @param [String] page_url the portion of the url that identifies this page when appended onto Capybara's app_host. Calling {SitePrism::Page#load} causes Capybara to visit this page.
42
- # @example
43
- # class SearchPage < SitePrism::Page
44
- # set_url "/search.htm"
45
- # end
46
17
  def self.set_url page_url
47
18
  @url = page_url
48
19
  end
49
20
 
50
- # Set the url matcher associated with this page
51
- # @param [Regexp] page_url_matcher a regular expression that when compared to the current browser url will match if we're on this page or not match if we're not on this page
52
- # @example
53
- # class SearchPage < SitePrism::Page
54
- # set_url_matcher /\/search.htm$/
55
- # end
56
21
  def self.set_url_matcher page_url_matcher
57
22
  @url_matcher = page_url_matcher
58
23
  end
59
24
 
60
- # Get the url associated with this page
61
- # @see SitePrism::Page#url
62
- # @return [String] the url originally set in {.set_url}
63
25
  def self.url
64
26
  @url
65
27
  end
66
28
 
67
- # Get the url matcher associated with this page
68
- # @see SitePrism::Page#url_matcher
69
- # @return [Regexp] the url matcher originally set in {.set_url_matcher}
70
29
  def self.url_matcher
71
30
  @url_matcher
72
31
  end
73
32
 
74
- # Get the url associated with this page
75
- # @see SitePrism::Page.url
76
33
  def url
77
34
  self.class.url
78
35
  end
79
36
 
80
- # Get the url matcher associated with this page
81
- # @see SitePrism::Page.url_matcher
82
37
  def url_matcher
83
38
  self.class.url_matcher
84
39
  end
85
40
 
86
- # Gets the title of the current page
87
- # @return [String, nil] the text value of the title element within the page's head block
88
41
  def title
89
42
  title_selector = 'html > head > title'
90
43
  using_wait_time(0) { page.find(title_selector).text if page.has_selector?(title_selector) }
@@ -92,22 +45,18 @@ module SitePrism
92
45
 
93
46
  private
94
47
 
95
- # Page specific element finder
96
48
  def find_one locator
97
49
  find locator
98
50
  end
99
51
 
100
- # Page specific elements finder
101
52
  def find_all locator
102
53
  all locator
103
54
  end
104
55
 
105
- # Page specific element existence check
106
56
  def element_exists? locator
107
57
  has_selector? locator
108
58
  end
109
59
 
110
- # Page specific element waiter
111
60
  def element_waiter locator
112
61
  wait_until { element_exists? locator }
113
62
  end
@@ -23,22 +23,18 @@ module SitePrism
23
23
 
24
24
  private
25
25
 
26
- # Section specific element finder
27
26
  def find_one locator
28
27
  @root_element.find locator
29
28
  end
30
29
 
31
- # Section specific elements finder
32
30
  def find_all locator
33
31
  @root_element.all locator
34
32
  end
35
33
 
36
- # Section specific element existence check
37
34
  def element_exists? locator
38
35
  @root_element.has_selector? locator
39
36
  end
40
37
 
41
- # Section specific element waiter
42
38
  def element_waiter locator
43
39
  Capybara.current_session.wait_until { element_exists? locator }
44
40
  end
@@ -1,3 +1,4 @@
1
1
  module SitePrism
2
- VERSION = "0.9.9"
2
+ VERSION = "1.0"
3
3
  end
4
+
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: site_prism
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.9.9
5
+ version: "1.0"
6
6
  platform: ruby
7
7
  authors:
8
8
  - Nat Ritmeyer
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2012-03-24 00:00:00 Z
13
+ date: 2012-04-19 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: capybara
@@ -52,6 +52,7 @@ files:
52
52
  - lib/site_prism/version.rb
53
53
  - lib/site_prism.rb
54
54
  - LICENSE
55
+ - README.md
55
56
  homepage: http://github.com/natritmeyer/site_prism
56
57
  licenses: []
57
58