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