rsel 0.0.2 → 0.0.3

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.
@@ -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