※本記事は、NorthDetail Advent Calendar 2020の一環として投稿しています
こんにちは、d_ice_kです。
今月から品質テストに関わることとなり、Cypressによるテスト環境を整えております。
今回は、一覧画面などテーブルやリストに関する項目の検証について考えます。
Cypressでは、どの要素を検証するかを選択するために、ロケータとしてCSSセレクタやXPathを利用することになりますが、公式ではロケータの一意性を保持するために推奨される書き方があります。以下公式より抜粋、
<button id="main" class="btn btn-large" name="submission"
role="button" data-cy="submit">Submit</button>
//データ属性でロケーティング
cy.get('[data-cy=submit]').click()
//テキストでロケーティング
cy.contains('Submit').click()
//name属性でロケーティング
cy.get('[name=submission]').click()
//id属性でロケーティング
cy.get('#main').click()
//class属性でロケーティング
cy.get('.btn.btn-large').click()
//要素名(タグ)でロケーティング
cy.get('button').click()
変更される可能性や、変更された時の修正のし易さに伴い、この順でのロケーティングが推奨されています。詳しくはこちら、Cypress.io Best Practices
上記の属性を保持していれば、属性でロケーティングするのが最短ですが、必ずしも推奨するロケーティング通りにいくとは限りません。順を追って考えてみます。
まず初めに、属性でロケーティングができる場合
<table>
<tr data-test="test1">
<td>1</td>
<td>テスト1</td>
</tr>
<tr data-test="test2">
<td>2</td>
<td>テスト2</td>
</tr>
<tr data-test="test3">
<td>3</td>
<td>テスト3</td>
</tr>
</table>
cy.get('[data-test=test2]').contains('テスト2')
データ属性のdata-test=test2でロケーティングし、テーブル2行目にテスト2があることを検証できます。
では、属性が乏しい、または属性が不定なテーブルを検証する場合はどうするのか?
<table>
<tr> <!-- eq(0) -->
<td>1</td>
<td>テスト1</td>
</tr>
<tr> <!-- eq(1) -->
<td>2</td>
<td>テスト2</td>
</tr>
<tr> <!-- eq(2) -->
<td>3</td>
<td>テスト3</td>
</tr>
</table>
CSSセレクタでロケーティングすると、
cy.get('table>tr:nth-child(2)').contains('テスト2')
これを、もっとテーブルの階層構造を直接的に指定する書き方にすると、
cy.get('tr').eq(1).contains('テスト2')
テーブルの2行目('tr').eq(1)を指定して、テスト2があることを検証できます。
上の2つは、画面内にテーブルが1つの場合は成り立ちますが、同じ画面内に複数のテーブルが存在する場合には、最初のテーブルの2行目だけをロケーティングしてしまいます。
では、複数のテーブルがある場合の属性に依存しない検証はどうすればいいのか?
<table>
<tr>
<td>1</td>
<td>テスト1-1</td>
<td>結果1-1</td>
<td><button>編集</button></td>
</tr>
<tr>
<td>2</td>
<td>テスト1-2</td>
<td>結果1-2</td>
<td><button>編集</button></td>
</tr>
<tr>
<td>3</td>
<td>テスト1-3</td>
<td>結果1-3</td>
<td><button>編集</button></td>
</tr>
</table>
<table>
<tr>
<td>1</td>
<td>テスト2-1</td>
<td>結果2-1</td>
<td><button>編集</button></td>
</tr>
<tr>
<td>2</td>
<td>テスト2-2</td>
<td>結果2-2</td>
<td><button>編集</button></td>
</tr>
<tr>
<td>3</td>
<td>テスト2-3</td>
<td>結果2-3</td>
<td><button>編集</button></td>
</tr>
</table>
cy.contains('テスト2-2').parents('tr').within(() => {
cy.get('td').eq(2).contains('結果2-2')
cy.get('td').eq(3).contains('編集').click()
})
構造そのものが変わらない限り、登録一覧画面のようなid属性などが不定なテーブルや、同じ構造が並ぶ画面の検証をする際は、属性に依存しない書き方が有効です。