site_prism 0.9.9 → 1.0

Sign up to get free protection for your applications and to get access to all the features.
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