kelp 0.1.9 → 0.2.0
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/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|
|