【ITニュース解説】🥷 Shadow DOM in Test Automation: A Practical Guide with Cypress
2025年09月24日に「Dev.to」が公開したITニュース「🥷 Shadow DOM in Test Automation: A Practical Guide with Cypress」について初心者にもわかりやすく解説しています。
ITニュース概要
Webアプリテスト時、画面要素がShadow DOMという独立したDOM構造内にあると、テストツールから直接アクセスできない場合がある。Cypressでは`.shadow()`コマンドや設定でこれに対応し、安定したテストを構築できる。Slotted要素やクローズドなShadow DOMには特別な対応が必要だ。
ITニュース解説
Webアプリケーションのテスト自動化を進める際、画面上に確かに存在するはずの要素に、テストコードがうまくアクセスできないという状況に遭遇することがある。これは、Webページの構造が想像以上に複雑になっている現代のアプリケーションでは珍しくない現象だ。この問題の背景には、Webページの要素が「iframe」と呼ばれる別のページの中に埋め込まれていたり、「Shadow DOM」と呼ばれる特殊な領域にカプセル化されていたりする場合がある。特にShadow DOMは、Webアプリケーション開発で広く用いられているため、システムエンジニアを目指す上でその仕組みを理解しておくことは、効果的なテストコードを書くために不可欠な知識となる。
Shadow DOMとは、Webページの構造を定義するDOM(Document Object Model)の中に、さらに独立した小さなDOM構造が存在するような仕組みを指す。これは、Web Componentsという技術仕様の一部として導入されたもので、現代のUIライブラリやフレームワーク(例えばMaterial UI、Ionic、Lit、Angularのコンポーネントなど)で広く利用されている。Shadow DOMの主な利点は、その「カプセル化」という特性にある。これは、Shadow DOM内部で定義されたCSSスタイルやJavaScriptコードが、Webページ全体の他の部分に影響を与えることなく、そのShadow DOMの内部に限定されるというものだ。これにより、コンポーネントは他の部分と衝突することなく独立して機能し、再利用性が高まる。また、Shadow DOMの内部構造がメインのDOMから隠蔽されるため、Webページ全体の複雑さが軽減され、開発者が個々のコンポーネントのロジックに集中しやすくなるというメリットもある。簡単に言えば、Shadow DOMはメインのDOMの中に作られた、ミニチュア版の独立したDOMのようなものだと考えると理解しやすいだろう。
Webアプリケーションの要素にアクセスする際、通常はIDやクラス名などのセレクタを使って目的の要素を指定する。例えば、一般的なHTML構造で、ログインフォームのユーザー名入力欄にアクセスする場合を考えてみよう。
もしHTMLが以下のような構成であれば、
<div id="login">
<input id="username" />
</div>
Cypressのようなテストフレームワークでは、cy.get('#username').type('hello')のようにIDを直接指定して、その入力欄に文字を入力する操作を行うことができる。これは、usernameというIDを持つinput要素が、メインのDOMに直接存在し、テストコードから簡単に発見できるためだ。
しかし、このinput要素がShadow DOMの中に存在している場合は、アクセス方法を工夫する必要がある。Shadow DOMには「open(オープン)」と「closed(クローズド)」という二つのタイプがあり、それぞれ外部からのアクセス可能性が異なる。
まず「open」タイプのShadow DOMの場合を見てみよう。これは、外部からのアクセスが許可されているShadow DOMを指す。例えば、カスタム要素である<my-login>の中に、openなShadow Rootとして#shadow-root (open)が作成され、その内部にinput id="username"が存在するようなケースだ。
<my-login>
#shadow-root (open)
<input id="username" />
</my-login>
この場合、単にcy.get('#username')と指定しても、目的の要素は見つからない。なぜなら、#usernameはShadow DOMの中にカプセル化されており、メインのDOMからは直接認識できない状態になっているからだ。
CypressでこのようなopenなShadow DOM内の要素にアクセスするには、主に二つの方法がある。
一つ目は、Cypressが提供する.shadow()コマンドを使う方法だ。まず、Shadow DOMのホスト要素である<my-login>を取得し、その後に.shadow()コマンドをチェーンしてShadow DOMの内部に入り、その中で目的の要素を探す。具体的なコードとしては、cy.get('my-login').shadow().find('#username').type('hello')のようになる。
二つ目は、Cypressの設定ファイルであるcypress.config.jsに、includeShadowDom: trueという設定をグローバルに有効にする方法だ。この設定を有効にすると、Cypressはテスト実行時に自動的にShadow DOMの内部も検索対象に含めるようになるため、個々のテストコードで.shadow()コマンドを明示的に記述する必要がなくなる。これは、アプリケーションが多くのShadow DOMを使用している場合に、テストコードをよりシンプルに保つために有効なアプローチだ。
次に「closed」タイプのShadow DOMについて説明する。これは、外部からのアクセスが厳しく制限されており、完全にプライベートな要素として設計されているShadow DOMだ。
<my-login>
# --- > shadow-root (closed)
<input id="username" />
</my-login>
このclosedタイプのShadow DOM内の要素には、Cypressの.shadow()コマンドを使っても、また通常のWebページの要素を検索するクエリセレクタを使っても、直接アクセスすることはできない。これは、意図的に外部からの操作を許さないように設計されているためである。もし、closed Shadow DOM内の要素をテストする必要がある場合は、開発チームと協力し、テストのためだけに、アプリケーションが何らかのテスト用フック(例えば、要素のプロパティや属性を外部から参照・操作できるような仕組み)を公開するように依頼するなどの対策が必要になる。あるいは、そのコンポーネントが単体で正しく動作するかを確認するコンポーネントレベルのテストに切り替えることも、一つの解決策となる場合がある。
さらに、Shadow DOMに関連して「Slotted Elements(スロットされた要素)」という概念も理解しておく必要がある。これは、メインのDOMに存在する要素が、Shadow DOM内部の特定の位置に「投影(projected)」されて表示される仕組みを指す。
例えば、Shadow DOMの内部にスロットが定義されており、
<my-login>
#shadow-root (open)
<slot name="email"></slot>
</my-login>
そのスロットに対応する形で、メインのDOMにメールアドレス入力欄が存在する場合を考えてみよう。
<input slot="email" id="email-input" />
このemail-inputというIDを持つinput要素自体は、実際にはShadow DOMの外部、つまりメインのDOMに存在している。しかし、slot="email"という属性によって、あたかもShadow DOMの内部にあるかのように視覚的に配置されて表示される。
このようなスロットされた要素に対してCypressでアクセスする場合、前述の.shadow()コマンドは直接的な解決策とならない。なぜなら、input要素はShadow Rootの外部に存在し、単に視覚的にその位置に投影されているだけだからだ。このケースでは、カスタムCypressコマンドを作成することが推奨される。具体的には、対象の要素を取得し、その要素がどのノードに割り当てられているかをassignedNodes()メソッドで取得し、その割り当てられたノードを操作する、という一連の処理をまとめたコマンドを作成することになる。
例えば、以下のようなカスタムコマンドを定義し、利用できる。
Cypress.Commands.add('getSlottedElement', (selector) => {
return cy.get(selector).then(($el) => {
return cy.wrap($el[0].assignedNodes()[0])
})
})
そして、このカスタムコマンドを使ってcy.getSlottedElement('#email-input').type('hello@qa.com')のように要素にアクセスし、操作することが可能になる。
では、なぜテスト自動化においてShadow DOMの知識がこれほど重要になるのだろうか。それは、現代のシングルページアプリケーション(SPA)やWeb Componentsでは、Shadow DOMが非常に広く利用されており、その採用が今後も拡大していくと見られているからだ。この技術を考慮せずにテストスクリプトを作成すると、テスト中に「要素が見つかりません」というエラーが頻繁に発生し、テストが不安定になるだろう。Shadow DOMを適切に扱う知識があれば、テストコードはより安定し、アプリケーションの変更にも柔軟に対応できる「将来にわたって機能する」テストを作成できるようになる。
まとめると、Webアプリケーションの要素は、常にメインのDOMから直接アクセスできるとは限らないという点を理解することが重要だ。アプリケーションの内部構造には、一般的なDOM要素だけでなく、iframeや、openまたはclosedなShadow DOMが含まれる可能性がある。CypressはopenなShadow DOMに対しては.shadow()コマンドやincludeShadowDom設定によって効果的に対応できる。スロットされた要素に対しては、カスタムヘルパーコマンドの作成が必要になる場合がある。そして、closedなShadow DOMについては、開発者との密な連携が不可欠となることを認識しておくべきだ。
もしあなたがテスト自動化に初めて取り組むシステムエンジニアを目指すのであれば、ぜひCypressとシンプルなWeb Componentのデモプロジェクトを使って、実際にShadow DOMの動作を体験してみることを強くお勧めする。実際にコードを書いてその挙動を確認することで、Shadow DOMの扱いがテスト自動化における必須スキルであることがすぐに理解できるはずだ。Webアプリケーションの内部構造を深く理解し、それに対応する適切なテスト手法を身につけることは、信頼性の高いシステムを開発し提供するために不可欠な能力となるだろう。