capybara-ui 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/capybara-ui.rb +31 -0
- data/lib/capybara-ui/assertions.rb +19 -0
- data/lib/capybara-ui/capybara.rb +7 -0
- data/lib/capybara-ui/checkpoint.rb +111 -0
- data/lib/capybara-ui/conversions.rb +31 -0
- data/lib/capybara-ui/cucumber.rb +5 -0
- data/lib/capybara-ui/dsl.rb +107 -0
- data/lib/capybara-ui/instance_conversions.rb +19 -0
- data/lib/capybara-ui/matchers.rb +28 -0
- data/lib/capybara-ui/optional_dependencies.rb +5 -0
- data/lib/capybara-ui/rails.rb +5 -0
- data/lib/capybara-ui/rails/role.rb +9 -0
- data/lib/capybara-ui/role.rb +19 -0
- data/lib/capybara-ui/text_table.rb +107 -0
- data/lib/capybara-ui/text_table/cell_text.rb +7 -0
- data/lib/capybara-ui/text_table/mapping.rb +40 -0
- data/lib/capybara-ui/text_table/transformations.rb +13 -0
- data/lib/capybara-ui/text_table/void_mapping.rb +8 -0
- data/lib/capybara-ui/version.rb +3 -0
- data/lib/capybara-ui/widgets.rb +61 -0
- data/lib/capybara-ui/widgets/check_box.rb +26 -0
- data/lib/capybara-ui/widgets/cucumber_methods.rb +73 -0
- data/lib/capybara-ui/widgets/document.rb +19 -0
- data/lib/capybara-ui/widgets/dsl.rb +47 -0
- data/lib/capybara-ui/widgets/field.rb +22 -0
- data/lib/capybara-ui/widgets/field_group.rb +329 -0
- data/lib/capybara-ui/widgets/form.rb +26 -0
- data/lib/capybara-ui/widgets/list.rb +200 -0
- data/lib/capybara-ui/widgets/list_item.rb +22 -0
- data/lib/capybara-ui/widgets/parts/container.rb +46 -0
- data/lib/capybara-ui/widgets/parts/struct.rb +117 -0
- data/lib/capybara-ui/widgets/radio_button.rb +62 -0
- data/lib/capybara-ui/widgets/select.rb +57 -0
- data/lib/capybara-ui/widgets/string_value.rb +43 -0
- data/lib/capybara-ui/widgets/table.rb +76 -0
- data/lib/capybara-ui/widgets/text_field.rb +27 -0
- data/lib/capybara-ui/widgets/widget.rb +392 -0
- data/lib/capybara-ui/widgets/widget/node_filter.rb +48 -0
- data/lib/capybara-ui/widgets/widget_class.rb +11 -0
- data/lib/capybara-ui/widgets/widget_name.rb +56 -0
- metadata +240 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23fc0acf5fe588ee3f45849a2874646086bd6ee5
|
4
|
+
data.tar.gz: cc92aa9e22aa4c98962fe0f69cf932064199eb44
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c7e1e9fe1f240f81740fd5069ed9a28fdadb8c0243cc46c614a6172cb6e88ed0e8e08ab58611093780b66eeea5356d5d6a5b8c7e1016ac897c77f49d078e24d5
|
7
|
+
data.tar.gz: a7cb453961ba99e4ef31daf00e0016fc8c9b802a018efdf030191b99cfe13878d65651514d35e43e89bb504f04ddc45845a66612a0361a70b210a009807172e8
|
data/lib/capybara-ui.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'chronic'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'capybara'
|
4
|
+
|
5
|
+
require 'capybara-ui/optional_dependencies'
|
6
|
+
require 'capybara-ui/conversions'
|
7
|
+
require 'capybara-ui/instance_conversions'
|
8
|
+
require 'capybara-ui/checkpoint'
|
9
|
+
require 'capybara-ui/widgets'
|
10
|
+
require 'capybara-ui/text_table'
|
11
|
+
require 'capybara-ui/text_table/mapping'
|
12
|
+
require 'capybara-ui/text_table/void_mapping'
|
13
|
+
require 'capybara-ui/text_table/transformations'
|
14
|
+
require 'capybara-ui/text_table/cell_text'
|
15
|
+
require 'capybara-ui/capybara'
|
16
|
+
require 'capybara-ui/dsl'
|
17
|
+
|
18
|
+
module CapybaraUI
|
19
|
+
# An exception that signals that something is missing.
|
20
|
+
class Missing < StandardError; end
|
21
|
+
class MissingWidget < StandardError; end
|
22
|
+
class AmbiguousWidget < StandardError; end
|
23
|
+
class InvalidOption < StandardError; end
|
24
|
+
class InvalidRadioButton < StandardError; end
|
25
|
+
|
26
|
+
def deprecate(method, alternate_method, once=false)
|
27
|
+
@deprecation_notified ||= {}
|
28
|
+
warn "DEPRECATED: ##{method} is deprecated, please use ##{alternate_method} instead" unless once and @deprecation_notified[method]
|
29
|
+
@deprecation_notified[method] = true
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
module Assertions
|
3
|
+
def assert_visible(role, widget_name, *args)
|
4
|
+
eventually do
|
5
|
+
assert role.see?(widget_name, *args)
|
6
|
+
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def assert_not_visible(role, widget_name, *args)
|
12
|
+
eventually do
|
13
|
+
refute role.see?(widget_name, *args)
|
14
|
+
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
# A point in time where some condition, or some set of conditions, should be
|
3
|
+
# verified.
|
4
|
+
#
|
5
|
+
# @see http://rubydoc.info/github/jnicklas/capybara/master/Capybara/Node/Base#synchronize-instance_method,
|
6
|
+
# which inspired this class.
|
7
|
+
class Checkpoint
|
8
|
+
class ConditionNotMet < StandardError; end
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :rescuable_errors
|
12
|
+
end
|
13
|
+
|
14
|
+
self.rescuable_errors = [StandardError]
|
15
|
+
|
16
|
+
if defined?(RSpec)
|
17
|
+
require 'rspec/expectations'
|
18
|
+
self.rescuable_errors << RSpec::Expectations::ExpectationNotMetError
|
19
|
+
end
|
20
|
+
|
21
|
+
class Timer
|
22
|
+
class Frozen < StandardError; end
|
23
|
+
|
24
|
+
def initialize(duration)
|
25
|
+
@duration = duration
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :duration
|
29
|
+
|
30
|
+
def expired?
|
31
|
+
duration < elapsed
|
32
|
+
end
|
33
|
+
|
34
|
+
def elapsed
|
35
|
+
now - start_time
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
@start_time = now
|
40
|
+
end
|
41
|
+
|
42
|
+
def tick
|
43
|
+
sleep tick_duration
|
44
|
+
|
45
|
+
raise Frozen, 'time appears to be frozen' if frozen?
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
def now
|
51
|
+
Time.now
|
52
|
+
end
|
53
|
+
|
54
|
+
attr_reader :start_time
|
55
|
+
|
56
|
+
def frozen?
|
57
|
+
now == start_time
|
58
|
+
end
|
59
|
+
|
60
|
+
def tick_duration
|
61
|
+
0.05
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Shortcut for instance level wait_for.
|
66
|
+
def self.wait_for(wait_time = Capybara.default_max_wait_time, &block)
|
67
|
+
new(wait_time).call(&block)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Initializes a new Checkpoint.
|
71
|
+
#
|
72
|
+
# @param wait_time how long this checkpoint will wait for its conditions to
|
73
|
+
# be met, in seconds.
|
74
|
+
def initialize(wait_time = Capybara.default_max_wait_time)
|
75
|
+
@timer = Timer.new(wait_time)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Executes +block+ repeatedly until it returns a "truthy" value or
|
79
|
+
# +wait_time+ expires.
|
80
|
+
#
|
81
|
+
# Swallows any StandardError or StandardError descendent until +wait_time+
|
82
|
+
# expires. If an exception is raised and the time has expired, that
|
83
|
+
# exception will be raised again.
|
84
|
+
#
|
85
|
+
# If the block does not return a "truthy" value until +wait_time+ expires,
|
86
|
+
# raises a CapybaraUI::Checkpoint::ConditionNotMet error.
|
87
|
+
#
|
88
|
+
# Returns whatever value is returned by the block.
|
89
|
+
def call(&condition)
|
90
|
+
timer.start
|
91
|
+
|
92
|
+
begin
|
93
|
+
yield or raise ConditionNotMet
|
94
|
+
rescue *rescuable_errors
|
95
|
+
raise if timer.expired?
|
96
|
+
|
97
|
+
timer.tick
|
98
|
+
|
99
|
+
retry
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
protected
|
104
|
+
|
105
|
+
def rescuable_errors
|
106
|
+
self.class.rescuable_errors
|
107
|
+
end
|
108
|
+
|
109
|
+
attr_reader :timer
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
module Conversions
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def Boolean(val)
|
6
|
+
case val
|
7
|
+
when 'yes', 'true', true
|
8
|
+
true
|
9
|
+
when 'no', 'false', false, nil, ''
|
10
|
+
false
|
11
|
+
else
|
12
|
+
raise ArgumentError, "can't convert #{val.inspect} to boolean"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def List(valstr, &block)
|
17
|
+
vs = valstr.strip.split(/\s*,\s*/)
|
18
|
+
|
19
|
+
block ? vs.map(&block) : vs
|
20
|
+
end
|
21
|
+
|
22
|
+
def Timeish(val)
|
23
|
+
raise ArgumentError, "can't convert nil to Timeish" if val.nil?
|
24
|
+
|
25
|
+
return val if Date === val || Time === val || DateTime === val
|
26
|
+
|
27
|
+
Chronic.parse(val) or
|
28
|
+
raise ArgumentError, "can't parse #{val.inspect} to Timeish"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
module DSL
|
3
|
+
attr_writer :widget_lookup_scope
|
4
|
+
|
5
|
+
# Clicks the widget defined by +name+ and optional +args+.
|
6
|
+
#
|
7
|
+
# Makes no distinction between links or buttons.
|
8
|
+
#
|
9
|
+
# class MyWidget < CapybaraUI::Widget
|
10
|
+
# root { |text| ['.my-widget', text: text] }
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# # <a href="#one" class="my-widget">One</li>
|
14
|
+
# # <a href="#two" class="my-widget">Two</li> <!-- clicks this node -->
|
15
|
+
# click :my_widget, 'Two'
|
16
|
+
def click(name, *args)
|
17
|
+
widget(name, *args).click
|
18
|
+
end
|
19
|
+
|
20
|
+
# Hovers the widget defined by +name+ and optional +args+.
|
21
|
+
def hover(name, *args)
|
22
|
+
widget(name, *args).hover
|
23
|
+
end
|
24
|
+
|
25
|
+
# Double clicks the widget defined by +name+ and optional +args+.
|
26
|
+
def double_click(name, *args)
|
27
|
+
widget(name, *args).double_click
|
28
|
+
end
|
29
|
+
|
30
|
+
# Right clicks the widget defined by +name+ and optional +args+.
|
31
|
+
def right_click(name, *args)
|
32
|
+
widget(name, *args).right_click
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [Document] the current document with the class of the
|
36
|
+
# current object set as the widget lookup scope.
|
37
|
+
def document
|
38
|
+
Document.new(widget_lookup_scope)
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [Boolean] Whether one or more widgets exist in the current
|
42
|
+
# document.
|
43
|
+
def has_widget?(name, *args)
|
44
|
+
document.has_widget?(name, *args)
|
45
|
+
end
|
46
|
+
|
47
|
+
alias_method :widget?, :has_widget?
|
48
|
+
|
49
|
+
def visible?(name, *args)
|
50
|
+
document.visible?(name, *args)
|
51
|
+
end
|
52
|
+
|
53
|
+
def not_visible?(name, *args)
|
54
|
+
document.not_visible?(name, *args)
|
55
|
+
end
|
56
|
+
|
57
|
+
def set(name, fields)
|
58
|
+
widget(name).set fields
|
59
|
+
end
|
60
|
+
|
61
|
+
def submit(name, fields = {})
|
62
|
+
widget(name).submit_with fields
|
63
|
+
end
|
64
|
+
|
65
|
+
def value(name, *args)
|
66
|
+
widget(name, *args).value
|
67
|
+
end
|
68
|
+
|
69
|
+
def values(name, *args)
|
70
|
+
widgets(name, *args).map(&:value)
|
71
|
+
end
|
72
|
+
|
73
|
+
def visit(path)
|
74
|
+
Capybara.current_session.visit path
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns a widget instance for the given name.
|
78
|
+
#
|
79
|
+
# @param name [String, Symbol]
|
80
|
+
def widget(name, *args)
|
81
|
+
eventually { document.widget(name, *args) }
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns a list of widget instances for the given name.
|
85
|
+
#
|
86
|
+
# @param name [String, Symbol]
|
87
|
+
def widgets(name, *args)
|
88
|
+
document.widgets(name, *args)
|
89
|
+
end
|
90
|
+
|
91
|
+
def widget_lookup_scope
|
92
|
+
@widget_lookup_scope ||= default_widget_lookup_scope
|
93
|
+
end
|
94
|
+
|
95
|
+
# re-run one or more assertions until either they all pass,
|
96
|
+
# or CapybaraUI times out, which will result in a test failure.
|
97
|
+
def eventually(wait_time = Capybara.default_max_wait_time, &block)
|
98
|
+
Checkpoint.wait_for wait_time, &block
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def default_widget_lookup_scope
|
104
|
+
Module === self ? self : self.class
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
module InstanceConversions
|
3
|
+
def self.included(base)
|
4
|
+
base.send :include, CapybaraUI::Conversions
|
5
|
+
end
|
6
|
+
|
7
|
+
def to_boolean
|
8
|
+
Boolean(self)
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_a
|
12
|
+
List(self)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_time
|
16
|
+
Timeish(self)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
begin
|
2
|
+
require 'rspec/matchers'
|
3
|
+
require 'rspec/version'
|
4
|
+
|
5
|
+
unless Gem::Version.new(RSpec::Version::STRING) >= Gem::Version.new(CapybaraUI::OptionalDependencies::RSPEC_VERSION)
|
6
|
+
raise LoadError,
|
7
|
+
"requires RSpec version #{CapybaraUI::OptionalDependencies::RSPEC_VERSION} or later. " \
|
8
|
+
"You have #{RSpec::Version::STRING}."
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec::Matchers.define :see do |widget_name, *args|
|
12
|
+
match do |role|
|
13
|
+
begin
|
14
|
+
eventually { role.see?(widget_name, *args) }
|
15
|
+
rescue CapybaraUI::Checkpoint::ConditionNotMet
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
match_when_negated do |role|
|
21
|
+
begin
|
22
|
+
eventually { !role.see?(widget_name, *args) }
|
23
|
+
rescue CapybaraUI::Checkpoint::ConditionNotMet
|
24
|
+
false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
class Role < CapybaraUI::Rails::Role
|
3
|
+
extend Widgets::DSL
|
4
|
+
|
5
|
+
include CapybaraUI::DSL
|
6
|
+
|
7
|
+
def see?(name, *args)
|
8
|
+
if respond_to?("see_#{name}?")
|
9
|
+
send("see_#{name}?", *args)
|
10
|
+
else
|
11
|
+
visible?(name, *args)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def inspect
|
16
|
+
self.class.name
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module CapybaraUI
|
2
|
+
class TextTable
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
include Enumerable
|
6
|
+
include Conversions
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def Array(table)
|
10
|
+
new(table).to_a
|
11
|
+
end
|
12
|
+
|
13
|
+
def Hash(table)
|
14
|
+
new(table).to_h
|
15
|
+
end
|
16
|
+
|
17
|
+
def map(name, options = {}, &block)
|
18
|
+
case name
|
19
|
+
when :*
|
20
|
+
set_default_mapping options, &block
|
21
|
+
else
|
22
|
+
set_mapping name, options, &block
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def mappings
|
27
|
+
@mappings ||= Hash.
|
28
|
+
new { |h, k| h[k] = Mapping.new }.
|
29
|
+
merge(with_parent_mappings)
|
30
|
+
end
|
31
|
+
|
32
|
+
def skip(name)
|
33
|
+
case name
|
34
|
+
when :*
|
35
|
+
set_default_mapping VoidMapping
|
36
|
+
else
|
37
|
+
raise ArgumentError, "can't convert #{name.inspect} to name"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def set_default_mapping(options, &block)
|
44
|
+
case options
|
45
|
+
when Hash
|
46
|
+
@mappings = Hash.
|
47
|
+
new { |h, k|
|
48
|
+
h[k] = Mapping.new(key_transformer: options[:to],
|
49
|
+
value_transformer: block) }.
|
50
|
+
merge(mappings)
|
51
|
+
when Class
|
52
|
+
@mappings = Hash.new { |h, k| h[k] = options.new }.merge(mappings)
|
53
|
+
else
|
54
|
+
raise ArgumentError, "can't convert #{options.inspect} to mapping"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def set_mapping(name, options, &block)
|
59
|
+
mappings[name] = Mapping.
|
60
|
+
new(key: options[:to], value_transformer: block)
|
61
|
+
end
|
62
|
+
|
63
|
+
def with_parent_mappings
|
64
|
+
if superclass.respond_to?(:mappings)
|
65
|
+
superclass.send(:mappings).dup
|
66
|
+
else
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def_delegators 'self.class', :mappings
|
73
|
+
|
74
|
+
def initialize(table)
|
75
|
+
self.table = table
|
76
|
+
end
|
77
|
+
|
78
|
+
def each(&block)
|
79
|
+
rows.each(&block)
|
80
|
+
end
|
81
|
+
|
82
|
+
def rows
|
83
|
+
@rows ||= table.hashes.map { |h| new_row(h) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def single_row
|
87
|
+
@single_row ||= new_row(table.rows_hash)
|
88
|
+
end
|
89
|
+
|
90
|
+
alias_method :to_a, :rows
|
91
|
+
alias_method :to_h, :single_row
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
attr_accessor :table
|
96
|
+
|
97
|
+
def new_row(hash)
|
98
|
+
hash.each_with_object({}) { |(k, v), h|
|
99
|
+
mapping_for(k).set(self, h, k, CellText.new(v))
|
100
|
+
}
|
101
|
+
end
|
102
|
+
|
103
|
+
def mapping_for(header)
|
104
|
+
mappings[header]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|