Playwrightでtableのtdの値を高速で取得する3行のコード

Playwrightでtableの値を取得

Playwrightを使用していると、tableのtd内の値を取得する機会が多いですが、このコードを正しく書けているケースは少ないです。

悪い例だと、tableの各行をループを使わずに取得しているケースがあり、保守性や再利用性の低いコードが多く存在します。

tableのtdの値は3行のコードで取得可能

Playwrightでtableのtdの値の取得に無駄に長いコードを書いている方をよく見かけますが、tableのtdの値は3行のコードで取得可能です。

この記事では、以下の天気サイトの週間天気のtableのtdの値を取得する方法を交えて説明します。

東京(東京)の天気 - Yahoo!天気・災害

まず、以下のコマンドで my-tenki のディレクトリを作成します。

mkdir my-tenki; cd my-tenki;

次にPlaywrightを以下のコマンドでインストールします。

npm init playwright@latest

npx playwright test を実行するとexample.spec.tsに書かれているコードのテストが実行されます。

今回は東京(東京)の天気のWebページの週間天気のtableのtdの値を取得するので、example.spec.tsを以下のように書き換えて、gotoで遷移するようにします。

example.spec.ts
import { test, expect } from '@playwright/test';

test('週間天気テスト', async ({ page }) => {
  await page.goto('https://weather.yahoo.co.jp/weather/jp/13/4410.html');
});

次に週間天気のページの table (.yjw_table)のtrをpage.locatorで指定して、all() で各行のlocatorをtrsに配列で代入します。

example.spec.ts
import { test, expect } from '@playwright/test';

test('週間天気テスト', async ({ page }) => {
  await page.goto('https://weather.yahoo.co.jp/weather/jp/13/4410.html');
  const trs = await page.locator('.yjw_table tr').all();
  console.log(trs);
  // [
  //   locator('.yjw_table tr').first(),
  //   locator('.yjw_table tr').nth(1),
  //   locator('.yjw_table tr').nth(2),
  //   locator('.yjw_table tr').nth(3)
  // ]
});

次にtr内のテキストをallInnerTexts()で取得して、join('').split(/\s+/) で配列化します。

tdタグ内の値の取得なので、page.locatorで「td」を指定されている方が多いですが、trからテキストを取得して配列化したほうが素早く処理できます。

example.spec.ts
import { test, expect } from '@playwright/test';

test('週間天気テスト', async ({ page }) => {
  await page.goto('https://weather.yahoo.co.jp/weather/jp/13/4410.html');
  const trs = await page.locator('.yjw_table tr').all();
  const getRowData = async (tr) =>
    (await tr.allInnerTexts()).join('').split(/\s+/);
  const rowsData = await Promise.all(trs.map(getRowData));
  console.log(rowsData);
  // [
  //   [
  //     '日付', '2月5日',
  //     '(水)', '2月6日',
  //     '(木)', '2月7日',
  //     '(金)', '2月8日',
  //     '(土)', '2月9日',
  //     '(日)', '2月10日',
  //     '(月)'
  //   ],
  //   [
  //     '天気', '晴れ',
  //     '晴れ', '晴れ',
  //     '晴れ', '晴れ',
  //     '晴れ'
  //   ],
  //   [
  //     '気温(℃)', '10',
  //     '1',         '9',
  //     '-1',        '10',
  //     '0',         '9',
  //     '0',         '9',
  //     '1',         '10',
  //     '1'
  //   ],
  //   [ '降水', '確率(%)', '10', '0', '0', '20', '10', '0' ]
  // ]
});

expectを使用してテストする際は、以下のようなコードになります。

分割代入をすると、変数名を付けられるため、何のデータかわかりやすくなります。

example.spec.ts
import { test, expect } from '@playwright/test';

test('週間天気テスト', async ({ page }) => {
  await page.goto('https://weather.yahoo.co.jp/weather/jp/13/4410.html');
  const trs = await page.locator('.yjw_table tr').all();
  const getRowData = async (tr) => (await tr.allInnerTexts()).join('').split(/\s+/);
  const [dates, weathers, temps, rains] = await Promise.all(trs.map(getRowData));
  const filteredDates = dates.filter((date) => /^\d/.test(date));
  console.log(filteredDates);
  // ['2月5日', '2月6日', '2月7日', '2月8日', '2月9日', '2月10日']
  const isValidDate = (date) => /^\d+月\d+$/.test(date);
  filteredDates.forEach(date => {
    expect(isValidDate(date)).toBe(true);
  })
});

ちなみに @playwright/test ではなく、tenki.mjsを作成して、import { chromium } from 'playwright' でインポートして、node tenki.mjsコマンドでtableのtdの値を取得する場合は以下のようなコードになります。

tenki.mjs
import { chromium } from 'playwright';

async function tenki() {
  const browser = await chromium.launch();
  const context = await browser.newContext();
  const page = await context.newPage();
  await page.goto('https://weather.yahoo.co.jp/weather/jp/13/4410.html');
  const trs = await page.locator('.yjw_table tr').all();
  const getRowData = async (tr) =>
    (await tr.allInnerTexts()).join('').split(/\s+/);
  const [dates, weathers, temps, rains] = await Promise.all(trs.map(getRowData));
  console.log(dates);
  // [
  //   '日付', '2月5日',
  //   '(水)', '2月6日',
  //   '(木)', '2月7日',
  //   '(金)', '2月8日',
  //   '(土)', '2月9日',
  //   '(日)', '2月10日',
  //   '(月)'
  // ]
  await context.close();
  await browser.close();
}

await tenki();