Расширить Selenium WebDriver WebElement?

Я следую шаблону объекта страницы, предложенному 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 прокладывает веб-элементы на странице на основе аннотаций.

Возможно ли это? И если да, то кто-нибудь знает, как это можно сделать?

Ответ 1

Я создал интерфейс, который объединяет все интерфейсы 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();
    }
}

При использовании в моем script:

CheckBox cb = new CheckBox(element);
cb.uncheck();

Я также придумал способ обертывания классов Element. Вам нужно создать несколько заводов для замены встроенного PageFactory, но это выполнимо, и это придает большую гибкость.

Я зарегистрировал этот процесс на своем сайте:

У меня также есть проект под названием selophane, который был вдохновлен этим и другими вопросами: selophane

Ответ 2

Вы можете создавать пользовательские WebElements, используя WebDriver Extensions, которая предоставляет класс 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);
        }
    }
}

... и затем добавьте его в свой PageObject с аннотацией @FindBy и используйте WebDriverExtensionFieldDecorator при вызове метода PageFactory.initElements

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 Реализация 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));
    }
}

Ответ 3

Боковое примечание: WebElement не является классом, его интерфейс, что означает, что ваш класс будет выглядеть примерно так:

public class TableWebElement implements WebElement {

Но в этом случае вы должны реализовать все методы, которые находятся в WebDriver. И его любопытный перебор.

Вот как я это делаю - я полностью избавился от предложенных 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);
 }

Ответ 4

Как продолжение этого, это то, что я закончил делать (в случае, если у кого-то еще такая же проблема). Ниже приведен фрагмент класса I, созданного как оболочка для 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, который был бы замечательным!

Ответ 5

Зачем беспокоиться о расширении WebElement? Вы пытаетесь фактически разработать пакет Selenium? Тогда, если это так, не имеет смысла расширять этот метод, если ваши дополнения не могут быть применимы к каждому используемому веб-элементу, а не только к тем, которые относятся к вашей таблице

Почему бы просто не сделать что-то вроде:

public class PermissionsPage extends TableWebElement{

...permissions stuff...

}

import WebElement

public class TableWebElement{

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

}

Я не знаю, отвечает ли это на ваш вопрос, но мне показалось, что это больше вопрос архитектуры класса, чем что-либо еще.

Ответ 6

Я бы создал то, что я называю "SubPageObject", который представляет объект PageObject для объекта PageObject...

Преимущество состоит в том, что вы можете создавать для него настраиваемые методы, и вы можете инициализировать только те WebElements, которые вам действительно нужны в этом SubPageObject.

Ответ 7

Почему бы не создать оболочку веб-элемента? как это?

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

Взгляните на htmlelements framework, это похоже на то, что вам нужно. Он имеет предварительно реализованные общие элементы, такие как checkbox, radioobox, table, form и т.д., Вы можете легко создавать свои собственные и вставлять одни элементы в другие, создавая четкую древовидную структуру.

Ответ 9

Шаг 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);