環境
- macOS Monterey 12.6
- Windows 11
- VSCode
- node.js v18.6.0
- npm 8.13.2
- Typescript 4.6.4
- cypress 11.1.0
Cypressとは
無料のE2Eテスト(エンドツーエンドテスト)用ツール
導入手順
cypressのインストール
npm install -D cypress
package.jsonに起動用コマンドを追加
"scripts": {
///
"e2e:open": "cypress open"
}
npm run e2e:openを実行すると、cypress
というディレクトリが作成される
TypeScript化
typescriptを追加し、
npm install -D typescript
tsconfig.tsを作成する
{
"compilerOptions": {
"target": "es5",
"isolatedModules": false,
"types": ["cypress"]
},
"include": ["node_modules/cypress", "./**/*.ts"]
}
cypress\e2e内のjsファイルやcypress\supportのjsファイルを.tsに変更する
特徴
- cypressでは、基本的にすべての処理を非同期に行っている
- cypressでは正しく対象の要素を操作できるように、対象の要素が表示されるまで自動的に待ってから実行するように常に動いている(タイムアウトするまで)
- 自動で待機、リトライまで行う
- Cypressのコマンドは、呼び出されたタイミングでは何も処理されず、後で実行されるように自分自身をキューに追加する。関数内のコマンドが全てキューに追加されたのち、順に実行を開始する
- 一部のコマンド(clickなど)ではリトライしてしまうとアプリケーションの状態を意図せず書き換えてしまう可能性があるため、リトライが行われないようになっている
- timeout値を指定することで、リトライの上限を決めることができる。(デフォルトは4秒)
- ファイルシステムの変更を検知し、ファイル変更が検知された場合、そのファイルが自動でリロードされ、テストが実行される
- cypressのメソッドには、大きく分けるとコマンド(getなど)とアサーション(shouldなど)の2種類がある
設定ファイル
- Cypress v10以降、設定ファイルがcypress.jsonではなく、cypress.config.jsに変わっている
- 設定ファイルには、基底パスなどが設定できる
cypress.config.js
const { defineConfig } = require("cypress");
module.exports = defineConfig({
projectId: '9hmm3t',
e2e: {
baseUrl: "http://localhost",
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});
頻用メソッド
ページ遷移
指定したurlにアクセス
cy.visit("https://maasaablog.com/")
要素の取得
セレクタに一致するHTML要素を取得できる
cy.get('#hoge')
要素の操作
文字列を入力
cy.get(セレクタ).type('入力')
※空文字は以下のように入力できる
cy.get(セレクタ).type("{backspace}");
セレクトボックスで選択
cy.get(セレクタ).select('入力')
クリック
セレクタに一致するボタンなどのHTML要素を取得しクリック
cy.get(セレクタ).click()
チェックをつける
cy.get('[value="1"]').check() // チェックボックス
cy.get('[type="radio"]').first().check() //ラジオボタン
その他の操作、以下のような操作も可能
- blur
- focus
- clear
- uncheck
- dblclick
- rightclick
要素の検証
要素のテキストや属性を検証
cy.get('h2').should('have.text', '顧客一覧')
ほかにも、id属性なら、.should(have.id,expected)、valueなら、.should(have.value, expected)、name属性なら、.should('have.attr', 'name', 'numberbox')などのように各種属性を検証可能。
また、特定の要素がチェックされていることを確認するには、.should('be.checked')
要素が表示されていることの検証
cy.get('h2').should('be.visible')
反対に、非表示となっていることを検証するには、以下のようにする。(アサーション系のメソッド全般で真逆のアサーションを使用したい場合は、 notを使用する)
cy.get('h2').should('not.be.visible')
domに存在しているが非表示になっている場合はnot.be.visibleを、そもそもdomに存在していない場合はnot.existを使用する
urlの検証
一致
cy.url().should('eq', '/products/1')
部分一致
cy.url().should('include', '/products')
ページ内に指定したテキストが含まれていることの検証
cy.contains('文字列')
Tips
要素の指定・取得
idやclassはcssの変更により影響を受ける可能性があるため(idはほぼないが)、専用のdata-属性をつけて、それをセレクタとして指定できるようにするのがベター
Selector Playground
cypressの実行画面で、下図の黄色線の部分をクリックすると、playgroundモードになる。この状態で対象の要素をクリックするだけで、要素をどのように取得すればいいかgetコマンドをサジェストしてくれる。取得したい指定する際、タイポなどを防止でき非常に便利である。
隠れた要素を強制的に操作する
要素を操作する際、何かしらの原因(ローディングアニメーション、モーダル等)で対象の要素が隠れてしまい、選択できない場合がある。このような場合はforce:trueを指定することで、強制的に選択・操作することが可能になる
cy.get('#button').click({ force: true })
ログ
テスト実行画面左側には、実行内容が順次表示されるが、これを選択すると、以下のように選択した用の内容やどのコマンドを使用したかなどの情報が確認できる
自動テストを一時停止する
任意の箇所でcy.pause() を呼び出すと、テストが一時停止するので、目視で確認したい内容などがある場合に便利である
cy.pause()
タイムアウトについて
cypressのコマンドの返り値は基本的にCypress.Chainableインターフェースを実装した内容となっているため、メソッドをチェーンすることができる(以下「コマンドチェイン」)
前述の通り、タイムアウトのデフォルト値は4秒であるが、コマンドチェインした際のタイムアウトは、各アサーションごとに適用される。つまり以下の場合、hogeという要素が表示されるまで4秒、期待するテキスト情報持つかどうかを検証するまでに4秒の計最大8秒待機し、それを超えるとタイムアウトとなる
cy.get('#hoge').should('have.text', "hogeです")
タイムアウト値の明示は可能である。(get以降のshouldなどにのチェーンされているほかのコマンドに対しても適用されるので、以下のようにすると、計40秒となる)
cy.get('#hoge', {timeout: 20000}).should('have.text', "hogeです")
コマンドチェインチェインへの干渉
cypressのコマンドは非同期で実行されるので、例えばcy.get()で取得した要素を直後の処理でそのまま使うことはできない。そのため、同期的な処理を書きたければ、thenを使う必要がある
cy.get("h3").then(($heading) => {
// $heading には直前のコマンドの結果がはいってくる
cy.log($heading.text())
})
// 後続の処理は↑の処理が完了してから実行される(完了しない限り、キューに積まれたまま実行されない)
カスタムコマンド
cypressの頻用コマンドを共通化することができる機能。
かなり簡易的な例であるが、getしてtypeするという処理をカスタムコマンドで実装すると以下のようになる
呼び出し側(sample.cy.ts)
// コマンド登録前
cy.get("#input_hoge").type("2023/01/01");
// コマンド登録後
cy.typeToTextInput("#input_hoge", "2023/01/01")
Cypress.Commands.add
でカスタムコマンドを登録
cypress\support\commands.ts
// コマンドの型定義ないとエラーになる
// https://github.com/cypress-io/cypress/issues/8127
declare namespace Cypress {
interface Chainable {
typeToTextInput(selector: string, value:string): Cypress.Chainable;
}
}
Cypress.Commands.add('typeToTextInput', function typeToTextInput(selector: string, value:string): Cypress.Chainable {
return cy.get(selector).type(value);
});
localStorage利用
以下のようにlocalStorageも使用でき、テスト後に、cy.clearAllLocalStorage()でキレイキレイすることが可能。
describe('ローカルストレージを使用した何かしらのテスト', () => {
beforeEach(() => {
localStorage.setItem(
'hoge',
JSON.stringify({ foo: { bar: '' } })
)
})
使うときは、普通に取り出せる
localStorage.getItem('hoge')
Httpリクエストのモック
interceptを使用することで可能。bodyにダミーのレスポンスを指定することができる。
cy.intercept('GET', '/api/users', {
statusCode: 201,
body: {
user: {
id: "1", name: "tom"
}
},
})
また、クエリパラメーターがurlにつくようなリクエストをinterceptする場合は以下のようにすることで、エンドポイントが一致し、実際のリクエストを置き換えることが可能になります。
Ex)記事IDが1の記事を表示する詳細画面
cy.intercept('GET', '/api/posts?postId=*', {
statusCode: 201,
body: { postId: 1, title: 'interceptの使い方について' },
}).as('fetchPost')
なお、interceptした処理にasでエイリアスをつけておくことで、後続の処理でcy.get(エイリアス名)で簡単にアクセスし、検証することが可能です。
cy.get('@fetchPost.all').should('have.length', 1)
例えば、以下は特定のinterceptの完了を待つ場合です
cy.intercept('GET', '/api/・・・', {
...
}).as('hoge1')
cy.intercept('GET', '/api/・・・', {
...
}).as('hoge2')
it('・・・', () => {
cy.wait('@hoge1').wait('@hoge2')
・・・
まとめ
いかがでしたでしょうか。本記事では、Cypressを実業務で使用する際に必要な最低限の使用方法やTipsなどについて紹介しました。具体的には要素の取得方法や検証方法、デバッグの手順などについて例を挙げながら説明していますのでぜひ参考にしてみてください。