CypressをWSL2上で動かすために必要な作業

リチャード 伊真岡 blog

CypressをWSL2上で動かすために必要な作業

Cypressはフロントエンドのend-to-endテストフレームワークの代表格です。ローカル開発環境で npx cypress open コマンドを実行すれば、Cypressはブラウザを自動で立ち上げて、高速で次々にテストを完了していきます。その軽快な動作は非常に心地よく、フロントエンドのテスト駆動開発を推進するのにピッタリなツールです。CIサーバー上ではブラウザを立ち上げることなくheadlessモードで動作します。

そんなCypressですが、困ったことにWindows Subsystem for Linux2(以下WSL2)上では、単純にインストール後 npx cypress open コマンドを打ち込むだけでは動いてくれません。ターミナルは何もせず待ち続けるだけで、テストを実行してくれないのです。

(この後何も起こりません、ブラウザが自動で立ち上がってほしいのですが) cypress not working on

この記事では、WSL2上でCypressを動かす方法を、その技術的背景とともにお伝えします。注意点として、ここで紹介する解決法はWSL2向けであって、WSL1ではまた別の問題が発生するようです。

WSL2上でCypressを動かせなくて苦労した人は私以外にもいるようで、Twitter検索でも何人か見かけました。WSL2で快適にフロントエンドのテスト駆動開発をしたい人に向けて、この記事が助けになれば幸いです。

良いニュース - いずれこの記事の手法は必要なくなる

手順を説明する前に、良いニュースを紹介します。将来はこの記事で紹介する面倒な手順は必要なくなります。こちらのMicrosoft公式ブログにあるように、Lniux GUIアプリケーションがネイティブにWSL上でサポートされる予定だからです。

それまでは、CypressをWSL2上で動したいときに、本記事の手法を試してください。

セットアップ手順

まずは忙しい人のために手順だけを列挙します。

  • Ubuntu/Debian上でCypressに必要な依存パッケージを apt-get insall
  • Cypressを npm install
  • SourceForgeからVcXsrvをインストール
  • xLaunchアプリケーションを立ち上げる
    • Disable Access controlをチェックすることに注意
    • Windows Defender ファイアウォールの警告でプライベートアクセスのみを許可
  • Windows Defender ファイアウォールで「受信の規則」のうち以下のルールを変更
    • プロファイル: パブリック
    • プロトコル: TCP
    • スコープ: プライベートIPアドレスのレンジ(例: 172.16.0.0/12)からのリモートアクセスを許可
  • WSL2側でDISPLAY環境変数を設定
    • export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2; exit;}'):0.0
  • xeyesコマンドでWSL2側からWindows側へのVcXsrvを経由した疎通確認
  • DBUSサーバーの立ち上げ
  • npx cypress openコマンドでブラウザを自動で立ち上げてテスト開始

セットアップ手順の詳細と背景

それではここから、上記で列挙した手順の詳細について説明します。

Cypressのinstallとverify

セットアップの最初の手順として、Cypress公式の手順に従ってインストールを終える必要があります。下記の公式インストール手順の、Ubuntu/Debianの部分に書いてあるaptコマンドを実行の後、npm install cypress --save-devしてください。

https://docs.cypress.io/guides/getting-started/installing-cypress

それが終わったら、npx cypress verify を行ってください。以下のように表示されたら成功です。

npx cypress verify

 ✔  Verified Cypress! /home/richardimaoka/.cache/Cypress/6.8.0/Cypress

一部のWSL2環境ではこの時点でcypress verifyがうまくいかないことがあるようです。以下のコマンドを実行して原因を探ってみてください。

DEBUG=cypress:* npx cypress verify

DEBUG=cypress:* の部分が何をしているのかというと、DEBUGレベルのログを出力する設定で、その説明はCypress公式ドキュメント - Print DEBUG logsにあります。

もしDEBUGログから原因がわかって npx cypress verify が成功すればよいのですが、結局原因がわからず npx cypress verify失敗し続ける場合、残念ながらこの先の手順には進めません

すでに多くの人が「cypress verifyが止まったまま何もしない」という報告を以下のGitHubイシューに寄せていて、原因がわからないケースがたくさんあります。

DEBUGログのメッセージやご自身の環境の詳細を、イシューのコメントとして残すとよいかもしれませんが、現状は決定的な解決策はないようです。困ったことにこのイシュー、タイトルがWSL2なのにWSL1の問題を報告する人や、CI環境であるJenkinsでの問題を報告する人もいて、なかなか混乱の様相を呈しています。

npx cypress verify が終わった後の手順は、下記の記事の手順がよくまとまっています。それでも、一部必要な手順が書かれていないのと、「なぜその手順が必要なのか?」という背景の説明が不足しているので、本記事ではそれらを補足します。

VcXsrvのインストール

WSL2から直接WindowsのWebブラウザを立ち上げることはできません。そこで必要になるのがXサーバーです。Unix/Linux系のOSではGUI環境を実現するために広く使われるXサーバーですが、WSL環境ではWindows側でXサーバーを起動します。

vcxsrv or not

Windowsで利用できるXサーバーにはVcXsrv, Cygwin/X, Xmingといくつか種類がありますが、その中で最近よく使われるのはVcXsrvなようです。

先ほど「手順がよくまとまっている」と紹介した記事「Nicky blogs - Using Graphical User Interfaces like Cypress' in WSL2」にもVcXsrvを使った例が紹介されていたので、本記事でもVcXsrvを利用します。

VcXsrvのインストール元は安全なのか?

ちなみに、SourceForgeがVcXsrvのダウンロード元と聞いて、次のニュースを覚えている人は不安を覚えるかもしれませんが、少なくとも当時の状況からは大きく改善されたようです。

こちらのニュースにSourceForgeの内部体制の改善が紹介されています。

Microsoft公式ブログからもSourceForgeからのVcXsrvのインストール手順が載っているので、少なくともMicrosoftがすすめるXサーバーの1つとは言えそうです。

VcXsrvの立ち上げにxLaunchアプリケーションを使う

VcXsrvがインストール出来たら、WindowsスタートメニューからXLaunchを立ち上げます。アプリケーション名がVcXsrvではなくXLaunchなことに注意してください。

xlaunch

立ち上げたら次のような画面が続くので、デフォルトの選択肢のまま次へ進み、

vcxsrv prompt 1

こちらの画面もデフォルトの選択肢のまま次へ進みましょう

vcxsrv prompt 2

この画面が表示されたら、デフォルトでチェックが外れているDisable access controlのチェックをつけてください。

vcxsrv prompt 3

Disable access controlのチェックが外れたままだと、後のステップで行うWSL2とVcXsrvの疎通確認でxeyesコマンドを走らせたときに以下のようなエラーが表示されます。かならずDisable access controlをチェックしてください。

> xeyes
Authorization required, but no authorization protocol specified
Error: Can't open display: 172.29.128.1:0.0

vcxsrv access control

xLaunch立ち上げ手順最後の画面はこちらです。そのまま完了を押しましょう。

vcxsrv prompt 4

xLaunchを立ち上げたらすぐに、以下のようなWindows Defenderファイアウォールの警告が表れるので、「プライベート ネットワーク (ホームネットワークや社内ネットワークなど)」のみを許可してください。

firewall prompt

このままではCypressに必要なWSL側との通信が出来なくなってしまうのですが、この後「Windows Defender FireWallの設定」というステップでより細かなWindows Defender ファイアウォールの制御を行います。

Ctrl+Shift+EscキーでWindowsタスクマネージャを立ち上げ、VcXsrvのプロセスを確認出来たらこのステップは完了です。

task manager

xeyesによるVcXsrv疎通確認「..この時点ではまだ繋がりません

このステップでは、VcXsrvとWSL2側での通信がうまくいっているか疎通確認を行いましょう。

疎通確認はxeyesコマンドを使います。以下のように非常に単純な目玉のGUIアプリケーションを立ち上げるコマンドで、Xサーバーの動作確認として利用できます。… が、この時点ではまだxeyesは立ち上がらないはずです

xeyes

xeyesで疎通確認するためには、次に紹介するDISPLAY環境変数の設定と、Windows Defenderファイアウォールの設定が必要になります。

DISPLAY環境変数の設定

VcXsrvとWSL2で通信を行うために、DISPLAY環境変数を変える必要があります。DISPLAY環境変数によってLinuxはどのXサーバーを利用するか指定するからです。

vcxsrv and DISPLAY environment variable

以下のコマンドでDISPLAY環境変数を変えましょう。

# set DISPLAY variable to the IP automatically assigned to WSL2
export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2; exit;}'):0.0
echo $DISPLAY
172.17.224.1:0.0

ここで使った/etc/resolv.confはWSLが自動で更新するファイルで、自身のプライベートIPアドレスが保存されています。

> cat /etc/resolv.conf
# This file was automatically generated by WSL. 
# To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 172.17.224.1

Windows Defender FireWallの設定

ここまでWindows側とWSL2側での疎通のために、VcXsrvの立ち上げとそのアクセスコントロール、そしてWSL2側でのDISPLAY環境変数の設定を行いました。 それらに加えてWindows Defender ファアウォールの設定を行うことで、ようやく疎通確認ができます。

vcxsrv-firewall

先ほど「VcXsrvの立ち上げにxLaunchアプリケーションを使う」の最後の方で、Windows Defenderファイアウォールのパブリック・ネットワークからのアクセスを全て禁止したので、これから先の設定でその一部を許可します。

firewall prompt

Microsoftの公式ドキュメント、Windows Defender ファイアウォールを介してアプリを許可する際のリスクにあるように、Windows Defender ファイアウォールはリスクが伴うので、正しい設定によって最小限のリスクにとどめましょう。

Windows Defender ファイアウォールを介してアプリを許可するには、2 つの方法があります。 どちらも危険です。

  • 許可されているアプリの一覧にアプリを追加する (リスクは低くなります)。
  • ポートを開く (リスクは高くなります)。

本記事では上記2択のうち、よりリスクの少ない「許可されているアプリの一覧にアプリを追加する」に従います。それではWindowsのスタートメニューからWindows Defender ファイアウォールを立ち上げます。

start-menu-firewall

下記の画面が現れるので詳細設定をクリックし、

firewall-settings

Vc-Xsrv windows serverと表示されているルールをみつけてください。4つのルールがあるはずです。

名前 プロファイル プロトコル 備考
VcXsrv windows xservr パブリック TCP ここを変更する
VcXsrv windows xservr パブリック UDP
VcXsrv windows xservr プライベート TCP
VcXsrv windows xservr プライベート UDP

firewall-list

プロファイル = パブリック、プロトコル = TCPとなっているVcXsrvのルールを開いたら、「スコープ」タブから プライベートIPアドレスのレンジ(例: 172.16.0.0/12)からのリモートアクセスを許可してください

private-ip-range

この設定はこちらのGitHubイシュー上のコメントにならいました。 これにより、プライベート・IPレンジからのアクセスのみVcXsrvへのアクセスが許可されました。ご自身のネットワークに設定を調べ、より狭い範囲のプライベートIPアドレスのレンジを設定すれば、さらに安全になるでしょう。

xeyesによるVcXsrv疎通確認ふたたび

これでようやくxeyesによる疎通確認ができるはずです!

xeyes

Cypressの立ち上げ

xeyesでの疎通確認が成功していれば、 npx cypress open コマンドでブラウザが立ち上がって表示されるはずです。下記の公式ドキュメントにそって簡単なテストを書いてみましょう。

cypress-open

DBUSの立ち上げ

先の手順で npx cypress open を実行した時、以下のようなエラーが出るかもしれません。

[9462:0411/161540.623026:ERROR:bus.cc(393)] 
Failed to connect to the bus: Failed to connect to 
socket /var/run/dbus/system_bus_socket: No such file or directory

その場合、dbusというプロセスが立ち上がっていないことが原因です。以下のコマンドでまずはdbusをインストールしましょう。

sudo apt-get install dbus

次に次のファイルが存在するかどうか調べてください。ちなみに/etc/init.dを使っているのは、WSL2はデフォルトではsystemdが使えないからです。

ls -la /etc/init.d/dbus

存在しなかったら、下記を展開して /etc/init.d/dbus に手動でペーストしてください。

/etc/init.d.dbusの中身を展開する
#!/bin/sh
### BEGIN INIT INFO
# Provides:          dbus
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:     2 3 4 5
# Default-Stop:
# Short-Description: D-Bus systemwide message bus
# Description:       D-Bus is a simple interprocess messaging system, used
#                    for sending messages between applications.
### END INIT INFO
# -*- coding: utf-8 -*-
# Debian init.d script for D-BUS
# Copyright © 2003 Colin Walters <walters@debian.org>
# Copyright © 2005 Sjoerd Simons <sjoerd@debian.org>

set -e

DAEMON=/usr/bin/dbus-daemon
UUIDGEN=/usr/bin/dbus-uuidgen
UUIDGEN_OPTS=--ensure
NAME=dbus
DAEMONUSER=messagebus
PIDDIR=/var/run/dbus
PIDFILE=$PIDDIR/pid
DESC="system message bus"

test -x $DAEMON || exit 0

. /lib/lsb/init-functions

# Source defaults file; edit that file to configure this script.
PARAMS=""
if [ -e /etc/default/dbus ]; then
  . /etc/default/dbus
fi

create_machineid() {
  # Create machine-id file
  if [ -x $UUIDGEN ]; then
    $UUIDGEN $UUIDGEN_OPTS
  fi
}

start_it_up()
{
  if [ ! -d $PIDDIR ]; then
    mkdir -p $PIDDIR
    chown $DAEMONUSER $PIDDIR
    chgrp $DAEMONUSER $PIDDIR
  fi

  if ! mountpoint -q /proc/ ; then
    log_failure_msg "Can't start $DESC - /proc is not mounted"
    return
  fi

  if [ -e $PIDFILE ]; then
    if $0 status > /dev/null ; then
      log_success_msg "$DESC already started; not starting."
      return
    else
      log_success_msg "Removing stale PID file $PIDFILE."
      rm -f $PIDFILE
    fi
  fi

  create_machineid

  # Force libnss-systemd to avoid trying to communicate via D-Bus, which
  # is never going to work well from within dbus-daemon. systemd
  # special-cases this internally, but we might need to do the same when
  # booting with sysvinit if libnss-systemd is still installed.
  # (Workaround for #940971)
  export SYSTEMD_NSS_BYPASS_BUS=1

  log_daemon_msg "Starting $DESC" "$NAME"
  start-stop-daemon --start --quiet --pidfile $PIDFILE \
    --exec $DAEMON -- --system $PARAMS
  log_end_msg $?
}

shut_it_down()
{
  log_daemon_msg "Stopping $DESC" "$NAME"
  start-stop-daemon --stop --retry 5 --quiet --oknodo --pidfile $PIDFILE \
    --user $DAEMONUSER
  # We no longer include these arguments so that start-stop-daemon
  # can do its job even given that we may have been upgraded.
  # We rely on the pidfile being sanely managed
  # --exec $DAEMON -- --system $PARAMS
  log_end_msg $?
  rm -f $PIDFILE
}

reload_it()
{
  create_machineid
  log_action_begin_msg "Reloading $DESC config"
  dbus-send --print-reply --system --type=method_call \
            --dest=org.freedesktop.DBus \
            / org.freedesktop.DBus.ReloadConfig > /dev/null
  # hopefully this is enough time for dbus to reload it's config file.
  log_action_end_msg $?
}

case "$1" in
  start)
    start_it_up
  ;;
  stop)
    shut_it_down
  ;;
  reload|force-reload)
    reload_it
  ;;
  restart)
    shut_it_down
    start_it_up
  ;;
  status)
    status_of_proc -p $PIDFILE $DAEMON $NAME && exit 0 || exit $?
    ;;
  *)
    echo "Usage: /etc/init.d/$NAME {start|stop|reload|restart|force-reload|status}" >&2
    exit 2
  ;;
esac

以下のコマンドでdbusを立ち上げれば、先ほどのエラーは消えてなくなるはずです。

sudo /etc/init.d/dbus start

それでも残るエラー?

私の環境ではここまでの手順でcypressは動くようになったのですが、どうしても以下のエラーは消すことができませんでした。エラーが残るのは気持ち悪いのですが、動作はしているので今は仕方ないものとして無視しています。

[11210:0411/163510.366298:ERROR:bus.cc(393)]
Failed to connect to the bus: Could not parse server address:
Unknown address type (examples of valid types are "tcp" and on UNIX "unix")
[11210:0411/163510.366996:ERROR:bus.cc(393)] 
Failed to connect to the bus: Could not parse server address:
Unknown address type (examples of valid types are "tcp" and on UNIX "unix")

こちらのエラーが気になる、もしくは別のエラーが出ていてどうしても解決したい、という人はDEBUGログを表示して調査してみるとわかるかもしれません。

DEBUG=cypress:* npx cypress open

まとめ

かなり手順の説明が長くなってしまった上、最後に一つエラーメッセージを解決できなかったのは残念ですが、最初は動かすことが出来なかったWSL2上でのCypressを動作させることはできました。 この記事がWSL2上での快適なCypressライフ、フロントエンドテスト駆動開発の助けになれば幸いです。