cucumber 5.1.1 → 6.0.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 +1348 -1094
- data/CONTRIBUTING.md +17 -5
- data/README.md +4 -2
- data/lib/cucumber/cli/configuration.rb +23 -1
- data/lib/cucumber/cli/options.rb +3 -2
- data/lib/cucumber/formatter/backtrace_filter.rb +1 -0
- data/lib/cucumber/formatter/html.rb +1 -1
- data/lib/cucumber/formatter/http_io.rb +33 -41
- data/lib/cucumber/formatter/io.rb +3 -3
- data/lib/cucumber/formatter/json.rb +2 -5
- data/lib/cucumber/formatter/junit.rb +18 -1
- data/lib/cucumber/formatter/message.rb +1 -1
- data/lib/cucumber/formatter/pretty.rb +1 -1
- data/lib/cucumber/formatter/progress.rb +1 -1
- data/lib/cucumber/formatter/publish_banner_printer.rb +3 -1
- data/lib/cucumber/formatter/rerun.rb +1 -1
- data/lib/cucumber/formatter/steps.rb +1 -1
- data/lib/cucumber/formatter/summary.rb +1 -1
- data/lib/cucumber/formatter/url_reporter.rb +3 -16
- data/lib/cucumber/glue/dsl.rb +1 -1
- data/lib/cucumber/glue/proto_world.rb +14 -32
- data/lib/cucumber/glue/registry_and_more.rb +1 -1
- data/lib/cucumber/version +1 -1
- metadata +91 -71
data/CONTRIBUTING.md
CHANGED
@@ -21,17 +21,29 @@ You can chat with the core team on https://gitter.im/cucumber/contributors. We t
|
|
21
21
|
|
22
22
|
## Installing your own gems
|
23
23
|
|
24
|
-
A `Gemfile.local`-file can be used to have your own gems installed to support
|
25
|
-
|
24
|
+
A `Gemfile.local`-file can be used to have your own gems installed to support your normal development workflow.
|
25
|
+
Execute `bundle config set --local gemfile Gemfile.local` to use it per default.
|
26
26
|
|
27
27
|
Example:
|
28
28
|
|
29
29
|
~~~ruby
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
# Include the regular Gemfile
|
31
|
+
eval File.read('Gemfile')
|
32
|
+
|
33
|
+
group :development do
|
34
|
+
gem 'byebug'
|
35
|
+
gem 'debase', require: false
|
36
|
+
gem 'ruby-debug-ide', require: false
|
37
|
+
gem 'pry'
|
38
|
+
gem 'pry-byebug'
|
39
|
+
end
|
33
40
|
~~~
|
34
41
|
|
42
|
+
## Using Visual Studio Code?
|
43
|
+
|
44
|
+
Sample for launch.json configuration is available in
|
45
|
+
[docs/vscode-example-launch-configuration.md](https://github.com/cucumber/cucumber-ruby/blob/master/docs/vscode-example-launch-configuration.md)
|
46
|
+
|
35
47
|
## Note on Patches/Pull Requests
|
36
48
|
|
37
49
|
* Fork the project. Make a branch for your change.
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
[](https://opencollective.com/cucumber)
|
2
2
|
[](https://opencollective.com/cucumber)
|
3
|
-
|
3
|
+
[](https://oselvar.com/github/cucumber/oselvar-github-metrics/main/cucumber/cucumber-ruby)
|
4
|
+
[](https://oselvar.com/github/cucumber/oselvar-github-metrics/main/cucumber/cucumber-ruby)
|
4
5
|
[](https://circleci.com/gh/cucumber/cucumber-ruby)
|
5
|
-
|
6
6
|
[](https://codeclimate.com/github/cucumber/cucumber-ruby)
|
7
7
|
[](https://coveralls.io/r/cucumber/cucumber-ruby?branch=master)
|
8
8
|
|
@@ -24,6 +24,8 @@ Where to get more info:
|
|
24
24
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for info on contributing to Cucumber.
|
25
25
|
|
26
26
|
## Supported platforms
|
27
|
+
* Ruby 3.0
|
28
|
+
* Ruby 2.7
|
27
29
|
* Ruby 2.6
|
28
30
|
* Ruby 2.5
|
29
31
|
* Ruby 2.4
|
@@ -126,13 +126,35 @@ module Cucumber
|
|
126
126
|
end
|
127
127
|
|
128
128
|
def arrange_formats
|
129
|
-
|
129
|
+
add_default_formatter if needs_default_formatter?
|
130
|
+
|
130
131
|
@options[:formats] = @options[:formats].sort_by do |f|
|
131
132
|
f[2] == @out_stream ? -1 : 1
|
132
133
|
end
|
133
134
|
@options[:formats].uniq!
|
134
135
|
@options.check_formatter_stream_conflicts
|
135
136
|
end
|
137
|
+
|
138
|
+
def add_default_formatter
|
139
|
+
@options[:formats] << ['pretty', {}, @out_stream]
|
140
|
+
end
|
141
|
+
|
142
|
+
def needs_default_formatter?
|
143
|
+
formatter_missing? || publish_only?
|
144
|
+
end
|
145
|
+
|
146
|
+
def formatter_missing?
|
147
|
+
@options[:formats].empty?
|
148
|
+
end
|
149
|
+
|
150
|
+
def publish_only?
|
151
|
+
@options[:formats]
|
152
|
+
.uniq
|
153
|
+
.map { |formatter, _, stream| [formatter, stream] }
|
154
|
+
.uniq
|
155
|
+
.reject { |formatter, stream| formatter == 'message' && stream != @out_stream }
|
156
|
+
.empty?
|
157
|
+
end
|
136
158
|
end
|
137
159
|
end
|
138
160
|
end
|
data/lib/cucumber/cli/options.rb
CHANGED
@@ -22,7 +22,8 @@ module Cucumber
|
|
22
22
|
"#{INDENT}filename instead."],
|
23
23
|
'stepdefs' => ['Cucumber::Formatter::Stepdefs', "Prints All step definitions with their locations. Same as\n" \
|
24
24
|
"#{INDENT}the usage formatter, except that steps are not printed."],
|
25
|
-
'junit' => ['Cucumber::Formatter::Junit',
|
25
|
+
'junit' => ['Cucumber::Formatter::Junit', "Generates a report similar to Ant+JUnit. Use\n" \
|
26
|
+
"#{INDENT}junit,fileattribute=true to include a file attribute."],
|
26
27
|
'json' => ['Cucumber::Formatter::Json', '[DEPRECATED] Prints the feature as JSON'],
|
27
28
|
'message' => ['Cucumber::Formatter::Message', 'Outputs protobuf messages'],
|
28
29
|
'html' => ['Cucumber::Formatter::HTML', 'Outputs HTML report'],
|
@@ -335,7 +336,7 @@ Specify SEED to reproduce the shuffling from a previous run.
|
|
335
336
|
'option is specified; all loading becomes explicit.',
|
336
337
|
'Files in directories named "support" are still always',
|
337
338
|
'loaded first when their parent directories are',
|
338
|
-
'required or if the "support"
|
339
|
+
'required or if the "support" directories themselves are',
|
339
340
|
'explicitly required.',
|
340
341
|
'This option can be specified multiple times.'
|
341
342
|
]
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'tempfile'
|
3
|
+
require 'shellwords'
|
3
4
|
|
4
5
|
module Cucumber
|
5
6
|
module Formatter
|
@@ -18,48 +19,43 @@ module Cucumber
|
|
18
19
|
|
19
20
|
class CurlOptionParser
|
20
21
|
def self.parse(options)
|
21
|
-
|
22
|
-
http_method = 'PUT'
|
23
|
-
url = chunks[0]
|
24
|
-
headers = ''
|
25
|
-
|
26
|
-
last_flag = nil
|
27
|
-
chunks.each do |chunk|
|
28
|
-
if ['-X', '--request'].include?(chunk)
|
29
|
-
last_flag = '-X'
|
30
|
-
next
|
31
|
-
end
|
32
|
-
|
33
|
-
if chunk == '-H'
|
34
|
-
last_flag = '-H'
|
35
|
-
next
|
36
|
-
end
|
22
|
+
args = Shellwords.split(options)
|
37
23
|
|
38
|
-
|
39
|
-
|
40
|
-
|
24
|
+
url = nil
|
25
|
+
http_method = 'PUT'
|
26
|
+
headers = {}
|
27
|
+
|
28
|
+
until args.empty?
|
29
|
+
arg = args.shift
|
30
|
+
case arg
|
31
|
+
when '-X', '--request'
|
32
|
+
http_method = remove_arg_for(args, arg)
|
33
|
+
when '-H'
|
34
|
+
header_arg = remove_arg_for(args, arg)
|
35
|
+
headers = headers.merge(parse_header(header_arg))
|
36
|
+
else
|
37
|
+
raise StandardError, "#{options} was not a valid curl command. Can't set url to #{arg} it is already set to #{url}" if url
|
38
|
+
url = arg
|
41
39
|
end
|
42
|
-
|
43
|
-
headers += chunk if last_flag == '-H'
|
44
40
|
end
|
41
|
+
raise StandardError, "#{options} was not a valid curl command" unless url
|
45
42
|
|
46
43
|
[
|
47
44
|
url,
|
48
45
|
http_method,
|
49
|
-
|
46
|
+
headers
|
50
47
|
]
|
51
48
|
end
|
52
49
|
|
53
|
-
def self.
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
headers.scan(str_scanner) do |header|
|
58
|
-
header = header.compact!
|
59
|
-
hash_headers[header[0]] = header[1]&.strip
|
60
|
-
end
|
50
|
+
def self.remove_arg_for(args, arg)
|
51
|
+
return args.shift unless args.empty?
|
52
|
+
raise StandardError, "Missing argument for #{arg}"
|
53
|
+
end
|
61
54
|
|
62
|
-
|
55
|
+
def self.parse_header(header_arg)
|
56
|
+
parts = header_arg.split(':', 2)
|
57
|
+
raise StandardError, "#{header_arg} was not a valid header" unless parts.length == 2
|
58
|
+
{ parts[0].strip => parts[1].strip }
|
63
59
|
end
|
64
60
|
end
|
65
61
|
|
@@ -76,10 +72,11 @@ module Cucumber
|
|
76
72
|
end
|
77
73
|
|
78
74
|
def close
|
79
|
-
|
80
|
-
|
81
|
-
@reporter.report(resource_uri)
|
75
|
+
response = send_content(@uri, @method, @headers)
|
76
|
+
@reporter.report(response.body)
|
82
77
|
@write_io.close
|
78
|
+
return if response.is_a?(Net::HTTPSuccess) || response.is_a?(Net::HTTPRedirection)
|
79
|
+
raise StandardError, "request to #{uri} failed with status #{response.code}"
|
83
80
|
end
|
84
81
|
|
85
82
|
def write(data)
|
@@ -121,16 +118,11 @@ module Cucumber
|
|
121
118
|
|
122
119
|
case response
|
123
120
|
when Net::HTTPAccepted
|
124
|
-
|
125
|
-
|
126
|
-
send_content(URI(response['Location']), 'PUT', headers, attempt - 1)
|
127
|
-
when Net::HTTPSuccess
|
128
|
-
uri
|
121
|
+
send_content(URI(response['Location']), 'PUT', {}, attempt - 1) if response['Location']
|
129
122
|
when Net::HTTPRedirection
|
130
123
|
send_content(URI(response['Location']), method, headers, attempt - 1)
|
131
|
-
else
|
132
|
-
raise StandardError, "request to #{uri} failed with status #{response.code}"
|
133
124
|
end
|
125
|
+
response
|
134
126
|
end
|
135
127
|
|
136
128
|
def build_request(uri, method, headers)
|
@@ -9,13 +9,13 @@ module Cucumber
|
|
9
9
|
module Io
|
10
10
|
module_function
|
11
11
|
|
12
|
-
def ensure_io(path_or_url_or_io)
|
12
|
+
def ensure_io(path_or_url_or_io, error_stream)
|
13
13
|
return nil if path_or_url_or_io.nil?
|
14
14
|
return path_or_url_or_io if io?(path_or_url_or_io)
|
15
15
|
|
16
16
|
io = if url?(path_or_url_or_io)
|
17
17
|
url = path_or_url_or_io
|
18
|
-
reporter = url.start_with?(Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL) ? URLReporter.new(
|
18
|
+
reporter = url.start_with?(Cucumber::Cli::Options::CUCUMBER_PUBLISH_URL) ? URLReporter.new(error_stream) : NoReporter.new
|
19
19
|
HTTPIO.open(url, nil, reporter)
|
20
20
|
else
|
21
21
|
File.open(path_or_url_or_io, Cucumber.file_mode('w'))
|
@@ -64,7 +64,7 @@ module Cucumber
|
|
64
64
|
raise "You *must* specify --out FILE for the #{name} formatter" unless String == path.class
|
65
65
|
raise "I can't write #{name} to a directory - it has to be a file" if File.directory?(path)
|
66
66
|
raise "I can't write #{name} to a file in the non-existing directory #{File.dirname(path)}" unless File.directory?(File.dirname(path))
|
67
|
-
ensure_io(path)
|
67
|
+
ensure_io(path, nil)
|
68
68
|
end
|
69
69
|
|
70
70
|
def ensure_dir(path, name)
|
@@ -22,7 +22,7 @@ module Cucumber
|
|
22
22
|
'6.0.0'
|
23
23
|
)
|
24
24
|
|
25
|
-
@io = ensure_io(config.out_stream)
|
25
|
+
@io = ensure_io(config.out_stream, config.error_stream)
|
26
26
|
@ast_lookup = AstLookup.new(config)
|
27
27
|
@feature_hashes = []
|
28
28
|
@step_or_hook_hash = {}
|
@@ -94,10 +94,7 @@ module Cucumber
|
|
94
94
|
test_step_output << src
|
95
95
|
return
|
96
96
|
end
|
97
|
-
if
|
98
|
-
content = File.open(src, 'rb', &:read)
|
99
|
-
data = encode64(content)
|
100
|
-
elsif mime_type =~ /;base64$/
|
97
|
+
if mime_type =~ /;base64$/
|
101
98
|
mime_type = mime_type[0..-8]
|
102
99
|
data = src
|
103
100
|
else
|
@@ -132,9 +132,12 @@ module Cucumber
|
|
132
132
|
duration = ResultBuilder.new(result).test_case_duration
|
133
133
|
@current_feature_data[:time] += duration
|
134
134
|
classname = @current_feature_data[:feature].name
|
135
|
+
filename = @current_feature_data[:uri]
|
135
136
|
name = scenario_designation
|
136
137
|
|
137
|
-
|
138
|
+
testcase_attributes = get_testcase_attributes(classname, name, duration, filename)
|
139
|
+
|
140
|
+
@current_feature_data[:builder].testcase(testcase_attributes) do
|
138
141
|
if !result.passed? && result.ok?(@config.strict)
|
139
142
|
@current_feature_data[:builder].skipped
|
140
143
|
@current_feature_data[:skipped] += 1
|
@@ -157,6 +160,20 @@ module Cucumber
|
|
157
160
|
@current_feature_data[:tests] += 1
|
158
161
|
end
|
159
162
|
|
163
|
+
def get_testcase_attributes(classname, name, duration, filename)
|
164
|
+
{ classname: classname, name: name, time: format('%<duration>.6f', duration: duration) }.tap do |attributes|
|
165
|
+
attributes[:file] = filename if add_fileattribute?
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def add_fileattribute?
|
170
|
+
return false if @config.formats.nil? || @config.formats.empty?
|
171
|
+
|
172
|
+
!!@config.formats.find do |format|
|
173
|
+
format.first == 'junit' && format.dig(1, 'fileattribute') == 'true'
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
160
177
|
def get_backtrace_object(result)
|
161
178
|
if result.failed?
|
162
179
|
result.exception
|
@@ -32,7 +32,7 @@ module Cucumber
|
|
32
32
|
private :in_scenario_outline, :print_background_steps
|
33
33
|
|
34
34
|
def initialize(config)
|
35
|
-
@io = ensure_io(config.out_stream)
|
35
|
+
@io = ensure_io(config.out_stream, config.error_stream)
|
36
36
|
@config = config
|
37
37
|
@options = config.to_hash
|
38
38
|
@snippets_input = []
|
@@ -5,7 +5,7 @@ module Cucumber
|
|
5
5
|
# The formatter used for <tt>--format steps</tt>
|
6
6
|
class Steps
|
7
7
|
def initialize(runtime, path_or_io, options)
|
8
|
-
@io = ensure_io(path_or_io)
|
8
|
+
@io = ensure_io(path_or_io, nil)
|
9
9
|
@options = options
|
10
10
|
@step_definition_files = collect_steps(runtime)
|
11
11
|
end
|
@@ -16,7 +16,7 @@ module Cucumber
|
|
16
16
|
|
17
17
|
def initialize(config)
|
18
18
|
@config = config
|
19
|
-
@io = ensure_io(config.out_stream)
|
19
|
+
@io = ensure_io(config.out_stream, config.error_stream)
|
20
20
|
@ast_lookup = AstLookup.new(config)
|
21
21
|
@counts = ConsoleCounts.new(@config)
|
22
22
|
@issues = ConsoleIssues.new(@config, @ast_lookup)
|
@@ -1,30 +1,17 @@
|
|
1
|
-
require 'cucumber/term/banner'
|
2
|
-
|
3
1
|
module Cucumber
|
4
2
|
module Formatter
|
5
3
|
class URLReporter
|
6
|
-
include Term::Banner
|
7
|
-
|
8
4
|
def initialize(io)
|
9
5
|
@io = io
|
10
6
|
end
|
11
7
|
|
12
|
-
def report(
|
13
|
-
|
14
|
-
display_banner(
|
15
|
-
[
|
16
|
-
'View your Cucumber Report at:',
|
17
|
-
[["https://reports.cucumber.io#{uri.path}", :cyan, :bold, :underline]],
|
18
|
-
'',
|
19
|
-
[['This report will self-destruct in 24h unless it is claimed or deleted.', :green, :bold]]
|
20
|
-
],
|
21
|
-
@io
|
22
|
-
)
|
8
|
+
def report(banner)
|
9
|
+
@io.puts(banner)
|
23
10
|
end
|
24
11
|
end
|
25
12
|
|
26
13
|
class NoReporter
|
27
|
-
def report(
|
14
|
+
def report(banner); end
|
28
15
|
end
|
29
16
|
end
|
30
17
|
end
|
data/lib/cucumber/glue/dsl.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'cucumber/gherkin/formatter/ansi_escapes'
|
4
4
|
require 'cucumber/core/test/data_table'
|
5
5
|
require 'cucumber/deprecate'
|
6
|
+
require 'mime/types'
|
6
7
|
|
7
8
|
module Cucumber
|
8
9
|
module Glue
|
@@ -72,47 +73,28 @@ module Cucumber
|
|
72
73
|
MultilineArgument::DataTable.from(text_or_table)
|
73
74
|
end
|
74
75
|
|
75
|
-
# Print a message to the output.
|
76
|
-
#
|
77
|
-
# @note Cucumber might surprise you with the behaviour of this method. Instead
|
78
|
-
# of sending the output directly to STDOUT, Cucumber will intercept and cache
|
79
|
-
# the message until the current step has finished, and then display it.
|
80
|
-
#
|
81
|
-
# If you'd prefer to see the message immediately, call {Kernel.puts} instead.
|
82
|
-
def puts(*messages)
|
83
|
-
Cucumber.deprecate(
|
84
|
-
'Messages emitted with "puts" will no longer be caught by Cucumber ' \
|
85
|
-
'and sent to the formatter. If you want message to be in the formatted output, ' \
|
86
|
-
"please use log(message) instead.\n" \
|
87
|
-
'If you simply want it in the console, '\
|
88
|
-
'keep using "puts" (or Kernel.puts to avoid this message)',
|
89
|
-
'puts(message)',
|
90
|
-
'6.0.0'
|
91
|
-
)
|
92
|
-
messages.each { |message| log(message.to_s) }
|
93
|
-
end
|
94
|
-
|
95
76
|
# Pause the tests and ask the operator for input
|
96
77
|
def ask(question, timeout_seconds = 60)
|
97
78
|
super
|
98
79
|
end
|
99
80
|
|
100
|
-
# Embed an image in the output
|
101
|
-
def embed(file, mime_type, _label = 'Screenshot')
|
102
|
-
Cucumber.deprecate(
|
103
|
-
'Please use attach(file, media_type) instead',
|
104
|
-
'embed(file, mime_type, label)',
|
105
|
-
'6.0.0'
|
106
|
-
)
|
107
|
-
attach(file, mime_type)
|
108
|
-
end
|
109
|
-
|
110
81
|
def log(*messages)
|
111
82
|
messages.each { |message| attach(message.to_s.dup, 'text/x.cucumber.log+plain') }
|
112
83
|
end
|
113
84
|
|
114
|
-
|
115
|
-
|
85
|
+
# Attach a file to the output
|
86
|
+
# @param file [string|io] the file to attach.
|
87
|
+
# It can be a string containing the file content itself,
|
88
|
+
# the file path, or an IO ready to be read.
|
89
|
+
# @param media_type [string] the media type. If file is a valid path,
|
90
|
+
# media_type can be ommitted, it will then be inferred from the file name.
|
91
|
+
def attach(file, media_type = nil)
|
92
|
+
return super unless File.file?(file)
|
93
|
+
|
94
|
+
content = File.read(file, mode: 'rb')
|
95
|
+
media_type = MIME::Types.type_for(file).first if media_type.nil?
|
96
|
+
|
97
|
+
super(content, media_type.to_s)
|
116
98
|
end
|
117
99
|
|
118
100
|
# Mark the matched step as pending.
|