<?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>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>
