pickles 0.1.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/.gitignore +9 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +196 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/cucumber/pickles.rb +6 -0
- data/lib/cucumber/pickles/check_in.rb +10 -0
- data/lib/cucumber/pickles/config.rb +61 -0
- data/lib/cucumber/pickles/errors/ambigious.rb +27 -0
- data/lib/cucumber/pickles/errors/node_find_error.rb +37 -0
- data/lib/cucumber/pickles/fill_in.rb +10 -0
- data/lib/cucumber/pickles/helpers.rb +40 -0
- data/lib/cucumber/pickles/helpers/extensions/chrome/.DS_Store +0 -0
- data/lib/cucumber/pickles/helpers/extensions/chrome/compiled.crx.base64 +1 -0
- data/lib/cucumber/pickles/helpers/extensions/chrome/manifest.json +15 -0
- data/lib/cucumber/pickles/helpers/extensions/chrome/src/.DS_Store +0 -0
- data/lib/cucumber/pickles/helpers/extensions/chrome/src/inject/inject.js +35 -0
- data/lib/cucumber/pickles/helpers/main.rb +88 -0
- data/lib/cucumber/pickles/helpers/node_finders.rb +125 -0
- data/lib/cucumber/pickles/helpers/regex.rb +6 -0
- data/lib/cucumber/pickles/helpers/waiter.rb +152 -0
- data/lib/cucumber/pickles/locator/equal.rb +26 -0
- data/lib/cucumber/pickles/locator/index.rb +20 -0
- data/lib/cucumber/pickles/refinements.rb +49 -0
- data/lib/cucumber/pickles/steps.rb +73 -0
- data/lib/cucumber/pickles/steps/can_see.rb +70 -0
- data/lib/cucumber/pickles/steps/check.rb +55 -0
- data/lib/cucumber/pickles/steps/check_in/complex_input.rb +17 -0
- data/lib/cucumber/pickles/steps/check_in/factory.rb +26 -0
- data/lib/cucumber/pickles/steps/check_in/input.rb +36 -0
- data/lib/cucumber/pickles/steps/check_in/text.rb +17 -0
- data/lib/cucumber/pickles/steps/click.rb +91 -0
- data/lib/cucumber/pickles/steps/fill.rb +72 -0
- data/lib/cucumber/pickles/steps/fill_in/complex_input.rb +19 -0
- data/lib/cucumber/pickles/steps/fill_in/factory.rb +25 -0
- data/lib/cucumber/pickles/steps/fill_in/input.rb +29 -0
- data/lib/cucumber/pickles/steps/fill_in/select.rb +30 -0
- data/lib/cucumber/pickles/steps/redirect.rb +3 -0
- data/lib/cucumber/pickles/transform.rb +13 -0
- data/lib/cucumber/pickles/version.rb +3 -0
- data/lib/pickles.rb +3 -0
- data/pickles.gemspec +36 -0
- data/spec/helpers/node_finders_spec.rb +155 -0
- data/spec/helpers/waiter_spec.rb +41 -0
- data/spec/locator_spec.rb +31 -0
- data/spec/spec_helper.rb +32 -0
- data/spec/step_def_spec.rb +0 -0
- data/spec/steps/check_in/factory_spec.rb +52 -0
- data/spec/steps/fill_in/factory_spec.rb +51 -0
- metadata +153 -0
@@ -0,0 +1,72 @@
|
|
1
|
+
# Use this to fill in an entire form with data from a table. Example:
|
2
|
+
#
|
3
|
+
# When I fill in the following:
|
4
|
+
# | | Account Number | 5002 |
|
5
|
+
# | | Expiry date | 2009-11-01 |
|
6
|
+
# | | Note | Nice guy |
|
7
|
+
# | | Wants Email? | |
|
8
|
+
# | User data | Sex (select) | Male |
|
9
|
+
# | | Avatar | avatar.png |
|
10
|
+
# | | Due date | 12:35 |
|
11
|
+
# | Additional data | Accept user agrement | true |
|
12
|
+
# | | Send me letters | false |
|
13
|
+
# | | radio 1 | true |
|
14
|
+
#
|
15
|
+
When(/^(?:|I )fill in the following:( within (?:.*))?$/,
|
16
|
+
&Pickles::StepDef.define_table_step([2, 3]) do |label, value, within|
|
17
|
+
|
18
|
+
FillIN::Factory.new(label, value, within: current_within).call.call
|
19
|
+
|
20
|
+
end)
|
21
|
+
|
22
|
+
# Waiter.wait_for_ajax
|
23
|
+
#
|
24
|
+
# if fields.headers.length == 3
|
25
|
+
# current_within = within
|
26
|
+
#
|
27
|
+
# rows = fields.rows.unshift(fields.headers)
|
28
|
+
#
|
29
|
+
# rows.each do |(within, label, value)|
|
30
|
+
# case within
|
31
|
+
# when Helpers::Regex::WITHIN
|
32
|
+
# current_within = Pickles.detect_node($1, $2, within: within)
|
33
|
+
# when "-"
|
34
|
+
# current_within = within
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# if label['pry']
|
38
|
+
# label['pry'] = ''
|
39
|
+
#
|
40
|
+
# pry binding
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# FillIN::Factory.new(label, value, within: current_within).call.call
|
44
|
+
# end
|
45
|
+
# elsif fields.headers.length == 2
|
46
|
+
# fields.rows_hash.each do |label, value|
|
47
|
+
# pry binding if label['pry']
|
48
|
+
# FillIN::Factory.new(label, value, within: within).call.call
|
49
|
+
# end
|
50
|
+
# else
|
51
|
+
# raise(ArgumentError, 'Unsupported table type. Must contain 2 or 3 columns')
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# end
|
55
|
+
|
56
|
+
# When /^(?:|I ) select "([^"]*)" from "([^"]*)"( within (?:.*))?$/ do |value, label, within|
|
57
|
+
# FillIN::Select.new(label, value, within).call
|
58
|
+
# end
|
59
|
+
|
60
|
+
When /^(?:|I )(fill|select|unselect)(?: "([^"]*)")?(?: with "([^"]*)")?( within (?:.*))?$/ do |type, labels, value, within|
|
61
|
+
if type == 'select' && value.present?
|
62
|
+
FillIN::Select.new(labels, value, within).call
|
63
|
+
else
|
64
|
+
labels.split(/\s*\|\s*/).each do |label|
|
65
|
+
FillIN::Factory.new(label, value, within: within).call.call
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"( within (?:.*))?$/ do |file_name, label, within|
|
71
|
+
FillIN::Input.new(label, file_name, within).call
|
72
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class FillIN::ComplexInput
|
2
|
+
|
3
|
+
include Pickles
|
4
|
+
|
5
|
+
def initialize(label, value, within)
|
6
|
+
@label = label
|
7
|
+
@value = value
|
8
|
+
@within = within || Capybara.current_session
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
@value.split(/\s*:\s*/).each.with_index do |value, index|
|
13
|
+
input_locator = "#{label}[#{index}]"
|
14
|
+
|
15
|
+
FillIN::Input.new(input_locator, value, @within)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class FillIN::Factory
|
2
|
+
|
3
|
+
TAG = /^(.+\S+)\s*\((.*)\)$/
|
4
|
+
|
5
|
+
def initialize(label, value, within: nil)
|
6
|
+
@label = label
|
7
|
+
@value = value
|
8
|
+
@within = within
|
9
|
+
end
|
10
|
+
|
11
|
+
def call
|
12
|
+
if !@value.nil? && @value[':']
|
13
|
+
step = FillIN::ComplexInput
|
14
|
+
elsif @label =~ TAG
|
15
|
+
@label = $1
|
16
|
+
tag = $2
|
17
|
+
step = Pickles.config.step_by_tag(tag) || FillIN::Input
|
18
|
+
else
|
19
|
+
step = FillIN::Input
|
20
|
+
end
|
21
|
+
|
22
|
+
step.new(@label, @value, @within)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class FillIN::Input
|
2
|
+
|
3
|
+
def initialize(label, value, within)
|
4
|
+
@label = label
|
5
|
+
@value = value
|
6
|
+
@within = within || Capybara.current_session
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
case input.native.attribute("type")
|
11
|
+
|
12
|
+
when "radio", "checkbox"
|
13
|
+
Pickles.select_input(input, @value)
|
14
|
+
when "file"
|
15
|
+
Pickles.attach_file(input, @value)
|
16
|
+
else # password email tel ...
|
17
|
+
input.set(@value)
|
18
|
+
end
|
19
|
+
|
20
|
+
input
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def input
|
26
|
+
@input ||= Pickles.find_input(@label, within: @within)
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class FillIN::Select
|
2
|
+
|
3
|
+
def initialize(label, value, within)
|
4
|
+
@label = label
|
5
|
+
@value = value
|
6
|
+
@within = within || Capybara.current_session
|
7
|
+
end
|
8
|
+
|
9
|
+
def call
|
10
|
+
input = FillIN::Input.new(@label, @value, @within).call
|
11
|
+
|
12
|
+
text, selector = NodeTextLookup.lookup_values(value)
|
13
|
+
item_xpath = selector.(text)
|
14
|
+
|
15
|
+
Waiter.wait do
|
16
|
+
input.find(:xpath, "./ancestor::*[#{item_xpath}][1]/#{item_xpath}").click
|
17
|
+
end
|
18
|
+
|
19
|
+
Pickles.blur(input)
|
20
|
+
|
21
|
+
Waiter.wait_for_ajax
|
22
|
+
|
23
|
+
input
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :label, :value
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
within_reg = /\A\s*(.*)?\s*(?:["|'](.*?)["|'])?\s*\Z/
|
2
|
+
|
3
|
+
Transform(/(within .*)$/) do |within_info|
|
4
|
+
splitted = within_info.split('within').reject(&:blank?)
|
5
|
+
|
6
|
+
splitted.reverse_each.each_with_object(page) do |info, within|
|
7
|
+
captures = Helpers::Regex::WITHIN.match(info).captures
|
8
|
+
el_alias = captures[0]
|
9
|
+
locator = captures[1]
|
10
|
+
|
11
|
+
Pickles.detect_node(el_alias, locator, within: within)
|
12
|
+
end
|
13
|
+
end
|
data/lib/pickles.rb
ADDED
data/pickles.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'cucumber/pickles/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "pickles"
|
8
|
+
s.version = Pickles::VERSION
|
9
|
+
s.authors = ["vs"]
|
10
|
+
s.email = ["vshaveyko@gmail.com"]
|
11
|
+
|
12
|
+
s.summary = %q{Capybara test helpers}
|
13
|
+
s.description = %q{Set of common everyday steps with shortcuts}
|
14
|
+
s.homepage = "https://github.com/vshaveyko/pickles"
|
15
|
+
s.license = "MIT"
|
16
|
+
|
17
|
+
# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
|
18
|
+
# to allow pushing to a single host or delete this section to allow pushing to any host.
|
19
|
+
# if s.respond_to?(:metadata)
|
20
|
+
# s.metadata['allowed_push_host'] = "TODO: Set to 'http://mygemserver.com'"
|
21
|
+
# else
|
22
|
+
# raise "RubyGems 2.0 or newer is required to protect against " \
|
23
|
+
# "public gem pushes."
|
24
|
+
# end
|
25
|
+
|
26
|
+
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|s|features)/}) }
|
27
|
+
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
|
+
|
29
|
+
s.add_dependency 'capybara', '>= 1.1.2'
|
30
|
+
|
31
|
+
s.require_paths = ["lib"]
|
32
|
+
|
33
|
+
s.add_development_dependency "bundler", "~> 1.13"
|
34
|
+
s.add_development_dependency "rake", "~> 10.0"
|
35
|
+
s.add_development_dependency "rs", "~> 3.0"
|
36
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe '#NodeFinders', :Helpers, :NodeFinders do
|
4
|
+
|
5
|
+
# let(:finder) { Object.new.extend(NodeFinders) }
|
6
|
+
# let(:stub) { Object.new }
|
7
|
+
|
8
|
+
describe '#find_node', :find_node do
|
9
|
+
|
10
|
+
before(:each) { @session.visit('/with_html') }
|
11
|
+
|
12
|
+
it 'raises ambgious error' do
|
13
|
+
expect { Pickles.find_node('A link') }.to raise_error(Pickles::Ambiguous)
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'Finds element with exact text with = ' do
|
17
|
+
expect(Pickles.find_node('=A link')).to have_attributes path: "/html/body/p[3]/a[3]"
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'Finds element by index with [1]' do
|
21
|
+
expect(Pickles.find_node('A link[1]')).to have_attributes path: "/html/body/p[3]/a[2]"
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#find_input', :find_input do
|
27
|
+
|
28
|
+
class TestApp
|
29
|
+
|
30
|
+
get '/with/inputs' do
|
31
|
+
<<~HTML
|
32
|
+
<!-- Case1 -->
|
33
|
+
<div> <div> <span>LABEL_INPUT</span> </div>
|
34
|
+
<input type="text" value="LABEL_INPUT_VALUE" />
|
35
|
+
</div>
|
36
|
+
|
37
|
+
<!-- Case2 -->
|
38
|
+
<input type="text" placeholder="PLACEHOLDER_INPUT" value="PLACEHOLDER_INPUT_VALUE" />
|
39
|
+
|
40
|
+
<!-- Case3 -->
|
41
|
+
<div> <div> <span>TEXTAREA_LABEL</span> </div>
|
42
|
+
<textarea>TEXTAREA_VALUE</textarea>
|
43
|
+
</div>
|
44
|
+
|
45
|
+
<!-- Case4 -->
|
46
|
+
<div contenteditable="true" placeholder="CONTENTEDITABLE_PLACEHOLDER">
|
47
|
+
CONTENTEDITABLE_PLACEHOLDER_VALUE
|
48
|
+
</div>
|
49
|
+
|
50
|
+
<!-- Case5 -->
|
51
|
+
<div> <div> <span>CONTENTEDITABLE_LABEL</span> </div>
|
52
|
+
<div contenteditable="true">
|
53
|
+
CONTENTEDITABLE_LABEL_VALUE
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
HTML
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
before(:each) { @session.visit('/with/inputs') }
|
62
|
+
|
63
|
+
describe 'input' do
|
64
|
+
it 'by span label' do
|
65
|
+
expect(
|
66
|
+
Pickles.find_input('LABEL_INPUT')
|
67
|
+
).to have_attributes value: "LABEL_INPUT_VALUE"
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'by placeholder' do
|
71
|
+
expect(
|
72
|
+
Pickles.find_input('PLACEHOLDER_INPUT')
|
73
|
+
).to have_attributes value: "PLACEHOLDER_INPUT_VALUE"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe 'textarea' do
|
78
|
+
it 'by span label' do
|
79
|
+
expect(
|
80
|
+
Pickles.find_input('TEXTAREA_LABEL')
|
81
|
+
).to have_attributes value: "TEXTAREA_VALUE"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'contenteditable' do
|
86
|
+
it 'by placeholder' do
|
87
|
+
expect(
|
88
|
+
Pickles.find_input('CONTENTEDITABLE_PLACEHOLDER')
|
89
|
+
).to have_attributes text: "CONTENTEDITABLE_PLACEHOLDER_VALUE"
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'by span label' do
|
93
|
+
expect(
|
94
|
+
Pickles.find_input('CONTENTEDITABLE_LABEL')
|
95
|
+
).to have_attributes text: "CONTENTEDITABLE_LABEL_VALUE"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
describe '#detect_node', :detect_node do
|
102
|
+
|
103
|
+
class TestApp
|
104
|
+
|
105
|
+
get '/with/divs' do
|
106
|
+
<<~HTML
|
107
|
+
<!-- Case1 -->
|
108
|
+
<div class="TEST_CLASS">TEST_CLASS_VALUE</div>
|
109
|
+
|
110
|
+
<!-- Case2 -->
|
111
|
+
<div> <div id="TEST_ID">TEST_ID_VALUE</div> </div>
|
112
|
+
|
113
|
+
<!-- Case3 -->
|
114
|
+
<custom-tag>CUSTOM_TAG_VALUE[1]</custom-tag>
|
115
|
+
<custom-tag>CUSTOM_TAG_VALUE[2]</custom-tag>
|
116
|
+
HTML
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
before(:each) { @session.visit('/with/divs') }
|
122
|
+
|
123
|
+
it 'finds by xpath_node_map' do
|
124
|
+
Pickles.configure do |c|
|
125
|
+
c.xpath_node_map = {
|
126
|
+
xpath_test_node: "//div[@id='TEST_ID']"
|
127
|
+
}
|
128
|
+
end
|
129
|
+
|
130
|
+
expect(
|
131
|
+
Pickles.detect_node(:xpath_test_node)
|
132
|
+
).to have_attributes text: 'TEST_ID_VALUE'
|
133
|
+
end
|
134
|
+
|
135
|
+
it 'finds by css_node_map' do
|
136
|
+
Pickles.configure do |c|
|
137
|
+
c.css_node_map = {
|
138
|
+
css_test_node: ".TEST_CLASS"
|
139
|
+
}
|
140
|
+
end
|
141
|
+
|
142
|
+
expect(
|
143
|
+
Pickles.detect_node(:css_test_node)
|
144
|
+
).to have_attributes text: 'TEST_CLASS_VALUE'
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'fallbacks to given value as css tag' do
|
148
|
+
expect(
|
149
|
+
Pickles.detect_node('custom-tag[1]')
|
150
|
+
).to have_attributes text: 'CUSTOM_TAG_VALUE[1]'
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'cucumber/pickles/helpers/waiter'
|
4
|
+
|
5
|
+
RSpec.describe '#Waiter' do
|
6
|
+
|
7
|
+
it 'Return 0 pending requests on page load' do
|
8
|
+
@session.visit('/with_js')
|
9
|
+
|
10
|
+
expect(Waiter.pending_ajax_requests_num).to eq 0
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'Return 1 pending requests with open request' do
|
14
|
+
@session.visit('/with_js')
|
15
|
+
|
16
|
+
@session.evaluate_script(
|
17
|
+
<<~JS
|
18
|
+
new XMLHttpRequest().open("GET", '')
|
19
|
+
JS
|
20
|
+
)
|
21
|
+
|
22
|
+
expect(Waiter.pending_ajax_requests_num).to eq 1
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'Return 0 pending requests with closed request' do
|
26
|
+
@session.visit('/with_js')
|
27
|
+
|
28
|
+
@session.evaluate_script("window.req = new XMLHttpRequest();")
|
29
|
+
|
30
|
+
@session.evaluate_script("window.req.open('GET', '', true);")
|
31
|
+
|
32
|
+
expect(Waiter.pending_ajax_requests_num).to eq 1
|
33
|
+
|
34
|
+
@session.evaluate_script("window.req.send();")
|
35
|
+
|
36
|
+
@session.evaluate_script("window.req.abort();")
|
37
|
+
|
38
|
+
expect(Waiter.pending_ajax_requests_num).to eq 0
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|