13

我正在遵循 Selenium 建议的页面对象模式,但我将如何为页面创建更专业的 WebElement。具体来说,我们的页面上有表格,我编写了一些辅助函数来获取表格的特定行、返回表格的内容等。

目前,这是我创建的具有表的页面对象的片段:

public class PermissionsPage  {

    @FindBy(id = "studyPermissionsTable")
    private WebElement permissionTable;

    @FindBy(id = "studyPermissionAddPermission")
    private WebElement addPermissionButton;

    ... 

}

所以,我想做的是让permissionsTable 成为一个更加自定义的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 根据注释将 web 元素连接到页面的方式。

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

4

10 回答 10

30

我创建了一个结合了所有 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

于 2012-12-07T19:38:12.193 回答
4

您可以使用WebDriver Extensions框架创建自定义 WebElements,该框架提供了实现 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);
    }
}

和具有 WebElements 的静态断言方法的 JUnitRunner

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));
    }
}
于 2015-03-04T18:35:48.140 回答
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,那就太好了!

于 2012-08-13T14:03:47.480 回答
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);
 }
于 2012-07-25T09:26:58.373 回答
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:过时的元素引用:元素未附加到页面文档”。发生这种情况是因为“页面”在构造函数中仅实例化了一次,而 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 中受益。在第二个和第三个中 - 您最终会为您在页面上执行的每个操作创建新的页面工厂实例。所以我想这一切都归结为两个问题:

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 的实例?如果没有,是否有任何替代方案?

于 2018-10-12T21:28:03.960 回答
0

为什么要扩展 WebElement?您是否正在尝试实际开发 Selenium 包?然后,如果您是,那么扩展该方法是没有意义的,除非您的添加可以适用于您使用的每个 Web 元素,而不仅仅是特定于您的表格的那些

为什么不这样做:

public class PermissionsPage extends TableWebElement{

...permissions stuff...

}

import WebElement

public class TableWebElement{

...custom web elements pertaining to my page...

}

我不知道这是否回答了你的问题,但在我看来,这更像是一个类架构问题,而不是其他任何问题。

于 2012-07-25T04:15:00.293 回答
0

我会创建一个我称之为“SubPageObject”的东西,它代表 PageObject 上的一个 PageObject ...

优点是您可以为其创建自定义方法,并且您可以在此 SubPageObject 中仅初始化您真正需要的 WebElement。

于 2012-07-25T07:33:16.893 回答
0

为什么不制作 Web 元素包装器?像这样?

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

但是你可以让你的课程比这更复杂。只是一个概念

于 2012-08-03T19:38:23.810 回答
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";
    }
}

用法:

一个)

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);
于 2017-07-20T09:42:31.577 回答
0

看看htmlelements框架,它看起来正是您所需要的。它具有预先实现的常用元素,例如复选框、单选框、表格、表单等,您可以轻松创建自己的元素并将一个元素插入其他元素,从而创建清晰的树形结构。

于 2015-09-09T06:14:31.117 回答