惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

美团技术团队
Microsoft Azure Blog
Microsoft Azure Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
B
Blog
Y
Y Combinator Blog
博客园_首页
有赞技术团队
有赞技术团队
博客园 - Franky
腾讯CDC
G
Google Developers Blog
Recent Announcements
Recent Announcements
博客园 - 【当耐特】
D
Docker
The GitHub Blog
The GitHub Blog
MyScale Blog
MyScale Blog
H
Help Net Security
Apple Machine Learning Research
Apple Machine Learning Research
A
About on SuperTechFans
D
DataBreaches.Net
T
The Blog of Author Tim Ferriss
V
V2EX
U
Unit 42
aimingoo的专栏
aimingoo的专栏
WordPress大学
WordPress大学

Giant Robots Smashing Into Other Giant Robots

Client success starts before kickoff 5 easy, actionable tips for software development in healthcare Announcing importmap-update: automated dependency updates for importmap-rails When to vibe code an app and when to hire someone Why leaders have to know about PMS 🩸 Is AI ruining my brain? Tech Leaders Meetup is coming to Edinburgh Designers already think in React Tech Leaders Meetups are back in London this autumn GPT and Claude go to heraldry school Inserting State Transitions in Postgres Don’t hire thoughtbot to write code AI makes creating software faster, but in regulated industries, judgment matters more Tech Leaders meetup in Amsterdam PMs Don't Need to Code, but They Do Need to Understand How healthcare tech teams innovate while balancing speed and security Can’t touch the DOM? Reach for :has() to style any element Buying Time, Choosing Words: Consulting Through Diplomatic Communication thoughtbot around the world, meet us at upcoming events Modeling State Transitions in Postgres Humid 1.0: React server-side rendering in Rails can be easy! A prototype is not a product. It's a conversation. New: The State of Software Delivery in Healthcare Sign in with Google for React Native What founders told us about working with AI tools for startups Join us: Building Secure Healthcare Systems Upcase has retired, but the learning continues The Bike Shed Ep 506: The Muppet Software Team Migrating to native stack navigation, with a surprise from iOS 26 Past and present thoughtbotters at LRUG this Monday
The Playwright debugging tool Rails devs aren't using
Justin Toniazzo · 2026-06-19 · via Giant Robots Smashing Into Other Giant Robots

Playwright ships with a black-box recorder that records every detail of every test, giving you a treasure trove of information for debugging flaky tests. Most Rails apps I’ve worked on don’t use it.

It’s called the Trace Viewer, and if you’re running Capybara with Playwright via playwright-ruby-client, turning it on takes a single block in rails_helper.rb.

What’s in a Playwright trace?

When Playwright records a trace, you get back a .zip you can replay in the Trace Viewer. The viewer is essentially a DVR for your test run. For every action Playwright took, every click, fill_in, and navigation, you get:

  • A before-and-after screenshot, so you can see what the page looked like at each moment
  • The DOM at that point in time, fully inspectable like in your devtools
  • The network requests in flight, with status codes, body, and timing
  • The JavaScript console output
  • A timeline of everything that happened

plawright trace viewer

Compare that to what you usually get when a test fails on CI: A screenshot, and maybe a stack trace pointing at expected to find "Saved". With a trace, you can scrub through the test like a video, and pinpoint exactly where things went wrong.

Enable Playwright traces in your Rails app

Drop this into your rails_helper.rb. It assumes you’re using playwright-ruby-client.

You may need to change type: :system to type: :feature if that’s how you set up your browser-based tests.

RSpec.configure do |config|
  config.before(:each, type: :system) do |example|
    driver = Capybara.current_session.driver
    next unless driver.respond_to?(:start_tracing)

    driver.start_tracing(
      screenshots: true,
      snapshots: true,
      sources: true,
      title: example.full_description
    )
    example.metadata[:playwright_tracing] = true
  end

  config.after(:each, type: :system) do |example|
    next unless example.metadata[:playwright_tracing]

    path = trace_path(example)
    Capybara.current_session.driver.stop_tracing(path:)
    if example.exception
      output = RSpec.configuration.output_stream
      output.puts("Playwright trace: #{path}")
    end
  rescue Playwright::Error => e
    raise unless e.message.start_with?("Must start tracing before stopping")
  end

  def trace_path(example)
    if example.exception
      Rails.root.join(
        "tmp",
        "playwright-traces",
        [
          example.full_description.parameterize[0, 150],
          "-#{Time.current.to_i}",
          ".zip"
        ].join
      )
    else
      File::NULL
    end
  end
end

This starts a trace before every feature spec and only saves it if the test failed. When that happens, you’ll see a line like this printed:

Playwright trace: /Users/jutonz/code/thoughtbot/testing/tmp/playwright-traces/it-works-1775576941.zip

A few things in the snippet might catch your eye (the before(:each) instead of around, the rescue, the conditional path). They’re like that for a reason. Copy as-is, or expand below for the rationale.

Why the snippet looks the way it does
  • before(:each) over around. Capybara only swaps from RackTest to Playwright after it sees the spec’s :js (or js: true) metadata. That swap happens after the around block, so a hook running there would still see the RackTest driver and respond_to?(:start_tracing) would always be false.
  • rescue Playwright::Error. If start_tracing failed for any reason, e.g. if a test modifies some low level driver config, stop_tracing will throw a “Must start tracing before stopping” error. Catching it won’t fail an otherwise passing test because of a tracing issue.
  • Conditional trace_path / File::NULL. Playwright requires you to write a trace somewhere once you’ve started one. Since we don’t care about traces for passing tests, we discard them by writing them to the /dev/null null device.

Download traces from CI for debugging

You’ll want to keep failed traces around for inspection. In GitHub Actions, add an upload step that runs even when the suite fails:

- name: Upload Playwright traces
  if: failure()
  uses: actions/upload-artifact@v4
  with:
    name: playwright-traces
    path: tmp/playwright-traces

Now you can download traces after a failed test.

Open a trace

Once you’ve downloaded a trace zip, open it with:

npx playwright show-trace path/to/trace.zip

That spins up the Trace Viewer in a local browser. The Playwright docs cover the UI in detail.

Gotchas

A couple of things worth knowing once traces are on.

Traces only show Playwright actions, not Capybara or RSpec.

A click_on("Save") call will appear as Click in the trace, since that’s the underlying Playwright action that’s performed. Usually it’s easy enough to map the Playwright action to the Capybara version, but it won’t be exactly the same.

The trace recorder is also capable of capturing Playwright-based assertions, but if you’re using Capybara, these won’t show up. Capybara does assertions itself, rather than delegating to the driver. This is because Selenium and other drivers don’t support built-in assertions.

There’s a small I/O cost.

Recording is mostly disk-bound. In my experience the cost is negligible, but if your CI is I/O-constrained, you might notice it as new flakiness. If that happens, you can store traces in a tmpfs or ramdisk by passing tracesDir to the driver:

Capybara::Playwright::Driver.new(
  app,
  # ... existing options ...
  tracesDir: ENV["CI"].present? ? "/mnt/ramdisk/playwright-traces" : nil,
)

CircleCI exposes /mnt/ramdisk by default. On other providers you can create a tmpfs directory manually.

Debug, don’t guess

Before traces, my approach to flaky CI was guess, re-run, and hope. Now I open the artifact, scrub through the test, and see the actual problem. The difference is night and day, and the setup is one block of code.

If you’re running Playwright through Capybara, you’ve already got everything you need. Turn it on.