2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【QualityForward】自動テスト安定性レポートを簡単に活用できるGitHub Actionsを作りました

Last updated at Posted at 2024-09-25

自動テストツールのテスト結果をより簡単にQualityForward1の自動テスト安定性レポートに連携できるGitHub Actionsを作りました。
今回は自動テスト安定性レポートの概要と作成したGitHub Actionsの使用方法について説明します。

サマリ

  • 自動テスト安定性レポートは、自動テストの結果や実行時間を時系列で分析・可視化し、自動テストメンテナンスをサポートする
  • 今回作成したGitHub Actionsを使用することで、動テスト結果の出力ファイル(json、xml)を簡単にQualityForwardの自動テスト安定性レポートに連携できる

自動テスト安定性レポートとは何か

自動テスト安定性レポート2は自動テストの結果や実行時間を時系列で分析し、安定性を可視化することで、自動テスト運用を支援するサービスです。

自動テストには開発スピードの向上、テストの効率性の向上など、プロジェクトに応じてさまざまな目的3があります。しかし、これらの目的を達成するためには自動テストのメンテナンスが不可欠です。メンテナンスが不十分だと、テスト結果の信用性が低下し、形骸化する可能性があります。

たとえば、以下のような項目があげられます。

  • フレーキーなテスト4があるかどうか
  • 極端に実行時間が増えていないかどうか
  • 不要な自動テスト(確認したいことがかぶっている、毎日実施すべきテストではない等)が含まれていないかどうか

このようなメンテナンスを行うためには、日々の自動テスト結果の集計や分析が必要です。そこで、自動テスト安定性レポートを活用すると、日々の集計や分析をより楽に実施でき、自動テストのメンテナンスが容易になります。

自動テスト安定性レポートが提供するもの

現在、提供しているものは以下の4つです。

  1. 自動テスト全体の結果分布と自動テストスイート5ごとの失敗数分布を円グラフで表現
  2. 自動テストスイート×実行環境6ごとの実行回数および累積成功・失敗率の一覧表示
  3. 自動テストスイート詳細内のテスト結果分布を円グラフで表現
  4. 自動テストスイート×データパターン7ごとのテスト結果の累積、実行時間およびテストサイクル8ごとの結果を一覧表示

各機能の詳細は以下のセクションで説明します。

1. 自動テスト全体の結果分布と自動テストスイートごとの失敗数分布を円グラフで表現

自動テスト結果分布表示.PNG

全体の何割が成功/失敗/エラー/スキップしているのか、またどのテストスイートでよく失敗しているのかが可視化され、改善すべきテストスイートを容易に識別できます。

QualityForwardのテスト結果ステータスについて

QualityForwardでは自動テスト結果を以下のように定義しています。

ステータス 定義
pass 自動テスト成功
fail 自動テスト失敗(アサーションエラー)
error テストの結果判定ができず、なんらかの理由でランタイムエラーが発生
skip 対象の自動テストケースを実施しなかった

2. 自動テストスイート×実行環境ごとの実行回数および累積成功・失敗率の一覧表示

自動テストスイート一覧.PNG

実行環境で成功率に差異がないか、よく実行している自動テストスイートはどれかを確認できます。自動テストスイート(auto_test_suite_external_key)ごとにソートして表示されます。

3. 自動テストスイート詳細内のテスト結果分布を円グラフで表現

テストスイート内の結果分布.PNG

テストスイート内でどのくらいのテストが成功/失敗/エラー/スキップしているのかを一目で確認できます。

4. 自動テストスイート×データパターンごとのテスト結果の累積、実行時間およびテストサイクルごとの結果を一覧表示

テストケース一覧.PNG

データパターンごとに成功率や実行時間の差異がないか、断続的に失敗しているテストケースがないかを確認できます。これは、フレーキーなテストの発見に役立ちます。

また、テストサイクルごとの結果に補足メモや結果詳細(ログやSaaSツールの自動テスト結果)のリンクを貼り付けることができ、すぐに詳細を確認できます。テストスイートごとの自動テスト結果は、前回のテストサイクルと比べて実行時間が伸びた場合に赤で強調表現され、自動テストスクリプトの改善のきっかけになります。

さらに、詳細な分析を行うためのcsv出力もあります。

自動テスト安定性レポートのデータ投入方法

QualityForward APIをもとに自動テスト結果データを整形し、QualityForwardに連携すると自動テスト安定性レポートに自動テスト結果が表示されます。

Payload

{
  "auto_test_suite_external_key": "string",
  "auto_test_suite_name": "string",
  "auto_test_cycle_name": "string",
  "auto_execution_device_external_key": "string",
  "auto_execution_device_name": "string",
  "auto_test_results": [
    {
      "auto_test_case_external_key": "string", 
      "auto_test_case_name": "string", 
      "auto_execution_pattern_external_key": "string", 
      "auto_execution_pattern_name": "string",
      "result": "pass",
      "execution_time_taken": 0,
      "info_url": "string",
      "remark": "string" 
    }
  ]
}

Payloadのプロパティ

プロパティ 説明
auto_test_suite_external_key 自動テストスイートID(必須) sample_test_suite
auto_test_suite_name 自動テスト安定性レポートに表示される自動テストスイート名(任意)
未設定の場合はauto_test_suite_external_keyで設定した値が表示
サンプル自動テストスイート
auto_test_cycle_name 自動テストサイクル名(必須) 2024-9-28
auto_execution_device_external_key 自動テスト実施環境ID(任意) ios17_4
auto_execution_device_name 自動テスト安定性レポートに表示される環境名(任意) iOS+17.4
auto_test_results 自動テスト結果の配列
各テストケースの実行結果を格納する
-
auto_test_case_external_key 自動テストケースID sample_test_case
auto_test_case_name 自動テスト安定性レポートに表示される自動テストケース名(任意)
未設定の場合はauto_test_case_external_keyで設定した値が表示
auto_execution_pattern_external_key 自動テストケースで使用したデータパターンID(任意)
データ駆動テストパターンなどで使用する
data_pattern1
auto_execution_pattern_name 自動テスト安定性レポートに表示されるデータパターン名(任意) データパターン1
result 自動テスト結果(必須) pass
execution_time_taken テスト実行時間(単位はミリ秒) 1000
info_url 自動テスト結果に関する詳細情報が記載されたWebページのURL https://example.com
remark 備考 要確認項目あり

詳細はQualityForwardのAPI仕様書をご確認ください。

curlコマンドの例

以下はcurlコマンドでデータを連携する例です。


curl --request POST \
  --header "Content-Type: application/json" \
  --data '{
    "api_key": "[生成したAPIキー]",
    "auto_test_suite_external_key": "sample",
    "auto_test_suite_name": "サンプル自動テスト",
    "auto_test_cycle_name": "2024-07-03",
    "auto_execution_device_external_key": "ios17_4",
    "auto_execution_device_name": "iOS+17.4",
    "auto_test_results": [
      {
        "auto_test_case_external_key": "auto_sample1",
        "result": "pass",
      },
      {
        "auto_test_case_external_key": "auto_sample2",
        "auto_test_case_name": "サンプル自動テストケース2",
        "auto_execution_pattern_external_key": "dataScenario1",
        "auto_execution_pattern_name": "データシナリオ1",
        "result": "fail",
        "execution_time_taken": 100,
        "info_url": "https://example.com",
        "remark": "sample_test2"
      },
        "auto_test_case_external_key": "auto_sample2",
        "auto_test_case_name": "サンプル自動テストケース2",
        "auto_execution_pattern_external_key": "dataScenario2",
        "auto_execution_pattern_name": "データシナリオ2",
        "result": "pass",
        "execution_time_taken": 100,
        "info_url": "https://example.com",
        "remark": "sample_test3"
      }
    ]
  }' \
  "https://cloud.veriserve.co.jp/api/v2/auto_test_suites"

自動テスト安定性レポートにデータを投入するためには、自動テスト結果を整形する必要があるため、専用のスクリプトが必要になります。
レポート投入用の準備工数が取れない方向けに、まずは自動テスト安定性レポートを簡単に体験できるよう、GitHub Actionsを作成しました。

このアクションは、自動テストで出力されたjsonおよびxmlファイルを整形し、QualityForwardの自動テスト安定性レポートに連携します。

自動テスト安定性レポート連携アクションの使い方

実際にGitHubActionsを使ったやり方を説明します。

大枠な流れ

本アクションを使う流れは以下です。

  1. 自動テストツール(OSS or SaaS)で自動テストを実行し、結果をxmlもしくはjsonファイルで出力する
  2. アクションを使用して、出力したファイルを読み込みQualityForwardの自動テスト安定性レポートに連携する

サンプルYamlファイル

以下はpytest-playwrightを使った自動テスト結果をQualityForwardの自動テスト安定性レポートに連携しているyamlファイルです。

有償テストツールのサンプルYamlファイルについては、本アクションのREADMEをご確認ください

.yaml
name: e2e Test and Publish Result

on:
  workflow_dispatch:

jobs:
  RunPytest:
    runs-on: ubuntu-latest
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      # リポジトリをチェックアウト
      - uses: actions/checkout@v4

      - name: Setup Python 3.12
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install poetry
          poetry config virtualenvs.create false
          poetry install
          playwright install --with-deps

      # テスト実行.
      - name: PyTest
        run: |
          python -m pytest tests/ --browser firefox --browser chromium --browser webkit --junit-xml results/pytest.xml
        continue-on-error: true

      # 自動安定性レポート連携アクションを使って、自動テスト結果を連携する
      - name: link automated test results
        uses: QualityForward/test-stability-report-sync@v1.2.0
        with:
          api-key: ${{ secrets.API_KEY }}
          file-path: results/pytest.xml
          test-framework: pytest-playwright
自動テスト結果ファイル(xml)

<?xml version="1.0" encoding="utf-8"?><testsuites><testsuite name="pytest" errors="0" failures="3" skipped="0" tests="18" time="105.426" timestamp="2024-09-18T07:57:48.023993" hostname="967691676da3"><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_before_today[firefox]" time="16.172" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_no_name[firefox]" time="3.343" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_after_90days[firefox]" time="3.170" /><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[firefox-3d-breakfast]" time="4.624" /><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[firefox-2d-early]" time="13.668"><failure message="AssertionError: Locator expected to have text '合計 16,000円(税込み)'&#10;Actual value: 合計 15,000円(税込み) &#10;Call log:&#10;LocatorAssertions.to_have_text with timeout 10000ms&#10;  - waiting for locator(&quot;#total-bill&quot;)&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt; &lt;/h3&gt;&#10;  -   unexpected value &quot; &quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt; &lt;/h3&gt;&#10;  -   unexpected value &quot; &quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;">self = &lt;test_reserve.TestReserve object at 0x7efd4225d790&gt;, page = &lt;Page url='https://hotel.testplanisphere.dev/ja/confirm.html'&gt;, date_from = '2024/10/02', term = 2, breakfast = False, early = True, sightseeing = False
total = '16,000円'

    @pytest.mark.parametrize(
        "date_from,term,breakfast,early,sightseeing,total",
        [
            ("2024/10/01", 3, True, False, False, "24,000円"),
            ("2024/10/02", 2, False, True, False, "16,000円"),
            ("2024/10/03", 1, False, False, True, "8,000円"),
        ],
        ids=["3d-breakfast", "2d-early", "1d-sightseeing"],
    )
    def test_reserve(
        self,
        page: Page,
        date_from: str,
        term: int,
        breakfast: bool,
        early: bool,
        sightseeing: bool,
        total: int,
    ) -&gt; None:
        """宿泊金額が正しいことを確認するテスト"""
        page_reserve = reserve.PageReserve(page)
        page_reserve.navigate()
    
        # 宿泊内容の設定
        page_reserve.set_date_and_term(date_from, term)
        page_reserve.set_headcount(1)
        page_reserve.select_plan(breakfast, early, sightseeing)
        page_reserve.input_name("てすと太郎")
        page_reserve.select_contact("no")
    
        # 予約内容の確認
        page_reserve.confirm()
    
        # 期待値確認
        page_confirm = confirm.PageConfirm(page)
&gt;       expect(page_confirm.total_bill).to_have_text(f"合計 {total}(税込み)", timeout=10000)
E       AssertionError: Locator expected to have text '合計 16,000円(税込み)'
E       Actual value: 合計 15,000円(税込み) 
E       Call log:
E       LocatorAssertions.to_have_text with timeout 10000ms
E         - waiting for locator("#total-bill")
E         -   locator resolved to &lt;h3 id="total-bill"&gt; &lt;/h3&gt;
E         -   unexpected value " "
E         -   locator resolved to &lt;h3 id="total-bill"&gt; &lt;/h3&gt;
E         -   unexpected value " "
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"

tests/test_reserve.py:42: AssertionError</failure></testcase><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[firefox-1d-sightseeing]" time="3.920" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_before_today[chromium]" time="9.618" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_no_name[chromium]" time="1.675" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_after_90days[chromium]" time="2.273" /><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[chromium-3d-breakfast]" time="2.308" /><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[chromium-2d-early]" time="11.072"><failure message="AssertionError: Locator expected to have text '合計 16,000円(税込み)'&#10;Actual value: 合計 15,000円(税込み) &#10;Call log:&#10;LocatorAssertions.to_have_text with timeout 10000ms&#10;  - waiting for locator(&quot;#total-bill&quot;)&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt; &lt;/h3&gt;&#10;  -   unexpected value &quot; &quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;">self = &lt;test_reserve.TestReserve object at 0x7efd4225e180&gt;, page = &lt;Page url='https://hotel.testplanisphere.dev/ja/confirm.html'&gt;, date_from = '2024/10/02', term = 2, breakfast = False, early = True, sightseeing = False
total = '16,000円'

    @pytest.mark.parametrize(
        "date_from,term,breakfast,early,sightseeing,total",
        [
            ("2024/10/01", 3, True, False, False, "24,000円"),
            ("2024/10/02", 2, False, True, False, "16,000円"),
            ("2024/10/03", 1, False, False, True, "8,000円"),
        ],
        ids=["3d-breakfast", "2d-early", "1d-sightseeing"],
    )
    def test_reserve(
        self,
        page: Page,
        date_from: str,
        term: int,
        breakfast: bool,
        early: bool,
        sightseeing: bool,
        total: int,
    ) -&gt; None:
        """宿泊金額が正しいことを確認するテスト"""
        page_reserve = reserve.PageReserve(page)
        page_reserve.navigate()
    
        # 宿泊内容の設定
        page_reserve.set_date_and_term(date_from, term)
        page_reserve.set_headcount(1)
        page_reserve.select_plan(breakfast, early, sightseeing)
        page_reserve.input_name("てすと太郎")
        page_reserve.select_contact("no")
    
        # 予約内容の確認
        page_reserve.confirm()
    
        # 期待値確認
        page_confirm = confirm.PageConfirm(page)
&gt;       expect(page_confirm.total_bill).to_have_text(f"合計 {total}(税込み)", timeout=10000)
E       AssertionError: Locator expected to have text '合計 16,000円(税込み)'
E       Actual value: 合計 15,000円(税込み) 
E       Call log:
E       LocatorAssertions.to_have_text with timeout 10000ms
E         - waiting for locator("#total-bill")
E         -   locator resolved to &lt;h3 id="total-bill"&gt; &lt;/h3&gt;
E         -   unexpected value " "
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"

tests/test_reserve.py:42: AssertionError</failure></testcase><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[chromium-1d-sightseeing]" time="3.350" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_before_today[webkit]" time="7.449" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_no_name[webkit]" time="2.115" /><testcase classname="tests.test_invalid_reserve.TestInvalidReserve" name="test_after_90days[webkit]" time="1.547" /><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[webkit-3d-breakfast]" time="2.161" /><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[webkit-2d-early]" time="11.799"><failure message="AssertionError: Locator expected to have text '合計 16,000円(税込み)'&#10;Actual value: 合計 15,000円(税込み) &#10;Call log:&#10;LocatorAssertions.to_have_text with timeout 10000ms&#10;  - waiting for locator(&quot;#total-bill&quot;)&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt; &lt;/h3&gt;&#10;  -   unexpected value &quot; &quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt; &lt;/h3&gt;&#10;  -   unexpected value &quot; &quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;&#10;  -   locator resolved to &lt;h3 id=&quot;total-bill&quot;&gt;合計 15,000円(税込み)&lt;/h3&gt;&#10;  -   unexpected value &quot;合計 15,000円(税込み)&quot;">self = &lt;test_reserve.TestReserve object at 0x7efd4225e330&gt;, page = &lt;Page url='https://hotel.testplanisphere.dev/ja/confirm.html'&gt;, date_from = '2024/10/02', term = 2, breakfast = False, early = True, sightseeing = False
total = '16,000円'

    @pytest.mark.parametrize(
        "date_from,term,breakfast,early,sightseeing,total",
        [
            ("2024/10/01", 3, True, False, False, "24,000円"),
            ("2024/10/02", 2, False, True, False, "16,000円"),
            ("2024/10/03", 1, False, False, True, "8,000円"),
        ],
        ids=["3d-breakfast", "2d-early", "1d-sightseeing"],
    )
    def test_reserve(
        self,
        page: Page,
        date_from: str,
        term: int,
        breakfast: bool,
        early: bool,
        sightseeing: bool,
        total: int,
    ) -&gt; None:
        """宿泊金額が正しいことを確認するテスト"""
        page_reserve = reserve.PageReserve(page)
        page_reserve.navigate()
    
        # 宿泊内容の設定
        page_reserve.set_date_and_term(date_from, term)
        page_reserve.set_headcount(1)
        page_reserve.select_plan(breakfast, early, sightseeing)
        page_reserve.input_name("てすと太郎")
        page_reserve.select_contact("no")
    
        # 予約内容の確認
        page_reserve.confirm()
    
        # 期待値確認
        page_confirm = confirm.PageConfirm(page)
&gt;       expect(page_confirm.total_bill).to_have_text(f"合計 {total}(税込み)", timeout=10000)
E       AssertionError: Locator expected to have text '合計 16,000円(税込み)'
E       Actual value: 合計 15,000円(税込み) 
E       Call log:
E       LocatorAssertions.to_have_text with timeout 10000ms
E         - waiting for locator("#total-bill")
E         -   locator resolved to &lt;h3 id="total-bill"&gt; &lt;/h3&gt;
E         -   unexpected value " "
E         -   locator resolved to &lt;h3 id="total-bill"&gt; &lt;/h3&gt;
E         -   unexpected value " "
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"
E         -   locator resolved to &lt;h3 id="total-bill"&gt;合計 15,000円(税込み)&lt;/h3&gt;
E         -   unexpected value "合計 15,000円(税込み)"

tests/test_reserve.py:42: AssertionError</failure></testcase><testcase classname="tests.test_reserve.TestReserve" name="test_reserve[webkit-1d-sightseeing]" time="4.966" /></testsuite></testsuites>

では、本アクションの設定項目を説明します。

自動安定性レポート連携アクションの設定項目(必須項目)

api-key

Quality ForwardのプロジェクトAPIキー9を設定してください。
GitHub Actionsでのシークレットの使用を推奨

file-path

自動テストの実行結果を格納したファイルパスを設定してください。
※ワイルドカードで指定可能です
※テストフレームワークによって、有効なファイル拡張子が異なります

test-framework

使用したテストフレームワークを設定してください。

設定できるテストフレームワーク

テストフレームワーク 出力ファイル拡張子 備考
junit xml データパターン未対応
pytest xml -
nodejs-playwright xml データパターン未対応
pytest-playwright xml -
magicpod json -
mabl-deploy-event10 json -

上記以外のテストフレームワークでも、junit-xml形式のファイルで出力できるものはjunitを選択してください

自動安定性レポート連携アクションの設定項目(任意項目)

auto_test_suite_external_key

連携する自動テストスイート名を設定してください。

設定しない場合の自動テストスイート名

テストフレームワーク 自動テストスイート名
junit,nodejs-playwright testsuite要素のname属性
pytest,pytest-playwright testcase要素のclassname属性
最後のドット以降を取得
(例)test.testSuiteの場合、”testSuite”を取得
magicpod テスト一括実行設定名
mabl-deploy-event テストプランid

auto_test_cycle_name

連携する自動テストサイクル名を設定してください。

設定しない場合の自動テストサイクル名

テストフレームワーク 自動テストサイクル名
junit,mabl-deploy-event アクション実行日時
pytest,nodejs-playwright,pytest-playwright testsuite要素のtimestamp属性
magicpod テスト一括実行終了日時

auto_execution_device_external_key

連携する自動テスト実施環境名を設定してください。

設定しない場合の自動テスト実施環境名

テストフレームワーク 自動テスト実施環境名
junit,pytest,magicpod 標準環境と表示
nodejs-playwright testsuite要素のhostname属性
pytest-playwright testcase要素のname属性から取得
(例)test_sample[chromium-data-driven]の場合、chromiumを取得
mabl-deploy-event 各 journey_executions の browser_type 属性
browser_type 属性がない場合は標準環境

実施後の表示

yamlファイル作成後、GitHubActionsのワークフローで実行します。
成功するとコンソールに「The integration of automated test results has been completed.」が表示されます。

GitHubActions結果.PNG

QualityForwardの自動テスト安定性レポートを確認すると投入した自動テスト結果を確認できます。

<自動テスト全体>

連携結果.PNG

<自動テストスイート詳細>

連携後の自動テストスイート詳細.PNG

自動テストスイート名や自動テストケース名等をカスタマイズしたい場合、独自のデータ整形スクリプトを作成することを推奨します。

おわりに

今回はQualityForwardの自動テスト安定性レポートの概要と簡単に活用できるGitHub Actionsについて説明しました。

GitHub Actionsでもまだまだ対応しきれていないフレームワークがあるため、これからも対応フレームワークを増やしていく予定です。
自動テスト結果分析の時間を短くしたい、QualityForwardの自動テスト安定性レポートの表示内容をとりあえず見てみたい方は、ぜひこのアクションを試してみてください。

この記事が少しでもお役に立てば幸いです。
ご意見やご質問がございましたら、ぜひフィードバックをお寄せください。

参考資料

  1. QualityForwardはテストの資産管理、実行管理、結果管理のためのテスト管理ツールです。テスト実行の進捗状況や、計画に対する予実、検出されたバグなどを自動的に集計し可視化します。これにより、テスト実行者とテスト管理者のやり取りや集計を効率化できます。詳細はQualityForwardの製品ページをご確認ください。

  2. QualityForwardのテスト自動化支援機能について

  3. ほかには機能カバレッジの拡大、総テストコストの削減、手動テスト担当者が行えないテストの実施、テスト実行期間の短縮やテスト実行頻度の向上およびテストサイクルに要する時間の短縮などがあります。(JSTQB ALシラバス(Specialist)テスト自動化エンジニア Version 2016.J01より)

  4. 断続的に失敗するテストのこと。この原因はテストケース、テスト対象、テストフレームワーク等いろんな要因があるため、調査や修正に時間がかかるため、改善(テストの削除やスクリプトの修正)が必要になる。

  5. QualityForwardではソフトウェアテストの目的や対象ごとに複数のテストケースをまとめたものと定義しています。たとえばテストケースをExcelで1シートずつに分けて管理している場合、以下の図のようにそれらを1つ1つのテストスイートに分けていくイメージです。基本用語 – QualityForwardサポートサイトのテストスイートより

  6. webの場合、chrome、Edge、firefoxなどがあります。

  7. データ駆動で用いられるデータのこと

  8. テストを実施する最小単位の期間です。1つのテストスイートに対して「テスト開始日」と「テスト終了日」を定めることで、1つのテストサイクルになります。基本用語 – QualityForwardサポートサイトのテストサイクルより

  9. APIキーの生成はQualityForwardのAPIキーの発行手順をご確認ください。

  10. デプロイ結果のサマリー取得のデータ(詳細は mabl ヘルプページ - デプロイイベントを参照のこと)

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?