on
E2Eテスティングフレームワーク、Cypress,TestCafeを試してみた
TweetE2Eテスティングフレームワーク、Cypress,TestCafeを試してみた
最近、注目されつつある2つのE2Eテスティングフレームワークを試してみました。
Cypressは一休さんのブログE2EテストをSelenium Webdriver からCypress.io に移行した話でも述べられており、注目されているように感じます。
2つのE2Eテスティングフレームワークを触ったのでこれらの違いについて述べていきたいと思います。
特徴
2つのテスティングフレームワークの特徴は以下です。
- JavaScriptやTypeScriptで記述できる
- Headless Chromeが動作する
- DOMレンダリングが完了するまで自動で待機する
- テスティングフレームワークに必要なアサーションメソッドが備わっている
個人的には3つ目の特徴DOMレンダリングが完了するまで自動で待機する
は便利かなと思います。
今までのE2Eやスクレイピングライブラリではページ遷移後にDOM要素が現れるまで待機する時間を開発者が記述しなければいけませんでした。
しかしながら2つのテスティングフレームワークはそれらを記述しなくても自動で待機してくれます。
Cypress
Cypressのテストの書き方は以下です。
describe('My First Test', function() {
it('Gets, types and asserts', function() {
cy.visit('https://example.cypress.io')
cy.contains('type').click()
// Should be on a new URL which includes '/commands/actions'
cy.url().should('include', '/commands/actions')
// Get an input, type into it and verify that the value has been updated
cy.get('.action-email')
.type('fake@email.com')
.should('have.value', 'fake@email.com')
})
})
cy.visit('https://example.cypress.io')
の記述でhttps://example.cypress.io
のページに遷移しています。
もちろんこの記述だけで、DOM要素がレンダリングされるまで勝手に待っていてくれます。
cy.contains('type').click()
では”type”という文字列を含んだ、要素をクリックしています。
cy.url().should('include', '/commands/actions')
こちらの構文がテストの構文です。
URLが/commands/actions
を含んでいるかをテストしています。
cy.get('.action-email')
でaction-email classのDOM要素を取得し、そのinputフォームに対してtype('fake@email.com')
で文字を入力します。
should('have.value', 'fake@email.com')
で入力が行われたかをチェックしています。
Cypressの良いところ
Cypressはcypress open
のコマンドで起動するとインタラクティブモードで起動できます。
起動するとまず、テストファイルの一覧が表示されます。
実行したいファイル名をクリックするとテストがブラウザ上で実行されます。
インタラクティブモードで実行すると動作がブラウザ上で確認できます。 また、テスト実行時に、メソッド1行1行ののスナップショットが保存されます。 テスト終了後、ウィンドウ左に表示されているメソッドを押すとそのメソッドの実行内容がブラウザ上に表示されます。 これが非常に便利です。 インタラクティブモードのウィンドウでDOM要素を選択すると、どのようなPATHでDOMにアクセスすれば良いかが表示されます。(画面上のcy.get()の部分)
Cypressでハマったところ
ハマったところは以下の2つでした。
- UTF-8以外の文字コードでエンコーディングされたサイトで文字化けがおきる
- DOMの子要素に対しての操作でコードが難解になる
1つ目のUTF-8以外の文字コードでエンコーディングされたサイトで文字化けがおこる事象はCypressのGitHub issuesでも議論されています。
例えばShift_JISでエンコーディングされたHTMLに対してcy.visit
すると文字化けが起こります。
実際に以下のような記述があるサイトで文字化けが起こりました。
<head>
<meta http-equiv="Content-Type" content="text/html;charset=Shift_JIS">
// 省略
</head>
詳しくはissses を参照してください。
次に、2つ目のDOMの子要素に対しての操作でコードが難解になるについてです。
CypressでDOMの複数の子要素に対して同様の操作を行うときはeach
関数を利用します。実際にeach
を使用したコードが以下です。
cy.get(".calendars > :nth-child(3) > table > tbody > tr").each(tr => {
return cy.wrap(tr.children()).each(elment => {
if (elment.hasClass("class0001") || elment.hasClass("class0003")) {
tdCount += 1;
}
if (tdCount === 7) {
cy.wrap(elment).click();
return;
}
});
});
each
がネストしたりするとコードが読みにくくなります。
for文のネストのようなものですが、DOM要素をたどるプログラムでは上のようなコードが頻出するのではないかと思います。読めなくはないですが、理解するのに時間がかかってしまいそうです。
TestCafe
続いてTestCafeです。 TestCafeのテストの書き方は以下です。
import { Selector } from 'testcafe';
fixture `Getting Started`
.page `http://devexpress.github.io/testcafe/example`;
test('My first test', async t => {
await t
.typeText('#developer-name', 'John Smith')
.click('#submit-button')
// Use the assertion to check if the actual header text is equal to the expected one
.expect(Selector('#article-header').innerText).eql('Thank you, John Smith!');
});
page
関数で遷移するページを指定しています。
今回はhttp://devexpress.github.io/testcafe/example
に遷移しています。
t.typeText
で#developper-nameのinputフォームに”John Smith”を入力し、click
で#submit-buttonのsubmitボタンをクリックします。
最後のexpect
とeql
で#article-headerのDOMの文字列が”Thank you, John Smith!“であるかをテストしています。
こちらもJestのような書き方でテストがかけます。
Cypressと違うところは各関数がPromiseを返すという点です。
Selector
関数でDOM要素を取得できます。
TestCafeの良いところ
TestCafeにもLive ModeというCypressのインタラクティブモードに似た機能が備わっています。
Live Modeで起動するにはtestcafe chrome test.js -L
のように-Lオプションをつけて実行します。
実行すると以下のようなウィンドウが起動します。
こちらもテストがブラウザ上で実行されているのを確認することができます。
また、DOM要素の属性を知りたい場合は通常のChrome同様、検証(F12)からDOM要素をクリックし、属性を確認します。
また、先ほども述べましたがTestCafeの各関数はPromiseを返します。JSに慣れている人であれば普通のライブラリを使うような感覚でプログラムを書くことができるます。
環境変数がprocess.env.HOGEHOGE
で取得できるのも地味にありがたかったです。
TestCafeでハマったところ
Client上でコードを実施したい場合はClientFunction
という関数を使う必要があります。
import { Selector } from 'testcafe';
import { ClientFunction } from 'testcafe';
fixture `My Fixture`
.page `examplepage.html`;
const testedPage = Selector('#testedpage');
const getChildNodeText = ClientFunction(index => {
return getEl().childNodes[index].textContent;
},{ dependencies: { getEl: testedPage } });
test('My Test', async t => {
await t.expect(getChildNodeText(0)).contains('This is my tested page.');
});
ClientFunction
を使うと慣れ親しんだDOM操作メソッドを記述することが可能です。ただ、この関数はTypeScriptの型定義がしっかりとできていなかったせいか、
dependencies
を使ったところでコンパイルエラーになってしまいました。
どっちが良いのか?
デバッグの観点からいくとCypressは優れているなと感じました。インタラクティブモードを使って実装を進めれば、DOM取得のプログラムが非常に簡単に実装できます。
しかしながら、コードの書きやす的にはTestCafeの方が書きやすかったです。 TestCafeの方が直感的にわかりやすいコードになるので、コードレビューもしやすそうです。
しかながら、どちらも使いやすツールなので、実際に使ってみて決めるのが良いと思います。