
はじめに
Playwright で chromium / webkit / firefox と専用 project を組んでいると、「グローバルの testIgnore で除外したはずの spec が、特定の project でだけ実行されてしまう」という罠にはまることがあります。
原因は仕様にあります。project レベルで testIgnore を定義すると、グローバルの testIgnore はマージされず、上書きされます。
この記事では、実運用のサービスで起きた漏れの事例をもとに、定数の export による再掲と、config の構造をユニットテストで守る回帰ガードの作り方を紹介します。
こんな人におすすめ
- Playwright で複数の project を定義している方
- グローバルの
testIgnoreやtestMatchを設定しているのに、意図しない spec が走って困った方 - PR 用の絞り込み実行と、nightly のフル実行を分けている方
- 設定ファイルそのものにもテストを書きたい方
何が起きたのか:nightly だけで壊れる
実運用しているサービスで、次の2種類の spec を通常の E2E から除外していました。
- 本番 origin を叩く視覚的回帰テスト用の spec(ベースライン画像が無い環境では必ず失敗します)
- 専用ブラウザ環境でしか動かない Service Worker の spec
グローバルの testIgnore に書いてあるので安心していたところ、あるブラウザ project が独自の testIgnore を持っていました。
その project では、グローバルの除外が丸ごと無効になり、除外したはずの spec が走って失敗しました。
厄介だったのは、PR の実行が @critical タグの grep で絞り込まれていたことです。
そのため PR ではグリーンのまま通過し、grep なしの nightly でだけ失敗として顕在化しました。
通常実行とフル実行が別れているほど、こうした漏れは見つかりにくくなります。
対策1:glob を定数にして export し、各 project に再掲する
まず、除外したいパターンを定数として定義し、export します。
// playwright.config.ts
// 視覚的回帰テスト専用 spec — 本番 origin を叩くため通常 E2E からは除外する
export const VISUAL_SPEC_IGNORE = '**/*.visual.spec.ts'
// 専用環境でのみ動く spec — それ以外の project では一律 ignore
export const SERVICE_WORKER_SPEC_IGNORE = '**/service-worker-lifecycle.spec.ts'
// project レベルの testIgnore はグローバルを上書きするため、
// 独自 testIgnore を持つ project には必ずこの配列を渡す
const DEV_SERVER_SPEC_IGNORE = [SERVICE_WORKER_SPEC_IGNORE, VISUAL_SPEC_IGNORE]
あとは、独自の testIgnore を持つ project でこの配列を明示的に参照します。
パターンを直接書かず定数を参照させると、新しい project を足すときに「何を入れるべきか」が定数名から分かります。
コメントに「上書きされるため再掲が必要」と書いておけば、レビューのコストも下がります。
export しておくと、テストファイルから config の定数をインポートできるようになります。これが次の回帰ガードの土台です。
対策2:config の選択ロジックをユニットテストで再現する
再掲を忘れたとき、CI で即座に気づけるようにします。
Playwright は内部で minimatch を使って spec のパスを解決しているため、同じライブラリで判定を模倣できます。
// e2e/lib/playwright-config.test.ts
// Playwright と同じ「いずれかの glob に一致すれば該当」判定
function matchesAny(filePath: string, patterns: string[]): boolean {
return patterns.some((pattern) => minimatch(filePath, pattern))
}
// project レベルの設定はグローバルを上書きする(?? でフォールバック)
function resolveSelection(
filePath: string,
globalConfig: { testMatch?: unknown; testIgnore?: unknown },
project: { testMatch?: unknown; testIgnore?: unknown },
): boolean {
const effectiveMatch = toGlobArray(project.testMatch ?? globalConfig.testMatch)
const effectiveIgnore = toGlobArray(project.testIgnore ?? globalConfig.testIgnore)
const matched = effectiveMatch.length === 0 || matchesAny(filePath, effectiveMatch)
const ignored = matchesAny(filePath, effectiveIgnore)
return matched && !ignored
}
ポイントは ?? によるフォールバックです。project 側に値があればグローバルを見ない、という上書きの仕様をそのままコードで表現しています。
toGlobArray は、文字列・正規表現・配列・未定義のいずれも配列に正規化する自前のヘルパーを想定しています。
次に、全 project に対してパラメトリックにアサートします。
const SAMPLE_VISUAL_SPEC = 'e2e/pages/top.visual.spec.ts'
it.each(projects.map((project) => [project.name ?? '(no name)', project] as const))(
'project "%s" は visual spec を選択しない',
(_name, project) => {
const selected = resolveSelection(SAMPLE_VISUAL_SPEC, playwrightConfig, project)
expect(selected).toBe(false)
},
)
it.each で project 一覧を回しているので、新しい project が追加されると自動でテスト対象に入ります。手動でテストを追記する必要はありません。
実ファイルのパスは定数と独立に書く
テストで使うサンプルのパスは、定数から組み立てず、実際のファイルパスとして別に書きます。
定数どうしを比較するだけだと、定数の値自体が間違っていてもテストが通ってしまうためです。
「glob 文字列が妥当か」と「適用した結果が正しいか」を同時に保証できます。
対策3:Vitest の node-only グループに登録する
この config テストはブラウザを必要としません。Playwright ではなく Vitest の Node.js 環境で実行すると、高速に検証できます。
// vitest.config.ts
const nodeOnlyTests = [
// ...
'e2e/lib/playwright-config.test.ts', // playwright.config.ts の構造テスト
]
ブラウザの起動が要らないので、PR のたびに実行しても負担になりません。
nightly でしか見つからなかった問題を、PR の時点で検知できるようになります。
つまづきやすいポイント
- 上書きであってマージではない:公式ドキュメントに記載はありますが、既存の
testIgnoreがある config に project を足すときに忘れがちです。testMatchも同様の挙動か、一度確認しておくと安心です。 - PR の grep 実行ではグリーンに見える:タグで絞った実行では漏れが隠れます。構造テストで通常実行・フル実行の差を埋めるのが有効です。
- 定数だけを比較するテストは抜け穴になる:実ファイルパスを独立して書き、定数の誤りも検出できるようにしてください。
- 模倣ロジックは Playwright の仕様変更に追従が必要:minimatch で再現した判定は、Playwright の内部挙動が変わると乖離する可能性があります。アップデート時に見直すのがおすすめです。
まとめ
project 別の testIgnore はグローバル設定を上書きするため、除外したい spec は各 project に明示的に再掲する必要があります。
glob を定数として export すれば、再掲が楽になり、config 自体もテスト可能になります。
さらに minimatch で選択ロジックを再現し、it.each で全 project を検証すれば、project 追加時の漏れを CI で自動検知できます。
設定ファイルは「書いて終わり」にせず、構造をテストで守るという考え方は、他の設定ファイルにも応用できそうです。

