ウェブ開発のモダン化に伴い、Web ComponentsやShadow DOMを採用するサイトが急速に増えています。
Seleniumを使用したブラウザ自動化において、通常の要素特定メソッドではShadow DOM内部の要素を検出できないため、多くのエンジニアがこの壁に直面します。
本記事では、2026年現在の最新のSelenium(Python版)を用いた、Shadow DOM内の要素を正確かつ効率的に操作するための実践的なテクニックを詳しく解説します。
Shadow DOMの基本概念とSeleniumでの操作が困難な理由
Shadow DOMは、Web Componentsの主要な仕様の一つであり、DOMツリーの一部をカプセル化して外部のスタイルやスクリプトから隔離する仕組みです。
このカプセル化により、メインのDOM(Light DOM)からは、シャドウツリーの中身が隠蔽されています。
Seleniumの標準的なfind_elementメソッドは、通常のDOMツリーのみを探索対象とするため、Shadow DOM内部の要素を直接指定してもNoSuchElementExceptionが発生してしまいます。
これを解決するためには、まず「シャドウホスト(Shadow Host)」と呼ばれる、Shadow DOMを保持している親要素を特定する必要があります。
そのホスト要素を経由して「シャドウルート(Shadow Root)」にアクセスし、そこから内部の要素を探索するという手順を踏まなければなりません。
モダンなWebアプリケーションでは、ボタンや入力フォーム自体が独自のコンポーネントとしてShadow DOM内に配置されていることが多いため、この操作の習得は必須と言えます。
Selenium 4以降で推奨されるshadow_rootプロパティの活用
Selenium 4の登場以降、Shadow DOMへのアクセスは非常に簡潔になりました。
以前はJavaScriptを介した複雑な記述が必要でしたが、現在はshadow_root属性を利用することで直感的にアクセスが可能です。
まず、以下のサンプルコードで基本的なアクセス方法を確認してみましょう。
from selenium import webdriver
from selenium.webdriver.common.by import By
# ドライバの初期化
driver = webdriver.Chrome()
# ターゲットサイトへアクセス
driver.get("https://example.com/shadow-dom-demo")
# 1. まずはシャドウホスト(Shadow DOMを持つ親要素)を見つける
shadow_host = driver.find_element(By.CSS_SELECTOR, "#shadow-host-element")
# 2. shadow_rootプロパティを使用してシャドウルートを取得する
shadow_root = shadow_host.shadow_root
# 3. シャドウルートの中から目的の要素を検索する
target_element = shadow_root.find_element(By.CSS_SELECTOR, ".inner-button")
# 4. 要素に対してアクションを実行
target_element.click()
print("Shadow DOM内のボタンをクリックしました。")
Shadow DOM内のボタンをクリックしました。
このコードのポイントは、shadow_rootプロパティからさらにfind_elementを呼び出している点にあります。
ただし、注意点としてshadow_root内での検索にはBy.XPATHを使用することができません。
Shadow DOM内部の探索では、By.CSS_SELECTORやBy.IDなどを使用する必要があることを覚えておきましょう。
JavaScript Executorを利用した代替アプローチ
環境やライブラリのバージョンによっては、shadow_rootプロパティが期待通りに動作しない場合や、より柔軟な操作が求められる場合があります。
そのような状況では、Seleniumのexecute_scriptメソッドを使用してJavaScriptを直接実行する手法が有効です。
JavaScriptであれば、DOMのshadowRootプロパティに直接アクセスできるため、確実性が高いというメリットがあります。
# JavaScriptを介してShadow DOM内の要素を取得する例
script = 'return document.querySelector("#shadow-host-element").shadowRoot.querySelector(".inner-input")'
input_field = driver.execute_script(script)
# 取得した要素に対して値を入力
input_field.send_keys("テスト入力")
この手法は、特に古いブラウザ環境のシミュレーションや、特定のフレームワークが独自にDOMを拡張している場合に強力な武器となります。
ただし、スクリプトが文字列として記述されるため、コードの可読性や保守性が低下する恐れがある点には留意してください。
ネストされたShadow DOMへのアクセス手法
近年の複雑なWebフロントエンドでは、Shadow DOMの中にさらに別のShadow DOMが存在する「ネスト構造」が採用されることも珍しくありません。
このような構造を操作する場合、段階的にシャドウルートを辿っていく必要があります。
以下の表に、ネストされた構造へのアクセス手順をまとめました。
| 階層 | 操作内容 | 対象オブジェクト |
|---|---|---|
| 第1階層 | メインDOMからホスト要素を取得 | driver |
| 第2階層 | 第1のシャドウルートを取得し、内部の次ホストを探索 | shadow_root_1 |
| 第3階層 | 第2のシャドウルートを取得し、目的の要素を特定 | shadow_root_2 |
具体的なコード例は次のようになります。
# 第1のホストを取得
host1 = driver.find_element(By.ID, "outer-host")
root1 = host1.shadow_root
# 第1のシャドウルート内にある第2のホストを取得
host2 = root1.find_element(By.CSS_SELECTOR, "inner-host")
root2 = host2.shadow_root
# 第2のシャドウルート内にある最終的な要素を操作
final_element = root2.find_element(By.TAG_NAME, "input")
final_element.send_keys("ネストの攻略完了")
このように、「ホスト取得 → ルート取得」のプロセスを繰り返すことが、深い階層にある要素に辿り着くための最短ルートです。
Shadow DOM操作を安定させるためのベストプラクティス
Shadow DOM内の要素操作は、通常の要素よりもタイミングの問題で失敗しやすい傾向にあります。
特に、コンポーネントが動的に生成される場合、シャドウルートがDOMにアタッチされるまでわずかなラグが生じることがあります。
安定性を高めるためには、適切な待機処理(Explicit Waits)を組み込むことが不可欠です。
しかし、Seleniumの標準的なWebDriverWaitとexpected_conditionsは、Shadow DOM内の要素を直接サポートしていません。
そのため、独自の待機ロジックを実装するか、以下のようにシャドウホストの存在を確認してからルートへアクセスする工夫が必要です。
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# シャドウホストが表示されるまで待機
wait = WebDriverWait(driver, 10)
host = wait.until(EC.presence_of_element_located((By.ID, "dynamic-shadow-host")))
# ホストが確定してからルートを取得
root = host.shadow_root
# 内部要素の取得(必要に応じてここでも短いスリープやリトライを入れる)
# ※2026年のモダンな開発では、カスタムWait関数を作成するのが一般的です
また、Shadow DOMには「Open」と「Closed」の2つのモードが存在します。
Seleniumで操作可能なのはmode: "open"で定義されたShadow DOMのみであり、「Closed」モードの場合は外部のスクリプトから一切アクセスできません。
もし、どうしても要素が取得できない場合は、対象のサイトがClosedモードを使用していないか、ブラウザの開発者ツール(F12)の「Elements」タブで確認してみてください。
要素の横に「#shadow-root (closed)」と表示されている場合、残念ながらSeleniumを含む標準的な自動化ツールではその中身を操作することは不可能です。
実践的なコード例:会員登録フォームの自動操作
ここでは、より実践的なシナリオとして、Shadow DOM内に構築された会員登録フォームへの入力を想定したコードを紹介します。
この例では、複数の入力フィールドとチェックボックスを操作します。
def fill_shadow_form(driver):
try:
# フォーム全体のホストを取得
form_host = driver.find_element(By.CSS_SELECTOR, "user-registration-form")
root = form_host.shadow_root
# ユーザー名入力
username = root.find_element(By.CSS_SELECTOR, "input[name='username']")
username.send_keys("selenium_user_2026")
# メールアドレス入力
email = root.find_element(By.CSS_SELECTOR, "input[name='email']")
email.send_keys("test@example.com")
# 利用規約に同意(カスタムチェックボックス)
# Shadow DOM内の要素からさらにネストされた要素を探すパターン
terms_host = root.find_element(By.CSS_SELECTOR, "terms-checkbox-component")
terms_root = terms_host.shadow_root
checkbox = terms_root.find_element(By.ID, "check-mark")
checkbox.click()
# 送信ボタンクリック
submit_btn = root.find_element(By.CLASS_NAME, "submit-action")
submit_btn.click()
return True
except Exception as e:
print(f"エラーが発生しました: {e}")
return False
# 実行
if fill_shadow_form(driver):
print("フォーム送信に成功しました。")
フォーム送信に成功しました。
このように、コンポーネントごとに細分化された構造であっても、一つずつルートを特定してアクセスする原則を守れば、確実に自動化を進めることができます。
複雑なセレクタを一行で書こうとせず、ステップごとに変数へ格納していくことで、デバッグが容易なコードになります。
まとめ
Seleniumを用いたShadow DOMの操作は、一見すると難解に思えますが、その仕組みを正しく理解すれば決して不可能ではありません。
重要なのは、通常のDOM探索とは異なり、shadow_rootという境界線を越えるための明示的なステップが必要であるという点です。
本記事で紹介した、Selenium 4標準のプロパティ活用、JavaScriptによる補完、そしてネスト構造への段階的アプローチを組み合わせることで、ほとんどのモダンWebサイトの自動操作に対応できるはずです。
Web Componentsの普及が進む2026年において、これらのテクニックは効率的なテスト自動化を実現するための必須スキルと言えるでしょう。
まずは、操作したい要素がどのシャドウルートに属しているかを開発者ツールで冷静に分析することから始めてみてください。
