最近Playwrightを試していたので、使い方を忘れてもまた思い出せるよう、Playwrightの使い方をメモしておこうと思います。
インストール
npm install --save playwright
- Playwrightをインストールすると、同時にブラウザChromium・Firefox・Webkitもインストールされる
- デフォルトではPlaywrightから呼び出されるブラウザは、その時にインストールされたブラウザが使われる
- ブラウザをインストールせずPlaywrightのみをインストールし、ブラウザは別途インストールしたものを使用することも可能
- しかし、Playwrightのバージョンとブラウザのバージョンは密接に関係があるため、Playwrightとブラウザは同時にインストールして、インストールしたブラウザを使うようにした方がよい
構成
Playwrightは下記クラスで構成される。
Browser
> Context
> Page
> Locator
Brower
- いわゆる Chromium・ChromeやFirefox、Edgeなどといったブラウザにあたるもの
- 設定可能項目例
- ヘッドレスモード
- ブラウザ起動オプション
- ゆっくり操作(slowMo)
Context
- 実行環境にあたるもの
- ブラウザで言うところの各ウィンドウ(タブではない)
- 1つのブラウザウィンドウが1つの
Context
に紐づく - 設定可能項目例
- ユーザーエージェント
- ロケール
- タイムゾーン
- ウィンドウサイズ
Page
- いわゆるブラウザのタブ
- JavaScriptのポップアップも1つの
Page
になる
Locator
html
内の各dom
要素
Context.close() Browser.close()
終わったらContext
、Browser
の順に.close()
で閉じる必要がある。
サンプル
例)Googleで「test」と検索し、結果ページのソースを表示する。
import { chromium } from 'playwright'; (async () => { try { const browser = await chromium.launch({ headless: false, }); const context = await browser.newContext(); const page = await context.newPage(); await page.goto('https://www.google.com/'); await page.locator('input[name="q"]').type("test\n"); await page.locator('#pnnext').click(); await page.waitForURL(/search\?q/); console.log(await page.content()); await context.close(); await browser.close(); } catch (err) { console.error(err); } })();
Locator
ブラウザ操作の流れは、対象となるdom
を取得し、そのdom
に対して操作の繰り返しになる。
Playwrightでは、dom
をLocator
で表現し、dom
の属性取得や操作は、全てLocator
を介して行う。
自動待機
dom
取得時にそのdom
がまだ生成されれていないことがある。Locator
を使ってdom
の属性取得や操作をしようとすると、Locator
はそのdom
が生成されるまで自動で待って、生成された後に実行してくれる。- 設定時間まで待って
dom
が見つからない場合はエラーになる。 - 設定時間はデフォルトで30sec
下記例は、Locator
内部でinput[type="button"]
が見つかるまで自動で待って、見つかったらクリックしてくれている。なので、見つかるまでwaitを入れる必要がない。
await page.locator('input[type="button"]').click();
Locator
で要素の指定方法は複数ある。
指定方法を以下に記載。
page.getBy...()
page
にpage.getBy...()
というLocator
を取得する関数がいくつかある- あまり小回りが効かないので、積極的には使わない
CSSセレクター・XPathセレクター
page.locator(<selector>)
で、CSSセレクター・XPathセレクターが使えるcss=
と書くとCSSセレクター、xpath=
と書くとXPathセレクター。(いずれも省略可能)
await page.locator('css=input.test1').click(); await page.locator('input.test1').click(); await page.locator('xpath=//input[@class="test1"]').click(); await page.locator('//input[@class="test1"]').click();
Playwright独自のセレクター
CSSセレクターにはない、Plawright独自のセレクターがあり、CSSセレクターと組み合わせてより複雑な選択ができる。
特定文字列を含む
locator.filter({hasText: <RegExp>})
locator
の中から特定の文字列を含むものを絞り込む- 文字列指定には正規表現が使える
- 文字列検索対象は子ノード以下に及ぶ
html
<div id="small">abc</div> <div id="large">ABC</div>
code
// large console.log( await page.locator('div').filter({ hasText: /ABC/ }).getAttribute('id') );
文字列を子ノードまで見るので、下記の場合はidが「test1」「test2」「large」が該当する。
html
<div id="test1"> <div id="test2"> <div id="small">abc</div> <div id="large">ABC</div> </div> </div>
code
await page.locator('div').filter({hasText: /test/).click();
親要素をたどる
locator.locator('..')
locator
の1つ上のdom
を指す- チェーンして上にたどっていくことができる
html
<div id="a"> <div id="b"> <div id="c">abc</div> </div> </div>
code
// c console.log( await page.locator('#c').getAttribute('id') ); // b console.log( await page.locator('#c').locator('..').getAttribute('id') ); // a console.log( await page.locator('#c').locator('..').locator('..').getAttribute('id') );
複数要素
- セレクターが複数要素にマッチする場合がある
locator.all()
でマッチするlocator
の配列を取得できる
for(const l of await page.locator('input').all()){ console.log(await l.getAttribute('id')); }
Locator(操作)
locator.click()
- クリック
locator.fill(<string>)
input
のvalue
を指定した文字列に設定する
locator.type(<string>)
- 文字列入力
- キーボード入力をエミュレート
locator.clear()
input
のvalue
をクリアにする
locator.press(<key>)
- 要素にフォーカスを移して、指定したキーを押して離す
- キー定義一覧
locator.forcus()
- フォーカスを移す
locator.scrollIntoViewIfNeeded()
- 要素が見える位置までスクロールしてくれる
Locator(属性)
locator.getAttribute(<attribute_name>)
- 属性値取得
- 属性名例
- id
- name
- value
Locator(その他)
locator.isChecked()
- チェックボックス・ラジオボタンのチェック状態を判別
locator.setChecked(true|false)
- チェックボックス・ラジオボタンのチェック状態を設定
locator.setInputFiles([<file>,...])
<input type="file" />
のファイルを設定- 複数ファイル選択の場合、配列で複数ファイルを渡す
locator.selectOption([<val>,...])
select
のoption
を選択value
もしくはlabel
を指定- 複数
option
選択の場合、配列で複数の値を渡す
locator.waitFor()
- その要素が現れるまで待つ
- 属性取得や操作は行わないが、表示まで待ちたい場合に使う
- ページのロード完了待ちなどに使う
selectのoptionの選択状態を取得
Playwrightの機能だけではできない。JavaScriptを使って取得する。
select(単一選択)
select.selectedIndex
を使って取得
html
<select> <option value="v1">v1</option> <option value="v2" selected>v2</option> <option value="v3">v3</option> </select>
code
const selectedOption = await page.locator('select') .evaluate((node: HTMLSelectElement) => { return node.options[node.selectedIndex].value; }); // v2 console.log(selectedOption);
select(複数選択)
option.selected
を使って取得
html
<select multiple> <option value="v1" selected>v1</option> <option value="v2">v2</option> <option value="v3" selected>v3</option> </select>
code
const selectedOptions = await page.locator('select#test7') .evaluate((node: HTMLSelectElement) => { const selectedValues: Array<string> = []; for (const option of node.options) { if (option.selected) { selectedValues.push(option.value); } } return selectedValues; }); // [ 'v1', 'v3' ] console.log(selectedOptions);
ナビゲーション
- デフォルトは
load
イベントが起こるまで待つ - 引数で
networkidle
になるまで待つように変更できる
遷移
page.goto(<URL>)
- 自動でロード完了まで待ってくれる
page.reload()
- 自動でロード完了まで待ってくれる
page.waitForTimeout()
- 指定時間(ms)待つ
ページロード完了を待つ
page.waitForLoadState()
- 今のページのロード完了まで待つ
page.waitForURL(<url|glob_string|RegExp>)
- 指定したURLでのロード完了を待つ
- URL・glob形式・正規表現が使える
page.waitForSelector(<selector>)
- 指定の要素が出るまで待つ
- その要素を使うのであれば、
locator
を直接使えば自動待機してくれるのであえて記述する必要はない。
locator.waitFor()
- 指定の要素が出るまで待つ
page.waitForSelector(<selector>)
とやっていることは同じ- その要素を使うのであれば、
locator
を直接使えば自動待機してくれるのであえて記述する必要はない。
例)リンクをクリックして新しいURLに遷移し、その遷移先でボタンをクリックする
ボタンクリックのlocator
が、ボタンが表示されるまで自動待機するので、ページロード完了を調べる必要がない。
await page.locator('a').click(); await page.locator('button').click();
例)リンクをクリックして新しいURLに遷移し、その遷移先のページソースを表示する
ページロード完了まで待つ必要がある。待つ方法は下記のいずれも可。
page.waitForLoadState()
page.waitForURL(<url>)
page.waitForSelector(<selector>)
page.locator(<selector>).waitFor()
await page.locator('a').click(); // wait load finish await page.waitForLoadState(); console.log(page.content());
属性
page.title()
page.url()
page.content()
- ページのhtml
フレーム
iframe
も他のタグ同様セレクターで取得できるifreme
を取得するにはlocator
ではなく、page.frameLocator(<selector>)
を使う- 取得した
iframe
は、page
のようなもので、そこを起点として、locator
を使ってiframe
内の要素にアクセスできる。
例)iframe
内のbutton
をクリックする
const iframe = await page.frameLocator('iframe'); await iframe.locator('button').click();
Tab
- タブと
page
は同じもの - 新しいタブが作成されると、新しい
page
が作成される
新規タブの取得方法
<a target="_blank">
などで新しくタブが生成される時に、新しく生成されたタブのpage
を取得する方法。
context
のpage
イベントハンドラーの戻り値が、新しく生成されたpage
オブジェクトになる- イベントハンドラーが新しくタブを作成するアクションをブロックしないよう、
Promise
で生成しておき、アクション後にawait
で待機する。
const pagePromise = context.waitForEvent('page'); await page.locator('a').click(); const newPage = await pagePromise; await newPage.locator('button').click();
Browser・Context設定
- 動作をゆっくりにする
Browser
slowMo
- ヘッドレスモードにする
Browser
headless
- 地域・言語設定
Context
locale
timezoneId
const browser = await chromium.launch({ headless: false, slowMo: 100, }); const context = await browser.newContext({ locale: 'ja', timezoneId: 'Asia/Tokyo', });
ブラウザの状態保存
cookie・localStorage 保存
cookie・localStorageを保存して、ブラウザの状態保存ができる。
context.storageState({ path: <file_path> })
でcookeiとlocalStorageの情報をファイルに保存するbrowser.newContext({ storageState: <file_path> })
でファイルに保存されたcookieとlocalStorage情報を復元してウィンドウを立ち上げるcontext.close()
の前に保存しないといけない
const context = await browser.newContext({ storageState: './state.json', }); ////////// await context.storageState({ path: './state.json' });
ブラウザのユーザーデータ保存
ブラウザのユーザーデータ保存先を指定して、ブラウザの状態保存ができる。
BrowerType.launchPersistentContext()
でユーザーデータ保存先を指定するBrowerType.launchPersistentContext()
はBrowser
とContext
の生成を同時に行うので、Browser
とContext
のオプションの設定はここで行う
const saveDir = './save/browser' const context = await chromium.launchPersistentContext( saveDir, { headless: false, locale: 'ja', timezoneId: 'Asia/Tokyo', slowMo: 100, } );
スマートフォンエミュレート
playwright.devices
にスマートフォンテンプレートが定義されている- iPhoneの場合は
webkit
を使った方がなお良い TLS certificate
エラーが出る場合はignoreHTTPSErrors
をセット
import { webkit, devices } from 'playwright'; const browser = await webkit.launch(); const context = await browser.newContext({ ...devices['iPhone 12'], ignoreHTTPSErrors: true, });
ファイルダウンロード
仕様・手順
- ファイルはテンポラリに保存される
context
が閉じられるとテンポラリのファイルは削除される- ダウンロードファイルの情報は
Download
オブジェクトに格納される page
のdownload
イベントハンドラーの戻り値がDownload
オブジェクトになる- イベントハンドラーがダウンロードアクションをブロックしないよう、
Promise
で生成しておき、アクション後にawait
で待機する。 - ダウンロードが完了すると、
await download.path()
が完了する - ファイル名は
download.suggestedFilename()
- テンポラリに保存されたファイルを、
download.saveAs(<path>)
で別の場所にコピーして使う
const downloadPromise = page.waitForEvent('download'); await page.locator('button#btn_save').click(); const download = await downloadPromise; await download.path(); const fileName = download.suggestedFilename(); await download.saveAs(`./download/${fileName}`);
ファイルアップロード
フォーム
<input type="file" />
にアップロードするファイルの絶対パスをlocator.setInputFiles([<path>])
で設定する
await page.locator('input[type="file"]').setInputFiles([<path>]); await page.locator('input[type="submit"]').click();
ファイル選択ダイアログ
クリックするとファイル選択ダイアログが出て、ファイルを指定してアップロードするパターン
仕様・手順
- ファイル選択ダイアログにファイルパスを設定してOKボタンを押すのは、
FileChooser
オブジェクトで行う page
のfilechooser
イベントハンドラーの戻り値がFileChooser
オブジェクトになる- イベントハンドラーがダウンロードアクションをブロックしないよう、
Promise
で生成しておき、アクション後にawait
で待機する。 FileChooser.setFiles([<path>])
でファイルパスを指定してOKボタンを押してくれる
const fileChooserPromise = page.waitForEvent('filechooser'); await page.locator('button#btn_save').click(); const fileChooser = await fileChooserPromise; await fileChooser.setFiles([<path>]);
Playwrightとブラウザ間のやり取りの仕組み
PlaywrightからブラウザでJavaScriptを実行させるにあたり、Playwrightとブラウザ間のやり取りをある程度知っておく必要があるので簡単に説明。
Playwrightに限らず、puppeteerやSeleniumも大体同じような仕組みになっている。
ブラウザのオブジェクトをPlaywrightに持ってくることは出来ない
ブラウザの<input>
や<button>
を取得して、属性取得・設定やクリックなどができる。
const input = await page.locator('input'); await input.fill('TEST'); const button = await page.locator('button'); await button.click();
一見、ブラウザのオブジェクトをPlaywrightに持ってきているように見えるが、そんなことは無理で、ブラウザからそのオブジェクトを識別するための参照情報を取得しているに過ぎない。
そして、Playwrightからそのオブジェクトの操作をしたい場合は、Playwrightからブラウザに識別情報と操作内容を伝えて、ブラウザ側で実行してもらい、その結果を受け取っている。
Locator & JSHandle
上記の識別情報をPlaywrightとpuppeteerではJSHandle
で定義している。
更にPlaywrightでは、JSHandle
を使いやすいようラップして拡張したLocator
があり、JSHandle
ではなくLocator
を使う。
PlaywrigthでJSHandleを使う場面
Playwrightからブラウザ上でJavaScriptを実行する際、ブラウザとブラウザ上のオブジェクトのやり取りは、Locator
ではなくJSHandle
を使って行う。
詳しくは下記JavaScriptで説明。
JavaScript
Playwrightからブラウザ上でJavaScriptを実行することができる。
値の送受信
- PlaywrightからJavaScriptに値を渡すことができる
- JavaScriptからPlaywrightに値を返すことができる
送受信データ
ブラウザのオブジェクトを直接受け渡しできないので、dom
はJSHandle
として受け渡しされる。
page.evaluate()
- JavaScriptを実行
return
で戻り値を返す- 戻り値はスカラー・配列・JSONが使える
- 引数をJavaScriptに渡すことができる
- 引数はスカラー・配列・JSON、および
JSHandle
が使える - 引数に渡した
JSHandle
はブラウザ上のJavaScriptオブジェクトに復元される
////////// // return json const res1 = await page.evaluate(() => { const obj = { val1: 123, val2: 'abc' }; return obj; }); // { val1: 123, val2: 'abc' } console.log(res1); ////////// // pass json const res2 = await page.evaluate((arg) => { const obj = { val1: arg.num, val2: arg.str }; return obj; }, { num: 123, str: 'abc' }); // { val1: 123, val2: 'abc' } console.log(res2);
locator.evaluate()
page.evaluate()
とほぼ同じ- 関数の第1引数が、
locator
で選択したdom
になる
// set input value 'test' await page.locator('input').evaluate((dom: HTMLInputElement, arg) => { dom.value = arg }, 'test');
page.evaluateHandle()
- JavaScriptを実行
return
で戻り値を返す- 戻り値は
JSHandle
のみ JSHandle
以外の値は返せない- 引数をJavaScriptに渡すことができる
- 引数はスカラー・配列・JSON、および
JSHandle
が使える - 引数に渡した
JSHandle
はブラウザ上のJavaScriptオブジェクトに復元される
例
ブラウザからinput
を取得して、そのinput
のvalue
に「abc」をセットする。
const res = await page.evaluateHandle(() => { return document.querySelector('input'); }); await page.evaluateHandle((arg) => { arg.dom.value = arg.val; }, { dom: res, val: 'abc' });
document.querySelector('input')
は、ブラウザ上のinput
を指すres
はinput
のJSHandle
res
をevaluateHandle()
に渡すと、JSHandle
からそれが参照するブラウザ上のオブジェクトinput
に復元される- つまり、
arg.dom
はJSHandle
ではなくdocument.querySelector('input')
- つまり、
locator.evaluateHandle()
page.evaluateHandle()
とほぼ同じ- 関数の第1引数が、
locator
で選択したdom
になる
デバッグ
console出力
- ブラウザのconsoleをPlaywrightに出力する
await page.on('console', msg => { console.log(msg.text()); });
一時停止
await page.pause()
を一時停止したい箇所に記入- 一時停止して
Inspector
ウィンドウが立ち上がる - 一時停止中もブラウザの操作は可能
- 一時停止して
要素を選択するセレクターの書き方を調べる
await page.pause()
でInspector
ウィンドウを立ち上げるPic locator
をクリック- プラウザのページからセレクターを調べたい要素をクリック
- その要素を選択するセレクター文が表示される
セレクターがページのどの要素かを調べる
await page.pause()
でInspector
ウィンドウを立ち上げるPic locator
の隣のテキストボックスにセレクター式を入力するlocator('input')
など
- 該当する要素がハイライト表示される
操作からコード自動生成
await page.pause()
でInspector
ウィンドウを立ち上げるRecord
をクリック- (コード化したい操作を行う)
Record
をクリック- 操作内容がコード化されて
Inspector
ウィンドウに表示される
感想など
クローラーは思いついた時にさっと作れると便利なのですが、使い方を調べるのが面倒なのでなかなか作る気になりません。
なので、必要最小限の機能説明をまとめておけば便利かなと思って書き始めたのですが、長くなってしまいました。
JavaScript関連で長々と書いてしまいましたが、JSHandle
を扱うケースはほとんどないので、evaluete()
の説明だけにしておいてもよかったかも。
あと、await page.pause()
がめっちゃ便利。