turnip-extra_steps 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 +10 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/Guardfile +77 -0
- data/LICENSE +21 -0
- data/README.md +39 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/config.ru +16 -0
- data/lib/turnip/extra_steps.rb +8 -0
- data/lib/turnip/extra_steps/all_steps.rb +8 -0
- data/lib/turnip/extra_steps/development_steps.rb +32 -0
- data/lib/turnip/extra_steps/email_steps.rb +148 -0
- data/lib/turnip/extra_steps/file_attachment_steps.rb +43 -0
- data/lib/turnip/extra_steps/support.rb +2 -0
- data/lib/turnip/extra_steps/support/custom_matchers.rb +30 -0
- data/lib/turnip/extra_steps/support/mail_finder.rb +59 -0
- data/lib/turnip/extra_steps/support/path_selector_fallbacks.rb +38 -0
- data/lib/turnip/extra_steps/support/step_fallback.rb +13 -0
- data/lib/turnip/extra_steps/support/tolerance_for_selenium_sync_issues.rb +42 -0
- data/lib/turnip/extra_steps/support/version.rb +3 -0
- data/lib/turnip/extra_steps/support/web_steps_helpers.rb +114 -0
- data/lib/turnip/extra_steps/table_steps.rb +187 -0
- data/lib/turnip/extra_steps/timecop_steps.rb +79 -0
- data/lib/turnip/extra_steps/version.rb +5 -0
- data/lib/turnip/extra_steps/web_steps.rb +760 -0
- data/turnip-extra_steps.gemspec +42 -0
- metadata +354 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module Turnip::ExtraSteps::Support::CustomMatchers
|
2
|
+
rspec = defined?(RSpec) ? RSpec : Spec
|
3
|
+
|
4
|
+
rspec::Matchers.define :contain_with_wildcards do |expected_string|
|
5
|
+
match do |field_value|
|
6
|
+
@field_value = field_value.to_s
|
7
|
+
@expected_string = expected_string
|
8
|
+
regex_parts = expected_string.to_s.split('*', -1).collect { |part| Regexp.escape(part) }
|
9
|
+
|
10
|
+
@field_value =~ /\A#{regex_parts.join(".*")}\z/m
|
11
|
+
end
|
12
|
+
|
13
|
+
failure_message do
|
14
|
+
"The field's content #{@field_value.inspect} did not match #{@expected_string.inspect}"
|
15
|
+
end
|
16
|
+
|
17
|
+
failure_message_when_negated do
|
18
|
+
"The field's content #{@field_value.inspect} matches #{@expected_string.inspect}"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
rspec::Matchers.define :be_sorted do
|
23
|
+
match do |array|
|
24
|
+
sort_method = defined?(array.natural_sort) ? :natural_sort : :sort
|
25
|
+
|
26
|
+
array == array.send(sort_method)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
class MailFinder
|
4
|
+
class << self
|
5
|
+
|
6
|
+
attr_accessor :user_identity
|
7
|
+
|
8
|
+
def find(conditions)
|
9
|
+
filename_method = Rails::VERSION::MAJOR < 3 ? 'original_filename' : 'filename'
|
10
|
+
ActionMailer::Base.deliveries.detect do |mail|
|
11
|
+
mail_body = email_text_body(mail)
|
12
|
+
[ conditions[:to].nil? || mail.to.include?(resolve_email conditions[:to]),
|
13
|
+
conditions[:cc].nil? || mail.cc.andand.include?(resolve_email conditions[:cc]),
|
14
|
+
conditions[:bcc].nil? || mail.bcc.andand.include?(resolve_email conditions[:bcc]),
|
15
|
+
conditions[:from].nil? || mail.from.include?(resolve_email conditions[:from]),
|
16
|
+
conditions[:reply_to].nil? || mail.reply_to.include?(resolve_email conditions[:reply_to]),
|
17
|
+
conditions[:subject].nil? || mail.subject.include?(conditions[:subject]),
|
18
|
+
conditions[:body].nil? || mail_body.include?(conditions[:body]),
|
19
|
+
conditions[:attachments].nil? || conditions[:attachments].split(/\s*,\s*/).sort == Array(mail.attachments).collect(&:"#{filename_method}").sort
|
20
|
+
].all?
|
21
|
+
end.tap do |mail|
|
22
|
+
log(mail)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def resolve_email(identity)
|
27
|
+
if identity =~ /^.+\@.+$/
|
28
|
+
identity
|
29
|
+
else
|
30
|
+
User.send("find_by_#{user_identity || 'email'}!", identity).email
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def log(mail)
|
35
|
+
if mail.present?
|
36
|
+
if defined?(Rails)
|
37
|
+
File.open(Rails.root.join("log/test_mails.log"), "a") do |file|
|
38
|
+
file << "From: #{mail.from}\n"
|
39
|
+
file << "To: #{mail.to.join(', ')}\n" if mail.to
|
40
|
+
file << "Subject: #{mail.subject}\n\n"
|
41
|
+
file << email_text_body(mail)
|
42
|
+
file << "\n-------------------------\n\n"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def email_text_body(mail)
|
49
|
+
if mail.parts.any?
|
50
|
+
mail.parts.select { |part| part.content_type.include?('text/') }.collect(&:decoded).join("\n")
|
51
|
+
elsif mail.body.respond_to? :raw_source
|
52
|
+
mail.body.raw_source
|
53
|
+
else
|
54
|
+
mail.body
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Turnip::ExtraSteps::Support::PathSelectorFallbacks
|
4
|
+
def _selector_for(locator)
|
5
|
+
if respond_to?(:selector_for)
|
6
|
+
selector_for(locator)
|
7
|
+
elsif locator =~ /^"(.+)"$/
|
8
|
+
$1
|
9
|
+
else
|
10
|
+
raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
|
11
|
+
"Add and require a selectors.rb file (compare /examples/selectors.rb)"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def _path_to(page_name)
|
16
|
+
if respond_to?(:path_to)
|
17
|
+
path_to(page_name)
|
18
|
+
else
|
19
|
+
begin
|
20
|
+
page_name =~ /^the (.*) page$/
|
21
|
+
path_components = $1.split(/\s+/)
|
22
|
+
self.send(path_components.push('path').join('_').to_sym)
|
23
|
+
rescue NoMethodError, ArgumentError
|
24
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
25
|
+
"Add and require a paths.rb file (compare /examples/paths.rb)"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
RSpec.configure { |c| c.include Turnip::ExtraSteps::Support::PathSelectorFallbacks}
|
31
|
+
|
32
|
+
module Turnip::ExtraSteps::Support::WithinHelpers
|
33
|
+
def with_scope(locator)
|
34
|
+
locator ? within(*_selector_for(locator)) { yield } : yield
|
35
|
+
end
|
36
|
+
end
|
37
|
+
RSpec.configure { |c| c.include Turnip::ExtraSteps::Support::WithinHelpers}
|
38
|
+
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
module Turnip::ExtraSteps::Support::ToleranceForSeleniumSyncIssues
|
4
|
+
RETRY_ERRORS = %w[
|
5
|
+
Capybara::ElementNotFound
|
6
|
+
Spec::Expectations::ExpectationNotMetError
|
7
|
+
RSpec::Expectations::ExpectationNotMetError
|
8
|
+
Minitest::Assertion
|
9
|
+
Capybara::Poltergeist::ClickFailed
|
10
|
+
Capybara::ExpectationNotMet
|
11
|
+
Selenium::WebDriver::Error::StaleElementReferenceError
|
12
|
+
Selenium::WebDriver::Error::NoAlertPresentError
|
13
|
+
Selenium::WebDriver::Error::ElementNotVisibleError
|
14
|
+
Selenium::WebDriver::Error::NoSuchFrameError
|
15
|
+
Selenium::WebDriver::Error::NoAlertPresentError
|
16
|
+
]
|
17
|
+
|
18
|
+
# This is similiar but not entirely the same as Capybara::Node::Base#wait_until or Capybara::Session#wait_until
|
19
|
+
def patiently(seconds=Capybara.default_wait_time, &block)
|
20
|
+
old_wait_time = Capybara.default_wait_time
|
21
|
+
# dont make nested wait_untils use up all the alloted time
|
22
|
+
Capybara.default_wait_time = 0 # for we are a jealous gem
|
23
|
+
if page.driver.wait?
|
24
|
+
start_time = Time.now
|
25
|
+
begin
|
26
|
+
block.call
|
27
|
+
rescue Exception => e
|
28
|
+
raise e unless RETRY_ERRORS.include?(e.class.name)
|
29
|
+
raise e if (Time.now - start_time) >= seconds
|
30
|
+
sleep(0.05)
|
31
|
+
raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if Time.now == start_time
|
32
|
+
retry
|
33
|
+
end
|
34
|
+
else
|
35
|
+
block.call
|
36
|
+
end
|
37
|
+
ensure
|
38
|
+
Capybara.default_wait_time = old_wait_time
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
RSpec.configure { |c| c.include Turnip::ExtraSteps::Support::ToleranceForSeleniumSyncIssues}
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module Turnip::ExtraSteps::Support::WebStepsHelpers
|
2
|
+
|
3
|
+
def assert_visible(options)
|
4
|
+
visibility_test(options.merge(:expectation => :visible))
|
5
|
+
end
|
6
|
+
|
7
|
+
def assert_hidden(options)
|
8
|
+
visibility_test(options.merge(:expectation => :hidden))
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def visibility_test(options)
|
14
|
+
case Capybara::current_driver
|
15
|
+
when :selenium, :webkit, :poltergeist
|
16
|
+
detect_visibility_with_js(options)
|
17
|
+
else
|
18
|
+
detect_visibility_with_capybara(options)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def detect_visibility_with_js(options)
|
23
|
+
patiently do
|
24
|
+
|
25
|
+
selector_javascript = if options.has_key?(:selector)
|
26
|
+
options[:selector].to_json
|
27
|
+
else
|
28
|
+
"':contains(' + " + options[:text].to_json + " + ')'"
|
29
|
+
end
|
30
|
+
|
31
|
+
visibility_detecting_javascript = %[
|
32
|
+
(function() {
|
33
|
+
var selector = #{selector_javascript};
|
34
|
+
var jqueryLoaded = (typeof jQuery != 'undefined');
|
35
|
+
|
36
|
+
function findCandidates() {
|
37
|
+
if (jqueryLoaded) {
|
38
|
+
return $(selector);
|
39
|
+
} else {
|
40
|
+
return $$(selector);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
|
44
|
+
function isExactCandidate(candidate) {
|
45
|
+
if (jqueryLoaded) {
|
46
|
+
return $(candidate).find(selector).length == 0;
|
47
|
+
} else {
|
48
|
+
return candidate.select(selector).length == 0;
|
49
|
+
}
|
50
|
+
}
|
51
|
+
|
52
|
+
function elementVisible(element) {
|
53
|
+
if (jqueryLoaded) {
|
54
|
+
return $(element).is(':visible');
|
55
|
+
} else {
|
56
|
+
return element.offsetWidth > 0 && element.offsetHeight > 0;
|
57
|
+
}
|
58
|
+
}
|
59
|
+
|
60
|
+
var candidates = findCandidates();
|
61
|
+
|
62
|
+
if (candidates.length == 0) {
|
63
|
+
throw("Selector not found in page: " + selector);
|
64
|
+
}
|
65
|
+
|
66
|
+
for (var i = 0; i < candidates.length; i++) {
|
67
|
+
var candidate = candidates[i];
|
68
|
+
if (isExactCandidate(candidate) && elementVisible(candidate)) {
|
69
|
+
return true;
|
70
|
+
}
|
71
|
+
}
|
72
|
+
return false;
|
73
|
+
|
74
|
+
})();
|
75
|
+
]
|
76
|
+
|
77
|
+
visibility_detecting_javascript.gsub!(/\n/, ' ')
|
78
|
+
if options[:expectation] == :visible
|
79
|
+
expect(page.evaluate_script(visibility_detecting_javascript)).to be_truthy
|
80
|
+
else
|
81
|
+
expect(page.evaluate_script(visibility_detecting_javascript)).to be_falsy
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def detect_visibility_with_capybara(options)
|
87
|
+
begin
|
88
|
+
old_ignore_hidden_elements = Capybara.ignore_hidden_elements
|
89
|
+
Capybara.ignore_hidden_elements = false
|
90
|
+
if options.has_key?(:selector)
|
91
|
+
expect(page).to have_css(options[:selector])
|
92
|
+
have_hidden_tag = have_css(".hidden #{options[:selector]}, .invisible #{selector_or_text}, [style~=\"display: none\"] #{options[:selector]}")
|
93
|
+
if options[:expectation] == :hidden
|
94
|
+
expect(page).to have_hidden_tag
|
95
|
+
else
|
96
|
+
expect(page).not_to have_hidden_tag
|
97
|
+
end
|
98
|
+
else
|
99
|
+
expect(page).to have_css('*', :text => options[:text])
|
100
|
+
have_hidden_text = have_css('.hidden, .invisible, [style~="display: none"]', :text => options[:text])
|
101
|
+
if options[:expectation] == :hidden
|
102
|
+
expect(page).to have_hidden_text
|
103
|
+
else
|
104
|
+
expect(page).not_to have_hidden_text
|
105
|
+
end
|
106
|
+
end
|
107
|
+
ensure
|
108
|
+
Capybara.ignore_hidden_elements = old_ignore_hidden_elements
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
RSpec.configure { |c| c.include Turnip::ExtraSteps::Support::WebStepsHelpers}
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
require 'turnip/extra_steps/support/tolerance_for_selenium_sync_issues'
|
4
|
+
|
5
|
+
module Turnip::ExtraSteps::Support::TableStepsHelper
|
6
|
+
module ArrayMethods
|
7
|
+
|
8
|
+
def find_row(expected_row)
|
9
|
+
find_index do |row|
|
10
|
+
expected_row.all? do |expected_column|
|
11
|
+
first_column = row.find_index do |column|
|
12
|
+
content = normalize_content(column.content)
|
13
|
+
expected_content = normalize_content(expected_column)
|
14
|
+
matching_parts = expected_content.split(/\s*\*\s*/, -1).collect { |part| Regexp.escape(part) }
|
15
|
+
matching_expression = /\A#{matching_parts.join(".*")}\z/
|
16
|
+
content =~ matching_expression
|
17
|
+
end
|
18
|
+
if first_column.nil?
|
19
|
+
false
|
20
|
+
else
|
21
|
+
row = row[(first_column + 1)..-1]
|
22
|
+
true
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def normalize_content(content)
|
29
|
+
nbsp = " "
|
30
|
+
content.gsub(/[\r\n\t]+/, ' ').gsub(nbsp, ' ').gsub(/ {2,}/, ' ').strip
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
rspec = defined?(RSpec) ? RSpec : Spec
|
36
|
+
|
37
|
+
rspec::Matchers.define :contain_table do |*args|
|
38
|
+
match do |tables|
|
39
|
+
@last_unmatched_row = nil
|
40
|
+
@extra_rows = nil
|
41
|
+
@best_rows_matched = -1
|
42
|
+
options = args.extract_options!
|
43
|
+
expected_table = args.first
|
44
|
+
tables.any? do |table|
|
45
|
+
skipped_rows = []
|
46
|
+
rows_matched = 0
|
47
|
+
match = expected_table.all? do |expected_row|
|
48
|
+
if @best_rows_matched < rows_matched
|
49
|
+
@last_unmatched_row = expected_row
|
50
|
+
@best_rows_matched = rows_matched
|
51
|
+
end
|
52
|
+
table.extend ArrayMethods
|
53
|
+
first_row = table.find_row(expected_row)
|
54
|
+
if first_row.nil?
|
55
|
+
false
|
56
|
+
else
|
57
|
+
rows_matched += 1
|
58
|
+
if options[:unordered]
|
59
|
+
table.delete_at(first_row)
|
60
|
+
else
|
61
|
+
skipped_rows += table[0...first_row]
|
62
|
+
table = table[(first_row + 1)..-1]
|
63
|
+
end
|
64
|
+
true
|
65
|
+
end
|
66
|
+
end
|
67
|
+
remaining_rows = skipped_rows + table
|
68
|
+
if match and options[:exactly] and not remaining_rows.empty?
|
69
|
+
@extra_rows = remaining_rows
|
70
|
+
match = false
|
71
|
+
end
|
72
|
+
match
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
failure_message do
|
77
|
+
if @extra_rows
|
78
|
+
"Found the following extra row: #{@extra_rows.first.collect(&:content).collect(&:squish).inspect}"
|
79
|
+
elsif @last_unmatched_row
|
80
|
+
"Could not find the following row: #{@last_unmatched_row.inspect}"
|
81
|
+
else
|
82
|
+
"Could not find a table"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
failure_message_when_negated do
|
87
|
+
"Found the complete table: #{args.first.inspect}."
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def parse_table(table)
|
92
|
+
if table.is_a?(String)
|
93
|
+
table.sub!(/^\n/, '')
|
94
|
+
# multiline string. split it assuming a format like cucumber tables have.
|
95
|
+
table.split(/\n/).collect do |line|
|
96
|
+
line.sub!(/^\|/, '')
|
97
|
+
line.sub!(/\|$/, '')
|
98
|
+
line.gsub!(/^\ +/, '')
|
99
|
+
line.gsub!(/\ +$/, '')
|
100
|
+
line.split(/\s*\|\s*/)
|
101
|
+
end
|
102
|
+
else
|
103
|
+
# vanilla cucumber table.
|
104
|
+
table.raw
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
RSpec.configure { |c| c.include Turnip::ExtraSteps::Support::TableStepsHelper}
|
109
|
+
|
110
|
+
|
111
|
+
# Check the content of tables in your HTML.
|
112
|
+
#
|
113
|
+
# See [this article](https://makandracards.com/makandra/763-cucumber-step-to-match-table-rows-with-capybara) for details.
|
114
|
+
#Then /^I should( not)? see a table with (exactly )?the following rows( in any order)?:?$/ do |negate, exactly, unordered, expected_table|
|
115
|
+
step "I :whether_to see a table with the following rows:" do |positive, expected_table|
|
116
|
+
patiently do
|
117
|
+
document = Nokogiri::HTML(page.body)
|
118
|
+
tables = document.xpath('//table').collect { |table| table.xpath('.//tr').collect { |row| row.xpath('.//th|td') } }
|
119
|
+
parsed_table = parse_table(expected_table)
|
120
|
+
|
121
|
+
options = { exactly: false , unordered: false }
|
122
|
+
|
123
|
+
if positive
|
124
|
+
expect(tables).to contain_table(parsed_table, options)
|
125
|
+
#tables.should contain_table(parsed_table, options)
|
126
|
+
else
|
127
|
+
expect(tables).not_to contain_table(parsed_table, options)
|
128
|
+
#tables.should_not contain_table(parsed_table, options)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
step "I :whether_to see a table with exactly the following rows:" do |positive, expected_table|
|
134
|
+
patiently do
|
135
|
+
document = Nokogiri::HTML(page.body)
|
136
|
+
tables = document.xpath('//table').collect { |table| table.xpath('.//tr').collect { |row| row.xpath('.//th|td') } }
|
137
|
+
parsed_table = parse_table(expected_table)
|
138
|
+
|
139
|
+
options = { exactly: true, unordered: false }
|
140
|
+
|
141
|
+
if positive
|
142
|
+
expect(tables).to contain_table(parsed_table, options)
|
143
|
+
#tables.should contain_table(parsed_table, options)
|
144
|
+
else
|
145
|
+
expect(tables).not_to contain_table(parsed_table, options)
|
146
|
+
#tables.should_not contain_table(parsed_table, options)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
step "I :whether_to see a table with the following rows in any order:" do |positive, expected_table|
|
152
|
+
patiently do
|
153
|
+
document = Nokogiri::HTML(page.body)
|
154
|
+
tables = document.xpath('//table').collect { |table| table.xpath('.//tr').collect { |row| row.xpath('.//th|td') } }
|
155
|
+
parsed_table = parse_table(expected_table)
|
156
|
+
|
157
|
+
options = { exactly: false, unordered: true }
|
158
|
+
|
159
|
+
if positive
|
160
|
+
expect(tables).to contain_table(parsed_table, options)
|
161
|
+
#tables.should contain_table(parsed_table, options)
|
162
|
+
else
|
163
|
+
expect(tables).not_to contain_table(parsed_table, options)
|
164
|
+
#tables.should_not contain_table(parsed_table, options)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
step "I :whether_to see a table with exactly the following rows in any order:" do |positive, expected_table|
|
170
|
+
patiently do
|
171
|
+
document = Nokogiri::HTML(page.body)
|
172
|
+
tables = document.xpath('//table').collect { |table| table.xpath('.//tr').collect { |row| row.xpath('.//th|td') } }
|
173
|
+
parsed_table = parse_table(expected_table)
|
174
|
+
|
175
|
+
options = { exactly: true, unordered: true }
|
176
|
+
|
177
|
+
if positive
|
178
|
+
expect(tables).to contain_table(parsed_table, options)
|
179
|
+
#tables.should contain_table(parsed_table, options)
|
180
|
+
else
|
181
|
+
expect(tables).not_to contain_table(parsed_table, options)
|
182
|
+
#tables.should_not contain_table(parsed_table, options)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
|