TVerにおけるテスト自動化の歩み

こんにちは。TVerのAutomationチームで、テストや開発業務の効率化・自動化を担当している城間です。

TVerの開発組織では、2025年からリリースサイクルの短縮に取り組み、リリーストレイン(複数チームが同期して決められたサイクルでリリースを行う運用)を導入しています。
いま現在、Web、iOS、Androidの各プラットフォームはおおむね隔週でのリリースを実現できており、その背景や経緯の詳細は弊社の「計測から始める品質とスピードの両立 - TVerの開発組織改革2年間の記録」で紹介しています。

techblog.tver.co.jp

リリースサイクルを短縮するためには、リリースのたびに行うリグレッションテストを短時間かつ確実に回せる仕組みが重要になります。

しかしながら、2025年の春先まで、TVerのリグレッションテストはすべて手動で行われていました。 リリースサイクルを短縮しようとすればするほど、テスト工数が線形に積み上がっていく構造で、これを解消しない限りリリーストレインの持続的な運用はできません。

リリーストレインの運用をはじめるにあたり、迅速かつ安定して検証ができる自動テスト基盤を構築することは、私たちAutomationチームの重大なミッションでした。

本稿では、私たちが約1年間、戦略策定から運用に至るまで、段階を踏んでテストの自動化を進めてきた過程とその取り組みを紹介したいと思います。

抱えていた課題

2025年の初頭、リリース前に実施していたリグレッションテストの所要工数は、概算で以下のとおりでした。

プラットフォーム リグレッションテストの所要工数
Web 約8.6人日
iOS 約4.9人日
Android 約5.1人日

これは、UIのリグレッションや、動画・ライブの目視確認、広告配信の検証など、複数のテストスイートにまたがる総工数の見積もりです。 継続的な機能改善とリリースを必要とするTVerサービスにおいて、毎回リリース前に数日分の検証工数を割き続ける運用には、いくつかの構造的な限界がありました。

  • テストの実行に一定の工数を要し、短サイクルでのリリース対応が困難
  • テスト実行の一部が属人化しており、チーム全体の生産性に波が出やすい
  • 人的ミスによる軽微な不具合の取りこぼしが発生しうる

また、TVerのテストには「動いて見える」だけでは検証できない領域があります。

ユーザーの操作の裏で送信されるTVerタグ(行動ログ)視聴データの計測用ビーコン広告に関連するURLパラメータ置換処理といったログ系の整合性確認です。
これらはTVerの主要KPI、ひいては放送局・広告主に対する説明責任に直結する重要な検証項目ですが、マニュアルではHTTPプロキシで通信ログを目視確認するしかなく、QAチームへの負荷も再現性も大きな課題でした。

戦略の策定から始める

私たちはテストの自動化を導入するにあたり、フレームワークの選定よりも前に、まずリグレッションテストの戦略を明文化することから始めました。
自動テストを持続可能なものにするためには、何を、どこまで、どのように自動化するのかを最初に定義しておく必要があります。

スコープの定義

戦略ドキュメントでは、テストピラミッドの考え方を参考に、リグレッションテストを正常系のユーザーシナリオに絞ることに決めました。
細かな分岐や例外は単体・結合テストに任せ、リグレッションテストでは主要導線がend-to-endで機能していること、ユーザー操作によって生じるCRUD処理(作成・読み取り・更新・削除)が正しく動くことを確認する。ピラミッドの頂点に置くべき範囲に限定する、という方針です。

加えて、前述したログ系の整合性確認も、主要KPIへの影響度が高いことからリグレッションテストのスコープに含めました。
ユーザー操作の裏で送信されるログまで含めて回帰を担保する、TVerのサービス特性を反映した判断です。

実行契機の整理

次に自動テストを「いつ、どのトリガーで、何のために回すか」を整理しました。 いまは主に次の3つの実行契機で回しています。

# 実行契機 目的 トリガー
1 mainブランチの変更 マージした変更のデグレードを早く検知し、不具合を溜め込まない mainブランチへのpush
2 リリース前検証 リリースコードの品質を確認し、リリース可否の判断材料とする releaseブランチへのpush
3 テスト環境のデイリーチェック 見逃した不具合や環境・依存の変化による不具合を早期に検知する CI日次実行

自動テストを導入する目的は単一ではなく、同じテストアセットでも、走らせるタイミングを変えれば狙えるリスクが変わります。
この前提を最初に揃えることで、後から個別の運用設計をブレずに進められました。

E2Eテスト環境を独立して用意する

戦略の次に着手したのは、E2Eテスト専用の検証環境を各プラットフォームで揃えて用意することでした。

通常の開発環境やステージング環境を流用すると、開発作業や手動テストの副作用が自動テストの結果に影響してしまい、再現性が大きく損なわれます。 テストごとに「いまの環境はどんな状態か」を気にしないと結果を信頼できない状況は、自動化の前提を崩します。

私たちはWeb/iOS/AndroidのそれぞれのOSで、E2Eテスト専用の環境を共通で見るようにしました。実装方法はOSごとに異なりますが、テストが実行されるバックエンドは同じ独立した環境を指しています。

データの独立性とテストの再現性、この2つを早い段階で担保することが、後の運用フェーズで「テストを信じてリリース判断する」という文化を作るうえで重要でした。

E2Eテスト専用の検証環境

リグレッションテストそのものを見直す

戦略と環境が用意できた後、私たちは自動化の実装に進む前に、もうひとつ大きな工程を挟みました。 既存のリグレッションテスト自体の作り直しです。

既存のリグレッションテストは、長らく画面の要素単位で書かれていました。
たとえば「シリーズ名が表示されていること」「エピソード名が表示されていること」「制作局が表示されていること」「配信終了日時が表示されていること」のように、画面のパーツがひとつずつ列挙され、それぞれを確認していくテストです。

このスタイルは、確認漏れを防ぐという観点では一定の意味がありましたが、自動化を視野に入れると、以下のような問題が見えてきました。

  • テストの単位がユーザーの目的と乖離している:「要素Aの表示確認」「要素Bの表示確認」と並んでいても、それが何のユーザーシナリオを担保しているのかが不明瞭
  • 項目数が膨らみやすい:同じ画面の要素を画面ごとに繰り返し確認することになり、機械的な確認項目が無限に増えていく
  • 自動化したときの価値が見えにくい:1要素1アサーションのテストを大量に走らせても、ユーザー体験のリスクをどこまで担保できているかが定量化しにくい

そこで私たちは、リグレッションテストをユーザーシナリオに沿ったend-to-endのテストに書き直しました。
「ユーザーが何をしようとしていて、そのために画面をどう操作し、結果として何が起きるべきか」を一つのシナリオにまとめ、その流れの中で要素の表示や挙動を検証する形に変えていきました。

書き直しに合わせて、テストスイートそのものの最適化も同時に進めました。
検証デバイスの精査、テストファイルの命名規則の整理、テストデータの記述ルール化、不具合があっても次回対応となるケースの除外といった、自動化と独立しても価値のある手動テストの整理も、自動化準備と並行して進めました。

テストケースの書き直しと最適化による効果は2つありました。

  1. テストの意義そのものが明確になった:1つのテストケースが「ユーザーがこれを達成できる」という保証単位になり、優先度づけやスコープの取捨選択が判断しやすくなった
  2. 自動化との相性が大幅に上がった:E2Eテストフレームワークは、もともとユーザー操作を起点としたシナリオの記述に最適化されており、書き直し後のテストはほぼそのまま自動化のテンプレートに乗せられる形になった

「既存のテストをそのまま自動化する」のではなく、自動化を視野に入れてテストそのものを再設計する。 この順序が、その後の実装フェーズの速度と質を大きく押し上げました。

ツールの選定

自動化ツールには、大きく分けてコード型OSS(Playwright/Cypress/Seleniumなど)ローコードSaaS(Autify/MagicPod/mablなど)の2系統があります。 私たちは双方を比較したうえで、最終的にコード型OSS(Web: Playwright、iOS: XCUITest、Android: Espresso)を選定しました。

選定にあたって特に重視したのは、プロダクトの開発サイクルとの親和性メンテナンス性です。

  • ランニングコストを抑えられる:実行回数やシナリオ数に制限がなく、追加コストなしでスケールできる
  • プロダクトの開発プロセスに乗せやすい:各プラットフォームの開発言語と同じ言語でテストを書ける。Page ObjectモデルやCI/CDへの組み込みも、エンジニアが普段使うワークフローに載せやすい
  • 柔軟性が高い:HTTPリクエストの傍受やフィクスチャの差し替えなど、TVer特有の検証要件にあわせて拡張できる

SaaSにはレコーディングによるテスト量産しやすさという強みがある一方、メンテナンス性や実行制限、ランニングコストの面で、私たちが向かいたい方向とはトレードオフが大きいと判断しました。

設計上の工夫

工夫① 「テストの安定性を最優先し、メンテナンスコストを最小限に抑える」

実装方針として最初に掲げたのが、この一文です。E2Eテストは作るより維持するほうがはるかにコストがかかるため、設計のすべての判断をこの軸で評価することにしました。具体的な設計は以下のとおりです。

Page Object Model

「画面」を1つのクラスに抽象化し、テストはそのメソッドを呼び出すだけにすることで、UI変更の影響範囲をクラス1つに閉じ込めます。 さらに、3つのOSでPage Objectのファイル名を揃えるルールを徹底しました。

Web:     e2e/pages/EpisodePage.ts
iOS:     TVerLibrary/UITests/PageObjects/EpisodePage.swift
Android: app/src/androidTest/.../e2e/pages/EpisodePage.kt

同じ「エピソード詳細画面」を扱うクラスは、どのOSでもEpisodePageという名前を持ちます。
これにより、新しいテストを書くエンジニアがどのOSでも同じ感覚で実装でき、レビュー時にも他OSとの比較がしやすくなりました。

画面遷移を戻り値の型で表現する

Page Objectのメソッドは、画面遷移を伴う場合に遷移先のPage Objectを返す設計にしています。 これによって、テストコードが「画面A → 画面B → 画面C」というユーザー操作の流れをそのまま読み下せるようになります。

// iOSのPage Object設計(一部抜粋)
struct SettingsPage: PageObject {
    let app: XCUIApplication

    init(app: XCUIApplication) throws {
        self.app = app
        try waitForPageToLoad()
    }

    func tapNotificationButton() throws -> NotificationPage {
        notificationButton.waitAndTap()
        return try NotificationPage(app: app)
    }

    private func waitForPageToLoad() throws {
        try settingsTitle.waitForExistenceOrThrow(timeout: 10)
    }
}

フェイルファストで余計な待機を消す

iOSではPage Objectのinit内で前提となる要素の待機を行い、見つからなかった場合はthrowsで即座にテストを中断します。 途中の画面遷移に失敗したテストが、その後の操作で何度もタイムアウトを待つ、という典型的な無駄を排除する設計です。

sleepを書かない

固定のsleepはflakyの温床になるため、ヘルパー関数で待機を抽象化しています。
iOSではwaitAndTap() / waitAndType()、AndroidではwaitAndClickById() / waitAndClickByText()のような形で、すべての操作に「要素が利用可能になるまで待つ」処理を組み込んでいます。

工夫② APIでテストの冪等性を担保する

E2Eテストには、前回の実行が次の実行を壊す順序依存の問題がついて回ります。
「お気に入り登録できる」テストを2回連続で実行すれば、2回目は「すでに登録済み」状態となって挙動が変わってしまう、という類のものです。

私たちはテストごとに、必要な前提状態をAPIで直接揃えるアプローチを採用しています。

  • テスト前: 「あとで見る」をクリア、視聴履歴を消去、お気に入りを全削除する
  • テスト中: UIではなくAPIでテストデータを生成する(テスト観点に直接関係しない手順を省略)
  • テスト後: 状態をリセットして副作用を残さない

UI経由でこれらを行うよりも圧倒的に高速で、テストが何を前提にしているかがコードから明示的に読み取れるという副次的なメリットもあります。

同様の発想で、Feature flagのような実行時に変わりうる外部設定もテスト用の値に固定しています。
テスト中に配信内容が切り替わると結果がぶれるため、E2Eテスト側で取得処理を差し替えて外部依存を切り離し、再現性を保っています。

工夫③ テストを並列で動かして実行時間を抑える

E2Eテストはもともと実行コスト・メンテナンスコストの大きいテストです。ケース数が増えれば実行時間もメンテナンス負荷も膨らみ、開発のフィードバックループを遅くする要因になりかねません。
カバレッジの広さもテストにとっては重要ですが、それと引き換えにフィードバックの遅さを受け入れてしまうと、自動化の効果が損なわれていきます。

このトレードオフを抑えるために、複数のレイヤーで並列性を確保する設計にしています。

テストレベルの並列化 WebではPlaywrightの fullyParallel を有効化し、テストごとの独立性を前提に、複数ワーカーで同時実行しています。冪等性を担保したことが、そのまま並列実行の前提条件として効いています。

CIランナーレベルの分散実行 iOS/AndroidではCIワークフローのmatrixを使い、shard単位でテストを並列に分散実行しています。Androidではさらにphone/tabletのデバイス軸 × shard軸の二次元matrixを組んで、CI全体の所要時間を短縮しています。

実行環境の高速化 WebではAWS CodeBuildのカスタムランナーを採用し、CI実行時のリソース確保と起動時間の安定化を図っています。Playwrightブラウザのキャッシュも併用し、依存物のダウンロードコストを削減しています。

これらを組み合わせた結果、Webでは300をこえるテストケースが20分前後で完走できるところまで高速化できました。 リリース前検証も、main/releaseブランチへのpush起点のCIも、開発者の手を止めない時間内に収まる現実的なフィードバックループとして機能しています。 速度改善は単独の工夫ではなく、冪等性の担保や環境分離といった設計判断の上に積み上がっているものでもあります。

約350件のテストが18分で完走しています

工夫④ ログそのものをE2Eテストで検証する

冒頭で述べたとおり、TVerのテストには「画面が表示された」だけでは不十分な領域があります。 ユーザーの操作の裏で送信されるログの整合性確認は、サービスの根幹に直結する検証項目です。

これらは画面操作の自動化だけでは検証できません。WebではPlaywrightのpage.on('request')、iOS/AndroidではHTTPAssertionライブラリでHTTPレイヤを傍受し、期待のログが期待のパラメータで送信されたかまでを確認しています。

たとえば計測用ビーコンでは、ログイン状態やプライバシー設定の組み合わせごとに、再生時に送るパラメータが異なるため、検証パターンが必然的に多くなります。
サービス根幹に関わる領域なので確実に見たい一方、マニュアルでは網羅しにくい組み合わせですが、テストの自動化を進めることによって網羅的な検証が可能になりました。
「動画を再生したらビーコンが飛んだ」で終わらず、そのときのプライバシー設定が正しくフラグに反映されているかまで踏み込んで検証する。組み合わせ網羅が必須でありながら手動では担保しにくい領域を、率先して自動テストに置き換えていきました。

工夫⑤ AllureレポートでFail調査のリードタイムを縮める

CIでE2Eを回し始めると、すぐに直面するのが「失敗したテストの原因が、ログだけでは追えない」問題です。 Stack Traceは得られても、どの画面で、何が表示されていて、どのHTTPリクエストが飛んでいたか、これらが分からない限り、実行結果の深掘りに時間がかかります。

そこで、Web/iOS/AndroidのすべてでAllure Reportを導入しました。

Web E2Eテストの実行結果レポート

各OSで生成されたAllureレポートはすべてS3にアップロードし、共通の独自ドメインから配信しています。
Slack通知には毎回レポートURLを貼っており、「落ちた → URLクリック → スクリーンショット・動画・通信ログを確認」という流れが1アクションで完結します。

工夫⑥ マルチブラウザ・マルチデバイスに、必要な分だけ対応する

TVerはWebで複数の主要ブラウザに対応し、iOS/Androidはスマートフォンとタブレットの両方をサポートしています。E2Eテストでこれらを全件 × 全環境で回すと実行時間が膨大になるため、テストの性質に応じて環境を絞る工夫をしています。

WebではPlaywrightのプロジェクト機能を使い、メインスイートはChromiumで、ブラウザ固有挙動が出やすいテストだけをFirefoxやEdgeに振り分けています。 Androidでもアノテーションで「タブレットでも実行するテスト」を明示する仕組みを用意し、CI側でデバイスごとに並列分散しています。

すべてのテストを全環境で走らせるのではなく、必要な検証を必要な環境にだけ届けるという設計判断を行いました。

TVer QA Suite:自動テストでの主な使い方

TVerでは、テスト用の内製ツールTVer QA Suiteを運用しています。 2025年末に公開したukitakaの記事では、Proxyを使ったログ収集と自動検証の仕組みを紹介しましたが、いまは横断的なツール群として実装され、機能拡張が続いています。

仕組みとしては、

  • アプリが叩く外部サーバのURLを、QA SuiteのProxy経由に書き換える
  • リクエスト/レスポンスをS3に記録する
  • 検証ルールをWebUIから登録できる

という構成ですが、自動テストの文脈で現在もっとも活用しているのは、広告サーバーのプロキシ機能です。
QA SuiteのProxyは、テストごとに配信される広告テンプレート(VMAP/VAST)を差し替えることができます。

通常、広告は本物の広告サーバーから配信されるため、テストのたびに広告内容が変わってしまい、E2Eでは再現性のある検証が困難です。QA Suiteを経由させることで、

  • 「このシナリオではCompanion広告を含むパターン」
  • 「このシナリオではVAST Wrapperを経由するパターン」
  • 「このシナリオでは広告なしのパターン」

といったように、再生中の広告挙動をテスト側から制御可能になります。
これにより、本物の広告配信では再現性を担保することが難しかったケースでも、E2Eテストでは同じ条件を繰り返し検証できるようになりました。

取り組みの結果

各プラットフォームの定量的な成果

テスト自動化による成果は、プラットフォームごとに以下の通りです。

プラットフォーム 初期工数 自動化後の工数 削減幅
Web 約8.6人日 3.0人日 約65%
iOS 約4.9人日 2.0人日 約59%
Android 約5.1人日 2.1人日 約59%

TVerという動画配信プラットフォームの性質上、動画再生やライブ配信、UI/UXの体感など、どうしても目視確認を残さざるをえない領域があり、すべてを自動化して実施工数をゼロにすることはできませんでした。

それでも、自動化可能な範囲においては、Web/iOS/Androidのいずれも、リリース前のリグレッションテストの工数を約59〜65%削減し、2〜3人日程度まで抑えられました。
3プラットフォーム合計では約18.6人日から約7.1人日(約62%削減)となり、リリースのたびに約11人日分の工数を他の業務に充てられるようになっています。

リグレッションテストの工数推移

組織全体への波及効果

自動テスト単体の成果だけでなく、組織全体の品質・スピード指標にも変化が出ました。2025年度を通じて、

  • インシデントの発生件数: 約7分の1に減少(昨年比 -86%)
  • リリース頻度: 約2倍に増加(昨年比 194%)

という結果につながっています。もちろんこれはAutomationチーム単独の成果ではなく、全プロダクトチームの取り組みの総体ですが、自動テストによる品質担保とリードタイム短縮の両面から、こうした全社的な改善に貢献できた手応えがあります。

特にリリース頻度の倍増は、冒頭で触れたリリーストレイン運用と表裏一体です。
リリーストレインで「決まった周期に必ずリリースする」運用を成立させるには、毎リリース前のリグレッションテストが短時間で確実に回せることが前提条件になります。
自動化の整備はリリーストレインを下支えするインフラそのものであり、リリースサイクル短縮の実現には欠かせない取り組みでした。

質的な変化

数値以上に大きかったのは、「自動化によって、QAチームの活動そのものが変わった」という質的な変化です。
これまでリリース前の手動検証に多くの工数を割いていましたが、その役割をE2Eテストが肩代わりするようになりました。
これによりQAチームは新規機能のテスト戦略やリスクベースの探索的テストに集中できるようになり、「同じ確認を繰り返す役割」から、「どこに品質リスクがあるかを見極め、よりよい検証戦略を設計する役割」へと変わりました。

これから

私たちが今期より取り組んでいることは、以下の方向性です。

  • CTV(Connected TV)への展開: Linux CTV、ATV/FTVなど、これまで自動化が及んでいなかった領域の自動テスト導入
  • マニュアルテスト工数のさらなる削減: 既存自動テストの安定運用とカバレッジ拡大
  • 業務効率化ツールの内製: 自動化・効率化のための社内ツールを継続的に作成

そして、特に注力しているのが、AIを活用した自動テスト運用です。
E2Eテストは一度導入したらそのまま自走するわけではなく、新たに追加された機能のテスト実装や、テストが落ちたときの一次切り分け、その後の修正対応に依然として人手のコストがかかります。私たちはこの領域をAIで軽減する取り組みを、2つの方向で試験運用しています。

1. Claude Codeによるテストの自動化

E2Eテストの実装には、Claude Codeを使ったAIによる自動テストの実装を検証しています。
自然言語で書かれたテストケースを受け取ったAIが、TVerのドメイン知識やテスト設計手法・技術の知識を持つQAエンジニアスキルと、Page ObjectやE2E環境での実行手順を押さえたE2E実装スキルを併用し、テストコードを実装します。
実装後はテストを実際に動かして動作を確認し、Failするようであれば原因を分析して修正、問題がなければ自動でPR作成まで行います。
自動テストの新規実装はAIに任せ、人間はレビューに集中できる状態を目指しています。

2. Claude Managed AgentによるE2E実行結果の自動切り分けと修正PR作成

また、実行結果の確認、修正などの運用面の自動化を目指して、Claude Platform上で動くAIエージェント、E2E Failure Analyzer & PR Fixerを構築しました。

AIによる実行結果分析と修正

SlackのE2E通知チャンネルを監視し、失敗通知を検知すると、GitHub Actionsのジョブログから失敗テストを特定し、直近のPR・コミット・関連Slackスレッドを横断的に調査して、根本原因を4種類(flaky / 環境問題 / 実装との乖離 / 本物のバグ)に分類します。
修正可能な内容であればDraft PRを自動作成し、元のSlackスレッドに調査結果とPRリンクを返信します。 「Slack通知を見る → 原因を切り分ける → 修正PRを作る」までを、人手を介さずに走らせる仕組みです。

いずれも試験運用フェーズですが、Agentic codingによる開発速度の高速化についていくためには、こうしたAI活用を進めていく必要があると考えています。

まとめ

本稿では、自動化を進めてきた過程と取り組みを紹介させていただきました。
振り返ると、まず戦略の策定・専用環境の整備・既存テストケースの見直しといった準備工程を先に整えてから実装に入ったことが、短期間での大きな工数削減とリリース頻度の改善につながったと感じています。

とはいえ、現状はまだ自動テストの運用を人間が支えているフェーズです。 次のステップとして、その運用までをAIに任せていく「自動テストの自動化」を目指していきます。

最後までお読みいただきありがとうございました。私たちと一緒に「品質と開発スピードを両立する開発組織」を作っていきたい方がいれば、ぜひ採用ページもご覧ください。