🦁

【jest-puppeteer】要素の取得に使うのはpage.evaluateと$methodsどっちが良いのか?

2022/09/17に公開

背景

Vue.jsでのブラウザテストとしてjest-puppeteerを採用しています。

使い始めは見様見真似でコードを書いていたのですが、要素の取得で$methodsを使う方法とpage.evaluateを使う方法があるが、どちらを使うのがよりベターなのか気になったので調査してみました。

要素の取得の比較

$methods

まずは$methodsでの要素取得について見てみましょう。

公式にも書かれているとおりpageオブジェクトは$$$メソッドを提供しており、両方ともdocument.querySelectorとdocument.querySelectorAllをラップしているメソッドのようです。

page.$(selector)

The method runs document.querySelector within the page. If no element matches the selector, the return value resolves to null.

// id属性を使って指定
const search = await page.$('#search');
// class属性を使って指定
const button = await page.$('.button');
// name属性を使って指定
const select = await page.$('[name=language]');

page.$$(selector)

The method runs document.querySelectorAll within the page. If no elements match the selector, the return value resolves to [].

// 要素名を使って指定
const options = await page.$$('option');

page.evaluate

次にpage.evaluateでの要素取得方法について見てみましょう。

pageオブジェクトのevaluateメソッドはNodeではなくブラウザ上で任意のスクリプトを実行し、その結果を取得することができます。

なので厳密にはpage.evaluateだけでは要素の取得はできないのでdocument.querySelectorやdocument.querySelectorAllを組み合わせて要素を取得します。

const button = await page.evaluate(() =>
  document.querySelector('button')
);
// button is an ElementHandle, so you can call methods such as click:
await button.click();

と、ここまでをまとめるとpage.evaluateでJavaScriptを実行することができ要素の取得はdocument.querySelectorやdocument.querySelectorAllで実現できる。一方で$methodsはより簡略したAPIとしてpage.$やpage.$$で要素を取得できることがわかりました。

パフォーマンスの比較

$methods

puppeteerの$methodsはpuppeteerがwebsocketsを介してブラウザにコマンドを飛ばしています。(これはpuppeteer.connentを詳しく見ると良いと思います。)

page.evaluate

一方でpage.evaluateを使用しdocument.queryselectorで要素を取得する方法は1個のコマンドでブラウザに指示を出していることがわかります。

実際に速度計測をしたわけではありませんが要素取得のためにwebsocketを挟んでいる$methodsの方が時間がかかってしまうことが推測されます。

と、言いつつも「両者のパフォーマンスに関して大きな差異はない」とのコメントも見受けられているのパフォーマンスに関してはどちらを使っても大差ないというのが現時点の個人的見解です。

結局、要素を取得するなら$methodsとpage.evaluateどっちを使うのが良いのか?

例えば要素取得後にクリックする実装例として$methodsを使ったほうがdocument.querySelector(”...”).click()よりもヒューマンライクになります。$methodsを使った方が可読性が上がりそうです。

await page.$('[data-jp="login-button"]').click();
let itemSelector = '[data-jp="login-button"]';
await page.evaluate((selector) => {
    document.querySelector(selector).click();
  }, itemSelector);

一方でpage.evaluateを使った要素取得では臨機応変に対応することができるのでエラーが起きたときのハンドリングを追加できたりと使い勝手がよいのでケースバイケースのような気がします。

なので基本的には$methodsを使う。複雑なロジックが必要になりそうな場合はpage.evaluateを使うのが良さそうだと思いました。

個人的には単純な要素取得には$methodsを使ってライブラリ(具体的にはvuejs-datepickerやvue-multiselect)に対する要素取得にはpage.evaluateを使ってハンドリングしています。

補足

今回はあくまで要素取得という観点で$methodsとpage.evaluateについて比較しましたが、これらを複合させたpage.$evalpage.$$evalについて簡単に紹介します。また機会があれば深ぼって調査してみたいです。

page.$eval() method

// ex)1
const searchValue = await page.$eval('#search', (el) => el.value);
// ex)2
const preloadHref = await page.$eval('link[rel=preload]', (el) => el.href);
// ex)3
const html = await page.$eval('.main-container', (e) => e.outerHTML);

page.$$eval() method

// ex)1
const divCount = await page.$$eval('div', (divs) => divs.length);
// ex)2
const options = await page.$$eval('div > span.options', (options) =>
  options.map((option) => option.textContent)
);

最後に

調査を通して$methodsとpage.evaluateの違いについて知ることができました。ある記事では$methodsを使って要素取得をしていたり、別の記事でpage.evaluateを使っていたりして混乱したので、この記事で情報の整理ができましたら幸いです。

参考記事

https://pptr.dev/next/api/puppeteer.page

https://solutionware.jp/blog/2019/01/01/puppeteerによるwebクローリング-基礎編/

https://gihyo.jp/article/2022/09/rapid-learning-puppeteer-02

Discussion