Расширить 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);