spreewald 2.4.2 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -3
- data/README.md +87 -15
- data/Rakefile +10 -5
- data/examples/paths.rb +36 -0
- data/examples/selectors.rb +28 -0
- data/lib/spreewald/all_steps.rb +4 -1
- data/lib/spreewald/browser_tab_steps.rb +91 -0
- data/lib/spreewald/development_steps.rb +1 -1
- data/lib/spreewald/email_steps.rb +3 -1
- data/lib/spreewald/session_steps.rb +18 -0
- data/lib/spreewald/time_steps.rb +102 -0
- data/lib/spreewald/timecop_steps.rb +6 -76
- data/lib/spreewald/web_steps.rb +25 -25
- data/lib/spreewald_support/driver_info.rb +8 -0
- data/lib/spreewald_support/tolerance_for_selenium_sync_issues.rb +9 -3
- data/lib/spreewald_support/version.rb +1 -1
- data/lib/spreewald_support/web_steps_helpers.rb +17 -0
- data/support/step_definition.rb +1 -1
- data/support/step_definition_file.rb +2 -0
- data/tests/rails-3_capybara-1/Gemfile +2 -1
- data/tests/rails-3_capybara-1/Gemfile.lock +3 -1
- data/tests/rails-3_capybara-2/Gemfile +2 -1
- data/tests/rails-3_capybara-2/Gemfile.lock +3 -1
- data/tests/rails-4_capybara-3/Gemfile.lock +1 -1
- data/tests/rails-4_capybara-3/features/browser_tab_steps.feature +1 -0
- data/tests/rails-4_capybara-3/features/session_steps.feature +1 -0
- data/tests/rails-4_capybara-3/features/time_steps.feature +1 -0
- data/tests/rails-6_capybara-3/.ruby-version +1 -1
- data/tests/rails-6_capybara-3/Gemfile +1 -1
- data/tests/rails-6_capybara-3/Gemfile.lock +1 -1
- data/tests/rails-6_capybara-3/features/browser_tab_steps.feature +1 -0
- data/tests/rails-6_capybara-3/features/session_steps.feature +1 -0
- data/tests/rails-6_capybara-3/features/time_steps.feature +1 -0
- data/tests/shared/app/controllers/downloads_controller.rb +1 -1
- data/tests/shared/app/controllers/static_pages_controller.rb +9 -0
- data/tests/shared/app/views/forms/disabled_elements.html.haml +18 -0
- data/tests/shared/app/views/mailer/html_email_with_links.haml +2 -0
- data/tests/shared/app/views/mailer/text_email_with_links.text.erb +1 -0
- data/tests/shared/app/views/static_pages/session_1.haml +1 -0
- data/tests/shared/app/views/static_pages/session_2.haml +1 -0
- data/tests/shared/app/views/static_pages/session_3.haml +1 -0
- data/tests/shared/app/views/static_pages/tab_1.haml +3 -0
- data/tests/shared/app/views/static_pages/tab_2.haml +2 -0
- data/tests/shared/app/views/static_pages/tab_3.haml +1 -0
- data/tests/shared/app/views/static_pages/time.html.haml +2 -0
- data/tests/shared/config/routes.rb +9 -2
- data/tests/shared/features/shared/browser_tab_steps.feature +103 -0
- data/tests/shared/features/shared/session_steps.feature +29 -0
- data/tests/shared/features/shared/time_steps.feature +31 -0
- data/tests/shared/features/shared/web_steps.feature +8 -2
- metadata +37 -2
@@ -65,6 +65,8 @@ Then /^(an|no) e?mail should have been sent((?: |and|with|from "[^"]+"|bcc "[^"]
|
|
65
65
|
end
|
66
66
|
end.overridable
|
67
67
|
|
68
|
+
# Please note that this step will only follow HTTP and HTTPS links.
|
69
|
+
# Other links (such as mailto: or ftp:// links) are ignored.
|
68
70
|
When /^I follow the (first|second|third)? ?link in the e?mail$/ do |index_in_words|
|
69
71
|
mail = @mail || ActionMailer::Base.deliveries.last
|
70
72
|
index = { nil => 0, 'first' => 0, 'second' => 1, 'third' => 2 }[index_in_words]
|
@@ -72,7 +74,7 @@ When /^I follow the (first|second|third)? ?link in the e?mail$/ do |index_in_wor
|
|
72
74
|
|
73
75
|
paths = if mail.html_part
|
74
76
|
dom = Nokogiri::HTML(mail.html_part.body.to_s)
|
75
|
-
(dom / 'a[href]').map { |a| a['href'].match(url_pattern)[1] }
|
77
|
+
(dom / 'a[href]').map { |a| a['href'].match(url_pattern) }.compact.map { |match| match[1] }
|
76
78
|
else
|
77
79
|
mail_body = MailFinder.email_text_body(mail).to_s
|
78
80
|
mail_body.scan(url_pattern).flatten(1)
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# You can append `in the browser session "name"` to any other step to execute
|
2
|
+
# the step in a different browser session.
|
3
|
+
#
|
4
|
+
# You may need to update other steps to allow multiple sessions (e.g. your
|
5
|
+
# authentication steps have to support multiple logged in users).
|
6
|
+
# More details [here](https://makandracards.com/makandra/474480-how-to-make-a-cucumber-test-work-with-multiple-browser-sessions).
|
7
|
+
When /^(.*) in the browser session "([^"]+)"$/ do |nested_step, session_name|
|
8
|
+
Capybara.using_session(session_name) do
|
9
|
+
step(nested_step)
|
10
|
+
end
|
11
|
+
end.overridable(priority: 5)
|
12
|
+
|
13
|
+
# nodoc
|
14
|
+
When /^(.*) in the browser session "([^"]+)":$/ do |nested_step, session_name, table_or_string|
|
15
|
+
Capybara.using_session(session_name) do
|
16
|
+
step("#{nested_step}:", table_or_string)
|
17
|
+
end
|
18
|
+
end.overridable(priority: 5)
|
@@ -0,0 +1,102 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
|
4
|
+
# Steps to travel through time
|
5
|
+
#
|
6
|
+
# This uses [Timecop](https://github.com/jtrupiano/timecop) or Active Support 4.1+ to stub Time.now / Time.current.
|
7
|
+
# The user is responsible for including one of the two gems.
|
8
|
+
#
|
9
|
+
# Please note that the two approaches branch. While ActiveSupport will freeze the time, Timecop will keep it running.
|
10
|
+
# FILE_COMMENT_END
|
11
|
+
|
12
|
+
major_minor_rails_version = defined?(ActiveSupport) ? [ActiveSupport::VERSION::MAJOR, ActiveSupport::VERSION::MINOR] : [0, 0]
|
13
|
+
is_at_least_rails_4_1 = (major_minor_rails_version <=> [4, 1]) != -1
|
14
|
+
|
15
|
+
if defined?(Timecop) || is_at_least_rails_4_1
|
16
|
+
|
17
|
+
module TimeHelpers
|
18
|
+
|
19
|
+
# When you have to make your rails app time zone aware you have to go 100%
|
20
|
+
# otherwise you are better off ignoring time zones at all.
|
21
|
+
# https://makandracards.com/makandra/8723-guide-to-localizing-a-rails-application
|
22
|
+
|
23
|
+
def use_timezones?
|
24
|
+
active_record_loaded = defined?(ActiveRecord::Base)
|
25
|
+
(!active_record_loaded || ActiveRecord::Base.default_timezone != :local) && Time.zone
|
26
|
+
end
|
27
|
+
|
28
|
+
def parse_time(str)
|
29
|
+
if use_timezones?
|
30
|
+
Time.zone.parse(str)
|
31
|
+
else
|
32
|
+
Time.parse(str)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_time
|
37
|
+
if use_timezones?
|
38
|
+
Time.current
|
39
|
+
else
|
40
|
+
Time.now
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
if defined?(Timecop)
|
45
|
+
# Emulate ActiveSupport time helper methods with Timecop - don't rename these methods
|
46
|
+
def travel(duration)
|
47
|
+
Timecop.travel(current_time + duration)
|
48
|
+
end
|
49
|
+
|
50
|
+
def travel_to(date_or_time)
|
51
|
+
Timecop.travel(date_or_time)
|
52
|
+
end
|
53
|
+
|
54
|
+
def travel_back
|
55
|
+
Timecop.return
|
56
|
+
end
|
57
|
+
else
|
58
|
+
require 'active_support/testing/time_helpers'
|
59
|
+
include ActiveSupport::Testing::TimeHelpers
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
World(TimeHelpers)
|
65
|
+
|
66
|
+
# Example:
|
67
|
+
#
|
68
|
+
# Given the date is 2012-02-10
|
69
|
+
# Given the time is 2012-02-10 13:40
|
70
|
+
When /^the (?:date|time) is "?(\d{4}-\d{2}-\d{2}(?: \d{1,2}:\d{2})?)"?$/ do |time|
|
71
|
+
travel_to parse_time(time)
|
72
|
+
end.overridable
|
73
|
+
|
74
|
+
# Example:
|
75
|
+
#
|
76
|
+
# Given the time is 13:40
|
77
|
+
When /^the time is "?(\d{1,2}:\d{2})"?$/ do |time_without_date|
|
78
|
+
travel_to parse_time(time_without_date) # date will be today
|
79
|
+
end.overridable
|
80
|
+
|
81
|
+
# Example:
|
82
|
+
#
|
83
|
+
# When it is 10 minutes later
|
84
|
+
# When it is a few hours earlier
|
85
|
+
When /^it is (\d+|an?|some|a few) (seconds?|minutes?|hours?|days?|weeks?|months?|years?) (later|earlier)$/ do |amount, unit, direction|
|
86
|
+
amount = case amount
|
87
|
+
when 'a', 'an'
|
88
|
+
1
|
89
|
+
when 'some', 'a few'
|
90
|
+
10
|
91
|
+
else
|
92
|
+
amount.to_i
|
93
|
+
end
|
94
|
+
amount = -amount if direction == 'earlier'
|
95
|
+
travel amount.send(unit)
|
96
|
+
end.overridable
|
97
|
+
|
98
|
+
After do
|
99
|
+
travel_back
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
@@ -1,79 +1,9 @@
|
|
1
|
-
#
|
2
|
-
|
3
|
-
|
4
|
-
# Steps to travel through time using [Timecop](https://github.com/jtrupiano/timecop).
|
5
|
-
#
|
6
|
-
# See [this article](https://makandracards.com/makandra/1222-useful-cucumber-steps-to-travel-through-time-with-timecop) for details.
|
1
|
+
# nodoc
|
7
2
|
# FILE_COMMENT_END
|
8
3
|
|
4
|
+
warn <<-WARNING
|
5
|
+
Warning: The file spreewald/timecop_steps.rb is deprecated. It was moved to
|
6
|
+
spreewald/time_steps.rb. Please require the new file instead.
|
7
|
+
WARNING
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
module TimecopHarness
|
13
|
-
|
14
|
-
# When you have to make your rails app time zone aware you have to go 100%
|
15
|
-
# otherwise you are better off ignoring time zones at all.
|
16
|
-
# https://makandracards.com/makandra/8723-guide-to-localizing-a-rails-application
|
17
|
-
|
18
|
-
def use_timezones?
|
19
|
-
active_record_loaded = defined?(ActiveRecord::Base)
|
20
|
-
(!active_record_loaded || ActiveRecord::Base.default_timezone != :local) && Time.zone
|
21
|
-
end
|
22
|
-
|
23
|
-
def parse_time(str)
|
24
|
-
if use_timezones?
|
25
|
-
Time.zone.parse(str)
|
26
|
-
else
|
27
|
-
Time.parse(str)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def current_time
|
32
|
-
if use_timezones?
|
33
|
-
Time.current
|
34
|
-
else
|
35
|
-
Time.now
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
40
|
-
|
41
|
-
World(TimecopHarness)
|
42
|
-
|
43
|
-
# Example:
|
44
|
-
#
|
45
|
-
# Given the date is 2012-02-10
|
46
|
-
# Given the time is 2012-02-10 13:40
|
47
|
-
When /^the (?:date|time) is "?(\d{4}-\d{2}-\d{2}(?: \d{1,2}:\d{2})?)"?$/ do |time|
|
48
|
-
Timecop.travel(parse_time(time))
|
49
|
-
end.overridable
|
50
|
-
|
51
|
-
# Example:
|
52
|
-
#
|
53
|
-
# Given the time is 13:40
|
54
|
-
When /^the time is "?(\d{1,2}:\d{2})"?$/ do |time_without_date|
|
55
|
-
Timecop.travel(parse_time(time_without_date)) # date will be today
|
56
|
-
end.overridable
|
57
|
-
|
58
|
-
# Example:
|
59
|
-
#
|
60
|
-
# When it is 10 minutes later
|
61
|
-
# When it is a few hours earlier
|
62
|
-
When /^it is (\d+|a|some|a few) (seconds?|minutes?|hours?|days?|weeks?|months?|years?) (later|earlier)$/ do |amount, unit, direction|
|
63
|
-
amount = case amount
|
64
|
-
when 'a'
|
65
|
-
1
|
66
|
-
when 'some', 'a few'
|
67
|
-
10
|
68
|
-
else
|
69
|
-
amount.to_i
|
70
|
-
end
|
71
|
-
amount = -amount if direction == 'earlier'
|
72
|
-
Timecop.travel(current_time + amount.send(unit))
|
73
|
-
end.overridable
|
74
|
-
|
75
|
-
After do
|
76
|
-
Timecop.return
|
77
|
-
end
|
78
|
-
|
79
|
-
end
|
9
|
+
require 'spreewald/time_steps'
|
data/lib/spreewald/web_steps.rb
CHANGED
@@ -35,7 +35,7 @@ require 'uri'
|
|
35
35
|
require 'cgi'
|
36
36
|
|
37
37
|
|
38
|
-
# You can append `within [selector]` to any other web step.
|
38
|
+
# You can append `within [selector]` to any other web step, even multiple times.
|
39
39
|
# Be aware that within will only look at the first element that matches.
|
40
40
|
# If this is a problem for you following links, you might want to have a look
|
41
41
|
# at the 'When I follow "..." inside any "..."'-step.
|
@@ -463,7 +463,18 @@ end.overridable
|
|
463
463
|
#
|
464
464
|
# Attention: Doesn't work with Selenium, see https://github.com/jnicklas/capybara#gotchas
|
465
465
|
Then /^I should get a download with filename "([^\"]*)"$/ do |filename|
|
466
|
-
|
466
|
+
content_disposition = page.response_headers['Content-Disposition']
|
467
|
+
expect(content_disposition).to be_present
|
468
|
+
|
469
|
+
fields = content_disposition.split(/;\s*/)
|
470
|
+
|
471
|
+
# Rails 6+ encodes filenames as defined in https://tools.ietf.org/html/rfc5987.
|
472
|
+
# If available, we prefer the UTF-8 filename.
|
473
|
+
download_filename = [/^filename\*=UTF-8''(.+)/, /^filename="(.+?)"/].each do |regexp|
|
474
|
+
matched_filename = fields.map { |field| field.scan(regexp).flatten.first }.compact.first
|
475
|
+
break CGI.unescape(matched_filename) if matched_filename
|
476
|
+
end
|
477
|
+
expect(download_filename).to eq(filename)
|
467
478
|
end.overridable
|
468
479
|
|
469
480
|
# Checks that a certain option is selected for a text field
|
@@ -595,7 +606,7 @@ end.overridable(priority: -5) # priority lower than within
|
|
595
606
|
#
|
596
607
|
# Then "Sponsor" should link to "http://makandra.com/"
|
597
608
|
#
|
598
|
-
# Don't forget the trailing slash. Otherwise you'll get the error
|
609
|
+
# Don't forget the trailing slash. Otherwise you'll get the error
|
599
610
|
# expected: /http:\/\/makandra.com(\?[^\/]*)?$/
|
600
611
|
# got: "http://makandra.com/" (using =~)
|
601
612
|
Then /^"([^"]*)" should link to "([^"]*)"$/ do |link_label, target|
|
@@ -641,49 +652,38 @@ end.overridable
|
|
641
652
|
|
642
653
|
When /^I confirm the browser dialog$/ do
|
643
654
|
patiently do
|
644
|
-
|
655
|
+
browser.switch_to.alert.accept
|
645
656
|
end
|
646
657
|
end.overridable
|
647
658
|
|
648
659
|
When /^I cancel the browser dialog$/ do
|
649
660
|
patiently do
|
650
|
-
|
661
|
+
browser.switch_to.alert.dismiss
|
651
662
|
end
|
652
663
|
end.overridable
|
653
664
|
|
654
665
|
When /^I enter "([^"]*)" into the browser dialog$/ do |text|
|
655
666
|
patiently do
|
656
|
-
alert =
|
667
|
+
alert = browser.switch_to.alert
|
657
668
|
alert.send_keys(text)
|
658
669
|
alert.accept
|
659
670
|
end
|
660
671
|
end.overridable
|
661
672
|
|
662
|
-
|
663
|
-
|
664
|
-
|
673
|
+
# Tests that an input, button, checkbox or radio button with the given label is disabled.
|
674
|
+
Then /^the "([^\"]*)" (field|button|checkbox|radio button) should( not)? be disabled$/ do |label, kind, negate|
|
675
|
+
element = if kind == 'button'
|
676
|
+
find_with_disabled(:button, label)
|
665
677
|
else
|
666
|
-
|
678
|
+
find_with_disabled(:field, label)
|
667
679
|
end
|
668
|
-
end.overridable
|
669
680
|
|
670
|
-
# Tests that an input, button or checkbox with the given label is disabled.
|
671
|
-
Then /^the "([^\"]*)" (field|button|checkbox) should( not)? be disabled$/ do |label, kind, negate|
|
672
681
|
if Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.1")
|
673
|
-
if kind == 'field' || kind == 'checkbox'
|
674
|
-
element = find_field(label)
|
675
|
-
else
|
676
|
-
element = find_button(label)
|
677
|
-
end
|
678
682
|
expect(element[:disabled]).send(negate ? :to : :not_to, eq(nil))
|
679
683
|
else
|
680
|
-
|
681
|
-
element = find_field(label, disabled: !negate)
|
682
|
-
else
|
683
|
-
element = find_button(label, disabled: !negate)
|
684
|
-
end
|
685
|
-
expect(element).to be_present
|
684
|
+
expect(!!element.disabled?).to be !negate
|
686
685
|
end
|
686
|
+
|
687
687
|
end.overridable
|
688
688
|
|
689
689
|
# Tests that a field with the given label is visible.
|
@@ -728,7 +728,7 @@ When /^I perform basic authentication as "([^\"]*)\/([^\"]*)" and go to (.*)$/ d
|
|
728
728
|
visit("http://#{user}:#{password}@#{server.host}:#{server.port}#{path}")
|
729
729
|
else
|
730
730
|
authorizers = [
|
731
|
-
(
|
731
|
+
(browser),
|
732
732
|
(self),
|
733
733
|
(page.driver)
|
734
734
|
].compact
|
@@ -17,6 +17,14 @@ module Spreewald
|
|
17
17
|
Object.const_defined?('Capybara::Webkit') && Capybara.current_session.driver.is_a?(Capybara::Webkit::Driver)
|
18
18
|
end
|
19
19
|
|
20
|
+
def browser
|
21
|
+
page.driver.browser if page.driver.respond_to?(:browser)
|
22
|
+
end
|
23
|
+
|
24
|
+
def require_selenium!
|
25
|
+
raise 'This step only works with Selenium' unless selenium_driver?
|
26
|
+
end
|
27
|
+
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
@@ -38,22 +38,28 @@ module ToleranceForSeleniumSyncIssues
|
|
38
38
|
WAIT_PERIOD = 0.05
|
39
39
|
|
40
40
|
def patiently(seconds, &block)
|
41
|
-
started =
|
41
|
+
started = monotonic_time
|
42
42
|
tries = 0
|
43
43
|
begin
|
44
44
|
tries += 1
|
45
45
|
block.call
|
46
46
|
rescue Exception => e
|
47
47
|
raise e unless retryable_error?(e)
|
48
|
-
raise e if (
|
48
|
+
raise e if (monotonic_time - started > seconds && tries >= 2)
|
49
49
|
sleep(WAIT_PERIOD)
|
50
|
-
raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if
|
50
|
+
raise Capybara::FrozenInTime, "time appears to be frozen, Capybara does not work with libraries which freeze time, consider using time travelling instead" if monotonic_time == started
|
51
51
|
retry
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
55
|
private
|
56
56
|
|
57
|
+
def monotonic_time
|
58
|
+
# We use the system clock (i.e. seconds since boot) to calculate the time,
|
59
|
+
# because Time.now may be frozen
|
60
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
61
|
+
end
|
62
|
+
|
57
63
|
def retryable_error?(e)
|
58
64
|
RETRY_ERRORS.include?(e.class.name)
|
59
65
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'spreewald_support/comparison'
|
2
|
+
|
1
3
|
module WebStepsHelpers
|
2
4
|
|
3
5
|
def assert_visible(options)
|
@@ -8,6 +10,21 @@ module WebStepsHelpers
|
|
8
10
|
visibility_test(options.merge(:expectation => :hidden))
|
9
11
|
end
|
10
12
|
|
13
|
+
def find_with_disabled(*args, **options, &optional_filter_block)
|
14
|
+
if Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.0")
|
15
|
+
find(:xpath, XPath::HTML.send(args[0], args[1])) # Versions < 2.0 will find both enabled and disabled fields, nothing to do
|
16
|
+
elsif Spreewald::Comparison.compare_versions(Capybara::VERSION, :<, "2.6")
|
17
|
+
begin
|
18
|
+
find(*args, **options, disabled: false, &optional_filter_block)
|
19
|
+
rescue Capybara::ElementNotFound
|
20
|
+
find(*args, **options, disabled: true, &optional_filter_block)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
options_with_disabled = { disabled: :all }.merge(options)
|
24
|
+
find(*args, **options_with_disabled, &optional_filter_block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
11
28
|
private
|
12
29
|
|
13
30
|
def visibility_test(options)
|
data/support/step_definition.rb
CHANGED