ブラウザ操作を自動化して,E2E (End to End) テストやスクレイピングをするときに使えるライブラリ Playwright を試す.Playwright の特徴としてまず「クロスブラウザ」があり,Chromium / Firefox / WebKit をサポートしている.また「複数言語」という特徴もあり,Node.js / Python / Java / .NET をサポートしている.今回は Playwright for Python を前提にする.
Playwright の前に
過去の経験を思い出すと,Capybara + PhantomJS で E2E テストを実装していた(2015-2016年頃でなつかしい!).また最近だと Puppeteer や Amazon CloudWatch Synthetics を使う機会も増えている.しかし,個人的に Node.js にあまり慣れてなく,他の選択肢を探していたところ,pyppeteer(Python に移植された Puppeteer)経由で Playwright for Python というライブラリに出会った.
Playwright に入門する
まず,雰囲気を掴むために Getting started のコードを参考にしながらザッと実装してみた.
今回は 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 ==================================================
Codegen(コード生成)
Playwright の便利な機能の一つに「Codegen(コード生成)」がある.playwright codegen
コマンドを実行すると Playwright Inspector というデバッグにも使える機能が起動して,ブラウザを操作しながら Playwright for Python のコードを自動生成できる.ブラウザを操作するコードも pytest コードもサポートしている.コード自体は完璧ではなく,手直しが必要になることもある.
$ playwright codegen https://playwright.dev -o codegen.py
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 for Python に入門した.実は個人的に実装しようと思っているアイデアがあって,さっそく Playwright を使って実装に着手している.Playwright では,要素が存在しないときに自動的に待ってくれる「Auto-waiting 機能」があるのも便利だった.まだまだ入門したばかりだし,ドキュメントを参考にして実装したコードも改善できそう.Playwright には多くの機能があるので,引き続き試していくぞー!