Playwrightをテストランナーなしで使う — @playwright/testを使わない選択肢とそのトレードオフ

Playwrightをテストランナーなしで使う — @playwright/testを使わない選択肢とそのトレードオフ

Playwrightには @playwright/test だけでなく、テストランナーなしで使えるコアライブラリ playwright があります。RPA的なブラウザ自動化や独自のオーケストレーション層が必要な場合に、テストフレームワークに縛られない選択肢とそのトレードオフを実例をもとに整理しました。
2026.08.01

はじめに

Playwrightといえば @playwright/test を使い、test()expect() でテストを書くのが一般的です。しかし、Playwrightには playwright というコアライブラリがあり、テストランナーなしでブラウザ操作スクリプトとして使うこともできます。

実際のプロジェクトで、@playwright/test ではなく素の playwright ライブラリを採用した事例を紹介します。なぜそうしたのか、何を失い何を得たのかを整理します。

前提・環境

  • playwright(コアライブラリ)
  • TypeScript
  • module-alias でパスエイリアス設定

@playwright/test と playwright の違い

playwright              → ブラウザ操作ライブラリ(コア)
@playwright/test        → テストランナー + playwright コア

@playwright/testplaywright の上にテストランナーを追加したものです。以下の機能が含まれます:

機能 @playwright/test playwright (コア)
ブラウザ操作
test() / expect() ×
フィクスチャ ×
自動リトライ ×
並列実行 ×
レポーター ×
トレース / スクリーンショット ○(自動) ○(手動)
playwright.config.ts ×

素のplaywrightで書くテストスクリプト

scripts/example.ts
import "module-alias/register";
import { chromium } from "playwright";
import CheckoutFlow from "@class/screen/CheckoutFlow";

(async () => {
  const browser = await chromium.launch({ headless: false });
  const context = await browser.newContext({ locale: "en-US" });
  const page = await context.newPage();

  await CheckoutFlow.new(page, "STAGING")
    .then((flow) => flow.addItemToCart({ productId: "A-001", quantity: 2 }))
    .then((flow) => flow.enterShippingAddress("東京都渋谷区1-2-3"))
    .then((flow) => flow.selectPaymentMethod("クレジットカード"));

  process.exit(0);
})();

(async () => { ... })() — 即時実行非同期関数(IIFE)でラップしています。@playwright/test では test() がこの役割を果たしますが、素のライブラリでは自分で書く必要があります。module-alias を使い、@class/... のようなパスエイリアスでプロジェクト内のクラスを参照しています。

なぜ @playwright/test を使わなかったのか

1. テストではなくRPA的なスクリプト

このプロジェクトの目的は「テスト結果の合否判定」ではなく、「業務フローを自動で実行する」ことでした。expect() でアサーションを書く場面がほとんどなく、test() のフレームワークはオーバースペックでした。

2. 独自のオーケストレーション層を構築

import Browser from "@class/wrapper/Browser";
import Slack from "@class/wrapper/Slack";
import Spreadsheet from "@class/wrapper/Spreadsheet";

スクリプトの冒頭で、独自のラッパークラスをimportしています。ブラウザ操作だけでなく、外部サービス(Slack通知やスプレッドシート出力など)との連携が必要で、@playwright/test のテストライフサイクルに縛られたくなかったためです。

3. 実行フローの完全な制御

@playwright/test では:

  • テストの実行順序はフレームワークが制御する
  • 並列実行のために各テストは独立している前提
  • フィクスチャで状態を管理する

素の playwright では:

  • スクリプトを上から下まで順に実行する
  • 前のステップの結果を次のステップで使える
  • 任意のタイミングで外部API呼び出しやファイルI/Oを挟める

headless: false の意図

const browser = await chromium.launch({ headless: false });

headless: false でブラウザのUIが表示されます。開発中は操作の様子を目視確認できるため、デバッグが格段に楽になります。

CI環境では headless: true に切り替えます:

const browser = await chromium.launch({
  headless: process.env.CI === "true",
});

locale設定の重要性

const context = await browser.newContext({ locale: "en-US" });

locale を明示的に設定しています。これが影響するのは:

  • 日付ピッカーの表示形式(MM/DD/YYYY vs DD/MM/YYYY)
  • 数値の桁区切り(1,000 vs 1.000)
  • ブラウザの Accept-Language ヘッダー

テスト対象アプリが navigator.language を参照して表示を切り替えている場合、localeを指定しないとテスト環境ごとに結果が変わることがあります。

process.exit(0) の注意点

process.exit(0);

スクリプトの最後で明示的にプロセスを終了しています。これがないと:

  • ブラウザのWebSocket接続が維持されたまま、Node.jsプロセスが終了しない
  • 特に headless: false の場合、ブラウザウィンドウが開いたままになる

より丁寧なクリーンアップ:

try {
  // テストフロー
  await CheckoutFlow.new(page, "STAGING")
    .then((flow) => flow.addItemToCart({ productId: "A-001", quantity: 2 }));
} finally {
  await browser.close();
}

browser.close() を呼べばブラウザも閉じ、プロセスも自然に終了します。この方法であれば process.exit(0) は不要です。process.exit(0)finally ブロックをスキップするため、リソースリークの原因になることがあります。

@playwright/test に移行すべきタイミング

以下の要件が出てきたら、@playwright/test への移行を検討します:

  • テスト結果のレポートが必要になった(HTML Reporter、JUnit Reporter)
  • CI/CDでの並列実行でテスト時間を短縮したい
  • 失敗時の自動リトライが必要(ネットワーク不安定な環境)
  • フィクスチャでテスト間の共通セットアップを管理したい
  • チームメンバーが増え、テスト構成の標準化が必要になった

逆に、スクリプトが少数で、RPA的な用途がメインなら、素の playwright のままで十分です。

まとめ

  • playwright(コア)はテストランナーなしでブラウザ操作ライブラリとして使える
  • @playwright/test を使わないことで、実行フローの完全な制御外部サービス連携の自由度を得る
  • 代わりにリトライ、並列実行、レポートは自前で実装するか、諦める必要がある
  • headless: false で開発、locale で表示形式を固定、browser.close() で適切にクリーンアップ
  • チーム規模やテスト数が増えたら @playwright/test への移行を検討する

ツールの「正しい使い方」に縛られず、プロジェクトの目的に合った使い方を選ぶことが大切です。

この記事をシェアする

関連記事