spreewald 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 +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +82 -0
- data/Rakefile +2 -0
- data/examples/paths.rb +30 -0
- data/examples/selectors.rb +41 -0
- data/lib/spreewald.rb +2 -0
- data/lib/spreewald/all_steps.rb +6 -0
- data/lib/spreewald/development_steps.rb +16 -0
- data/lib/spreewald/email_steps.rb +64 -0
- data/lib/spreewald/table_steps.rb +102 -0
- data/lib/spreewald/timecop_steps.rb +28 -0
- data/lib/spreewald/web_steps.rb +334 -0
- data/lib/spreewald_support/github.rb +5 -0
- data/lib/spreewald_support/mail_finder.rb +42 -0
- data/lib/spreewald_support/path_selector_fallbacks.rb +36 -0
- data/lib/spreewald_support/tolerance_for_selenium_sync_issues.rb +20 -0
- data/lib/spreewald_support/version.rb +3 -0
- data/spreewald.gemspec +22 -0
- metadata +129 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Tobias Kraze
|
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,82 @@
|
|
1
|
+
# Spreewald
|
2
|
+
|
3
|
+
Spreewald is a collection of useful steps for cucumber. Feel free to fork.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'spreewald'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install spreewald
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Steps are grouped into a number of categories. You can pick and choose single categories by putting something like
|
22
|
+
require 'spreewald/email_steps'
|
23
|
+
into your `support/env.rb`
|
24
|
+
|
25
|
+
Alternatively, you can require everything by doing
|
26
|
+
require 'spreewald/all_steps'
|
27
|
+
|
28
|
+
## Steps
|
29
|
+
|
30
|
+
For a complete list of steps you have to take a look at the step definitions themselves. This is just a rough overview.
|
31
|
+
|
32
|
+
### [development_steps](/makandra/spreewald/blob/master/lib/spreewald/development_steps.rb)
|
33
|
+
|
34
|
+
Some development steps. Supports
|
35
|
+
|
36
|
+
* `Then debugger`
|
37
|
+
* `Then it should work` (marks step as pending)
|
38
|
+
* `@slow-motion` (waits 2 seconds after each step)
|
39
|
+
* `@single-step` (waits for keyboard input after each step)
|
40
|
+
|
41
|
+
|
42
|
+
### [email_steps](/makandra/spreewald/blob/master/lib/spreewald/email_steps.rb)
|
43
|
+
|
44
|
+
Check for the existance of an email with
|
45
|
+
|
46
|
+
Then an email should have been sent with:
|
47
|
+
"""
|
48
|
+
From: max.mustermann@example.com
|
49
|
+
To: john.doe@example.com
|
50
|
+
Subject: Unter anderem der Betreff kann auch "Anführungszeichen" enthalten
|
51
|
+
Body: ...
|
52
|
+
Attachments: ...
|
53
|
+
"""
|
54
|
+
|
55
|
+
You can obviously skip lines.
|
56
|
+
|
57
|
+
After you have used that step, you can also check for content with
|
58
|
+
|
59
|
+
And that mail should have the following lines in the body:
|
60
|
+
"""
|
61
|
+
Jede dieser Text-Zeilen
|
62
|
+
muss irgendwo im Body vorhanden sein
|
63
|
+
"""
|
64
|
+
|
65
|
+
### [table_steps](/makandra/spreewald/blob/master/lib/spreewald/table_steps.rb)
|
66
|
+
|
67
|
+
Check the content of tables in your HTML.
|
68
|
+
|
69
|
+
See [this article](https://makandracards.com/makandra/763-cucumber-step-to-match-table-rows-with-capybara) for details.
|
70
|
+
|
71
|
+
|
72
|
+
### [timecop_steps](/makandra/spreewald/blob/master/lib/spreewald/timecop_steps.rb)
|
73
|
+
|
74
|
+
Steps to travel through time using [Timecop](https://github.com/jtrupiano/timecop).
|
75
|
+
|
76
|
+
See [this article](https://makandracards.com/makandra/1222-useful-cucumber-steps-to-travel-through-time-with-timecop) for details.
|
77
|
+
|
78
|
+
### [web_steps](/makandra/spreewald/blob/master/lib/spreewald/web_steps.rb)
|
79
|
+
|
80
|
+
Most of cucumber-rails' original websteps plus some of our own.
|
81
|
+
|
82
|
+
Note that cucumber-rails deprecated those a while ago (you can see the original deprecation notice at the top of [our web_steps](/makandra/spreewald/blob/master/lib/spreewald/web_steps.rb)). Make up your own mind whether you want to use them or not.
|
data/Rakefile
ADDED
data/examples/paths.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Put this into features/support and require it in your env.rb
|
2
|
+
#
|
3
|
+
module NavigationHelpers
|
4
|
+
# Maps a name to a path. Used by the
|
5
|
+
#
|
6
|
+
# When /^I go to (.+)$/ do |page_name|
|
7
|
+
#
|
8
|
+
# step definition in web_steps.rb
|
9
|
+
#
|
10
|
+
def path_to(page_name)
|
11
|
+
case page_name
|
12
|
+
|
13
|
+
when /^the home\s?page$/
|
14
|
+
root_path
|
15
|
+
|
16
|
+
else
|
17
|
+
begin
|
18
|
+
page_name =~ /^the (.*) page$/
|
19
|
+
path_components = $1.split(/\s+/)
|
20
|
+
self.send(path_components.push('path').join('_').to_sym)
|
21
|
+
rescue NoMethodError, ArgumentError
|
22
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
23
|
+
"Now, go and add a mapping in #{__FILE__}"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
World(NavigationHelpers)
|
30
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Put this into features/support and require it in your env.rb
|
2
|
+
module HtmlSelectorsHelpers
|
3
|
+
# Maps a name to a selector. Used primarily by the
|
4
|
+
#
|
5
|
+
# When /^(.+) within (.+)$/ do |step, scope|
|
6
|
+
#
|
7
|
+
# step definitions in web_steps.rb
|
8
|
+
#
|
9
|
+
def selector_for(locator)
|
10
|
+
case locator
|
11
|
+
|
12
|
+
when "the page"
|
13
|
+
"html > body"
|
14
|
+
|
15
|
+
# Add more mappings here.
|
16
|
+
# Here is an example that pulls values out of the Regexp:
|
17
|
+
#
|
18
|
+
# when /^the (notice|error|info) flash$/
|
19
|
+
# ".flash.#{$1}"
|
20
|
+
|
21
|
+
# You can also return an array to use a different selector
|
22
|
+
# type, like:
|
23
|
+
#
|
24
|
+
# when /the header/
|
25
|
+
# [:xpath, "//header"]
|
26
|
+
|
27
|
+
# This allows you to provide a quoted selector as the scope
|
28
|
+
# for "within" steps as was previously the default for the
|
29
|
+
# web steps:
|
30
|
+
when /^"(.+)"$/
|
31
|
+
$1
|
32
|
+
|
33
|
+
else
|
34
|
+
raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
|
35
|
+
"Now, go and add a mapping in #{__FILE__}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
World(HtmlSelectorsHelpers)
|
41
|
+
|
data/lib/spreewald.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'spreewald_support/mail_finder'
|
2
|
+
|
3
|
+
Before do
|
4
|
+
ActionMailer::Base.deliveries.clear
|
5
|
+
end
|
6
|
+
|
7
|
+
Then /^(an|no) e?mail should have been sent((?: |and|with|from "[^"]+"|to "[^"]+"|the subject "[^"]+"|the body "[^"]+"|the attachments "[^"]+")+)$/ do |mode, query|
|
8
|
+
conditions = {}
|
9
|
+
conditions[:to] = $1 if query =~ /to "([^"]+)"/
|
10
|
+
conditions[:cc] = $1 if query =~ / cc "([^"]+)"/
|
11
|
+
conditions[:bcc] = $1 if query =~ /bcc "([^"]+)"/
|
12
|
+
conditions[:from] = $1 if query =~ /from "([^"]+)"/
|
13
|
+
conditions[:subject] = $1 if query =~ /the subject "([^"]+)"/
|
14
|
+
conditions[:body] = $1 if query =~ /the body "([^"]+)"/
|
15
|
+
conditions[:attachments] = $1 if query =~ /the attachments "([^"]+)"/
|
16
|
+
@mail = MailFinder.find(conditions)
|
17
|
+
expectation = mode == 'no' ? 'should_not' : 'should'
|
18
|
+
@mail.send(expectation, be_present)
|
19
|
+
end
|
20
|
+
|
21
|
+
When /^I follow the (first|second|third)? ?link in the e?mail$/ do |index_in_words|
|
22
|
+
mail = @mail || ActionMailer::Base.deliveries.last
|
23
|
+
index = { nil => 0, 'first' => 0, 'second' => 1, 'third' => 2 }[index_in_words]
|
24
|
+
visit mail.body.scan(Patterns::URL)[index][2]
|
25
|
+
end
|
26
|
+
|
27
|
+
Then /^no e?mail should have been sent$/ do
|
28
|
+
ActionMailer::Base.deliveries.should be_empty
|
29
|
+
end
|
30
|
+
|
31
|
+
Then /^I should see "([^\"]*)" in the e?mail$/ do |text|
|
32
|
+
ActionMailer::Base.deliveries.last.body.should include(text)
|
33
|
+
end
|
34
|
+
|
35
|
+
Then /^show me the e?mails$/ do
|
36
|
+
ActionMailer::Base.deliveries.each do |mail|
|
37
|
+
p [mail.from, mail.to, mail.subject]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Then /^(an|no) e?mail should have been sent with:$/ do |mode, raw_data|
|
42
|
+
raw_data.strip!
|
43
|
+
conditions = {}.tap do |hash|
|
44
|
+
raw_data.split("\n").each do |row|
|
45
|
+
if row.match(/^[a-z]+: /i)
|
46
|
+
key, value = row.split(": ", 2)
|
47
|
+
hash[key.downcase.to_sym] = value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@mail = MailFinder.find(conditions)
|
52
|
+
expectation = mode == 'no' ? 'should_not' : 'should'
|
53
|
+
@mail.send(expectation, be_present)
|
54
|
+
end
|
55
|
+
|
56
|
+
Then /^that e?mail should have the following lines in the body:$/ do |body|
|
57
|
+
body.each do |line|
|
58
|
+
@mail.body.should include(line.strip)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
Then /^that e?mail should have the following body:$/ do |body|
|
63
|
+
@mail.body.should include(body.strip)
|
64
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'spreewald_support/tolerance_for_selenium_sync_issues'
|
2
|
+
|
3
|
+
module TableStepsHelper
|
4
|
+
module ArrayMethods
|
5
|
+
|
6
|
+
def find_row(expected_row)
|
7
|
+
find_index do |row|
|
8
|
+
expected_row.all? do |expected_column|
|
9
|
+
first_column = row.find_index do |column|
|
10
|
+
content = normalize_content(column.content)
|
11
|
+
expected_content = normalize_content(expected_column)
|
12
|
+
matching_parts = expected_content.split('*', -1).collect { |part| Regexp.escape(part) }
|
13
|
+
matching_expression = /\A#{matching_parts.join(".*")}\z/
|
14
|
+
content =~ matching_expression
|
15
|
+
end
|
16
|
+
if first_column.nil?
|
17
|
+
false
|
18
|
+
else
|
19
|
+
row = row[(first_column + 1)..-1]
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def normalize_content(content)
|
27
|
+
nbsp = 0xC2.chr + 0xA0.chr
|
28
|
+
content.gsub(/[\r\n\t]+/, ' ').gsub(nbsp, ' ').gsub(/ {2,}/, ' ').strip
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
rspec = defined?(RSpec) ? RSpec : Spec
|
34
|
+
|
35
|
+
rspec::Matchers.define :contain_table do |*args|
|
36
|
+
match do |tables|
|
37
|
+
@last_unmatched_row = nil
|
38
|
+
@best_rows_matched = -1
|
39
|
+
expected_table, unordered = args
|
40
|
+
tables.any? do |table|
|
41
|
+
rows_matched = 0
|
42
|
+
expected_table.all? do |expected_row|
|
43
|
+
if @best_rows_matched < rows_matched
|
44
|
+
@last_unmatched_row = expected_row
|
45
|
+
@best_rows_matched = rows_matched
|
46
|
+
end
|
47
|
+
table.extend ArrayMethods
|
48
|
+
first_row = table.find_row(expected_row)
|
49
|
+
if first_row.nil?
|
50
|
+
false
|
51
|
+
else
|
52
|
+
rows_matched += 1
|
53
|
+
if unordered
|
54
|
+
table.delete_at(first_row)
|
55
|
+
else
|
56
|
+
table = table[(first_row + 1)..-1]
|
57
|
+
end
|
58
|
+
true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
failure_message_for_should do
|
65
|
+
"Could not find the following row: #{@last_unmatched_row.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
failure_message_for_should_not do
|
69
|
+
"Found the complete table: #{args.first.inspect}."
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_table(table)
|
74
|
+
if table.is_a?(String)
|
75
|
+
# multiline string. split it assuming a format like cucumber tables have.
|
76
|
+
table.split(/\n/).collect do |line|
|
77
|
+
line.sub!(/^\|/, '')
|
78
|
+
line.sub!(/\|$/, '')
|
79
|
+
line.split(/\s*\|\s*/)
|
80
|
+
end
|
81
|
+
else
|
82
|
+
# vanilla cucumber table.
|
83
|
+
table.raw
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
World(TableStepsHelper)
|
88
|
+
|
89
|
+
|
90
|
+
Then /^I should( not)? see a table with the following rows( in any order)?:?$/ do |negate, unordered, expected_table|
|
91
|
+
patiently do
|
92
|
+
document = Nokogiri::HTML(page.body)
|
93
|
+
tables = document.xpath('//table').collect { |table| table.xpath('.//tr').collect { |row| row.xpath('.//th|td') } }
|
94
|
+
parsed_table = parse_table(expected_table)
|
95
|
+
|
96
|
+
if negate
|
97
|
+
tables.should_not contain_table(parsed_table, unordered)
|
98
|
+
else
|
99
|
+
tables.should contain_table(parsed_table, unordered)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
if defined?(Timecop)
|
2
|
+
|
3
|
+
When /^the (?:date|time) is "(\d{4}-\d{2}-\d{2}(?: \d{1,2}:\d{2})?)"$/ do |time|
|
4
|
+
Timecop.travel Time.parse(time)
|
5
|
+
end
|
6
|
+
|
7
|
+
When /^the time is "(\d{1,2}:\d{2})"$/ do |time|
|
8
|
+
Timecop.travel Time.parse(time) # date will be today
|
9
|
+
end
|
10
|
+
|
11
|
+
When /^it is (\d+|a|some|a few) (seconds?|minutes?|hours?|days?|weeks?|months?|years?) (later|earlier)$/ do |amount, unit, direction|
|
12
|
+
amount = case amount
|
13
|
+
when 'a'
|
14
|
+
1
|
15
|
+
when 'some', 'a few'
|
16
|
+
10
|
17
|
+
else
|
18
|
+
amount.to_i
|
19
|
+
end
|
20
|
+
amount = -amount if direction == 'earlier'
|
21
|
+
Timecop.travel(Time.now + amount.send(unit))
|
22
|
+
end
|
23
|
+
|
24
|
+
After do
|
25
|
+
Timecop.return
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,334 @@
|
|
1
|
+
# Deprecation notice from the original web-steps:
|
2
|
+
#
|
3
|
+
# This file was generated by Cucumber-Rails and is only here to get you a head start
|
4
|
+
# These step definitions are thin wrappers around the Capybara/Webrat API that lets you
|
5
|
+
# visit pages, interact with widgets and make assertions about page content.
|
6
|
+
#
|
7
|
+
# If you use these step definitions as basis for your features you will quickly end up
|
8
|
+
# with features that are:
|
9
|
+
#
|
10
|
+
# * Hard to maintain
|
11
|
+
# * Verbose to read
|
12
|
+
#
|
13
|
+
# A much better approach is to write your own higher level step definitions, following
|
14
|
+
# the advice in the following blog posts:
|
15
|
+
#
|
16
|
+
# * http://benmabey.com/2008/05/19/imperative-vs-declarative-scenarios-in-user-stories.html
|
17
|
+
# * http://dannorth.net/2011/01/31/whose-domain-is-it-anyway/
|
18
|
+
# * http://elabs.se/blog/15-you-re-cuking-it-wrong
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'spreewald_support/tolerance_for_selenium_sync_issues'
|
22
|
+
require 'spreewald_support/path_selector_fallbacks'
|
23
|
+
require 'uri'
|
24
|
+
require 'cgi'
|
25
|
+
|
26
|
+
# Single-line step scoper
|
27
|
+
When /^(.*) within (.*[^:])$/ do |nested_step, parent|
|
28
|
+
with_scope(parent) { step(nested_step) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Multi-line step scoper
|
32
|
+
When /^(.*) within (.*[^:]):$/ do |nested_step, parent, table_or_string|
|
33
|
+
with_scope(parent) { step("#{nested_step}:", table_or_string) }
|
34
|
+
end
|
35
|
+
|
36
|
+
Given /^(?:|I )am on (.+)$/ do |page_name|
|
37
|
+
visit _path_to(page_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
When /^(?:|I )go to (.+)$/ do |page_name|
|
41
|
+
visit _path_to(page_name)
|
42
|
+
end
|
43
|
+
|
44
|
+
When /^(?:|I )press "([^"]*)"$/ do |button|
|
45
|
+
patiently do
|
46
|
+
click_button(button)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
When /^(?:|I )follow "([^"]*)"$/ do |link|
|
51
|
+
patiently do
|
52
|
+
click_link(link)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
When /^(?:|I )fill in "([^"]*)" with "([^"]*)"$/ do |field, value|
|
57
|
+
patiently do
|
58
|
+
fill_in(field, :with => value)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
When /^(?:|I )fill in "([^"]*)" for "([^"]*)"$/ do |value, field|
|
63
|
+
patiently do
|
64
|
+
fill_in(field, :with => value)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
When /^(?:|I )select "([^"]*)" from "([^"]*)"$/ do |value, field|
|
69
|
+
patiently do
|
70
|
+
select(value, :from => field)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
When /^(?:|I )check "([^"]*)"$/ do |field|
|
75
|
+
patiently do
|
76
|
+
check(field)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
When /^(?:|I )uncheck "([^"]*)"$/ do |field|
|
81
|
+
patiently do
|
82
|
+
uncheck(field)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
When /^(?:|I )choose "([^"]*)"$/ do |field|
|
87
|
+
patiently do
|
88
|
+
choose(field)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
When /^(?:|I )attach the file "([^"]*)" to "([^"]*)"$/ do |path, field|
|
93
|
+
patiently do
|
94
|
+
attach_file(field, File.expand_path(path))
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
Then /^(?:|I )should see "([^"]*)"$/ do |text|
|
99
|
+
patiently do
|
100
|
+
page.should have_content(text)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Then /^(?:|I )should see \/([^\/]*)\/$/ do |regexp|
|
105
|
+
regexp = Regexp.new(regexp)
|
106
|
+
patiently do
|
107
|
+
page.should have_xpath('//*', :text => regexp)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
Then /^(?:|I )should not see "([^"]*)"$/ do |text|
|
112
|
+
patiently do
|
113
|
+
page.should have_no_content(text)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
Then /^(?:|I )should not see \/([^\/]*)\/$/ do |regexp|
|
118
|
+
patiently do
|
119
|
+
regexp = Regexp.new(regexp)
|
120
|
+
page.should have_no_xpath('//*', :text => regexp)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
Then /^the "([^"]*)" field(?: within (.*))? should contain "([^"]*)"$/ do |field, parent, value|
|
125
|
+
patiently do
|
126
|
+
with_scope(parent) do
|
127
|
+
field = find_field(field)
|
128
|
+
field_value = (field.tag_name == 'textarea') ? field.text : field.value
|
129
|
+
field_value.should =~ /#{value}/
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
Then /^the "([^"]*)" field(?: within (.*))? should not contain "([^"]*)"$/ do |field, parent, value|
|
135
|
+
patiently do
|
136
|
+
with_scope(parent) do
|
137
|
+
field = find_field(field)
|
138
|
+
field_value = (field.tag_name == 'textarea') ? field.text : field.value
|
139
|
+
field_value.should_not =~ /#{value}/
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
Then /^the "([^"]*)" field should have the error "([^"]*)"$/ do |field, error_message|
|
145
|
+
patiently do
|
146
|
+
element = find_field(field)
|
147
|
+
classes = element.find(:xpath, '..')[:class].split(' ')
|
148
|
+
|
149
|
+
form_for_input = element.find(:xpath, 'ancestor::form[1]')
|
150
|
+
using_formtastic = form_for_input[:class].include?('formtastic')
|
151
|
+
error_class = using_formtastic ? 'error' : 'field_with_errors'
|
152
|
+
|
153
|
+
classes.should include(error_class)
|
154
|
+
|
155
|
+
if using_formtastic
|
156
|
+
error_paragraph = element.find(:xpath, '../*[@class="inline-errors"][1]')
|
157
|
+
error_paragraph.should have_content(error_message)
|
158
|
+
else
|
159
|
+
page.should have_content("#{field.titlecase} #{error_message}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
Then /^the "([^"]*)" field should have no error$/ do |field|
|
165
|
+
patiently do
|
166
|
+
element = find_field(field)
|
167
|
+
classes = element.find(:xpath, '..')[:class].split(' ')
|
168
|
+
classes.should_not include('field_with_errors')
|
169
|
+
classes.should_not include('error')
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
Then /^the "([^"]*)" checkbox(?: within (.*))? should be checked$/ do |label, parent|
|
174
|
+
patiently do
|
175
|
+
with_scope(parent) do
|
176
|
+
field_checked = find_field(label)['checked']
|
177
|
+
field_checked.should be_true
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
Then /^the "([^"]*)" checkbox(?: within (.*))? should not be checked$/ do |label, parent|
|
183
|
+
patiently do
|
184
|
+
with_scope(parent) do
|
185
|
+
field_checked = find_field(label)['checked']
|
186
|
+
field_checked.should be_false
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
Then /^(?:|I )should be on (.+)$/ do |page_name|
|
192
|
+
patiently do
|
193
|
+
current_path = URI.parse(current_url).path
|
194
|
+
current_path.should == _path_to(page_name)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
Then /^(?:|I )should have the following query string:$/ do |expected_pairs|
|
199
|
+
patiently do
|
200
|
+
query = URI.parse(current_url).query
|
201
|
+
actual_params = query ? CGI.parse(query) : {}
|
202
|
+
expected_params = {}
|
203
|
+
expected_pairs.rows_hash.each_pair{|k,v| expected_params[k] = v.split(',')}
|
204
|
+
|
205
|
+
actual_params.should == expected_params
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
Then /^show me the page$/ do
|
210
|
+
save_and_open_page
|
211
|
+
end
|
212
|
+
|
213
|
+
|
214
|
+
Then /^I should( not)? see a field "([^"]*)"$/ do |negate, name|
|
215
|
+
expectation = negate ? :should_not : :should
|
216
|
+
patiently do
|
217
|
+
begin
|
218
|
+
field = find_field(name)
|
219
|
+
rescue Capybara::ElementNotFound
|
220
|
+
# In Capybara 0.4+ #find_field raises an error instead of returning nil
|
221
|
+
end
|
222
|
+
field.send(expectation, be_present)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
Then /^I should( not)? see the (?:number|amount) ([\-\d,\.]+)(?: (.*?))?$/ do |negate, amount, unit|
|
227
|
+
no_minus = amount.starts_with?('-') ? '' : '[^\\-]'
|
228
|
+
nbsp = 0xC2.chr + 0xA0.chr
|
229
|
+
regexp = Regexp.new(no_minus + "\\b" + Regexp.quote(amount) + (unit ? "( |#{nbsp}| )(#{unit}|#{Regexp.quote(HTMLEntities.new.encode(unit, :named))})" :"\\b"))
|
230
|
+
expectation = negate ? :should_not : :should
|
231
|
+
patiently do
|
232
|
+
page.body.send(expectation, match(regexp))
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
Then /^I should get a response with content-type "([^\"]*)"$/ do |expected_content_type|
|
237
|
+
page.response_headers['Content-Type'].should =~ /\A#{Regexp.quote(expected_content_type)}($|;)/
|
238
|
+
end
|
239
|
+
|
240
|
+
Then /^I should get a download with filename "([^\"]*)"$/ do |filename|
|
241
|
+
page.response_headers['Content-Disposition'].should =~ /filename="#{filename}"$/
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
Then /^"([^"]*)" should be selected for "([^"]*)"(?: within "([^\"]*)")?$/ do |value, field, selector|
|
246
|
+
patiently do
|
247
|
+
with_scope(selector) do
|
248
|
+
field_labeled(field).find(:xpath, ".//option[@selected = 'selected'][text() = '#{value}']").should be_present
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
Then /^nothing should be selected for "([^"]*)"?$/ do |field|
|
254
|
+
patiently do
|
255
|
+
select = find_field(field)
|
256
|
+
select.should_not have_css('option[selected]')
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
Then /^"([^"]*)" should( not)? be an option for "([^"]*)"(?: within "([^\"]*)")?$/ do |value, negate, field, selector|
|
261
|
+
patiently do
|
262
|
+
with_scope(selector) do
|
263
|
+
expectation = negate ? :should_not : :should
|
264
|
+
field_labeled(field).first(:xpath, ".//option[text() = '#{value}']").send(expectation, be_present)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
Then /^(?:|I )should see '([^']*)'(?: within '([^']*)')?$/ do |text, selector|
|
270
|
+
patiently do
|
271
|
+
with_scope(selector) do
|
272
|
+
page.should have_content(text)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
Then /^I should see "([^\"]*)" in the HTML$/ do |text|
|
278
|
+
patiently do
|
279
|
+
page.body.should include(text)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
Then /^I should not see "([^\"]*)" in the HTML$/ do |text|
|
284
|
+
patiently do
|
285
|
+
page.body.should_not include(text)
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
Then /^I should see an error$/ do
|
290
|
+
(400 .. 599).should include(page.status_code)
|
291
|
+
end
|
292
|
+
|
293
|
+
Then /^the window should be titled "([^"]*)"$/ do |title|
|
294
|
+
page.should have_css('title', :text => title)
|
295
|
+
end
|
296
|
+
|
297
|
+
When /^I reload the page$/ do
|
298
|
+
visit current_path
|
299
|
+
end
|
300
|
+
|
301
|
+
Then /^"([^\"]+)" should( not)? be visible$/ do |text, negate|
|
302
|
+
paths = [
|
303
|
+
"//*[@class='hidden']/*[contains(.,'#{text}')]",
|
304
|
+
"//*[@class='invisible']/*[contains(.,'#{text}')]",
|
305
|
+
"//*[@style='display: none;']/*[contains(.,'#{text}')]"
|
306
|
+
]
|
307
|
+
xpath = paths.join '|'
|
308
|
+
expectation = negate ? :should : :should_not
|
309
|
+
patiently do
|
310
|
+
page.send(expectation, have_xpath(xpath))
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
When /^I click on "([^\"]+)"$/ do |text|
|
315
|
+
matcher = ['*', { :text => text }]
|
316
|
+
patiently do
|
317
|
+
element = page.find(:css, *matcher)
|
318
|
+
while better_match = element.first(:css, *matcher)
|
319
|
+
element = better_match
|
320
|
+
end
|
321
|
+
element.click
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
Then /^I should (not )?see an element "([^"]*)"$/ do |negate, selector|
|
326
|
+
expectation = negate ? :should_not : :should
|
327
|
+
patiently do
|
328
|
+
page.send(expectation, have_css(selector))
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
Then /^I should get a text response$/ do
|
333
|
+
step 'I should get a response with content-type "text/plain"'
|
334
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class MailFinder
|
2
|
+
class << self
|
3
|
+
|
4
|
+
attr_accessor :user_identity
|
5
|
+
|
6
|
+
def find(conditions)
|
7
|
+
ActionMailer::Base.deliveries.detect do |mail|
|
8
|
+
[ conditions[:to].nil? || mail.to.include?(resolve_email conditions[:to]),
|
9
|
+
conditions[:cc].nil? || mail.cc.andand.include?(resolve_email conditions[:cc]),
|
10
|
+
conditions[:bcc].nil? || mail.bcc.andand.include?(resolve_email conditions[:bcc]),
|
11
|
+
conditions[:from].nil? || mail.from.include?(resolve_email conditions[:from]),
|
12
|
+
conditions[:subject].nil? || mail.subject.include?(conditions[:subject]),
|
13
|
+
conditions[:body].nil? || mail.body.include?(conditions[:body]),
|
14
|
+
conditions[:attachments].nil? || conditions[:attachments].split(/\s*,\s*/).sort == Array(mail.attachments).collect(&:original_filename).sort
|
15
|
+
].all?
|
16
|
+
end.tap do |mail|
|
17
|
+
log(mail)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def resolve_email(identity)
|
22
|
+
if identity =~ /^.+\@.+$/
|
23
|
+
identity
|
24
|
+
else
|
25
|
+
User.send("find_by_#{user_identity || 'email'}!", identity).email
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def log(mail)
|
30
|
+
if mail.present?
|
31
|
+
File.open("log/test_mails.log", "a") do |file|
|
32
|
+
file << "From: #{mail.from}\n"
|
33
|
+
file << "To: #{mail.to.join(', ')}\n"
|
34
|
+
file << "Subject: #{mail.subject}\n\n"
|
35
|
+
file << mail.body
|
36
|
+
file << "\n-------------------------\n\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module PathSelectorFallbacks
|
2
|
+
def _selector_for(locator)
|
3
|
+
if respond_to?(:select_for)
|
4
|
+
selector_for(locator)
|
5
|
+
elsif locator =~ /^"(.+)"$/
|
6
|
+
$1
|
7
|
+
else
|
8
|
+
raise "Can't find mapping from \"#{locator}\" to a selector.\n" +
|
9
|
+
"Add and require a selectors.rb file (compare #{Spreewald.github_url}/examples/selectors.rb)"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def _path_to(page_name)
|
14
|
+
if respond_to?(:path_to)
|
15
|
+
path_to(page_name)
|
16
|
+
else
|
17
|
+
begin
|
18
|
+
page_name =~ /^the (.*) page$/
|
19
|
+
path_components = $1.split(/\s+/)
|
20
|
+
self.send(path_components.push('path').join('_').to_sym)
|
21
|
+
rescue NoMethodError, ArgumentError
|
22
|
+
raise "Can't find mapping from \"#{page_name}\" to a path.\n" +
|
23
|
+
"Add and require a paths.rb file (compare #{Spreewald.github_url}/examples/paths.rb)"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
World(PathSelectorFallbacks)
|
29
|
+
|
30
|
+
module WithinHelpers
|
31
|
+
def with_scope(locator)
|
32
|
+
locator ? within(*_selector_for(locator)) { yield } : yield
|
33
|
+
end
|
34
|
+
end
|
35
|
+
World(WithinHelpers)
|
36
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module ToleranceForSeleniumSyncIssues
|
2
|
+
# This is similiar but not entirely the same as Capybara::Node::Base#wait_until or Capybara::Session#wait_until
|
3
|
+
def patiently(seconds=Capybara.default_wait_time, &block)
|
4
|
+
if page.driver.wait?
|
5
|
+
start_time = Time.now
|
6
|
+
begin
|
7
|
+
block.call
|
8
|
+
rescue => e
|
9
|
+
raise e if (Time.now - start_time) >= seconds
|
10
|
+
sleep(0.05)
|
11
|
+
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
|
12
|
+
retry
|
13
|
+
end
|
14
|
+
else
|
15
|
+
block.call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
World(ToleranceForSeleniumSyncIssues)
|
data/spreewald.gemspec
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$: << File.expand_path('../lib', __FILE__)
|
3
|
+
require 'spreewald_support/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.authors = ["Tobias Kraze"]
|
7
|
+
gem.email = ["tobias@kraze.eu"]
|
8
|
+
gem.description = %q{A collection of cucumber steps we use in our projects, including steps to check HTML, tables, emails and some utility methods.}
|
9
|
+
gem.summary = %q{Collection of useful cucumber steps.}
|
10
|
+
gem.homepage = "https://github.com/makandra/spreewald"
|
11
|
+
|
12
|
+
gem.files = `git ls-files`.split($\)
|
13
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
14
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
15
|
+
gem.name = "spreewald"
|
16
|
+
gem.require_paths = ["lib"]
|
17
|
+
gem.version = Spreewald::VERSION
|
18
|
+
|
19
|
+
gem.add_runtime_dependency('cucumber-rails', '>=1.3.0')
|
20
|
+
gem.add_runtime_dependency('cucumber')
|
21
|
+
gem.add_runtime_dependency('capybara')
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: spreewald
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Tobias Kraze
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-09-21 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: cucumber-rails
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 27
|
29
|
+
segments:
|
30
|
+
- 1
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
version: 1.3.0
|
34
|
+
type: :runtime
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: cucumber
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
version: "0"
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
- !ruby/object:Gem::Dependency
|
51
|
+
name: capybara
|
52
|
+
prerelease: false
|
53
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
hash: 3
|
59
|
+
segments:
|
60
|
+
- 0
|
61
|
+
version: "0"
|
62
|
+
type: :runtime
|
63
|
+
version_requirements: *id003
|
64
|
+
description: A collection of cucumber steps we use in our projects, including steps to check HTML, tables, emails and some utility methods.
|
65
|
+
email:
|
66
|
+
- tobias@kraze.eu
|
67
|
+
executables: []
|
68
|
+
|
69
|
+
extensions: []
|
70
|
+
|
71
|
+
extra_rdoc_files: []
|
72
|
+
|
73
|
+
files:
|
74
|
+
- .gitignore
|
75
|
+
- Gemfile
|
76
|
+
- LICENSE
|
77
|
+
- README.md
|
78
|
+
- Rakefile
|
79
|
+
- examples/paths.rb
|
80
|
+
- examples/selectors.rb
|
81
|
+
- lib/spreewald.rb
|
82
|
+
- lib/spreewald/all_steps.rb
|
83
|
+
- lib/spreewald/development_steps.rb
|
84
|
+
- lib/spreewald/email_steps.rb
|
85
|
+
- lib/spreewald/table_steps.rb
|
86
|
+
- lib/spreewald/timecop_steps.rb
|
87
|
+
- lib/spreewald/web_steps.rb
|
88
|
+
- lib/spreewald_support/github.rb
|
89
|
+
- lib/spreewald_support/mail_finder.rb
|
90
|
+
- lib/spreewald_support/path_selector_fallbacks.rb
|
91
|
+
- lib/spreewald_support/tolerance_for_selenium_sync_issues.rb
|
92
|
+
- lib/spreewald_support/version.rb
|
93
|
+
- spreewald.gemspec
|
94
|
+
homepage: https://github.com/makandra/spreewald
|
95
|
+
licenses: []
|
96
|
+
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
none: false
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
hash: 3
|
108
|
+
segments:
|
109
|
+
- 0
|
110
|
+
version: "0"
|
111
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
hash: 3
|
117
|
+
segments:
|
118
|
+
- 0
|
119
|
+
version: "0"
|
120
|
+
requirements: []
|
121
|
+
|
122
|
+
rubyforge_project:
|
123
|
+
rubygems_version: 1.8.24
|
124
|
+
signing_key:
|
125
|
+
specification_version: 3
|
126
|
+
summary: Collection of useful cucumber steps.
|
127
|
+
test_files: []
|
128
|
+
|
129
|
+
has_rdoc:
|