Playwrightログイン自動化で2時間溶かした私が、最初に書けばよかった20行

ログイン自動化は簡単そうに見えます。

IDを入れて、パスワードを入れて、ボタンを押すだけ。そう思って始めたら、私は2時間溶かしました。原因の半分はセレクタ、残り半分は待機です。

夜中にコーヒーを飲みながら、ログインボタンを15回押す自動化を見つめる時間は、なかなか味があります。

目次

最初に知るべきこと

Playwrightで大事なのは、クリックより待機です。

人間は画面が表示されるまで自然に待ちます。でもコードは待ちません。要素がまだ出ていないのに入力しようとして落ちます。ページ遷移の途中で次の処理に進んで落ちます。

私が最初に書いたコードは、見た目だけは正しそうでした。でも実行すると3回に1回失敗しました。自動化で一番困るのは、毎回ではなく、ときどき落ちることです。

動く最小コード

Python版Playwrightで、ログインの基本形はこうです。

from playwright.sync_api import sync_playwright, expect


LOGIN_URL = "https://example.com/login"


def main() -> None:
    with sync_playwright() as p:
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()

        page.goto(LOGIN_URL, wait_until="domcontentloaded")
        page.get_by_label("Email").fill("user@example.com")
        page.get_by_label("Password").fill("password")

        with page.expect_navigation():
            page.get_by_role("button", name="Log in").click()

        # ハマりポイント: 遷移後に見える要素で成功判定する
        expect(page.get_by_text("Dashboard")).to_be_visible(timeout=10000)
        browser.close()


if __name__ == "__main__":
    main()

これはサンプルなので、実際のラベルやボタン名は対象サイトに合わせてください。重要なのは、expect_navigation と成功判定を入れていることです。

私が踏んだ4つの穴

1つ目は、CSSセレクタに頼りすぎたことです。

#login > div:nth-child(2) > input のようなセレクタは、見た瞬間に不安になります。画面構造が少し変わるだけで壊れます。可能なら get_by_labelget_by_role を使うほうが読みやすく、壊れにくいです。

2つ目は、ログイン成功の判定をURLだけにしたことです。

URLが変わっても、実はエラー表示が出ていることがあります。成功後に見えるテキストや見出しで確認したほうが安心です。

3つ目は、ヘッドレスでいきなり走らせたことです。

最初は headless=False にして、ブラウザの動きを見たほうが速いです。何が起きているか分からない自動化ほど怖いものはありません。

4つ目は、認証情報をコードに書いたことです。検証用とはいえ、よくありません。環境変数に逃がします。

環境変数版

少しだけ実用に寄せるとこうなります。

import os
from playwright.sync_api import sync_playwright, expect


def main() -> None:
    login_url = os.environ["LOGIN_URL"]
    user = os.environ["LOGIN_USER"]
    password = os.environ["LOGIN_PASSWORD"]

    with sync_playwright() as p:
        browser = p.chromium.launch(headless=True)
        page = browser.new_page()
        page.goto(login_url, wait_until="domcontentloaded")

        page.get_by_label("Email").fill(user)
        page.get_by_label("Password").fill(password)
        page.get_by_role("button", name="Log in").click()

        # ハマりポイント: 固定秒sleepではなく、見えるべき要素を待つ
        expect(page.get_by_text("Dashboard")).to_be_visible(timeout=10000)
        browser.close()


if __name__ == "__main__":
    main()

固定の sleep(5) を入れたくなりますが、できるだけ避けます。速い時は無駄に待ち、遅い時は足りません。

自動化してはいけない瞬間

ログイン自動化は便利ですが、何でも自動化してよいわけではありません。

二段階認証、利用規約で禁止されている操作、人間の確認が必要な送信処理。このあたりは慎重に扱うべきです。

私のルールは、最初は「見るだけ」「下書きまで」「送信しない」です。クリック1つで外部に影響が出る操作は、最後に人間が確認します。

失敗時の証拠を残す

Playwrightで落ちた時、ログだけでは分からないことがあります。

私は最初、エラー文を読んで3回ほど見当違いの修正をしました。実際には、ログイン画面に小さなエラーメッセージが出ていただけです。スクリーンショットを撮っていれば5分で気づけました。

from pathlib import Path


def save_debug(page, name: str) -> None:
    Path("debug").mkdir(exist_ok=True)
    page.screenshot(path=f"debug/{name}.png", full_page=True)
    Path(f"debug/{name}.html").write_text(
        page.content(),
        encoding="utf-8",
    )

ハマりポイントは、成功時ではなく失敗時に呼ぶことです。毎回保存するとファイルが増えすぎますが、例外時だけなら原因調査に効きます。

自動化は、動いた時より落ちた時の情報量で品質が決まります。

まとめ

Playwrightのログイン自動化は、20行でも十分始められます。

ただし、安定させるにはセレクタ、待機、成功判定の3つが必要です。私は2時間ハマって、結局そこに戻ってきました。

ブラウザ自動化で本当に書くべきなのは、クリック手順ではなく、失敗した時に止まれる確認です。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

目次