CircleCI上でCypress起動時に `Missing X server or $DISPLAY` で落ちる原因と回避策
CircleCIで、CypressによるE2Eテストを実行しようとした時に、5~10%の頻度でMissing X server or $DISPLAY というエラーでflakyに落ちる問題に悩んでいました。
同様のエラーに悩んでいる方がこの記事を読んでいると仮定して、結論からお伝えします。
もし、Dockerイメージとして、cimg/node:*-browsers を利用し、Cypress CircleCI Orb ではない形で直接実行しているなら、以下の回避策を適用できます。
▼ 上記Issueのサンプルコードより抜粋
jobs:
test:
docker:
- image: cimg/node:24.14.0-browsers
environment:
DISPLAY: '' # ここがポイント!
steps:
- checkout
- node/install-packages
- browser-tools/install_chrome
- run:
command: npx cypress run --browser
この回避策を適用してから1000回以上、CircleCI上でCypressを起動していますが、今のところ一度も Missing X server or $DISPLAY が出ていません。
re-runによるコストをざっくり計算すると、月に15,000円以上を節約することができました。コスト削減だけでなく、E2Eがflakyに落ちることがなくなり、開発者体験・開発速度の向上に寄与しました。
回避策を提供してくださった MikeMcC399 さんに心から感謝しています。
ここまででこの記事の目的の大半は達成できました。ここからは、なぜこのエラーが起きて、なぜこの回避策が有効なのか、という点を解説します。
Missing X server or $DISPLAY となる原因
まず、CypressをLinux上(CircleCI上での実行ではDockerコンテナ)で動かす場合、X11サーバーを必要とします。
以下の通り、実行時にX11サーバーがない場合、Cypressは自前のX11サーバーを起動します。
When running on Linux, Cypress needs an X11 server; otherwise it spawns its own X11 server during the test run.
ref: https://docs.cypress.io/app/continuous-integration/overview#Xvfb
cimg/node:*-browsers ではX11サーバーを用意してくれているため、Cypressは自前のサーバーを起動せず、用意されているものを参照します。
また、CypressインスタンスがX11サーバーの起動ポートを参照するための、DISPLAY 環境変数も設定されています。
この時、cimg/node:*-browsers で用意したX11サーバーの起動が完了していない状態でCypressを実行すると、DISPLAY に指定した :99 ポートをみにいってもX11サーバーが存在せず、Missing X server or $DISPLAY になっていたと考えられます。
先ほどのDockerfileから分かる通り、起動完了まで待つためのwait処理が入っていたりと対策が施されていますが、5~10%の確率で起動未完了のままとなるようです。
なぜ DISPLAY: '' の回避策が有効か
前項で述べた以下が回答のほとんどです。
実行時にX11サーバーがない場合、Cypressは自前のX11サーバーを起動します。
DISPLAY: '' とすることで、Cypressは cimg/node:*-browsers で起動されたX11サーバーを見にいかず、自前のX11サーバーを起動します。
CypressのX11サーバーの起動プロセスはどうやら安定しているようで、前述の通り、1000回以上実行して一度も接続に失敗していません。
MikeMcC399 さんのこちらのレス によると、Cypress側では起動時に30秒のタイムアウトを設定しており、以来、起動完了が安定したとのことでした。
ただ、複数のCypressインスタンスで並列実行する場合は自前のX11サーバー起動でも接続エラーになることがあるらしく、以下にその回避策が記載されています。
cimg/node:*-browsers を使用し、かつそのjob内でCypressを並列実行する場合は、Xvfb を :99 以外のポートで起動し、DISPLAY にそのポートを割り当てることで同様に回避できると考えられます。
根本解決策について
ここまで、CircleCI上で cimg/node:*-browsers を使い、cypress run で直接実行する場合に生じうる、Missing X server or $DISPLAY エラーの回避策をご紹介しました。
Cypress CircleCI Orb を使った実行では、この回避策は適用できないようなのでご注意ください。
upstream側(cimg/node:*-browsers)の根本解決策を自分なりに調査してみたところ、 -displayfd オプションを使ってXvfb が ready になるまで待つ修正を入れることで、cimg-node 側で確実にX11サーバーを立ち上げることができるかもしれないと考えています。
▼投稿したレス
Cypress CircleCI Orb の default executor は以下のように cimg/node:*-browsers をDockerイメージとして参照しているため、上記の解決策が有効であれば、Cypress CircleCI Orb による実行でも安定してX11サーバーに接続できるようになるのではと期待しています。
もしメンテナの方が他の対応で忙しそうな状況なら、自分の方でPRを上げてみようと思っています。
以上、同じような問題で悩んでいた方の参考になれば幸いです。
Discussion