Seleniumコトハジメ - PubMed検索とUCSC Genome browserへのCustom Trackのアップロードを自動化してみた

はじめに

Seleniumとは、Webアプリケーションの画面操作を自動化するためのツールです。主に、Webアプリケーション開発のUIテスト時に利用するものです。また、キー入力などの操作を自動化できることから、Webスクレイピングのツールとして活用される例もあるようです。

Seleniumは、Apatch 2.0ライセンスで公開されているオープンソースのツールです。
https://www.seleniumhq.org/about/license.jsp

Seleniumの内部で使われているWebDriverは、Webブラウザを外部から遠隔操作するためのインターフェースであり、W3Cによって標準化されています(厳密には、標準化された内容と乖離する部分あり?)。
https://www.w3.org/TR/webdriver/

Selenium2では、Selenium RCと呼ばれる古い実装が残っていましたが、2016年に公開されたSelenium3では、完全にWebDriverの実装に移行しました。古い記事では、Selenium RCでの操作方法が記載されており、これは現在では動作しない方法になるので注意が必要です。

本来はUIテスト用のツールですが、今回は研究室時代にめんどくさいと思っていた作業を自動化してみました。1つはPubMedによるAdvanced Search、もう1つはUCSC Genome BrowserにCustom trackをアップロードする作業です。繰り返し同じことを繰り返す作業に対して、こういった自動化の方法は役に立ちますね。

Selenium実行サンプル

PubMed検索の自動化

selenium-test.gif (753.1 kB)

UCSC Genome BrowserへのCustom Trackのアップロード

selenium-test2.gif (834.3 kB)

実行環境・ツール

Mac OSX Sierra (v10.12.6)

  • Selenium Client for Java (v3.10.0)
  • ChromeDriver (v2.35)
  • GeckoDriver (v0.19.0)
  • SafariDriver + Safari (v11.0.3)

Windows10 (Build 16299)

Seleniumの導入

Seleniumを動かすのに必要なものは以下の通りです。

  • Selenium Client & WebDriver Language Bindings
  • 各種ブラウザのWebDriver

今回はJavaでの例を示したいと思いますが、javaであれば、MavenやGradleを使って導入する方法もあります。ただ、今回はJARファイルをダウンロードして1からプロジェクトを作っていきます。

Selenium Client & WebDriver Language Bindings

Seleniumは、JavaC#RubyPythonJavascript (Node)を公式でサポートしており、それぞれの言語についてSeleniumAPIが使えるライブラリ(Selenium Client)を用意しています。

まず、以下のサイトからJavaSelenium Clientのファイルをダウンロードします。 selenium-java-3.xx.xx.zipというファイルをダウンロードできるはずです。
https://www.seleniumhq.org/download/

image.png (195.3 kB)

各種ブラウザのWebDriver

続いて、各種ブラウザのWebDriverをダウンロードします。私の使っているPCはMac OSXなので、とりあえずFireFoxGoogle ChromeSafariのWebDriverをそれぞれダウンロードすることにします(自身のパソコンにそれぞれのブラウザがすでにインストール済みでであることを前提とします)。

image.png (310.2 kB)

余談ですが、WebDriverはSeleniumを開発しているグループが作成しているわけではなく、Webブラウザの開発元であるGoogleMozillaAppleMicrosoftなどがそれぞれ開発をしています。そのため、基本的に最新バージョンのWebブラウザに対応するWebDriverを、これらのITベンダーが提供してくれる状態にあります。こういった点も、Seleniumを使うメリットですね。

Mozilla GeckoDriver (FireFox) (Selenium3.5以降でのみ動作)

以下からダウンロードします。
https://github.com/mozilla/geckodriver/releases

自身のパソコンのOSに合ったWebDriverをダウンロードしてきます。

image.png (251.7 kB)

以下のコマンドでファイルを解凍します。

$ tar zxvf geckodriver-v0.19.1-macos.tar.gz

geckodriverという名前のWebDriverの実行ファイルが解凍されます。

Google Chrome Driver (Google Chrome)

以下からダウンロードします。
https://sites.google.com/a/chromium.org/chromedriver/

image.png (410.3 kB)

WebDriverをインストールする際は、Google Chromeのバージョンに気をつけてください。最新のものだと、対応しているバージョンはv62-64に限定されます(もちろん、以前のバージョンのWebDriverについてもダウンロード可能です)。自分のパソコンにインストールされているGoogle Chromeのバージョンを確認し、古いバージョンであれば、そのバージョンをサポートしているWebDriverをダウンロードしてください。

image.png (334.8 kB)

自身のパソコンのOSに合ったWebDriverをダウンロードしてきます。 image.png (175.0 kB)

以下のコマンドでファイルを解凍します。

$ unzip chromedriver_mac64.zip

chromedriverという名前のWebDriverの実行ファイルが解凍されます。

SafariDriver (Safari)

Mac OSX内にすでにインストール済みです。
/usr/bin/safaridriverの場所に存在します。

Safariの設定を変更する必要があります。 まず、Safariの環境設定を開きます。 image.png (67.6 kB)

次に、「メニューバーに'開発'メニューを表示」にチェックをつけます。 image.png (362.8 kB)

すると、メニューバーに「開発」が追加されるので、そこから「リモートオートメーションを許可」をクリックします。これで、ブラウザ側の設定は完了です。 image.png (1.1 MB)

リモートオートメーションを許可していない場合、Javaのテストコードを実行すると、以下のようなエラーが出力されます。このエラーが出た場合は、上記の設定ができてないので、設定を変更してください。

org.openqa.selenium.SessionNotCreatedException: Could not create a session: You must enable the 'Allow Remote Automation' option in Safari's Develop menu to control Safari via WebDriver. (WARNING: The server did not provide any stacktrace information)

Microsoft Edge

以下からダウンロードします。
https://developer.microsoft.com/en-us/microsoft-edge/tools/webdriver/

image.png (389.2 kB)

自身のパソコンのOSのビルドに合ったWebDriverをダウンロードしてきます。 image.png (120.6 kB)

Internet Explorer 11

設定が複雑なので、必要でない限り使わないほうが良いと思います。

こちらに詳しい使用方法が記載されているので参照してください。 保護モードの設定が、「インターネット」「イントラネット」「信頼済みサイト」「制限付きサイト」の4つで統一されていることがかなり重要です(この設定が揃っていないとWebDriverが動作しません)。

seleniumでinternet-explorer11を動かす方法

http://bitwave.showcase-tv.com/seleniumでinternet-explorer11を動かす方法/

トラブルシューティング(一部)

画面は拡大せずに、100%でないと動作しません。以下のエラーが出た場合は、拡大率を100%にしましょう。

org.openqa.selenium.SessionNotCreatedException: Unexpected error launching Internet Explorer. Browser zoom level was set to 110%. It should be set to 100%

image.png (26.9 kB)

Seleniumを動かしてみる

以下では、Eclipseがインストール済みであることを前提として説明を行います。 素のEclipseをインストールするのではなく、以下のAll-in-Oneのパッケージ(Pleiades All in One)をインストールすることをお勧めします。
http://mergedoc.osdn.jp/

1. プロジェクトファイルの作成

Seleniumを動かすためのプロジェクトファイルを作成します。 [新規] -> [Javaプロジェクト]をクリックします。 image.png (70.3 kB)

適当なプロジェクト名をつけて、「次へ」をクリックします。 image.png (51.9 kB)

先ほどダウンロードしたSelenium Clientを解凍し、libsフォルダのjarとclient-combined-3.10.0.jarclient-combined-3.10.0-sources.jarをそれぞれプロジェクトファイルに取り込みます。

image.png (86.4 kB)

まず、「ライブラリ」タブを選択し、「外部JARの追加」をクリックします。 image.png (41.3 kB)

上述のSelenium ClientのJARファイル一式を追加します。 最後に、「完了」ボタンをクリックし、プロジェクトを生成します。 image.png (75.1 kB)

すると、参照ライブラリに先ほど取り込んだSelenium ClientのJARファイルが参照されて取り込まれていることが確認できると思います。

image.png (51.5 kB)

次に、WebDriverを保存するためのフォルダを用意します。 [新規] -> [フォルダー]をクリックします。

image.png (117.2 kB)

フォルダー名を「exe」として、「完了」ボタンをクリックします。 image.png (25.0 kB)

作成した「exe」フォルダ内に、先ほどダウンロードしたWebDriverをドラック&ドロップでインポートします。(ここでは、chromedriver.exeをインポートした例を示した。)

image.png (13.3 kB)

Mac OSXの場合、実行権限を付与しておきます。 image.png (58.4 kB)

$ chmod 755 geckodriver

続いて、Seleniumを実行するためのテストクラスを作成していきます。 プロジェクトの「src」フォルダを右クリックし、[新規] -> [Junitテスト・ケース]をクリックします。

image.png (80.9 kB)

適当なパッケージ名とクラス名を記入し、setUp()tearDown()にチェックを付けます。

image.png (47.3 kB)

JUnit4がビルドパスに入っていない場合、追加するかどうか聞かれるので、「OK」をクリックしてビルドパスに加えます。

image.png (20.0 kB)

2. PubMedUCSC Genome Browserを操作するコードを書いてみる

ページオブジェクトパターン

下図にページオブジェクトパターンと呼ばれる、一般的なクラスの設計方法を示しました。この方法を取り入れることで、メニューやボタン、検索入力欄など、Webページへの操作を共通クラスにまとめることでメンテナンス性の高いコードを書くことができます。

image.png (538.4 kB)

ロケータの調べ方

selenium用のコードを書くために、まずロケータ(ボタンやメニューなどを操作するための目印)を調べる必要があります。例えば、FireFoxを使うと、要素(下の例だと"Advanced")を右クリックして「要素を調査」をクリックすることでロケータを調べることができます。

image.png (353.4 kB)

どのタグが使われているかなどの情報を調べることができます(以下の図だと、INPUTタグのID要素が"Submit"であることがわかります。これを目印にしてSeleniumでWebブラウザを操作していきます)。

image.png (682.9 kB)

コーディング

以下のような構成で、プログラムを用意しました。 pageパッケージにページオブジェクトクラスを、testパッケージにJUnitのテストケースクラスを配置するようにしました。

image.png (36.0 kB)

WebDriverの呼び出し

System.setProperty()の2番目の引数は、ダウンロードしたWebDriverを指定してください。OSによって、拡張子が異なるので注意する必要があります。以下でいくつか例を示しました。

Google Chrome (Mac OSX)
System.setProperty("webdriver.chrome.driver", "exe/chromedriver");
WebDriver driver = new ChromeDriver();
Firefox (Mac OSX)
System.setProperty("webdriver.gecko.driver", "exe/geckodriver");
WebDriver driver = new FirefoxDriver();
Safari (Mac OSX)
WebDriver driver = new SafariDriver();
Microsoft Edge (Windows10)
System.setProperty("webdriver.edge.driver", "exe/MicrosoftWebDriver.exe");
WebDriver driver = new EdgeDriver();
Internet Explorer (Windows10)
System.setProperty("webdriver.ie.driver", "exe/IEDriverServer.exe");
WebDriver driver = new InternetExplorerDriver();

ソースコード

ソースコードの内容について言及しません。 主に、「Selenium実践入門」と言う本と下記のWebサイトを参考にしました。

PubMedSearchTest.java
package test;

import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import page.PubMedAdvancedSearchPage;

public class PubMedSearchTest {

    private WebDriver driver;

    @Before
    public void setUp() throws Exception {
        System.setProperty("webdriver.chrome.driver", "exe/chromedriver.exe");
        driver = new ChromeDriver();
        //System.setProperty("webdriver.edge.driver", "exe/MicrosoftWebDriver.exe");
        //driver = new EdgeDriver();
        //System.setProperty("webdriver.ie.driver", "exe/IEDriverServer.exe");
        //driver = new InternetExplorerDriver();

        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
    }

    @After
    public void tearDown() throws Exception {
        //driver.quit();
    }

    @Test
    public void pubmedAdvancedSearchTest() {
        // PubMed Top
        PubMedAdvancedSearchPage pmPage = new PubMedAdvancedSearchPage(driver);

        // Go to Advanced Search page
        pmPage.goToAdvancedSearch();

        // Set keywords
        pmPage.setJournal("Nature communications");
        pmPage.setJournal("Nature biotechnology");
        pmPage.setSearchSelect(1, "OR");
        pmPage.setJournal("Nature methods");
        pmPage.setSearchSelect(2, "OR");
        pmPage.setJournal("Nature genetics");
        pmPage.setSearchSelect(3, "OR");
        pmPage.setJournal("Molecular cell");
        pmPage.setSearchSelect(4, "OR");
        pmPage.setJournal("eLife");
        pmPage.setSearchSelect(5, "OR");
        pmPage.setJournal( "PLoS biology");
        pmPage.setSearchSelect(6, "OR");
        pmPage.setJournal("Genome research");
        pmPage.setSearchSelect(7, "OR");
        pmPage.setJournal("Genes & development");
        pmPage.setSearchSelect(8, "OR");
        pmPage.setJournal("Nature cell biology");
        pmPage.setSearchSelect(9, "OR");

        // Click Search button
        pmPage.clickSearchButton();
    }

}
UCSCGenomeBrowserTest.java
package test;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

import page.UCSCGenomeBrowserUploadPage;

public class UCSCGenomeBrowserTest {

    private WebDriver driver;

    @Before
    public void setUp() throws Exception {
        System.setProperty("webdriver.chrome.driver", "exe/chromedriver.exe");
        driver = new ChromeDriver();
        //System.setProperty("webdriver.edge.driver", "exe/MicrosoftWebDriver.exe");
        //driver = new EdgeDriver();
        //System.setProperty("webdriver.ie.driver", "exe/IEDriverServer.exe");
        //driver = new InternetExplorerDriver();
    }

    @After
    public void tearDown() throws Exception {
    }

    @Test
    public void test() {
        UCSCGenomeBrowserUploadPage gmPage = new UCSCGenomeBrowserUploadPage(driver);
        gmPage.goToCustomTrack();

        gmPage.setGenomeRefSelect("hg19");

        // Upload file
        gmPage.UploadFile("C://Users/imama/Desktop/test1.bed");
        gmPage.submit();

        // Back to custom track page
        gmPage.goBackToCustomTrack();

        // Upload file
        gmPage.UploadFile("C://Users/imama/Desktop/test2.bed");
        gmPage.submit();

        // Back to custom track page
        gmPage.goBackToCustomTrack();

        // Upload file
        gmPage.UploadFile("C://Users/imama/Desktop/test3.bed");
        gmPage.submit();

    }

}
PubMedAdvancedSearchPage.java
package page;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

public class PubMedAdvancedSearchPage {

    /* WebDriverクラスのインスタンス */
    private WebDriver driver;

    // 明示的な待機
    WebDriverWait wait;

    // キーワードカウンタ
    private Integer keywordCounter;

    // コンストラクタ
    public PubMedAdvancedSearchPage(WebDriver driver){
        this.driver = driver;
        this.keywordCounter = 0;
        this.wait = new WebDriverWait(driver, 10);
    }

    // Advanced saerch画面へ遷移
    public void goToAdvancedSearch() {
        driver.get("https://www.ncbi.nlm.nih.gov/pubmed/");
        driver.findElement(By.linkText("Advanced")).click();

        // Goto PubMed Advanced search
        String previousURL = driver.getCurrentUrl();
        System.out.println(previousURL);
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
        ExpectedCondition<Boolean> e = new ExpectedCondition<Boolean>() {
              public Boolean apply(WebDriver d) {
                return (d.getCurrentUrl() != previousURL);
              }
        };
        wait.until(e);
        String currentURL = driver.getCurrentUrl();
        System.out.println(currentURL);

        // ページタイトルが変更されるまで待ち
        wait.until(ExpectedConditions.titleContains("Advanced search"));
    }

    // Set journal
    public void setJournal(String journal) {
        // FieldにJournalを選択
        Select select = new Select(driver.findElement(By.id("ff_" + keywordCounter)));
        select.selectByValue("Journal");

        // Journal名を入力
        WebElement searchBox = driver.findElement(By.id("fv_" + keywordCounter));
        searchBox.sendKeys(journal);

        // カウントアップ
        keywordCounter += 1;
    }

    // Set search select
    public void setSearchSelect(Integer id, String value) {
        Select select = new Select(driver.findElement(By.id("fop_" + id)));
        select.selectByValue(value);
    }

    // Click Search button
    public void clickSearchButton() {
        driver.findElement(By.id("search")).click();
    }
}
UCSCGenomeUploadPage.java
package page;

import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.WebDriverWait;

public class UCSCGenomeBrowserUploadPage {

    /* WebDriverクラスのインスタンス */
    private WebDriver driver;

    // 明示的な待機
    WebDriverWait wait;

    // コンストラクタ
    public UCSCGenomeBrowserUploadPage(WebDriver driver){
        this.driver = driver;
        this.wait = new WebDriverWait(driver, 10);

        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
    }

    // Go to Custom track page
    public void goToCustomTrack() {
        driver.get("https://genome.ucsc.edu/");
        driver.findElement(By.id("myData")).click();

        wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("customTracksMenuLink")));
        driver.findElement(By.id("customTracksMenuLink")).click();
    }

    // Upload file
    public void UploadFile(String filePath) {
        WebElement upload = driver.findElement(By.name("hgt.customFile"));
        upload.sendKeys(filePath);
    }

    // Submit
    public void submit() {
        driver.findElement(By.id("Submit")).click();
    }

    // Return Custom track page
    public void goBackToCustomTrack() {
        WebDriverWait wait2 = new WebDriverWait(driver, 10000);
        wait2.until(ExpectedConditions.visibilityOfElementLocated(By.id("addTracksButton")));
        driver.findElement(By.id("addTracksButton")).click();
    }

    public void setGenomeRefSelect(String genomeRef) {
        Select select = new Select(driver.findElement(By.id("db")));
        select.selectByValue(genomeRef);
    }
}

スクリーンショットの取り方について(トラブルシューティング

余談ですが、Java8の場合、FileUtils(Java7以前)ではなく、FileHandler(Java8以降)なので間違いがないように。 https://github.com/SeleniumHQ/selenium/issues/4919

File tmpFile = ((TakesScreenshot)driver).getScreenshotAs(OutputType.FILE);
try {
    FileHandler.copy(tmpFile, new File("/Users/imamachinaoto/Desktop/test.png"));
} catch (IOException e) {
    // TODO 自動生成された catch ブロック
    e.printStackTrace();
}

参考

[初心者向け] JavaSeleniumを動かす
https://qiita.com/tsukakei/items/41bc7f3827407f8f37e8

selenium get current url after loading a page
https://stackoverflow.com/questions/16242340/selenium-get-current-url-after-loading-a-page

Selenium WebDriverのwaitを活用しよう
http://softwaretest.jp/labo/tech/labo-294/

Best Practices & Tips: Selenium File Upload
https://saucelabs.com/resources/articles/best-practices-tips-selenium-file-upload