capybara-harness 0.0.1
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/.gitignore +18 -0
- data/Gemfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +48 -0
- data/Rakefile +1 -0
- data/capybara-harness.gemspec +22 -0
- data/lib/capybara-harness.rb +7 -0
- data/lib/capybara/harness/dom/attribute.rb +25 -0
- data/lib/capybara/harness/dom/configuration.rb +23 -0
- data/lib/capybara/harness/dom/field.rb +45 -0
- data/lib/capybara/harness/dom/field_set.rb +32 -0
- data/lib/capybara/harness/dom/form.rb +29 -0
- data/lib/capybara/harness/dom/subject.rb +93 -0
- data/lib/capybara/harness/dom_harness.rb +57 -0
- data/lib/capybara/harness/version.rb +5 -0
- metadata +90 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2013 Theo Mills
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Capybara::Harness
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
Capybara::Harness implements a test harness strategy to easily query and manipulate DOM elements as self-contained
|
|
6
|
+
objects in the context of feature or acceptance tests.
|
|
7
|
+
|
|
8
|
+
By pulling redundant behavior out of scenarios and tests (such as filling out and submitting forms, or ensuring a
|
|
9
|
+
comment element is visible on the page), the focus moves to the essential steps of the use cases and the intent of
|
|
10
|
+
the feature becomes more legible.
|
|
11
|
+
|
|
12
|
+
## Props
|
|
13
|
+
|
|
14
|
+
I had been playing with the concept for a while in my acceptance tests, but this thoughtbot article really helped
|
|
15
|
+
solidify the concept and highly influenced me:
|
|
16
|
+
|
|
17
|
+
http://robots.thoughtbot.com/post/35776432958/better-support-with-test-harnesses
|
|
18
|
+
|
|
19
|
+
## Status
|
|
20
|
+
|
|
21
|
+
This library is still very much in the baking phase, although I am successfully using it in 2 production projects. I'll
|
|
22
|
+
be adding specs in the coming weeks.
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
Add this line to your application's Gemfile:
|
|
27
|
+
|
|
28
|
+
gem 'capybara-harness'
|
|
29
|
+
|
|
30
|
+
And then execute:
|
|
31
|
+
|
|
32
|
+
$ bundle
|
|
33
|
+
|
|
34
|
+
Or install it yourself as:
|
|
35
|
+
|
|
36
|
+
$ gem install capybara-harness
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
TODO: Write usage instructions here
|
|
41
|
+
|
|
42
|
+
## Contributing
|
|
43
|
+
|
|
44
|
+
1. Fork it
|
|
45
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
46
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
47
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
48
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require "bundler/gem_tasks"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require 'capybara/harness/version'
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |gem|
|
|
7
|
+
gem.name = "capybara-harness"
|
|
8
|
+
gem.version = Capybara::Harness::VERSION
|
|
9
|
+
gem.authors = ["Theo Mills"]
|
|
10
|
+
gem.email = ["twmills@twmills.com"]
|
|
11
|
+
gem.description = "A test harness strategy to easily query and manipulate DOM elements as self-contained objects in the context of feature or acceptance tests."
|
|
12
|
+
gem.summary = "A test harness strategy to easily query and manipulate DOM elements as self-contained objects in the context of feature or acceptance tests."
|
|
13
|
+
gem.homepage = "https://github.com/twmills/capybara-harness"
|
|
14
|
+
|
|
15
|
+
gem.files = `git ls-files`.split($/)
|
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
18
|
+
gem.require_paths = ["lib"]
|
|
19
|
+
|
|
20
|
+
gem.add_runtime_dependency('capybara', '>= 2.0')
|
|
21
|
+
gem.add_runtime_dependency(%q<activesupport>, [">= 3.0"])
|
|
22
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Capybara::Harness::Dom
|
|
2
|
+
class Attribute
|
|
3
|
+
include Capybara::DSL
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :contents
|
|
6
|
+
|
|
7
|
+
def initialize(name, options = {})
|
|
8
|
+
self.name = name
|
|
9
|
+
self.contents = options.delete(:contents)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def visible?(values)
|
|
13
|
+
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def extract_value(values)
|
|
17
|
+
values = HashWithIndifferentAccess.new(values)
|
|
18
|
+
if contents
|
|
19
|
+
contents.call(values)
|
|
20
|
+
else
|
|
21
|
+
values[name].to_s
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Capybara::Harness::Dom
|
|
2
|
+
class Configuration
|
|
3
|
+
|
|
4
|
+
attr_accessor :selector, :finder_attr_name, :attributes, :form
|
|
5
|
+
|
|
6
|
+
def initialize(klass)
|
|
7
|
+
self.selector = klass.name.split('::').last.gsub("OnPage", '').underscore
|
|
8
|
+
self.attributes = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def add_attribute(name, options = {})
|
|
12
|
+
self.finder_attr_name = name.to_sym if options.has_key?(:finder) && options[:finder] == true
|
|
13
|
+
self.attributes[name] = Capybara::Dom::Attribute.new(name, options)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def set_form(name = nil, block = nil)
|
|
17
|
+
form = Capybara::Dom::Form.new(name)
|
|
18
|
+
block.call(form) unless block.nil?
|
|
19
|
+
self.form = form
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module Capybara::Harness::Dom
|
|
2
|
+
class Field
|
|
3
|
+
include Capybara::DSL
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :label, :data_type, :through
|
|
6
|
+
|
|
7
|
+
def initialize(options = {})
|
|
8
|
+
self.name = options.delete(:name)
|
|
9
|
+
self.label = options.fetch(:label, name.to_s.titleize)
|
|
10
|
+
self.data_type = options.fetch(:data_type, :string)
|
|
11
|
+
self.through = options.delete(:through)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def fill(attrs = {})
|
|
15
|
+
if has_attribute?(attrs)
|
|
16
|
+
value = extract_value(attrs)
|
|
17
|
+
case data_type
|
|
18
|
+
when :string then fill_in(label, :with => value)
|
|
19
|
+
when :select then select(value, :from => label)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def through
|
|
25
|
+
@through.to_sym if @through
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def name
|
|
29
|
+
@name.to_sym
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def has_attribute?(attrs)
|
|
35
|
+
return attrs[through].has_key?(name) if through && attrs.has_key?(through)
|
|
36
|
+
attrs.has_key?(name)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def extract_value(attrs)
|
|
40
|
+
attrs = attrs[through] if attrs.has_key?(through)
|
|
41
|
+
attrs[name.to_sym]
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Capybara::Harness::Dom
|
|
2
|
+
class FieldSet
|
|
3
|
+
attr_accessor :name, :fields
|
|
4
|
+
|
|
5
|
+
def initialize(name)
|
|
6
|
+
self.name = name
|
|
7
|
+
self.fields = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def field(*args)
|
|
11
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
12
|
+
|
|
13
|
+
args.each do |field_name|
|
|
14
|
+
fields << Capybara::Dom::Field.new(options.merge(:name => field_name))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fill(attrs = {})
|
|
19
|
+
if name
|
|
20
|
+
within("##{name}") { fill_fields(attrs) }
|
|
21
|
+
else
|
|
22
|
+
fill_fields(attrs)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def fill_fields(attrs)
|
|
29
|
+
fields.each { |f| f.fill(attrs) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Capybara::Harness::Dom
|
|
2
|
+
class Form
|
|
3
|
+
attr_accessor :name, :elements
|
|
4
|
+
|
|
5
|
+
def initialize(name = nil)
|
|
6
|
+
self.name = name
|
|
7
|
+
self.elements = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def field(*args)
|
|
11
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
|
12
|
+
|
|
13
|
+
args.each do |field_name|
|
|
14
|
+
elements << Capybara::Dom::Field.new(options.merge(:name => field_name))
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def fieldset(name = nil, &block)
|
|
19
|
+
fieldset = Capybara::Dom::FieldSet.new(name)
|
|
20
|
+
block.call(fieldset)
|
|
21
|
+
elements << fieldset
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def fill(attrs = {})
|
|
25
|
+
elements.each { |e| e.fill(attrs) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
module Capybara::Harness::Dom
|
|
2
|
+
class Subject
|
|
3
|
+
include Capybara::DSL
|
|
4
|
+
|
|
5
|
+
attr_accessor :selector, :attributes, :finder_attr_name, :values, :form
|
|
6
|
+
|
|
7
|
+
def initialize(configuration, values = nil)
|
|
8
|
+
self.selector = configuration.selector
|
|
9
|
+
self.finder_attr_name = configuration.finder_attr_name
|
|
10
|
+
self.attributes = configuration.attributes
|
|
11
|
+
self.form = configuration.form
|
|
12
|
+
self.reset!(values)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Public: Returns the value of the finder attribute for this subject.
|
|
16
|
+
def finder_attr
|
|
17
|
+
values[finder_attr_name] || ''
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Public: Scans the DOM and finds the node that represents the subject.
|
|
21
|
+
def element(root = nil)
|
|
22
|
+
root = root || page
|
|
23
|
+
root.find(".#{selector} .#{finder_attr_name}", :text => finder_attr).find(:xpath, ".//ancestor::*[@class='#{selector}']")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Public: Scans the DOM and finds the node that represents the subject's list element.
|
|
27
|
+
def list
|
|
28
|
+
find("##{selector.to_s.pluralize}")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Public: Scans the DOM and finds the node that represents the subject's form element and fills it with the supplied
|
|
32
|
+
# values. It also resets the subjects current value hash has with the new values.
|
|
33
|
+
#
|
|
34
|
+
# new_values - The values to fill the form with.
|
|
35
|
+
#
|
|
36
|
+
# Returns nothing of interest.
|
|
37
|
+
def fill_form(new_values = {})
|
|
38
|
+
new_values = HashWithIndifferentAccess.new(new_values)
|
|
39
|
+
form.fill(new_values)
|
|
40
|
+
self.reset!(new_values)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Public: Returns true if the subject's element is present on the page.
|
|
44
|
+
def on_the_page?
|
|
45
|
+
has_attrs?(element)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Public: Returns true if the subject's element is in the page's subject list.
|
|
49
|
+
def in_the_list?
|
|
50
|
+
return false unless list.has_css?(".#{selector} .#{finder_attr_name}", :text => finder_attr)
|
|
51
|
+
has_attrs?(element(list))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Public: Returns true if the subject's element is located first in its list.
|
|
55
|
+
def first_in_the_list?
|
|
56
|
+
node = list.first(".#{selector}")
|
|
57
|
+
return false if node.nil?
|
|
58
|
+
has_attrs?(node)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Public: Returns true if the subject's element is located last in its list.
|
|
62
|
+
def last_in_the_list?
|
|
63
|
+
node = list.all(".#{selector}").last
|
|
64
|
+
return false if node.nil?
|
|
65
|
+
has_attrs?(node)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Public: Finds and clicks the element's link that is assigned the data attribute for the supplied action. Common actions
|
|
69
|
+
# include :edit or :delete.
|
|
70
|
+
def click_action(action)
|
|
71
|
+
element.find("[data-#{selector}-action='#{action}']").click
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def has_attrs?(node)
|
|
77
|
+
attributes.each do |attr_name, attr|
|
|
78
|
+
text = attr.extract_value(values)
|
|
79
|
+
return false unless node.has_css?(".#{attr_name}", :text => text)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
true
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def reset!(subject_values = {})
|
|
86
|
+
self.values = {}
|
|
87
|
+
attributes.each do |name, attribute|
|
|
88
|
+
values[name] = attribute.extract_value(subject_values)
|
|
89
|
+
end
|
|
90
|
+
self.values = HashWithIndifferentAccess.new(self.values.merge(subject_values))
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
require 'active_support/core_ext/class'
|
|
2
|
+
require 'spec/support/capybara/dom/configuration'
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
class Capybara::Harness::DomHarness
|
|
6
|
+
|
|
7
|
+
extend Forwardable
|
|
8
|
+
include Capybara::DSL
|
|
9
|
+
include Rails.application.routes.url_helpers
|
|
10
|
+
|
|
11
|
+
attr_accessor :subject
|
|
12
|
+
|
|
13
|
+
def_delegators :subject,
|
|
14
|
+
:selector,
|
|
15
|
+
:element,
|
|
16
|
+
:list,
|
|
17
|
+
:on_the_page?,
|
|
18
|
+
:in_the_list?,
|
|
19
|
+
:last_in_the_list?,
|
|
20
|
+
:first_in_the_list?,
|
|
21
|
+
:fill_form,
|
|
22
|
+
:click_action
|
|
23
|
+
|
|
24
|
+
def initialize(values = {})
|
|
25
|
+
self.subject = Capybara::Dom::Subject.new(self.class.configuration, values)
|
|
26
|
+
create_aliases!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.configuration
|
|
30
|
+
@configuration = Capybara::Dom::Configuration.new(self) unless @configuration
|
|
31
|
+
@configuration
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def self.dom_selector(name)
|
|
35
|
+
self.configuration.selector = name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def self.define_form(name = nil, &block)
|
|
39
|
+
self.configuration.set_form(name, block)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.dom_attr(name, options = {})
|
|
43
|
+
self.configuration.add_attribute(name, options)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def create_aliases!
|
|
49
|
+
singleton = class << self;
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
singleton.send(:alias_method, selector, element)
|
|
54
|
+
singleton.send(:alias_method, "#{selector}_list", list)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: capybara-harness
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
prerelease:
|
|
6
|
+
platform: ruby
|
|
7
|
+
authors:
|
|
8
|
+
- Theo Mills
|
|
9
|
+
autorequire:
|
|
10
|
+
bindir: bin
|
|
11
|
+
cert_chain: []
|
|
12
|
+
date: 2013-01-20 00:00:00.000000000 Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
15
|
+
name: capybara
|
|
16
|
+
requirement: &70275552304340 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ! '>='
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '2.0'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *70275552304340
|
|
25
|
+
- !ruby/object:Gem::Dependency
|
|
26
|
+
name: activesupport
|
|
27
|
+
requirement: &70275552303820 !ruby/object:Gem::Requirement
|
|
28
|
+
none: false
|
|
29
|
+
requirements:
|
|
30
|
+
- - ! '>='
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: *70275552303820
|
|
36
|
+
description: A test harness strategy to easily query and manipulate DOM elements as
|
|
37
|
+
self-contained objects in the context of feature or acceptance tests.
|
|
38
|
+
email:
|
|
39
|
+
- twmills@twmills.com
|
|
40
|
+
executables: []
|
|
41
|
+
extensions: []
|
|
42
|
+
extra_rdoc_files: []
|
|
43
|
+
files:
|
|
44
|
+
- .gitignore
|
|
45
|
+
- Gemfile
|
|
46
|
+
- LICENSE.txt
|
|
47
|
+
- README.md
|
|
48
|
+
- Rakefile
|
|
49
|
+
- capybara-harness.gemspec
|
|
50
|
+
- lib/capybara-harness.rb
|
|
51
|
+
- lib/capybara/harness/dom/attribute.rb
|
|
52
|
+
- lib/capybara/harness/dom/configuration.rb
|
|
53
|
+
- lib/capybara/harness/dom/field.rb
|
|
54
|
+
- lib/capybara/harness/dom/field_set.rb
|
|
55
|
+
- lib/capybara/harness/dom/form.rb
|
|
56
|
+
- lib/capybara/harness/dom/subject.rb
|
|
57
|
+
- lib/capybara/harness/dom_harness.rb
|
|
58
|
+
- lib/capybara/harness/version.rb
|
|
59
|
+
homepage: https://github.com/twmills/capybara-harness
|
|
60
|
+
licenses: []
|
|
61
|
+
post_install_message:
|
|
62
|
+
rdoc_options: []
|
|
63
|
+
require_paths:
|
|
64
|
+
- lib
|
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
66
|
+
none: false
|
|
67
|
+
requirements:
|
|
68
|
+
- - ! '>='
|
|
69
|
+
- !ruby/object:Gem::Version
|
|
70
|
+
version: '0'
|
|
71
|
+
segments:
|
|
72
|
+
- 0
|
|
73
|
+
hash: 3234655397199029985
|
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
|
+
none: false
|
|
76
|
+
requirements:
|
|
77
|
+
- - ! '>='
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: '0'
|
|
80
|
+
segments:
|
|
81
|
+
- 0
|
|
82
|
+
hash: 3234655397199029985
|
|
83
|
+
requirements: []
|
|
84
|
+
rubyforge_project:
|
|
85
|
+
rubygems_version: 1.8.15
|
|
86
|
+
signing_key:
|
|
87
|
+
specification_version: 3
|
|
88
|
+
summary: A test harness strategy to easily query and manipulate DOM elements as self-contained
|
|
89
|
+
objects in the context of feature or acceptance tests.
|
|
90
|
+
test_files: []
|