メディアと広告のフロント周りを見ている自分がよく使う不具合調査のときのChrome DevToolsの機能(実践編)

この記事は GMOアドマーケティング Advent Calendar 2019 7日目の記事です。

こんにちは、フロント周りを担当しているY.A.です。

以前に『メディアと広告のフロント周りを見ている自分がよく使う不具合調査のときのChrome DevToolsの機能(tips編)』でいくつかtipsを紹介しました。
https://techblog.gmo-ap.jp/2019/07/11/using-chromedevtools-like-pro-tips/

ただ実際にこれらを「どういう場面で」「どのように使用するのか」、イメージ付いていない方もいたかもしれません。
Chrome DevToolsを使用して不具合調査するときは、1つのパネル/機能だけで原因特定まで至らないことも多いです。ほとんどの場合仮説とその検証のために、複数のパネル/機能を使用しますので、そのため少し難易度高く感じている方もいるかと思います。

今回は3つの具体例を通して、それらパネル/機能をどう活用できるのか説明したいと思います。
事例はいずれもメディアサイト(または広告や様々なサードパーティJavaScriptが入っているサイト)を想定しています。
それぞれのセクションでサンプルページも用意したので、必要に応じてご自身の手で確かめてみてください。

また本稿では「ブレークポイント」をよく使います。
ブレークポイントについては前回触れておりませんが、Googleの『Chrome DevTools で JavaScript をデバッグする』が詳しいので、触れたことない方は事前に目を通しておくと、よりイメージつきやすいかと思います。

【目次】


1. リンクの遷移先の不具合 (Elements/Event Listeners/Sources/Breakpoint)

サイト内で3つ目のリンクの遷移がうまくいっていない(404)になるという不具合があり、調べることになりました。

サンプルページ
https://arasaki-yuki.github.io/techblog/example-click-event/

『Elements』 パネルを開いてみたところ、aタグのhrefの値(URL)は問題なさそうですが、確かにユーザーがタップするとhrefに指定してあるURLに遷移しませんでした。

上からリンクをタップしていく様子(gif)

さっそく調べます。

調査方法

本件はタップしたときのアクションに不具合が起きていることから、何かしらJSのイベントリスナーが関係していると考えます。

そこでまずChrome DevToolsで『Elements』パネルにある 『Event Listeners』を開きます。ここでイベントリスナーを確認します。

『Elements』パネル内にて、DOMノードを選択(クリック)します。するとそのノードに紐づいたイベントリスナーが表示されます。今回は問題のある3つ目のliタグのノードを選択しています。

※注意ですが、パネル内で『Ancestors All』 にチェックが入っていると、『Elements』パネルでクリックしたDOMノードを基点に、祖先のイベントまで検知されて表示されます。 https://developers.google.com/web/tools/chrome-devtools/inspect-styles/edit-dom?hl=ja
ページによってはイベントリスナーが多く出てくるため、見やすいように 『Ancestors All』 のチェックを外して絞ります。詳しくは前回記事をご参考ください。 https://techblog.gmo-ap.jp/2019/07/11/using-chromedevtools-like-pro-tips/#section5

パネルを見ると、DOMノードにクリックイベントが設定されていることが分かりました。それではイベントの横にある▼のアイコンをクリックしてイベントリスナーを展開します。

すると要素と、イベントハンドラへのリンクが表示されています。

さっそくリンク先へいってもよいのですが、ひとまず先にこのイベントが不具合に関係しているかどうかを見てみます。
イベントハンドラとして出ている要素の横でマウスオーバーすると 『Remove』という文字列が表示されます。『Remove』をクリックしてイベントの紐付けを解除します。

解除した上で改めて3つ目のリンクをタップしてみます。

イベントを解除した上でタップした動き(gif)

すると正常に遷移しました。やはりこのイベントリスナーが何かしらの不具合を生んでいる可能性が高いようです。
ここからはそのハンドラにブレークポイントを貼って処理を追っていきます。

先のイベントハンドラを見るために、ページをリロードし直します。リンク(click.js)も再度表示されますので、それをクリックします。

クリックすると、該当箇所が『Sources』パネルで展開されます。

それではブレークポイントを貼ります。
ブレークポイントは、JSの実行を一時的に止めるポイントのことです。貼ることで、そこを起点にステップ実行を行えるので、じっくり処理の流れを確認することができます。

ブレークポイントの設定はソースの任意の行をクリックすることでできます。まずはクリックした後の直後の動きを見たいので、下記箇所に貼ります。

それでは改めて3つ目のリンクをタップしてみます。

イベントが発火し、ブレークポイントを貼った箇所で一旦実行が止まりました。このときUIの画面はグレーアウトします。

ここからはステップ実行で一つひとつ処理を追っていきます。
実際に実行していくとわかると思いますが、3つめのリンクをクリックした時だけ、「href」という変数の値に「undefined」が入ります。

ブレークポイントを貼った状態で3つ目のリンクをタップ、その後ステップ実行していく様子(gif)

「undefined」になる理由を調べていきましょう。
コードを目で追っていっても良いですし、ブレークポイントを貼ったまま、リロード/リンクタップ/ステップ実行を何回か繰り返してみても良いでしょう。どうやらクリックした要素の先祖要素に任意のクラスがないといけないことが分かると思います。
今回の場合でいうとaタグの親要素に「wrap」というクラス名があること前提の仕組みだったようです。

これで対応すべきことが見えました。
先祖要素にそのクラスを付与するか、あるいはこのイベントハンドラの改修をかけるかといったところです。

依頼者にその旨報告して完了です。
その他少しtipsです。

*ブレークポイントの解除

ブレークポイントを外すには、マーカーを再度クリックします。

ブレークポイントを貼ったり解除したりする動き(gif)

その他に、右ペインの『Breakoints』にてチェックボックスのon/offで任意のブレークポイントの一時的に無効にできたり、右クリックメニューからブレークポイントの一括削除などもできます。

上記のことをしたときの動き(gif)

*「Event Listeners」を使わないイベント検出方法

前回の記事でも紹介しましたが、今回事例で紹介した方法よりもっと手軽にイベントリスナーを見つける方法として、『Sources』内の『Event Listener Breakpoints』があります。
https://techblog.gmo-ap.jp/2019/07/11/using-chromedevtools-like-pro-tips/#section5

例えば、「なんでも良いからページ内で起きたクリックイベントを捕捉したい」場合、『Mouse』>『click』にチェックを入れることでハンドラを捕捉することができます。

『Mouse』>『click』にチェックを入れて今回の3つ目のリンクをタップした時の様子(gif)

この方法でも同様に、今回の3つ目のリンクのハンドラを検出することができます。
ケースによってはこちらの方が便利かもしれませんので、試してみてください。


2. アドサーバーに入れた広告が期待通りに展開していない (Elements/Network)

広告が1つしか出るべきでない場所に、3つ出る(2つ余計に出る)という不具合の相談を受けました。

サンプルページ
https://arasaki-yuki.github.io/techblog/example-using-network/

調査方法

調べます。まずは『Elements』パネルを開きます。すると広告タグが3つ展開されてしまいました。

この時点で「アドサーバーに広告タグを(誤ってか)3つ入稿しているのではないか」という疑いが出てきます。

さっそく依頼者にそのことを伝えて、入稿内容の確認を取っても良いかもしれませんが、しかしもう少し確信をもちたいところです。
なぜなら、そもそも『Elements』パネルに展開されているのはあくまでJSなどが実行された後のDOMツリーのビューだからです。つまり1つの広告タグがもう一つの広告タグを読み込んで展開、という再帰的な広告呼び出しが起こっている可能性も無くはありません。
この場合でいうと、1つ目の広告タグと思しきものが内部で2つめ3つめを呼び出しているケースです。

では先に立てた「入稿画面で広告タグを3つ入れている」という仮説をどうやって検証するかというと、ここで『Network』タブを使用します。

『Network』タブはページ内で起こったリクエスト、レスポンスを確認するのに使われます。

この『Network』タブを開いた後に「Ctrl + f」のショートカットを打ってください。すると左側に検索ペインが表示されます。

その検索ペインのinputに検索したい文字列を打つことで、ページ内で起こったリクエストとそのレスポンスのheaderとbody内にマッチするものをリストで出してくれます。
https://developers.google.com/web/updates/2018/04/devtools#network-search

今回の場合は広告タグの文字列を検索します。
『Elements』パネルで見たところ「ad.js」というscriptを読み込むようなので、「ad.js」という文字列で検索します。検索結果にはリクエストURLとレスポンス内容が表示されます。

いくつか検索結果に出ていますが、上の3つは中身を見ると広告タグそのもののようなので、最後の「endpoint.json」が怪しそうです。
ここにリストされる検索結果はクリッカブルになっていますので、「endpoint.json」のbody部をクリックしましょう。右のペインに内容が展開されます。

展開されたら該当文字列を調べます。
するとレスポンスにやはり広告タグらしきものが3つ確認できました。

アドサーバーから返されているJSONの中に、広告タグが3つ入っていたということなので、やはり3つ入稿されている可能性がかなり高いことが分かりました。

依頼者にその旨伝えて確認してもらうようにして完了です。


3. インフィニティスクロールコンテンツの表示不具合(Elements/DOM Breakpoints/Breakpoint)

後読みコンテンツでJSのscriptが正しく実行されていないという不具合調査依頼がありました。
『Elements』パネル上では存在確認できるのですが、実行されていないということです。

サンプルページ
https://arasaki-yuki.github.io/techblog/example-breakpoint/

調査方法

それでは見てみます。

インフィニティスクロールであれば、「あるところまでスクロールしたらコンテンツが追加される」仕様ではないかと考えます。確認のため一旦『Elements』パネルを開きながらスクロールして、動きを見てみます。

『Elements』パネルを開きながらページをスクロールして、いくつかの要素に子要素が追加された様子(gif)

見たところやはりある地点で、特定の要素に子要素が追加されていました。何かがインビュー(または〇px手前まで来たら)されたタイミングで起こっているようです。

その追加しているscriptの処理内容を調べれば原因が分かりそうです。処理を行っているJSのscriptがどこかに存在するはずなので探します。

今回は『DOM Breakpoints』を使います。『Elements』パネルを開いて、スクロールで要素が追加されるDOMをクリックして、右クリックで『Break On』を選び、『subtree modification』を選択します。

選択するとDOMノードの左に青い丸のマーカーが付きます。マーカーがあることを確認出来たら改めてリロードしてスクロールします。

すると突然ブレークポイントを置いた時と同じように、UI画面がグレーアウトします。これは『Break On』を設定したDOMノードに、子要素が追加されたため(『subtree modification』が起きたため)、scriptの実行が一時停止したのです。

DOMノードに『Break On』『subtree modification』を設定し、その上でリロード、スクロールして、ブレークした様子(gif)

そしてパネル側を見ると、『Sources』パネルに子要素を追加するハンドラが展開されています。

見たところ、innerHTMLで子要素を入れていることが分かりました。そして代入しているのはHTMLタグの文字列で、その中に今回依頼者のいうscriptタグが入っていることが分かりました。マウスオーバーすると中身全体が表示されます。

入っている文字列を見る限り問題なさそうです。『Elements』パネルにもこの文字列同様にscriptタグがDOMノードとして存在するのは最初の通りです。

ではなぜscriptタグが実行されないかという話ですが、調べたところHTML5の仕様上innerHTMLでscriptタグは実行できないことがわかりました。

Although this may look like a cross-site scripting attack, the result is harmless. HTML5 specifies that a tag inserted with innerHTML should not execute.

こうなるとscriptタグだけinnerHTMLでなく、createElement(‘script’)などで実装を変えるなど、仕様変更が必要です。

とりあえず状況が分かったのでその旨依頼者に報告します。

—-

以上がほんの3例でしたが、実際の活用例でした。
今回紹介したやり方はあくまで1つの方法です。DevToolsは触れるほど理解が深まると思いますので、みなさんそれぞれで効率的な方法を見つけていただければと思います。

まだまだ紹介したい強力な機能もあるのですが、それはまたどこかのタイミングでできればとは思います。

最後におまけです。


4. おまけ(Breakpoint/Console)

ブレークポイントと『Console』をうまく使うことでより手軽にデバッグができます。

サンプルページ
https://arasaki-yuki.github.io/techblog/example-breakpoint_tip/

上記画像は、とある即時関数内でブレークポイントを貼り、一時停止した様子です。

ブレーク中に右ペインの『Scope』を見ると、ローカル/グローバル変数の値を確認することができます。
ただ『Scope』以外に、『Console』でそのローカル/グローバル変数を打っても、同様に同じ値が返ってきます。(上の画像参照)

この時点でお気づきになる方もいるかと思いますが、ブレークポイントと『Console』を組み合わせて使えば、グローバル/ローカル変数の値を参照だけでなく、それらの上書きも可能となります

例えば今回のサンプルページで見てみます。
breakpoint.jsを読み込んでおり、その中で1/100の確率で、あるクリエイティブを出すというロジックが実装されています。

scriptを見ると1~100までの乱数を生成して変数「rand_num」に入れ、その後にif文で「(s > 99)」という条件を設定し、ブロック内にクリエイティブを出す処理を入れることで、実現しているようです。

つまり頑張ってリロードをし続ければ、1/100の確率でいつかクリエイティブを見ることができますが、ただ手っ取り早くこのクリエイティブを見ないといけないなどの場合(=if文を通したいと思った場合)先のテクニックを使用することで解決できます。

まずif文の行にブレークポイントを貼り、リロードします。
ブレークしてグレーアウトしたタイミングで『Console』にて、random_num = 100; と打ち、上書きします。

その後ステップ実行、あるいは再生アイコンをクリックして処理を流します。こうすることでif文のブロックに処理が通すことができました。

上の処理をやった様子(gif)

なお『Console』以外にも『Sources』内の『Snippets』も同様にJSを実行できます。長くなる場合などはブレークポイントと『Snippets』の組み合わせでやると良いかと思います。

大変長くなりましたが、以上です。

前回もお伝えしましたが、情報収集においては、TwitterのChrome DevToolsアカウントやFirefox DevToolsアカウントなどおすすめです。tips満載です。
https://twitter.com/chromedevtools
https://twitter.com/FirefoxDevTools

さらに時間を見つけて、Googleが用意している『Tools for Web Devloper』も読むと良いでしょう。勉強になります。
https://developers.google.com/web/tools/chrome-devtools/javascript/?hl=ja

そして当社サービス「めるも」もどうぞよろしくお願いいたします。
https://news.merumo.ne.jp/

明日は、KONCEさんによる「エンジニアになってみて、難しいなって思ったコト」です。
引き続き、GMOアドマーケティング Advent Calendar 2019 をお楽しみください!

■エンジニア採用ページ ~福利厚生や各種制度のご案内はこちら~
https://www.gmo-ap.jp/engineer/
■Wantedlyページ ~ブログや求人を公開中!~
https://www.wantedly.com/projects/199431
■エンジニア学生インターン募集中! ~就業型インターンでアドテクの先端技術を体験しよう~
https://hrmos.co/pages/gmo-ap/jobs/0000027