C#でSeleniumをやめてPlaywrightを使ってみた3

Playwright Inspectorで自動コード生成できるみたいよ

前回、InfoseekのトップページにログインするコードをDevToolsで頑張ってやる方法を試してみたが、もっと簡単な方法がある。

ま、当たり前かもしれないけど、自動コード生成ツールを使う方法ね。


と、いうわけで今回は Playwright Inspector の使い方のはなし。

Playwright Inspector はブラウザの操作を記録して、C#、Python、 node.jsなどのスクリプトを自動生成してくれるツール。単純にそれだけではなく、作ったコードをプログラミングプラットフォームのようにデバッグ実行も可能で、ステップトレースできてしまうという、超便利なツール。

Seleniumとかでもこういうツールあったみたいだけど、自分は全く使ってなくて、もっぱらDevToolsと比較にらめっこしていたという、超原始人でした。


と、いうことで以下の手順で試してみよう。

・テスト用のプロジェクトを用意して、デバッグビルドする。

・pwshのインストール

・Playwright Inspectorの設定

・実行、操作の記録

・デバッグトレースを試す



テスト用プロジェクトの作成

テスト用のプロジェクトを用意して、デバッグビルドする。

とりあえず、ブラウザ開くだけのプロジェクトを用意する。

前回作ったものの最小限で良いけど、せっかく作ったので使えるところは使って。NuGetでMicrosoft.Playwrightをインストールしてブラウザを立ち上げるコードを書く。プロジェクト名は今回は「Playwrightテスト2」で作っている。

枠内をクリックするとソースがクリップボードにコピーされます
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Playwright;

namespace Playwrightテスト2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            ブラウザ.Close += ブラウザ終了;
            Task.Run(() => 自動処理());
        }

        Chromeコントロール ブラウザ = new Chromeコントロール();

        public async Task 自動処理()
        {
            await ブラウザ.ブラウザ起動();
        }

        private void ブラウザ終了(object? sender, EventArgs e)
        {
            Application.Exit();
        }

        private class Chromeコントロール
        {
            public event EventHandler? Close;

            private IPlaywright? playwright;
            private IBrowserContext? ブラウザコンテキスト;
            private IPage? ページ;

            public async Task ブラウザ起動()
            {
                playwright = await Playwright.CreateAsync();

                ブラウザコンテキスト = await playwright.Chromium.LaunchPersistentContextAsync(
                    @"C:\ChromeProfile",
                    new BrowserTypeLaunchPersistentContextOptions
                    {
                        Headless = false,
                        Channel = "chrome"
                    }
                );

                ブラウザコンテキスト.Close += ブラウザコンテキスト_Close;
            }

            private void ブラウザコンテキスト_Close(object? sender, IBrowserContext e)
            {
                Close(this, EventArgs.Empty);
            }
        }
    }
}

これをデバッグ実行してブラウザが立ち上がっているのを確認する。



pwshのインストール

すでにインストールしている人もいるかと思うが、私の環境にはインストールされていない。

Visual Studioのメニューで「ツール」→「コマンドライン」→「開発者コマンドプロンプト」でコマンドプロンプトを立ち上げる。

pwshがインストールされているか確認するため、「pwsh」と打ってみる。

pwshはインストールされている?

はい、コマンド認識しません。

ということで、以下のコマンドでインストールする。

枠内をクリックするとソースがクリップボードにコピーされます
dotnet tool update --global PowerShell

実行するとこうなる。

PowerShellのアップデート

これでpwshのインストールは終わり。


Playwright Inspectorの設定

先ほどのコマンドプロンプトに以下を投入する。

枠内をクリックするとソースがクリップボードにコピーされます
set PWDEBUG=1
dotnet test

そうすると、プロジェクトの処理がされる。

Playwright Inspectorの設定

そして、ページに対し、PauseAsync()を呼び出すようにする。

枠内をクリックするとソースがクリップボードにコピーされます
            public async Task ブラウザ起動()
            {
                playwright = await Playwright.CreateAsync();

                ブラウザコンテキスト = await playwright.Chromium.LaunchPersistentContextAsync(
                    @"C:\ChromeProfile",
                    new BrowserTypeLaunchPersistentContextOptions
                    {
                        Headless = false,
                        Channel = "chrome"
                    }
                );

                ブラウザコンテキスト.Close += ブラウザコンテキスト_Close;

                ページ = ブラウザコンテキスト.Pages[0];
                await ページ.PauseAsync();
            }

こんな感じね。



実行、操作の記録

ではでは、Inspectorを立ち上げてみるよ。前回同様にInfoseekのログインで試してみよう。やり方は簡単。Visual Studioのデバッグ実行をするだけ。

Inspectorを起動

すると、こんな風にブラウザとInspectorが立ち上がる。この状態でインスペクタの「Record」ボタンを押す。

操作記録モード

これで操作記録モードになるので、URLにwww.infoseek.co.jpと入力して、ログインの操作をする。

Inspectorの動作

記録中は上の絵のように、Locator用のセレクタの参考情報が表示される。でもどうせInspectorが勝手に記録しているので、そんなに気にする必要はない。ログインボタンを押して、ユーザーID、パスワード入力、ログインと実行したら操作記録をやめる。

Inspectorの記録終了

「Record」ボタンを再度押すだけ。その下にはC#用のソースが既に表示されている。

ソースをクリップボードにコピー

出来上がっているソースはRecordの隣のCopyボタンでコピーする。一度デバッガを終了させて、ソースを修正にはいる。ここで作られたソースは標準的な変数名になっているので、これを置換する(自分の場合は「page.」→「ページ.」に置換)。で、それを使ってメインのソースに貼り付ける。

枠内をクリックするとソースがクリップボードにコピーされます
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.Playwright;

namespace Playwrightテスト2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();

            ブラウザ.Close += ブラウザ終了;
            Task.Run(() => 自動処理());
        }

        Chromeコントロール ブラウザ = new Chromeコントロール();

        public async Task 自動処理()
        {
            await ブラウザ.ブラウザ起動();
            await ブラウザ.Infoseekログイン();
        }

        private void ブラウザ終了(object? sender, EventArgs e)
        {
            Application.Exit();
        }

        private class Chromeコントロール
        {
            public event EventHandler? Close;

            private IPlaywright? playwright;
            private IBrowserContext? ブラウザコンテキスト;
            private IPage? ページ;

            public async Task ブラウザ起動()
            {
                playwright = await Playwright.CreateAsync();

                ブラウザコンテキスト = await playwright.Chromium.LaunchPersistentContextAsync(
                    @"C:\ChromeProfile",
                    new BrowserTypeLaunchPersistentContextOptions
                    {
                        Headless = false,
                        Channel = "chrome"
                    }
                );

                ブラウザコンテキスト.Close += ブラウザコンテキスト_Close;

                ページ = ブラウザコンテキスト.Pages[0];
                await ページ.PauseAsync();
            }

            public async Task Infoseekログイン()
            {

                // Go to https://www.infoseek.co.jp/
                await ページ.GotoAsync("https://www.infoseek.co.jp/");

                // Click header[role="banner"] >> text=ログイン
                await ページ.RunAndWaitForNavigationAsync(async () =>
                {
                    await ページ.Locator("header[role=\"banner\"] >> text=ログイン").ClickAsync();
                }/*, new PageWaitForNavigationOptions
                {
                    UrlString = "https://login.account.rakuten.com/sso/authorize?client_id=rakuten_infoseek_web&redirect_uri=https://www.infoseek.co.jp/login/process&response_type=code&scope=openid&state=infoseekLoginProcess&ui_locales=ja-JP#/sign_in"
                }*/);
                // Assert.AreEqual("https://login.account.rakuten.com/sso/authorize?client_id=rakuten_infoseek_web&redirect_uri=https://www.infoseek.co.jp/login/process&response_type=code&scope=openid&state=infoseekLoginProcess&ui_locales=ja-JP", ページ.Url);

                // Fill [aria-label="ユーザIDまたはメールアドレス"]
                await ページ.Locator("[aria-label=\"ユーザIDまたはメールアドレス\"]").FillAsync("hoge@hogehoge.com");

                // Click div[role="button"]:has-text("次へ")
                await ページ.RunAndWaitForNavigationAsync(async () =>
                {
                    await ページ.Locator("div[role=\"button\"]:has-text(\"次へ\")").ClickAsync();
                }/*, new PageWaitForNavigationOptions
                {
                    UrlString = "https://login.account.rakuten.com/sso/authorize?client_id=rakuten_infoseek_web&redirect_uri=https://www.infoseek.co.jp/login/process&response_type=code&scope=openid&state=infoseekLoginProcess&ui_locales=ja-JP#/sign_in/password"
                }*/);

                // Fill [aria-label="パスワード"]
                await ページ.Locator("[aria-label=\"パスワード\"]").FillAsync("hogehoge");

                // Click div[role="button"]:has-text("ログイン") >> nth=0
                await ページ.Locator("div[role=\"button\"]:has-text(\"ログイン\")").First.ClickAsync();
            }

            private void ブラウザコンテキスト_Close(object? sender, IBrowserContext e)
            {
                Close(this, EventArgs.Empty);
            }
        }
    }
}

緑字は自動生成したソースの部分。赤字が付け足した部分。コメント周りのインデントは貼り付けたときにおかしくなるので、そこは修正。


デバッグトレースを試す

なんてことはないです。ただVisual Studioのデバッグ実行をするだけ。(※ただし、今回のinfoseekは最初にログアウトしてから始めてます)

Inspectorでデバッグトレース

PauseAsync()しているところから開始できる=ブレークポイントのようなもの。今回はブラウザの起動の所にあったのをそのままにしているので、about:blankで停止する。これをステップトレースボタンを押して進めていくと、ページの処理をしているステップの所で停止してくれる。

トレース中の確認

次のステップがどこに影響するかはブラウザの中に表示される。(赤枠は後から足したものだけど、ログインボタンの上の赤丸はInspectorが描画している)

こんな感じで、操作した内容をトレースできる。そのまま動くならあとは余計なコメントなどを消したりすれば、一通り処理の出来上がり。

ただし、タイムアウトの例外などは発生しやすいので、ちゃんと例外処理は作り込みましょう。


あと、ステップトレース中だったり、特定の場所から動作がおかしくなる場合は、そこからまたRecordすればOK。PauseAsync()の場所を変えて問題箇所までいっきに進んでからRecordでもいいしね。


最後に、PauseAsync()は必ずコメントアウトなり、削除なりしましょう。毎回止まってInspectorが立ち上がってしまいます。


ん~、これは便利だなぁ。今までの苦労は何だったのかというぐらい、らくちんなのでした。

※追記、今更ですが、.NET 5.0はもうサポート外らしいので.NET 6.0を使いましょう。



Inspectorの実際(追記 2022/5/10)

InspectorはLocatorの設定確認に使用するのがいいようだ。

いろいろなページで試してみたが、そんなにうまくはいかなかった。


Inspectorが自動ではじき出すセレクタは、なぜかテキストでの特定を優先したがる。これ、要素の選択を確実におこなうには使いにくかったりする。(ボタン名がステータスによって変わるとか、リストを選択したいときに、nthにしてほしいところをtext="~"といったセレクタで推してくる。)


結局、DevToolsとにらめっこして、Locatorに渡すセレクタを決めたほうが、間違いない。

それでも「>>」だけで要素をどんどん重ねて記述できるので、Seleniumとかよりは断然らくちんだけど。


それと、RunAndWaitForNavigationAsync()にも注意。別タブで開くようなリンクの場合、消さないとタイムアウトする。


あとは鬱陶しい全面広告が入るページへの自動操作はなかなか面倒。こいつは工夫するしかないけどね…。



コメント

このブログの人気の投稿

楽天ラッキーくじ 処理設定ページ

楽天ラッキーくじ スタートページ