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でできるので安定しそう。めでたしめでたし。