LoginSignup
1
0

More than 3 years have passed since last update.

Quickstromでwebフロントエンドの仕様を書いてproperty basedなブラウザテストをする。

Last updated at Posted at 2020-09-13

Quickstrom

新しいプロジェクトの仕様とテストを何で書こうかなと色々探していたら辿り着いたのですが, ブラウザテストでPBTができるというのが面白そうだったので試してみました。
「ボタンをランダムに数百回押した時に意図しない挙動にならないだろうか」などをテストできます。

仕様自体はPureScriptで記述しますが, TLA+に強い影響を受けているようです。

公式のチュートリアル( https://docs.quickstrom.io/tutorials/first.html )があり,とてもわかりやすいので真面目にやるひとはそちらをお勧めしますが,今回はチュートリアルとは違うケースを考えてやってみます。

前提

こんな感じの複数選択可能なアイテムを作ろうと思います。
また,何も選択されていない場合は警告メッセージを表示します。

HTML

まずはレイアウトです。このようなHTMLを用意しました。

index.html
<!doctype html>
<html>
<head>
    <style>
        #warn { /* 略 */ }
        .choice { /* 略 */ }
        .selected { background-color: #ffcc33; }
    </style>
</head>
<body>
    <div class="choice">A</div>
    <div class="choice">B</div>
    <div class="choice">C</div>
    <div class="choice">D</div>
    <div id="warn"></div>
    <script>
         /* ここに選択の処理を書いていく */
    </script>
</body>
</html>

Quickstromで仕様を書く

モジュールの宣言とインポート

Choice.spec.purs
module Choice where

import Quickstrom
import Data.Tuple (Tuple(..))
import Data.Maybe (Maybe(..), maybe)
import Data.Array (length)

テストを開始するタイミングを宣言する

readyWhen にセレクターを書いておくと,その対象がreadyになったタイミングでテストが開始されます。
今回は .choice をクリックしたいので次のようにします。

Choice.spec.purs
readyWhen :: Selector
readyWhen = ".choice"

Quickstromが行うActionを宣言する

ActionsはQuickstromがブラウザに対して行ってくれる操作たちです。
デフォルトで clicks という Actions が提供されていますが, これはDOMの buttoninput[type=submit] などが対象で div はクリックされないので自前で定義してみます。

Choice.spec.purs
actions :: Actions
actions = [ Tuple 1 (Click ".choice") ]

Tupleの最初の要素に指定している1はそのアクションが全体に占める比率です。
複数のアクションを指定する際には 2:1:1 のようにどのアクションを多めにするかなどを指定できます。

テストでチェックするデータを定義しておく

Choice.spec.purs
selected :: Array String
selected = map _.textContent (queryAll ".choice.selected" { textContent } )

warning :: Maybe String
warning = map _.textContent (queryOne "#warn" {textContent} )

複数の該当するDOMのプロパティをリストで返す queryAll とユニークなDOMのプロパティをMaybeで返すqueryOne があります。
また,取得できるプロパティについては textContent, value, disabled, checked が提供されています。
(記事を書いた時点では, property が外部に公開されていないのでそれ以外を自分で定義するのは現時点ではできなさそう)

今回は選択されたアイテムのリストを取得する selected と, 選択されていない状態で表示する警告メッセージを取得する warning を定義しています。

UIが満たすべき制約を書く

ここがメインです。
proposition 関数に画面が満たすべき制約を記述します。
コメントを入れていますが, 現状日本語が入っているとエラーになるようですので, 実際にはコメントを削除しています。

Choice.spec.purs
proposition :: Boolean
proposition =
     let
         -- 初期状態。"A"が選択されていて, 且つ警告メッセージは表示されていない
         initial =
             selected == ["A"]
             && warning == Just ""

         -- 選択可能なアイテムは最大4つである
         -- 選択されているアイテムが4つの場合, 次に変化があれば選択されているアイテムは3つとなっている
         -- 選択されているアイテムがない場合, 次に変化があれば選択されているアイテムが1つとなっている
         maximumSelection =
             length selected <= 4
             && implies (length selected == 4) (length (next selected) == 3)
             && implies (length selected == 0) (length (next selected) == 1)

         -- <div id="warn"></div>自体が画面から消えることはない
         -- 選択されているアイテムがない場合は, warnのメッセージは空文字ではない
         -- 選択されているアイテムがある場合は, warnのメッセージは空文字である
         warningShown =
             warning /= Nothing
             && implies (selected == []) (warning /= Just "")
             && implies (selected /= []) (warning == Just "")
     in
         initial 
         && always (maximumSelection && warningShown)

一度テストをしてみる

まだ実装していないけれども, ここで一度テストをします。
この記事を書いている時点では以下のような感じでセットアップしていますが, この時点ではドキュメントがちょいちょい更新されているので公式サイトに倣ってください。

セットアップ
% docker network create quickstrom
% docker run --rm -d \
    --network quickstrom \
    --name webdriver \
    -v /dev/shm:/dev/shm \
    -v $PWD:/spec \
    selenium/standalone-chrome:3.141.59-20200826
テストを実行
docker run --rm \
  --network quickstrom \
  -v $PWD:/spec \
  quickstrom/quickstrom \
  quickstrom check \
  --webdriver-host=webdriver \
  --webdriver-path=/wd/hub \
  --browser=chrome \
  --tests=5 \
  /spec/Choice.spec.purs \
  /spec/index.html

エラーが出ました。
Quickstromは大量にイベントを発火させてエラーケースを探し出すのですが, 見つけた後にshrinkingで簡約してくれます。
このエラーはshrinkingするまでも無いのですが, "#warnのtextContentが空文字で且つselectedが空な状態になっている" ことを教えてくれているようです。

propositioninitialが満たされていないということですね。

修正してみる

初期状態では"A"が選択されているべきでしたので, index.htmlを修正します。

index.html
<div class="choices">
    <div class="choice selected">A</div>
    <div class="choice">B</div>
    <div class="choice">C</div>
    <div class="choice">D</div>
    <div id="warn"></div>
</div>

もう一度テストをしてみると...

テストを通りました!
initial以外については, そもそも現時点ではイベントハンドラを作っていないので初期状態以外はテストできていません。

JavaScriptで実装する

index.html
<script>
    document.querySelectorAll('.choice').forEach( (c) => {
        c.addEventListener('click', () => {
            c.classList.toggle('selected');
        });
    });
</script>

こんな感じかな。
テストをしてみましょう。
(ここではうっかり何も選択されてない時に警告メッセージを表示することを忘れています)

初期状態から, click .choice[0]("A"をクリック) すると何も選択していないのに, #warnのtextContentが空文字 になっている と表示されています。
うっかり警告メッセージの実装を忘れていたので, そのことを怒られたみたいですね。

JavaScriptを修正する

index.html
<script>
    document.querySelectorAll('.choice').forEach((c) => {
        c.addEventListener('click', () => {
            c.classList.toggle('selected');

            // 何も選択されていないと警告メッセージを表示
            const selected = document.querySelectorAll('.choice.selected').length;
            const warn = document.getElementById('warn');
            if(selected == 0){
                warn.innerText = "一つ以上選んでください。";
            } else {
                warn.innerText = "";
            }
        });
    });
</script>

成功です。

所感

Quickstromはfirst commitが今年の4月でまだまだ新しく, 2020/09/13時点では認証に対応していなかったりと自分が今関わっているプロジェクトでE2Eテストとして導入するのは難しそうです。
ただ, 複雑なコンポーネントなんかが改修によってデグレを起こしていないかをチェックするなんていう用途には今からでも使えそうなので, StrobyBook的なカタログに組み込んでいけないかなどを試してみようと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0