私たちの立場を率直に述べると、私たちは Geonode であり、プロキシを販売しています。Playwright は、スクレイピングや地理的テストのために、頻繁にこれらのプロキシを経由して実行されています。 率直に申し上げると、Playwrightのタイムアウトの圧倒的多数はプロキシとは無関係です。 セレクタが何も一致しない、クッキーバナーに要素が隠れている、アニメーションが完全に停止しない――こうした状況では、トラフィックが直接送信されていようが、6つの中継を経由していようが、同じエラーが発生します。 プロキシに起因する真の問題が1つあり、それは記事の最後の方で取り上げられています。それは、一般家庭の接続には実際の遅延が生じるため、ローカルテスト用に調整されたデフォルト設定では、誤った失敗が発生してしまうというものです。しかし、まずはセレクタを確認してください。プロキシを設定していない状態でテストが全く同じように失敗する場合は、プロキシが問題ではありません。
6つのタイムアウトとそのデフォルト設定
まず理解しておくべきことは、これらはそれぞれ独立した仕組みであり、デフォルト設定も異なるということです。どのタイムアウトが発生したかを把握することで、どこを確認すべきかが分かります。
| タイムアウト | デフォルト | 設定方法 |
|---|---|---|
| テスト | 30,000 ms | testConfig.timeout, test.setTimeout() |
| 期待値 | 5,000 ms | testConfig.expect.timeout, アサーションごとのオプション |
| アクション | タイムアウトなし | testOptions.actionTimeout, 呼び出しごとのオプション |
| ナビゲーション | タイムアウトなし | testOptions.navigationTimeout, 呼び出しごとのオプション |
| beforeAll / afterAll フック | 30,000 ms | フック内部の test.setTimeout() |
| グローバル | なし | testConfig.globalTimeout |
値は Playwright タイムアウトのドキュメント からのものです。
このうち 2 つは、多くの人を驚かせるものです。
アクションとナビゲーションには、デフォルトでタイムアウトがありません。 これらはテストのタイムアウトによってのみ制限されます。したがって、修飾子なしの page.click() は、残りのテスト時間枠が尽きるまで待機し、発生するエラーはクリックそのものではなく、テストがタイムアウトしたことに起因します。そのため、30秒のクリックを設定していなくても、メッセージに「30000ms」と表示されるのです。
グローバルタイムアウトにはデフォルト値が一切ありません。 ドキュメントでは、その目的を「すべてがうまくいかなかった場合の過剰なリソース使用を防ぐこと」と説明しています。CI環境では、テストスイートがハングした際にランナーを無期限に占有し続けるのではなく、テストスイート自体が失敗するように設定しておく価値があります。
「30000msのタイムアウトを超えました」が実際に意味すること
このメッセージはテストのタイムアウトを示しており、テストのタイムアウトは診断結果というよりは「許容時間」のようなものです。その範囲内で何かが処理に時間がかかりすぎたことを示しており、このメッセージは原因ではなく、許容時間を示しているに過ぎません。
各原因が実際の原因である頻度の高い順に並べると:
1. ロケーターが何も一致しなかった。 セレクタが間違っているか、要素が表示されていないか、あるいは想定していなかったiframeやシャドウルート内に存在している。Playwrightは、決して存在しないものを辛抱強く待ち続けている。
2. 要素は存在するが、操作できない。 オーバーレイ、クッキーバナー、またはスティッキーヘッダーで覆われている。無効化されている。まだアニメーション中である。Playwrightはクリック可能になるのを待ち続けるが、その状態には決してならない。
3. ナビゲーションが完了しなかった。 ネットワークリクエストがハングアップした、リダイレクトループに陥った、あるいはwaitUntilの条件(特にnetworkidle)が満たされなかった。持続的な接続を持つページでは、この条件が満たされることは決してない。
4. アサーションが真にならなかった。 アプリケーションが到達しない条件をポーリングするexpect。
5. テストの処理範囲が実際に広すぎる。 現実的なケースであり、最も発生頻度が低い。
順序が重要なのは、修正方法が全く異なるためです。タイムアウト時間を延長することで解決できるのはケース5のみです。他の4つのケースでは、タイムアウト時間を延長しても、同じ失敗が発生するまでより長く待つことになるだけです。
「アクション実行可能性」こそが、クリックが反応するまで時間がかかる理由
これを理解すれば、その30秒の間にPlaywrightが何を行っているのかが説明されるため、混乱の大部分は解消されます。
アクション実行可能性に関するドキュメント によると、Playwrightは「アクションを実行する前に要素に対して一連のアクション実行可能性チェックを行い、これらのアクションが期待通りに動作することを確認する」とされており、また「関連するすべてのチェックが合格するまで自動的に待機し、その後にのみ要求されたアクションを実行する」とされています。 チェックが時間内に満たされない場合、「TimeoutError」が発生し、アクションは失敗します。
必要なチェックはアクションごとに異なり、その違いは診断のヒントとなります:
| アクション | 必要なチェック |
|---|---|
click, dblclick, check, uncheck, tap, setChecked | 表示可能、安定、イベントを受信可能、有効 |
hover, dragTo | 表示可能、安定、イベントを受信可能 |
fill, clear | 表示可能、有効 |
selectOption | 表示可能、有効 |
screenshot, selectText | 表示可能 |
scrollIntoViewIfNeeded | 安定 |
blur, focus, press, pressSequentially, dispatchEvent, setInputFiles | なし |
この表から、すぐに2つのことがわかります。
同じ要素に対してfillが実行されている間に、clickがタイムアウトするという現象は、「安定している」または「イベントを受信している」状態を示しています。つまり、要素が動いているか、その上に何かが重なっているということです。アニメーションやオーバーレイが典型的な原因です。
チェックを行わないアクションは、逃げ道であると同時に警告サインでもあります。 locator.click() がタイムアウトする一方で、dispatchEvent('click') が機能する場合、何も修正できていないことになります。つまり、実際のユーザーもその要素をクリックできないことを示していたチェックを迂回してしまったのです。場合によってはそれが許容されることもありますが、通常は、テストが検出できなくなった本物のオーバーレイの問題が存在することを意味します。
各タイムアウトを適切な場所で変更する
設定はいくつかのレベルに存在しており、間違ったレベルに配置すると混乱を招く結果となります。
グローバル設定:playwright.config.ts
内の
export default defineConfig({
timeout: 60_000,
globalTimeout: 60 * 60 * 1000,
expect: { timeout: 10_000 },
use: {
actionTimeout: 15_000,
navigationTimeout: 30_000,
},
});
それぞれの配置場所に注意してください。timeout
および globalTimeout
はトップレベルの設定です。expect.timeout
は expect
の下にあります。actionTimeout
および navigationTimeout
は use
の下にあります。これらはランナーの設定ではなく、テストの オプション だからです。 これらを誤ったレベルに配置しても、何も表示されずに無視されます。
テスト単位:
test('slow one', async ({ page }) => {
test.setTimeout(120_000);
// ...
});
**test.slow()
** は、デフォルトのタイムアウトを3倍にします。これは、恣意的な数値を選ばずに、確実に時間がかかるテストに適したデフォルト設定です。
テストごと: ** ** これにより、デフォルトのタイムアウトが3倍になります。これは、恣意的な数値を設定することなく、確実に時間がかかるテストに適したデフォルト値です。
アクションごと:
await expect(page.getByRole('status')).toHaveText('Done', { timeout: 30_000 });
** アクションごと:**
await page.getByRole('button', { name: 'Export' }).click({ timeout: 15_000 });
**beforeAll
および afterAll
** では、独自の30秒の予算が割り当てられているため、フック内部で test.setTimeout()
を呼び出してください。
処理に時間がかかるフィクスチャについては、それを使用するすべてのテストの処理時間を延ばすのではなく、test.extend()
内で独自のタイムアウトを設定してください:
export const test = base.extend<{ seeded: void }>({
seeded: [async ({}, use) => {
await seedDatabase();
await use();
}, { timeout: 60_000 }],
});
一般的な原則:問題を解決するために必要な最小限の範囲を設定することです。1つの処理に時間がかかるテストに対応するためにグローバルなテストタイムアウトを延長すると、他のすべてのテストの失敗までの時間が長くなり、CIにおいて実際の時間を浪費することになります。
テストのタイムアウト時間に算入されるもの
よく誤解されがちですが、これにより「何も実行する前に」タイムアウトしてしまうテストの理由が説明されます。
ドキュメントには次のように明記されています。「テスト関数、フィクスチャのセットアップ、およびbeforeEachフックに要した時間は、テストのタイムアウト時間に算入されます。」
つまり、ログイン、データのシード、ナビゲーションを行うbeforeEachは、テスト本体に必要な30秒と同じ時間を消費します。最初の行でタイムアウトしたように見えるテストでも、セットアップに28秒を費やしていた可能性があります。
フィクスチャはデフォルトでテストのタイムアウトを共有します。これは形は違えど同じ落とし穴です。つまり、処理に時間がかかるフィクスチャは、それに依存するすべてのテストのタイムアウト枠を消費してしまうのです。テスト全体のタイムアウトを延長するのではなく、処理に時間がかかるフィクスチャには独自のタイムアウト枠を割り当ててください。
テアダウンは分離されています。フィクスチャのテアダウンとafterEachフックは、テスト関数が完了した後に独自の時間を割り当てられるため、処理に時間がかかるテアダウンがあってもテストの時間を消費することはありません。
デバッグにおける実用的な意味合い:テストがタイムアウトした場合は、エラーが指し示す行だけでなく、フィクスチャ、beforeEach、テスト本体といったチェーン全体を確認してください。
エスカレーションを行う前に診断を行う
数分でほとんどのタイムアウトを解決できる手順。
トレースビューアを使って実行してください。 これは最も価値の高いツールですが、十分に活用されていません:
npx playwright test --trace on
npx playwright show-trace trace.zip
トレースには、すべてのアクション、その所要時間、前後のDOMスナップショット、およびネットワークアクティビティが表示されます。 何も一致しなかったロケーターはすぐに目につきます。ボタンの上に表示されているクッキーバナーも同様です。
現象を観察したい場合は、ヘッドモードで実行し、速度を落としてください:
npx playwright test --headed --debug
ロケーターが正しく解決されるか確認してください:
console.log(await page.getByRole('button', { name: 'Save' }).count());
0 の場合、セレクタに問題があり、タイムアウト値を調整しても解決しません。
安定性の問題かどうかを確認するには、修正手段としてではなく、診断手段としてチェックを行わないアクションを使用してください。click()
でタイムアウトが発生する場面で、dispatchEvent('click')
が動作する場合は、何かが要素を覆っているか、移動させている可能性があります。
**networkidle
による待機がないか確認してください。** アナリティクスビーコン、WebSocket、またはポーリングを含むページは、ネットワークがアイドル状態になることがない場合があります。実際に確認したい事象が発生するまで待つことをお勧めします:
await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
エラーメッセージを最後まで読みましょう。 Playwrightのタイムアウトエラーには、ロケーター、解決された要素の数、および保留中だったアクション可能性チェックが含まれます。この最後の詳細が、通常、問題の原因を明確に示しています。
タイムアウト時間を長くすると、通常は不安定性が悪化する
直感に反するが、重要な点だ。
不安定なテストとは、結果がタイミングに左右されるテストのことです。タイムアウトを長くすると、テストが成功する時間枠が広がり、不安定な挙動は稀になります。それに応じて、再現が難しくなり、診断も困難になり、失敗した際の処理も遅くなります。
その一方で、失敗するたびにコストが発生します。タイムアウトが30秒の200件のテストスイートが完全に失敗するまでにかかる時間は、最大で100分ですが、120秒に設定すると400分かかります。CI環境では、これは実質的なコストと待ち時間となります。
不安定性を実際に解消する方法:
時間ではなく、状態を待つ。 waitForTimeout はほとんどの場合、誤った結果をもたらします。重要な条件をアサーションし、Playwrightにポーリングさせましょう。
Webファーストのアサーションを使用する。 expect(locator).toBeVisible() は自動的に再試行します。expect(await locator.isVisible()).toBe(true) は一度だけチェックし、最初の失敗で失敗を返します。これは微妙ですが、不安定性の非常に一般的な原因です。
オーバーレイには決定論的に対処する。 クッキーバナーが消えていることを期待するのではなく、フィクスチャ内で明示的に閉じるようにする。
アニメーションは、アニメーションが落ち着くのを待つのではなく、可能な場合は設定で無効にする。
ネットワークが静かになるのを待つのではなく、依存している特定のネットワーク応答を待ちましょう。
データを安定させましょう。 共有された可変状態に依存するテストは、タイムアウトでは解決できない理由で不安定になりがちです。
タイムアウトを本当に発生させるべき場合: 操作が実際に遅く、それを回避する方法がない場合です。たとえば、大容量ファイルのアップロード、生成に1分かかるレポート、意図的に帯域制限がかけられたネットワーク環境などです。そのような場合は、そのテストやアサーションに限定してタイムアウトを発生させ、デフォルト設定はそのままにしておきましょう。
プロキシ経由での実行時のタイムアウト
タイムアウトが正当なケースと、それに伴う設定について。
Playwrightは、ネットワーク設定でプロキシ設定を受け付けます:
export default defineConfig({
use: {
proxy: {
server: 'http://proxy.example.com:9000',
username: 'user',
password: 'pass',
},
},
});
あるいは、コンテキストごとに設定することも可能です。これは、テストごとに異なる出口地点が必要な場合に適しています:
const context = await browser.newContext({
proxy: { server: 'http://proxy.example.com:9000' },
});
これによる3つの実用上の影響。
住宅用プロキシは実際の遅延をもたらします。 トラフィックは実際の一般ユーザーの接続を経由して送信されるため、リクエストごとに数百ミリ秒の追加遅延が生じるのは不具合ではなく、正常な動作です。 80回のリクエストを行うページでは、その遅延が80倍に積み重なります。localhost向けに調整されたデフォルト設定では、プロキシの不具合のように見えるエラーが発生しますが、実際には単に距離によるものです。
適切な対応は、推測するのではなく測定することです。テストスイートをプロキシ経由で実行し、トレースのタイミングを確認した上で、観察結果に基づいて十分な余裕を持たせて navigationTimeout
および actionTimeout
を設定してください。
帯域幅こそが真のコストであり、ブラウザではそのコストが莫大になります。 Playwrightは、すべての画像、フォント、スクリプト、および動画のプリロードを取得します。1GBあたり0.79ドルの従量制の家庭用通信環境では、これが他のすべてのコストを上回ります。不要なリソースの種類をブロックすることが、最も大きな節約効果をもたらします:
await page.route('**/*.{png,jpg,jpeg,webp,gif,woff,woff2,mp4}', r => r.abort());
これにより、トラフィックを総量の大部分まで削減できるのが常であり、副次的な効果としてテストの高速化も実現します。
ブロックはタイムアウトではありません。 ターゲットがチャレンジページを返す場合、Playwrightはチャレンジページ上に存在しない要素を待ち続けてタイムアウトになります。これは一見タイムアウトのように見えますが、実際にはタイムアウトではありません。 失敗時にスクリーンショットを撮り、実際にレンダリングされた内容を確認してください:
use: { screenshot: 'only-on-failure', trace: 'retain-on-failure' }
これは、プロキシのテストが重要な理由で説明した「サイレント・フェイル」のパターンです。リクエストは成功し、ページはレンダリングされましたが、それは間違ったページだったのです。
よくある質問
Playwrightのデフォルトのタイムアウト時間はどれくらいですか?
テストおよびbeforeAll/afterAllフックでは30,000 ms、expectアサーションでは5,000 msです。アクションおよびナビゲーションのタイムアウトにはデフォルト値がなく、テストのタイムアウトによってのみ制限されます。そのため、クリック操作に時間がかかった場合、その操作自体の制限時間ではなく、テストの制限時間である30秒が報告されます。
特定のPlaywrightテストのタイムアウト時間を延長するにはどうすればよいですか?
テスト内でtest.setTimeout(120_000)を呼び出すか、test.slow()を呼び出してデフォルト値の3倍に設定してください。グローバルなタイムアウト時間を延長すると、他のすべてのテストの失敗が遅くなるため、これらを優先してください。
要素がページ上に存在しているのに、なぜPlaywrightテストがタイムアウトしてしまうのですか?
通常は、その要素が 操作可能 ではないためです。click では、要素が可視状態で、安定しており、イベントを受信可能で、かつ有効である必要があります。そのため、バナーで覆われている要素や、まだアニメーション中の要素は検出されますが、クリックされることはありません。エラーメッセージには、どのチェックが保留中だったかが明記されます。
テストのタイムアウトと expect のタイムアウトの違いは何ですか?
テストタイムアウトとは、テスト関数、フィクスチャの設定、およびbeforeEachフックを合わせた合計の許容時間であり、デフォルトは30秒です。エクスペクトタイムアウトとは、単一のWebファーストアサーションがポーリングを行う時間であり、デフォルトは5秒です。5秒経過後にアサーションが失敗した場合は、テストタイムアウトではなく、エクスペクトタイムアウトによるものです。
PlaywrightでwaitForTimeoutを使用すべきですか?
ほとんどの場合、使用すべきではありません。固定の待機時間は、短すぎるとテストが不安定になり、長すぎるとテストスイートの実行が遅くなります。通常、マシンによって両方の問題が発生します。代わりに、Webファーストアサーションを使用して条件が満たされるのを待ちましょう。Webファーストアサーションは、タイムアウトになるまで自動的に再試行します。
なぜ networkidle は決して解決しないのですか?
ページが絶えずリクエストを送り続けているためです(アナリティクスビーコン、WebSocket、ポーリング、長期接続など)。networkidleには「静寂」が必要ですが、多くの最新アプリケーションは決して静かになりません。代わりに、関心のある特定の要素やレスポンスを待機するようにしてください。
タイムアウトを延長すると、不安定なテストは修正されますか?
それは問題を隠蔽するだけです。不安定な挙動はより稀になり、再現が難しくなり、失敗までの時間が長くなりますが、その一方で、テストスイート内の真の失敗はすべてより長くかかるようになります。原因を修正してください。時間ではなく状態を待ち、再試行アサーションを使用し、オーバーレイを確定的に閉じ、アニメーションを無効にしてください。
プロキシを使用する場合、タイムアウトを長くする必要がありますか?
多くの場合、ナビゲーションやアクションに関しては「はい」です。なぜなら、レジデンシャルプロキシはリクエストごとに実際の遅延を加え、ページが行う多数のリクエストを通じてその遅延が累積するからです。トレースを有効にしてプロキシ経由で遅延を測定し、事前にすべてを延長するのではなく、観測結果に基づいて値を設定してください。
まとめ
メッセージには「テストが30秒を超えた」とありますが、これは説明というよりは単なる制限です。テスト内部のどこかで、決して発生しない条件を待っていたのです。5件中4件の場合、その条件とは、何にも一致しないセレクタ、あるいは実行可能状態にならなかった要素です。
したがって、時間を節約するための手順は次の通りです。まず、保留中の実行可能性チェックの名前が記載されたエラーメッセージを全文読み、次に、失敗した瞬間のDOM状態を示すトレースを開き、ロケーターが正しく解決されることを確認してから、その数値を検討してください。 タイムアウトを発生させることは、まさに1つの原因――実際に予算時間を超える操作――に対しては正しい修正ですが、他の4つのケースでは、失敗までの時間を延ばすだけという誤った修正となります。
タイムアウトを発生させる場合は、その範囲を厳密に限定してください。テストごと、アサーションごと、フィクスチャごとに設定します。単一の遅いアップロードに対応するためにグローバルなデフォルト値を拡大すると、スイート内のすべての失敗のコストが高くなり、CI時間は二度と戻ってこない唯一のリソースだからです。
