kelp 0.1.9 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +1 -1
- data/History.md +10 -0
- data/examples/sinatra_app/features/checkbox_fail.feature +11 -0
- data/examples/sinatra_app/features/dropdown.feature +4 -4
- data/examples/sinatra_app/features/dropdown_fail.feature +28 -0
- data/examples/sinatra_app/features/field.feature +57 -0
- data/examples/sinatra_app/features/field_fail.feature +31 -0
- data/examples/sinatra_app/features/visibility.feature +96 -6
- data/examples/sinatra_app/features/visibility_fail.feature +30 -2
- data/examples/sinatra_app/views/form.erb +14 -0
- data/features/kelp_step_definitions.feature +101 -12
- data/features/step_definitions/app_steps.rb +24 -8
- data/kelp.gemspec +1 -1
- data/lib/kelp.rb +0 -15
- data/lib/kelp/attribute.rb +26 -4
- data/lib/kelp/checkbox.rb +33 -8
- data/lib/kelp/dropdown.rb +37 -11
- data/lib/kelp/exceptions.rb +4 -2
- data/lib/kelp/field.rb +80 -41
- data/lib/kelp/helper.rb +8 -1
- data/lib/kelp/navigation.rb +14 -8
- data/lib/kelp/scoping.rb +1 -3
- data/lib/kelp/visibility.rb +152 -167
- data/rails_generators/kelp/templates/web_steps.rb +2 -2
- data/spec/attribute_spec.rb +4 -4
- data/spec/checkbox_spec.rb +4 -4
- data/spec/dropdown_spec.rb +10 -10
- data/spec/field_spec.rb +15 -15
- data/spec/navigation_spec.rb +2 -2
- data/spec/spec_helper.rb +0 -9
- data/spec/visibility_spec.rb +40 -103
- metadata +8 -5
@@ -24,14 +24,30 @@ When /^I run cucumber on "(.+)"$/ do |feature|
|
|
24
24
|
end
|
25
25
|
|
26
26
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
27
|
+
# Ensure that the results include the expected lines, in the same order.
|
28
|
+
# Extra lines in the results are ignored.
|
29
|
+
Then /^the results should include:$/ do |expected_lines|
|
30
|
+
# Full actual output, for error reporting
|
31
|
+
got = @output.collect {|line| " #{line}"}.join
|
32
|
+
# Remove leading whitespace and split into lines
|
33
|
+
got_lines = @output.gsub(/^\s*/, '').split("\n")
|
34
|
+
want_lines = expected_lines.gsub(/^\s*/, '').split("\n")
|
35
|
+
last_index = -1
|
36
|
+
# Ensure that each wanted line is present in the output,
|
37
|
+
# and that it comes after any preceding expected lines.
|
38
|
+
want_lines.each do |want|
|
39
|
+
if !want.empty?
|
40
|
+
index = got_lines.index(want)
|
41
|
+
# Not found?
|
42
|
+
if index.nil?
|
43
|
+
raise("Got:\n#{got}\nExpected line not found:\n #{want}")
|
44
|
+
# Not in order?
|
45
|
+
elsif index < last_index
|
46
|
+
raise("Got:\n#{got}\nExpected line found, but out of order:\n #{want}")
|
47
|
+
else
|
48
|
+
last_index = index
|
49
|
+
end
|
50
|
+
end
|
35
51
|
end
|
36
52
|
end
|
37
53
|
|
data/kelp.gemspec
CHANGED
data/lib/kelp.rb
CHANGED
@@ -7,20 +7,5 @@ require 'kelp/scoping'
|
|
7
7
|
require 'kelp/visibility'
|
8
8
|
|
9
9
|
module Kelp
|
10
|
-
class << self
|
11
|
-
attr_writer :driver
|
12
|
-
|
13
|
-
# @return [Symbol]
|
14
|
-
# Name of the default driver used by Kelp
|
15
|
-
def default_driver
|
16
|
-
:capybara
|
17
|
-
end
|
18
|
-
|
19
|
-
# @return [Symbol]
|
20
|
-
# Name of the current driver used by Kelp (:capybara or :webrat)
|
21
|
-
def driver
|
22
|
-
@driver || default_driver
|
23
|
-
end
|
24
|
-
end
|
25
10
|
end
|
26
11
|
|
data/lib/kelp/attribute.rb
CHANGED
@@ -9,9 +9,20 @@ module Kelp
|
|
9
9
|
# @param [String] element_id
|
10
10
|
# HTML `id` attribute of the element that should be disabled
|
11
11
|
#
|
12
|
+
# @raise [Kelp::Unexpected]
|
13
|
+
# If the given element id is not found, or the element is not disabled
|
14
|
+
#
|
12
15
|
def should_be_disabled(element_id)
|
13
|
-
page.
|
14
|
-
|
16
|
+
if page.has_xpath?("//*[@id='#{element_id}']")
|
17
|
+
if !page.has_xpath?("//*[@id='#{element_id}' and @disabled]")
|
18
|
+
raise Kelp::Unexpected,
|
19
|
+
"Expected element with id='#{element_id}' to be disabled," + \
|
20
|
+
" but the 'disabled' attribute is not present."
|
21
|
+
end
|
22
|
+
else
|
23
|
+
raise Kelp::Unexpected,
|
24
|
+
"Element with id='#{element_id}' not found."
|
25
|
+
end
|
15
26
|
end
|
16
27
|
|
17
28
|
|
@@ -21,9 +32,20 @@ module Kelp
|
|
21
32
|
# @param [String] element_id
|
22
33
|
# HTML `id` attribute of the element that should be enabled
|
23
34
|
#
|
35
|
+
# @raise [Kelp::Unexpected]
|
36
|
+
# If the given element id is not found, or the element is not enabled
|
37
|
+
#
|
24
38
|
def should_be_enabled(element_id)
|
25
|
-
page.
|
26
|
-
|
39
|
+
if page.has_xpath?("//*[@id='#{element_id}']")
|
40
|
+
if page.has_xpath?("//*[@id='#{element_id}' and @disabled]")
|
41
|
+
raise Kelp::Unexpected,
|
42
|
+
"Expected element with id='#{element_id}' to be enabled," + \
|
43
|
+
" but the 'disabled' attribute is present."
|
44
|
+
end
|
45
|
+
else
|
46
|
+
raise Kelp::Unexpected,
|
47
|
+
"Element with id='#{element_id}' not found."
|
48
|
+
end
|
27
49
|
end
|
28
50
|
|
29
51
|
end
|
data/lib/kelp/checkbox.rb
CHANGED
@@ -1,29 +1,54 @@
|
|
1
1
|
require 'kelp/helper'
|
2
2
|
require 'kelp/scoping'
|
3
|
+
require 'kelp/exceptions'
|
3
4
|
|
4
5
|
module Kelp
|
5
6
|
module Checkbox
|
6
7
|
include Scoping
|
7
8
|
include Helper
|
8
9
|
|
10
|
+
# Verify that the given checkbox is checked.
|
11
|
+
#
|
12
|
+
# @param [String] checkbox
|
13
|
+
# Capybara locator for the checkbox
|
14
|
+
# @param [Hash] scope
|
15
|
+
# Scoping keywords as understood by {#in_scope}
|
16
|
+
#
|
17
|
+
# @raise [Kelp::Unexpected]
|
18
|
+
# If the given checkbox is not checked
|
19
|
+
#
|
20
|
+
# @since 0.1.2
|
21
|
+
#
|
9
22
|
def checkbox_should_be_checked(checkbox, scope={})
|
10
23
|
in_scope(scope) do
|
11
24
|
field_checked = find_field(checkbox)['checked']
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
25
|
+
puts field_checked.inspect
|
26
|
+
if !field_checked
|
27
|
+
raise Kelp::Unexpected,
|
28
|
+
"Expected '#{checkbox}' to be checked, but it is unchecked."
|
16
29
|
end
|
17
30
|
end
|
18
31
|
end
|
19
32
|
|
33
|
+
|
34
|
+
# Verify that the given checkbox is not checked.
|
35
|
+
#
|
36
|
+
# @param [String] checkbox
|
37
|
+
# Capybara locator for the checkbox
|
38
|
+
# @param [Hash] scope
|
39
|
+
# Scoping keywords as understood by {#in_scope}
|
40
|
+
#
|
41
|
+
# @raise [Kelp::Unexpected]
|
42
|
+
# If the given checkbox is checked
|
43
|
+
#
|
44
|
+
# @since 0.1.2
|
45
|
+
#
|
20
46
|
def checkbox_should_not_be_checked(checkbox, scope={})
|
21
47
|
in_scope(scope) do
|
22
48
|
field_checked = find_field(checkbox)['checked']
|
23
|
-
if field_checked
|
24
|
-
|
25
|
-
|
26
|
-
assert !field_checked
|
49
|
+
if field_checked
|
50
|
+
raise Kelp::Unexpected,
|
51
|
+
"Expected '#{checkbox}' to be unchecked, but it is checked."
|
27
52
|
end
|
28
53
|
end
|
29
54
|
end
|
data/lib/kelp/dropdown.rb
CHANGED
@@ -22,6 +22,9 @@ module Kelp
|
|
22
22
|
# @param [Hash] scope
|
23
23
|
# Scoping keywords as understood by {#in_scope}
|
24
24
|
#
|
25
|
+
# @raise [Kelp::Unexpected]
|
26
|
+
# If the given dropdown does not equal `value`
|
27
|
+
#
|
25
28
|
def dropdown_should_equal(dropdown, value, scope={})
|
26
29
|
in_scope(scope) do
|
27
30
|
field = nice_find_field(dropdown)
|
@@ -37,7 +40,10 @@ module Kelp
|
|
37
40
|
field_value = xpath_sanitize(field.value)
|
38
41
|
selected = field.find(:xpath, ".//option[@value=#{field_value}]")
|
39
42
|
end
|
40
|
-
selected.text
|
43
|
+
if selected.text != value
|
44
|
+
raise Kelp::Unexpected,
|
45
|
+
"Expected '#{dropdown}' dropdown to equal '#{value}'\nGot '#{selected.text}' instead"
|
46
|
+
end
|
41
47
|
end
|
42
48
|
end
|
43
49
|
|
@@ -57,15 +63,22 @@ module Kelp
|
|
57
63
|
# @param [Hash] scope
|
58
64
|
# Scoping keywords as understood by {#in_scope}
|
59
65
|
#
|
66
|
+
# @raise [Kelp::Unexpected]
|
67
|
+
# If the given dropdown does not include all `values`
|
68
|
+
#
|
60
69
|
def dropdown_should_include(dropdown, values, scope={})
|
61
70
|
in_scope(scope) do
|
62
71
|
# If values is a String, convert it to an Array
|
63
72
|
values = [values] if values.class == String
|
64
|
-
|
65
73
|
field = nice_find_field(dropdown)
|
66
|
-
#
|
67
|
-
values.
|
68
|
-
page.
|
74
|
+
# Select all expected values that don't appear in the dropdown
|
75
|
+
unexpected = values.select do |value|
|
76
|
+
!page.has_xpath?(".//option[text()=#{xpath_sanitize(value)}]")
|
77
|
+
end
|
78
|
+
if !unexpected.empty?
|
79
|
+
raise Kelp::Unexpected,
|
80
|
+
"Expected '#{dropdown}' dropdown to include: #{values.inspect}" + \
|
81
|
+
"\nMissing: #{unexpected.inspect}"
|
69
82
|
end
|
70
83
|
end
|
71
84
|
end
|
@@ -82,17 +95,24 @@ module Kelp
|
|
82
95
|
# @param [Hash] scope
|
83
96
|
# Scoping keywords as understood by {#in_scope}
|
84
97
|
#
|
98
|
+
# @raise [Kelp::Unexpected]
|
99
|
+
# If the given dropdown includes any value in `values`
|
100
|
+
#
|
85
101
|
# @since 0.1.2
|
86
102
|
#
|
87
103
|
def dropdown_should_not_include(dropdown, values, scope={})
|
88
104
|
in_scope(scope) do
|
89
105
|
# If values is a String, convert it to an Array
|
90
106
|
values = [values] if values.class == String
|
91
|
-
|
92
107
|
field = nice_find_field(dropdown)
|
93
|
-
#
|
94
|
-
values.
|
95
|
-
page.
|
108
|
+
# Select all not-expected values that do appear in the dropdown
|
109
|
+
unexpected = values.select do |value|
|
110
|
+
page.has_xpath?(".//option[text()=#{xpath_sanitize(value)}]")
|
111
|
+
end
|
112
|
+
if !unexpected.empty?
|
113
|
+
raise Kelp::Unexpected,
|
114
|
+
"Expected '#{dropdown}' dropdown to not include: #{values.inspect}" + \
|
115
|
+
"\nDid include: #{unexpected.inspect}"
|
96
116
|
end
|
97
117
|
end
|
98
118
|
end
|
@@ -108,10 +128,16 @@ module Kelp
|
|
108
128
|
# @param [String] value
|
109
129
|
# Expected `value` attribute of the selected `option`
|
110
130
|
#
|
131
|
+
# @raise [Kelp::Unexpected]
|
132
|
+
# If the given dropdown's 'value' attribute is not equal to `value`
|
133
|
+
#
|
111
134
|
def dropdown_value_should_equal(dropdown, value)
|
112
|
-
# FIXME: When this returns False, does that fail the step?
|
113
135
|
field = find_field(dropdown)
|
114
|
-
field.value
|
136
|
+
if field.value != value
|
137
|
+
raise Kelp::Unexpected,
|
138
|
+
"Expected '#{dropdown}' dropdown's value to equal '#{value}'" + \
|
139
|
+
"\nGot '#{field.value}' instead"
|
140
|
+
end
|
115
141
|
end
|
116
142
|
|
117
143
|
end
|
data/lib/kelp/exceptions.rb
CHANGED
data/lib/kelp/field.rb
CHANGED
@@ -19,13 +19,35 @@ module Kelp
|
|
19
19
|
# @param [String] value
|
20
20
|
# Text to select from a dropdown or enter in a text box
|
21
21
|
#
|
22
|
+
# @raise [Kelp::FieldNotFound]
|
23
|
+
# If no field matching `field` is found
|
24
|
+
# @raise [Kelp::OptionNotFound]
|
25
|
+
# If a dropdown matching `field` is found, but it has no
|
26
|
+
# option matching `value`
|
27
|
+
#
|
22
28
|
# @since 0.1.9
|
23
29
|
#
|
24
30
|
def select_or_fill(field, value)
|
25
31
|
begin
|
26
32
|
select(value, :from => field)
|
27
|
-
rescue
|
28
|
-
|
33
|
+
rescue Capybara::ElementNotFound => err
|
34
|
+
# The select method may raise ElementNotFound in two cases:
|
35
|
+
# (1) The given field does not exist, in which case try a text field
|
36
|
+
if err.message =~ /no select box with id, name, or label/
|
37
|
+
begin
|
38
|
+
fill_in(field, :with => value)
|
39
|
+
rescue Capybara::ElementNotFound
|
40
|
+
raise Kelp::FieldNotFound,
|
41
|
+
"No field with id, name, or label '#{field}' found"
|
42
|
+
end
|
43
|
+
# (2) The field exists, but the value does not
|
44
|
+
elsif err.message =~ /no option with text/
|
45
|
+
raise Kelp::OptionNotFound,
|
46
|
+
"Field '#{field}' has no option '#{value}'"
|
47
|
+
# And just in case...
|
48
|
+
else
|
49
|
+
raise
|
50
|
+
end
|
29
51
|
end
|
30
52
|
end
|
31
53
|
|
@@ -41,70 +63,77 @@ module Kelp
|
|
41
63
|
# Text to select from a dropdown or enter in a text box, or
|
42
64
|
# `checked` or `unchecked` to operate on a checkbox
|
43
65
|
#
|
66
|
+
# @raise [Kelp::Nonexistent]
|
67
|
+
# If no checkbox, dropdown, or text box is found with the given
|
68
|
+
# identifier
|
69
|
+
#
|
44
70
|
# @since 0.1.9
|
45
71
|
#
|
46
72
|
def check_or_select_or_fill(field, value)
|
47
73
|
# If value is "checked" or "unchecked", assume
|
48
74
|
# field is a checkbox
|
49
|
-
|
50
|
-
|
75
|
+
begin
|
76
|
+
if value == "checked"
|
51
77
|
check(field)
|
52
|
-
|
53
|
-
select_or_fill(field, value)
|
54
|
-
end
|
55
|
-
elsif value == "unchecked"
|
56
|
-
begin
|
78
|
+
elsif value == "unchecked"
|
57
79
|
uncheck(field)
|
58
|
-
|
80
|
+
else
|
59
81
|
select_or_fill(field, value)
|
60
82
|
end
|
61
|
-
|
83
|
+
rescue Capybara::ElementNotFound
|
62
84
|
select_or_fill(field, value)
|
63
85
|
end
|
64
86
|
end
|
65
87
|
|
66
88
|
|
67
|
-
# Fill in
|
68
|
-
#
|
89
|
+
# Fill in a single field within the scope of a given selector.
|
90
|
+
# The field may be a text box, dropdown/listbox, or checkbox.
|
69
91
|
# See {#check_or_select_or_fill} for details.
|
70
92
|
#
|
71
93
|
# @example
|
72
|
-
#
|
73
|
-
#
|
94
|
+
# fill_in_field "Email", "otto@scratchansniff.wb"
|
95
|
+
# fill_in_field "Send me junk email", "unchecked"
|
74
96
|
#
|
75
|
-
# @param [
|
76
|
-
#
|
97
|
+
# @param [String] field
|
98
|
+
# Capybara locator for the field (name, id, or label text)
|
99
|
+
# @param [String] value
|
100
|
+
# Text to select from a dropdown or enter in a text box, or
|
101
|
+
# `checked` or `unchecked` to operate on a checkbox
|
77
102
|
# @param [Hash] scope
|
78
103
|
# Scoping keywords as understood by {#in_scope}
|
79
104
|
#
|
80
|
-
|
105
|
+
# @raise [Kelp::FieldNotFound]
|
106
|
+
# If no field is found matching the given identifier
|
107
|
+
#
|
108
|
+
def fill_in_field(field, value, scope={})
|
81
109
|
in_scope(scope) do
|
82
|
-
|
83
|
-
check_or_select_or_fill(field, value)
|
84
|
-
end
|
110
|
+
check_or_select_or_fill(field, value)
|
85
111
|
end
|
86
112
|
end
|
87
113
|
|
88
114
|
|
89
|
-
# Fill in
|
90
|
-
#
|
115
|
+
# Fill in multiple fields according to values in a `Hash`.
|
116
|
+
# Fields may be text boxes, dropdowns/listboxes, or checkboxes.
|
91
117
|
# See {#check_or_select_or_fill} for details.
|
92
118
|
#
|
93
119
|
# @example
|
94
|
-
#
|
95
|
-
#
|
120
|
+
# fill_in_fields "First name" => "Otto", "Last name" => "Scratchansniff"
|
121
|
+
# fill_in_fields "phone" => "303-224-7428", :within => "#home"
|
96
122
|
#
|
97
|
-
# @param [
|
98
|
-
#
|
99
|
-
# @param [String] value
|
100
|
-
# Text to select from a dropdown or enter in a text box, or
|
101
|
-
# `checked` or `unchecked` to operate on a checkbox
|
123
|
+
# @param [Hash] field_values
|
124
|
+
# "field" => "value" for each field to fill in, select, or check/uncheck
|
102
125
|
# @param [Hash] scope
|
103
126
|
# Scoping keywords as understood by {#in_scope}
|
104
127
|
#
|
105
|
-
|
106
|
-
|
107
|
-
|
128
|
+
# @raise [Kelp::Nonexistent]
|
129
|
+
# If any field could not be found
|
130
|
+
#
|
131
|
+
def fill_in_fields(field_values, scope={})
|
132
|
+
in_scope(scope) do
|
133
|
+
field_values.each do |field, value|
|
134
|
+
check_or_select_or_fill(field, value)
|
135
|
+
end
|
136
|
+
end
|
108
137
|
end
|
109
138
|
|
110
139
|
|
@@ -135,11 +164,15 @@ module Kelp
|
|
135
164
|
# @param [Hash] scope
|
136
165
|
# Scoping keywords as understood by {#in_scope}
|
137
166
|
#
|
167
|
+
# @raise [Kelp::Unexpected]
|
168
|
+
# If the given field is not empty or nil
|
169
|
+
#
|
138
170
|
def field_should_be_empty(field, scope={})
|
139
171
|
in_scope(scope) do
|
140
172
|
_field = nice_find_field(field)
|
141
173
|
if !(_field.nil? || _field.value.nil? || _field.value == '')
|
142
|
-
raise
|
174
|
+
raise Kelp::Unexpected,
|
175
|
+
"Expected field '#{field}' to be empty, but value is '#{_field.value}'"
|
143
176
|
end
|
144
177
|
end
|
145
178
|
end
|
@@ -154,12 +187,15 @@ module Kelp
|
|
154
187
|
# @param [Hash] scope
|
155
188
|
# Scoping keywords as understood by {#in_scope}
|
156
189
|
#
|
190
|
+
# @raise [Kelp::Unexpected]
|
191
|
+
# If the given field does not contain `value`
|
192
|
+
#
|
157
193
|
def field_should_contain(field, value, scope={})
|
158
194
|
in_scope(scope) do
|
159
195
|
# TODO: Make this work equally well with any kind of field
|
160
196
|
# (text, single-select, multi-select)
|
161
|
-
|
162
|
-
field_value = (
|
197
|
+
element = find_field(field)
|
198
|
+
field_value = (element.tag_name == 'textarea') ? element.text : element.value
|
163
199
|
# If field value is an Array, take the first item
|
164
200
|
if field_value.class == Array
|
165
201
|
field_value = field_value.first
|
@@ -167,17 +203,17 @@ module Kelp
|
|
167
203
|
# Escape any problematic characters in the expected value
|
168
204
|
value = Regexp.escape(value)
|
169
205
|
# Match actual to expected
|
170
|
-
if field_value
|
171
|
-
|
172
|
-
|
173
|
-
|
206
|
+
if !(field_value =~ /#{value}/)
|
207
|
+
raise Kelp::Unexpected,
|
208
|
+
"Expected '#{field}' to contain '#{value}'" + \
|
209
|
+
"\nGot '#{field_value}'"
|
174
210
|
end
|
175
211
|
end
|
176
212
|
end
|
177
213
|
|
178
214
|
|
179
215
|
def field_should_not_contain(field, value, scope={})
|
180
|
-
raise "Not implemented yet"
|
216
|
+
raise NotImplementedError, "Not implemented yet"
|
181
217
|
#with_scope(parent) do
|
182
218
|
#field = find_field(field)
|
183
219
|
#field_value = (field.tag_name == 'textarea') ? field.text : field.value
|
@@ -197,6 +233,9 @@ module Kelp
|
|
197
233
|
# @param [Hash] scope
|
198
234
|
# Scoping keywords as understood by {#in_scope}
|
199
235
|
#
|
236
|
+
# @raise [Kelp::Unexpected]
|
237
|
+
# If any field does not contain the expected value
|
238
|
+
#
|
200
239
|
def fields_should_contain(field_values, scope={})
|
201
240
|
in_scope(scope) do
|
202
241
|
field_values.each do |field, value|
|