HTMLの要素を調べてPageクラス(Selenide利用)を作成する

# はじめに

[Selenium(Java)でE2Eテスト環境を構築する](https://qiita.com/umasaki/items/7ac1ea1719c1a786d64f)

構築をしたものの、『Pageクラス作るのかよー』とか言われて重い腰を上げてもらえそうなので、できるだけ簡単にPageクラスを作る方法を記録しておきます。
今までの経験上たぶん色々言ってくる:innocent:


# 操作をしたい要素を特定する(CSSセレクタなど)

Windows版で解説。
Chromeのデベロッパーツールを使用します。

1. 調べたい画面を表示
![aWS050241.JPG](https://qiita-image-store.s3.amazonaws.com/0/187061/e4675654-0f0f-74c8-b969-80534c3f00b1.jpeg)

2. F12キーでデベロッパーツールを表示
![aWS050242.JPG](https://qiita-image-store.s3.amazonaws.com/0/187061/93a4c916-272d-3824-d517-393c0c263692.jpeg)

3. 選択ボタンをクリックし、操作したい要素を選択
![aWS050243.JPG](https://qiita-image-store.s3.amazonaws.com/0/187061/c63df728-03c8-4a7f-8b8c-d121c9ad75d4.jpeg)

4. 反転されたHTMLソースを右クリックし、Copy - Copy selector で『#q』のような情報がコピーできる
![aWS050244.JPG](https://qiita-image-store.s3.amazonaws.com/0/187061/dff32e93-c2ce-9679-40b2-d73560242511.jpeg)


# Pageクラスを作成してみる

先ほど調べた要素を反映させると以下のPageクラスを作成することになる

```java:Google.java
package page;

import static com.codeborne.selenide.Selenide.*;

import org.openqa.selenium.WebDriver;

public class Google extends PageBase {

    public static final String _url = "https://www.google.co.jp/webhp";

    public Google(WebDriver webDriver) {
        super(webDriver);
    }

    private String _id_search = "#lst-ib";

    // ****************************************
    // 検索ページ
    // ****************************************

    public Google __検索ワード入力(String s) {
        $(_id_search).setValue(s);
        return this;
    }

    public Google __検索を行う() {
        $(_id_search).submit();
        return this;
    }
}
```

テストは以下のようなコード。

```java:GoogleTest.java
package test;

import org.junit.Test;

import com.htmlhifive.pitalium.core.model.ScreenshotArgument;

import page.Google;

public class GoogleTest extends TestBase {

    @Test
    public void OK() throws Exception {

        driver.get(Google._url);
        Google page = new Google(driver);

        page.__検索ワード入力("test")
            .__検索を行う();

        ScreenshotArgument arg = ScreenshotArgument.builder("result").addNewTarget().build();
        assertionView.verifyView(arg);
    }
}
```


# クリックメニューを扱えるようなPageクラスを作成してみる

検索結果表示後のツールをクリックして操作できるメソッドを追加してみる。
要素の調べ方は先ほどと同じ。

Seleniumは操作が早すぎるようで、要素がないみたいなエラーになることがある。
クリック後に表示される要素を待つようにすると安定する。

```java:Google.java
package page;

import static com.codeborne.selenide.Condition.*;
import static com.codeborne.selenide.Selenide.*;

import java.util.Iterator;

import org.openqa.selenium.NoSuchElementException;
import org.openqa.selenium.WebDriver;

import com.codeborne.selenide.SelenideElement;

public class Google extends PageBase {

    public static final String _url = "https://www.google.co.jp/webhp";

    public Google(WebDriver webDriver) {
        super(webDriver);
    }

    private String _id_search = "#lst-ib";

    // ****************************************
    // 検索ページ
    // ****************************************

    public Google __検索ワード入力(String s) {
        $(_id_search).setValue(s);
        return this;
    }

    public Google __検索を行う() {
        $(_id_search).submit();
        return this;
    }

    // ****************************************
    // 検索結果ページ（以下は本来なら別クラス）
    // 本来なら「__検索を行う()」メソッドで検索結果Pageクラスを返せば良い
    // ****************************************

    public Google __ツールをクリック() {
        $("#hdtb-tls").click();
        return this;
    }

    public Google __ツール$期間をクリック() {
        // ツール選択後のメニューが表示されているか確認
        if (!$("#hdtbMenus").isDisplayed()) {
            __ツールをクリック();
        }
        // 選択後に表示される項目を60秒待つ
        $("#hdtbMenus").waitUntil(appear, 60000);

        $("#hdtbMenus > div > div:nth-child(5) > div").click();

        return this;
    }

    public Google __ツール$期間$1時間以内をクリック() {
        return __ツール$期間$指定文字をクリック("1 時間以内");
    }

    public Google __ツール$期間$1年以内をクリック() {
        return __ツール$期間$指定文字をクリック("1 年以内");
    }

    private Google __ツール$期間$指定文字をクリック(String text) {
        // 期間のメニューが表示されているか確認
        if (!$("#hdtbMenus > div > ul.hdtbU.hdtb-mn-o").isDisplayed()) {
            __ツール$期間をクリック();
        }
        // 選択後に表示される項目を60秒待つ
        $("#hdtbMenus > div > ul.hdtbU.hdtb-mn-o").waitUntil(appear, 60000);

        return matchTextClick(text, $$("#hdtbMenus > div > ul.hdtbU.hdtb-mn-o > li").iterator());
    }

    private Google matchTextClick(String text, Iterator<SelenideElement> seItr) {

        boolean ckickFlg = false;

        while (seItr.hasNext()) {
            SelenideElement se = seItr.next();
            if (text.equals(se.getText())) {
                se.click();
                ckickFlg = true;
                break;
            }
        }

        if (!ckickFlg) {
            throw new NoSuchElementException("Click error:" + text);
        }

        return this;
    }
}
```

テストは以下のようなコード。

```java:GoogleTest.java
package test;

import org.junit.Test;

import com.htmlhifive.pitalium.core.model.ScreenshotArgument;

import page.Google;

public class GoogleTest extends TestBase {

    @Test
    public void OK() throws Exception {

        driver.get(Google._url);
        Google page = new Google(driver);

        page.__検索ワード入力("test")
            .__検索を行う();

        ScreenshotArgument arg = ScreenshotArgument.builder("result1").addNewTarget().build();
        assertionView.verifyView(arg);

        page.__ツール$期間$1時間以内をクリック();
// 画面状態が完璧にわかるなら以下の記述の方が処理速度が速い
//        page.__ツールをクリック()
//            .__ツール$期間をクリック()
//            .__ツール$期間$1時間以内をクリック();


        arg = ScreenshotArgument.builder("result2").addNewTarget().build();
        assertionView.verifyView(arg);

        page.__ツール$期間$1年以内をクリック();


        arg = ScreenshotArgument.builder("result3").addNewTarget().build();
        assertionView.verifyView(arg);
    }
}
```

# Frameが分かれている画面を処理するPageクラスを作ってみる

Frameが分かれているページは切り替えなくてはいけないらしい。
この制御もやはりPageクラスでカバーする方が良いと思ってる。
（切り替えなしでいける方法があればご教授ください）

18/04/23 変更
コメントをいただいた内容は理解できたが、実力不足でコードには反映できず:scream:
"__項目1に入力"メソッドでも同じような記述ができれば良いのだが思いつかず。

```java:FrameSample.java
package page;

import static com.codeborne.selenide.Selenide.*;

import org.openqa.selenium.WebDriver;

import com.codeborne.selenide.SelenideElement;
import com.codeborne.selenide.WebDriverRunner;

public class FrameSample extends PageBase {

    public static final String _url = "https://";

    public FrameSample(WebDriver webDriver) {
        super(webDriver);
    }

    /** デフォルトフレームに存在している要素に入力 */
    public FrameSample __項目1に入力(String s) throws InterruptedException {
        $("#in1").setValue(s);
        return this;
    }

    /** 別フレームに存在している要素に入力 */
    public FrameSample __項目2に入力(String s) throws InterruptedException {
        switcFrame($(".iframe"));
        $("#in2").setValue(s);
        switchDefaultFrame();
        return this;
    }

    /***
     * デフォルトフレームに切り替える
     * @throws InterruptedException
     */
    private void switchDefaultFrame() throws InterruptedException{
        // 処理が安定しないのでフレーム変更にWaitをとりあえず入れる
        WebDriverRunner.getWebDriver().switchTo().defaultContent();
        Thread.sleep(2000);
    }

    /***
     * 別フレームに切り替える
     * @param se フレームのElement
     * @throws InterruptedException
     */
    private void switcFrame(SelenideElement se) throws InterruptedException{
        // 処理が安定しないのでフレーム変更にWaitをとりあえず入れる
        WebDriverRunner.getWebDriver().switchTo().frame(se);
        Thread.sleep(2000);
    }
}
```