Slide 1

Slide 1 text

なぜ E2E テストがたまに落ちるのか @mtsmfm Fumiaki Matsushima Rails Developers Meetup 2018 Day 3 Extreme

Slide 2

Slide 2 text

➔ Quipper Ltd ➔ Rubyと麻雀とDbDが好き ➔ 西日暮里.rb主催 ➔ GraphQL Tokyo 主催 @mtsmfm.inspect

Slide 3

Slide 3 text

https://studysapuri.jp/

Slide 4

Slide 4 text

https://techplay.jp/event/680406

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

https://www.careertrek.com/jobs/view/614827/com_detail

Slide 7

Slide 7 text

なぜ E2E テストがたまに落ちるのか @mtsmfm Fumiaki Matsushima Rails Developers Meetup 2018 Day 3 Extreme

Slide 8

Slide 8 text

➔ ユーザの操作を再現して行うテスト ◆ ここでは実ブラウザ、特に Chrome を用いるものにつ いて ➔ Rails System Test ➔ Feature Spec / System Spec ➔ Cucumber / Turnip E2E テスト

Slide 9

Slide 9 text

https://github.com/rails/rails/releases/v5.1.0 もはや1年以上前

Slide 10

Slide 10 text

E2E テスト 書いてますか

Slide 11

Slide 11 text

➔ E2E テストを書いている ◆ プロジェクトで既に導入されていた ◆ どうやって動いているかはあまり知らない ➔ たまに落ちて困っている 想定読者

Slide 12

Slide 12 text

Agenda | 01 Capybara の visit の向こう側 02 落ちる原因と対策 03 たまに落ちるテストと向き合う

Slide 13

Slide 13 text

01 Capybara の visit の向こう側

Slide 14

Slide 14 text

visit "/"

Slide 15

Slide 15 text

Minitest Capybara Rails Chrome visit “/” localhost:XXXX で起動 起動 localhost:XXXX へ GET localhost:XXXX

Slide 16

Slide 16 text

Minitest Capybara Rails Chrome visit “/” localhost:XXXX で起動 起動 localhost:XXXX へ GET localhost:XXXX

Slide 17

Slide 17 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/dsl.rb#L49-L54

Slide 18

Slide 18 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara.rb#L299

Slide 19

Slide 19 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/session.rb#L89

Slide 20

Slide 20 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/server.rb#L68-L70

Slide 21

Slide 21 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara.rb#L460-L464

Slide 22

Slide 22 text

➔ visit 時に Rails の実サーバが test モードで起動する ◆ Initializers の類の stub タイミングには注意 ➔ 同一プロセスで動いている ◆ そのおかげでテスト中の stub などが効く ポイント

Slide 23

Slide 23 text

Minitest Capybara Rails Chrome visit “/” localhost:XXXX で起動 起動 localhost:XXXX へ GET localhost:XXXX

Slide 24

Slide 24 text

Capybara selenium- webdriver 初期化 起動 Chrome Driver 起動 Chrome

Slide 25

Slide 25 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/session.rb#L269

Slide 26

Slide 26 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara.rb#L492-L494

Slide 27

Slide 27 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/selenium/driver.rb#L59

Slide 28

Slide 28 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/selenium/driver.rb#L31

Slide 29

Slide 29 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/chro me/driver.rb#L43

Slide 30

Slide 30 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/chro me/service.rb#L27

Slide 31

Slide 31 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/chro me/driver.rb#L48

Slide 32

Slide 32 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/bridge.rb#L53

Slide 33

Slide 33 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/bridge.rb#L97

Slide 34

Slide 34 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/bridge.rb#L164

Slide 35

Slide 35 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/bridge.rb#L27

Slide 36

Slide 36 text

https://github.com/chromium/chromium/tree/69.0.3489.2/chrome/test/chromedriver

Slide 37

Slide 37 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/server/htt p_handler.cc#L91

Slide 38

Slide 38 text

https://github.com/chromium/chromium/blob/69.0.3491.1/chrome/test/chromedriver/session_co mmands.cc#L341

Slide 39

Slide 39 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/session_co mmands.cc

Slide 40

Slide 40 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/chrome_la uncher.cc

Slide 41

Slide 41 text

Minitest Capybara Rails Chrome visit “/” localhost:XXXX で起動 起動 localhost:XXXX へ GET localhost:XXXX

Slide 42

Slide 42 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/selenium/driver.rb#L59

Slide 43

Slide 43 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/com mon/navigation.rb#L30

Slide 44

Slide 44 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/oss/bridge.rb#L50

Slide 45

Slide 45 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/oss/commands.rb#L39

Slide 46

Slide 46 text

➔ Capybara は selenium-webdriver 越しの ChromeDriver 越しに Chrome を起動している ➔ ChromeDriver とは REST API でやりとりしている ◆ WebDriver の仕様がある https://w3c.github.io/webdriver/ ➔ Capybara の DSL は driver に処理を移譲している ◆ driver を差し替えられるようにしている ポイント

Slide 47

Slide 47 text

Agenda | 01 Capybara の visit の向こう側 02 落ちる原因と対策 03 たまに落ちるテストと向き合う

Slide 48

Slide 48 text

02 落ちる原因と対策

Slide 49

Slide 49 text

クリックに 失敗する

Slide 50

Slide 50 text

➔ 失敗時のスクショを見ると、遷移ができていない ➔ 90% くらいこれ クリックに失敗する

Slide 51

Slide 51 text

➔ クリック処理は2段階に分かれている ◆ 1. 要素の座標を求める ◆ 2. 座標をクリックする ➔ 1 と 2 の間に要素の座標が変わると見当違いの場所を クリックすることになる クリックに失敗する (原因)

Slide 52

Slide 52 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/selenium/node.rb#L87

Slide 53

Slide 53 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/com mon/element.rb#L72

Slide 54

Slide 54 text

https://github.com/SeleniumHQ/selenium/blob/selenium-3.13.0/rb/lib/selenium/webdriver/remo te/oss/commands.rb#L118

Slide 55

Slide 55 text

https://github.com/chromium/chromium/blob/18bace42984d03e65cc2ebb76b8e7cd0ca2c99db/ch rome/test/chromedriver/server/http_handler.cc#L199-L201

Slide 56

Slide 56 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/element_c ommands.cc

Slide 57

Slide 57 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/element_c ommands.cc

Slide 58

Slide 58 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/element_c ommands.cc

Slide 59

Slide 59 text

http://chromedriver.chromium.org/help/clicking-issues

Slide 60

Slide 60 text

https://w3c.github.io/webdriver/#element-click

Slide 61

Slide 61 text

➔ 要素の座標が変わりやすいもの ◆ アニメーション ● 想像がつきやすいので自前で待つ処理が入って いることが多い ◆ 画像読み込み完了時にガクッとなるような CSS に なっている ● 見落としがち クリックに失敗する (原因)

Slide 62

Slide 62 text

➔ 要素の座標が変わりやすいもの ◆ アニメーション ● 想像がつきやすいので自前で待つ処理が入って いることが多い ◆ 画像読み込み完了時にガクッとなるような CSS に なっている ● 見落としがち クリックに失敗する (原因)

Slide 63

Slide 63 text

https://github.com/chromium/chromium/blob/69.0.3489.2/chrome/test/chromedriver/element_c ommands.cc

Slide 64

Slide 64 text

https://github.com/teamcapybara/capybara/pull/2042/files

Slide 65

Slide 65 text

➔ 要素の座標が変わりやすいもの ◆ アニメーション ● 想像がつきやすいので自前で待つ処理が入って いることが多い ◆ 画像読み込み完了時にガクッとなるような CSS に なっている ● 見落としがち クリックに失敗する (原因)

Slide 66

Slide 66 text

つまりユーザも クリックミスるかも

Slide 67

Slide 67 text

とりあえずの回避

Slide 68

Slide 68 text

def wait_for_image_loading Timeout.timeout(Capybara.default_wait_time) do sleep 0.5 until evaluate_script(<<~JS) Array.prototype.every.call( document.querySelectorAll("img"), (e) => e.complete ) JS end end

Slide 69

Slide 69 text

➔ ユーザが読み込みが終わったと判断するのと同じ方法で 待つ ➔ テスト中は !important とかでアニメーション無効にしてみ る ➔ ユーザが触る時にも要素の位置がずれ得るスタイルに なっていないか確認する ◆ 画像の読み込みを待ってみる クリックに失敗する (対策)

Slide 70

Slide 70 text

Assertion 後に サーバーエラー

Slide 71

Slide 71 text

➔ テスト本体の assertion は通っているが、404 で落ちてい る ◆ DB がまっさらだと起きそうなエラー内容 Assertion 後にサーバーエラー

Slide 72

Slide 72 text

➔ DBクリーンアップ処理が終わったあとにブラウザから ポーリングなどによりリクエストが飛びサーバーエラー (404 とか) Assertion 後にサーバーエラー (原因)

Slide 73

Slide 73 text

BEGIN TRANSACTION Minitest DB Rails Chrome SELECT GET /users/1 ROLLBACK create(:user) visit ブラウザ終了

Slide 74

Slide 74 text

Chrome GET /users/1 (ポーリング) BEGIN TRANSACTION Minitest DB Rails SELECT GET /users/1 ROLLBACK create(:user) visit SELECT ブラウザ終了

Slide 75

Slide 75 text

Chrome GET /users/1 (ポーリング) BEGIN TRANSACTION Minitest DB Rails SELECT GET /users/1 ROLLBACK create(:user) visit SELECT ブラウザ終了

Slide 76

Slide 76 text

https://github.com/mtsmfm/rails/commit/1a197e4

Slide 77

Slide 77 text

GET /users/1 (ポーリング) BEGIN TRANSACTION Minitest DB Rails SELECT GET /users/1 ROLLBACK create(:user) visit SELECT ブラウザ終了 Chrome

Slide 78

Slide 78 text

https://github.com/rails/rails/pull/28223/files

Slide 79

Slide 79 text

➔ ブラウザの終了処理をDBクリーンアップより先にやらな いといけない ◆ Rails System Test はリリース前に直した ➔ 自分で database-cleaner など teardown 処理を入れて いる場合には、順番に注意 Assertion 後にサーバーエラー (対策)

Slide 80

Slide 80 text

何もしてないのに ログイン済み

Slide 81

Slide 81 text

➔ ログイン処理前なのにログインが済んでいることになって いる ➔ 直前のテストのセッションが残っている 何もしてないのにログイン済み

Slide 82

Slide 82 text

➔ ブラウザのクリーンアップは Cookie 削除してから blank ページを開く ➔ Cookie 削除前に開始したリクエストが、Cookie 削除後 に完了すると Cookie が消されない 何もしてないのにログイン済み (原因)

Slide 83

Slide 83 text

Minitest Chrome Cookie 削除 teardown Capybar a about:blank へ

Slide 84

Slide 84 text

Set-Cookie Minitest Chrome Rails GET Cookie 削除 teardown Capybar a about:blank へ

Slide 85

Slide 85 text

https://github.com/teamcapybara/capybara/blob/3.3.1/lib/capybara/selenium/driver.rb#L127

Slide 86

Slide 86 text

➔ blank ページにいる状態で全 cookie を消す 何もしてないのにログイン済み (対策)

Slide 87

Slide 87 text

そんな API はない

Slide 88

Slide 88 text

➔ 現在開いているドメイン以外の Cookie を消す方法がな い... ◆ モンキーパッチでお茶を濁しているが... 何もしてないのにログイン済み (対策) Capybara::Selenium::Driver.prepend(Module.new do def reset! quit end end)

Slide 89

Slide 89 text

Agenda | 01 Capybara の visit の向こう側 02 落ちる原因と対策 03 たまに落ちるテストと向き合う

Slide 90

Slide 90 text

03 たまに落ちるテストと向き合う

Slide 91

Slide 91 text

➔ 直すのめちゃくちゃめんどくさい ◆ デバッグが大変 ◆ 再現しない ➔ テストの書き方の問題やライブラリの問題なだけで、本番 影響がないことが多い ◆ いっそ無視 直しても費用対効果がよくない

Slide 92

Slide 92 text

➔ rspec-retry ◆ Retry 時のクリーンアップ処理まで考慮しきれず、 ちゃんと Retry できない場合も ➔ 既出かどうかのデータを集めて無視する? 無視する方法

Slide 93

Slide 93 text

https://ai.google/research/pubs/pub46593

Slide 94

Slide 94 text

➔ 420万あるテストの結果を集積しまくっている ➔ 16%はたまに落ちるテスト ➔ テスト結果が変化するとき、84%は不安定なテストによる もの Advances in Continuous Integration Testing at Google

Slide 95

Slide 95 text

➔ 過去に落ちたテスト一覧とか出してくれる ➔ ちょっと情報が足りない Circle CI

Slide 96

Slide 96 text

➔ テスト結果集積基盤を自前で作る? ◆ Quipper にはまだない ◆ 取り組みを教えて欲しい ➔ OSS 化の余地がありそう Future work

Slide 97

Slide 97 text

➔ Capybara とか DB とか ChromeDriver とかがどう絡み 合ってるか知ってると、何かおかしいときにアタリがつくの で覚えて損はない ◆ 別言語でも似たような構成にはなるはず ➔ だいたいの場合クリックに失敗してる ◆ わかっててもミスる ➔ たまに落ちるやつをうまいこと無視する仕組みが欲しい まとめ