Suppose you have this:
expect(page).to have_content("My Awesome Site")
And Capybara says that that content is not there and that is all it says. You might slap in a puts page.html
and try again. Instead, what if you could not do that and do this?
with_clues do
expect(page).to have_content("My Awesome Site")
end
And that would print out your HTML? Or your JavaScript console? Or whatever else? Neat, right?
gem install with_clues
Or, in Gemfile
:
gem "with_clues"
For Rails, you might want to do this:
gem "with_clues", group: :test
Then bundle install
Best thing to do is mix into your base test class.
If you are using Rails, probably something like this:
# test/test_helper.rb
require "with_clues"
class ActiveSupport::TestCase
include WithClues::Method
# ...
end
If you aren't using Rails, add the require
and include
wherever you configure your base test case (or just put it in each test individually).
You'll want to put this in your spec/spec_helper.rb
file:
require "with_clues"
RSpec.configure do |c|
c.include WithClues::Method
end
In general, you would not want to wrap all tests with with_clues
. This is a diagnostic tool to allow you to get more information on a test that is failing. As such, your workflow might be:
-
Notice a test failing that you cannot easily diagnose
-
Wrap the failing assertion in
with_clues
:with_clues do expect(page).to have_selector("div.foo.bar") end
-
Run the test again, and see the additional info.
-
Once you've made the test pass, remove
with_clues
There are three clues included:
-
Dumping HTML - when
page
exists, it will dump the contents ofpage.html
(for Selenium) orpage.content
(for Playwright) when the test fails -
Dumping Browser logs - for a browser-based test, it will dump anything that was
console.log
'ed. This should work with Selenium and Playwright -
Arbitrary context you pass in, for example when testing an Active Record
person = Person.where(name: "Pat") with_clues(person.inspect) do expect(person.valid?).to eq(true) end
If the test fails,
person.inspect
is included in the output
with_clues
is intended as a diagnostic tool you can develop and enhance over time. As your team writes more code or develops
more conventions, you can develop diagnostics as well.
To add one, create a class that implements dump(notifier, context:)
or dump(notifier, context:, page:)
or
dump(notifier, context:, page:, captured_logs)
:
notifier
is aWithClues::Notifier
that you should use to produce output via the following methods:notifier.notify
- output text, preceded with[ with_clues ]
(this is so you can tell output from your code vs fromwith_clues
)notifier.blank_line
- a blank line (no prefix)notifier.notify_raw
- output text without a prefix, useful for removing ambiguity about what is being output
context:
the context passed intowith_clues
(nil if it was omitted)page:
will be given the Selenium or Playwright page objectcaptured_logs:
for Playwright, this will be the browser console logs captured inside the block
For example, suppose you want to output information about an Active Record like so:
with_clues(person) do
# some test
end
If this test fails, you output the person's ID and any errors
.
Create this class, e.g. in spec/support/active_record_clues.rb
:
class ActiveRecordClues
def dump(notifier, context:)
if context.kind_of?(ActiveRecord::Base)
notifier.notify "#{context.class}: id: #{context.id}"
notifier.notify "#{context.class}: errors: #{context.errors.inspect}"
end
end
end
To use it, call WithClues::Method.use_custom_clue
, for example, in your spec_helper.rb
:
require "with_clues"
require_relative "support/active_record_clues"
RSpec.configure do |c|
c.include WithClues::Method
end
WithClues::Method.use_custom_clue ActiveRecordClues
You can use multiple clues by repeatedly calling use_custom_clue
Note that if your clue implements the three-arg version of dump
( dump(notifier, context:, page:)
), it will only be used when in
a context where Capybara's page
element in in play.
- Get set up with
bin/setup
- Run tests with
bin/ci