sitemap.xmlからURLを良い感じに抽出してPlaywrightでUIを目視確認する

やりたいこと

フレームワークや言語のバージョンを上げたり、UI周りのリファクタリングをした際に、500やデザイン崩れがないかを確認したい。 理想を言うとxUnitのようなテストコードを書いたりする方が良いのだが、テストコードを書くと工数が非常にかかるし、もともとテストコードがない環境だとしんどい。

また、テストコードでは当然UIの目視確認まではできない。

サクっと主要なページを目視で眺める方法ないかなと考えてたところ、「確認したいページリストを sitemap.xml から生成して良い感じに自動でスクロールすれば良いのでは。..?」ということを思いついたので書いていく。

特に拘泥がなかったので 2021年現在、Puppeteerを使う理由はなくなった。Playwrightを使おう。 を読んでPlaywrightを選んだ。

ソースコード

必要packageは以下の3つ。

$ npm install playwright
$ npm install axios
$ npm install xml2json

コード全体像は以下。

const axios = require("axios");
const parser = require("xml2json");
const { chromium } = require("playwright");

const BASE_URL = "<edit your url>";

const fetchSitemap = async () => {
    const res = await axios.get(`${BASE_URL}/sitemap.xml`);
    const body = JSON.parse(parser.toJson(res.data));
    return body.urlset.url.map((url) => url.loc);
};

const filterSitemapUrls = (urls) => {
    const cb = (path) => {
        let count = 0;

        return (url) => {
            if (!url.match(path)) return true;
            if (count === 3) return false;

            count++;
            return true;
        };
    };

    return urls
        .filter(cb(/posts\//))
        .filter(cb(/articles\//));
};

const evaluateScript = async () => {
    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    for (let index = 0; index < document.body.scrollHeight / 10; index += 100) {
        window.scrollTo(0, index * 10);
        await delay(1000);
    }
};

const runSitemapScenarios = async (page) => {
    const urls = await fetchSitemap();
    for (const url of filterSitemapUrls(urls)) {
        await page.goto(url);
        await page.evaluate(evaluateScript);
    }
};

const main = async () => {
    const browser = await chromium.launch({ headless: false });
    const page = await browser.newPage();

    await runSitemapScenarios(page)

    await browser.close();
};

main();

解説

<base_url>/sitemap.xmlaxios で取得し、 xml2json でxmlをjsonに変換後、playwrightで下までスクロールする。 ただ、純粋に sitemap.xml をすべて眺めるのは厳しい。 /articles/<id> のような動的に生成されたページをすべて見るのは無駄だ。

特定のパターンのURLは先頭3つのみにするなど丸める必要があったので以下のようなコードを書いた。

const filterSitemapUrls = (urls) => {
    const cb = (path) => {
        let count = 0;

        return (url) => {
            if (!url.match(path)) return true;
            if (count === 3) return false;

            count++;
            return true;
        };
    };

    return urls
        .filter(cb(/posts\//))
        .filter(cb(/articles\//));
};

また、ページを開いてから一番下まで目視確認できるようにゆっくりスクロールするようにした。

const evaluateScript = async () => {
    const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
    for (let index = 0; index < document.body.scrollHeight / 10; index += 100) {
        window.scrollTo(0, index * 10);
        await delay(1000);
    }
};

const runSitemapScenarios = async (page) => {
    const urls = await fetchSitemap();
    for (const url of filterSitemapUrls(urls)) {
        await page.goto(url);
        await page.evaluate(evaluateScript);
    }
};