spreewald 2.4.2 → 2.8.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 +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