14

WebDriverの使用を開始したばかりで、特にPageObjectsPageFactoryを使用してベストプラクティスを学習しようとしています。

PageObjectsはWebページのさまざまな操作を公開し、WebDriverコードをテストクラスから分離する必要があることを理解しています。多くの場合、同じ操作を行うと、使用するデータに応じて異なるページに移動する可能性があります。

たとえば、この架空のログインシナリオでは、管理者の資格情報を提供するとAdminWelcomeページに移動し、顧客の資格情報を提供するとCustomerWelcomeページに移動します。

したがって、これを実装する最も簡単な方法は、異なるPageObjectsを返す2つのメソッドを公開することです。

ログインPageObject

package example;

import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;

public class Login {

    @FindBy(id = "username")
    private WebElement username;

    @FindBy(id = "password")
    private WebElement password;

    @FindBy(id = "submitButton")
    private WebElement submitButton;

    private WebDriver driver;

    public Login(WebDriver driver){
        this.driver = driver;
    }

    public AdminWelcome loginAsAdmin(String user, String pw){
        username.sendKeys(user);
        password.sendKeys(pw);
        submitButton.click();
        return PageFactory.initElements(driver, AdminWelcome.class);
    }

    public CustomerWelcome loginAsCustomer(String user, String pw){
        username.sendKeys(user);
        password.sendKeys(pw);
        submitButton.click();
        return PageFactory.initElements(driver, CustomerWelcome.class);
    }

}

そして、テストクラスで次のことを行います。

Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = loginPage.loginAsAdmin("admin", "admin");

また

Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = loginPage.loginAsCustomer("joe", "smith");

代替アプローチ

コードを複製する代わりにlogin()、関連するPageObjectを返す単一のメソッドを公開するよりクリーンな方法があることを望んでいました。

ページの階層を作成して(またはインターフェイスを実装して)、それを戻り型として使用できるようにすることを考えましたが、不器用に感じます。私が思いついたのは次のとおりです。

public <T> T login(String user, String pw, Class<T> expectedPage){
    username.sendKeys(user);
    password.sendKeys(pw);
    submitButton.click();
    return PageFactory.initElements(driver, expectedPage);
}

つまり、テストクラスで次のことができます。

Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = 
    loginPage.login("admin", "admin", AdminWelcome.class);

また

Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = 
    loginPage.login("joe", "smith", CustomerWelcome.class);

これは柔軟性があります-ExpiredPasswordページを追加でき、login()メソッドをまったく変更する必要はありません-別のテストを追加して、適切な期限切れのクレデンシャルとExpiredPasswordページを期待されるページとして渡すだけです。

もちろん、loginAsAdmin()loginAsCustomer()メソッドを非常に簡単に残して、それらの内容をジェネリックへの呼び出しに置き換えることができますlogin()(その後、プライベートになります)。新しいページ(たとえば、ExpiredPasswordページ)には、別のメソッド(たとえばloginWithExpiredPassword())が必要になります。

これには、メソッド名が実際に何かを意味するという利点があり(ログインの結果が3つあることが簡単にわかります)、PageObjectのAPIは少し使いやすくなっています(「期待されるページ」が渡されない)が、WebDriverコードはまだ再利用されています。

さらなる改善...

単一のlogin()メソッドを公開した場合は、それらのページにマーカーインターフェイスを追加することで、ログインからどのページに到達できるかをより明確にすることができます(各シナリオのメソッドを公開する場合、これはおそらく必要ありません)。

public interface LoginResult {}

public class AdminWelcome implements LoginResult {...}

public class CustomerWelcome implements LoginResult {...}

そして、ログイン方法を次のように更新します。

public <T extends LoginResult> T login(String user, String pw, 
    Class<T> expectedPage){
    username.sendKeys(user);
    password.sendKeys(pw);
    submitButton.click();
    return PageFactory.initElements(driver, expectedPage);
}

どちらのアプローチもうまくいくようですが、より複雑なシナリオでどのように拡張できるかはわかりません。このようなコード例は見たことがないので、ページ上のアクションがデータに応じて異なる結果をもたらす可能性がある場合、他のすべての人が何をするのだろうかと思います。

または、WebDriverコードを複製し、データ/ PageObjectsの順列ごとに多くの異なるメソッドを公開するのが一般的な方法ですか?

4

2 に答える 2

8

ボヘミアンの答えは柔軟ではありません-同じページに戻るページアクション(不正なパスワードの入力など)を行うことはできません。また、複数のページアクションを実行して異なるページを作成することもできません(ログインページには別のアクションがあり、結果が異なります)。また、さまざまな結果に対応するためだけに、さらに多くのPageObjectが山積みになります。

これをもう少し試した後(そして失敗したログインシナリオを含めて)、私は次のことに決めました:

private <T> T login(String user, String pw, Class<T> expectedPage){
    username.sendKeys(user);
    password.sendKeys(pw);
    submitButton.click();
    return PageFactory.initElements(driver, expectedPage);
}

public AdminWelcome loginAsAdmin(String user, String pw){
    return login(user, pw, AdminWelcome.class);
}

public CustomerWelcome loginAsCustomer(String user, String pw){
    return login(user, pw, CustomerWelcome.class);
}

public Login loginWithBadCredentials(String user, String pw){
    return login(user, pw, Login.class);
}

これは、ログインロジックを再利用できることを意味しますが、テストクラスが期待されるページに合格する必要はありません。つまり、テストクラスは非常に読みやすくなります。

Login login = PageFactory.initElements(driver, Login.class);
login = login.loginWithBadCredentials("bad", "credentials");
// TODO assert login failure message
CustomerWelcome customerWelcome = login.loginAsCustomer("joe", "smith");
// TODO do customer things

シナリオごとに個別のメソッドを使用すると、LoginPageObjectのAPIも非常に明確になります。また、ログインのすべての結果を簡単に伝えることができますlogin()。メソッドで使用するページを制限するためにインターフェイスを使用することに価値はありませんでした。

再利用可能なWebDriverコードをきめ細かいメソッドにリファクタリングする必要があるというTomAndersonに同意します。それらがきめ細かく公開されるか(テストクラスが関連する操作を選択して選択できるように)、または単一の粗い方法としてテストクラスに結合されて公開されるかどうかは、おそらく個人的な好みの問題です。

于 2012-11-21T04:36:26.620 に答える
7

APIを複数のタイプで汚染しています-ジェネリックスと継承を使用するだけです:

public abstract class Login<T> {

    @FindBy(id = "username")
    private WebElement username;

    @FindBy(id = "password")
    private WebElement password;

    @FindBy(id = "submitButton")
    private WebElement submitButton;

    private WebDriver driver;

    private Class<T> clazz;

    protected Login(WebDriver driver, Class<T> clazz) {
        this.driver = driver;
        this.clazz = clazz
    }

    public T login(String user, String pw){
        username.sendKeys(user);
        password.sendKeys(pw);
        submitButton.click();
        return PageFactory.initElements(driver, clazz);
    }
}

その後

public AdminLogin extends Login<AdminWelcome> {

   public AdminLogin(WebDriver driver) {
       super(driver, AdminWelcome.class);
   }
}

public CustomerLogin extends Login<CustomerWelcome> {

   public CustomerLogin(WebDriver driver) {
       super(driver, CustomerWelcome.class);
   }
}

ログインページのすべてのタイプのその他


クラスのインスタンスをコンストラクターに渡すことにより、のインスタンスをメソッドに渡すことができるという型消去の 回避策に注意してください。これは、「型トークン」パターンとして知られています。Class<T>PageFactory.initElements()

于 2012-11-14T01:57:05.467 に答える