Puppeteerは、E2Eテストやスクレイピングで使用されるヘッドレスChromeです。

今回は業務で「とあるサイトにログインし特定の動作を行う」という要件をPuppeteerを使って行う事になったので、使い方やコードを書く際のポイントをまとめてみようと思います。

 

動作準備

Puppeteerはnode.jsのライブラリです。

npm、pnpm、yarnのいずれかからインストールが可能です、今回はnpmを使います。

1
npm i puppeteer-core

ライブラリはnpm i puppeteerでもインストール可能ですが、chromiumも一緒にダウンロードされる為より軽量なpuppeteer-coreを使用します。

実行環境は下記の通りです。

  • Node.js(v22.0.0)
  • puppeteer-core(v24.2.0)
  • typescript(v5.6.3)

 

Puppeteerの基本操作

以下はGoogleのトップページのスクリーンショットを取得するサンプルコードです。

1
2
3
4
5
6
7
8
import puppeteer from 'puppeteer-core';
 
const browser = await puppeteer.launch();
const page = await browser.newPage();
 
await page.goto('https://www.google.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();

ブラウザを操作するまでの準備としてはpageを取得するだけです、かなりシンプルに操作できて分かりやすいですね。

もし動作確認の際に操作時の画面を表示したいのであれば、下記のようにheadlessオプションをfalseに設定します。

1
const browser = await puppeteer.launch({headless:false});

 

主に使用したコマンド

以下に、今回サイトを操作する際に使用した主要なコマンドをまとめます。

ページを指定したURLに移動

URLを指定し、特定のサイトにアクセスします。

1
await page.goto('https://example.com', {waitUntil: 'networkidle2'});

上記のようにpage.gotoの第二引数にはwaitUntilオプションが渡せます。

それぞれ指定可能な値とその意味は以下の通りです。

  • load(デフォルト):ページのloadイベントが発生するまで待機します
  • domcontentloaded:ページのDOMContentLoadedイベントが発生するまで待機します
  • networkidle0:すべてのネットワークリクエストが完了し、少なくとも500ms間ネットワークがアイドル状態(リクエストが0件)になるまで待機します。
  • networkidle2:ネットワークリクエストが2件以下で、少なくとも500ms間アイドル状態が続くまで待機します。

参考:PuppeteerLifeCycleEvent タイプ

特に問題なければ指定する必要はありませんが、JavaScriptでコンテンツを動的にロードするページ等で遷移後の操作が上手くいかない場合は、networkidle2等を試してみると良いと思います。

要素を選択してテキストを取得

下記例では、h1見出しのテキストを取得してログ出力します。

1
2
const text = await page.$eval('h1', element => element.textContent);
console.log(text);

APIを提供していないサイトから、コンテンツの内容を取得する等のケースでも使用できそうです。

 

要素に入力する

要素はCSSセレクタもしくはXPathを指定して、操作を行います。

下記例では、CSSセレクタ#usernameの要素に入力を行います。

1
await page.type('#username', 'test user');

サイトによってはCSSのIDが動的に変わったりする事もあるので、指定するセレクタは確実に取得可能か注意が必要です。

 

ボタンをクリック

type時と同様に、CSSセレクタを指定してクリックを行います。

1
await page.click('#submit-button');

ブラウザでマウスを使ってクリックする際と同様に、ボタンが表示されている必要があります。

 

ハマったポイントと解決策

Timeoutが起きて入力やクリックが出来ない

こちらが開発中に一番起きたエラーでした。

確認する主なポイントとしては、下記2点です。

  1. 要素のセレクタが合っているか
  2. 要素が表示されているか

1.に関してはコードを見直すと解決しますが、2.はブラウザ上で要素が確実に表示されている事を保証する必要がありました。

下記のようにPromise.allを活用して、特定の要素が表示されるのを待って操作を行うことで解決出来ます。

1
2
3
4
await Promise.all([
  page.waitForSelector('#confirm-button', { visible: true }),
  page.click('#confirm-button'),
]);

 

要素が表示されているにも関わらずクリックが出来ない

上述のように要素のクリックの際は表示を待ってから操作するロジックを組んでいましたが、それでも時々タイムアウトが起こりました。

エラー時にスクリーンショットを撮ってみても画面上に要素は表示されており、何が原因か分からずハマりました。

色々と試した結果、下記のようにpage.evaluateを使ったクリックに変更することで安定動作するようになりました。

1
2
3
4
5
6
7
8
await page.evaluate((selector) => {
  const element = document.querySelector(selector) as HTMLElement;
  if (element) {
    element.click();
  } else {
    throw new Error('指定された要素が見つかりません');
  }
}, '#test-selector');

page.evaluateを使ったクリックは、DOMを直接操作してクリックを実行します。

これはユーザーが実際にクリックするのではなく、JavaScriptコードが要素に対してクリックを「強制的に」実行する形になるので、要素が表示されていなくてもクリックが可能です。

ただし、通常のpage.clickとは違い挙動が正しく反映されない可能性も考えられる為、ブラウザ上の動きを確認し注意して使用しましょう。

 

終わりに

今回はPuppeteerを使ったブラウザ操作についてまとめてみました。

初めて触ってみて、スクレイピングやPDF生成・テストの自動化など、幅広い用途に対応できる強みがあると感じました。

しかし、実際にPuppeteerを動かしてみると思った以上に多くのメモリを消費したので、スケーラビリティを意識した設計は大事だと思いました。

また、サイトによってはヘッドレスChromeでの操作が禁止されていたり大量のアクセスを投げると怒られるケースもあるので、規約はきちんと確認しましょう。

最後までご覧いただきありがとうございました。