Selenium WebDriverでWebページを印刷する方法

C#で書きますが、適宜自分の言語で読み替えてください。

やりたいこと

Webページ操作のエビデンス、つまりスクショがいる。だるいのでSeleniumで自動化したいが、pngではなくxpsが必要なので、Selenium スクショ機能が使えない。

テスト準備

C#ならNuGetでSelenium.WebDriverSelenium.Supportを入れ、Downloads - ChromeDriver - WebDriver for Chromeからchromedriver.exeをダウンロード、プロジェクトに追加し、プロパティでビルドするときに出力フォルダにコピーするようにする。

以下、テストするまでのコード。PrintPage()で印刷をかけることとする。

[TestClass]
public class PrintTest {
    [TestMethod]
    public void TestSearch() {
        var chromeOptions = new ChromeOptions();
        var chromeDriverService = ChromeDriverService.CreateDefaultService();
        IWebDriver driver
            = new ChromeDriver(chromeDriverService, chromeOptions);

        driver.Navigate().GoToUrl("http://www.google.com");

        IWebElement element = driver.FindElement(By.Name("q"));
        element.SendKeys("Hello selenium WebDriver");

        element.Submit();

        PrintPage(driver);
    }
}

SendKeys

ctrl + Pを押して……ってやる方法。あまりやりたくない。印刷ドライバ選択も面倒そう。

private static void PrintPage(IWebDriver driver) {
    var keys = new List<string> {
    "^(p)",  "{Tab}", "{Tab}", "{Enter}"};  // 実際はドライバ選択操作も入れる。

    keys.ForEach(s => {
        SendKeys.SendWait(s);
        Thread.Sleep(500); // これ入れないと操作がPCの処理を追い越してしまう。
    });
}

XPath(できない)

XPathを使う方法。実はchromeの印刷画面はDriverのWindowHandlesの末尾に入るらしい。このHandleを取得して操作する。XPathは要素を右クリックし、検証、ソースの選択行を右クリック、Copy, Copy XPathクリップボードにとれる。

private void PrintPage(IWebDriver driver, int loopSpan = 500) {
    var handles = driver.WindowHandles;

   // stringでwindowの識別子みたいなのが入る
    var oldLastTab = driver.WindowHandles.Last();

    var executor = (IJavaScriptExecutor)driver;
    // 印刷windowを開く
    executor.ExecuteScript("setTimeout(function(){window.print();}, 0);");

    // 新しいhandleを取得するだけ
    while(true) {
        Thread.Sleep(loopSpan);
        handles = driver.WindowHandles;
        var lastTab = driver.WindowHandles.Last();
        if (lastTab != oldLastTab) {
            driver.SwitchTo().Window(lastTab);
            break;
        }
    }
    
    // 実際はここでエラーが吐かれる
    driver.FindElement(By.XPath("//*[@id=\"button-strip\"]/paper-button[1]"));
    
    driver.SwitchTo().Window(oldLastTab);
}

上記で実行すると、「XPathがみつからない」というエラーが出て実行されない。なぜかというと、chromeの印刷画面ではShadow DOMなるものが使われており、配下の要素はXPathで取得できないらしい。

shadow DOM の使い方 - Web Components | MDN

実際ソースコードを見ると、確かにそれっぽいところが見つかる。

f:id:usagisagi:20190217110015p:plain

JS Pathをつかう

ではどうするかというと、JSPathなるものを使ってJava Script上で要素を選択しJava Script上でクリックする。chrome上ではCopy XPathのかわりにCopy JS Pathを選択することで、Java Scriptでの要素がコピーできる。

shadow DOM の使い方 - Web Components | MDN

// driver.FindElement(By.XPath("//*[@id=\"button-strip\"]/paper-button[1]"));

var jsPath = "document.querySelector('body > print-preview-app')" + 
  ".shadowRoot.querySelector('#sidebar > print-preview-header')" +
  ".shadowRoot.querySelector('#button-strip > paper-button.action-button').click();"
executor.ExecuteScript(jsPath);

これならドライバ選択もJava Scriptでできるので安定しそう。めでたしめでたし。

2/8

  • 五輪のPC回収がもうすぐ終わるらしい。回収されるPCの条件は割とゆるい(HDDいらない)らしいので、いらないディスプレイなりキーボードなりを出してやろうと思う。古い空気清浄機やディスプレイを廃棄したいモノである。扇風機は入らなそうだが、空気清浄機は入りそうだ。とりあえず明日は近所のヤマトにダンボールを買いに行こう。いつもダンボールは捨てるものなのに今回は買うとかこれいかに。

  • とりあえず回収の依頼をして退路を絶った。

  • そろそろセキュスペに本気出さないとやばい。ほとんど何もしてないぞい。でも、C#GUIのアプリケーションを作りたいと企んでいる。ただ、どうしたらよいか、フレームワークがあるのかすらわからない。自分の頭で作ろうとすればなにかしら作れるだろうが、無駄な努力、俗にいう車輪の再発明になりそうで怖い。WPFってのが良いのか?

ここが参考になりそうだ。 stackoverflow.com

  • crankyのRave-Slaveの2017や2018聞いてるけど買って本当に良かった。テンションが上がる。

あしたやること

  • ダンボールを買いに行って家電製品を詰める。
  • 洗濯をする
  • セキュスペの勉強をする。
  • 時間があれば温泉行きたい

2/8

  • 五輪のPC回収がもうすぐ終わるらしい。回収されるPCの条件は割とゆるい(HDDいらない)らしいので、いらないディスプレイなりキーボードなりを出してやろうと思う。古い空気清浄機やディスプレイを廃棄したいモノである。扇風機は入らなそうだが、空気清浄機は入りそうだ。とりあえず明日は近所のヤマトにダンボールを買いに行こう。いつもダンボールは捨てるものなのに今回は買うとかこれいかに。

  • そろそろセキュスペに本気出さないとやばい。ほとんど何もしてないぞい。でも、C#GUIのアプリケーションを作りたいと企んでいる。ただ、どうしたらよいか、フレームワークがあるのかすらわからない。自分の頭で作ろうとすればなにかしら作れるだろうが、無駄な努力、俗にいう車輪の再発明になりそうで怖い。WPFってのが良いのか?

  • crankyのRave-Slaveの2017や2018聞いてるけど買って本当に良かった。テンションが上がる。

2/6 ボツにした

・久しぶりにAccessをやったら頭がこんがらがった。俺はデータベーススペシャリスト保持者なのにテーブル設計できないのか。と思っていたけど、これはデータベーススペシャリスト云々の話じゃなくて、多分自分が計画性がなく、いきなりテーブル作成とかしているせい。

社内ツールでも少し複雑なものは、手癖で打たないでちゃんと要件を定義して、機能と画面を設計して、それからモノを作り始めなければならないな、と思いました。こういう計画性のなさが今までの経歴に如実に現れている。いきあたりばったり。(56分、1回書いたけどまとまらないので全部消した。)

2/5

人生はどうにもならないことが多すぎる。例えば生まれた時点で家庭の経済状況は確定していて、当分の間(下手したら一生の)上流下流が決定する。上流階級に生まれたらどうなるんだと頭を働かせて考えてみたが、私立中学 => 私立高校 => 東大京大慶応大 => 一流企業 という、ステレオタイプのエリートコースのイメージがあり、人生が楽しそう、というか後ろめたい人生を送らないで良さそうで、非常に羨ましかった。

一回仕事でそういう上流階級が集まるパーティーに出席したことがあるのだが、高そうな服を着た秀麗眉目な女子大学生が「私は内科にいきたい」だなんだと言っていた。おそらく頭も良く、都内の医学部なのだろう。彼女らは自分のような奴の存在すら気づかず、「周りの環境に感謝」とかいう台詞を嘯きながら一生を過ごすのであろう。羨ましい。ちなみに自分は「今ここにテロリストがやってきて消毒すれば世界は少し良くなるのかな」とか思いながら、営業の仕事なのに会話もせず、ムシャコラ上流階級の料理を食べていた。料理はバイキングにもかかわらず結構美味しかった。

どうにもならないことといえば、過去のこともどうにもならない。自分の進路の後悔を遡って考えると「なんでこの研究室に修士で入ったんだ」「なんでこの研究室に学士で入ったんだ」「なんで薬剤師になれない課程に行ったんだ」「なんでこの学部に行ってしまったんだ」「なんで高専でなく普通科に入ったんだ」…とひたすら意味のないネガティブな思考を繰り返し、最終的には決まって「産まれてしまったのが間違い、今すぐ死んで転生するしかない」とどうしようもない結論になってしまう。最も、このあたりは自分の将来に対する思考の浅さや、人生に対する不真面目さが影響している気がするのだが。

しかし、「過去のことを後悔する」というのは「当たらない馬券を買ったことを後悔する」のと同じこと。必要以上に悔やんでも仕方がないのである。恐らくなのだけれども、過去のことを後悔しないで生きられる人は、きっと上流階級でも無理で、それこそ当たり馬券の未来を知っている非科学的な人しかできないだろう。ちっぽけな自分にできるのは、実際に痛い目をみた過去を教訓とすること、そしてこれからどうするかであり、過去をひたすら責めることでは無い。

だから、今日ムシャコラ食べたすた丼餃子つきも責める必要はなく、「もうすた丼は食べない、明日からダイエットする」と考えればよいのだ。(25分)

2/4

メガネを調節をしに渋谷のパリミキへ行った。前通っていたパリミキは落ち着いていた普通の雰囲気であったが、この店は全く違う。何が違うというと、内装がナンバープレートとかピアノとかだったり、商品の半分がグラサンだったり、どう見てもサブカル系。童貞のおっさんが来るところでわない。レイバンもあったよ。

あたりはリア充若者が満載なのに、童貞のおっさんが一人座っているのはどう見ても晒し者。挙げ句の果てにそのまま放置プレイされるしで死にたくなった。早く殺してくれ。

グラサンのお兄ちゃんは見た目とは正反対で驚くほどちゃんと対応してくれた。でも、もうあんな恥ずかしいプレイはしたくないので行かない。でもおっさんのメガネとか渋谷のどこで買えばいいんだ。眼鏡市場とかか。執事眼鏡でてーちゃんの眼鏡が出てくれればそれ買うので早く売り出してくれ。

2/3

たまにはBlogでも始めるか(1年ぶりn回目)。

  • ジムでリカンベントバイクしながら過去問道場するの、何ら後ろめたさを感じる事が無い時間を過ごせるのでよい。セキュスペの勉強ほとんどしてないからやばい。2月中に午後問やるくらいの事をしなければならない。

  • ゲームが辛い。BtoWはイーガ団は突破できたものの、ロリと一緒に突入した3連ドラムが難しくて詰んでる。オクトラはトレサ4章に入って、RPGの「ラスボス前になるとやる気が無くなる病」にかかってやる気があまりない。スマブラは灯火でラスボス前にも行ったけど、これまたこの病にかかってしまいやる気がいまいち。この病なんとかしたい。

  • ピカブイはヤマブキで積んでしまっている。

  • 恵方巻安くないし、コスパも悪いので、ネギトロを買ってネギトロ丼にした(満足した)。

  • ロータスラビリンスが延期になってしまった。今度割引になったらふし幻買いたい。

  • Linuxの日本語文字入力がポンコツで辛い。ATOK入れたい。

  • DNNで使う予定だったGPUが仕事してない。ゲームにも使って無いし悲しい。

  • お金使い過ぎなので節約しなければならない。