Повторное использование объекта страницы Selenium

Мне очень нравится, как селен 2 по соглашению подталкивает вас к использованию объектов PageObjects как POJO, а затем просто использует PageFactory для создания экземпляров полей этого класса.

То, что я считаю ограниченным, заключается в том, что мы повторно используем много элементов на разных страницах. Большая проблема заключается в том, что эти повторно используемые компоненты не имеют одинакового идентификатора/имени, когда они появляются на разных страницах; однако тесты, которые мы будем запускать для каждого из них, одинаковы.

В качестве примера мы собираем даты во многих местах. Таким образом, пример страницы для этого может быть (удалены поля месяца, дня):

public class DatePageObject {
    private WebDriver driver;

    DatePageObject(WebDriver driver) {
        this.driver = driver;
    }

    @FindBy( id = "someIdForThisInstance")
    private WebElement year;

    public void testYearNumeric() {
        this.year.sendKeys('aa');
        this.year.submit();
        //Logic to determine Error message shows up
    }
}

Тогда я мог бы просто проверить это с помощью кода ниже:

public class Test {
    public static void main(String[] args) {
         WebDriver driver = new FirefoxDriver();
         DatePageObject dpo = PageFactory.initElements(driver, DriverPageObject.class);
         driver.get("Some URL");
         dpo.testYearNumeric();
    }
}

Мне бы очень хотелось, чтобы у меня была настройка, в которой с помощью Spring я могу добавить в приложение id/name/xpath и т.д.

Есть ли способ, которым я могу это сделать, не теряя возможности использовать PageFactory?

Редактирование 1 - Добавление идеальных классов базового уровня, работающих на пользовательских локаторах и фабриках.

public class PageElement {
    private WebElement element;
    private How how;
    private String using;

    PageElement(How how, String using) {
        this.how = how;
        this.using = using;
    }
    //Getters and Setters
}


public class PageWidget {
    private List<PageElement> widgetElements;
}


public class Screen {
    private List<PageWidget> fullPage;
    private WebDriver driver;

    public Screen(WebDriver driver) {
        this.driver = driver;
        for (PageWidget pw : fullPage) {
            CustomPageFactory.initElements(driver, pw.class);
        }
}

Редактировать 2 - как примечание, пока вы используете Selenium 2.0.a5 или выше, теперь вы можете дать драйверу неявное значение таймаута.

Итак, вы можете заменить свой код:

private class CustomElementLocator implements ElementLocator {
    private WebDriver driver;
    private int timeOutInSeconds;
    private final By by;


    public CustomElementLocator(WebDriver driver, Field field,
            int timeOutInSeconds) {
        this.driver = driver;
        this.timeOutInSeconds = timeOutInSeconds;
        CustomAnnotations annotations = new CustomAnnotations(field);
        this.by = annotations.buildBy();
        driver.manage().timeouts().implicitlyWait(15, TimeUnit.SECONDS); //Set this value in a more realistic place
    }


    public WebElement findElement() {
        return driver.findElement(by);
    }
}

Ответ 1

Вы можете создать свой Page Object of the Common Web Elements (только что придумал это имя:)) - каждый CWE будет представлять собой "виджет", который используется на разных страницах. В вашем примере это будет своего рода виджет даты - он содержит год, месяц и день. В основном это будет объект страницы.

PageFactory требует, чтобы строковые константы использовались в аннотациях @FindBy.

Чтобы устранить это ограничение, мы создали собственный ElementLocator s.

Вы можете использовать DateWidget в своем тесте:

....
DateWidget widget = new DateWidget(driver, "yearId", "monthId", "dayId");
....

public void testYearNumeric() {
        widget.setYear("aa");
        widget.submit();
        //Logic to determine Error message shows up

        // ... and day
        widget.setDay("bb");
        widget.submit();
        //Logic to determine Error message shows up
    }

Класс DateWidget, который содержит пользовательские локаторы и синтаксические анализаторы:

package pagefactory.test;

import java.lang.reflect.Field;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.openqa.selenium.support.pagefactory.Annotations;
import org.openqa.selenium.support.pagefactory.ElementLocator;
import org.openqa.selenium.support.pagefactory.ElementLocatorFactory;
import org.openqa.selenium.support.ui.ExpectedCondition;
import org.openqa.selenium.support.ui.Wait;
import org.openqa.selenium.support.ui.WebDriverWait;

public class DateWidget {

    // These constants are used to identify that they should be changed to the actual IDs
    private static final String YEAR_ID = "$YEAR_ID$";
    private static final String MONTH_ID = "$MONTH_ID$";
    private static final String DAY_ID = "$DAY_ID$";

    // Elements whose ids will be replaced during run-time
    /** Year element */
    @FindBy(id = YEAR_ID)
    private WebElement year;

    /** Month element */
    @FindBy(id = MONTH_ID)
    private WebElement month;

    /** day element */
    @FindBy(id = DAY_ID)
    private WebElement day;

    // The ids of the elements
    /** ID of the year element */
    private String yearId;

    /** ID of the month element */
    private String monthId;

    /** ID of the day element */
    private String dayId;

    public DateWidget(WebDriver driver, String yearId, String monthId,
            String dayId) {
        this.yearId = yearId;
        this.monthId = monthId;
        this.dayId = dayId;

        PageFactory.initElements(new CustomLocatorFactory(driver, 15), this);
    }

    public String getYear() {
        return year.getValue();
    }

    public void setYear(String year) {
        setValue(this.year, year);
    }

    public String getMonth() {
        return month.getValue();
    }

    public void setMonth(String month) {
        setValue(this.month, month);
    }

    public String getDay() {
        return day.getValue();
    }

    public void setDay(String day) {
        setValue(this.day, day);
    }

    public void submit() {
        year.submit();
    }

    private void setValue(WebElement field, String value) {
        field.clear();
        field.sendKeys(value);
    }

    private class CustomLocatorFactory implements ElementLocatorFactory {
        private final int timeOutInSeconds;
        private WebDriver driver;

        public CustomLocatorFactory(WebDriver driver, int timeOutInSeconds) {
            this.driver = driver;
            this.timeOutInSeconds = timeOutInSeconds;
        }

        public ElementLocator createLocator(Field field) {
            return new CustomElementLocator(driver, field, timeOutInSeconds);
        }
    }

    private class CustomElementLocator implements ElementLocator {
        private WebDriver driver;
        private int timeOutInSeconds;
        private final By by;

        public CustomElementLocator(WebDriver driver, Field field,
                int timeOutInSeconds) {
            this.driver = driver;
            this.timeOutInSeconds = timeOutInSeconds;
            CustomAnnotations annotations = new CustomAnnotations(field);
            this.by = annotations.buildBy();
        }

        @Override
        public WebElement findElement() {
            ExpectedCondition<Boolean> e = new ExpectedCondition<Boolean>() {
                public Boolean apply(WebDriver d) {
                    d.findElement(by);
                    return Boolean.TRUE;
                }
            };
            Wait<WebDriver> w = new WebDriverWait(driver, timeOutInSeconds);
            w.until(e);

            return driver.findElement(by);
        }
    }

    private class CustomAnnotations extends Annotations {

        public CustomAnnotations(Field field) {
            super(field);
        }

        @Override
        protected By buildByFromShortFindBy(FindBy findBy) {

            if (!"".equals(findBy.id())) {
                String id = findBy.id();
                if (id.contains(YEAR_ID)) {
                    id = id.replace(YEAR_ID, yearId);
                    return By.id(id);
                } else if (id.contains(MONTH_ID)) {
                    id = id.replace(MONTH_ID, monthId);
                    return By.id(id);
                } else if (id.contains(DAY_ID)) {
                    id = id.replace(DAY_ID, dayId);
                    return By.id(id);
                }
            }

            return super.buildByFromShortFindBy(findBy);
        }

    }

}