WebDriver を使い始めたばかりで、特に PageObjects と PageFactory を使用して、ベストプラクティスを学習しようとしています。
PageObjectsはWebページのさまざまな操作を公開し、WebDriverコードをテストクラスから分離する必要があることを理解しています。多くの場合、同じ操作を行うと、使用するデータに応じて異なるページに移動する可能性があります。
たとえば、この架空のログインシナリオでは、管理者の資格情報を提供するとAdminWelcomeページに移動し、顧客の資格情報を提供するとCustomerWelcomeページに移動します。
したがって、これを実装する最も簡単な方法は、異なるPageObjectsを返す2つのメソッドを公開することです...
_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");
_
コードを複製する代わりに、関連するPageObjectを返す単一のlogin()
メソッドを公開するよりクリーンな方法があることを望んでいました。
戻り値の型として使用できるように、ページの階層を作成する(またはインターフェイスを実装する)ことを考えましたが、不器用に感じます。私が思いついたのは次のとおりです。
_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の順列ごとに多くの異なるメソッドを公開するのが一般的な方法ですか?
ボヘミアンの答えは柔軟ではありません-同じページに戻るページアクション(間違ったパスワードの入力など)を行うことはできません。また、複数のページアクションを実行して異なるページを作成することもできません(混乱した場合を考えてください)。ログインページには別のアクションがあり、結果が異なります)。また、さまざまな結果に対応するためだけに、さらに多くの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
_
シナリオごとに個別のメソッドを使用すると、Login
PageObjectのAPIも非常に明確になります。また、ログインのすべての結果を簡単に伝えることができます。インターフェイスを使用して使用するページを制限することに価値はありませんでした。 login()
メソッド。
再利用可能なWebDriverコードをきめ細かいメソッドにリファクタリングする必要があるというTomAndersonに同意します。それらがきめ細かく公開されるか(テストクラスが関連する操作を選択して選択できるように)、または単一の粗い方法としてテストクラスに結合されて公開されるかどうかは、おそらく個人的な好みの問題です。
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()
メソッドに渡すには、 「型トークン」パターンとして知られているコンストラクターへのクラス。