BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Web開発を補助する目的でPuppeteerを使う

f:id:ao_kiken:20201222131636p:plain

この記事はBASE Advent Calendar 2020の22日目の記事です

devblog.thebase.in

どうもこんにちは、Web Frontend Groupの青木です

今回は、個人的にWeb開発を補助する目的でPuppeteerを使っていることがあるので、その話をします

前半では、普段どう使っているのか

後半では、ブラウザ操作を記録してコード生成してくれるRecoderについて紹介します

そもそも、Puppeteerって?

Puppeteer is a Node library which provides a high-level API to control Chrome or Chromium over the DevTools Protocol. Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium.

はい、Chrome、またはChromiumを、DevTools Protocolを使って制御できるNodeライブラリです(※1)

どう使っているのか

主に、開発中や問い合わせの動作確認の際に、手動で繰り返し行うことに対して使っています

たとえば

  • 開発環境で商品をn件購入する
  • 開発環境で商品をn件登録する(※2)
  • 開発環境でショップを新規に開設する
  • リグレッションが発生していないか変更に伴い繰り返し動作確認する

などですね

上記操作を必要とする要件は内容によってさまざまですが、たとえば注文管理の開発をしていた際に、ページャーの動作確認がしたくて商品をn件購入する、みたいな使い方をしていました

Chrome CanaryのRecorderを使ってコードを生成してみる

普段はChrome DevToolsから操作する要素のselectorをcopyしてコードを書いて...としていたのですが、Chrome CanaryにPuppeteerで操作するコードを生成するRecoderがついて話題に(※3, ※4)なったりとPuppeteer使う敷居が下がってくれそうだったので、そちらの紹介も兼ねて試します

Recoderを有効化し、コード生成

大まかには次の手順でコード生成できます

  1. Chrome CanaryのDevTools > Setting Recorder > ExperimentsからReoderを有効化
  2. DevToolsを再読み込み
  3. SourcesのタブからRecordingsを選択し、Add Recordingを実行
  4. Recordを実行し、画面操作
  5. 画面操作を終えたらRecordを終了し、生成されたコードを取得

1

画面操作で生成した流れは、商品をカートに入れて、カートから削除したものになります

実行環境を用意し、ひとまず実行、修正

今回向けに実行環境(※5)を用意したので、そちらに生成されたコードの主要部分を貼り付けひとまず実行してみます

import { launch } from 'puppeteer'
import * as expect from 'expect'

;(async () => {
    const browser = await launch({
        headless: false,
        args: ['--window-size=2000,1000'],
        devtools: true,
    })
    const page = await browser.newPage()

    await page.setViewport({ width: 1440, height: 900 })
    await page.goto(URL);
    await page.click("aria/マルチミニポシェットバッグ ¥ 4,800 20%OFF");
    await page.submit("div#mainitmAyLjI2NT > div.item-detail_container_3758d544 > div.item-detail_details_3758d544 > div.item-detail_itemOrder_3758d544.js-itemOrder.cot-itemOrder > form");
    await page.click("aria/削除");
    await page.click("div#orderItems > div.innerContent > div");
    await browser.close();
})()

さっそく、実行以前にTypeScriptで次の指摘が入ったのでその部分を修正

Property 'submit' does not exist on type 'Page'.
-   await page.submit(selector);
+   await page.click(selector);

次に実行すると、一番最初のclickで失敗するのでDevToolsからselectorのコピーをして修正

-   await page.click("aria/マルチミニポシェットバッグ ¥ 4,800 20%OFF");
+   await page.click("#itemList > li:nth-child(1) > a > div > div > div > div.items-grid_infoContainer_c3a2778a");

次に実行すると、Error: No node found for selectorとなったので要素が出るまで待つ処理を追加

+   await page.waitForSelector(selector);
    await page.click(selector);

上記のような修正をして、ひととおり動作を再現

2

生成したコード修正版

import { launch } from 'puppeteer'
import * as expect from 'expect'

;(async () => {
    const browser = await launch({
        // headless: false,
        args: ['--window-size=2000,1000'],
        // devtools: true,
    })
    const page = await browser.newPage()

    await page.setViewport({ width: 1440, height: 900 })
    await page.goto(URL)
    await page.click('#itemList > li:nth-child(1) > a > div > div > div > div.items-grid_infoContainer_c3a2778a')
    await page.waitForSelector('div#mainitmAyLjI2NT > div.item-detail_container_3758d544 > div.item-detail_details_3758d544 > div.item-detail_itemOrder_3758d544.js-itemOrder.cot-itemOrder > form')
    await page.click('div#mainitmAyLjI2NT > div.item-detail_container_3758d544 > div.item-detail_details_3758d544 > div.item-detail_itemOrder_3758d544.js-itemOrder.cot-itemOrder > form')
    await page.waitForSelector('aria/削除')
    await page.click('aria/削除')
    await page.waitForSelector('div#orderItems > div.innerContent > div')
    const innerText = await page.evaluate(() => (document.querySelector('div#orderItems > div.innerContent > div') as HTMLDivElement).innerText)
    expect(innerText).toBe('ショッピングカートに商品は入っていません。')
    await browser.close()
})()

expectを追記、headlessで動くようlaunchの引数オプション一部コメントアウト

気に入っているところ

  • 繰り返し画面操作をする手間を少し楽できる
  • 画面操作で作りたいデータを作るハードルを下げてくれる
  • Recorderで生成したコード、下書き程度ではあるが結構嬉しい

課題と感じているところ

  • Recorderで生成したコードは手入れする前提
  • コードの寿命が短い
    • UIの変更やデプロイによって、要素の特定に使っているclass名のhash部分が変わったりするため

最後に

最近のrecorderを使ってみた結果、興味を持ってもらえそうだと記事にしてみました

課題となる性質を理解した上で、少しでもWebの画面操作の自動化に対する敷居が下がったら嬉しいなと思うところです

明日は、BASE BANK株式会社の清水さんです! お楽しみに!

参考