event

Kaigi on Rails 2022で登壇しました

#登壇#kaigionrails

10 月 21 日、22 日に開催された Kaigi on Rails 2022 に Speaker として登壇しました!

https://kaigionrails.org/2022/

初のカンファレンス登壇で順番最後、かつギリギリまで準備が終わらず、最後の最後までずっとソワソワして参加者としてはガッツリは参加できなかった(アーカイブ見るぞ)のですが、とてもいい経験ができました!運営の皆さん本当にありがとうございました!

登壇資料はこちらです!

https://speakerdeck.com/ikumatadokoro/zi-fen-dakenoxiao-sanaselenium-olenium-wozuo-tuteshi-meru-burauzazi-dong-hua-ji-shu-noli-lun-toshi-jian

せっかくなので自分の発表についてつらつらと書いていきたいと思います。

登壇へのモチベーション

スライドにもあるとおり、FJORD BOOT CAMP での学習期間を経て、今年の 6 月からエンジニアとして働き始めています。

https://bootcamp.fjord.jp/

で、やっぱりエンジニアになったからにはカンファレンスとか登壇してみたいわけですね。学び初めの時は「地域 rb に顔出すのは上級者」くらいの気持ちだった人がそう思っているのは、完全に FJORD BOOT CAMP にいた影響だと思っています。

なんか知っている人結構いるし、卒業生の中から RubyKaigi に登壇している人もいるし、じぶんもやってみたい!と自然に思えるようになっていました。

登壇テーマの選定

今回はブラウザ自動化技術を登壇テーマに選んだんですが、理由としては 2 つありました。

  1. ブラウザ自動化を便利に使っていたが、その中身はよく知らないままだなと思っていたので、その中身を知りたかった
  2. もともとブラウザ自体に興味があった

1 つ目は発表にあった通りで、2 つ目については Web アプリを作る以上ブラウザに載せることがほとんどなので、そこをちゃんと知りたいなあという思いです。

昨今では CSS フレームワークとかもよくできているので、見栄え的にはあまり考えなくてもまあまあ綺麗なものができるのですが、でももうちょっとブラウザ自体を知らないとブラウザの真価を発揮したりとかセキュリティ意識したりとかいろんな環境を意識して作るとかできないよなあと。

動きを把握する時にはやっぱり実際に動きが始まるところから見るのがわかりやすいと思うのですが、ブラウザ自動化は自分が知っている領域でもあり、ブラウザに対するエントリポイント的な位置付けなので、今回はここにフォーカスしようという気持ちもありました。

近いうちに簡易なレンダリングエンジン実装とかやってみたいですね(Olelink とか OleKit とかですかねえ)

準備

実際に WebDriver や CDP を読んでいく

発表ではわりとさっくり「意外とシンプルに接続できる」という流れで紹介していましたが、当然のごとくミリしら状態で仕様を見始めたので、なかなかうまくいかず苦労しました。

資料の中で「Capabilities」や「WebSocket」が用語で 1 ページとってありますが、これは実際に試している中で「なんだこれ」とか「いまいちわからん」と詰まった箇所です(ActionCable ちゃんと使ったことないのがバレた)。

例えば WebSocket の接続をする際にも WebSocket 自体の理解が曖昧で、最初は色々と回りくどい方法で URL を取得していたのですが、Ruby の CDP クライアント ferrum のソースを読んでいる中で「あ、サブプロセスで起動した Chrome の標準出力を正規表現で抽出して、WebSocket の URL 取得しているじゃん、これでええやん」と理解し、発表のような説明となりました。先人偉大なり。

https://github.com/rubycdp/ferrum/blob/efb6f2035c442582e815328e633d1f1d6c97e456/lib/ferrum/browser/process.rb#L99-L102

大変といえば大変だったんですが、その分色々と自分が知らない知識も増えて面白かったです。例えば上記箇所だと ObjectSpace.define_finalizer を知ることができました(一人 igaiga ふむふむタイム)。

https://twitter.com/igaiga555/status/1569154474060705792

https://zenn.dev/igaiga/books/rails-practice-note/viewer/ruby_debugging#objectspace%23finalizer

chromedp/pdlgen の実装を読むのが大変だった

なにが大変だったって、Go の文法を一切知らない状態から始めたのもあるんですが、chromedp で CDP をパースしているライブラリの最新ブランチが master じゃなくて old だったことなんですよね 😅

自分のローカルに Clone して chromedp/pdlgen を実行した時に panic になって落ちてしまいました。 Go の知識が乏しく「自分の環境が悪いってことかな」と思って何日か文法調べつつ読んでいき、最終的に一部分岐で落ちていることがわかったため、Issue で報告しようとしたタイミングで同じ事象の Issue があることに気が付き…

https://github.com/chromedp/pdlgen/issues/16

I guess its required to use the old branch?

Yes, the old branch is the latest branch.

old ブランチにしたら動きました 😅

実装:Olaywright の実装で頭ごっちゃに

CFP 提出時点で Olenium は動くものができていたので、上記の調べ物をしたのち、Olaywright の実装に取り掛かりました。

が、この辺りで「フロントエンドエンジニア」となったため、仕事前には Ruby 書いて業務中は TypeScript だけ書いてて、仕事終わったら Ruby 書いて…となり、地味に大変でした。マジで何回 const で変数定義して、const がなくて怒られたと思っているんだ?(===もめっちゃやったなあ)

まあそれくらいならまだいいんですが、オブジェクトに対する考え方が完全に JavaScript に引っ張られていたのが一番大変でした。今回の発表では JSON をパースして、Ruby のメソッドとして呼び出せるようにしていましたが、そこです。

縛りなしで、個人的にもっとも実行しやすいインターフェースを考えると、たとえば Target.createTarget という CDP のメソッドはtarget.create_target(url: 'https://google.com')という形で呼べるのが Ruby っぽくかつシンプルで分かりやすいと考えました。

で、これを静的に定義しておこうと思ったところで頭がこんがらがります。

  • Chrome へのメソッド実行は、実際のところメソッド名をただメッセージに渡すだけ。つまりメソッドの中身がないので、わざわざあらかじめ定義しておく意味があまりない。
  • JSON で落ちてきた CDP には、そのメソッドに関する説明が多分に入っており、それをすべて削除するような構造になってしまう。 target.create_target()がメソッドだとすると、CDP でやってくる「Target.createTarget の説明」や「Target.createTarget は experimental かどうか」といった情報をtarget.create_target.descriptiontarget.create_target.experimental?のように持たせるのが難しい(私がわからなかっただけなので、できたらどこかで教えてください〜〜!)

さらにめんどうなことに

  • Go の方読んでたら構造体が出てきてクラスが歪み始める
  • 一日の大半は TypeScript を書いているので、またまたオブジェクトに対する認知が歪み始める

となり、かなりの日数手が止まったあげく最終的には以下のような方針・実装になりました。

  • やっぱりtarget.create_target.descriptionとかtarget.create_target.experimental?とか呼び出せると嬉しい(なお boolean を返すメソッドには Ruby ライクに「?」つけたかったのですが、忘れていたので実際のコードではやっていないです。)
  • WebSocket へメッセージを送信する部分以外は振る舞いではなく、単なる情報(説明とか実験的機能だとか任意のパラメータだとか)の集まりなので、Struct でいいんでは?
  • JS の Function オブジェクト的な感じで格納できればなおのことよかったんだけど、それは Ruby ではできない(?)ので、Struct のプロパティの中で他の情報と並列にexecというプロパティを保持し、ここに Proc オブジェクトを持たせる形で定義する
    • Sturct のメソッドとして定義すりゃよかったんですが、Olaywright クラスで持っている WebSocket の接続情報のコンテキストを渡すのがさっとできなかったので、呼び出しが Proc の呼び出し(exec[])になったわけです。
  • 愚直によぶとbrowser.domains.page.navigate.exec[url: 'https://google.com']のような長さになる。これは嫌だ。
  • というわけで最終的に必要な Domain の情報を一行パターンマッチでキャプチャすりゃあええやろ(browser.domains in { page: }

https://gist.github.com/IkumaTadokoro/26e08130f849c9aac8c4b18a1adc7561

かなり頭の中で考えるのに時間を使ってしまって、ちょっと前髪整えますねくらいのリファクタリングはしたんですが、そこまでで力尽きてインスタンス生成に 3 秒かかるプログラムになりました 💦 どう考えてもループしすぎですね、どうもありがとうございました。

実装:Gem 使うかどうか

String#underscoreを refinement で拡張しているところとか別に ActiveSupport 入れてもよかったんですが、ライブラリ使わないでも Ruby 本体だけで欲しいもの書けるんだ!っていう思いも発表にこめたかったので自前で定義しています(発表では見せませんが)。

じゃあなんで WebSocket はライブラリを使っているかというと、この Gem の管理を現在やっているのが運営のうなすけさんだったので、何か選考に有利に働いてくれないかなあと思い、CFP の時点でこの Gem を使うということを明記したという経緯でした笑

https://rubygems.org/gems/websocket-client-simple

名前の通りめちゃくちゃシンプルでコードも読みやすいので、想定聴者がジュニア〜中級くらいのこの発表の筋にもあっている気がして、WebSocket の中でもこの Gem にしたというのもあります。

websocket-client-simple のコードを読む過程で EventEmitter について知り、一人ターミナルチャットアプリやってました。勉強にはなりましたが寂しかったです。

なんで irb で動かすことにしたの?

苦肉の策で上記の通り多段 Struct をやった結果、Olenium 同様にスクリプトを書いても全然補完が走らず、これはどうしたものかと思ったところ、「irb 上でインスタンス化した後なら補完走るじゃん!!」と思い立ちこうなりました。いやあ irb 便利。

ちなみに補完が走る際に、Struct ではセッターの方のメソッドも補完が走ります。これはスクリプトを動かす際には使わないのでどうにか消したかったんですが(freeze はしているけど)やり方わからず断念しました(Struct のサブクラスに対して、これ呼べないですよね?できるのであれば教えてほしいです〜〜)。

Olaywright.usage

こうやって作っていく上でもう Playwright の面影なぞ全くないに等しかったので、もう好き勝手やろうと思って追加しました。

def self.usage
  puts <<~USAGE
    ○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●
       ____  __                          _       __    __
      / __ \/ /___ ___  ___      _______(_)___ _/ /_  / /_
     / / / / / __ `/ / / / | /| / / ___/ / __ `/ __ \/ __/
    / /_/ / / /_/ / /_/ /| |/ |/ / /  / / /_/ / / / / /_
    \____/_/\__,_/\__, / |__/|__/_/  /_/\__, /_/ /_/\__/
                /____/                /____/
    ○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●○●
    ┌─────────────────────────┐
    │   What is Olaywright?   ├────────────────────────────┐
    └┬────────────────────────┘                            │
     │  "Ole no" Playwright.                               │
     └─────────────────────────────────────────────────────┘
    ┌─────────────────────────┐
    │       Basic Usage       ├────────────────────────────┐
    └┬────────────────────────┘                            │
     │ # Setup                                             │
     │ browser = Olaywright.new                            │
     │ browser.domains in { page:, target: }               │
     │                                                     │
     │ # Exec Command ex) Target.createTarget              │
     │ # Notice: Use snake_case. You're in Ruby world.     │
     │ target.create_target.exec[url: 'https://google.com']│
     └─────────────────────────────────────────────────────┘
  USAGE
end

これは Monodraw というツールで描いてみました。

https://monodraw.helftone.com/

GitHub の人が ER 図をアスキーアートで書いてコミットメッセージに貼ることがあるという記事を見かけて、なるほどお!と思ったのをきっかけにインストールし全然違うきっかけで使ってしまいました。非常に便利なので今後も活躍しそうです。

資料作成

今回の資料、再掲部分も結構ありますが、合計で 78 枚となっています。自己最多量です。

カラーパレットとコンポーネント

作成は Figma で行なっていたのですが、調査とか実装が煮詰まった時にカラーパレットとコンポーネントを作成していたのが、直前になって非常に役に立ちました。

今回は背景色に真っ白を選択しました。今年の Kaigi on Rails のテーマカラーが大人ダークな色だったので、Youtube に載せた時に画面共有の枠が目立つように白にしたという感じです。

フォント

フォントに関しては今回は 3 種類をミックスしていて

  • Noto Sans JP
  • New Rodin Pro
  • Montserrat

を使用しています。これまではコーポレートロゴ ver2 とかラグランパンチとか奇抜なフォントを使っていたのですが、今回は時間も長いのとカンファレンスなので、読みやすいフォントにしました。

コード部分

コードを掲載する部分も結構あったのですが、これには Figma の Code Syntax Highlighter プラグインを使用しています。

https://www.figma.com/community/plugin/938793197191698232

生成されたものをちょっとだけ加工して、角丸を作ったりしています。

表紙について

あと、最後にほんとどうでもいいところなのですが、表紙に載っている丸は実は点字になっています。

発表練習

資料ができたのが、本番二日前の遅い時間だったので、通しでの練習を始めたのは発表の前日。そのため、1 日目は要所要所でしか発表が聞けず残念でした 🥲

今回すごい当たり前のことに気がついたんですが、30 分の登壇って通しで練習すると 30 分かかるんですよ…。なので 2 回練習すると 1 時間とぶ。 なので、セクションごとに目標時間決めて各セクション単位で練習し、早口で喋ればぎりぎり時間内におわることは計測できたので、2 日目は普通に楽しみました。

本番の時間オーバーだけは避けなければならなかったので、とにかく「とまるんじゃねえぞ」で早口でしゃべっていたらなんと巻きで終わりました。あらら笑

感想

今回 CFP 提出にあたり、業務経験値的には話せるものはあまりないのでソフトトークに踏み切るという選択肢もありましたが、職業エンジニアになった以上、今回は絶対技術系の話をメインにしたいという思いが強くありました。

「スパイス的にソフトトークを混ぜる感じにするといいかも」と CFP 考案中にレビューをいただいたので技術 9:ソフト 1 くらいの構成にしましたが、ちゃんと技術メインで話せたこと、話を構成していくその過程で業務とは直接関係ない知識をじっくり読み解いていき、それを自分の言葉に落とし込んでいく過程を締切つきでやれたのは非常によかったと思っています。

また CFP を出すこと自体も勉強になりましたし、カンファレンスで話すというのがどういう準備が必要なのかというのも今回知ることができました。

一方で Olaywright のコードを書く部分で技術力的な脆さを感じたり、資料デザインも使える引き出しの数に限界を感じている部分もあったので、そこは今後の課題としてやっていこうと思います。

今は仕事で Ruby を書いていないですが、今後も Ruby 関連のイベントには参加していきたいと思いますし、あとは今回カンファレンス登壇をさせていただいた経験を糧にフロントエンド関連のカンファレンスも登壇挑戦していきたいです!

謝辞

今回 CFP を出すのも初めてだったので、チーフオーガナイザーの大倉さんには Grow.rb を通じて 2 回相談にのっていただきました。1 回目の時にはまだテーマも決まっていない状態で、2 回目の時には技術トークとソフトトークをどれくらいの割合にするかの方向性についてなど相談させていただきました。

当初は自分の発表で 30 分も話すのは…と思っていたのですが、「話したいことがあるなら 30 分でもいいと思う」とアドバイスをいただけたおかげで今回の発表につなげることができました。

この 1 回目の Grow.rb ではうなすけさん、ima1zumi さんに実際に RubyKaigi に提出した CFP を見せていただき、どういうふうに CFP を書いていくべきかについて非常に参考にさせていただきました。おかげさまでしっかりと CFP を書ききることができました。

会社の開発チームの方も CFP 提出前にレビューいただきありがとうございました!


運営の皆さんも準備から当日までありがとうございました 🙇‍♂️ 来年はハイブリッド開催とのことで非常に楽しみです!ではでは!


Buy Me A Coffeeikuma-tにお恵みを!