Posted at

CodeceptJSのwithin()が最高だという話(と注意点)

CodeceptJSには within() という強力な機能があります。

簡単に言うと、セレクタのルートノードを固定してくれる機能で、コンポーネント単位のテストに非常に有用です。

雑すぎる例ですが、二つのdivに同じ名前の要素があり、片方を選びたいことは多いでしょう。


<div>
<div id="content1">
<button>ボタン1</button>
<button>ボタン2</button>
<button>ボタン3</button>
</div>
<div id="content2">
<button>ボタン1</button>
<button>ボタン2</button>
<button>ボタン3</button>
</div>
</div>

こういう場合に、 within() を使うとすっきり書くことが出来ます。


// withinを使わない場合

I.click(locate('#content1').find('button').withText('ボタン1'))
I.click(locate('#content1').find('button').withText('ボタン2'))
I.click(locate('#content1').find('button').withText('ボタン3'))
I.click(locate('#content2').find('button').withText('ボタン1'))
I.click(locate('#content2').find('button').withText('ボタン2'))
I.click(locate('#content2').find('button').withText('ボタン3'))

// withinを使う場合
within('#content1', () => {
I.click('ボタン1')
I.click('ボタン2')
I.click('ボタン3')
})
within('#content2', () => {
I.click('ボタン1')
I.click('ボタン2')
I.click('ボタン3')
})

within()を使った時にlocate()すらも登場しなくなっているのは、対象となる要素が1つに限定されたので、 Semantic Locator を利用できるようになったからです。


注意点


within()のネストはできない

出来ません。諦めて下さい。


後から増えた要素は見つけられない

例えば、上記のケースで、 ボタン3 をクリックすると ボタン4 が増えるようなjavascriptが仕込まれていたとします。


<div>
<div id="content1">
<button>ボタン1</button>
<button>ボタン2</button>
<button id=button3>ボタン3</button>
</div>
</div>

<script>
$('#button3').on('click', function() {
$(this).parent().append('<button>ボタン4</button')
})
</script>

このとき、同じwithin()の中でボタン4まで押しに行くと失敗します。


// 失敗するパターン
within('#content1', () => {
I.click('ボタン1')
I.click('ボタン2')
I.click('ボタン3')
I.click('ボタン4')
})

一度withinを抜けて、また入り直せばOKです。


// 大丈夫なパターン
within('#content1', () => {
I.click('ボタン1')
I.click('ボタン2')
I.click('ボタン3')
})
within('#content1', () => {
I.click('ボタン4')
})

なので、「ボタン3を押したらボタン4が出てくるかどうかを検証する」ようなテストの場合、アサーションだけwithinの外に書かないといけないです。この仕様だけちょっと気に食わないですね……!

// 失敗するやつ

within('#content1', () => {
I.click('ボタン1')
I.click('ボタン2')
I.click('ボタン3')
I.waitForText('ボタン4')
})

// 成功するやつ
within('#content1', () => {
I.click('ボタン1')
I.click('ボタン2')
I.click('ボタン3')
})
I.waitForText('ボタン4')