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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -3
  3. data/README.md +87 -15
  4. data/Rakefile +10 -5
  5. data/examples/paths.rb +36 -0
  6. data/examples/selectors.rb +28 -0
  7. data/lib/spreewald/all_steps.rb +4 -1
  8. data/lib/spreewald/browser_tab_steps.rb +91 -0
  9. data/lib/spreewald/development_steps.rb +1 -1
  10. data/lib/spreewald/email_steps.rb +3 -1
  11. data/lib/spreewald/session_steps.rb +18 -0
  12. data/lib/spreewald/time_steps.rb +102 -0
  13. data/lib/spreewald/timecop_steps.rb +6 -76
  14. data/lib/spreewald/web_steps.rb +25 -25
  15. data/lib/spreewald_support/driver_info.rb +8 -0
  16. data/lib/spreewald_support/tolerance_for_selenium_sync_issues.rb +9 -3
  17. data/lib/spreewald_support/version.rb +1 -1
  18. data/lib/spreewald_support/web_steps_helpers.rb +17 -0
  19. data/support/step_definition.rb +1 -1
  20. data/support/step_definition_file.rb +2 -0
  21. data/tests/rails-3_capybara-1/Gemfile +2 -1
  22. data/tests/rails-3_capybara-1/Gemfile.lock +3 -1
  23. data/tests/rails-3_capybara-2/Gemfile +2 -1
  24. data/tests/rails-3_capybara-2/Gemfile.lock +3 -1
  25. data/tests/rails-4_capybara-3/Gemfile.lock +1 -1
  26. data/tests/rails-4_capybara-3/features/browser_tab_steps.feature +1 -0
  27. data/tests/rails-4_capybara-3/features/session_steps.feature +1 -0
  28. data/tests/rails-4_capybara-3/features/time_steps.feature +1 -0
  29. data/tests/rails-6_capybara-3/.ruby-version +1 -1
  30. data/tests/rails-6_capybara-3/Gemfile +1 -1
  31. data/tests/rails-6_capybara-3/Gemfile.lock +1 -1
  32. data/tests/rails-6_capybara-3/features/browser_tab_steps.feature +1 -0
  33. data/tests/rails-6_capybara-3/features/session_steps.feature +1 -0
  34. data/tests/rails-6_capybara-3/features/time_steps.feature +1 -0
  35. data/tests/shared/app/controllers/downloads_controller.rb +1 -1
  36. data/tests/shared/app/controllers/static_pages_controller.rb +9 -0
  37. data/tests/shared/app/views/forms/disabled_elements.html.haml +18 -0
  38. data/tests/shared/app/views/mailer/html_email_with_links.haml +2 -0
  39. data/tests/shared/app/views/mailer/text_email_with_links.text.erb +1 -0
  40. data/tests/shared/app/views/static_pages/session_1.haml +1 -0
  41. data/tests/shared/app/views/static_pages/session_2.haml +1 -0
  42. data/tests/shared/app/views/static_pages/session_3.haml +1 -0
  43. data/tests/shared/app/views/static_pages/tab_1.haml +3 -0
  44. data/tests/shared/app/views/static_pages/tab_2.haml +2 -0
  45. data/tests/shared/app/views/static_pages/tab_3.haml +1 -0
  46. data/tests/shared/app/views/static_pages/time.html.haml +2 -0
  47. data/tests/shared/config/routes.rb +9 -2
  48. data/tests/shared/features/shared/browser_tab_steps.feature +103 -0
  49. data/tests/shared/features/shared/session_steps.feature +29 -0
  50. data/tests/shared/features/shared/time_steps.feature +31 -0
  51. data/tests/shared/features/shared/web_steps.feature +8 -2
  52. metadata +37 -2
@@ -57,7 +57,7 @@ AfterStep('@slow-motion') do
57
57
  sleep 2
58
58
  end
59
59
 
60
- # Waits for keypress after each step
60
+ # Waits for a keypress after each step
61
61
  AfterStep('@single-step') do
62
62
  print "Single Stepping. Hit enter to continue"
63
63
  STDIN.getc
@@ -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
- # coding: UTF-8
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
- if defined?(Timecop)
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'
@@ -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
- expect(page.response_headers['Content-Disposition']).to match /filename="#{Regexp.escape(filename)}"(;|$)/
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
- page.driver.browser.switch_to.alert.accept
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
- page.driver.browser.switch_to.alert.dismiss
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 = page.driver.browser.switch_to.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
- When /^I switch to the new tab$/ do
663
- if javascript_capable?
664
- page.driver.browser.switch_to.window(page.driver.browser.window_handles.last)
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
- raise("This step works only with selenium")
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
- if kind == 'field' || kind == 'checkbox'
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
- (page.driver.browser if page.driver.respond_to?(:browser)),
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 = Time.now
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 (Time.now - started > seconds && tries >= 2)
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 Time.now == started
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,3 @@
1
1
  module Spreewald
2
- VERSION = '2.4.2'
2
+ VERSION = '2.8.0'
3
3
  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)
@@ -37,7 +37,7 @@ class StepDefinition
37
37
 
38
38
  def pretty_step
39
39
  if kind == 'AfterStep'
40
- step[/@\w+/]
40
+ step[/@[\w-]+/]
41
41
  elsif step =~ /^'(.*)'$/ # Surrounded by single quotes
42
42
  $1
43
43
  else
@@ -16,6 +16,8 @@ class StepDefinitionFile
16
16
  end
17
17
 
18
18
  def to_markdown
19
+ return '' if @comment =~ /\bnodoc\b/
20
+
19
21
  spaced_comment = "\n\n" + @comment if @comment
20
22
 
21
23
  <<-TEXT
@@ -16,4 +16,5 @@ group :test do
16
16
  gem 'selenium-webdriver'
17
17
  gem 'rspec-rails'
18
18
  gem 'spreewald', :path => '../..'
19
- end
19
+ gem 'timecop'
20
+ end