LoginSignup
5
3

CodeceptJSにトライしてみた。

Last updated at Posted at 2023-11-11

最初に

本記事はGitHub: GOAMI-Takaaki/codeceptjs-hotel-planisphereの転記になります。

概要

自動化練習サイト「HOTEL PLANISPHERE」を対象に、Gherkin記法のテストを、CodeceptJS で実装したサンプルコードを紹介します。

対象

構成

環境

基本

CodeceptJS とは

  • E2Eテストフレームワークである。
  • Node.jsプロジェクトである。
  • Gherkin記法をサポートしている。

Pros

  • コードが直感的で分かりやすい。
    • ex. I.click('ログイン')
  • 対応しているテストツールが多い。
    • ex. Playwright, WebDriver, Puppeteer, TestCafe, Appium
  • プラグインが豊富である。
    • ex. ビジュアルテスト、データ駆動テスト、テストレポートなど

Cons

  • ドキュメント通りに動作しないことがある。
    • ex. iframeやポップアップ
  • 日本語ドキュメントが少ない。
    • CodeceptJS に関する Qiita は 44 記事だけである。
  • Gherkin記法がサブ的な位置づけである。
    • 機能強化の見通しが不透明である。

Gherkin 記法とは

  • 自然言語で記述するシナリオ・フォーマットの1つである。
    • ex. Feature / Scenario / Given / when / Then で記述する。
  • 振る舞い駆動開発 (Behavior Driven Development: BDD) で利用される。
    • TDD としての SpceBDD と 受け入れテストとしての StoryBDD がある。
  • Cucumberで利用されている記法として知られる。

Pros

  • 自然言語で記述できる。
    • 誰でも理解・記述でき、関係者と認識を合わせやすい。
  • シナリオだけで記述できる。
    • 画面操作を含まず、実装前に定義できる。
  • 記述方法が共通化されている。
    • ナレッジの再利用性がある。

Cons

  • 記述が冗長になりやすい。
    • ex. ログイン画面のメールアドレスに{string}を入力する。
  • 曖昧になりやすい。
    • 実装者との認識が一致しない可能性がある。
  • フォーマットの拡張性がない。
    • デシジョン・テーブルなどパターンテストには利用しづらい。

プロジェクト作成

ref. Quickstart | CodeceptJS

# フォルダを作成する。
$ mkdir codeceptjs-hotel-planishpere
$ cd codeceptjs-hotel-planishpere

# CodeceptJS と Playwright をインストールする。
$ npx create-codeceptjs .

# プロジェクトの初期化をする。
$ npx codeceptjs init
# TypeScriptは利用する。
? Do you plan to write tests in TypeScript? Yes
# テストを各ファイル名のルールだが、今回は使用しない。
? Where are your tests located? ./*_test.ts
# 使うテストツールは Playwright にする。
? What helpers do you want to use? Playwright
# 出力フォルダ名は output にする。
? Where should logs, screenshots, and reports to be stored? ./output
# ローカライズは英語のままにする。ja-JP だと不具合もある。
? Do you want to enable localization for tests? http://bit.ly/3GNUBbh English

# ブラウザは chromium を選択する。
Configure helpers...
? [Playwright] Browser in which testing will be performed. Possible options: chromium, firefox, webkit or electron chrom
ium
# ベース URL は HOTEL PLANISPHERE に指定する。
? [Playwright] Base url of site to be tested https://hotel.testplanisphere.dev/ja/
# テスト時の画面は非表示にする。
? [Playwright] Show browser window No
# 最初に作るシナリオ名は login にする。
? Feature which is being tested (ex: account, login, etc) login
# シナリオを記述するファイル名は login.ts にする。
? Filename of a test login.ts

# Gherkin 用の初期化をする。
$ npx codeceptjs gherkin:init

フォルダ構成

自動作成

  • features
    • Gherkin形式で書くシナリオを配置する。
  • output
    • 実行時のスクリーンショットなどが配置される。
  • step_definitions
    • シナリオで記載されるステップを配置する。
  • codecept.conf.js
    • 動作を切り替える設定ファイル。
  • step_file.ts
    • 共通で利用するステップを配置する。
  • steps.d.ts
    • defコマンドによりTypescript用の定義が自動生成される。

手動作成

  • data
    • シナリオに利用するファイルを配置する。
  • src
    • 共通のコードを配置する。

設定

自動作成

ref. Configuration | CodeceptJS

export const config: CodeceptJS.MainConfig = {
  tests: './*_test.ts', // テスト対象(未使用)
  output: './output', // 出力先
  helpers: {
    Playwright: {
      browser: 'chromium', //ブラウザ
      url: 'https://hotel.testplanisphere.dev/ja/index.html', // 初期URL
    },
  },
  include: { 
    I: './steps_file' // 共通ステップを定義するファイルを指定する。
  },
  gherkin: {
    features: [ 
      // シナリオ毎に作成する。
    ],
    steps: [ 
      // ページ毎に作成する。
    ]
  },
  name: 'hotel-example-codeceptjs-ja' // プロジェクト名
}

手動作成

ref. Configuration in Playwright | CodeceptJS

export const config: CodeceptJS.MainConfig = {
  ...
  helpers: {
    Playwright: {
      ...
      windowSize: '1980x1080', // 画面サイズ
      locale: 'ja-JP', // 言語
      video: false, // 動画を取得するか
      keepVideoForPassedTests: false, // 成功時も動画を残すか
      disableScreenshots: false, // スクリーンショットを無効にするか
      fullPageScreenshots: true, // スクリーンショットを全画面にするか
      uniqueScreenshotNames: true, // スクリーンショット名をユニークにするか
      highlightElement: false, // エラー箇所をハイライトするか(不具合?)
      show: false, // 画面表示をするか
      trace: true, // 詳細な記録(htmlやスクリーンショット)を残すか
      keepTraceForPassedTests: false, // 成功時も詳細な記録を残すか
    },
  },
  ...
}

テスト作成

ref. Behaivior Driven Development | CodeceptJS

シナリオ記述

  • features にファイルを追加する。

    $ touch https://github.com/GOAMI-Takaaki/codeceptjs-hotel-planisphere/tree/main/features/login.feature
    
  • シナリオを記述する。

    login.feature

    Feature: ログイン
      シンプルなテキストインプットとボタンの画面です。
      ログイン情報はCookieに保存されます。
      会員登録画面で保存したユーザの他、登録済みのユーザ(下記)があります。
    
      Scenario: 定義済みユーザでログインができること
        Given ホームを開く。
          And ログインペ―ジに移動する。
          And "ichiro@example.com" "password"でログインする。
        Then マイペ―ジである事を確認する。
    
  • codecept.conf.ts の features に追記する。

    export const config: CodeceptJS.MainConfig = {
      ...
      gherkin: {
        features: [ 
          './features/login.feature', // 追記
          ...
        ],
      },
      ...
    }
    

ステップ実装

ref. Playwright Helper, Locators | CodeceptJS

  • step_definitions にファイルを追加する。

    $ touch ./step_definitions/login.ts
    $ touch ./step_definitions/home.ts
    $ touch ./step_definitions/mypage.ts
    
  • 各ステップを記述する。

    home.ts

    const { I } = inject();
    
    const URL = 'https://hotel.testplanisphere.dev/ja/index.html';
    Given('ホームを開く。', () => {
      // URLを開く
      I.amOnPage(URL);
    });  
    
    export {};
    

    login.ts

    const { I } = inject();
    
    Given('ログインペ―ジに移動する。', () => {
      I.click('ログイン', locate('nav'));
    });
    
    const login = (email: string, password:string) => {
      I.fillField('メールアドレス', email);
      I.fillField('パスワード', password);
      I.click('ログイン', '#login-button');
    };
    
    Given('{string} {string}でログインする。', login);
    
    export {};
    

    mypage.ts

    const { I } = inject();
    
    const URL = 'https://hotel.testplanisphere.dev/ja/mypage.html';
    
    Then('マイペ―ジである事を確認する。', () => {
      I.seeCurrentUrlEquals(URL);
    });
    
    export {};
    
  • codecept.conf.ts の step_definitions に追記する。

    export const config: CodeceptJS.MainConfig = {
      ...
      gherkin: {
        steps: [
          './step_definitions/home.ts', //追記
          './step_definitions/login.ts', //追記
          './step_definitions/mypage.ts', //追記
        ]
      },
      ...
    }
    

テスト実行

ref. Commands | CodeceptJS

# ログインを実行する。
$ npx codeceptjs run features/login.feature

# 特定のシナリオだけ実行する。
$ npx codeceptjs run --verbose --grep "定義済みユーザでログインができること"

# 全シナリオを実行する。
$ npx codeceptjs run 

プロジェクト作成してない場合

# ライブラリをインストールする。
$ npm ci

# Playwright の関連ライブラリをインストールする。
$ npx playwright install-deps

デバッグ実行

デバック用引数を指定して実行する。

$ npx codeceptjs run --verbose

ステップの実行状況を確認する。

ログイン --
  シンプルなテキストインプットとボタンの画面です。
  ログイン情報はCookieに保存されます。
  会員登録画面で保存したユーザの他、登録済みのユーザ(下記)があります。
  ✖ 定義済みユーザでログインができること in 7514ms

-- FAILURES:

  1) ログイン
      定義済みユーザでログインができること:
    expected url of current page "https://hotel.testplanisphere.dev/ja/mypage.html" to equal "https://hotel.testplanisphere.dev/ja/login.html"
  
  Scenario Steps:
  - I.seeCurrentUrlEquals("https://hotel.testplanisphere.dev/ja/mypage.html") at ./step_definitions/mypage.ts:26:5
  - I.click("ログイン", "#login-button") at login (./step_definitions/login.ts:24:5)
  - I.fillField("パスワード", "wrong") at login (./step_definitions/login.ts:23:5)
  - I.fillField("メールアドレス", "ichiro@example.com") at login (./step_definitions/login.ts:22:5)
  - I.click("ログイン", nav) at ./step_definitions/login.ts:10:5
  - I.amOnPage("https://hotel.testplanisphere.dev/ja/index.html") at ./step_definitions/home.ts:6:5

出力された画像や動画、トレースを確認する。

Artifacts:
- screenshot: ~/output/定義済みユーザでログ_1693978807.failed.png
- video: ~/output/videos/6bdb1195-5850-431b-8722-14924907c83c_定義済みユーザでログインができること.failed.webm
- trace: ~/output/trace/39dade40-4344-4e00-98d3-ea5953f89d14_定義済みユーザでログインができること.failed.zip

画面を表示して確認する。

codeceptjs.conf.ts

export const config: CodeceptJS.MainConfig = {
  helpers: {
    Playwright: {
      show: true // false -> true
    },
  },
};

変数を出力して確認する。

login.ts

const login = (email: string, password:string) => {
  console.log(`email=${email}`);
  I.fillField('メールアドレス', email);
  I.fillField('パスワード', password);
  I.click('ログイン', '#login-button');
};
Given('{string} {string}でログインする。', login);

Tips

複数データでシナリオ実行する。

Paramterized Testのような動作になる。

ref. Examples in Behavior Driven Development | CodeceptJS

mypage.feature

  • Scenario を Outline にする。
      Scenario Outline: 定義済みユーザの情報が表示されること
    
  • 変数を<カラム名>で指定する。
        Given ホームを開く。
          And ログインペ―ジに移動する。
          And "<email>" "<password>"でログインする。
        Then マイペ―ジである事を確認する。
          And メールアドレスが"<email>"である事を確認する。
          And 氏名が"<username>"である事を確認する。
          And 会員ランクが"<rank>"である事を確認する。
          ...
    
  • Examples にデータを表形式で記載する。
        Examples:
          | email               | password  | rank         | username  |...|
          |	ichiro@example.com  | password  | プレミアム会員 | 山田一郎  |...|
          |	sakura@example.com  | pass1234  | 一般会員      | 松本さくら |...|
          |	jun@example.com     | pa55w0rd! | プレミアム会員 | 林潤      |...|
          |	yoshiki@example.com | pass-pass | 一般会員      | 木村良樹   |...|
    

複数データでステップを実行する。

ref. Tables in Behavior Driven Development | CodeceptJS

  • ステップの下にデータを表形式で記載する。

    • ex. カラム名: planName

    plan.feature

      Scenario: 未ログイン状態でプラン一覧が表示されること
        Given ホームを開く。
          And 宿泊予約ペ―ジに移動する。
        Then プラン数が7である。
          And 以下のプランが表示されている。
          | planName               |
          | お得な特典付きプラン     |
          | 素泊まり                |
          | 出張ビジネスプラン       |
          | エステ・マッサージプラン |
          | 貸し切り露天風呂プラン   |
          | カップル限定プラン       |
          | テーマパーク優待プラン   |
    
  • ステップ実装

    plan.ts

    // 変数 table で受け取る。
    Then('以下のプランが表示されている。', (table: any) => { 
      // データを行単位に分割する。
      const rows = table.parse().hashes(); 
      for (const row of rows) { 
        // 値をカラム名で取得する。
        I.see(row.planName); 
      }
    });
    

共通で利用するステップを追加する。

  • step_files.ts に追記する。

    export = function() {
      return actor({
        // 独自定義の関数を追加する。
        seeNumberOfTabs: async (expected: number)=> {
          const actual = await this.grabNumberOfOpenTabs();
          assert(actual == expected, `期待されたタブの数は${expected}だが、実際は${actual}である。`);
        },
      });
    }
    
  • ステップで利用する。

    reserve.ts

    Given('宿泊予約確認が閉じられる。', () => {
      // どこからでも利用可能になる。
      I.seeNumberOfTabs(1);
    });
    

アクションをステップにする。

  • シナリオに記載できるよう、ステップとしてラップする。

    action.ts

    Given('{int}秒待つ。', (sec: number) => {
      I.wait(sec);
    });
    
  • シナリオに記載する。

    Scenario: 未入力でエラーとなること
      Given ホームを開く。
        And 3秒待つ。
        And ログインペ―ジに移動する。
        ...
    

TODO

修正

  • ポップアップ不具合
  • 金額計算不具合

追加

最後に

本記事が自動化検討のお役に立てば幸いです。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3