経緯
Capybara + Turnip を書く時、だいたいこういう Feature になると思います
Feature: GitHub を巡る Background: When "https://github.com/login" にアクセスする And ユーザ名に "gongo" と入力する And パスワードに "gongo" と入力する And "Sign in" ボタンをクリックする Scenario: トップ画面 Then アクティビティが表示されていること And 自分のリポジトリ一覧が表示されていること Scenario: Explorer When "Explorer" をクリックする Then "Browse interesting projects" と表示されていること Scenario: Blog When "Blog" をクリックする Then "The GitHub Blog" と表示されていること Scenario: Gist When "Gist" をクリックする Then 自分の Gist が表示されていること
なぜ Background を使ってシナリオの前に毎回ログインさせているのかというと、 Capybara + Turnip で動かすと Scenario 終了後に Capybara.reset_sessions! が走るから です。
capybara/lib/capybara/rspec.rb#L18-L23
これはこれで特に問題ない動きかなと思いますが、シナリオ毎にログインするの無駄じゃね? と思うことが稀によくあります。
願望
ログイン処理、そこそこ重い部類に入るんじゃないかと思ってて、シナリオの数が増えれば増えるほどになかなか無視できない実行時間を生み出していきます。もし Capybara.reset_sessions!
が毎シナリオで走らなかった場合、つまり「一度ログインしたらそのまま」という状況が実現できれば、さきほどの Feature は以下のように直せるのではないでしょうか
Feature: GitHub を巡る Scenario: ログインしてトップ画面を表示する When "https://github.com/login" にアクセスする And ユーザ名に "gongo" と入力する And パスワードに "gongo" と入力する And "Sign in" ボタンをクリックする Then アクティビティが表示されていること And 自分のリポジトリ一覧が表示されていること Scenario: Explorer When "Explorer" をクリックする Then "Browse interesting projects" と表示されていること Scenario: Blog When "Blog" をクリックする Then "The GitHub Blog" と表示されていること Scenario: Gist When "Gist" をクリックする Then 自分の Gist が表示されていること
ログイン処理は最初のみ。それ以降のシナリオは続けて実行しても問題ないためそのままに。 これで単純にログイン処理3回分の実行時間を削減できました。
もちろん全シナリオに渡ってこのような書き方をできるわけではなく、「このシナリオに入ったらログインやりなおしたいな」とか「ここからはセッションリセットしても大丈夫だ」みたいなのが出てくるわけです。それも制御したい!!
解決策(ボツ)
RSpec.configuration.after に登録 されてしまってからでは遅いので、
まずは capybara/rspec
を require しない形を考えます。簡単に言えば上記コードの Capybara.reset_sessions!
以外を helper か何かに書いてしまえばいいのです。
ですがそれはめんどくさい。今後 capybara/rspec.rb
に修正が加えられることになればそれに追従していかなければなりませんし、なにより turnip/capybara.rb が capybara/rspec
を require してしまっています。
じゃあ今度は turnip/capybara を require しないで〜とか考えると、また気をつけないといけないコードが増えます。それはもうだめだ。
解決策(本命)
じゃあモンキーパッチですよね。Capybara.reset_session!
だけを対象に、最低限に済ませます。
module Capybara class << self alias_method :original_reset_sessions!, :reset_sessions! def reset_sessions! # Noop end def reset_sessions_of_truth! original_reset_sessions! end end end
これでひとまず各シナリオ終了後に呼ばれる reset_sessions!
では何も起きなくなります。
次に Feature 開始時に reset_session! を呼ぶ という RSpec.configuration.before を設定します。
RSpec.configure do |config| config.before :all, type: :feature do Capybara.reset_sessions_of_truth! end end
Turnip の設定により、Feature (実体は RSpec の ExampleGroup) が持つ metadata に :feature が設定されているので (※) 、type: :feature
と記述することで Feature に対する before/after が記述できます。
上記の場合だと before :all, type: :feature
なので、Feature が持つ Scenario (describe に対する it みたいな位置付け) の最初の1回だけセッションリセットを行う、ということになります。
せっかくなので「このシナリオに入ったらログインやりなおしたいな」も実現しましょう。
RSpec.configure do |config| config.before :each, type: :feature, reset_session: true do Capybara.reset_sessions_of_truth! end end
Turnip では Feature や Scenario に対してタグ(@foo
)を設定すると、その項目の metadata に foo: true
がセットされます。
つまり :each, type: :feature, reset_session: true
と書くことで @reset_session タグがつけられたシナリオが開始される前
という処理が記述できます。
結果
各 Feature の最初の Scenario の時だけ Capybara.reset_session!
が処理されるようになった。
もし「このシナリオではセッションリセットしたいわ」という記述をしたい場合には
Feature: GitHub を巡る Scenario: ログインしてトップ画面を表示する When "https://github.com/login" にアクセスする And ユーザ名に "gongo" と入力する And パスワードに "gongo" と入力する And "Sign in" ボタンをクリックする Then アクティビティが表示されていること And 自分のリポジトリ一覧が表示されていること Scenario: まだログインしてる状態 When "https://github.com" にアクセスする Then アクティビティが表示されていること @reset_session Scenario: ログアウト状態 When "https://github.com" にアクセスする Then "Sign up for GitHub" というボタンが表示されていること
といった使い方ができるようになりました。
まとめ
Turnip に限ったことではなく、Capybara + RSpec でも似たようなことはできるようになると思います。
※ 昔、これと同じようなの実現したぜ紹介記事どっかで見た気もしますが、せっかくなので挙げました。車輪は回る。