java 如何实现可以返回不同 PageObjects 的 WebDriver PageObject 方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13371699/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to implement WebDriver PageObject methods that can return different PageObjects
提问by James Bassett
I've just started using WebDriver, and I'm trying to learn the best practices, in particular using PageObjectsand PageFactory.
我刚刚开始使用WebDriver,我正在尝试学习最佳实践,特别是使用PageObjects和PageFactory。
It's my understanding that PageObjects should expose the various operations on a web page, and isolate the WebDriver code from the test class. Quite often, the same operation can result in navigating to different pages depending on the data used.
我的理解是 PageObjects 应该公开网页上的各种操作,并将 WebDriver 代码与测试类隔离。通常,根据所使用的数据,相同的操作可能会导致导航到不同的页面。
For example, in this hypothetical Login scenario, providing admin credentials takes you to the AdminWelcome page, and providing Customer credentials takes you to the CustomerWelcome page.
例如,在这个假设的登录场景中,提供管理员凭据会将您带到 AdminWelcome 页面,而提供 Customer 凭据会将您带到 CustomerWelcome 页面。
So the easiest way to implement this is to expose two methods which return different PageObjects...
因此,实现这一点的最简单方法是公开两个返回不同 PageObjects 的方法......
Login 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);
}
}
And do the following in the test class:
并在测试类中执行以下操作:
Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome = loginPage.loginAsAdmin("admin", "admin");
or
或者
Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome = loginPage.loginAsCustomer("joe", "smith");
Alternative approach
替代方法
Instead of duplicating code, I was hoping there was a cleaner way of exposing a single login()
method which returned the relevant PageObject.
我希望有一种更简洁的方法来公开login()
返回相关 PageObject的单个方法,而不是复制代码。
I thought about creating a hierarchy of pages (or having them implement an interface) so that I could use that as the return type, but it feels clumsy. What I came up with was the following:
我想过创建一个页面层次结构(或让它们实现一个接口),以便我可以将其用作返回类型,但感觉很笨拙。我想出的是以下内容:
public <T> T login(String user, String pw, Class<T> expectedPage){
username.sendKeys(user);
password.sendKeys(pw);
submitButton.click();
return PageFactory.initElements(driver, expectedPage);
}
Which means you can do the following in the test class:
这意味着您可以在测试类中执行以下操作:
Login loginPage = PageFactory.initElements(driver, Login.class);
AdminWelcome adminWelcome =
loginPage.login("admin", "admin", AdminWelcome.class);
or
或者
Login loginPage = PageFactory.initElements(driver, Login.class);
CustomerWelcome customerWelcome =
loginPage.login("joe", "smith", CustomerWelcome.class);
This is flexible - you could add an ExpiredPassword page and not have to change the login()
method at all - just add another test and pass in the appropriate expired credentials and the ExpiredPassword page as the expected page.
这是灵活的 - 您可以添加 ExpiredPassword 页面而根本不必更改login()
方法 - 只需添加另一个测试并传入适当的过期凭据和 ExpiredPassword 页面作为预期页面。
Of course, you could quite easily leave the loginAsAdmin()
and loginAsCustomer()
methods and replace their contents with a call to the generic login()
(which would then be made private). A new page (e.g. the ExpiredPassword page) would then require another method (e.g. loginWithExpiredPassword()
).
当然,您可以很容易地离开loginAsAdmin()
和loginAsCustomer()
方法并用对泛型的调用login()
(然后将其设为私有)来替换它们的内容。一个新页面(例如 ExpiredPassword 页面)将需要另一种方法(例如loginWithExpiredPassword()
)。
This has the benefit that the method names actually mean something (you can easily see that there are 3 possible results of logging in), the PageObject's API is a bit easier to use (no 'expected page' to pass in), but the WebDriver code is still being reused.
这样做的好处是方法名称实际上意味着什么(您可以很容易地看到有 3 种可能的登录结果),PageObject 的 API 更易于使用(没有“预期页面”传入),但 WebDriver代码仍在重用。
Further improvements...
进一步改进...
If you did expose the single login()
method, you could make it more obvious which pages can be reached from logging in by adding a marker interface to those pages (this is probably not necessary if you expose a method for each scenario).
如果您确实公开了单个login()
方法,则可以通过向这些页面添加标记界面来使登录可以访问哪些页面变得更加明显(如果您为每个场景公开一个方法,则这可能不是必需的)。
public interface LoginResult {}
public class AdminWelcome implements LoginResult {...}
public class CustomerWelcome implements LoginResult {...}
And update the login method to:
并将登录方法更新为:
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);
}
Either approach seems to work well, but I'm not sure how it would scale for more complicated scenarios. I haven't seen any code examples like it, so I'm wondering what everyone else does when actions on a page can result in different outcomes depending on the data?
这两种方法似乎都很有效,但我不确定它如何扩展到更复杂的场景。我还没有看到任何类似的代码示例,所以我想知道当页面上的操作会根据数据导致不同的结果时,其他人会怎么做?
Or is it common practice to just duplicate the WebDriver code and expose lots of different methods for each permutation of data/PageObjects?
或者只是复制 WebDriver 代码并为数据/页面对象的每个排列公开许多不同的方法是常见的做法吗?
采纳答案by James Bassett
Bohemian's answer is not flexible - you cannot have a page action returning you to the same page (such as entering a bad password), nor can you have more than 1 page action resulting in different pages (think what a mess you'd have if the Login page had another action resulting in different outcomes). You also end up with heaps more PageObjects just to cater for different results.
Bohemian 的答案并不灵活 - 您不能让页面操作将您返回到同一页面(例如输入错误的密码),也不能有超过 1 个页面操作导致不同的页面(想想如果登录页面有另一个导致不同结果的操作)。你最终也会堆更多的 PageObjects 来满足不同的结果。
After trialing this some more (and including the failed login scenario), I've settled on the following:
在尝试了更多(包括失败的登录场景)之后,我确定了以下几点:
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);
}
This means you can reuse the login logic, but prevent the need for the test class to pass in the expected page, which means the test class is very readable:
这意味着您可以重用登录逻辑,但防止测试类需要传入预期页面,这意味着测试类非常具有可读性:
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
Having separate methods for each scenario also makes the Login
PageObject's API very clear - and it's very easy to tell all of the outcomes of logging in. I didn't see any value in using interfaces to restrict the pages used with the login()
method.
为每个场景设置单独的方法也使得Login
PageObject 的 API 非常清晰 - 并且很容易说出登录的所有结果。我没有看到使用接口来限制与login()
方法一起使用的页面的任何价值。
I'd agree with Tom Anderson that reusable WebDriver code should be refactored into fine-grained methods. Whether they are exposed finely-grained (so the test class can pick and choose the relevant operations), or combined and exposed to the test class as a single coarsely-grained method is probably a matter of personal preference.
我同意 Tom Anderson 的观点,即应将可重用的 WebDriver 代码重构为细粒度的方法。它们是细粒度暴露(以便测试类可以选择相关操作),还是组合并作为单个粗粒度方法暴露给测试类可能是个人喜好的问题。
回答by Bohemian
You are polluting your API with multiple types - just use generics and inheritance:
您正在使用多种类型污染 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);
}
}
and then
接着
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);
}
}
etc for all types on login pages
等适用于登录页面上的所有类型
Note the work-around for type erasureof being able to pass an instance of Class<T>
to the PageFactory.initElements()
method, by passing an instance of the class into the constructor, which is known as the "type token" pattern.
请注意通过将类的实例传递给构造函数来将 的实例传递给方法的类型擦除的解决方法,这称为“类型标记”模式。Class<T>
PageFactory.initElements()