rsel 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,17 @@
1
1
  Rsel History
2
2
  ============
3
3
 
4
+ 0.0.3
5
+ -----
6
+
7
+ - Added `row_exists`, for verifying the presence of a table row
8
+ - Added `scope` hash with support for `within` clause on most methods
9
+ - Bugfix for `pause_seconds` (was waiting 1000 times too long)
10
+ - Fix behavior of enable/disable checkbox, so onclick events will fire
11
+ - Tell Selenium to highlight located elements
12
+ - XPath 0.1.4 is now required
13
+
14
+
4
15
  0.0.2
5
16
  -----
6
17
 
@@ -7,6 +7,7 @@ you to use a natural English syntax to write web application tests.
7
7
 
8
8
  - [Installation & Configuration](install.md)
9
9
  - [Usage](usage.md)
10
+ - [Scoping](scoping.md)
10
11
  - [Customization](custom.md)
11
12
  - [Development](development.md)
12
13
  - [To-do list](todo.md)
@@ -0,0 +1,72 @@
1
+ Scoping
2
+ -------
3
+
4
+ Several of the actions and verifications accept an optional hash of scoping
5
+ keywords, allowing you to be specific about what area of the page to operate
6
+ on. For example, you might have a page of contact information with HTML that
7
+ looks something like this:
8
+
9
+ <div id="work">
10
+ <label for="work_phone">Phone number</label>
11
+ <input id="work_phone" type="text" />
12
+ </div>
13
+
14
+ <div id="home">
15
+ <label for="home_phone">Phone number</label>
16
+ <input id="home_phone" type="text" />
17
+ </div>
18
+
19
+ Notice that both text inputs are labeled "Phone number", which means that if
20
+ you try to do this in your Rsel table:
21
+
22
+ | Type | 123-456-7890 | into | Phone number | field |
23
+
24
+ Rsel will just fill in the first matching field that it finds (in this case,
25
+ the "work" phone number). If you needed to fill in the "home" phone number,
26
+ you'd have to refer to it by something less ambiguous, such as its `id`:
27
+
28
+ | Type | 123-456-7890 | into | home_phone | field |
29
+
30
+ In this case, the `id` is short and intelligible, so it's not such a bad thing,
31
+ but it's fairly common for the `id` to be autogenerated, in which case it might
32
+ have some ridiculous-looking string like `form_customer_contact_field_home_phone_0`,
33
+ and you'd like to avoid that.
34
+
35
+ This is where scoping comes in. Scoping in Rsel allows you to be more specific
36
+ about which "Phone number" field you want to type into, by specifying the `id`
37
+ of its container:
38
+
39
+ | Type | 111-222-3333 | into | Phone number | field | !{within:work} |
40
+ | Type | 111-222-4444 | into | Phone number | field | !{within:home} |
41
+
42
+ Yeah I know, we're still using an `id` which could be annoyingly long, but if
43
+ there are a lot of fields in each container, you only need to keep track of one
44
+ `id` instead of several.
45
+
46
+ One important thing to note is that due to the way FitNesse Slim script tables
47
+ are evaluated, the scoping hash must be added after a cell that contains part
48
+ of the method name. Cells in these tables must contain alternating (function,
49
+ argument) chunks; the way the above example breaks down is:
50
+
51
+ | Type | | into | | field | |
52
+ | | 111-222-3333 | | Phone number | | !{within:work} |
53
+
54
+ That is, this row calls the `type_into_field` method with three arguments
55
+ (`111-222-3333`, `Phone number`, `!{within:work}`). If you're using a function
56
+ call row that does not end with a function-name component, you need to include
57
+ a semicolon after the last function-name component, so all remaining cells will
58
+ be treated as arguments. These are valid alternative ways of calling the same function:
59
+
60
+ | Type | 111-222-3333 | into field; | Phone number | !{within:work |
61
+ | Type into field; | 111-222-3333 | Phone number | !{within:work |
62
+
63
+ The `SeleniumTest` method names were, for the most part, crafted so that the
64
+ alternating (function, argument) form reads the most naturally.
65
+
66
+ As of now, `within` is the only supported scoping keyword, but additional ones
67
+ are being considered. For example, it might be useful to scope to something
68
+ that is `below` another element (such as a heading), or `next_to` an element
69
+ (such as in a table row or list).
70
+
71
+ Next: [Customization](custom.md)
72
+
@@ -46,5 +46,5 @@ driver frowns upon it.)
46
46
  See the `SeleniumTest` class documentation for a full list of available methods
47
47
  and how to use them.
48
48
 
49
- Next: [Customization](custom.md)
49
+ Next: [Scoping](scoping.md)
50
50
 
@@ -59,6 +59,8 @@ module Rsel
59
59
  # @example
60
60
  # | Open browser |
61
61
  #
62
+ # @raise [SeleniumNotRunning] if Selenium connection cannot be made
63
+ #
62
64
  def open_browser
63
65
  begin
64
66
  @browser.start_new_browser_session
@@ -68,6 +70,8 @@ module Rsel
68
70
  else
69
71
  visit @url
70
72
  end
73
+ # Make Selenium highlight elements whenever it locates them
74
+ @browser.highlight_located_element = true
71
75
  end
72
76
 
73
77
 
@@ -190,15 +194,17 @@ module Rsel
190
194
  #
191
195
  # @param [String] locator
192
196
  # Text or id of the link, or image alt text
197
+ # @param [Hash] scope
198
+ # Scoping keywords as understood by {#xpath}
193
199
  #
194
200
  # @example
195
201
  # | Link | Logout | exists |
196
- # | Link exists | Logout |
202
+ # | Link | Logout | exists | !{within:header} |
197
203
  #
198
204
  # @since 0.0.2
199
205
  #
200
- def link_exists(locator)
201
- return @browser.element?(xpath('link', locator))
206
+ def link_exists(locator, scope={})
207
+ return @browser.element?(xpath('link', locator, scope))
202
208
  end
203
209
 
204
210
 
@@ -206,15 +212,34 @@ module Rsel
206
212
  #
207
213
  # @param [String] locator
208
214
  # Text, value, or id of the button
215
+ # @param [Hash] scope
216
+ # Scoping keywords as understood by {#xpath}
209
217
  #
210
218
  # @example
211
219
  # | Button | Search | exists |
212
- # | Button exists | Search |
220
+ # | Button | Search | exists | !{within:members} |
213
221
  #
214
222
  # @since 0.0.2
215
223
  #
216
- def button_exists(locator)
217
- return @browser.element?(xpath('button', locator))
224
+ def button_exists(locator, scope={})
225
+ return @browser.element?(xpath('button', locator, scope))
226
+ end
227
+
228
+
229
+ # Ensure that a table row with the given cell values exists.
230
+ #
231
+ # @param [String] cells
232
+ # Comma-separated cell values you expect to see
233
+ #
234
+ # @example
235
+ # | Row exists | First, Middle, Last, Email |
236
+ # | Row | First, Middle, Last, Email | exists |
237
+ #
238
+ # @since 0.0.3
239
+ #
240
+ def row_exists(cells)
241
+ row = XPath.descendant(:tr)[XPath::HTML.table_row(cells.split(/, */))]
242
+ return @browser.element?("xpath=#{row.to_s}")
218
243
  end
219
244
 
220
245
 
@@ -224,14 +249,16 @@ module Rsel
224
249
  # What to type into the field
225
250
  # @param [String] locator
226
251
  # Label, name, or id of the field you want to type into
252
+ # @param [Hash] scope
253
+ # Scoping keywords as understood by {#xpath}
227
254
  #
228
255
  # @example
229
- # | Type | Dale | into field | First name |
230
256
  # | Type | Dale | into | First name | field |
257
+ # | Type | Dale | into | First name | field | !{within:contact} |
231
258
  #
232
- def type_into_field(text, locator)
259
+ def type_into_field(text, locator, scope={})
233
260
  return_error_status do
234
- @browser.type(xpath('field', locator), text)
261
+ @browser.type(xpath('field', locator, scope), text)
235
262
  end
236
263
  end
237
264
 
@@ -304,15 +331,17 @@ module Rsel
304
331
  #
305
332
  # @param [String] locator
306
333
  # Text or id of the link, or image alt text
334
+ # @param [Hash] scope
335
+ # Scoping keywords as understood by {#xpath}
307
336
  #
308
337
  # @example
309
338
  # | Click | Logout | link |
310
- # | Click link | Logout |
311
339
  # | Follow | Logout |
340
+ # | Click | Logout | link | !{within:header} |
312
341
  #
313
- def click_link(locator)
342
+ def click_link(locator, scope={})
314
343
  return_error_status do
315
- @browser.click(xpath('link', locator))
344
+ @browser.click(xpath('link', locator, scope))
316
345
  end
317
346
  end
318
347
  alias_method :follow, :click_link
@@ -322,49 +351,59 @@ module Rsel
322
351
  #
323
352
  # @param [String] locator
324
353
  # Text, value, or id of the button
354
+ # @param [Hash] scope
355
+ # Scoping keywords as understood by {#xpath}
325
356
  #
326
357
  # @example
327
358
  # | Click | Search | button |
328
- # | Click button | Search |
329
359
  # | Press | Login |
360
+ # | Click | Search | button | !{within:customers} |
330
361
  #
331
- def click_button(locator)
362
+ def click_button(locator, scope={})
332
363
  # TODO: Make this fail when the button is disabled
333
364
  return_error_status do
334
- @browser.click(xpath('button', locator))
365
+ @browser.click(xpath('button', locator, scope))
335
366
  end
336
367
  end
337
368
  alias_method :press, :click_button
338
369
 
339
370
 
340
- # Enable (check) a checkbox.
371
+ # Enable (check) a checkbox by clicking on it.
372
+ # If the checkbox is already enabled, do nothing.
341
373
  #
342
374
  # @param [String] locator
343
375
  # Label, value, or id of the checkbox to check
376
+ # @param [Hash] scope
377
+ # Scoping keywords as understood by {#xpath}
344
378
  #
345
379
  # @example
346
380
  # | Enable | Send me spam | checkbox |
347
- # | Enable checkbox | Send me spam |
381
+ # | Enable | Send me spam | checkbox | !{within:opt_in} |
348
382
  #
349
- def enable_checkbox(locator)
383
+ def enable_checkbox(locator, scope={})
384
+ return true if checkbox_is_enabled(locator, scope)
350
385
  return_error_status do
351
- @browser.check(xpath('checkbox', locator))
386
+ @browser.click(xpath('checkbox', locator, scope))
352
387
  end
353
388
  end
354
389
 
355
390
 
356
- # Disable (uncheck) a checkbox.
391
+ # Disable (uncheck) a checkbox by clicking on it.
392
+ # If the checkbox is already disabled, do nothing.
357
393
  #
358
394
  # @param [String] locator
359
395
  # Label, value, or id of the checkbox to uncheck
396
+ # @param [Hash] scope
397
+ # Scoping keywords as understood by {#xpath}
360
398
  #
361
399
  # @example
362
400
  # | Disable | Send me spam | checkbox |
363
- # | Disable checkbox | Send me spam |
401
+ # | Disable | Send me spam | checkbox | !{within:opt_in} |
364
402
  #
365
- def disable_checkbox(locator)
403
+ def disable_checkbox(locator, scope={})
404
+ return true if checkbox_is_disabled(locator, scope)
366
405
  return_error_status do
367
- @browser.uncheck(xpath('checkbox', locator))
406
+ @browser.click(xpath('checkbox', locator, scope))
368
407
  end
369
408
  end
370
409
 
@@ -373,16 +412,19 @@ module Rsel
373
412
  #
374
413
  # @param [String] locator
375
414
  # Label, value, or id of the checkbox to inspect
415
+ # @param [Hash] scope
416
+ # Scoping keywords as understood by {#xpath}
376
417
  #
377
418
  # @example
378
- # | Checkbox is enabled | send me spam |
379
419
  # | Checkbox | send me spam | is enabled |
380
- # | Radio is enabled | medium |
381
420
  # | Radio | medium | is enabled |
421
+ # | Checkbox | send me spam | is enabled | !{within:opt_in} |
422
+ # | Radio | medium | is enabled | !{within:shirt_size} |
382
423
  #
383
- def checkbox_is_enabled(locator)
424
+ def checkbox_is_enabled(locator, scope={})
425
+ xp = xpath('checkbox', locator, scope)
384
426
  begin
385
- enabled = @browser.checked?(xpath('checkbox', locator))
427
+ enabled = @browser.checked?(xp)
386
428
  rescue
387
429
  return false
388
430
  else
@@ -396,16 +438,19 @@ module Rsel
396
438
  #
397
439
  # @param [String] locator
398
440
  # Label, value, or id of the checkbox to inspect
441
+ # @param [Hash] scope
442
+ # Scoping keywords as understood by {#xpath}
399
443
  #
400
444
  # @example
401
- # | Checkbox is disabled | send me spam |
402
445
  # | Checkbox | send me spam | is disabled |
403
- # | Radio is disabled | medium |
404
446
  # | Radio | medium | is disabled |
447
+ # | Checkbox | send me spam | is disabled | !{within:opt_in} |
448
+ # | Radio | medium | is disabled | !{within:shirt_size} |
405
449
  #
406
- def checkbox_is_disabled(locator)
450
+ def checkbox_is_disabled(locator, scope={})
451
+ xp = xpath('checkbox', locator, scope)
407
452
  begin
408
- enabled = @browser.checked?(xpath('checkbox', locator))
453
+ enabled = @browser.checked?(xp)
409
454
  rescue
410
455
  return false
411
456
  else
@@ -419,14 +464,16 @@ module Rsel
419
464
  #
420
465
  # @param [String] locator
421
466
  # Label, id, or name of the radio button to select
467
+ # @param [Hash] scope
468
+ # Scoping keywords as understood by {#xpath}
422
469
  #
423
470
  # @example
424
471
  # | Select | female | radio |
425
- # | Select radio | female |
472
+ # | Select | female | radio | !{within:gender} |
426
473
  #
427
- def select_radio(locator)
474
+ def select_radio(locator, scope={})
428
475
  return_error_status do
429
- @browser.click(xpath('radio_button', locator))
476
+ @browser.click(xpath('radio_button', locator, scope))
430
477
  end
431
478
  end
432
479
 
@@ -442,9 +489,9 @@ module Rsel
442
489
  # | Select | Tall | from | Height | dropdown |
443
490
  # | Select | Tall | from dropdown | Height |
444
491
  #
445
- def select_from_dropdown(option, locator)
492
+ def select_from_dropdown(option, locator, scope={})
446
493
  return_error_status do
447
- @browser.select(xpath('select', locator), option)
494
+ @browser.select(xpath('select', locator, scope), option)
448
495
  end
449
496
  end
450
497
 
@@ -464,7 +511,7 @@ module Rsel
464
511
  def dropdown_includes(locator, option)
465
512
  dropdown = XPath::HTML.select(locator)
466
513
  opt = dropdown[XPath::HTML.option(option)]
467
- opt_str = opt.to_xpaths.join(' | ')
514
+ opt_str = opt.to_s
468
515
  return @browser.element?("xpath=#{opt_str}")
469
516
  end
470
517
 
@@ -504,6 +551,7 @@ module Rsel
504
551
  sleep seconds.to_i
505
552
  return true
506
553
  end
554
+ alias_method :pause_secs, :pause_seconds
507
555
 
508
556
 
509
557
  # Wait some number of seconds for the current page request to finish.
@@ -517,7 +565,7 @@ module Rsel
517
565
  #
518
566
  def page_loads_in_seconds_or_less(seconds)
519
567
  return_error_status do
520
- @browser.wait_for_page_to_load("#{seconds}000")
568
+ @browser.wait_for_page_to_load(seconds)
521
569
  end
522
570
  end
523
571
 
@@ -534,7 +582,6 @@ module Rsel
534
582
  begin
535
583
  yield
536
584
  rescue => e
537
- #puts e.message
538
585
  #puts e.backtrace
539
586
  return false
540
587
  else
@@ -543,24 +590,62 @@ module Rsel
543
590
  end
544
591
 
545
592
 
546
- # Return a Selenium-style xpath for the given locator.
593
+ # Return a Selenium-style xpath generated by calling `XPath::HTML.<kind>`
594
+ # with the given `locator`.
547
595
  #
548
596
  # @param [String] kind
549
597
  # What kind of locator you're using (link, button, checkbox, field etc.).
550
598
  # This must correspond to a method name in `XPath::HTML`.
551
599
  # @param [String] locator
552
- # Name, id, value, label or whatever locator the `XPath::HTML.<kind>`
553
- # method accepts.
600
+ # Name, id, value, label or whatever other locators are accepted by
601
+ # `XPath::HTML.<kind>`
602
+ # @param [Hash] scope
603
+ # Keywords to restrict the scope of matching elements
604
+ # @option scope [String] :within
605
+ # Restrict scope to elements having this id, matching `locator` only if
606
+ # it's contained within an element with this id.
554
607
  #
555
608
  # @example
556
609
  # xpath('link', 'Log in')
557
610
  # xpath('button', 'Submit')
558
611
  # xpath('field', 'First name')
612
+ # xpath('table_row', ['First', 'Last'])
613
+ #
614
+ def xpath(kind, locator, scope={})
615
+ loc_xp = XPath::HTML.send(kind, locator)
616
+ if scope[:within]
617
+ parent = XPath.descendant[XPath.attr(:id).equals(scope[:within])]
618
+ # Prepend the scoping clause to each expression in loc_xp,
619
+ # then recombine into a union again
620
+ scoped_expressions = xpath_expressions(loc_xp).collect do |expr|
621
+ parent.child(expr)
622
+ end
623
+ result = XPath::Union.new(*scoped_expressions).to_s
624
+ else
625
+ result = loc_xp.to_s
626
+ end
627
+ return "xpath=#{result}"
628
+ end
629
+
630
+
631
+ # Return an array of individual Expressions in the given XPath::Union, or
632
+ # just `[union]` if it has no sub-expressions. This is an ugly recursive
633
+ # hack, designed to allow splitting up unions into their constituents for
634
+ # the purpose of modifying them individually and re-combining them.
559
635
  #
560
- def xpath(kind, locator)
561
- # Doing explicit string-join here, so it'll work with older versions
562
- # of the XPath module that don't have `Union#to_s` defined.
563
- "xpath=" + XPath::HTML.send(kind, locator).to_xpaths.join(' | ')
636
+ # @param [XPath::Union, XPath::Expression] union
637
+ # The xpath you want to break down into individual expressions
638
+ #
639
+ # @since 0.0.3
640
+ #
641
+ def xpath_expressions(union)
642
+ if union.respond_to?(:expressions)
643
+ return union.expressions.collect do |expr|
644
+ xpath_expressions(expr)
645
+ end.flatten
646
+ else
647
+ return [union]
648
+ end
564
649
  end
565
650
 
566
651
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = "rsel"
3
- s.version = "0.0.2"
3
+ s.version = "0.0.3"
4
4
  s.summary = "Runs Selenium tests from FitNesse"
5
5
  s.description = <<-EOS
6
6
  Rsel provides a Slim fixture for running Selenium tests, with
@@ -12,7 +12,7 @@ Gem::Specification.new do |s|
12
12
  s.platform = Gem::Platform::RUBY
13
13
 
14
14
  s.add_dependency 'rubyslim-unofficial'
15
- s.add_dependency 'xpath'
15
+ s.add_dependency 'xpath', '>= 0.1.4'
16
16
  s.add_dependency 'selenium-client'
17
17
 
18
18
  s.add_development_dependency 'rake', '0.8.7'
@@ -20,72 +20,121 @@ describe Rsel::SeleniumTest do
20
20
  end
21
21
 
22
22
  describe "#enable_checkbox" do
23
- context "passes when" do
24
- it "checkbox with the given label is present" do
25
- @st.enable_checkbox("I like cheese").should be_true
26
- @st.enable_checkbox("I like salami").should be_true
23
+ context "checkbox with label" do
24
+ context "passes when" do
25
+ it "is present" do
26
+ @st.enable_checkbox("I like cheese").should be_true
27
+ @st.enable_checkbox("I like salami").should be_true
28
+ end
29
+
30
+ it "is present within scope" do
31
+ @st.enable_checkbox("I like cheese", :within => "cheese_checkbox").should be_true
32
+ @st.enable_checkbox("I like salami", :within => "salami_checkbox").should be_true
33
+ end
27
34
  end
28
- end
29
35
 
30
- context "fails when" do
31
- it "checkbox with the given label is absent" do
32
- @st.enable_checkbox("I dislike bacon").should be_false
33
- @st.enable_checkbox("I like broccoli").should be_false
36
+ context "fails when" do
37
+ it "is absent" do
38
+ @st.enable_checkbox("I dislike bacon").should be_false
39
+ @st.enable_checkbox("I like broccoli").should be_false
40
+ end
41
+
42
+ it "exists, but not within scope" do
43
+ @st.enable_checkbox("I like cheese", :within => "salami_checkbox").should be_false
44
+ @st.enable_checkbox("I like salami", :within => "cheese_checkbox").should be_false
45
+ end
34
46
  end
35
47
  end
36
48
  end
37
49
 
38
50
  describe "#disable_checkbox" do
39
- context "passes when" do
40
- it "checkbox with the given label is present" do
41
- @st.disable_checkbox("I like cheese").should be_true
42
- @st.disable_checkbox("I like salami").should be_true
51
+ context "checkbox with label" do
52
+ context "passes when" do
53
+ it "is present" do
54
+ @st.disable_checkbox("I like cheese").should be_true
55
+ @st.disable_checkbox("I like salami").should be_true
56
+ end
57
+
58
+ it "is present within scope" do
59
+ @st.disable_checkbox("I like cheese", :within => "cheese_checkbox").should be_true
60
+ @st.disable_checkbox("I like salami", :within => "preferences_form").should be_true
61
+ end
43
62
  end
44
- end
45
- context "fails when" do
46
- it "checkbox with the given label is absent" do
47
- @st.disable_checkbox("I dislike bacon").should be_false
48
- @st.disable_checkbox("I like broccoli").should be_false
63
+
64
+ context "fails when" do
65
+ it "is absent" do
66
+ @st.disable_checkbox("I dislike bacon").should be_false
67
+ @st.disable_checkbox("I like broccoli").should be_false
68
+ end
69
+
70
+ it "exists, but not within scope" do
71
+ @st.disable_checkbox("I like cheese", :within => "salami_checkbox").should be_false
72
+ @st.disable_checkbox("I like salami", :within => "cheese_checkbox").should be_false
73
+ end
49
74
  end
50
75
  end
51
76
  end
52
77
 
53
78
  describe "#checkbox_is_enabled" do
54
- context "passes when" do
55
- it "checkbox with the given label exists and is checked" do
56
- @st.enable_checkbox("I like cheese").should be_true
57
- @st.checkbox_is_enabled("I like cheese").should be_true
58
- end
59
- end
79
+ context "checkbox with label" do
80
+ context "passes when" do
81
+ it "exists and is checked" do
82
+ @st.enable_checkbox("I like cheese").should be_true
83
+ @st.checkbox_is_enabled("I like cheese").should be_true
84
+ end
60
85
 
61
- context "fails when" do
62
- it "checkbox with the given label exists but is unchecked" do
63
- @st.disable_checkbox("I like cheese").should be_true
64
- @st.checkbox_is_enabled("I like cheese").should be_false
86
+ it "exists within scope and is checked" do
87
+ @st.enable_checkbox("I like cheese", :within => "cheese_checkbox").should be_true
88
+ @st.checkbox_is_enabled("I like cheese", :within => "cheese_checkbox").should be_true
89
+ end
65
90
  end
66
91
 
67
- it "checkbox with the given label does not exist" do
68
- @st.checkbox_is_enabled("I dislike bacon").should be_false
92
+ context "fails when" do
93
+ it "exists but is unchecked" do
94
+ @st.disable_checkbox("I like cheese").should be_true
95
+ @st.checkbox_is_enabled("I like cheese").should be_false
96
+ end
97
+
98
+ it "exists and is checked, but not within scope" do
99
+ @st.enable_checkbox("I like cheese", :within => "cheese_checkbox").should be_true
100
+ @st.checkbox_is_enabled("I like cheese", :within => "salami_checkbox").should be_false
101
+ end
102
+
103
+ it "does not exist" do
104
+ @st.checkbox_is_enabled("I dislike bacon").should be_false
105
+ end
69
106
  end
70
107
  end
71
108
  end
72
109
 
73
110
  describe "#checkbox_is_disabled" do
74
- context "passes when" do
75
- it "checkbox with the given label exists and is unchecked" do
76
- @st.disable_checkbox("I like cheese").should be_true
77
- @st.checkbox_is_disabled("I like cheese").should be_true
78
- end
79
- end
111
+ context "checkbox with label" do
112
+ context "passes when" do
113
+ it "exists and is unchecked" do
114
+ @st.disable_checkbox("I like cheese").should be_true
115
+ @st.checkbox_is_disabled("I like cheese").should be_true
116
+ end
80
117
 
81
- context "fails when" do
82
- it "checkbox with the given label exists but is checked" do
83
- @st.enable_checkbox("I like cheese").should be_true
84
- @st.checkbox_is_disabled("I like cheese").should be_false
118
+ it "exists within scope and is unchecked" do
119
+ @st.disable_checkbox("I like cheese", :within => "cheese_checkbox").should be_true
120
+ @st.checkbox_is_disabled("I like cheese", :within => "cheese_checkbox").should be_true
121
+ end
85
122
  end
86
123
 
87
- it "checkbox with the given label does not exist" do
88
- @st.checkbox_is_disabled("I dislike bacon").should be_false
124
+ context "fails when" do
125
+ it "exists but is checked" do
126
+ @st.enable_checkbox("I like cheese").should be_true
127
+ @st.checkbox_is_disabled("I like cheese").should be_false
128
+ end
129
+
130
+ it "exists and is unchecked, but not within scope" do
131
+ @st.disable_checkbox("I like cheese", :within => "cheese_checkbox").should be_true
132
+ @st.checkbox_is_disabled("I like cheese", :within => "salami_checkbox").should be_false
133
+ end
134
+
135
+ it "does not exist" do
136
+ @st.checkbox_is_disabled("I dislike bacon").should be_false
137
+ end
89
138
  end
90
139
  end
91
140
  end
@@ -103,6 +152,10 @@ describe Rsel::SeleniumTest do
103
152
  @st.select_from_dropdown("Tall", "Height").should be_true
104
153
  @st.select_from_dropdown("Medium", "Weight").should be_true
105
154
  end
155
+
156
+ it "option exists in the dropdown within scope" do
157
+ @st.select_from_dropdown("Tall", "Height", :within => "spouse_form").should be_true
158
+ end
106
159
  end
107
160
 
108
161
  context "fails when" do
@@ -111,6 +164,10 @@ describe Rsel::SeleniumTest do
111
164
  @st.select_from_dropdown("Obese", "Weight").should be_false
112
165
  end
113
166
 
167
+ it "dropdown exists, but not within scope" do
168
+ @st.select_from_dropdown("Medium", "Weight", :within => "spouse_form").should be_false
169
+ end
170
+
114
171
  it "no such dropdown exists" do
115
172
  @st.select_from_dropdown("Over easy", "Eggs").should be_false
116
173
  end
@@ -219,14 +276,24 @@ describe Rsel::SeleniumTest do
219
276
  end
220
277
 
221
278
  describe "#click_link" do
222
- it "passes and loads the correct page when a link exists" do
223
- @st.click_link("About this site").should be_true
224
- @st.see_title("About this site").should be_true
225
- @st.see("This site is really cool").should be_true
279
+ context "passes when" do
280
+ it "link exists" do
281
+ @st.click_link("About this site").should be_true
282
+ end
283
+
284
+ it "link exists within scope" do
285
+ @st.click_link("About this site", :within => "header").should be_true
286
+ end
226
287
  end
227
288
 
228
- it "fails when a link does not exist" do
229
- @st.click_link("Bogus link").should be_false
289
+ context "fails when" do
290
+ it "link does not exist" do
291
+ @st.click_link("Bogus link").should be_false
292
+ end
293
+
294
+ it "link exists, but not within scope" do
295
+ @st.click_link("About this site", :within => "footer").should be_false
296
+ end
230
297
  end
231
298
  end
232
299
 
@@ -236,6 +303,12 @@ describe Rsel::SeleniumTest do
236
303
  @st.link_exists("About this site").should be_true
237
304
  @st.link_exists("Form test").should be_true
238
305
  end
306
+
307
+ it "link with the given text exists within scope" do
308
+ @st.link_exists("About this site", :within => "header").should be_true
309
+ @st.link_exists("Form test", :within => "footer").should be_true
310
+ @st.link_exists("Table test", :within => "footer").should be_true
311
+ end
239
312
  end
240
313
 
241
314
  context "fails when" do
@@ -243,6 +316,12 @@ describe Rsel::SeleniumTest do
243
316
  @st.link_exists("Welcome").should be_false
244
317
  @st.link_exists("Don't click here").should be_false
245
318
  end
319
+
320
+ it "link exists, but not within scope" do
321
+ @st.link_exists("About this site", :within => "footer").should be_false
322
+ @st.link_exists("Form test", :within => "header").should be_false
323
+ @st.link_exists("Table test", :within => "header").should be_false
324
+ end
246
325
  end
247
326
  end
248
327
  end
@@ -258,6 +337,10 @@ describe Rsel::SeleniumTest do
258
337
  it "button exists and is enabled" do
259
338
  @st.click_button("Submit person form").should be_true
260
339
  end
340
+
341
+ it "button exists within scope" do
342
+ @st.click_button("Submit person form", :within => "person_form").should be_true
343
+ end
261
344
  end
262
345
 
263
346
  context "fails when" do
@@ -265,6 +348,10 @@ describe Rsel::SeleniumTest do
265
348
  @st.click_button("No such button").should be_false
266
349
  end
267
350
 
351
+ it "button exists, but not within scope" do
352
+ @st.click_button("Submit person form", :within => "spouse_form").should be_false
353
+ end
354
+
268
355
  it "button exists but is disabled" do
269
356
  # TODO
270
357
  end
@@ -277,6 +364,11 @@ describe Rsel::SeleniumTest do
277
364
  @st.button_exists("Submit person form").should be_true
278
365
  @st.button_exists("Save preferences").should be_true
279
366
  end
367
+
368
+ it "button with the given text exists within scope" do
369
+ @st.button_exists("Submit person form", :within => "person_form").should be_true
370
+ @st.button_exists("Submit spouse form", :within => "spouse_form").should be_true
371
+ end
280
372
  end
281
373
 
282
374
  context "fails when" do
@@ -284,6 +376,11 @@ describe Rsel::SeleniumTest do
284
376
  @st.button_exists("Apple").should be_false
285
377
  @st.button_exists("Big Red").should be_false
286
378
  end
379
+
380
+ it "button exists, but not within scope" do
381
+ @st.button_exists("Submit spouse form", :within => "person_form").should be_false
382
+ @st.button_exists("Submit person form", :within => "spouse_form").should be_false
383
+ end
287
384
  end
288
385
  end
289
386
  end
@@ -315,6 +412,11 @@ describe Rsel::SeleniumTest do
315
412
  @st.type_into_field("Blah blah blah", "biography").should be_true
316
413
  @st.fill_in_with("biography", "Jibber jabber").should be_true
317
414
  end
415
+
416
+ it "text field with the given label exists within scope" do
417
+ @st.type_into_field("Eric", "First name", :within => 'person_form').should be_true
418
+ @st.type_into_field("Andrea", "First name", :within => 'spouse_form').should be_true
419
+ end
318
420
  end
319
421
 
320
422
  context "fails when" do
@@ -322,6 +424,10 @@ describe Rsel::SeleniumTest do
322
424
  @st.type_into_field("Matthew", "Middle name").should be_false
323
425
  @st.fill_in_with("middle_name", "Matthew").should be_false
324
426
  end
427
+
428
+ it "field exists, but not within scope" do
429
+ @st.type_into_field("Long story", "Life story", :within => 'spouse_form').should be_false
430
+ end
325
431
  end
326
432
  end
327
433
 
@@ -407,5 +513,44 @@ describe Rsel::SeleniumTest do
407
513
  end
408
514
  end
409
515
 
516
+
517
+ context "tables" do
518
+ before(:each) do
519
+ @st.visit("/table").should be_true
520
+ end
521
+
522
+ describe "#row_exists" do
523
+ context "passes when" do
524
+ it "full row of headings exists" do
525
+ @st.row_exists("First name, Last name, Nickname, Email").should be_true
526
+ end
527
+
528
+ it "partial row of headings exists" do
529
+ @st.row_exists("First name, Last name").should be_true
530
+ @st.row_exists("Nickname, Email").should be_true
531
+ end
532
+
533
+ it "full row of cells exists" do
534
+ @st.row_exists("Eric, Pierce, epierce, epierce@example.com").should be_true
535
+ end
536
+
537
+ it "partial row of cells exists" do
538
+ @st.row_exists("Eric, Pierce").should be_true
539
+ @st.row_exists("epierce, epierce@example.com").should be_true
540
+ end
541
+ end
542
+
543
+ context "fails when" do
544
+ it "no row exists" do
545
+ @st.row_exists("Middle name, Maiden name, Email").should be_false
546
+ end
547
+
548
+ it "cell values are not consecutive" do
549
+ @st.row_exists("First name, Email").should be_false
550
+ @st.row_exists("Eric, epierce").should be_false
551
+ end
552
+ end
553
+ end
554
+ end
410
555
  end
411
556
 
@@ -21,6 +21,10 @@ class TestApp < Sinatra::Base
21
21
  erb :form
22
22
  end
23
23
 
24
+ get '/table' do
25
+ erb :table
26
+ end
27
+
24
28
  get '/thanks' do
25
29
  erb :thanks
26
30
  end
@@ -21,8 +21,8 @@
21
21
  </textarea>
22
22
  </p>
23
23
  <p>
24
- <label for="height">Height</label>
25
- <select id="height">
24
+ <label for="person_height">Height</label>
25
+ <select id="person_height">
26
26
  <option value="short">Short</option>
27
27
  <option value="average" selected="selected">Average</option>
28
28
  <option value="tall" >Tall</option>
@@ -40,6 +40,28 @@
40
40
  </form>
41
41
  </div>
42
42
 
43
+ <div id="spouse_form">
44
+ <form action="/thanks" method="get">
45
+ <p>
46
+ <label for="spouse_first_name">First name</label>
47
+ <input id="spouse_first_name" type="text" />
48
+ </p>
49
+ <p>
50
+ <label for="spouse_last_name">Last name</label>
51
+ <input id="spouse_last_name" type="text" />
52
+ </p>
53
+ <p>
54
+ <label for="spouse_height">Height</label>
55
+ <select id="spouse_height">
56
+ <option value="short">Short</option>
57
+ <option value="average" selected="selected">Average</option>
58
+ <option value="tall" >Tall</option>
59
+ </select>
60
+ </p>
61
+ <p><button value="submit_spouse_form">Submit spouse form</button></p>
62
+ </form>
63
+ </div>
64
+
43
65
  <div id="preferences_form">
44
66
  <form action="/thanks" method="get">
45
67
  <p>
@@ -50,11 +72,11 @@
50
72
  <option value="b">Blue</option>
51
73
  </select>
52
74
  </p>
53
- <p>
75
+ <p id="cheese_checkbox">
54
76
  <label for="like_cheese">I like cheese</label>
55
77
  <input id="like_cheese" type="checkbox" checked="checked"/>
56
78
  </p>
57
- <p>
79
+ <p id="salami_checkbox">
58
80
  <label for="like_salami">I like salami</label>
59
81
  <input id="like_salami" type="checkbox" />
60
82
  </p>
@@ -5,9 +5,17 @@
5
5
  <h1>Welcome</h1>
6
6
  <p>This is a Sinatra webapp for unit testing Rsel.</p>
7
7
 
8
- <div id="links">
9
- <a href="/about">About this site</a>
10
- <a href="/form">Form test</a>
8
+ <div id="header">
9
+ <ul>
10
+ <li><a href="/about">About this site</a></li>
11
+ </ul>
12
+ </div>
13
+
14
+ <div id="footer">
15
+ <ul>
16
+ <li><a href="/form">Form test</a></li>
17
+ <li><a href="/table">Table test</a></li>
18
+ </ul>
11
19
  </div>
12
20
 
13
21
  </body>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head><title>Rsel Table test</title></head>
4
+ <body>
5
+ <h1>Tables</h1>
6
+
7
+ <table id="contact">
8
+ <tr>
9
+ <th>First name</th>
10
+ <th>Last name</th>
11
+ <th>Nickname</th>
12
+ <th>Email</th>
13
+ </tr>
14
+ <tr>
15
+ <td>Eric</td>
16
+ <td>Pierce</td>
17
+ <td>epierce</td>
18
+ <td>epierce@example.com</td>
19
+ </tr>
20
+ </table>
21
+
22
+ </body>
23
+ </html>
24
+
25
+
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rsel
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Marcus French
@@ -17,7 +17,7 @@ autorequire:
17
17
  bindir: bin
18
18
  cert_chain: []
19
19
 
20
- date: 2011-07-21 00:00:00 -06:00
20
+ date: 2011-07-23 00:00:00 -06:00
21
21
  default_executable:
22
22
  dependencies:
23
23
  - !ruby/object:Gem::Dependency
@@ -42,10 +42,12 @@ dependencies:
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- hash: 3
45
+ hash: 19
46
46
  segments:
47
47
  - 0
48
- version: "0"
48
+ - 1
49
+ - 4
50
+ version: 0.1.4
49
51
  type: :runtime
50
52
  version_requirements: *id002
51
53
  - !ruby/object:Gem::Dependency
@@ -156,6 +158,7 @@ files:
156
158
  - docs/history.md
157
159
  - docs/index.md
158
160
  - docs/install.md
161
+ - docs/scoping.md
159
162
  - docs/todo.md
160
163
  - docs/usage.md
161
164
  - lib/rsel.rb
@@ -169,6 +172,7 @@ files:
169
172
  - test/views/about.erb
170
173
  - test/views/form.erb
171
174
  - test/views/index.erb
175
+ - test/views/table.erb
172
176
  - test/views/thanks.erb
173
177
  - vendor/rubyslim-0.1.1/.gitignore
174
178
  - vendor/rubyslim-0.1.1/README.md