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.
- data/docs/history.md +11 -0
- data/docs/index.md +1 -0
- data/docs/scoping.md +72 -0
- data/docs/usage.md +1 -1
- data/lib/rsel/selenium_test.rb +131 -46
- data/rsel.gemspec +2 -2
- data/spec/selenium_test_spec.rb +193 -48
- data/test/app.rb +4 -0
- data/test/views/form.erb +26 -4
- data/test/views/index.erb +11 -3
- data/test/views/table.erb +25 -0
- metadata +10 -6
data/docs/history.md
CHANGED
@@ -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
|
|
data/docs/index.md
CHANGED
data/docs/scoping.md
ADDED
@@ -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
|
+
|
data/docs/usage.md
CHANGED
data/lib/rsel/selenium_test.rb
CHANGED
@@ -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 |
|
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 |
|
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
|
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.
|
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
|
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.
|
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?(
|
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?(
|
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 |
|
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.
|
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(
|
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
|
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
|
553
|
-
#
|
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
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
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
|
data/rsel.gemspec
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = "rsel"
|
3
|
-
s.version = "0.0.
|
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'
|
data/spec/selenium_test_spec.rb
CHANGED
@@ -20,72 +20,121 @@ describe Rsel::SeleniumTest do
|
|
20
20
|
end
|
21
21
|
|
22
22
|
describe "#enable_checkbox" do
|
23
|
-
context "
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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 "
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
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 "
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
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
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
68
|
-
|
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 "
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
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
|
-
|
88
|
-
|
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
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
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
|
-
|
229
|
-
|
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
|
|
data/test/app.rb
CHANGED
data/test/views/form.erb
CHANGED
@@ -21,8 +21,8 @@
|
|
21
21
|
</textarea>
|
22
22
|
</p>
|
23
23
|
<p>
|
24
|
-
<label for="
|
25
|
-
<select id="
|
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>
|
data/test/views/index.erb
CHANGED
@@ -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="
|
9
|
-
<
|
10
|
-
|
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:
|
4
|
+
hash: 25
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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-
|
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:
|
45
|
+
hash: 19
|
46
46
|
segments:
|
47
47
|
- 0
|
48
|
-
|
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
|