Skip to content
Open
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
12 changes: 3 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
steps:
# Downloads a copy of the code in your repository before running CI tests
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v6
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of sonarcloud analysis

Expand All @@ -26,18 +26,12 @@ jobs:
run: |
bundle exec rubocop --format progress --format json --out rubocop-result.json

# This includes an extra run step. The sonarcloud analysis will be run in a docker container with the current
# folder mounted as `/github/workspace`. The problem is when the .resultset.json file is generated it will
# reference the code in the current folder. So to enable sonarcloud to matchup code coverage with the files we use
# sed to update the references in .resultset.json
# https://community.sonarsource.com/t/code-coverage-doesnt-work-with-github-action/16747/6
- name: Run unit tests
run: |
bundle exec rspec
sed -i 's/\/home\/runner\/work\/quke\/quke\//\/github\/workspace\//g' coverage/.resultset.json

- name: Analyze with SonarCloud
uses: sonarsource/sonarcloud-github-action@master
uses: sonarsource/sonarqube-scan-action@v7
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This is provided automatically by GitHub
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} # This needs to be set in your repo; settings -> secrets
SONAR_TOKEN: ${{ secrets.RUBY_SONAR_TOKEN }} # This needs to be set in your repo; settings -> secrets
5 changes: 4 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ inherit_gem:
defra_ruby_style:
- default.yml

require:
plugins:
- rubocop-rake
- rubocop-rspec

AllCops:
TargetRubyVersion: 3.4
32 changes: 9 additions & 23 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
@@ -1,31 +1,22 @@
# This configuration was generated by
# `rubocop --auto-gen-config`
# on 2023-08-14 14:02:05 UTC using RuboCop version 1.56.0.
# on 2026-05-01 16:22:43 UTC using RuboCop version 1.86.1.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
# versions of RuboCop, may require this file to be generated again.

# Offense count: 8
# Configuration parameters: EnforcedStyle, AllowedGems, Include.
# SupportedStyles: Gemfile, gems.rb, gemspec
# Include: **/*.gemspec, **/Gemfile, **/gems.rb
Gemspec/DevelopmentDependencies:
Exclude:
- 'quke.gemspec'

# Offense count: 1
# Configuration parameters: Severity, Include.
# Include: **/*.gemspec
Gemspec/RequiredRubyVersion:
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
Lint/DuplicateBranch:
Exclude:
- 'quke.gemspec'
- 'lib/quke/driver_registration.rb'

# Offense count: 1
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches.
Lint/DuplicateBranch:
# Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
Metrics/AbcSize:
Exclude:
- 'lib/quke/driver_registration.rb'
- 'lib/quke/driver_configuration.rb'

# Offense count: 16
# Configuration parameters: Prefixes, AllowedPatterns.
Expand All @@ -36,7 +27,7 @@ RSpec/ContextWording:
- 'spec/quke/driver_configuration_spec.rb'
- 'spec/quke/driver_registration_spec.rb'

# Offense count: 18
# Offense count: 17
# Configuration parameters: CountAsOne.
RSpec/ExampleLength:
Max: 22
Expand All @@ -57,7 +48,7 @@ RSpec/IdenticalEqualityAssertion:
RSpec/MessageSpies:
EnforcedStyle: receive

# Offense count: 10
# Offense count: 9
RSpec/MultipleExpectations:
Max: 17

Expand All @@ -75,8 +66,3 @@ RSpec/NamedSubject:
- 'spec/quke/browserstack_status_reporter_spec.rb'
- 'spec/quke/configuration_spec.rb'
- 'spec/quke/proxy_configuration_spec.rb'

# Offense count: 1
# Configuration parameters: AllowedGroups.
RSpec/NestedGroups:
Max: 4
2 changes: 1 addition & 1 deletion .ruby-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.2.2
3.4.6
3 changes: 2 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ group :development, :test do
gem "rdoc"
gem "rspec"
gem "rubocop"
gem "rubocop-factory_bot"
gem "rubocop-rake"
gem "rubocop-rspec"
gem "simplecov", "~> 0.17.1"
gem "simplecov", "~> 0.22"
gem "simplecov-json", require: false
gem "webmock"
end
46 changes: 23 additions & 23 deletions lib/features/support/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,25 @@
driver_reg = Quke::DriverRegistration.new(driver_config, Quke::Quke.config)
driver = driver_reg.register(Quke::Quke.config.driver)

# We need bs_local to be declared outside of the AfterConfiguration block below
# so that it's available in the at_exit block.
# Did try simply calling it @bs_local inside the AfterConfiguration but that
# just kept causing Quke to crash immediately (shrug!)
bs_local = nil

Capybara.default_driver = driver
Capybara.javascript_driver = driver

# Chrome 147+ raises UnknownError with "Node with given id does not belong to
# the document" instead of StaleElementReferenceError when a cached DOM node
# reference becomes invalid after navigation. Adding UnknownError to Capybara's
# retriable errors makes Chrome behave like Firefox, which retries internally.
if Quke::Quke.config.driver == "chrome"
module QukeChromeStaleNodeFix
def invalid_element_errors
@invalid_element_errors ||=
super + [::Selenium::WebDriver::Error::UnknownError]
end
end
Capybara::Selenium::Driver.prepend(QukeChromeStaleNodeFix)
end

# default_max_wait_time is the maximum time Capybara will wait for an element
# to appear. You may wish to override it if you are having to deal with a slow
# or unresponsive web site.
Expand All @@ -41,27 +51,21 @@
# which can mess up your project structure.
Capybara.save_path = "tmp/"

# There aren't specific hooks we can attach to that only get called once before
# and after all tests have run in Cucumber. Therefore the next best thing is to
# hook into the AfterConfiguration and at_exit blocks.
#
# As its name suggests, this gets called after Cucumber has been configured i.e.
# all the steps above are complete. Fortunately this is before the tests start
# running so its the best place for us to start up the browserstack local
# testing binary (if it's required)
AfterConfiguration do
# BeforeAll / AfterAll run exactly once around the entire test run (Cucumber 8+).
# We use BeforeAll to start the BrowserStack Local binary when local testing is
# enabled, and AfterAll to stop it cleanly inside Cucumber's lifecycle.
BeforeAll do
if Quke::Quke.config.browserstack.test_locally?
bs_local = BrowserStack::Local.new

# starts the Local instance with the required arguments via its management
# API
bs_local.start(Quke::Quke.config.browserstack.local_testing_args)
end
end

# This is the very last thing Cucumber calls that we can hook onto. Typically
# used for final cleanup, we make use of it to kill our browserstack local
# testing binary, and update the status of the session in browserstack
AfterAll do
bs_local&.stop
end

# Update the BrowserStack session status (pass/fail) after all tests complete.
at_exit do
# Because of the way cucumber works everthing is made global. This also means
# any variables we set also need to be made global so they can be accessed
Expand All @@ -80,8 +84,4 @@
end
end
# rubocop:enable Style/GlobalVars
if bs_local && Quke::Quke.config.browserstack.test_locally?
# stop the local instance
bs_local.stop
end
end
6 changes: 3 additions & 3 deletions lib/quke/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ def pause
def stop_on_error
# This use of Yaml.load to convert a string to a boolean comes from
# http://stackoverflow.com/a/21804027/6117745
YAML.load(@data["stop_on_error"])
YAML.safe_load(@data["stop_on_error"])
end

# Returns the value set for +display_failures+.
Expand Down Expand Up @@ -267,7 +267,7 @@ def default_data!(data)
# rubocop:disable Style/InverseMethods
"display_failures" => !(data["display_failures"].to_s.downcase.strip == "false"),
# rubocop:enable Style/InverseMethods
"custom" => (data["custom"] || nil)
"custom" => data["custom"] || nil
)
end
# rubocop:enable Metrics/AbcSize
Expand All @@ -278,7 +278,7 @@ def load_yml_data
if File.exist? self.class.file_location
# YAML.load_file returns false if the file exists but is empty. So
# added the || {} to ensure we always return a hash from this method
YAML.load_file(self.class.file_location) || {}
YAML.safe_load_file(self.class.file_location) || {}
else
{}
end
Expand Down
5 changes: 4 additions & 1 deletion lib/quke/driver_configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ def chrome

options = Selenium::WebDriver::Options.chrome(args: args)

options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-back-forward-cache")
options.add_argument("--disable-features=BackForwardCache,BuiltInJsonViewer")
options.add_argument("--proxy-server=#{config.proxy.host}:#{config.proxy.port}") if config.proxy.use_proxy?
options.add_argument("--proxy-bypass-list=#{no_proxy}") unless config.proxy.no_proxy.empty?

Expand Down Expand Up @@ -97,7 +100,7 @@ def chrome
#
def firefox
options = Selenium::WebDriver::Firefox::Options.new(profile: firefox_profile)
options.headless! if config.headless
options.add_argument("--headless") if config.headless

options
end
Expand Down
2 changes: 1 addition & 1 deletion lib/quke/driver_registration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def browserstack
app,
browser: :remote,
url: @config.browserstack.url,
desired_capabilities: @driver_config.browserstack
capabilities: [@driver_config.browserstack]
)
# :simplecov_ignore:
end
Expand Down
2 changes: 1 addition & 1 deletion lib/quke/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# frozen_string_literal: true

module Quke # :nodoc:
VERSION = "0.10.0"
VERSION = "0.11.0"
end
14 changes: 7 additions & 7 deletions quke.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,25 @@ Gem::Specification.new do |spec|
"public gem pushes."
end

spec.required_ruby_version = ">= 2.4"
spec.required_ruby_version = ">= 3.4"

# We need the cucumber gem to use cucumber, obviously!
spec.add_dependency "cucumber", "~> 3.1"
spec.add_dependency "cucumber", "~> 11.0"

# We use capybara to drive whichever browser we are using, and by drive we
# mean things like fill_in x, click_on y etc. Capybara makes it much easier to
# do this, though if you're willing to go a level lower you can write your own
# code to tell selenium how to interact with a web page
spec.add_dependency "capybara", "~> 3.14"
spec.add_dependency "capybara", "~> 3.40"

# We bring in rspec-expectations to simplify how to actually test if a page is
# correct. For example you can test you are on the right page in a step using
# expect(page).to have_text 'Welcome to test nirvana!'
spec.add_dependency "rspec-expectations", "~> 3.8"
spec.add_dependency "rspec-expectations", "~> 3.13"

# selenium-webdriver is used to drive browsers like Firefox, Chrome and
# Internet Explorer.
spec.add_dependency "selenium-webdriver", "~> 4.1"
spec.add_dependency "selenium-webdriver", "~> 4.43"

# Experience has shown that keeping tests dry helps make them more
# maintainable over time. One practice that helps is the use of the
Expand All @@ -63,13 +63,13 @@ Gem::Specification.new do |spec|
# different steps. Site_Prism provides a page object framework, and we build
# it into the gem so users of Quke don't have to add and setup this dependency
# themselves
spec.add_dependency "site_prism", "~> 3.0"
spec.add_dependency "site_prism", "~> 6.0"

# Capybara includes a method called save_and_open_page. Without Launchy it
# will still save to file a copy of the source html of the page in question
# at that time. However simply adding this line into the gemfile means it
# will instead open in the default browser instead.
spec.add_dependency "launchy", "~> 2.4"
spec.add_dependency "launchy", "~> 3.1"

# Ruby bindings for BrowserStack Local. This gem handles downloading and
# installing the right version of the binary for the OS Quke is running on,
Expand Down
26 changes: 9 additions & 17 deletions spec/quke/driver_configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,16 @@
it "returns an instance of Chrome::Options where the proxy details are NOT set" do
Quke::Configuration.file_location = data_path(".no_file.yml")
config = Quke::Configuration.new
expect(described_class.new(config).chrome.args).to eq([])
expect(described_class.new(config).chrome.args).not_to include(start_with("--proxy-server"))
end
end

context "basic proxy details have been set in the .config.yml" do
it "returns an instance of Chrome::Options containing basic proxy settings" do
Quke::Configuration.file_location = data_path(".proxy_basic.yml")
config = Quke::Configuration.new
expect(described_class.new(config).chrome.args).to eq(
[
"--proxy-server=#{config.proxy.host}:#{config.proxy.port}"
]
expect(described_class.new(config).chrome.args).to include(
"--proxy-server=#{config.proxy.host}:#{config.proxy.port}"
)
end
end
Expand All @@ -30,11 +28,9 @@
it "returns an instance of Chrome::Options containing proxy settings including no-proxy details" do
Quke::Configuration.file_location = data_path(".proxy.yml")
config = Quke::Configuration.new
expect(described_class.new(config).chrome.args).to eq(
[
"--proxy-server=#{config.proxy.host}:#{config.proxy.port}",
"--proxy-bypass-list=127.0.0.1;192.168.0.1"
]
expect(described_class.new(config).chrome.args).to include(
"--proxy-server=#{config.proxy.host}:#{config.proxy.port}",
"--proxy-bypass-list=127.0.0.1;192.168.0.1"
)
end
end
Expand All @@ -43,19 +39,15 @@
it "returns an instance of Chrome::Options containing the specified user-agent" do
Quke::Configuration.file_location = data_path(".user_agent.yml")
config = Quke::Configuration.new
expect(described_class.new(config).chrome.args).to eq(
[
"--user-agent=#{config.user_agent}"
]
)
expect(described_class.new(config).chrome.args).to include("--user-agent=#{config.user_agent}")
end
end

context "headless mode has been set in the .config.yml" do
it "returns an instance of Chrome::Options set to run the browser in headless mode" do
Quke::Configuration.file_location = data_path(".headless.yml")
config = Quke::Configuration.new
expect(described_class.new(config).chrome.args).to eq(["--headless=new"])
expect(described_class.new(config).chrome.args).to include("--headless=new")
end
end

Expand Down Expand Up @@ -129,7 +121,7 @@
it "returns an instance of Firefox::Options set to run the browser in headless mode" do
Quke::Configuration.file_location = data_path(".headless.yml")
config = Quke::Configuration.new
expect(described_class.new(config).chrome.args).to eq(["--headless=new"])
expect(described_class.new(config).firefox.args).to include("--headless")
end
end

Expand Down
Loading