Skip to content
Draft
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
2 changes: 0 additions & 2 deletions docs/guide/advanced/composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,6 @@ Another way to share functionality between test helpers is to use inheritance.
Just like methods, any [aliases] defined in ancestors will also be available in the subclass.

```ruby
Capybara.get_test_helper_class(:table) # or require_relative './table_test_helper'

class UsersTestHelper < TableTestHelper
aliases(
el: '.users'
Expand Down
2 changes: 2 additions & 0 deletions docs/guide/essentials/injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ The following convention is applied when injecting test helpers:

Test helpers should be located in a `test_helpers` folder at the root of your project.

Test helpers are loaded lazily, both when injected with `use_test_helpers` and when referenced directly as constants (for example when subclassing another helper).

You may configure a different location by configuring `helpers_paths`:

```ruby
Expand Down
6 changes: 3 additions & 3 deletions examples/rails_app/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ gem 'turbolinks', '~> 5'
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
gem 'pry-byebug'
gem 'pry-rescue'
gem 'pry-stack_explorer'
gem 'pry-byebug', require: false
gem 'pry-rescue', require: false
gem 'pry-stack_explorer', require: false
gem 'rainbow'
end

Expand Down
2 changes: 2 additions & 0 deletions lib/capybara_test_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
require 'capybara_test_helpers/config'
require 'capybara_test_helpers/test_helper'
require 'capybara_test_helpers/dependency_injection'

CapybaraTestHelpers.sync_helpers_loader!
2 changes: 1 addition & 1 deletion lib/capybara_test_helpers/benchmark_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def on_test_helper_load
def benchmark_all
return if defined?(@benchmarked_all)

benchmark(instance_methods - superclass.instance_methods - [:lazy_for])
benchmark(instance_methods(false) - [:lazy_for])
@benchmarked_all = true
end

Expand Down
54 changes: 53 additions & 1 deletion lib/capybara_test_helpers/config.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# frozen_string_literal: true

require 'capybara/rspec'
require 'set'
require 'zeitwerk'

# Internal: Configuration for Provides the basic functionality to create simple test helpers.
module CapybaraTestHelpers
Expand Down Expand Up @@ -37,10 +39,60 @@ module CapybaraTestHelpers
# Public: Returns the current configuration for the test helpers.
def self.config
@config ||= OpenStruct.new(DEFAULTS)
yield @config if block_given?
if block_given?
yield @config
sync_helpers_loader!
end
@config
end


def self.helpers_loader
@helpers_loader ||= build_helpers_loader
end

def self.sync_helpers_loader!
loader = helpers_loader
desired_paths = configured_helpers_paths
current_paths = loader.dirs.to_a

return true if @helpers_loader_setup && current_paths == desired_paths

if @helpers_loader_setup
loader.unregister
@helpers_loader = build_helpers_loader
loader = @helpers_loader
end

desired_paths.each { |path| loader.push_dir(path) if Dir.exist?(path) }
loader.setup
@helpers_loader_setup = true
true
end

def self.initialize_test_helper_class!(klass)
return klass unless klass.is_a?(Class) && klass <= Capybara::TestHelper
return klass if klass.instance_variable_defined?(:@capybara_test_helpers_initialized)

klass.on_test_helper_load
klass.instance_variable_set(:@capybara_test_helpers_initialized, true)
klass
end

def self.build_helpers_loader
Zeitwerk::Loader.new.tap do |loader|
loader.inflector.inflect('test_helper' => 'TestHelper')
loader.enable_reloading
loader.on_load do |_cpath, value, _abspath|
initialize_test_helper_class!(value)
end
end
end

def self.configured_helpers_paths
config.helpers_paths.map { |path| File.expand_path(path) }.uniq
end

# Internal: Allows to define methods that are a part of the Capybara DSL, as
# well as RSpec matchers.
def self.define_helper_method(klass, method_name, wrap: false, assertion: false, target: 'current_context', return_self: assertion, inject_test_helper: true)
Expand Down
24 changes: 11 additions & 13 deletions lib/capybara_test_helpers/dependency_injection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,22 @@ def get_test_helper_class(name)
file_name = "#{ name }_test_helper"
ivar_name = "@#{ file_name }_test_helper_class"
instance_variable_get(ivar_name) || begin
require_test_helper(file_name)
test_helper_class = file_name.camelize.constantize
test_helper_class.on_test_helper_load
instance_variable_set(ivar_name, test_helper_class)
test_helper_class = resolve_test_helper_class(file_name)
instance_variable_set(ivar_name, CapybaraTestHelpers.initialize_test_helper_class!(test_helper_class))
end
end

# Internal: Requires a test helper file.
def require_test_helper(name)
CapybaraTestHelpers.config.helpers_paths.each do |path|
require Pathname.new(File.expand_path(path)).join("#{ name }.rb").to_s
return true # Don't check on the result, it could have been required earlier.
rescue LoadError
false
end
raise LoadError, "No '#{ name }.rb' file found in #{ CapybaraTestHelpers.config.helpers_paths.inspect }. "\

# Internal: Loads and resolves a test helper class using Zeitwerk.
def resolve_test_helper_class(file_name)
CapybaraTestHelpers.sync_helpers_loader!
file_name.camelize.constantize
rescue NameError
raise LoadError, "No '#{ file_name }.rb' file found in #{ CapybaraTestHelpers.config.helpers_paths.inspect }. "\
'Check for typos, or make sure the dirs in `CapybaraTestHelpers.config.helpers_paths` are in the load path.'
end


end

Capybara.extend(CapybaraTestHelpers::DependencyInjection)
4 changes: 4 additions & 0 deletions spec/capybara_test_helpers/inheritance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
visit '/form'
end

it 'autoloads a parent helper when subclassing by constant' do
expect(PersonFormTestHelper.superclass).to eq(FormPageTestHelper)
end

it 'inherits locator aliases from parent class' do
person_form.within {
title = person_form.title
Expand Down
7 changes: 7 additions & 0 deletions spec/capybara_test_helpers/wrap_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# frozen_string_literal: true

RSpec.feature 'Wrapping Test Helpers', test_helpers: [:html_page, :form_page] do

it 'autoloads helper constants used directly to wrap elements' do
visit_page(:html)
wrapped = HtmlPageTestHelper.new(page).wrap_element(find('p', text: 'Lorem ipsum dolor sit amet'))
expect(wrapped).to be_a(HtmlPageTestHelper)
end

it 'wraps elements as expected' do
visit_page(:html)
expect(html_page.first_paragraph).to be_a(HtmlPageTestHelper)
Expand Down
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
require 'bundler/setup'
require 'rspec/expectations'

require 'pry-byebug'
require 'pry-byebug' unless ENV['CI']

require 'capybara'
require 'capybara/rspec'
Expand Down
1 change: 0 additions & 1 deletion test_helpers/person_form_test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

Capybara.get_test_helper_class(:form_page)

class PersonFormTestHelper < FormPageTestHelper
aliases(
Expand Down
Loading