画面に見える見出しなのに、page.get_by_roleで見つからない場合、別のiframe内にある可能性があります。通常のページ操作はメインフレームを対象にするため、内側の文書を指定する段階が必要です。
要素の名前を変える前に、どのフレームに入っているかを確認します。タイムアウトを延ばしても、探している範囲が違えば見つかりません。
iframeの件数と属性を確認する
ブラウザーの開発者ツールなどで、対象を囲むiframeがあるかを見ます。id、name、title、srcを確認し、どのフレームを指定するかを決めます。
iframeのsrcが別ホストなら、そのホストも通信先になります。親ページの取得条件だけで判断せず、埋め込まれた内容の利用条件や認証が関わるかも確認します。また、srcがない場合はsrcdocへ内容が書かれていることもあります。
ローカルのsrcdocで試す
次をframe-example.htmlとしてUTF-8で保存します。内側の見出しを一つ読むだけの架空HTMLです。外部ページへの接続はありません。
<!doctype html>
<html lang="ja">
<head><meta charset="utf-8"><title>フレームの例</title></head>
<body>
<p>親ページです。</p>
<iframe id="detail" title="資料詳細"
srcdoc="<h2>資料A</h2><p>架空の詳細です。</p>"></iframe>
</body>
</html>
Playwrightと対応するブラウザーがある環境で、次を同じフォルダーから実行します。
from pathlib import Path
from playwright.sync_api import sync_playwright, expect
with sync_playwright() as p:
browser = p.chromium.launch(headless=True)
try:
page = browser.new_page()
page.goto(Path("frame-example.html").resolve().as_uri())
frames = page.locator("iframe")
print("iframe数:", frames.count())
print("親ページの見出し数:", page.get_by_role("heading").count())
print("対象title:", page.locator("iframe#detail").get_attribute("title"))
detail = page.frame_locator("iframe#detail")
heading = detail.get_by_role("heading", name="資料A")
expect(heading).to_be_visible(timeout=3000)
print(heading.inner_text())
finally:
browser.close()
Python 3.12.10、Playwright 1.58.0と既存のChromium 145でローカル実行し、iframe数1、親ページの見出し数0、内側を指定した結果が資料Aであることを確認しました。親ページを対象にした検索が、iframeの内側まで一律に横断するわけではないことを確認できます。
フレームの指定を一つに絞る
iframeというタグ名だけで指定すると、広告や別のウィジェットなど複数に一致する場合があります。目的のidや、安定した属性で対象を絞ります。単に最初のフレームを選ぶと、並び順が変わったときに別の文書を操作するおそれがあります。
フレームの内側にさらにiframeがある場合は、その入れ子の関係も確認します。対象が読み込まれるタイミングや、画面遷移でフレーム自体が置き換わる条件も調査対象です。
読み取りから始める
実サイトへ置き換えるときも、最初はフレームの件数と対象の文字を確認するところから始めます。埋め込まれたフォームへの入力・送信は、この読み取り例に含めていません。
ログへ残す際は、iframeのURLにトークンや個人情報が含まれないかを見ます。必要な属性と件数を確認できれば、待機不足なのか、範囲や指定の問題なのかを分けて次の調査へ進めます。
関連する作業
Playwrightの基本と、待機条件を調べる方法は次の記事で確認できます。
