Skip to content

goataka/codeceptjs-hotel-planisphere

Repository files navigation

hotel-example-codeceptjs-ja

自動化練習サイト「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 ./features/login.feature
  • シナリオを記述する。

    login.feature

    Feature: ログイン
      シンプルなテキストインプットとボタンの画面です。
      ログイン情報はCookieに保存されます。
      会員登録画面で保存したユーザの他、登録済みのユーザ(下記)があります。
    
      Scenario: 定義済みユーザでログインができること
        Given ホームを開く。
          And ログインペ―ジに移動する。
          And "[email protected]" "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("メールアドレス", "[email protected]") 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

修正

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

追加

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published