扩展Selenium WebDriver WebElement?

时间:2012-07-25 03:39:56

标签: java selenium webdriver

我正在遵循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连接到页面的方式。

有可能吗?如果是这样,有谁知道如何做到这一点?

10 个答案:

答案 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);