最近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設定
- 動作をゆっくりにする
BrowserslowMo
- ヘッドレスモードにする
Browserheadless
- 地域・言語設定
ContextlocaletimezoneId
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のJSHandleresを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()がめっちゃ便利。