cucumber 2.3.3 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE.md +48 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +39 -0
- data/.travis.yml +4 -5
- data/Gemfile +3 -1
- data/History.md +18 -2
- data/LICENSE +1 -1
- data/README.md +5 -2
- data/cucumber.gemspec +2 -13
- data/examples/i18n/eo/features/adicio.feature +6 -6
- data/examples/i18n/eo/features/divido.feature +6 -6
- data/examples/i18n/eo/features/step_definitions/calculator_steps.rb +2 -2
- data/features/docs/cli/randomize.feature +91 -15
- data/features/docs/cli/retry_failing_tests.feature +32 -0
- data/features/docs/defining_steps/snippets.feature +1 -1
- data/features/docs/formatters/json_formatter.feature +2 -6
- data/features/docs/formatters/junit_formatter.feature +47 -1
- data/features/docs/writing_support_code/after_step_hooks.feature +53 -0
- data/features/docs/{defining_steps → writing_support_code}/transforms.feature +42 -7
- data/features/lib/step_definitions/cucumber_steps.rb +28 -0
- data/features/lib/step_definitions/retry_steps.rb +35 -0
- data/lib/cucumber/cli/configuration.rb +4 -0
- data/lib/cucumber/cli/options.rb +7 -2
- data/lib/cucumber/cli/rerun_file.rb +1 -1
- data/lib/cucumber/configuration.rb +4 -0
- data/lib/cucumber/events/finished_testing.rb +9 -0
- data/lib/cucumber/filters/randomizer.rb +6 -1
- data/lib/cucumber/filters/retry.rb +32 -0
- data/lib/cucumber/formatter/cucumber.css +0 -0
- data/lib/cucumber/formatter/cucumber.sass +0 -0
- data/lib/cucumber/formatter/event_bus_report.rb +1 -0
- data/lib/cucumber/formatter/json.rb +18 -8
- data/lib/cucumber/formatter/junit.rb +56 -42
- data/lib/cucumber/gherkin/data_table_parser.rb +17 -9
- data/lib/cucumber/gherkin/steps_parser.rb +13 -21
- data/lib/cucumber/platform.rb +1 -0
- data/lib/cucumber/rake/task.rb +1 -1
- data/lib/cucumber/rb_support/rb_hook.rb +1 -1
- data/lib/cucumber/rb_support/snippet.rb +1 -1
- data/lib/cucumber/runtime.rb +4 -5
- data/lib/cucumber/runtime/for_programming_languages.rb +2 -0
- data/lib/cucumber/runtime/step_hooks.rb +1 -1
- data/lib/cucumber/step_match.rb +1 -1
- data/lib/cucumber/version +1 -1
- data/spec/cucumber/cli/configuration_spec.rb +7 -0
- data/spec/cucumber/cli/options_spec.rb +14 -0
- data/spec/cucumber/cli/rerun_spec.rb +3 -7
- data/spec/cucumber/filters/retry_spec.rb +79 -0
- data/spec/cucumber/formatter/event_bus_report_spec.rb +9 -0
- data/spec/cucumber/formatter/json_spec.rb +1 -1
- data/spec/cucumber/formatter/junit_spec.rb +2 -2
- data/spec/cucumber/formatter/spec_helper.rb +6 -1
- data/spec/cucumber/rake/task_spec.rb +85 -0
- data/spec/cucumber/rb_support/snippet_spec.rb +2 -2
- metadata +19 -139
@@ -50,7 +50,7 @@ Feature: Snippets
|
|
50
50
|
Then the output should contain:
|
51
51
|
"""
|
52
52
|
Given(/^a table$/) do |table|
|
53
|
-
# table is a Cucumber::
|
53
|
+
# table is a Cucumber::MultilineArgument::DataTable
|
54
54
|
pending # Write code here that turns the phrase above into concrete actions
|
55
55
|
end
|
56
56
|
"""
|
@@ -286,7 +286,6 @@ Feature: JSON output formatter
|
|
286
286
|
|
287
287
|
"""
|
288
288
|
|
289
|
-
@spawn
|
290
289
|
Scenario: DocString
|
291
290
|
Given a file named "features/doc_string.feature" with:
|
292
291
|
"""
|
@@ -349,9 +348,8 @@ Feature: JSON output formatter
|
|
349
348
|
]
|
350
349
|
"""
|
351
350
|
|
352
|
-
@spawn
|
353
351
|
Scenario: embedding screenshot
|
354
|
-
When I run `cucumber
|
352
|
+
When I run `cucumber --format json features/embed.feature`
|
355
353
|
Then it should pass with JSON:
|
356
354
|
"""
|
357
355
|
[
|
@@ -630,9 +628,8 @@ Feature: JSON output formatter
|
|
630
628
|
|
631
629
|
"""
|
632
630
|
|
633
|
-
@spawn
|
634
631
|
Scenario: embedding data directly
|
635
|
-
When I run `cucumber
|
632
|
+
When I run `cucumber --format json -x features/embed_data_directly.feature`
|
636
633
|
Then it should pass with JSON:
|
637
634
|
"""
|
638
635
|
[
|
@@ -733,7 +730,6 @@ Feature: JSON output formatter
|
|
733
730
|
]
|
734
731
|
|
735
732
|
"""
|
736
|
-
@spawn
|
737
733
|
Scenario: handle output from hooks
|
738
734
|
Given a file named "features/step_definitions/output_steps.rb" with:
|
739
735
|
"""
|
@@ -1,4 +1,3 @@
|
|
1
|
-
@spawn
|
2
1
|
Feature: JUnit output formatter
|
3
2
|
In order for developers to create test reports with ant
|
4
3
|
Cucumber should be able to output JUnit xml files
|
@@ -60,6 +59,7 @@ Feature: JUnit output formatter
|
|
60
59
|
| is undefined |
|
61
60
|
"""
|
62
61
|
|
62
|
+
@spawn
|
63
63
|
Scenario: one feature, one passing scenario, one failing scenario
|
64
64
|
When I run `cucumber --format junit --out tmp/ features/one_passing_one_failing.feature`
|
65
65
|
Then it should fail with:
|
@@ -101,6 +101,7 @@ Feature: JUnit output formatter
|
|
101
101
|
|
102
102
|
"""
|
103
103
|
|
104
|
+
@spawn
|
104
105
|
Scenario: one feature in a subdirectory, one passing scenario, one failing scenario
|
105
106
|
When I run `cucumber --format junit --out tmp/ features/some_subdirectory/one_passing_one_failing.feature --require features`
|
106
107
|
Then it should fail with:
|
@@ -245,6 +246,7 @@ can't convert .* into String \(TypeError\)
|
|
245
246
|
You *must* specify --out DIR for the junit formatter
|
246
247
|
"""
|
247
248
|
|
249
|
+
@spawn
|
248
250
|
Scenario: strict mode, one feature, one scenario outline, four examples: one passing, one failing, one pending, one undefined
|
249
251
|
When I run `cucumber --strict --format junit --out tmp/ features/scenario_outline.feature`
|
250
252
|
Then it should fail with:
|
@@ -326,6 +328,7 @@ You *must* specify --out DIR for the junit formatter
|
|
326
328
|
|
327
329
|
"""
|
328
330
|
|
331
|
+
@spawn
|
329
332
|
Scenario: strict mode with --expand option, one feature, one scenario outline, four examples: one passing, one failing, one pending, one undefined
|
330
333
|
When I run `cucumber --strict --expand --format junit --out tmp/ features/scenario_outline.feature`
|
331
334
|
Then it should fail with exactly:
|
@@ -406,3 +409,46 @@ You *must* specify --out DIR for the junit formatter
|
|
406
409
|
</testsuite>
|
407
410
|
|
408
411
|
"""
|
412
|
+
|
413
|
+
@spawn
|
414
|
+
Scenario: run test cases from different features interweaved
|
415
|
+
When I run `cucumber --format junit --out tmp/ features/one_passing_one_failing.feature:3 features/pending.feature:3 features/one_passing_one_failing.feature:6`
|
416
|
+
Then it should fail with:
|
417
|
+
"""
|
418
|
+
|
419
|
+
"""
|
420
|
+
And the junit output file "tmp/TEST-features-one_passing_one_failing.xml" should contain:
|
421
|
+
"""
|
422
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
423
|
+
<testsuite failures="1" errors="0" skipped="0" tests="2" time="0.05" name="One passing scenario, one failing scenario">
|
424
|
+
<testcase classname="One passing scenario, one failing scenario" name="Passing" time="0.05">
|
425
|
+
<system-out>
|
426
|
+
<![CDATA[]]>
|
427
|
+
</system-out>
|
428
|
+
<system-err>
|
429
|
+
<![CDATA[]]>
|
430
|
+
</system-err>
|
431
|
+
</testcase>
|
432
|
+
<testcase classname="One passing scenario, one failing scenario" name="Failing" time="0.05">
|
433
|
+
<failure message="failed Failing" type="failed">
|
434
|
+
<![CDATA[Scenario: Failing
|
435
|
+
|
436
|
+
Given this step fails
|
437
|
+
|
438
|
+
Message:
|
439
|
+
]]>
|
440
|
+
<![CDATA[ (RuntimeError)
|
441
|
+
./features/step_definitions/steps.rb:4:in `/^this step fails$/'
|
442
|
+
features/one_passing_one_failing.feature:7:in `Given this step fails']]>
|
443
|
+
</failure>
|
444
|
+
<system-out>
|
445
|
+
<![CDATA[]]>
|
446
|
+
</system-out>
|
447
|
+
<system-err>
|
448
|
+
<![CDATA[]]>
|
449
|
+
</system-err>
|
450
|
+
</testcase>
|
451
|
+
</testsuite>
|
452
|
+
|
453
|
+
"""
|
454
|
+
And a file named "tmp/TEST-features-pending.xml" should exist
|
@@ -0,0 +1,53 @@
|
|
1
|
+
Feature: AfterStep Hooks
|
2
|
+
AfterStep hooks can be used to further inspect the Step object of the step
|
3
|
+
that has just run, or to simply check the step's result.
|
4
|
+
|
5
|
+
Background:
|
6
|
+
Given the standard step definitions
|
7
|
+
And a file named "features/sample.feature" with:
|
8
|
+
"""
|
9
|
+
Feature: Sample
|
10
|
+
|
11
|
+
Scenario: Success
|
12
|
+
Given this step passes
|
13
|
+
"""
|
14
|
+
|
15
|
+
Scenario: Access Test Step object in AfterStep Block
|
16
|
+
Given a file named "features/support/env.rb" with:
|
17
|
+
"""
|
18
|
+
AfterStep do |result, test_step|
|
19
|
+
expect(test_step).to be_a(Cucumber::Core::Test::Step)
|
20
|
+
end
|
21
|
+
"""
|
22
|
+
When I run `cucumber features`
|
23
|
+
Then it should pass with:
|
24
|
+
"""
|
25
|
+
Feature: Sample
|
26
|
+
|
27
|
+
Scenario: Success # features/sample.feature:3
|
28
|
+
Given this step passes # features/step_definitions/steps.rb:1
|
29
|
+
|
30
|
+
1 scenario (1 passed)
|
31
|
+
1 step (1 passed)
|
32
|
+
|
33
|
+
"""
|
34
|
+
|
35
|
+
Scenario: An AfterStep with one named argument receives only the result
|
36
|
+
Given a file named "features/support/env.rb" with:
|
37
|
+
"""
|
38
|
+
AfterStep do |result|
|
39
|
+
expect(result).to be_a(Cucumber::Core::Test::Result::Passed)
|
40
|
+
end
|
41
|
+
"""
|
42
|
+
When I run `cucumber features`
|
43
|
+
Then it should pass with:
|
44
|
+
"""
|
45
|
+
Feature: Sample
|
46
|
+
|
47
|
+
Scenario: Success # features/sample.feature:3
|
48
|
+
Given this step passes # features/step_definitions/steps.rb:1
|
49
|
+
|
50
|
+
1 scenario (1 passed)
|
51
|
+
1 step (1 passed)
|
52
|
+
|
53
|
+
"""
|
@@ -1,12 +1,12 @@
|
|
1
1
|
Feature: Transforms
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
Transforms allow you to convert primitive string arguments captured in step definitions
|
4
|
+
into more meaningful data types.
|
5
|
+
|
6
6
|
Background:
|
7
7
|
Let's just create a simple feature for testing out Transforms.
|
8
8
|
We also have a Person class that we need to be able to build.
|
9
|
-
|
9
|
+
|
10
10
|
Given a file named "features/foo.feature" with:
|
11
11
|
"""
|
12
12
|
Feature:
|
@@ -26,8 +26,9 @@ Feature: Transforms
|
|
26
26
|
|
27
27
|
Scenario: Basic Transform
|
28
28
|
This is the most basic way to use a transform. Notice that the regular
|
29
|
-
expression is pretty much duplicated
|
30
|
-
|
29
|
+
expression is pretty much duplicated between the step defintion and the
|
30
|
+
transform itself.
|
31
|
+
|
31
32
|
And a file named "features/step_definitions/steps.rb" with:
|
32
33
|
"""
|
33
34
|
Transform(/a Person aged (\d+)/) do |age|
|
@@ -42,7 +43,7 @@ Feature: Transforms
|
|
42
43
|
"""
|
43
44
|
When I run `cucumber features/foo.feature`
|
44
45
|
Then it should pass
|
45
|
-
|
46
|
+
|
46
47
|
Scenario: Re-use Transform's Regular Expression
|
47
48
|
If you keep a reference to the transform, you can use it in your
|
48
49
|
regular expressions to avoid repeating the regular expression.
|
@@ -61,3 +62,37 @@ Feature: Transforms
|
|
61
62
|
"""
|
62
63
|
When I run `cucumber features/foo.feature`
|
63
64
|
Then it should pass
|
65
|
+
|
66
|
+
Scenario: Use Transform from code
|
67
|
+
You can also call the `Transform` method from within a step definition to
|
68
|
+
dynamically run a tranform.
|
69
|
+
|
70
|
+
Note that this is rather useless at present, unless the transform itself has a side-effect, since the `Transform`
|
71
|
+
function still returns the regular expression pattern.
|
72
|
+
|
73
|
+
Given a file named "features/people.feature" with:
|
74
|
+
"""
|
75
|
+
Feature:
|
76
|
+
Scenario:
|
77
|
+
Given people with these ages:
|
78
|
+
| name | age |
|
79
|
+
| sue | 25 |
|
80
|
+
| sally | 77 |
|
81
|
+
"""
|
82
|
+
And a file named "features/step_definitions/steps.rb" with:
|
83
|
+
"""
|
84
|
+
Transform(/a Person aged (\d+)/) do |age|
|
85
|
+
$person = Person.new
|
86
|
+
$person.age = age.to_i
|
87
|
+
$person
|
88
|
+
end
|
89
|
+
|
90
|
+
Given(/^people with these ages:$/) do |table|
|
91
|
+
table.rows.each do |name, age|
|
92
|
+
Transform("a Person aged #{age}")
|
93
|
+
expect($person.age).to eq age.to_i
|
94
|
+
end
|
95
|
+
end
|
96
|
+
"""
|
97
|
+
When I run `cucumber features/people.feature`
|
98
|
+
Then it should pass
|
@@ -32,6 +32,34 @@ Given /^a step definition that looks like this:$/ do |string|
|
|
32
32
|
create_step_definition { string }
|
33
33
|
end
|
34
34
|
|
35
|
+
Given /^a scenario "([^\"]*)" that passes$/ do |name|
|
36
|
+
write_file "features/#{name}.feature",
|
37
|
+
<<-FEATURE
|
38
|
+
Feature: #{name}
|
39
|
+
Scenario: #{name}
|
40
|
+
Given it passes
|
41
|
+
FEATURE
|
42
|
+
|
43
|
+
write_file "features/step_definitions/#{name}_steps.rb",
|
44
|
+
<<-STEPS
|
45
|
+
Given(/^it passes$/) { expect(true).to be true }
|
46
|
+
STEPS
|
47
|
+
end
|
48
|
+
|
49
|
+
Given /^a scenario "([^\"]*)" that fails$/ do |name|
|
50
|
+
write_file "features/#{name}.feature",
|
51
|
+
<<-FEATURE
|
52
|
+
Feature: #{name}
|
53
|
+
Scenario: #{name}
|
54
|
+
Given it fails
|
55
|
+
FEATURE
|
56
|
+
|
57
|
+
write_file "features/step_definitions/#{name}_steps.rb",
|
58
|
+
<<-STEPS
|
59
|
+
Given(/^it fails$/) { expect(false).to be true }
|
60
|
+
STEPS
|
61
|
+
end
|
62
|
+
|
35
63
|
When /^I run the feature with the (\w+) formatter$/ do |formatter|
|
36
64
|
expect(features.length).to eq 1
|
37
65
|
run_feature features.first, formatter
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Given /^a scenario "([^\"]*)" that fails once, then passes$/ do |name|
|
2
|
+
write_file "features/#{name}.feature",
|
3
|
+
<<-FEATURE
|
4
|
+
Feature: #{name}
|
5
|
+
Scenario: #{name}
|
6
|
+
Given it fails once, then passes
|
7
|
+
FEATURE
|
8
|
+
|
9
|
+
write_file "features/step_defnitions/#{name}_steps.rb",
|
10
|
+
<<-STEPS
|
11
|
+
Given(/^it fails once, then passes$/) do
|
12
|
+
$#{name.downcase} ||= 0
|
13
|
+
$#{name.downcase} += 1
|
14
|
+
expect($#{name.downcase}).to eql 2
|
15
|
+
end
|
16
|
+
STEPS
|
17
|
+
end
|
18
|
+
|
19
|
+
Given /^a scenario "([^\"]*)" that fails twice, then passes$/ do |name|
|
20
|
+
write_file "features/#{name}.feature",
|
21
|
+
<<-FEATURE
|
22
|
+
Feature: #{name}
|
23
|
+
Scenario: #{name}
|
24
|
+
Given it fails twice, then passes
|
25
|
+
FEATURE
|
26
|
+
|
27
|
+
write_file "features/step_definitions/#{name}_steps.rb",
|
28
|
+
<<-STEPS
|
29
|
+
Given(/^it fails twice, then passes$/) do
|
30
|
+
$#{name.downcase} ||= 0
|
31
|
+
$#{name.downcase} += 1
|
32
|
+
expect($#{name.downcase}).to eql 3
|
33
|
+
end
|
34
|
+
STEPS
|
35
|
+
end
|
data/lib/cucumber/cli/options.rb
CHANGED
@@ -44,9 +44,10 @@ module Cucumber
|
|
44
44
|
PROFILE_LONG_FLAG = '--profile'
|
45
45
|
NO_PROFILE_LONG_FLAG = '--no-profile'
|
46
46
|
FAIL_FAST_FLAG = '--fail-fast'
|
47
|
+
RETRY_FLAG = '--retry'
|
47
48
|
OPTIONS_WITH_ARGS = ['-r', '--require', '--i18n', '-f', '--format', '-o', '--out',
|
48
49
|
'-t', '--tags', '-n', '--name', '-e', '--exclude',
|
49
|
-
PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG,
|
50
|
+
PROFILE_SHORT_FLAG, PROFILE_LONG_FLAG, RETRY_FLAG,
|
50
51
|
'-l', '--lines', '--port',
|
51
52
|
'-I', '--snippet-type']
|
52
53
|
ORDER_TYPES = %w{defined random}
|
@@ -188,6 +189,9 @@ module Cucumber
|
|
188
189
|
"Disables all profile loading to avoid using the 'default' profile.") do |v|
|
189
190
|
@disable_profile_loading = true
|
190
191
|
end
|
192
|
+
opts.on("#{RETRY_FLAG} ATTEMPTS", "Specify the number of times to retry failing tests (default: 0)") do |v|
|
193
|
+
@options[:retry] = v.to_i
|
194
|
+
end
|
191
195
|
opts.on("-c", "--[no-]color",
|
192
196
|
"Whether or not to use ANSI color in the output. Cucumber decides",
|
193
197
|
"based on your platform and the output destination if not specified.") do |v|
|
@@ -450,7 +454,8 @@ TEXT
|
|
450
454
|
:diff_enabled => true,
|
451
455
|
:snippets => true,
|
452
456
|
:source => true,
|
453
|
-
:duration => true
|
457
|
+
:duration => true,
|
458
|
+
:retry => 0
|
454
459
|
}
|
455
460
|
end
|
456
461
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
|
1
3
|
module Cucumber
|
2
4
|
module Filters
|
3
5
|
|
@@ -29,7 +31,10 @@ module Cucumber
|
|
29
31
|
private
|
30
32
|
|
31
33
|
def shuffled_test_cases
|
32
|
-
|
34
|
+
digester = Digest::SHA2.new(256)
|
35
|
+
@test_cases.map.with_index.
|
36
|
+
sort_by { |_, index| digester.digest((@seed + index).to_s) }.
|
37
|
+
map { |test_case, _| test_case }
|
33
38
|
end
|
34
39
|
|
35
40
|
attr_reader :seed
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'cucumber/core/filter'
|
2
|
+
require 'cucumber/running_test_case'
|
3
|
+
require 'cucumber/events/bus'
|
4
|
+
require 'cucumber/events/after_test_case'
|
5
|
+
|
6
|
+
module Cucumber
|
7
|
+
module Filters
|
8
|
+
class Retry < Core::Filter.new(:configuration)
|
9
|
+
|
10
|
+
def test_case(test_case)
|
11
|
+
configuration.on_event(:after_test_case) do |event|
|
12
|
+
next unless retry_required?(test_case, event)
|
13
|
+
|
14
|
+
test_case_counts[test_case] += 1
|
15
|
+
event.test_case.describe_to(receiver)
|
16
|
+
end
|
17
|
+
|
18
|
+
super
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def retry_required?(test_case, event)
|
24
|
+
event.test_case == test_case && event.result.failed? && test_case_counts[test_case] < configuration.retry_attempts
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_case_counts
|
28
|
+
@test_case_counts ||= Hash.new {|h,k| h[k] = 0 }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|