<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Yusuke Iwaki</title>
    <description>The latest articles on DEV Community by Yusuke Iwaki (@yusukeiwaki).</description>
    <link>https://dev.to/yusukeiwaki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F42030%2F0555c844-8774-4807-977d-738f68a65d9c.png</url>
      <title>DEV Community: Yusuke Iwaki</title>
      <link>https://dev.to/yusukeiwaki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yusukeiwaki"/>
    <language>en</language>
    <item>
      <title>Introducing pytest-style fixtures into Ruby for smarter browser testing</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Sun, 03 May 2026 16:22:00 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/introducing-pytest-style-fixtures-into-ruby-for-smarter-browser-testing-lbi</link>
      <guid>https://dev.to/yusukeiwaki/introducing-pytest-style-fixtures-into-ruby-for-smarter-browser-testing-lbi</guid>
      <description>&lt;h2&gt;
  
  
  Why I built another Ruby test runner inspired by Playwright Test
&lt;/h2&gt;

&lt;p&gt;Ruby already has great testing tools.&lt;/p&gt;

&lt;p&gt;If you are building Rails applications today, you probably use one of these combinations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;RSpec + Capybara&lt;/li&gt;
&lt;li&gt;Minitest + Capybara&lt;/li&gt;
&lt;li&gt;Rails system tests&lt;/li&gt;
&lt;li&gt;Maybe Selenium, Cuprite, Ferrum, or Playwright through Ruby bindings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools are mature, battle-tested, and widely used.&lt;/p&gt;

&lt;p&gt;So the natural question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Why create yet another test runner?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly the question the Playwright team asked themselves when they created Playwright Test.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/JjhY2aFBTTk"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;In the Playwright video that inspired this project, the speaker says they did not originally want to create another JavaScript testing framework. They tried existing frameworks such as Mocha and Jest, but those frameworks had historically been designed for unit testing. End-to-end testing had a different set of problems: cross-browser execution, parallelization, browser-side isolation, and a high degree of fixture flexibility.&lt;/p&gt;

&lt;p&gt;That explanation clicked with me.&lt;/p&gt;

&lt;p&gt;Because Ruby on Rails browser testing has a similar problem.&lt;/p&gt;

&lt;p&gt;RSpec and Minitest are excellent general-purpose Ruby test frameworks. Capybara is an excellent browser automation DSL. But the current mainstream Rails browser testing style was not designed after learning from Playwright Test. It still feels like a unit-test-era architecture with browser automation added on top.&lt;/p&gt;

&lt;p&gt;Playwright Test showed that browser testing deserves a test runner designed around browser testing itself.&lt;/p&gt;

&lt;p&gt;That is the idea behind &lt;strong&gt;Smartest&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vqs4s0f4hdpf7ajd1ox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8vqs4s0f4hdpf7ajd1ox.png" alt=" " width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repository:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/YusukeIwaki" rel="noopener noreferrer"&gt;
        YusukeIwaki
      &lt;/a&gt; / &lt;a href="https://github.com/YusukeIwaki/smartest" rel="noopener noreferrer"&gt;
        smartest
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Introduce Pytest-style fixtures for Ruby. A tiny test runner with keyword-argument fixture injection, dependencies, and cleanup.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Smartest&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a href="https://rubygems.org/gems/smartest" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/825805e7b2daeb8dfa323e448b87ff912cc99e1e790ebdc93961962b1d416579/68747470733a2f2f62616467652e667572792e696f2f72622f736d6172746573742e737667" alt="Gem Version"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Smartest is a Ruby test runner that brings pytest-style fixtures to Ruby
with explicit fixture dependencies, automatic teardown, and Playwright-friendly
browser testing.&lt;/p&gt;
&lt;p&gt;Tests request fixtures with Ruby keyword arguments. Fixtures define their own
dependencies the same way:&lt;/p&gt;
&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;class&lt;/span&gt; &lt;span class="pl-v"&gt;WebFixture&lt;/span&gt; &amp;lt; &lt;span class="pl-v"&gt;Smartest&lt;/span&gt;::&lt;span class="pl-v"&gt;Fixture&lt;/span&gt;
  &lt;span class="pl-en"&gt;fixture&lt;/span&gt; &lt;span class="pl-pds"&gt;:server&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt;
    &lt;span class="pl-s1"&gt;server&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-v"&gt;TestServer&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;start&lt;/span&gt;
    &lt;span class="pl-en"&gt;on_teardown&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-s1"&gt;server&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;stop&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt;
    &lt;span class="pl-s1"&gt;server&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;

  &lt;span class="pl-en"&gt;fixture&lt;/span&gt; &lt;span class="pl-pds"&gt;:client&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt; |&lt;span class="pl-s1"&gt;server&lt;/span&gt;:|
    &lt;span class="pl-v"&gt;Client&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;new&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-pds"&gt;base_url&lt;/span&gt;: &lt;span class="pl-s1"&gt;server&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;url&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
  &lt;span class="pl-k"&gt;end&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;

&lt;span class="pl-en"&gt;around_suite&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt; |&lt;span class="pl-s1"&gt;suite&lt;/span&gt;|
  &lt;span class="pl-en"&gt;use_fixture&lt;/span&gt; &lt;span class="pl-v"&gt;WebFixture&lt;/span&gt;
  &lt;span class="pl-s1"&gt;suite&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;run&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;

&lt;span class="pl-en"&gt;test&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"GET /health"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt; |&lt;span class="pl-s1"&gt;client&lt;/span&gt;:|
  &lt;span class="pl-s1"&gt;response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;client&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;get&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;"/health"&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;

  &lt;span class="pl-en"&gt;expect&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;response&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;status&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;to&lt;/span&gt; &lt;span class="pl-en"&gt;eq&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-c1"&gt;200&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;p&gt;Smartest is designed around three ideas:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Tests should be readable at the top level.&lt;/li&gt;
&lt;li&gt;Fixture dependencies should be explicit.&lt;/li&gt;
&lt;li&gt;Teardown should be written only when it is needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Smartest?&lt;/h2&gt;

&lt;/div&gt;
&lt;p&gt;Ruby…&lt;/p&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/YusukeIwaki/smartest" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;Documentation: &lt;a href="https://smartest-rb.vercel.app/" rel="noopener noreferrer"&gt;https://smartest-rb.vercel.app/&lt;/a&gt;&lt;br&gt;
Browser testing guide: &lt;a href="https://smartest-rb.vercel.app/docs/playwright-browser-tests" rel="noopener noreferrer"&gt;https://smartest-rb.vercel.app/docs/playwright-browser-tests&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  The inspiration: Playwright Test was not just another runner
&lt;/h2&gt;

&lt;p&gt;Playwright Test is not only a wrapper around browser automation.&lt;/p&gt;

&lt;p&gt;It is a test runner designed for the real constraints of end-to-end testing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cross-browser support out of the box&lt;/li&gt;
&lt;li&gt;parallel execution&lt;/li&gt;
&lt;li&gt;browser-context isolation&lt;/li&gt;
&lt;li&gt;web-first assertions&lt;/li&gt;
&lt;li&gt;traceability and debugging support&lt;/li&gt;
&lt;li&gt;fixtures inspired by pytest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The official Playwright repository describes Playwright Test as a full-featured test runner for end-to-end testing, with Chromium, Firefox, and WebKit support, full browser isolation, auto-waiting, and web-first assertions.&lt;/p&gt;

&lt;p&gt;The important point is not simply “Playwright controls browsers.”&lt;/p&gt;

&lt;p&gt;The important point is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Playwright Test changed the test runner because the target domain changed.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Browser tests are not just unit tests with a browser.&lt;/p&gt;

&lt;p&gt;They need their own setup model.&lt;/p&gt;


&lt;h2&gt;
  
  
  The fixture idea I wanted in Ruby
&lt;/h2&gt;

&lt;p&gt;One of the best ideas in pytest is fixture injection.&lt;/p&gt;

&lt;p&gt;In pytest, a test can request what it needs by naming it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_get_me&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/me&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test body does not say how to create &lt;code&gt;logged_in_client&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The fixture system knows how to resolve it.&lt;/p&gt;

&lt;p&gt;Fixtures can depend on other fixtures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;logged_in_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is extremely useful for browser tests, because browser tests often have layered setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;app server&lt;/li&gt;
&lt;li&gt;browser runtime&lt;/li&gt;
&lt;li&gt;browser process&lt;/li&gt;
&lt;li&gt;browser context&lt;/li&gt;
&lt;li&gt;page&lt;/li&gt;
&lt;li&gt;user&lt;/li&gt;
&lt;li&gt;login state&lt;/li&gt;
&lt;li&gt;seeded data&lt;/li&gt;
&lt;li&gt;teardown&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RSpec and Minitest can express all of this, of course.&lt;/p&gt;

&lt;p&gt;But the dependency graph is often implicit.&lt;/p&gt;

&lt;p&gt;With RSpec, we may write something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:server&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;start_server&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;base_url: &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;create_user&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:logged_in_client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;client&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s2"&gt;"GET /me"&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works.&lt;/p&gt;

&lt;p&gt;But the fixture dependency graph is hidden inside method calls.&lt;/p&gt;

&lt;p&gt;I wanted this instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GET /me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And fixture definitions like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AppFixture&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Smartest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Fixture&lt;/span&gt;
  &lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="ss"&gt;:client&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
    &lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;base_url: &lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="ss"&gt;:logged_in_client&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important design decision is the keyword argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GET /me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the test’s dependencies explicit.&lt;/p&gt;

&lt;p&gt;And:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="ss"&gt;:logged_in_client&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes the fixture’s dependencies explicit.&lt;/p&gt;

&lt;p&gt;No positional argument order.&lt;br&gt;
No hidden &lt;code&gt;let&lt;/code&gt; dependency.&lt;br&gt;
No separate &lt;code&gt;with: [:server]&lt;/code&gt; declaration.&lt;/p&gt;

&lt;p&gt;The dependency declaration and usage live in one place: the Ruby method signature.&lt;/p&gt;


&lt;h2&gt;
  
  
  Smartest: a small keyword-fixture-first Ruby test runner
&lt;/h2&gt;

&lt;p&gt;Smartest is a small Ruby test runner with a keyword-fixture-first design.&lt;/p&gt;

&lt;p&gt;The goal is not to replace all Ruby testing immediately.&lt;/p&gt;

&lt;p&gt;The goal is to explore what Ruby testing could feel like if we applied the lessons of pytest fixtures and Playwright Test to Ruby browser testing.&lt;/p&gt;

&lt;p&gt;A normal Smartest test looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"factorial"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A fixture-driven test looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"GET /me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logged_in_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"/me"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Smartest is designed around three ideas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Tests should be readable at the top level.&lt;/li&gt;
&lt;li&gt;Fixture dependencies should be explicit.&lt;/li&gt;
&lt;li&gt;Teardown should be written only when it is needed.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That third point is especially important for browser tests.&lt;/p&gt;

&lt;p&gt;Some fixtures are just values.&lt;br&gt;
Some fixtures need teardown.&lt;br&gt;
Some fixtures should live for one test.&lt;br&gt;
Some fixtures should live for the whole suite.&lt;/p&gt;

&lt;p&gt;Smartest supports both regular fixtures and suite fixtures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PlaywrightFixture&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Smartest&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Fixture&lt;/span&gt;
  &lt;span class="n"&gt;suite_fixture&lt;/span&gt; &lt;span class="ss"&gt;:playwright&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;
    &lt;span class="n"&gt;on_teardown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;runtime&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;suite_fixture&lt;/span&gt; &lt;span class="ss"&gt;:browser&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;
    &lt;span class="n"&gt;on_teardown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;fixture&lt;/span&gt; &lt;span class="ss"&gt;:page&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_context&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;

    &lt;span class="n"&gt;on_teardown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;page&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This expresses the browser lifecycle directly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start Playwright once&lt;/li&gt;
&lt;li&gt;launch a browser once&lt;/li&gt;
&lt;li&gt;create a fresh browser context and page per test&lt;/li&gt;
&lt;li&gt;close the context after each test&lt;/li&gt;
&lt;li&gt;close the browser after the suite&lt;/li&gt;
&lt;li&gt;stop Playwright after the suite&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the kind of lifecycle that is awkward when browser testing is treated as a thin layer on top of a unit testing framework.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why this matters for Rails browser testing
&lt;/h2&gt;

&lt;p&gt;Rails browser testing has traditionally been shaped by RSpec/Minitest + Capybara.&lt;/p&gt;

&lt;p&gt;That combination is powerful, but it also reflects an older mental model:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test framework first&lt;/li&gt;
&lt;li&gt;browser automation second&lt;/li&gt;
&lt;li&gt;browser lifecycle hidden in configuration&lt;/li&gt;
&lt;li&gt;setup split across &lt;code&gt;before&lt;/code&gt;, &lt;code&gt;let&lt;/code&gt;, support files, shared contexts, and driver config&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Playwright Test takes a different position:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;browser testing is the domain&lt;/li&gt;
&lt;li&gt;isolation is part of the runner&lt;/li&gt;
&lt;li&gt;fixture composition is part of the runner&lt;/li&gt;
&lt;li&gt;cross-browser execution is part of the runner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Smartest is an attempt to bring that kind of thinking to Ruby.&lt;/p&gt;

&lt;p&gt;Not by copying Playwright Test’s TypeScript API exactly.&lt;/p&gt;

&lt;p&gt;But by asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What would a Ruby-native pytest-style fixture system for browser testing look like?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The current answer is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"finds the smartest gem on RubyGems"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://rubygems.org/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"input[name='query']"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"smartest"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Enter"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"a[href='/gems/smartest']"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://rubygems.org/gems/smartest"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"h1"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"smartest"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;page:&lt;/code&gt; fixture is the key.&lt;/p&gt;

&lt;p&gt;It looks small.&lt;/p&gt;

&lt;p&gt;But behind it, Smartest can manage the Playwright runtime, browser process, browser context, page creation, and teardown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe17b8ac62g1kiaf2nzt6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe17b8ac62g1kiaf2nzt6.png" alt=" " width="800" height="381"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting started with browser tests
&lt;/h2&gt;

&lt;p&gt;Smartest includes a browser-test scaffold.&lt;/p&gt;

&lt;p&gt;Add Smartest to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"smartest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;install
&lt;/span&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;smartest &lt;span class="nt"&gt;--init-browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The browser scaffold sets up the files needed to run Playwright browser tests from Ruby.&lt;/p&gt;

&lt;p&gt;It creates files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;smartest/test_helper.rb
smartest/fixtures/playwright_fixture.rb
smartest/matchers/playwright_matcher.rb
smartest/example_browser_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also installs the browser testing dependencies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;playwright-ruby-client&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the Playwright Node.js package&lt;/li&gt;
&lt;li&gt;browser binaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then run the generated example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;smartest smartest/example_browser_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also choose a browser with environment variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BROWSER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;firefox bundle &lt;span class="nb"&gt;exec &lt;/span&gt;smartest smartest/example_browser_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or watch the browser while the test runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;HEADLESS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false &lt;/span&gt;&lt;span class="nv"&gt;SLOW_MO&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;250 bundle &lt;span class="nb"&gt;exec &lt;/span&gt;smartest smartest/example_browser_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated fixture uses this lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;suite_fixture :playwright
  -&amp;gt; starts Playwright runtime once

suite_fixture :browser
  -&amp;gt; launches one browser process for the suite

fixture :page
  -&amp;gt; creates a fresh browser context and page per test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentionally close to the Playwright Test mental model.&lt;/p&gt;




&lt;h2&gt;
  
  
  AI changed the feasibility of this project
&lt;/h2&gt;

&lt;p&gt;There is another reason I built this now.&lt;/p&gt;

&lt;p&gt;A few years ago, building a new test runner would have felt expensive.&lt;/p&gt;

&lt;p&gt;You would need to design the DSL, implement the runner, handle fixture resolution, write CLI behavior, create matchers, generate scaffolding, document the behavior, and iterate for a long time.&lt;/p&gt;

&lt;p&gt;But with OpenAI Codex, the first working version was surprisingly quick to build.&lt;/p&gt;

&lt;p&gt;That changes the economics of experimentation.&lt;/p&gt;

&lt;p&gt;I do not mean that AI magically makes design decisions for us.&lt;/p&gt;

&lt;p&gt;The important design still comes from human taste:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should fixtures be positional or keyword-based?&lt;/li&gt;
&lt;li&gt;Should fixture dependencies be declared separately or via block parameters?&lt;/li&gt;
&lt;li&gt;Should teardown be expressed with &lt;code&gt;yield&lt;/code&gt;, &lt;code&gt;ensure&lt;/code&gt;, or &lt;code&gt;on_teardown&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;Should browser support be built in or generated?&lt;/li&gt;
&lt;li&gt;Should this coexist with Minitest and RSpec?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But once the design direction is clear, AI makes it much easier to produce a working prototype and iterate.&lt;/p&gt;

&lt;p&gt;In this case, the ideal implementation became possible much faster than I expected.&lt;/p&gt;

&lt;p&gt;That matters for open source.&lt;/p&gt;

&lt;p&gt;Because many useful developer tools do not start as huge polished projects.&lt;/p&gt;

&lt;p&gt;They start as a small experiment with a strong point of view.&lt;/p&gt;




&lt;h2&gt;
  
  
  This is not “RSpec is bad”
&lt;/h2&gt;

&lt;p&gt;I want to be clear.&lt;/p&gt;

&lt;p&gt;RSpec is not bad.&lt;br&gt;
Minitest is not bad.&lt;br&gt;
Capybara is not bad.&lt;/p&gt;

&lt;p&gt;They are important parts of the Ruby ecosystem.&lt;/p&gt;

&lt;p&gt;But Playwright Test showed something valuable:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Sometimes a new testing domain deserves a new test runner.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Modern browser testing is one of those domains.&lt;/p&gt;

&lt;p&gt;If JavaScript testing needed Playwright Test even though Mocha and Jest already existed, then Ruby can also explore a browser-testing-oriented runner even though RSpec and Minitest already exist.&lt;/p&gt;

&lt;p&gt;The question is not:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does Ruby already have testing frameworks?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Of course it does.&lt;/p&gt;

&lt;p&gt;The better question is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Does Ruby have a test runner designed around pytest-style fixture composition and Playwright-style browser lifecycle management?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the space Smartest explores.&lt;/p&gt;


&lt;h2&gt;
  
  
  Current status
&lt;/h2&gt;

&lt;p&gt;Smartest is still young.&lt;/p&gt;

&lt;p&gt;It is best understood as a design-stage test runner and a working experiment.&lt;/p&gt;

&lt;p&gt;The current focus is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;top-level &lt;code&gt;test&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;keyword-argument fixture injection&lt;/li&gt;
&lt;li&gt;fixture dependencies through keyword arguments&lt;/li&gt;
&lt;li&gt;fixture teardown&lt;/li&gt;
&lt;li&gt;suite fixtures&lt;/li&gt;
&lt;li&gt;around-suite and around-test hooks&lt;/li&gt;
&lt;li&gt;browser scaffold generation&lt;/li&gt;
&lt;li&gt;Playwright fixture and matcher examples&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is already enough to write Playwright-style browser tests in Ruby.&lt;/p&gt;

&lt;p&gt;But it is not yet a mature replacement for RSpec or Minitest.&lt;/p&gt;

&lt;p&gt;That is fine.&lt;/p&gt;

&lt;p&gt;The goal right now is to validate the idea:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;pytest-style fixtures make Ruby browser tests smarter.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Let's Try it!
&lt;/h2&gt;

&lt;p&gt;Install:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;gem&lt;/span&gt; &lt;span class="s2"&gt;"smartest"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize browser tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;smartest &lt;span class="nt"&gt;--init-browser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;bundle &lt;span class="nb"&gt;exec &lt;/span&gt;smartest smartest/example_browser_test.rb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"my browser test"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="ss"&gt;:|&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://example.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"h1"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="n"&gt;have_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Example Domain"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Closing thought
&lt;/h2&gt;

&lt;p&gt;Playwright Test was created because existing JavaScript test frameworks were not built for the unique challenges of end-to-end browser testing.&lt;/p&gt;

&lt;p&gt;That lesson should not stay only in the JavaScript world.&lt;/p&gt;

&lt;p&gt;Ruby browser testing can also learn from it.&lt;/p&gt;

&lt;p&gt;Smartest is my attempt to bring pytest-style fixtures and Playwright-style browser testing ergonomics into Ruby.&lt;/p&gt;

&lt;p&gt;Smarter browser testing starts with smarter setup.&lt;/p&gt;

</description>
      <category>rspec</category>
      <category>playwright</category>
      <category>ruby</category>
    </item>
    <item>
      <title>Ruby on Rails System testing (E2E testing) using Natural Language with OpenAI API</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Fri, 20 Dec 2024 20:04:56 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/ruby-on-rails-system-testing-e2e-testing-using-natural-language-with-openai-5d24</link>
      <guid>https://dev.to/yusukeiwaki/ruby-on-rails-system-testing-e2e-testing-using-natural-language-with-openai-5d24</guid>
      <description>&lt;h1&gt;
  
  
  Background
&lt;/h1&gt;

&lt;p&gt;End-to-end (E2E) testing can be challenging to maintain.&lt;/p&gt;

&lt;p&gt;For instance, even a simple login screen can have various variations. As a service evolves, there often arises a need to upgrade a straightforward login screen to a more advanced one with enhanced functionality.&lt;/p&gt;

&lt;p&gt;As shown in the figure below, what appears to humans as the same "screen for entering ID and password" can be vastly different for machines. It is not easy for a system to identify both interfaces as login screens and test them with the same script.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58agxt25gglh73epae2u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F58agxt25gglh73epae2u.png" alt="Image description" width="800" height="424"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is the Problem?
&lt;/h2&gt;

&lt;p&gt;E2E testing is not a one-and-done task; it needs to adapt to changes in the user interface (UI). Otherwise, the cost of updating the tests to keep up with UI changes can outweigh the benefits of ensuring the functionality specifications through testing.&lt;/p&gt;

&lt;p&gt;In current common practices for E2E testing, UI elements such as buttons and text boxes are selected using CSS selectors or ARIA (Accessibility). These elements are then interacted with, such as clicking. However, even for the same login screen, relying solely on mechanical selection via CSS selectors makes it difficult to accommodate UI changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3x3t7yuevqi55x3fa03.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft3x3t7yuevqi55x3fa03.png" alt="Image description" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Additionally, when humans conduct tests, they attempt to recover from operational errors along the way. On the other hand, mechanical testing lacks such adaptability. For instance, if login information is mistakenly omitted and a submission results in an error, the automated test engine may still try to proceed with operations as if the login were successful. This is a common cause of what is known as a "flaky test."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse8esqpqhcctenmhzcay.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fse8esqpqhcctenmhzcay.png" alt="Image description" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Writing Ruby on Rails System Tests in Natural Language!
&lt;/h1&gt;

&lt;p&gt;With the remarkable advancements in generative AI, it is now conceivable to devise a method that allows tests to be written in natural language and executed directly. Inspired by this idea, I have developed a prototype and published it as a Rubygem.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/YusukeIwaki/charai" rel="noopener noreferrer"&gt;https://github.com/YusukeIwaki/charai&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;current_driver&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:charai&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;javascript_driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:charai&lt;/span&gt;

  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;additional_instruction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;MARKDOWN&lt;/span&gt;&lt;span class="sh"&gt;
  * Our website consists of 2-pain layout. The core function of this web service is helping users to find a good job offer.
  * On the left-pain, we can filter job offers by text search queries and conditions by checkboxes. On the center-pain, job offers are shown. It contains 30 items, and the second item can be a sponsored advertisement item.
  * The center-pain is scrollable while the left-pain is not. So please keep it mind that we have to put our mouse cursor to the center-pain for scrolling.
&lt;/span&gt;&lt;span class="no"&gt;  MARKDOWN&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'should work'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class="no"&gt;MARKDOWN&lt;/span&gt;&lt;span class="sh"&gt;
  * Browse to the job offer list view. Then input "Ruby on Rails".
  * After inputing the search query, submit it and check if the job offers shown in the center-pain is really related to Ruby on Rails.
  * If most of the search result is irrelevant to Ruby on Rails, please mark this testcase as "Assertion failure".
&lt;/span&gt;&lt;span class="no"&gt;  MARKDOWN&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Directions
&lt;/h2&gt;

&lt;p&gt;Traditionally, natural language commands are converted into CSS selectors for execution. While this approach can adapt UI changes, it does not resolve the issue of recovering from operational errors.&lt;/p&gt;

&lt;p&gt;To overcome this limitation, this library employs a unique approach: the AI autonomously observes the screen and generates Ruby code for performing the required actions. The generated Ruby code is executed, and the resulting screenshots are feedback into the AI for analysis. Based on this analysis, the next steps are determined and executed. This process essentially models how humans perform manual testing.&lt;/p&gt;

&lt;p&gt;By adopting this method, even if an click opens an unintended page, the AI can analyze the screenshot of the unexpected page, issue instructions to return to the previous page, and continue testing from there. This approach closely mimics the way humans recover from errors during testing, ensuring robustness and adaptability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F932k28uqmxooeaux2m4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F932k28uqmxooeaux2m4g.png" alt="Image description" width="800" height="628"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to implement it?
&lt;/h2&gt;

&lt;p&gt;To implement this approach in Ruby on Rails, creating a custom Capybara driver is the simplest method.&lt;/p&gt;

&lt;p&gt;There is plenty of information available via a quick Google search on how to use a Capybara driver. However, guidance on creating a Capybara driver is scarce. You would need to study the Capybara source code or refer to the implementation of existing libraries like Cuprite, and then replicate their techniques to build your own driver.&lt;/p&gt;

&lt;p&gt;Traditional Capybara drivers offer a wide range of methods under the Capybara DSL, such as click, find, and set. However, the driver we aim to create simplifies this dramatically by providing only three methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;visit&lt;/code&gt; Method: This is used to navigate to the initial web page.&lt;/li&gt;
&lt;li&gt;Precondition Method: This method communicates the preconditions for the test.&lt;/li&gt;
&lt;li&gt;Test Content Method: This method conveys the actual test instructions to be executed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By limiting the functionality to these three essential methods, this new driver focuses on allowing AI-driven natural language instructions to handle dynamic testing processes. This approach aligns closely with the goal of creating adaptive and resilient E2E tests.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwg2gi1tyfthwymvwf5vx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwg2gi1tyfthwymvwf5vx.png" alt="Image description" width="800" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More detailed instruction to implement the driver is introduced in this movie and deck (Sorry in Japanese!!)&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/yrVIgFgjQ-Q"&gt;
&lt;/iframe&gt;
&lt;br&gt;
&lt;iframe class="speakerdeck-iframe ltag_speakerdeck" src="https://speakerdeck.com/player/23b11c66d8924b49a64f0748552a9f4f"&gt;
&lt;/iframe&gt;
&lt;/p&gt;
&lt;h2&gt;
  
  
  Does it really work??
&lt;/h2&gt;

&lt;p&gt;At this moment, it works... to some extent. The current accuracy is still low, and it is not yet production-ready. However, this approach holds the potential to significantly transform the development experience of writing and running system tests in Rails.&lt;/p&gt;

&lt;p&gt;The prototype showcases how leveraging AI in this manner could revolutionize E2E testing by making it more adaptable and human-like. While there is much room for improvement, the foundation is promising, and further refinements could lead to practical, impactful solutions in the future.&lt;/p&gt;

&lt;p&gt;So please try it, and give me your feedback! :)&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/YusukeIwaki" rel="noopener noreferrer"&gt;
        YusukeIwaki
      &lt;/a&gt; / &lt;a href="https://github.com/YusukeIwaki/charai" rel="noopener noreferrer"&gt;
        charai
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Charai(Chat + Ruby + AI) driver for Capybara. Prototype impl for Kaigi on Rails 2024 presentation.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;p&gt;&lt;a href="https://badge.fury.io/rb/charai" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/88edc3fb1ad32d4371fff30ff28be5b9e29d4bd80e575b4d6e7cf26ace4d8674/68747470733a2f2f62616467652e667572792e696f2f72622f6368617261692e737667" alt="Gem Version"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Charai&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;Chat + Ruby + AI = Charai&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Setup&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Add &lt;code&gt;gem 'charai'&lt;/code&gt; into your project's Gemfile, and then &lt;code&gt;bundle install&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Also, this gem requires &lt;strong&gt;Firefox Developer Edition&lt;/strong&gt; to be installed on the location below:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;/Applications/Firefox Developer Edition.app (macOS)&lt;/li&gt;
&lt;li&gt;/usr/bin/firefox-devedition (Linux)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Configuration&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Configure your Capybara driver like below.&lt;/p&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;Capybara.register_driver :charai do |app|
  Charai::Driver.new(app, openai_configuration: config)
end

Capybara.register_driver :charai_headless do |app|
  Charai::Driver.new(app, openai_configuration: config, headless: true)
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Please note that this driver required OpenAI service.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;OpenAI&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;config = Charai::OpenaiConfiguration.new(
  model: 'gpt-4o',
  api_key: 'sk-xxxxxxxxxxx'
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h3 class="heading-element"&gt;Azure OpenAI (Recommended)&lt;/h3&gt;

&lt;/div&gt;

&lt;div class="snippet-clipboard-content notranslate position-relative overflow-auto"&gt;&lt;pre class="notranslate"&gt;&lt;code&gt;config = Charai::AzureOpenaiConfiguration.new(
  endpoint_url: 'https://YOUR-APP.openai.azure.com/openai/deployments/gpt-4o/chat/completions?api-version=2024-05-01-preview',
  api_key: 'aabbcc00112233445566'
)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Usage&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Since this driver works with the OpenAI service, we can easily describe E2E test like below :)&lt;/p&gt;

&lt;div class="highlight highlight-source-ruby notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;before&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt;
  &lt;span class="pl-v"&gt;Capybara&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;current_driver&lt;/span&gt;    &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-pds"&gt;:charai&lt;/span&gt;
  &lt;span class="pl-v"&gt;Capybara&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;javascript_driver&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-pds"&gt;:charai&lt;/span&gt;
  &lt;span class="pl-en"&gt;page&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;driver&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;additional_instruction&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&amp;lt;&amp;lt;~MARKDOWN&lt;/span&gt;
&lt;span class="pl-s"&gt;  * このページは、3ペイン構造です。ユーザが仕事を探すためのページです。&lt;/span&gt;
&lt;span class="pl-s"&gt;  * 左ペインには仕事の絞り込みができるフィルター、中央ペインが仕事（求人）の一覧で、30件ずつ表示されます。&lt;/span&gt;
&lt;span class="pl-s"&gt;  * 左ペインにマウスを置いてスクロールしても、中央ペインはスクロールされません。一覧をスクロールしたいときには、中央ペインの座標を確認し、その中央にマウスを置いてスクロールしてください。&lt;/span&gt;
&lt;span class="pl-s"&gt;  * 右ペインは、広告エリアです。検索条件に応じた広告が表示されます。&lt;/span&gt;
&lt;span class="pl-s"&gt;  MARKDOWN&lt;/span&gt;
&lt;span class="pl-k"&gt;end&lt;/span&gt;

&lt;span class="pl-en"&gt;it&lt;/span&gt; &lt;span class="pl-s"&gt;'should work'&lt;/span&gt; &lt;span class="pl-k"&gt;do&lt;/span&gt;
  &lt;span class="pl-en"&gt;page&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;driver&lt;/span&gt; &amp;lt;&amp;lt; &lt;span class="pl-s"&gt;&amp;lt;&amp;lt;~MARKDOWN&lt;/span&gt;
&lt;span class="pl-s"&gt;  *&lt;/span&gt;&lt;/pre&gt;…
&lt;/div&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/YusukeIwaki/charai" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;



</description>
    </item>
    <item>
      <title>Backport `Data.define` for Ruby 2.7, 3.0, 3.1</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Wed, 08 Mar 2023 04:17:00 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/backport-datadefine-for-ruby-27-30-31-3jc6</link>
      <guid>https://dev.to/yusukeiwaki/backport-datadefine-for-ruby-27-30-31-3jc6</guid>
      <description>&lt;p&gt;&lt;code&gt;Data.define&lt;/code&gt; is introduced by Zverok in Ruby 3.2.&lt;/p&gt;


&lt;div class="ltag__link"&gt;
  &lt;a href="/baweaver" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--tNigK_I7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://res.cloudinary.com/practicaldev/image/fetch/s--rrim98LT--/c_fill%2Cf_auto%2Cfl_progressive%2Ch_150%2Cq_auto%2Cw_150/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/129384/bc4c54aa-bacc-4804-8de6-edc3ff7a173b.png" alt="baweaver"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/baweaver/new-in-ruby-32-datadefine-2819" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;New in Ruby 3.2 - Data.define&lt;/h2&gt;
      &lt;h3&gt;Brandon Weaver ・ Oct 6 '22 ・ 2 min read&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;This is really amazing and desired by most Rubyists. However, regardless to say, the function is not available in Ruby 2.7-3.1 applications. Some users (including me), at this moment, still uses older versions of Ruby with struggling to upgrade large Rails applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  data_class_factory
&lt;/h2&gt;

&lt;p&gt;I developed a backport of &lt;code&gt;Data.define&lt;/code&gt; for Ruby 2.7, 3.0, 3.1.&lt;br&gt;
&lt;a href="https://rubygems.org/gems/data_class_factory"&gt;https://rubygems.org/gems/data_class_factory&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'data_class_factory'&lt;/span&gt;

&lt;span class="no"&gt;Point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;define&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;norm&lt;/span&gt;
    &lt;span class="no"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;p1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;x: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;y: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;p2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Point&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;x: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;y: &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;p1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;p2&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;span class="n"&gt;p1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;norm&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It covers most of &lt;a href="https://github.com/ruby/ruby/blob/v3_2_0/test/ruby/test_data.rb"&gt;the spec&lt;/a&gt; of original &lt;code&gt;Data&lt;/code&gt; class introduced in Ruby 3.2.&lt;br&gt;
&lt;a href="https://github.com/YusukeIwaki/data_class_factory/blob/0.1.0/test/data_test.rb"&gt;https://github.com/YusukeIwaki/data_class_factory/blob/0.1.0/test/data_test.rb&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback is really welcome :)
&lt;/h2&gt;

&lt;p&gt;I personally use this library on my Rails apps. So try this lib for your app, and let me know issues and feel free to feedback.&lt;br&gt;
&lt;a href="https://github.com/YusukeIwaki/data_class_factory/issues"&gt;https://github.com/YusukeIwaki/data_class_factory/issues&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ruby</category>
    </item>
    <item>
      <title>Dart + GitHub Actions: Publish your command-line tools for Windows/macOS/Linux into GitHub Releases</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Sat, 02 Apr 2022 06:29:10 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/dart-github-actions-publish-your-command-line-tools-for-windowsmacoslinux-into-github-releases-4hmn</link>
      <guid>https://dev.to/yusukeiwaki/dart-github-actions-publish-your-command-line-tools-for-windowsmacoslinux-into-github-releases-4hmn</guid>
      <description>&lt;h2&gt;
  
  
  Motivation
&lt;/h2&gt;

&lt;p&gt;Dart is very suitable for building quick-and-dirty CLI tools and share them among your collegue, since it produces a single binary with &lt;code&gt;dart compile exe&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It would be useful if organization-internal tools are published in GitHub Releases like this:&lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--LmT1LQx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0n5a3y26wmp8b7sblncs.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--LmT1LQx9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0n5a3y26wmp8b7sblncs.png" alt="image" width="880" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Unfortunately, &lt;a href="https://github.com/dart-lang/sdk/issues/28617"&gt;Dart doesn't support cross-compile&lt;/a&gt; at this moment and &lt;strong&gt;we have to compile the source code on each platform (Windows, macOS, Linux)&lt;/strong&gt; before publishing the binaries, while Golang supports cross-compile and &lt;a href="https://goreleaser.com/"&gt;GoReleaser&lt;/a&gt; already made it easy to publish the binaries for each platform.&lt;/p&gt;

&lt;p&gt;GitHub Actions provides Windows/macOS/Linux build environments for free and it would be really useful for compiling and publishing the CLI tools composed with Dart.&lt;/p&gt;

&lt;p&gt;This article shows how to configure the GitHub Actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Actions for build
&lt;/h2&gt;

&lt;p&gt;As many articles already says, only 4 step are required for compiling the Dart code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart-lang/setup-dart@v1&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart pub get&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart compile exe bin/mycli.dart -o mycli&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One point to consider is that the output binary should be named as mycli &lt;strong&gt;.exe&lt;/strong&gt; on Windows and mycli (without extension) on macOS/Linux.&lt;br&gt;
We can solve the naming by defining the matrix like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart compile exe&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
            &lt;span class="na"&gt;binary-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mycli_linux_amd64&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
            &lt;span class="na"&gt;binary-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mycli_macos_amd64&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows-latest&lt;/span&gt;
            &lt;span class="na"&gt;binary-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mycli_windows.exe&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.runs-on }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart-lang/setup-dart@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart pub get&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mkdir ${{ matrix.runs-on }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart compile exe bin/mycli.dart -o ${{ matrix.runs-on }}/${{ matrix.binary-name }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Actions for release
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.github.com/en/rest/reference/releases"&gt;https://docs.github.com/en/rest/reference/releases&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;According to this reference, GitHub Release requires 2 steps for publishing some binaries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a release resource

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST https://api.github.com/repos/{owner}/{repo}/releases&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;Upload the binaries into the release

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;POST https://uploads.github.com/repos/{owner}/{repo}/releases/{release_id}/assets&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Use action-gh-release for simple uploading
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/softprops/action-gh-release"&gt;action-gh-release&lt;/a&gt; really makes it easy to do these steps.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;softprops/action-gh-release@v1&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;out/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this simple definition, GitHub Actions will upload all files in &lt;code&gt;out/&lt;/code&gt; into a new draft release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;out/mycli_linux&lt;/span&gt;
      &lt;span class="s"&gt;out/mycli_macos&lt;/span&gt;
      &lt;span class="s"&gt;out/mycli_windows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also &lt;code&gt;files&lt;/code&gt; can be specified to individual file list instead of glob expression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Combine the build process with the release process using artifact
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VTtHUFxr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyczrfy03wv2abphbgy5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VTtHUFxr--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pyczrfy03wv2abphbgy5.png" alt="build and release" width="880" height="630"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each of 3 build actions generate a binary and release process should upload them all at once. The flow can be implemented with GitHub artifact.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Publish&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[0-9]+.[0-9]+.[0-9]+'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;compile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart compile exe&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
            &lt;span class="na"&gt;binary-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mycli_linux_amd64&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;macos-latest&lt;/span&gt;
            &lt;span class="na"&gt;binary-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mycli_macos_amd64&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;windows-latest&lt;/span&gt;
            &lt;span class="na"&gt;binary-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mycli_windows.exe&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.runs-on }}&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart-lang/setup-dart@v1&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart pub get&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mkdir ${{ matrix.runs-on }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;dart compile exe bin/mycli.dart -o ${{ matrix.runs-on }}/${{ matrix.binary-name }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-${{ matrix.runs-on }}&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.runs-on }}&lt;/span&gt;

  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;needs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;compile&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github release&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-ubuntu-latest&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-linux&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-macos-latest&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-macos&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/download-artifact@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-windows-latest&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-windows&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;softprops/action-gh-release@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;draft&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bin-*/*&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works expectedly like below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--E9FeUufh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwyvq5hpaw10fzo31rky.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--E9FeUufh--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wwyvq5hpaw10fzo31rky.png" alt="flow" width="880" height="477"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and the artifacts are successfully uploaded to GitHub Releases! :)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--jo2j5lCW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kg70ttdrqii5o2up4ngq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--jo2j5lCW--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kg70ttdrqii5o2up4ngq.png" alt="Released" width="880" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>dart</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>System testing for Rails application without Capybara, using Puppeteer</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Fri, 03 Sep 2021 05:58:03 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/system-testing-for-rails-application-without-capybara-using-puppeteer-5882</link>
      <guid>https://dev.to/yusukeiwaki/system-testing-for-rails-application-without-capybara-using-puppeteer-5882</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Refer &lt;a href="https://zenn.dev/yusukeiwaki/articles/449a860a750561"&gt;this article&lt;/a&gt; instead if you can read Japanese sentences.&lt;br&gt;
日本語を読める方は&lt;a href="https://zenn.dev/yusukeiwaki/articles/449a860a750561"&gt;こちらの記事&lt;/a&gt;を見てください&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;Rails introduced "system testing", that makes it easy to configure Capybara: acceptance test framework for Ruby. We may often use it not for "acceptance test" but for just UI testing of the Rails application with React/Vue or other rich JavaScript-powered features.&lt;/p&gt;

&lt;p&gt;Capybara DSL is actually a little &lt;strong&gt;inaccurate&lt;/strong&gt; for working with pages with rich JavaScript features.&lt;/p&gt;

&lt;p&gt;Capybara has internally polling with interval 10msec for waiting for Elements available and actionable.&lt;br&gt;
&lt;a href="https://github.com/teamcapybara/capybara/blob/0468de5a810aae75ab9de20447e246c5c35473f0/lib/capybara/node/base.rb#L91"&gt;https://github.com/teamcapybara/capybara/blob/0468de5a810aae75ab9de20447e246c5c35473f0/lib/capybara/node/base.rb#L91&lt;/a&gt;&lt;br&gt;
I don't know this is really the main cause, but we may often face flaky/unstable testcases in using Capybara.&lt;/p&gt;

&lt;p&gt;On the other hand, &lt;a href="https://pptr.dev/"&gt;Puppeteer&lt;/a&gt; or &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt; have a relyable solution for waiting for Elements: &lt;code&gt;Page#waitForSelector&lt;/code&gt; and &lt;code&gt;Page#waitForNavigation&lt;/code&gt;. (Playwright has more advanced feature of &lt;a href="https://playwright.dev/docs/actionability"&gt;auto-waiting&lt;/a&gt; )&lt;/p&gt;

&lt;p&gt;I actually ported the libraries for using them in Rails application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Playwright for Ruby: &lt;a href="https://playwright-ruby-client.vercel.app/"&gt;https://playwright-ruby-client.vercel.app/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Puppeteer for Ruby: &lt;a href="https://github.com/YusukeIwaki/puppeteer-ruby"&gt;https://github.com/YusukeIwaki/puppeteer-ruby&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However &lt;strong&gt;the relyable features cannot be used from Capybara DSL&lt;/strong&gt;. So let's try to configure system tests without Capybara, and using Puppeteer.&lt;/p&gt;
&lt;h2&gt;
  
  
  What happens if Capybara is absent??
&lt;/h2&gt;

&lt;p&gt;Since Capybara is a huge framework, it would be difficult for some users to imagine what happens without Capybara.&lt;/p&gt;

&lt;p&gt;Actually Rails application without Capybara loses the functionalities&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launching test HTTP server on starting tests&lt;/li&gt;
&lt;li&gt;Capybara::DSL

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;visit&lt;/code&gt; or other methods raises &lt;code&gt;NoMethodError&lt;/code&gt; instead of NotImplementedError&lt;/li&gt;
&lt;li&gt;System spec (on RSpec) or SystemTestCase (on MiniTest), which depends on Capybara::DSL&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However rspec-rails still provides features like&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Attaching &lt;code&gt;type: :feature&lt;/code&gt; &lt;code&gt;type: :system&lt;/code&gt; to example.metadata&lt;/li&gt;
&lt;li&gt;Defining &lt;code&gt;feature&lt;/code&gt; &lt;code&gt;background&lt;/code&gt; &lt;code&gt;scenario&lt;/code&gt; methods for feature spec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So actually all we have to prepare is&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Launching test HTTP server&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Performing automation with browsers

&lt;ul&gt;
&lt;li&gt;This will be satisfied by Puppeteer&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Launching test HTTP server without Capybara
&lt;/h2&gt;

&lt;p&gt;Capybara is really kind that&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;finds available port&lt;/li&gt;
&lt;li&gt;allows to configure host&lt;/li&gt;
&lt;li&gt;allows to launch Webrick and Puma&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However let's forget them at this moment, and assume that &lt;strong&gt;we only have to launch Puma with the baseURL &lt;code&gt;http://127.0.0.1:3000&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;With referring the logics of Capybara &lt;a href="https://github.com/teamcapybara/capybara/blob/master/lib/capybara/server.rb"&gt;preparing server&lt;/a&gt; and &lt;a href="https://github.com/teamcapybara/capybara/blob/master/lib/capybara/registrations/servers.rb"&gt;launching Puma server&lt;/a&gt;, then we can easily find the minimum configuration for launching test server like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:suite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# launching Rails server for system testing&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/builder'&lt;/span&gt;
    &lt;span class="n"&gt;testapp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="s1"&gt;'/__ping'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="c1"&gt;# debugging endpoint for heartbeat&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/plain'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/handler/puma'&lt;/span&gt;
    &lt;span class="n"&gt;server_thread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Puma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testapp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="no"&gt;Threads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'0:4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;workers: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="ss"&gt;daemonize: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# Waiting for Rails server is ready, using Net::HTTP&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'net/http'&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'timeout'&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kp"&gt;loop&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:3000/__ping"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EADDRNOTAVAIL&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ECONNREFUSED&lt;/span&gt;
        &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Configure puppeteer-ruby for automation&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;type: :feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;channel: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headless: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@server_base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://127.0.0.1:3000'&lt;/span&gt;
      &lt;span class="vi"&gt;@puppeteer_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By putting this configuration into spec/support/integration_test_helper.rb or another place you prefer, now we can enjoy system testing with Puppeteer like below!!&lt;/p&gt;

&lt;p&gt;As is already described, &lt;strong&gt;we cannot use system spec. Use feature spec (provided by rspec-rails) instead&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rails_helper'&lt;/span&gt;

&lt;span class="n"&gt;describe&lt;/span&gt; &lt;span class="s1"&gt;'example'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:base_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@server_base_url&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="vi"&gt;@puppeteer_page&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="no"&gt;FactoryBot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="s1"&gt;'can browse'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/tests/&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;visible: &lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'hoge'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;keyboard&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Enter'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eval_on_selector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'#content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'el =&amp;gt; el.textContent'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="kp"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'hoge'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to&lt;/span&gt; &lt;span class="kp"&gt;include&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--FY7qBFea--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfgmuh1orp6d6rvk9hv2.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--FY7qBFea--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfgmuh1orp6d6rvk9hv2.gif" alt="integ_rails" width="800" height="456"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A little refactoring for production use
&lt;/h3&gt;

&lt;p&gt;The logic of launching test server, introduced in the previous section, is really straightforward. However most users would hesitate to copy/paste it into your own products because it's too dirty :(&lt;/p&gt;

&lt;p&gt;No worry, here is a better code with more relyable &lt;code&gt;Rack::Server.start&lt;/code&gt; to launch HTTP server, which is actually used in &lt;a href="https://github.com/rack/rack/blob/2.2.3/bin/rackup#L5"&gt;the implementation of &lt;code&gt;rackup&lt;/code&gt; command&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RackTestServer&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;

    &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Host&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Port&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;

    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/builder'&lt;/span&gt;
    &lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:app&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;app&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="s1"&gt;'/__ping'&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'text/plain'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'OK'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;end&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;base_url&lt;/span&gt;
    &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Host&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:Port&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rack/server'&lt;/span&gt;
    &lt;span class="no"&gt;Rack&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="vi"&gt;@options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ready?&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'net/http'&lt;/span&gt;
    &lt;span class="k"&gt;begin&lt;/span&gt;
      &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="n"&gt;base_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/__ping"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;EADDRNOTAVAIL&lt;/span&gt;
      &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;rescue&lt;/span&gt; &lt;span class="no"&gt;Errno&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;ECONNREFUSED&lt;/span&gt;
      &lt;span class="kp"&gt;false&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wait_for_ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;timeout: &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'timeout'&lt;/span&gt;
    &lt;span class="no"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="nb"&gt;sleep&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="n"&gt;ready?&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this test server, the RSpec configuration file can be much shorter and simplified :)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;RSpec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:suite&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="c1"&gt;# Launch Rails application&lt;/span&gt;
    &lt;span class="n"&gt;test_server&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;RackTestServer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;# options for Rack::Server&lt;/span&gt;
      &lt;span class="c1"&gt;# https://github.com/rack/rack/blob/2.2.3/lib/rack/server.rb#L173&lt;/span&gt;
      &lt;span class="ss"&gt;app: &lt;/span&gt;&lt;span class="no"&gt;Rails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;application&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;server: :puma&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'127.0.0.1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="no"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;daemonize: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

      &lt;span class="c1"&gt;# options for Rack::Handler::Puma&lt;/span&gt;
      &lt;span class="c1"&gt;# https://github.com/puma/puma/blob/v5.4.0/lib/rack/handler/puma.rb#L84&lt;/span&gt;
      &lt;span class="no"&gt;Threads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'0:4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="ss"&gt;workers: &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="no"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;test_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;test_server&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait_for_ready&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="c1"&gt;# Configure puppeteer-ruby for automation&lt;/span&gt;
  &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;around&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;type: :feature&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="no"&gt;Puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;channel: :chrome&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headless: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
      &lt;span class="vi"&gt;@server_base_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'http://127.0.0.1:3000'&lt;/span&gt;
      &lt;span class="vi"&gt;@puppeteer_page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;
      &lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, you can now get rid of Capybara from your app if you just want to launch HTTP server for testing :)&lt;/p&gt;

&lt;p&gt;You can also use &lt;a href="https://rubygems.org/gems/rack-test_server"&gt;rack-test_server&lt;/a&gt; Gem instead of defining your own RackTestServer.&lt;/p&gt;

</description>
      <category>puppeteer</category>
      <category>rails</category>
      <category>capybara</category>
      <category>rspec</category>
    </item>
    <item>
      <title>Using 🎭Playwright in Ruby/Rails</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Tue, 10 Aug 2021 14:25:06 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/using-playwright-in-ruby-rails-2km0</link>
      <guid>https://dev.to/yusukeiwaki/using-playwright-in-ruby-rails-2km0</guid>
      <description>&lt;p&gt;🎭Playwright is a really awesome library for browser automation. Microsoft already published several language bindings.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;TypeScript: &lt;a href="https://github.com/microsoft/playwright" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Java (or Kotlin): &lt;a href="https://github.com/microsoft/playwright-java" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright-java&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python: &lt;a href="https://github.com/microsoft/playwright-python" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright-python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;C#: &lt;a href="https://github.com/microsoft/playwright-dotnet" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright-dotnet&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And also playwright-go is planned to be official.&lt;br&gt;
&lt;a href="https://github.com/microsoft/playwright/issues/6856" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright/issues/6856&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  No Ruby client?
&lt;/h2&gt;

&lt;p&gt;Ruby or Rails developers would like to use Playwright for acceptance testing or other use-cases, but it is not available at this moment.&lt;/p&gt;

&lt;p&gt;Playwright team explicitly says there is no plan to release Ruby client.&lt;br&gt;
&lt;a href="https://github.com/microsoft/playwright/issues/5874" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright/issues/5874&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Ruby users should be happy with Playwright!!
&lt;/h2&gt;

&lt;p&gt;It is clear that Ruby is not so famous as Python or TypeScript, but many Rails users still use it.&lt;/p&gt;

&lt;p&gt;Rails already included an acceptance testing framework (called SystemTestCase). It uses &lt;a href="https://github.com/teamcapybara/capybara" rel="noopener noreferrer"&gt;Capybara&lt;/a&gt; and most users already noticed Capybara tends to produce flaky testcases.&lt;/p&gt;

&lt;p&gt;Playwright provides very clever DOM selectors with auto-waiting feature, and useful utility functions. As Internet Explorer is already dead, no reason to keep using Selenium and Capybara.&lt;/p&gt;

&lt;p&gt;Fortunately, Playwright is server-client architecture. We can use the useful features on any languages by just implementing a Playwright API client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmp57apr9txqa33jgs3ei.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmp57apr9txqa33jgs3ei.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it on Ruby!!
&lt;/h3&gt;

&lt;p&gt;I actually created a Playwright client for Ruby.&lt;br&gt;
&lt;a href="https://github.com/YusukeIwaki/playwright-ruby-client" rel="noopener noreferrer"&gt;https://github.com/YusukeIwaki/playwright-ruby-client&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="no"&gt;Playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect_to_playwright_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'ws://127.0.0.1:8080'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'https://github.com/YusukeIwaki'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;path: &lt;/span&gt;&lt;span class="s1"&gt;'./YusukeIwaki.png'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Note that we have to launch Playwright server in advance, like this: &lt;code&gt;npx playwright install &amp;amp;&amp;amp; npx playwright run-server 8080&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it on Rails!!
&lt;/h3&gt;

&lt;p&gt;I also created a Capybara driver of Playwright.&lt;br&gt;
&lt;a href="https://github.com/YusukeIwaki/capybara-playwright-driver" rel="noopener noreferrer"&gt;https://github.com/YusukeIwaki/capybara-playwright-driver&lt;/a&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;

&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;register_driver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:playwright&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Playwright&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;browser_type: :firefox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;headless: &lt;/span&gt;&lt;span class="kp"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_max_wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;
&lt;span class="no"&gt;Capybara&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;default_driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:playwright&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Capybara DSL is supported, and we can also use Playwright-native features such as recording video during testing.&lt;br&gt;
&lt;a href="https://playwright-ruby-client.vercel.app/docs/article/guides/rails_integration#available-functions-and-limitations" rel="noopener noreferrer"&gt;https://playwright-ruby-client.vercel.app/docs/article/guides/rails_integration#available-functions-and-limitations&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Playwright's architecture is great, and we Ruby/Rails users can use Playwright features by developing our own Playwright client API library.&lt;/p&gt;

&lt;p&gt;Unfortunately Microsoft doesn't make effort for Ruby users, and difficult to ask for official support for Ruby.&lt;/p&gt;

</description>
      <category>playwright</category>
    </item>
    <item>
      <title>Using 🎭Playwright in python:alpine</title>
      <dc:creator>Yusuke Iwaki</dc:creator>
      <pubDate>Tue, 10 Aug 2021 13:41:26 +0000</pubDate>
      <link>https://dev.to/yusukeiwaki/using-playwright-in-python-alpine-53o1</link>
      <guid>https://dev.to/yusukeiwaki/using-playwright-in-python-alpine-53o1</guid>
      <description>&lt;p&gt;&lt;a href="https://github.com/microsoft/playwright-python" rel="noopener noreferrer"&gt;playwright-python&lt;/a&gt; is a really awesome library for browser automation.&lt;/p&gt;

&lt;p&gt;However it doesn't support running on Alpine Linux.&lt;br&gt;
&lt;a href="https://github.com/microsoft/playwright-python/issues/234" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright-python/issues/234&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most web developers would use Alpine based Docker images, and actually playwright-python internally has a capability of running scripts in Alpine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why playwright-python cannot run on Alpine?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FYusukeIwaki%2Fplaywright-python-playWithWebSocket%2Fblob%2Fmain%2Fdocs%2Fcurrent.png%3Fraw%3Dtrue" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FYusukeIwaki%2Fplaywright-python-playWithWebSocket%2Fblob%2Fmain%2Fdocs%2Fcurrent.png%3Fraw%3Dtrue" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;playwright-python comes with a browser downloader. As we know, browsers requires many dependencies. The deps are not compatible with Alpine.&lt;/p&gt;

&lt;p&gt;This might be a main reason why playwright-python deny running on Alpine.&lt;/p&gt;

&lt;p&gt;By the way, we already know Selenium Remote driver (Selenium Grid), it separates script runner and browser environments communicating with HTTP port (default: 4444).&lt;/p&gt;

&lt;h2&gt;
  
  
  How to separate script runner and browsers
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FYusukeIwaki%2Fplaywright-python-remote%2Fraw%2Fmain%2FREADME%2Fstructure.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2FYusukeIwaki%2Fplaywright-python-remote%2Fraw%2Fmain%2FREADME%2Fstructure.png" alt="image"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;playwright-python is just a Python client library of Playwright. Playwright server communicates with browser via WebSocket or other protocols, and playwright-python communicates with the server via &lt;strong&gt;pipe&lt;/strong&gt; by default.&lt;/p&gt;

&lt;p&gt;However playwright-python already introduced &lt;strong&gt;WebSocketTransport&lt;/strong&gt; for the API &lt;code&gt;BrowserType#connect()&lt;/code&gt; in Playwright v1.11 and later. It can be also used for Playwright connection between client and server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Try it!
&lt;/h3&gt;

&lt;p&gt;I created a tiny library.&lt;br&gt;
&lt;a href="https://github.com/YusukeIwaki/playwright-python-remote" rel="noopener noreferrer"&gt;https://github.com/YusukeIwaki/playwright-python-remote&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This library provides a Playwright initializer (ContextManager) just replacing PipeTransport with WebSocketTransport.&lt;br&gt;
&lt;a href="https://github.com/YusukeIwaki/playwright-python-remote/blob/main/playwright_remote/sync_api/sync_playwright_remote.py" rel="noopener noreferrer"&gt;https://github.com/YusukeIwaki/playwright-python-remote/blob/main/playwright_remote/sync_api/sync_playwright_remote.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Playwright Server can be prepared very easily with Docker like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; mcr.microsoft.com/playwright:focal&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /root&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;playwright@1.12.3 &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./node_modules/.bin/playwright &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["./node_modules/.bin/playwright", "run-server"]&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now we can enjoy playwright-python on Alpine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright_remote.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright_remote&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;sync_playwright_remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ws://127.0.0.1:8080/ws&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;playwright&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://github.com/YusukeIwaki&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YusukeIwaki.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;I also tried it on Heroku: &lt;a href="https://zenn.dev/yusukeiwaki/scraps/3ad5f8ed536720" rel="noopener noreferrer"&gt;https://zenn.dev/yusukeiwaki/scraps/3ad5f8ed536720&lt;/a&gt; (Sorry, Japanese only...)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;playwright-python is really awesome, it runs on Alpine actually.&lt;/p&gt;

&lt;p&gt;Note that WebSocketTransport (introduced in this article) is internal class and can be changed in future releases.&lt;/p&gt;

&lt;p&gt;Just expect the feature is officially supported. Let's upvote&lt;br&gt;
&lt;a href="https://github.com/microsoft/playwright/issues/4687" rel="noopener noreferrer"&gt;https://github.com/microsoft/playwright/issues/4687&lt;/a&gt; 👍&lt;/p&gt;

</description>
      <category>playwright</category>
    </item>
  </channel>
</rss>
