Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use cucumber --fast-fail to stop on error #90

Merged
merged 1 commit into from
Mar 1, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,11 @@ You should be aware of some default behaviours included in Quke.

Capybara includes the ability to save the source of the current page at any point. Quke has been configured so that if you are not using a headless browser and a step fails it will save the source to file and then use a tool called [Launchy](https://github.com/copiousfreetime/launchy) to open it in your default browser.

You can disable this behaviour using `display_failures: false` in your `.config.yml`

### Quit on 5 failures

If you are running using Chrome or Firefox after the 5th failure Quke will automatically stop. This is to prevent scores of tabs being opened in the browser when an error is found, which may just be the result of an error in the test code.
If you are running using Chrome or Firefox after the 5th failure Quke will automatically stop. This is to prevent scores of tabs being opened in the browser when an error is found and Quke is set to show failures, which may just be the result of an error in the test code.

### Automatically setting Browserstack session status

Expand Down
6 changes: 2 additions & 4 deletions lib/features/support/after_hook.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@
if scenario.failed?
$fail_count = $fail_count + 1

# Tell Cucumber to quit after first failing scenario when stop_on_error is
# true.
# Also experience has shown that should a major element of your service go
# Experience has shown that should a major element of your service go
# down all your tests will start failing which means you can be swamped
# with output from `save_and_open_page`. Using a global count of the
# number of fails, if it hits 5 it will cause cucumber to close.
if Quke::Quke.config.stop_on_error || $fail_count >= 5
if $fail_count >= 5 && !%w["chrome firefox"].include?(Quke::Quke.config.driver)
Cucumber.wants_to_quit = true
else
# Depending on our config, driver and whether we are running headless we
Expand Down
29 changes: 28 additions & 1 deletion lib/quke/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def initialize
# Order is important. @browserstack relies on @proxy being set
@proxy = ::Quke::ProxyConfiguration.new(@data["proxy"] || {})
@browserstack = ::Quke::BrowserstackConfiguration.new(self)
@parallel = ::Quke::ParallelConfiguration.new(@data["parallel"] || {})
@parallel = ::Quke::ParallelConfiguration.new(self)
end

# Returns the value set for +features_folder+.
Expand Down Expand Up @@ -199,6 +199,33 @@ def custom
@data["custom"]
end

# Returns a string representing the agruments that are passed to Cucumber
# by ParallelTests when it creates a new process and executes.
#
# Specifically its the value for ParallelTests' `--test-options` argument
# which will be used when generating the array of args to be passed to
# ParallelTests. It returns just a string rather than an array because
# ParallelTests needs to see this as a single argument.
#
# The additional args are whatever a user enters after the
# `bundle exec quke` command.
def cucumber_arg(additional_args)
# Because cucumber is called in the context of the executing project and
# not Quke it will take its arguments in the context of that location, and
# not from where the Quke currently sits. This means to Cucumber
# 'lib/features' doesn't exist, which means our env.rb never gets loaded.
# Instead we first have to determine where this file is running from when
# called, then we simply replace the last part of that result (which we
# know will be lib/quke) with lib/features. For example __dir__ returns
# '/Users/acruikshanks/projects/defra/quke/lib/quke' but we need Cucumber
# to load '/Users/acruikshanks/projects/defra/quke/lib/features'
# We then pass this full path to Cucumber so it can correctly find the
# folder holding our predefined env.rb file.
env_folder = __dir__.sub!("lib/quke", "lib/features")
fail_fast = "--fail-fast" if stop_on_error
"#{fail_fast} --format pretty -r #{env_folder} -r #{features_folder} #{additional_args.join(' ')}".strip
end

private

# rubocop:disable Metrics/AbcSize
Expand Down
2 changes: 1 addition & 1 deletion lib/quke/cuke_runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class CukeRunner
# parallel.
def initialize(passed_in_args = [])
Quke.config = Configuration.new
@args = Quke.config.parallel.command_args(Quke.config.features_folder, passed_in_args)
@args = Quke.config.parallel.command_args(passed_in_args)
end

# Executes ParallelTests, which in turn executes Cucumber passing in the
Expand Down
31 changes: 8 additions & 23 deletions lib/quke/parallel_configuration.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require "quke/configuration"

module Quke #:nodoc:

# Manages all parallel configuration for Quke.
Expand All @@ -13,7 +15,9 @@ class ParallelConfiguration
# ParallelTests to determine the number.
attr_reader :processes

def initialize(data = {})
def initialize(config)
@config = config
data = @config.data["parallel"] || {}
@enabled = (data["enable"].to_s.downcase.strip == "true")
@group_by = (data["group_by"] || "default").downcase.strip
@processes = (data["processes"] || "0").to_s.downcase.strip.to_i
Expand All @@ -25,12 +29,12 @@ def initialize(data = {})
# The arguments are based on the values set for the parallel configuration
# plus those passed in. It then orders them in an order that makes sense to
# parallel tests.
def command_args(features_folder, additional_args = [])
args = standard_args(features_folder)
def command_args(additional_args = [])
args = standard_args(@config.features_folder)
args += ["--single", "--quiet"] unless @enabled
args += ["--group-by", @group_by] unless @group_by == "default"
args += ["-n", @processes.to_s] if @enabled && @processes.positive?
args + test_options_args(features_folder, additional_args)
args + ["--test-options", @config.cucumber_arg(additional_args)]
end

private
Expand All @@ -44,25 +48,6 @@ def standard_args(features_folder)
]
end

def test_options_args(features_folder, additional_args)
# Because cucumber is called in the context of the executing project and
# not Quke it will take its arguments in the context of that location, and
# not from where the Quke currently sits. This means to Cucumber
# 'lib/features' doesn't exist, which means our env.rb never gets loaded.
# Instead we first have to determine where this file is running from when
# called, then we simply replace the last part of that result (which we
# know will be lib/quke) with lib/features. For example __dir__ returns
# '/Users/acruikshanks/projects/defra/quke/lib/quke' but we need Cucumber
# to load '/Users/acruikshanks/projects/defra/quke/lib/features'
# We then pass this full path to Cucumber so it can correctly find the
# folder holding our predefined env.rb file.
env_folder = __dir__.sub!("lib/quke", "lib/features")
[
"--test-options",
"--format pretty -r #{env_folder} -r #{features_folder} #{additional_args.join(' ')}".strip
]
end

end

end
5 changes: 4 additions & 1 deletion spec/data/.parallel.yml
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
parallel: true
parallel:
enable: true
group_by: "scenarios"
processes: 4
4 changes: 4 additions & 0 deletions spec/data/.parallel_disabled.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
parallel:
enable: false
group_by: "scenarios"
processes: 4
1 change: 1 addition & 0 deletions spec/data/.stop_on_error.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
stop_on_error: true
26 changes: 26 additions & 0 deletions spec/quke/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,32 @@
end
end

describe "#cucumber_arg" do
let(:default_arg) { "--format pretty -r #{File.join(Dir.pwd, 'lib', 'features')} -r features" }
let(:additional_args) { ["--tags", "@wip"] }

context "when there are no additional arguments" do
it "returns the default cucumber arg value" do
Quke::Configuration.file_location = data_path(".no-file.yml")
expect(subject.cucumber_arg([])).to eq(default_arg)
end
end

context "when `stop_on_error` is true" do
it "returns the default cucumber arg value including the '--fail-fast' option" do
Quke::Configuration.file_location = data_path(".stop_on_error.yml")
expect(subject.cucumber_arg([])).to eq("--fail-fast #{default_arg}")
end
end

context "when there are additional arguments" do
it "returns the default cucumber arg value plus the arguments" do
Quke::Configuration.file_location = data_path(".no-file.yml")
expect(subject.cucumber_arg(additional_args)).to eq("#{default_arg} #{additional_args.join(' ')}")
end
end
end

describe ".file_name" do
context "environment variable not set" do
it "returns the default value '.config.yml'" do
Expand Down
50 changes: 33 additions & 17 deletions spec/quke/parallel_configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,75 +46,91 @@
end

describe "#command_args" do
let(:feature_folder) { "features" }
let(:additional_args) { ["--tags", "@wip"] }

context "when the instance has been instantiated with no data" do
subject { Quke::ParallelConfiguration.new }
subject do
Quke::Configuration.file_location = data_path(".no-file.yml")
Quke::Configuration.new.parallel
end

it "returns an array of default args for ParallelTests" do
expect(subject.command_args(feature_folder)).to match_array(
expect(subject.command_args).to match_array(
[
feature_folder,
"features",
"--type",
"cucumber",
"--serialize-stdout",
"--combine-stderr",
"--single",
"--quiet",
"--test-options",
"--format pretty -r #{File.join(Dir.pwd, 'lib', 'features')} -r #{feature_folder}"
"--format pretty -r #{File.join(Dir.pwd, 'lib', 'features')} -r features"
]
)
end

end

context "when the instance has been instantiated with parallel enabled" do
subject { Quke::ParallelConfiguration.new("enable" => "true") }
subject do
Quke::Configuration.file_location = data_path(".parallel.yml")
Quke::Configuration.new.parallel
end

it "returns an array without the args '--single' and '--quiet'" do
args = subject.command_args(feature_folder)
args = subject.command_args
expect(args).not_to include(["--single", "--quiet"])
end

end

context "when the instance has been instantiated with group_by set" do
subject { Quke::ParallelConfiguration.new("group_by" => "scenarios") }
subject do
Quke::Configuration.file_location = data_path(".parallel.yml")
Quke::Configuration.new.parallel
end

it "returns an array with the args '--group-by' and 'scenarios'" do
args = subject.command_args(feature_folder)
args = subject.command_args
expect(args).to include("--group-by", "scenarios")
end

end

context "when the instance has been instantiated with processes set" do
subject { Quke::ParallelConfiguration.new("enable" => "true", "processes" => "4") }
subject do
Quke::Configuration.file_location = data_path(".parallel.yml")
Quke::Configuration.new.parallel
end

it "returns an array with the args '-n' and '4'" do
args = subject.command_args(feature_folder)
args = subject.command_args
expect(args).to include("-n", "4")
end

end

context "when the instance has been instantiated with processes set but parallel disabled" do
subject { Quke::ParallelConfiguration.new("processes" => "4") }
subject do
Quke::Configuration.file_location = data_path(".parallel_disabled.yml")
Quke::Configuration.new.parallel
end

it "returns an array without the args '-n' and '4'" do
args = subject.command_args(feature_folder)
args = subject.command_args
expect(args).not_to include("-n", "4")
end

end

context "when additional arguments are passed in" do
subject { Quke::ParallelConfiguration.new }
let(:additional_args) { ["--tags", "@wip"] }
subject do
Quke::Configuration.file_location = data_path(".no-file.yml")
Quke::Configuration.new.parallel
end

it "the last argument contains those values" do
args = subject.command_args(feature_folder, additional_args)
args = subject.command_args(additional_args)
expect(args.last).to include(additional_args.join(" "))
end

Expand Down