我正在遵循Selenium建议的页面对象模式,但我如何为页面创建更专业的WebElement。具体来说,我们在页面上有表格,我编写了一些辅助函数来获取表格的特定行,返回表格的内容等。
目前,这是我创建的具有表格的页面对象的片段:
public class PermissionsPage {
@FindBy(id = "studyPermissionsTable")
private WebElement permissionTable;
@FindBy(id = "studyPermissionAddPermission")
private WebElement addPermissionButton;
...
}
所以,我想做的是将权限表作为一个更加自定义的WebElement,其中包含我之前提到的一些方法。
例如:
public class TableWebElement extends WebElement {
WebElement table;
// a WebDriver needs to come into play here too I think
public List<Map<String, String>> getTableData() {
// code to do this
}
public int getTableSize() {
// code to do this
}
public WebElement getElementFromTable(String id) {
// code to do this
}
}
我希望这是有道理的,我正在努力解释。我想我正在寻找的是让这个自定义WebElement做一些特定于表的其他东西的方法。将此自定义元素添加到页面,并利用Selenium基于注释将webelements连接到页面的方式。
有可能吗?如果是这样,有谁知道如何做到这一点?
答案 0 :(得分:27)
我创建了一个结合了所有WebDriver接口的接口:
public interface Element extends WebElement, WrapsElement, Locatable {}
它就是用来包装WebElements在包装元素时可以做的所有事情。
然后是一个实现:
public class ElementImpl implements Element {
private final WebElement element;
public ElementImpl(final WebElement element) {
this.element = element;
}
@Override
public void click() {
element.click();
}
@Override
public void sendKeys(CharSequence... keysToSend) {
element.sendKeys(keysToSend);
}
// And so on, delegates all the way down...
}
然后,例如一个复选框:
public class CheckBox extends ElementImpl {
public CheckBox(WebElement element) {
super(element);
}
public void toggle() {
getWrappedElement().click();
}
public void check() {
if (!isChecked()) {
toggle();
}
}
public void uncheck() {
if (isChecked()) {
toggle();
}
}
public boolean isChecked() {
return getWrappedElement().isSelected();
}
}
在我的脚本中使用它时:
CheckBox cb = new CheckBox(element);
cb.uncheck();
我还提出了一种包装Element
类的方法。您必须创建一些工厂来替换内置的PageFactory
,但它是可行的,并且它提供了很大的灵活性。
我在我的网站上记录了这个过程:
我还有一个名为selophane的项目受到了这个和其他问题的启发:selophane
答案 1 :(得分:3)
您可以使用WebDriver Extensions框架创建自定义Web元素,该框架提供实现WebElement接口的WebComponent类
创建自定义WebElement
public class Table extends WebComponent {
@FindBy(tagName = "tr")
List<Row> rows;
public Row getRow(int row) {
return rows.get(row - 1);
}
public int getTableSize() {
return rows.size();
}
public static class Row extends WebComponent {
@FindBy(tagName = "td")
List<WebElement> columns;
public WebElement getCell(int column) {
return columns.get(column - 1);
}
}
}
...然后使用@FindBy注释将其添加到PageObject中,并在调用PageFactory.initElements方法时使用WebDriverExtensionFieldDecorator
public class PermissionPage {
public PermissionPage(WebDriver driver) {
PageFactory.initElements(new WebDriverExtensionFieldDecorator(driver), this);
}
@FindBy(id = "studyPermissionsTable")
public Table permissionTable;
@FindBy(id = "studyPermissionAddPermission")
public WebElement addPermissionButton;
}
...然后在测试中使用它
public class PermissionPageTest {
@Test
public void exampleTest() {
WebDriver driver = new FirefoxDriver();
PermissionPage permissionPage = new PermissionPage(driver);
driver.get("http://www.url-to-permission-page.com");
assertEquals(25, permissionPage.permissionTable.getTableSize());
assertEquals("READ", permissionPage.permissionTable.getRow(2).getCell(1).getText());
assertEquals("WRITE", permissionPage.permissionTable.getRow(2).getCell(2).getText());
assertEquals("EXECUTE", permissionPage.permissionTable.getRow(2).getCell(3).getText());
}
}
或者甚至更好地使用WebDriver Extensions PageObject实现
public class PermissionPage extends WebPage {
@FindBy(id = "studyPermissionsTable")
public Table permissionTable;
@FindBy(id = "studyPermissionAddPermission")
public WebElement addPermissionButton;
@Override
public void open(Object... arguments) {
open("http://www.url-to-permission-page.com");
assertIsOpen();
}
@Override
public void assertIsOpen(Object... arguments) throws AssertionError {
assertIsDisabled(permissionTable);
assertIsDisabled(addPermissionButton);
}
}
和JUnitRunner以及WebElements的静态断言方法
import static com.github.webdriverextensions.Bot.*;
@RunWith(WebDriverRunner.class)
public class PermissionPageTest {
PermissionPage permissionPage;
@Test
@Firefox
public void exampleTest() {
open(permissionPage);
assertSizeEquals(25, permissionPage.permissionTable.rows);
assertTextEquals("READ", permissionPage.permissionTable.getRow(2).getCell(1));
assertTextEquals("WRITE", permissionPage.permissionTable.getRow(2).getCell(2));
assertTextEquals("EXECUTE", permissionPage.permissionTable.getRow(2).getCell(3));
}
}
答案 2 :(得分:1)
附注:WebElement
不是类,其接口表示您的类看起来更像是这样:
public class TableWebElement implements WebElement {
但在这种情况下,您必须实施所有 WebDriver中的方法。它有点矫枉过正。
以下是我如何做到这一点 - 我完全摆脱了selenium提出的建议的PageObjects,并通过拥有自己的类来“重新发明轮子”。我有一个整个应用程序类:
public class WebUI{
private WebDriver driver;
private WebElement permissionTable;
public WebUI(){
driver = new firefoxDriver();
}
public WebDriver getDriver(){
return driver;
}
public WebElement getPermissionTable(){
return permissionTable;
}
public TableWebElement getTable(){
permissionTable = driver.findElement(By.id("studyPermissionsTable"));
return new TableWebElement(this);
}
}
然后我有我的帮助班
public class TableWebElement{
private WebUI webUI;
public TableWebElement(WebUI wUI){
this.webUI = wUI;
}
public int getTableSize() {
// because I dont know exactly what are you trying to achieve just few hints
// this is how you get to the WebDriver:
WebElement element = webUI.getDriver().findElement(By.id("foo"));
//this is how you get to already found table:
WebElement myTable = webUI.getPermissionTable();
}
}
测试样本:
@Test
public void testTableSize(){
WebUI web = new WebUI();
TableWebElement myTable = web.getTable();
Assert.assertEquals(myTable.getSize(), 25);
}
答案 3 :(得分:1)
作为后续行动,这就是我最终做的事情(如果其他人有同样的问题)。下面是我作为WebElement的包装器创建的类的片段:
public class CustomTable {
private WebDriver driver;
private WebElement tableWebElement;
public CustomTable(WebElement table, WebDriver driver) {
this.driver = driver;
tableWebElement = table;
}
public WebElement getTableWebElement() {
return tableWebElement;
}
public List<WebElement> getTableRows() {
String id = tableWebElement.getAttribute("id");
return driver.findElements(By.xpath("//*[@id='" + id + "']/tbody/tr"));
}
public List<WebElement> getTableHeader() {
String id = tableWebElement.getAttribute("id");
return tableWebElement.findElements(By.xpath("//*[@id='" + id + "']/thead/tr/th"));
}
.... more utility functions here
}
然后我在任何通过引用它来创建的页面中使用它:
public class TestPage {
@FindBy(id = "testTable")
private WebElement myTestTable;
/**
* @return the myTestTable
*/
public CustomTable getBrowserTable() {
return new CustomTable(myTestTable, getDriver());
}
我唯一不喜欢的是,当页面想要获取表格时,它会创建一个“新”表。我这样做是为了避免更新表中的数据时发生的StaleReferenceExeptions(即更新单元格,添加行,删除行)。如果有人对我每次请求时如何避免创建新实例有任何建议,而是返回更新的WebElement,这将是非常棒的!
答案 4 :(得分:1)
我还通过扩展DefaultFieldDecorator创建了自定义WebElement实现,它可以与PageFactory模式一起使用。但是我担心使用PageFactory本身。它似乎仅对“静态”应用程序有效(用户交互不会更改应用程序的组件布局)。
但是,每当您需要处理一些更复杂的事情时(例如,如果您有一个带有表的页面,其中包含用户列表和每个用户旁边的删除按钮),使用PageFactory就会出现问题。
这里有一个例子来说明我在说什么:
public class UserList
{
private UserListPage page;
UserList(WebDriver driver)
{
page = new UserListPage(driver);
}
public void deleteFirstTwoUsers()
{
if (page.userList.size() <2) throw new RuntimeException("Terrible bug!");
page.deleteUserButtons.get(0).click();
page.deleteUserButtons.get(0).click();
}
class UserListPage {
@FindBy(xpath = "//span[@class='All_Users']")
List<BaseElement> userList;
@FindBy(xpath = "//span[@class='All_Users_Delete_Buttons']")
List<BaseElement> deleteUserButtons;
UserListPage(WebDriver driver)
{
PageFactory.initElements(new ExtendedFieldDecorator(driver), this);
}
}
因此,在上述情况下,当一个调用deleteFirstTwoUsers()方法时,当尝试使用“ StaleElementReferenceException:stale元素引用:元素未附加到页面文档”来删除第二个User时,它将失败。发生这种情况的原因是,在构造函数中仅实例化了“页面”一次,而PageFactory却“无法知道” :)用户数量减少了。
我到目前为止发现的解决方法是以太:
1)请勿将Page Factory完全用于此类方法,而应将WebDriver直接用于某种while循环:driver.findElementsBy()...
。
2)或执行以下操作:
public void deleteFirstTwoUsers()
{
if (page.userList.size() <2) throw new RuntimeException("Terrible bug!");
new UserListPage(driver).deleteUserButtons.get(0).click();
new UserListPage(driver).deleteUserButtons.get(1).click();
}
3)或这样:
public class UserList {
private WebDriver driver;
UserList(WebDriver driver)
{
this.driver = driver;
}
UserListPage getPage { return new UserListPage(driver);})
public void deleteFirstTwoUsers()
{
if (getPage.userList.size() <2) throw new RuntimeException("Terrible bug!");
getPage.deleteUserButtons.get(0).click();
getPage.deleteUserButtons.get(0).click();
}
但是在第一个示例中,您不能从使用包装的BaseElement中受益。在第二和第三个中-您最终将为在页面上执行的每个操作创建Page Factory的新实例。 所以我想这全都归结为两个问题:
1)您真的认为对所有内容使用PageFactory真的值得吗? 像这样动态地创建每个元素会很“便宜”:
class UserListPage
{
private WebDriver driver;
UserListPage(WebDriver driver)
{
this.driver = driver;
}
List<BaseElement> getUserList() {
return driver.findElements(By.xpath("//span[@class='All_Users']"));
}
2)是否可以使用@Override driver.findElements方法,以便它返回包装的BaseElement对象而不是WebElement的实例?如果没有,还有其他选择吗?
答案 5 :(得分:0)
为什么要费心扩展WebElement?您是否正在尝试开发Selenium包?然后,如果你是扩展该方法是没有意义的,除非你的添加内容适用于你使用的每个web元素,而不仅仅是那些特定于你的表格的元素
为什么不做以下事情:
public class PermissionsPage extends TableWebElement{
...permissions stuff...
}
import WebElement
public class TableWebElement{
...custom web elements pertaining to my page...
}
我不知道这是否能回答你的问题,但在我看来,它更像是一个阶级架构问题。
答案 6 :(得分:0)
我会创建我称之为“SubPageObject”的东西,它代表PageObject ...上的PageObject ...
优点是您可以为它创建自定义方法,并且只能在此SubPageObject中初始化您真正需要的WebElements。
答案 7 :(得分:0)
为什么不制作网页元素包装器?像这样?
class WEBElment
{
public IWebElement Element;
public WEBElement(/*send whatever you decide, a webelement, a by element, a locator whatever*/)
{
Element = /*make the element from what you sent your self*/
}
public bool IsDisplayed()
{
return Element.Displayed;
}
} // end of class here
但是你可以让你的课比这更复杂。只是一个概念
答案 8 :(得分:0)
看一下htmlelements框架,它看起来就像你需要的那样。它有预先实现的常用元素,如复选框,radiobox,table,form等,您可以轻松创建自己的元素,并将一个元素插入到其他元素中,从而创建清晰的树结构。
答案 9 :(得分:0)
步骤1)创建一个名为Element
的基类,扩展IWrapsElement
接口
public class Element : IWrapsElement
{
public IWebElement WrappedElement { get; set; }
public Element(IWebElement element)
{
this.WrappedElement = element;
}
}
步骤2)为每个自定义元素创建一个类,并从Element
类
public class Checkbox : Element
{
public Checkbox(IWebElement element) : base(element) { }
public void Check(bool expected)
{
if (this.IsChecked()!= expected)
{
this.WrappedElement.Click();
}
}
public bool IsChecked()
{
return this.WrappedElement.GetAttribute("checked") == "true";
}
}
用法:
A)
Checkbox x = new Checkbox(driver.FindElement(By.Id("xyz")));
x.Check(true)
b)
private IWebElement _chkMale{ get; set; }
[FindsBy(How = How.Name, Using = "chkMale")]
private Checkbox ChkMale
{
get { return new Checkbox (_chkMale); }
}
ChkMale.Check(true);