1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Playwright で GA4 のログ検証を自動化してみた

1
Last updated at Posted at 2025-07-18

▼この記事で何が分かる?

  • playwrightって何かをざっくり理解
  • playwrightをGA4ログ検証に使うイメージが付けられる
  • playwrightにてログ検証自動化する流れを理解できる

まずはplaywrightについて簡単に述べた後、ログ検証の自動化の全体像と結果を紹介する。
最後に手順を説明する。

▼この記事を書いたきっかけ

私はある企業でアナリティクスエンジニア?データアナリストのような業務を幅広くやっている。
webサイトにGA4のログが飛ぶように設定してそのログがちゃんと飛んでいるかを検証することがある。この検証をログ検証と呼んでいる。
そのようなログ検証の一貫で、複数ページにてどのようなログが飛んでいるかを一覧化して欲しいと言われることがあった。ページ数は膨大のため各要素をクリックしてそれを書き起こして...と作業するのは骨が折れる。
このログ検証作業を自動化したいと考えた。

これを実現できそうなツールとしてplaywrightを見つけた。
この記事では、playwrightを使ってログ検証を行ってみた事例を紹介する。

先に述べておくが完全に成功は出来なかった。しかし、可能性が感じられる結果であった。

▼playwright とは? (by Gemini CLI)

Playwright は Microsoft が開発した、Web ブラウザを自動操作するためのライブラリ。Chromium, Firefox, WebKit (Safari) など、主要なブラウザを単一の API で操作できるのが特徴です。テスト自動化だけでなく、スクレイピングや今回のような計測検証にも活用できる。

▼ログ検証自動化全体像と結果

全体像

  1. node.js環境構築
    1. node.jsインストール
    2. playwrightインストール
    3. jsコードの記述
  2. 作成したjsコードを実行
    1. ログ検証したいURLとGA4測定IDを指定
  3. ログ検証結果がjsonファイルとして出力される(以下写真の通り)
    1. クリック出来る全要素をクリックした際のログが出力される
      1. イベント名
      2. イベントパラメータ
      3. CSSセレクタ
        1. 結果が合っているかを検証ツールにて確認する際に使えると考えたため加えた。

結果

githubpagesにて作成したページで実際にやってみたが以下のようになった。
(GA4のログは設定済み)

image.png

image.png

指定通りにログがjsonに書き出されている。
※この記事では紹介しないがクリックした要素のスクリーンショットも自動で取って保存することも出来た。

▼感想と展望

初めてplaywrightを触りログ検証の自動化を行ってみた。
サンプルで作った簡単なページに関してはログ検証の自動化は上手くいった。
しかし、この記事では紹介していないが業務にて扱っているウェブサイトにて同様に実施したところ成功しなかった。
理由は明確でないがtimeoutの結果のエラーだったため処理に時間が掛かっていることが原因であろう。
timeoutの時間を引き延ばしたり試したがエラーは解消出来なかった。
playwrightの操作はまだまだ知見が無いのでtimeoutエラーを解消するための方法を探っていきたい。

また、今回はログ検証の結果をjsonとして吐き出すことをやった。
しかし、最終的にはログ検証の結果をクリックした要素のキャプチャとそのログをスプレッドシートに書きこんでログ設計書を作成するところを自動化するところを目指したいと考えている。


▼手順

簡単にではあるが手順を説明する。詳細の説明はしない。
私はGemini CLIを使って簡単に実装出来た。

➀セットアップ

まず、プロジェクトディレクトリを作成し、Playwright をインストール。

mkdir ga4-playwright-verifier
cd ga4-playwright-verifier
npm init -y
npm install playwright

➁jsファイルの作成

// 必要なモジュールをインポート
const { chromium } = require('playwright'); // Playwrightのchromiumブラウザを操作するためのモジュール
const fs = require('fs'); // ファイルシステムを操作するためのNode.js標準モジュール

// 非同期の即時実行関数でスクリプト全体を囲む
(async () => {
  // Playwrightのブラウザインスタンスを起動
  const browser = await chromium.launch();
  // 新しいページ(タブ)を作成
  const page = await browser.newPage();

  // ページ全体のデフォルトタイムアウトを100秒に設定
  page.setDefaultTimeout(100000);

  // スキャン結果を保存するための配列
  const results = [];
  // レポートを保存するJSONファイルのパス
  const reportPath = 'ga4_click_events_report.json'; // レポートファイル名

  // スキャン対象のウェブページのURL
  const targetUrl = 'https://your-target-site.com'; // 検証したいサイトのURLに置き換えてください
  // 指定したURLに移動し、ページのDOMが完全に読み込まれるまで待機
  await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
  console.log(`Navigated to ${targetUrl}`);

  // クリック可能な要素を特定するためのCSSセレクタを定義
  const clickableSelectors = [
    'a', // リンク
    'button', // ボタン
    '[role="button"]', // role属性がbuttonの要素
    '[onclick]' // onclick属性を持つ要素
  ].join(', '); // セレクタをカンマで連結

  try {
    // クリック可能なすべての要素を取得し、その数を数える
    let elementsCount = (await page.locator(clickableSelectors).all()).length;
    console.log(`Found approximately ${elementsCount} clickable elements.`);

    // 見つかったすべてのクリック可能な要素に対してループ処理
    for (let i = 0; i < elementsCount; i++) {
      // ループの各反復で、ページ上の要素を再取得(ページ遷移などで要素が無効になる可能性があるため)
      const elements = await page.locator(clickableSelectors).all();
      elementsCount = elements.length; // 要素数を再評価
      if (i >= elementsCount) break; // インデックスが範囲外になったらループを終了

      const element = elements[i];

      // 要素が非表示または無効である場合は、処理をスキップ
      if (!(await element.isVisible()) || !(await element.isEnabled())) {
        continue;
      }

      // ログ出力用に、要素のテキスト内容またはaria-labelを取得
      const elementInfoForLog = (await element.textContent() || await element.getAttribute('aria-label') || '').trim().substring(0, 30);
      console.log(`\n[${i + 1}/${elementsCount}] Processing: ${elementInfoForLog}...`);

      // 要素のCSSセレクタを取得
      const cssSelector = await element.evaluate(el => {
        // 最も具体的なセレクタを生成するヘルパー関数
        const getCssSelector = (el) => {
          if (!el || el.nodeType !== Node.ELEMENT_NODE) {
            return null;
          }

          // IDがあればIDを使用
          if (el.id) {
            return `#${el.id}`;
          }

          const path = [];
          while (el.nodeType === Node.ELEMENT_NODE) {
            let selector = el.nodeName.toLowerCase();
            if (el.className) {
              selector += `.${Array.from(el.classList).join('.')}`;
            }
            let sib = el, nth = 1;
            while (sib = sib.previousElementSibling) {
              if (sib.nodeName.toLowerCase() == selector.split('.')[0]) {
                nth++;
              }
            }
            if (nth != 1) {
              selector += `:nth-of-type(${nth})`;
            }
            path.unshift(selector);
            el = el.parentNode;
          }
          return path.join(' > ');
        };
        return getCssSelector(el);
      });

      let ga4Event = null;
      try {
        // Promise.allを使用して、クリック操作とGA4リクエストの待機を並行して実行
        const [request] = await Promise.all([
          // GA4の測定リクエスト(URLに'/collect?v=2'を含む)を10秒間待機
          page.waitForRequest(req => req.url().includes('/collect?v=2'), { timeout: 10000 }),
          // 要素をクリック(タイムアウト10秒)
          element.click({ timeout: 10000 })
        ]);

        // 取得したリクエストURLをデコード
        const decodedUrl = decodeURIComponent(request.url());
        // URLのクエリパラメータをパース
        const params = new URLSearchParams(decodedUrl.split('?')[1]);

        // GA4の測定ID(tid)が指定したものと一致するか確認
        if (params.get('tid') === 'G-XXXXXXXXXX') { // あなたのGA4測定IDに置き換えてください
          ga4Event = {};
          // クエリパラメータをループ処理
          params.forEach((value, key) => {
            // 指定した条件(en, epで始まる、またはdl, dt, tid)に一致するパラメータのみをga4Eventオブジェクトに格納
            if (key.startsWith('en') || key.startsWith('ep') || ['dl', 'dt', 'tid'].includes(key)) {
              ga4Event[key] = value;
            }
          });
          const eventName = ga4Event['en'] || 'N/A';
          console.log(`  -> GA4 Event Found: ${eventName}`);
        } else {
          console.log('  -> GA4 event with non-matching tid captured and ignored.');
        }
      } catch (error) {
        // GA4イベントが取得できなかった場合のエラー処理
        console.log(`  - No GA4 event captured for this click: ${error.message}`);
      }

      // スキャン結果を配列に追加
      results.push({
        elementIndex: i,
        elementText: elementInfoForLog,
        cssSelector: cssSelector, // CSSセレクタを追加
        ga4Event: ga4Event,
      });

      // クリックによってページが遷移した場合の処理
      if (page.url() !== targetUrl) {
        console.log(`  -> Page navigated to ${page.url()}. Returning to original page...`);
        // 元のページに再移動
        await page.goto(targetUrl, { waitUntil: 'domcontentloaded' });
        console.log('  -> Returned to original page.');
      }
    }
  } finally {
    // スクリプトの最後(エラーが発生しても実行される)で、結果をJSONファイルに書き込む
    fs.writeFileSync(reportPath, JSON.stringify(results, null, 2));
    console.log(`\nScan complete. Report saved to ${reportPath}`);
    // ブラウザを閉じる
    await browser.close();
  }
})();

③スクリプトの実行

作成した scan-clicks.js は、以下のコマンドで実行できる。

node scan-clicks.js

実行すると、Playwright がブラウザを起動し、指定された URL にアクセスして自動的にクリックイベントの検証を開始する。コンソールには処理の進捗が表示される。

結果の確認

スクリプトの実行が完了すると、ga4_click_events_report.json というファイルが生成される。この JSON ファイルには、各クリックイベントに関する以下の情報が含まれる。

  • elementIndex: クリックした要素のインデックス
  • elementText: クリックした要素のテキスト内容または aria-label
  • cssSelector: クリックした要素の CSS セレクタ(検証ツールでの特定に使う意図)
  • ga4Event: 取得された GA4 イベントのパラメータ(イベント名、カスタムパラメータなど)。GA4 イベントが計測されなかった場合は null になる。

出力例:

[
  {
    "elementIndex": 0,
    "elementText": "お問い合わせ",
    "cssSelector": "a.contact-link",
    "ga4Event": {
      "en": "click",
      "ep.link_text": "お問い合わせ",
      "dl": "https://your-target-site.com/contact",
      "dt": "お問い合わせページ",
      "tid": "G-XXXXXXXXXX"
    }
  },
  {
    "elementIndex": 1,
    "elementText": "資料ダウンロード",
    "cssSelector": "button#download-button",
    "ga4Event": null
  }
]
1
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?