るちtechブログ

とあるサイトを作っています

PlaywrightでTurnstileの出現を待機する

備考

  • 文章は人間が一生懸命タイピングしてます。

経緯

PlaywightでTurnstileを使った認証ページをテストする時に、Turnstileのiframe取得がうまくいかないことがありました。結果として、適当に数秒待つというコードを使ってテストの実行時間が無駄に伸びている時がありました。そこで、TurnstileがSuccesになるまで待機するというコードを書いたので共有します。

コード

/**
 * Cloudflare Turnstileのiframeが出現するまで待機する関数
 */
async function waitForTurnstileFrame(
   page: Page,
   timeout = 20000,
): Promise<Frame> {
    console.log("Waiting for Turnstile iframe to appear (polling)...");
    const startTime = Date.now();
    while (Date.now() - startTime < timeout) {
        const frame = page.frames().find((f) => f.url().includes("challenges.cloudflare.com"));
        if (frame) {
            console.log("Found Turnstile frame via polling.");
            return frame;
        }
        await page.waitForTimeout(500); // 500ms待機
    }
    throw new Error(`Turnstile frame not found within ${timeout}ms`);
}

/**
 * Turnstileのトークンが生成され、inputに格納されるまで待機して値を返す関数
 */
async function waitForTurnstileToken(
   page: Page,
   timeout = 30000,
): Promise<string> {
    console.log("Waiting for Turnstile token to be generated...");
    const tokenInput = page.locator('input[id^="cf-chl-widget-"][id$="_response"]');

    // 要素がDOMに出現するまで待機
    await tokenInput.waitFor({ state: "attached", timeout });

    // 値が空でなくなるまで待機
    await page.waitForFunction(
        (el) => (el as HTMLInputElement).value !== "",
        await tokenInput.elementHandle(),
        { timeout },
    );

    const token = await tokenInput.inputValue();
    return token;
}

/**
 * Turnstileが出てきてからトークン取得までをまとめて行う関数
 */
export async function getTurnstileToken(page: Page, timeout = 30000): Promise<string> {
    // 1. iframeが出現するまで待機
    await waitForTurnstileFrame(page, timeout);
    // 2. トークンが生成されるまで待機して取得
    return await waitForTurnstileToken(page, timeout);
}

短いですが以上です。