【E2E】CodeceptJS入門編

テストの重要性を分かってはいるけれども、テストをやるのは大嫌いなことで有名な私ですが、
CodeceptJSというE2Eテストフレームワークに惚れ込んでしまったので
勝手に宣伝させていただきます!

そもそもテストフレームワークについて初心者レベルなので、
書いている内容は浅いことばかりなのでご了承ください🙇

CodeceptJSとは

CodeceptJSはNode.jsで動作するE2Eテストフレームワークです。
(´-`).。oO(E2Eテストについてわからんって方はぐぐってください。

CodeceptJSに惚れ込んだ3つの理由

CodeceptJSに惚れ込んでしまった主な3つの理由は以下の通りです!

  • 簡単にテストコードを書ける
  • 簡単に環境構築できる
  • 曖昧な要素指定でも判別してくれる

簡単にテストコードを書ける

CodeceptJSの一番の特徴は何かと言うと、「簡単にテストコードを書ける」ことです。

簡単にテストコード書けるってどういうことやねん?騙されへんぞ??という方のために、
SeleniumとCodeceptJSで書いたテストコードを比較してみました。

Seleniumでのテストコード

Seleniumの公式ドキュメントに記載されているサンプルコードを引用させてもらいました。

const {Builder, By, Key, until} = require('selenium-webdriver');

(async function example() {
    let driver = await new Builder().forBrowser('firefox').build();
    try {
        // Navigate to Url
        await driver.get('https://www.google.com');

        // Enter text "cheese" and perform keyboard action "Enter"
        await driver.findElement(By.name('q')).sendKeys('cheese', Key.ENTER);

        let firstResult = await driver.wait(until.elementLocated(By.css('h3')), 10000);

        console.log(await firstResult.getAttribute('textContent'));
    }
    finally{
        driver.quit();
    }
})();

やっていることは以下の通りです。

  1. https://www.google.comにアクセスする
  2. name属性「q」の要素を探す
  3. 「cheese」と入力する
  4. 「Enter」キーを押下する
  5. タグ「h3」の要素から最初に発見された要素を取得する
  6. 取得した要素からテキストを取得して表示する

Seleniumのテストコードでも十分に読みやすいですが、
CodeceptJSだと更に読みやすく書けてしまいます!

CodeceptJSでのテストコード

先ほどのSeleniumのテストコードをCodeceptJSで書き直したものです。

Feature('はじめてのCodeceptJS');

Scenario('Googleで検索する', async({ I }) => {
    I.amOnPage('https://www.google.com');
    I.fillField('q', 'cheese');
    I.pressKey('Enter');
    I.wait(1);
    let firstResult = await I.grabTextFrom('h3');
    console.log(firstResult);
});

SeleniumとCodeceptJSのテストコードを比較してみると
Seleniumではメソッドチェーンでコーディングされている部分がありましたが、
CodeceptJSでは各行の処理が短くまとまっています。

また、テストコードの流れもCodeceptJSの方が直感的に理解しやすく感じます。

個人的にはCodeceptJSの方が好きです(´∀`*)ポッ

実行結果

テストコードの実行結果は以下の通りです。

MEMO
実行結果は対応する国の言語に翻訳して表示させることができます。

おまけ

翻訳されているメソッドであれば、以下のようにも書けちゃいます。

Feature('はじめてのCodeceptJS');

Scenario('Googleで検索する', async({ I }) => {
    I.ページを移動する('https://www.google.com');
    I.フィールドに入力する('q','cheese');
    I.キー入力する('Enter');
    I.待つ(1);
    let firstResult = await I.テキストを取得する('h3');
    console.log(firstResult);
});

用意されているメソッド全てが翻訳されているわけではないので、
基本は英語のメソッド名を利用した方が良い気がしています。

簡単に環境構築できる

非常に簡単にCodeceptJSの実行環境を構築することができます。

Node.jsがインストールされていれば、
PowerShellなどで以下のコマンドを実行するだけでほぼ完了です。

npx create-codeceptjs .

より詳細な構築手順は後で説明します!

曖昧な要素指定でも判別してくれる

Webサイトのテストコードを作成するにあたって、
対処となるサイト・ページのHTML要素やCSSなどを把握しておく必要がありました。

ですが、CodeceptJSだとテスト対象のWebサイトについて
理解していなくても大体テストコード書けてしまいます。

曖昧な要素指定について

それでは、Yahoo! JAPANのトップページを例に紹介します。

Yahoo! Japanのトップページに表示されている
赤枠で囲んだ「IT・科学タブ」をクリックするテストコードは以下の通りです。

I.amOnPage('https://www.yahoo.co.jp/');
I.click('IT・科学', 'li#tabTopics7');

勿論、Yahoo! JapanのトップページのHTML構成なんて知っているわけがないので
li#tabTopics7については開発者ツールを用いて調べました。

もしタブの順番が変わった場合には、このテストコードは動かなくなってしまいます。
また、他の箇所をテストするにも開発者ツールを立ち上げて要素を調べ上げなければならなくなるかもしれません。

ですが、CodeceptJSだと以下のコードでも同じ操作をしてくれます。

I.amOnPage('https://www.yahoo.co.jp/');
I.click('IT');

動作については以下の通りです。

  1. ページの中から「IT」の文言が含まれている要素を探す
  2. 最初に探し出された要素をクリックする

このようにHTMLタグやID、クラスを指定しなくても
画面に表示されている文言だけでテストコードを書けてしまいます。

もちろんデメリットもある

察しの良い方は気づいていると思いますが、
何も考えずにCodeceptJSに頼り過ぎると痛い目を見ることになります。

  • 他に「IT」と記載されている要素があった場合に、
    そちらが優先してクリックされる可能性がある。
  • 該当の要素が見つかるまで全要素を探索する場合があるので、
    処理が遅くなる可能性がある。

以上のデメリットがあるので、
まず動かしてみる!という場合には要素を指定しなくても良いですが、
本番用のテストコードを作成する際には最低限の要素指定しておいた方が良いでしょう。

どんな時に役立つ?

以下のようなWebサイトをE2Eテストする場合には、
物凄く役に立つと考えています。

  • HTML要素に対してIDやクラス指定がされていない場合
  • HTML構造がゴチャゴチャな場合

環境構築

ここまで読んでみて使ってみたいって方はCodeceptJSをセットアップしちゃいましょう。

Node.js インストール

ダウンロードページから
対象OS向けのインストーラーをダウンロードして、インストールしてください。
※できれば最新版がヨシッ!

CodeceptJS セットアップ

コマンドプロンプト、PowerShell、ターミナルのいずれかを開き、以下のコマンドを実行します。

npx create-codeceptjs .
注意
「.」まで入力が必要です。

コマンド実行後に以下のイカしたレインボーな文字が表示されたら、
各種モジュールがダウンロードされるのを待ちます。

ダウンロードが完了すれば、もう環境構築完了です!

これで準備完了です。
ね?簡単でしょ?

CodeceptJSの使い方

さて、実際に使ってみましょう。

必要ファイルの作成

まずはテスト実行に必要なファイルを作成するために、以下のコマンドを実行します。

npx codeceptjs init

各質問に答えつつ、設定ファイルやテストファイルなどを作成します。
途中で入力ミスしたとしても、後で設定ファイルを直接編集すれば良いので焦らなくても大丈夫です。

# テストファイルはどこに配置する?あとテストファイル名のフォーマット教えて。
? Where are your tests located? ./*_test.js

# どのテストツール使う?
? What helpers do you want to use? Playwright

# スクリーンショットとかログとかどこのフォルダに出力する?
? Where should logs, screenshots, and reports to be stored? ./output

# 何語に翻訳してほしいよ?
? Do you want localization for tests? (See https://codecept.io/translation/) ja-JP
Configure helpers...

# テスト対象のサイトURL教えてちょ
? [Playwright] Base url of site to be tested http://localhost

# テスト時にブラウザ画面表示する?
? [Playwright] Show browser window Yes

# ブラウザどれ使ってやれば良い?chromium、firefoxとか使えるで。
? [Playwright] Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron chromium

# テストファイル作成するべ。初めてのテストはどんなことする?
? Feature which is being tested (ex: account, login, etc) search

# テストファイル名どうする?
? Filename of a test search_test.js

テストコードの作成

作成したテストコードを開きましょう。先ほどの例だと「search_test.js」がフォルダ内に作成されています。

最初に紹介したサンプルコードを書いてみましょう。

Feature('はじめてのCodeceptJS');

Scenario('Googleで検索する', async({ I }) => {
    I.amOnPage('https://www.google.com');
    I.fillField('q', 'cheese');
    I.pressKey('Enter');
    I.wait(1);
    let firstResult = await I.grabTextFrom('h3');
    console.log(firstResult);
});

実行

以下のコマンドを実行してテストしてみましょう。

npx codeceptjs run --steps

成功すればOKです。多分エラーにならないはずです。

メソッドについて

サンプルコードで利用したコマンドについて少し解説します。

amOnPage

指定されたURLにアクセスするメソッドです。
絶対URLまたは相対URLで指定する方法があります。

相対URLで指定する場合には/から始まる文字列を指定します。初回設定時に設定したURLがベースURLになります。

Scenario('amOnPageについて', async({ I }) => {
    // 絶対URL
    I.amOnPage('https://www.google.com');
    // 相対URL
    I.amOnPage('/hoge'); // => http://localhost/hoge
});

fillField

テキストフィールドに値を入力するメソッドです。
第一引数に入力対象の要素を指定し、第二引数に入力する文字列を指定します。

Scenario('fillFieldについて', async({ I }) => {
    I.amOnPage('https://github.com/login');
    // ID名から判別する
    I.fillField('login', 'hogehoge_id');
    // ラベル名から判別する
    I.fillField('Username or email address', 'hogehoge_label');
});

pressKey

フォーカスされた要素上でキー入力します。

Scenario('pressKeyについて', async({ I }) => {
    // Enterキーを押下します
    I.pressKey('Enter');
    // Ctrl + Sキーを押下します
    I.pressKey(['Control', 'S']);
});

一部ですが、以下のキーが対応しています。

  • Alt
  • Delete
  • End
  • Enter
  • Escape
  • F1 to F12
  • Home
  • PageDown
  • Shift
  • Space
  • Tab

wait

指定した秒数待つメソッドです。

Scenario('waitについて', async({ I }) => {
    // 1秒待ちます
    I.wait(1);
});

grabTextFrom

指定した要素からテキストを取得するメソッドです。
複数の要素が見つかった場合には、最初の要素から取得されます。
全ての要素を取得するにはgrabTextFromAllメソッドを利用します。

Scenario('grabTextFromについて', async({ I }) => {
    // #hogeからテキストを取得します
    let hoge = await I.grabTextFrom('#hoge');
    console.log(hoge);
    // h3要素のテキストを全て取得します
    let allH3 = await I.grabTextFromAll('h3');
    console.log(allH3);
});

これ以外にも数多くのメソッドが用意されているので、
公式リファレンスを一度読んでみてください!

ファイル説明

主に利用するファイルについて説明します。

codecept.conf.js

CodeceptJSの設定ファイルです。npx codeceptjs initで設定した内容が登録されています。
必要に応じて設定値を書き換えます。

const { setHeadlessWhen } = require('@codeceptjs/configure');

setHeadlessWhen(process.env.HEADLESS);

exports.config = {
  tests: './tests/**/*_test.js',
  output: './output',
  helpers: {
    Playwright: {
      url: 'http://localhost',
      show: true,
      browser: 'chromium'
    }
  },
  include: {
    I: './libs/steps_file.js'
  },
  bootstrap: null,
  mocha: {},
  name: 'codeceptjs',
  translation: 'ja-JP',
  plugins: {
    pauseOnFail: {},
    retryFailedStep: {
      enabled: true
    },
    tryTo: {
      enabled: true
    },
    screenshotOnFail: {
      enabled: true
    },
    stepByStepReport: {
      enabled: false
    }
  }
}

tests

テストファイルを見つけるための正規表現(globパターン)を指定します。
./tests/**/*_test.jsと指定することで
testsフォルダ内にあり、かつ*_test.jsのパターンと一致するファイルを
テストファイルとして認識します。

バージョン3.1.2から配列で複数パターンを指定できるようになりました。
tests: ['./*_test.js','./sampleTest.js']

steps_file.js

複数の操作をまとめたメソッドを定義できます。
ここで定義するとI.login('mailaddress', 'password123456');といった感じに
独自のメソッドとして使えるようになります。

module.exports = function() {
  return actor({
    login: function(email, password) {
      this.fillField('Email', email);
      this.fillField('Password', password);
      this.click('ログイン');
    }
  });
}

steps.d.ts

VSCodeなどのIDEで自動補完機能が使えるようになる定義ファイルです。

npx codeceptjs defコマンドで定義ファイルの更新ができます。

Helper

各種ライブラリのラッパークラスを作成できます。

以下のコマンドを実行することでヘルパーファイルを作成し、設定ファイルにもセクションを追加してくれます。

npx codeceptjs gh

サンプルコード

PostgreSQLを操作するためのラッパークラスのサンプルを紹介します。

const { Client, Pool } = require('pg');

class postgresHelper {

    async connect(config) {
        const pool = new Pool(config);
        this.client = await pool.connect();
    }

    async close() {
        await this.client.end();
    }

    async query(query, parameters = {}) {
        const res = await this.client.query(query);
        return res.rows;
    }
}

module.exports = postgresHelper;

使い方

以下のコードの通りにIからヘルパーファイルで用意したメソッドを呼び出すことができます。

Scenario('データベースから値を取得する', async({ I }) => {
    // コンフィグの設定
    const config = {hoge: "hoge"};
    await I.connect(config);
    const queryString = 'SELECT * FROM hoge';
    let result = await I.query(queryString);
    console.log(result);
    await I.close();
});

外部サービスとの連携やファイル操作などE2Eテストを実行するにあたって
必要となるライブラリのラッパーを作成するとより便利に活用できます。

コミュニティによって作成されたヘルパー

こちらのページでコミュニティの人たちによって作成されたヘルパーが紹介されています。

Webhookを呼び出すヘルパーやメールテスト用のヘルパーなどがあり、追加も簡単です。

(´-`).。oO(db向けのヘルパーもあるのですが、自分の環境では使えなかったので自作しちゃいました…。気が向いたら公開します。

Plugin

プラグインを利用することでCodeceptJSの機能を拡張できます。

プラグインの作成方法はまた今度紹介しようと思っているので、
CodeceptJSで準備されているプラグインを一部紹介します。

retryFailedStep

retryFailedStepはステップ失敗時にステップを再実行するプラグインです。
このプラグインはデフォルトで有効になっています。

screenshotOnFail

screenshotonfailはテスト失敗時のスクリーンショットを撮影してくれるプラグインです。
このプラグインはデフォルトで有効になっています。

tryTo

tryToはステップの実行結果をtrue/falseで返してくれるプラグインです。
ステップが失敗した場合には、そのタイミングでテストが中断されてしまいます。
しかし、tryToを使って以下のようにコードを書くことで成功時にはtrue、失敗時にはfalseが返ってきます。

const result = await tryTo(() => I.see('本気出さない'));

さいごに

E2Eテストを導入したいけど以下のことで断念したという方も多いのではないでしょうか。

  • 環境構築するのが面倒…。
  • 対象のシステムのHTML要素がゴチャゴチャで無理…。
  • 過去にE2Eテストしようとしたけど断念した…🤔
  • 学習コストが高いのは嫌…。そんな時間がない…。

CodeceptJSはこれらの問題を解決してくれる素晴らしいツールだと感じているので、
E2Eテスト導入してみたい、またとりあえず触ってみたいという方は
是非CodeceptJSを使ってみてください。

まだまだ紹介できていない便利な機能が多くあるので、
また次の記事で紹介しようと思っています!

(´-`).。oO(年内には投稿したい。

コメントを残す

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください