kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Playwright for Python: ブラウザ操作を自動化しよう!

ブラウザ操作を自動化して,E2E (End to End) テストやスクレイピングをするときに使えるライブラリ Playwright を試す.Playwright の特徴としてまず「クロスブラウザ」があり,Chromium / Firefox / WebKit をサポートしている.また「複数言語」という特徴もあり,Node.js / Python / Java / .NET をサポートしている.今回は Playwright for Python を前提にする.

playwright.dev

Playwright の前に

過去の経験を思い出すと,Capybara + PhantomJS で E2E テストを実装していた(2015-2016年頃でなつかしい!).また最近だと Puppeteer や Amazon CloudWatch Synthetics を使う機会も増えている.しかし,個人的に Node.js にあまり慣れてなく,他の選択肢を探していたところ,pyppeteer(Python に移植された Puppeteer)経由で Playwright for Python というライブラリに出会った.

Playwright に入門する

まず,雰囲気を掴むために Getting started のコードを参考にしながらザッと実装してみた.

playwright.dev

今回は Chromium を使う.page.goto() で Playwright のサイトにアクセスして,page.title() で title tag の値(タイトル)を取得している.そして page.locator() を使ってセレクタを記述できるので,page.locator('.hero__title') で画面上部の「ヒーロー」を特定したり,page.locator('.lightToggleIcon_pyhR') で画面右上の light / dark モードを切り替えるボタンを特定したりしている..text_content() を使えばテキストを取得できるし,.click() を使えば要素をクリックできる.

from playwright.sync_api import sync_playwright
from rich import print

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://playwright.dev/')

    # Fast and reliable end-to-end testing for modern web apps | Playwright
    print(page.title())
    # Playwright enables reliable end-to-end testing for modern web apps.
    print(page.locator('.hero__title').text_content())

    page.screenshot(path='images/playwright-light.png')
    page.locator('.lightToggleIcon_pyhR').click()
    page.screenshot(path='images/playwright-dark.png')

    browser.close()

そして,page.screenshot() で取得したスクリーンショットを以下に貼っておく.Playwright には他にも多くの機能があり,ドキュメントも充実している.便利!

pytest 連携

Playwright for Python は pytest と連携して,簡単に E2E テストを実装できる.以下にはドキュメントを参考に抜粋したサンプルコードを載せているけど,Playwright のサイトのタイトルに Playwright が含まれるかどうかをテストしている.

import re

from playwright.sync_api import Page, expect

def test_playwright(page: Page):
    page.goto('https://playwright.dev/')
    expect(page).to_have_title(re.compile('Playwright'))

pytest を実行するとちゃんと 1 passed と表示される!

$ pytest
test_playwright.py
.                                                                                           [100%]

================================================= 1 passed in 2.33s ==================================================

playwright.dev

Codegen(コード生成)

Playwright の便利な機能の一つに「Codegen(コード生成)」がある.playwright codegen コマンドを実行すると Playwright Inspector というデバッグにも使える機能が起動して,ブラウザを操作しながら Playwright for Python のコードを自動生成できる.ブラウザを操作するコードも pytest コードもサポートしている.コード自体は完璧ではなく,手直しが必要になることもある.

$ playwright codegen https://playwright.dev -o codegen.py

playwright.dev

Interactive mode (REPL)

紹介した Codegen も便利だけど,もっと細かく試行錯誤するときには Python の REPL も使える.特に .locator を使ってセレクタを記述するときに試行錯誤する必要があって,何度も REPL にはお世話になった.

>>> from playwright.sync_api import sync_playwright
>>> playwright = sync_playwright().start()
>>> browser = playwright.chromium.launch(headless=False)
>>> page = browser.new_page()
>>> page.goto('https://kakakakakku.hatenablog.com/')
<Response url='https://kakakakakku.hatenablog.com/' request=<Request url='https://kakakakakku.hatenablog.com/' method='GET'>>
>>> browser.close()
>>> playwright.stop()

kakakakakku blog で複数要素を取得する

他にもコードを試すために,kakakakakku blog の「人気記事」を取得するコードを書いてみた.「人気記事」は ul tag の中に 10 個の li tag が並んでいるため,複数要素を取得するコードを書く必要がある.Locators のドキュメントには「セレクタを複雑に書くのはアンチパターン」と書いてあって,微妙かもしれないけど,for 文と .nth() を組み合わせて記事タイトルを取得してみた.

from playwright.sync_api import sync_playwright

with sync_playwright() as p:
    browser = p.chromium.launch()
    page = browser.new_page()
    page.goto('https://kakakakakku.hatenablog.com/')

    entries = page.locator('ul.entries-access-ranking li')

    for i in range(entries.count()):
        print(entries.nth(i).locator('div > .urllist-title').text_content())

    browser.close()

playwright.dev

まとめ

今回はここまで!Playwright for Python に入門した.実は個人的に実装しようと思っているアイデアがあって,さっそく Playwright を使って実装に着手している.Playwright では,要素が存在しないときに自動的に待ってくれる「Auto-waiting 機能」があるのも便利だった.まだまだ入門したばかりだし,ドキュメントを参考にして実装したコードも改善できそう.Playwright には多くの機能があるので,引き続き試していくぞー!

playwright.dev