capybara-ui 0.10.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.
- 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
|