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

Playwrightでもう少し試したこと

その3でやめようと思ったが、もう少し書きたいことがあったので、書いておく。

「拡張機能」「要素の存在確認」「JavaScriptの実行」についてのメモメモ。


拡張機能を使いたいのにダメ?

まずは拡張機能について。

SeleniumのときはChromeの拡張機能はそのまま使えた。
特に何も設定しなくても、立ち上げるChromeの--user-data-dirを適切に設定しておけば、そこでインストールした拡張機能はSeleniumで起動させたChromeでも普通に使えた。

しかし、Playwrightはそうはいかない。

拡張機能がインストールできない!!

拡張機能をインストールしようとするとこんな感じでエラーが発生。入れられない。

このメッセージが出る理由とか対処法を検索すると、プロファイル作り直せばできたとかなんとかというページばかりだったが、消してもだめ。たどりついた結果は「chrome://version」にあった。

拡張機能が禁止になってた

立ち上げのオプションに「--disable-extensions」が入っている。そりゃ、だめだわ。これ外せないかとおもったけど、外す方法は分からず。

その代わり、どうやら「--disable-extensions-except」をつかって特定の拡張機能だけは使えるようにすることはできそう。とはいえ、「--disable-extensions-except」を指定するためには先に拡張機能をインストールしていなければならない。


拡張機能を使えるようにする

と、いうことで、まずはコマンドプロンプトを立ち上げて、Chromeを「--user-data-dir」指定して起動する。

枠内をクリックするとソースがクリップボードにコピーされます
"C:\Program Files\Google\Chrome\Application\chrome.exe" --user-data-dir="C:\ChromeProfile"

これで、ブラウザが立ち上がったら入れたい拡張機能をChromeウェブストアからインストールする。

インストールした後、該当の拡張機能の詳細を表示して、そのときに表示されるURLを確認。

拡張機能のIDを調べる

IDがわかるので、「--user-data-dir」下の「Default\extensions」の下の該当IDのフォルダを調べる。

例えば楽天Web検索なら「C:\ChromeProfile\Default\Extensions\iihkglbebihpaflfihhkfmpabjgdpnol」を調べる。

拡張機能のバージョン情報

バージョン番号を示すフォルダがあるのでこれも合わせたフォルダが、指定すべきフォルダとなる。

その結果、ブラウザを立ち上げる時は以下のように指定する。

枠内をクリックするとソースがクリップボードにコピーされます
                ブラウザコンテキスト = await playwright.Chromium.LaunchPersistentContextAsync(
                    @"C:\ChromeProfile",
                    new BrowserTypeLaunchPersistentContextOptions
                    {
                        Headless = false,
                        Channel = "chrome",
                        Args = new[] {
                            "--disable-extensions-except=" +
                            @"C:\ChromeProfile" +
                            @"\Default\Extensions" +
                            @"\iihkglbebihpaflfihhkfmpabjgdpnol" +
                            @"\4.663_0" 
                        }
                    }
                );

ただ、バージョン番号は当然変わっていく可能性があるのでハードコーディングしないようにしたほうがいいわね。

もしかしたら「--load-extension」も指定したほうがいいのかもしれない。


拡張機能が消える?

ここで試しているうちにあることに気づいた。上記でPlaywrightでたち上げると、楽天Web検索が立ち上がるのだか、毎回インストール直後の使い方のタブが起動する。立ち上げ済みのフラグなどが保存されないってこと?

拡張機能の動きがなんか変。

一度たち上げたら出ないはずと思い、確かめるためPlaywrightではなく、コマンドプロンプトから--user-data-dirだけ指定して立ち上げたら???拡張機能出ない。なんで?

デフォルトで拡張機能がロードされなくなった

そのあと、プロファイル一回全部消して全部クリアな状態から始めても同じ。一度Playwrightで起ち上げるたあとに、コマンドプロンプトで起動した場合に拡張機能がクリアされてしまう。なぜだ?--disable-extensionsが悪いのか?と思って起動オプションいろいろ試してみたら、「--disable-extensions-except」を使ってしまうと、その拡張機能がデフォルトでロードされなくなってしまうようだ。その結果?毎回初回拡張設定インストール直後扱いとなって使い方のタブが出てしまうのか。ちょっと使いにくい。


拡張機能を使うためのコード例

ま、しゃ~ない。ということで、こんなコードにしてみました。

枠内をクリックするとソースがクリップボードにコピーされます
                String ユーザフォルダ = @"C:\ChromeProfile";
                String 楽天Web検索拡張機能フォルダ = ユーザフォルダ + @"\Default\Extensions\iihkglbebihpaflfihhkfmpabjgdpnol";
                String 楽天Web検索拡張機能バージョン = "";
                try
                {
                    楽天Web検索拡張機能バージョン = Path.GetFileName(Directory.GetDirectories(楽天Web検索拡張機能フォルダ).First());
                } 
                catch
                {

                }

                if (楽天Web検索拡張機能バージョン != "")
                {
                    ブラウザコンテキスト = await playwright.Chromium.LaunchPersistentContextAsync(
                        ユーザフォルダ,
                        new BrowserTypeLaunchPersistentContextOptions
                        {
                            Headless = false,
                            Channel = "chrome",
                            Args = new[] { "--disable-extensions-except=" + 楽天Web検索拡張機能フォルダ + "\\" + 楽天Web検索拡張機能バージョン }
                        }
                    );
                }
                else
                {
                    ブラウザコンテキスト = await playwright.Chromium.LaunchPersistentContextAsync(
                        ユーザフォルダ,
                        new BrowserTypeLaunchPersistentContextOptions
                        {
                            Headless = false,
                            Channel = "chrome"
                        }
                    );
                }


要素の存在チェックがしたい

次、要素の存在確認について。

前回/前々回ともInfoseekのTOPページでログインする処理を試したが、Cookieが有効であれば、再度ブラウザ起ち上げてTOPページに来た場合はログイン状態が維持される。ログインする必要がないのでログイン処理を飛ばしたくなる。

そもそも、ログインボタンが出ないので、

枠内をクリックするとソースがクリップボードにコピーされます
await ページ.Locator("header[role=\"banner\"] >> text=ログイン").ClickAsync();

などでログインボタンを待つような処理をすると、タイムアウトの例外が発生する。

やりたいのは要素がないことを検知して、処理を飛ばしたい。これ、意外に情報がなくて困った。

ClickAsync()のタイムアウト値を極端に下げて、タイムアウトした場合に処理を飛ばしてもいいのだけどスマートじゃない。InnerHTMLを読んで、自前でIndexOf()で文字列検索してというのもスマートじゃない。

で、これまた探しましたよ。方法。

結局一番わかりやすいのは以下でした。

枠内をクリックするとソースがクリップボードにコピーされます
var ログインボタンチェック = await ページ.Locator("header[role=\"banner\"] >> text=ログイン").CountAsync() > 0;


CountAsync()で0が返ってこない罠

と、おもったんですが、そうでもなかったです。

要素が存在するか否かはCountAsync()で確かにできるんだけど、今回のページはログインボタンのStyle="display: none;"に設定されていて、見えなくなっているだけで要素が存在してた。これだと常にCountAsync()の結果は1以上になってしまう。

この場合は、ログインボタンがVisibleかどうかで判断です。(ページ内にあれば表示範囲外でもVisible扱い。)

枠内をクリックするとソースがクリップボードにコピーされます
var ログインボタンチェック = await ページ.Locator("header[role=\"banner\"] >> text=ログイン").IsVisibleAsync();

IsVisible()はTimeout値を無視して、即時に返ってくる。で、trueになっているのを確認して、ClickAsync()してもいいし、分岐をしてもいい。絶対に要素があるのがわかっているなら、間違いなく使えます。

また、style="display: none;"の要素の場合はIsVisible()もちゃんとfalseになるので、めちゃめちゃ便利です。こういうのSeleniumでは簡単じゃなかったからなぁ。


枠内をクリックするとソースがクリップボードにコピーされます
var ログインボタンチェック = await ページ.Locator("header[role=\"banner\"] >> text=ログイン").IsEnabledAsync();

IsEnabledAsync()は似ていてこれでも行けそうに思えるが、Enable状態になるまで待ってしまうので即判断したい場合はダメ。というか、存在チェックには使いにくい(存在しない場合はタイムアウトだから)。

使い方としてはサーバの応答が遅かったり、処理の終わりで「完了しました」のようなメッセージを待つだけで、アクションはいらない場合はLocator("text=完了しました").IsEnabledAsync()などとするのがいい。

あと、もう一つの方法はCountAsync()に使うLocatorの中で"visible=true"を指定する方法。

枠内をクリックするとソースがクリップボードにコピーされます
var ログインボタンチェック = await ページ.Locator("header[role=\"banner\"] >> text=ログイン >> visible=true").CountAsync() > 0;



要素の存在チェックの方法の結論

Locator().CountAsync()が使えるときは使ってもいいけど、クリックができる場所を探すならLocator().IsVisibleAsync()が一番使える。

絶対見つかるはずの要素を待つならLocator().IsEnabledAsync()で見つかるまで待つ。ただし、この場合はTimeoutの設定を適切に。


JavaScriptを使うときの書き方

最後、JavaScriptの実行について。これは単に、JavaScriptを実行するときはどうするの?っていうだけ。

ページに対し、EvaluateAsync()をつかうだけです。例えば、ページの最下部までスクロールするなら

枠内をクリックするとソースがクリップボードにコピーされます
await ページ.EvaluateAsync("window.scroll(0, document.documentElement.scrollHeight);");

とやればいいし、上で一生懸命要素のチェックをLocatorつかってやりましたが、idとか決まっているなら

枠内をクリックするとソースがクリップボードにコピーされます
var ログインボタン = await ページ.EvaluateAsync<int>("document.getElementsByClassName('button login').length;");

のようにすれば、Javascriptから結果を受け取ることもできる。

さらに、Locatorで取得した要素を引数にJavaScriptを動かすこともできる。

枠内をクリックするとソースがクリップボードにコピーされます
await ページ.Locator("button:has-text(\"ログイン\")").EvaluateAsync("ele => ele.click();");

のようにすれば、Locatorで特定された要素が変数「ele」を経由して渡され、ログインのテキストをもつボタンをJavaScriptでクリックさせられる。

指定した要素を引数に値を受け取るようなものもページと同様にLocator().EvaluateAsync<int>()などで可能。

あと、実際に試していて、わかったのはボタンがIsVisibleAsync() == trueでも、その上に広告(Googleのアンカー広告とか)などがかぶさっていたりすると、Playwrightによるスクロールがうまく行かずに、ボタンクリックが失敗することがある。そういう場合は

枠内をクリックするとソースがクリップボードにコピーされます
await ページ.Locator("text=ログイン").EvaluateAsync(
    "element => window.scroll(0, element.offsetTop - 100);"
);
await ページ.Locator("text=ログイン").ClickAsync();

みたいにするとうまく行くかもしれない。と、いうことです。



Playwrightを使いまくって作ったアプリ

Playwrightを使いまくって作ったアプリは以下

楽天ラッキーくじ 半自動君

このぐらいはSeleniumに比べて結構簡単に作れた。と、いうかもうSeleniumは使う気にならなくなったのでした。

素晴らしい。Playwright!!

コメント

このブログの人気の投稿

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

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