気づけばかれこれ1年以上、Ruby言語向けのブラウザ自動操作のソフト開発を趣味の時間でやっている。
puppeteer-rubyで既存のRailsアプリケーションのシステムテストを段階的に良くしたい、というコンセプトは以前書いたが、最近はPlaywrightをRubyから使えるようにすることに注力している。
なので、playwright-ruby-clientを作っているから、puppeteer-rubyはもうオワコン?とはならない。用途が全然違う。
playwright-ruby-clientはどちらかというと、Ferrumに近い位置づけで、今後はCupriteのようなCapybaraドライバを作ったり、Vesselのようなクローラー書く仕組みを作ったり、みたいな方向性で成長させていく必要がある。
puppeteer-rubyはあくまでCapybaraとの「共存」ができることを強みに、(本家Puppeteerがなくならない限りはw)育てていくだろう。
(https://yusukeiwaki.hatenablog.com/entry/2021/01/24/puppeteer-ruby-on-fukuoka-ruby-award-13 の末尾から引用)
ここで書いてた「Capybaraドライバを作ったり」のところをまさに最近やっている。
playwright-ruby-clientがだいぶ安定したきたことだし、Railsとのつなぎ込みのためのCapybaraドライバでも書いてみよっかな
— Yusuke Iwaki (@yi01imagination) 2021年4月22日
Capybaraのカスタムドライバの実装方法を書いた唯一の記事(誇張&まだ作成中https://t.co/vy7BncV1Xp
— Yusuke Iwaki (@yi01imagination) 2021年4月30日
(おさらい)Railsのsystem spec
一言でいうと、「FactoryBotでテストデータを作った状態&モックが効いた状態で、JavaScriptをふんだんに使ったページを自動試験する」という曲芸ができる優れもの。
人の手で再現困難な「外部のAPIサーバーが壊れていたときの○○の表示」とか「絶妙なタイミングで、二重決済が走ってしまったときの△△表示」みたいなのを機械的に状況を作って試験できる。
かなり夢が広がる仕組みなのだけど、実際に運用をすると1つだけ難点がある。それが、自動操作が不安定だということ。「なんかわからんけど20回に1回くらいコケる」みたいなことが稀によくある。
すでに試験を作ってしまった状況を救うpuppeteer-ruby
ここに書いていたことそのものだが、発想としては 「CapybaraからSeleniumドライバを利用して ヘッドレスChromeを操作する」というベースの仕組みは変えず、Seleniumドライバが起動したChromeをPuppeteer経由でも操作できるようにして、Seleniumの弱みを補強する というものだ。
これから試験を作るなら...? → capybara-playwright-driver
もし既存の試験というものが無く、これからsystem specを書き始めるのであれば、どうせなら ChromeだけじゃなくてFirefoxもWebKitも操作できるほうがありがたいと思うことも多いはず。
まじSeleniumなんてIE11が滅びたら使う意味ないぞ。自動テストならともかく、スクレイピングでSeleniumなんて使う必要皆無。Playwright使いましょう
— Yusuke Iwaki (@yi01imagination) 2021年4月18日
こんなつぶやきをしていたのだけど、IE11が本格的に滅び始めているので、もうSeleniumを使う意味ってほとんど無くて、Playwright使いたいよね、と。
ただ、RailsからPlaywrightでの自動操作をするには少しハードルが高い。
Playwrightは(Chromeを除いて)自分が起動したブラウザじゃないと自動操作できない ので、CapybaraがSeleniumドライバ経由でブラウザを起動しているところをごっそり置き換えてあげる必要がある。
途中のあれこれはさておき、とりあえずCapybaraのカスタムドライバを作ったのが capybara-playwright-driverだ。
Capybaraドライバの実装は本当に悪戦苦闘
ドキュメントはありません。ソースを見ましょう
そもそもCapybaraは利用者向けのドキュメントはそこそこあるが、ドライバ開発者向けの情報が皆無だ。既存のドライバのソースを斜め読みして、既存のドライバの挙動をサンプルコードで確認しつつ、zennにいろいろ書き残しながらやった。
Capybaraドライバの実装、もうちょっとドキュメントまとまってたらいいのになぁ。現状だとApparitionやCupriteやSeleniumドライバをコピペプログラミングするしかない。
— Yusuke Iwaki (@yi01imagination) 2021年4月23日
Capybaraが用意している単体試験は、実行順序に依存している?!?!
Capybaraには Capybara::SpecHelper
というコンポーネントがあって、ここに各ドライバが満たすべき単体試験がたくさんある。(詳しくはこのへんに書いた→
https://zenn.dev/yusukeiwaki/scraps/280aabf289ae29#comment-ece446f4bd072c)
単体試験の実装工数が大幅削減できるのは非常〜〜〜にありがたいのだけど、ただこいつは本当にくせもので、単発で実行したときと全体で実行したときで、なぜか結果が変わることがある。Capybaraはクラス変数にいろいろ詰め込んでいて副作用満載なので、なにかリセット処理がどこかで漏れているのだろう...。
CapybaraのRSpec、部分実行したときと全実行したときで結果が違うので、どこかでリセット処理が漏れてそう。コントリビューションチャンスだが気力がない
— Yusuke Iwaki (@yi01imagination) 2021年5月16日
単体試験の実行に時間がかかる
Firefoxの結果まで待つと、CI待ちがめちゃくちゃ長いww
結果がわかるのは67分後か...w
— Yusuke Iwaki (@yi01imagination) 2021年5月17日
テキスト入力などを行う Node#set
のパラメータ仕様が複雑怪奇
テキスト入力も、チェックボックスも、ラジオボタンも、select/optionも、全部 Node#set
というメソッドが担っている。その結果、当然ながら各種ドライバは、どれも Node#set が複雑になってしまっている。
- capybara/node.rb at 312ab56ac488d872986701fddfeeae120e308fbd · teamcapybara/capybara · GitHub
- apparition/node.rb at 11aca464b38b77585191b7e302be2e062bdd369d · twalpole/apparition · GitHub
残念ながら、これを真似するほかない。
キーボード入力をする send_keys
のパラメータ仕様も複雑怪奇
- 配列かもしれないし、文字列かもしれない
- 配列の場合、各要素はシンボルかもしれないしテキストかもしれないし、
[:ctrl, 'C']
のような複合キーを表す配列かもしれない - 途中で
:shift
があったら、それ以降のテキストは大文字にする
などなど、なかなかに配慮が必要。
しかもsend_keysはDriverとNodeの2箇所に定義が必要だ。どのDOM要素にフォーカスがあたっているかを特に気にせずキーボード操作する用のと、DOM要素に対してキーボード操作する用の。
Capybaraのソースを読んでの発見
Capybaraのfindってメソッドは、内部でquerySelectorAllとvisible?(これは各ドライバが実装するもの)をポーリングして要素を見つけるような実装してるけど、この実装があるかぎりflaky testがなくならん気がする...。ポーリングしてるかぎり絶対取りこぼしが発生するので。
— Yusuke Iwaki (@yi01imagination) 2021年5月7日
RubyのCapybaraでテストを書くとFlakyになる諸悪の根源これな気がしてならない。10msって、Ruby的には一瞬なんかもしれないけど、JSの世界で言うと5回SharedTimerがtickしうるわけで... pic.twitter.com/Snc7sgkVIH
— Yusuke Iwaki (@yi01imagination) 2021年5月8日
SeleniumはwaitForElement的な実装がないので不安定なテストになりがちなのはともかく、単体では優秀(比較的安定)なCDP経由の自動操作もCapybaraを通すと0.01秒のsleepのせいで不安定になる、というのはソース読まないとわからない知見。つまりCupriteやapparitionもflakyテストを量産してしまう。
— Yusuke Iwaki (@yi01imagination) 2021年5月8日
Tweetそのままだが、 自動操作をCDPをベースにしたもの(= PuppeteerやPlaywright)でもCapybaraを通すと精度が出ない! ということだ。
ようするに、 selenium-webdriverをやめて Cuprite や Apparition を使うだけでは、システムテストの精度は改善しない。
Capybaraドライバを実装時に、実際にGitHubのリポジトリ検索をCIで動作確認するようにしていたのだけど、
Capybara.app_host = 'https://github.com' visit '/' fill_in('q', with: 'Capybara') sleep 1 # これがないと、時々失敗する find('a[data-item-type="global_search"]').click
Capybara DSLだけでは、このようにお粗末なsleepが必要だった。これをPlaywrightネイティブの page.click('a[data-item-type="global_search"]')
に置き換えるだけで、それ以降は(sleep 1をなくしても)一度も失敗しなくなった。
ともあれ、とりあえずリリースしてみた
まだまだプレビュー版だけど、gem pushしてみた。
こんなかんじで従来のselenium-webdriverと似たような感じでセットアップして
Firefox(のマイクロソフト独自改造版のjuggler)がポンポン起動して自動試験が走るようになる。
今後...
ぶっちゃけ、(Railsじゃなければ)PlaywrightでE2Eテストを書くなら、playwright-pytestを使えばいいし、自分自身もブラウザの自動操作はだいたいplaywright-pythonで書いているw
とはいえ、Railsのsystem specの運用に不満を抱える状況自体はそう簡単に解消しないだろうし、もしPlaywrightでRailsのシステムテストが改善できるなら、いろんな人を幸せにできて良さそうだなとも感じている。
現状はplaywright-ruby-clientのドキュメントが未整備なので、「Railsユーザで、Playwrightを楽しんでみたい方はどうぞw」と言われてもcapybara-playwright-driverは多分まともに使えるのが自分しかいない状態だろう。
そんなわけで、今後はplaywright-ruby-clientのドキュメントを少しずつ整備して、長期的にはRailsのシステムテストでPlaywrightを使う人を増やしていきたい。