Page Object + AJAX и ExpectedConditions

Добрый день.

Пытаюсь автоматизировать тестирование одной страницы, но из-за динамического содержимого совсем запутался. Использую Selenium WebDriver +IE+ Java + Page Object

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

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

Как подружить проверку типа ExpectedConditions.textToBePresentInElement и объявленный в Page Object WebElement?

Вот кусок с элементами одного из окон:

    @FindBy(css = "#divCustomer button")
    private WebElement CustButton;
 
    @FindBy(css = "div#CustomerID-awepw [name=search]")
    private WebElement CustSearchField;
 
    @FindBy(css = "div#CustomerID-awepw button[type=submit]")
    private WebElement CustSearchButton;
 
    @FindBy(css = "div[aria-labelledby=\"ui-id-6\"] button[role=button]:nth-child(1)")
    private WebElement CustSelectButton;
 
    @FindBy(css = "div#CustomerID-awepw tr.awe-li:nth-child(1)")
    private WebElement CustListElement;

Вот метод:

    public void setCustomer(String s) {
        CustButton.click();
        CustSearchField.sendKeys(s);
        CustSearchButton.click();
        // Тут должно быть ожидание загрузки найденной строки
        CustListElement.click();
        CustSelectButton.click();
    }

Допустим я не буду использовать проверку внутри метода и разделю его на простые операции которые буду вызывать последовательно в самом тесте. Но как тогда в самом тесте реализовать ожидание\проверку что нужная строка отобразилась? wait.until(ExpectedConditions.textToBePresentInElement требует чтобы я передавал локатор, но мне нужно передать WebElement или уже сохраненный локатор в переменной.

Буду признателен за помощь, т.к. с java пока не очень разобрался.

You have to wait until NO AJAX activity. You can do it by runing jquery ($.active === 0) with WebDriver-> JavaScriptExecuter until it's true

Откуда эти стереотипы что AJAX=>jQuery?

И такие js скрипты всегда на грани фола - есть вероятность что AJAX запрос просто не успеет создаться после какого-нибудь экшена и вызовом этого скрипта.

Объявил отдельный элемент на выдачу поиска:

@FindBy(css = "div#CustomerID-awepw tr.awe-li:nth-child(1) span")
    private List<WebElement> CustList;

Попробовал переписать метод так. Чтоб если нашел запись ==поисковому запросу, то выбирал её и прерывал цикл:

    public void setCustomer(String s) throws Exception {
        WebDriverWait wait = new WebDriverWait(driver, 10);
        CustButton.click();
        CustSearchField.clear();
        CustSearchField.sendKeys(s);
        CustSearchButton.click();
        M2:for (int second = 0;second < 15; second++)
            {
           // if (second >= 15) fail("timeout");
            for(byte i=0;i<=CustList.size();i++)
                {  
                if(CustList.get(i).getText().equals(s))
                    {
                   CustList.get(i).click(); break M2;
                    }
                }
            Thread.sleep(1000);
            }
        CustSelectButton.click();
       }

Работает, но 1 раз из 10 случаев. Не могу понять в чем ошибка.

При работе с аяксом я бы рекомендовал все же использовать By + различные реализации wait'ов.

    private By locator = By.cssSelector("...");
 
    public WebElement fluentWait(final By locator) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
                .withTimeout(30, TimeUnit.SECONDS)
                .pollingEvery(5, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class);
 
        WebElement element = wait.until(
               new Function<WebDriver, WebElement>() {
                     public WebElement apply(WebDriver driver) {
                            return driver.findElement(locator);
                     }
                });
        
        return element;
    }
    
    WebElement element = fluentWait(locator);

 

Читал про FluentWait, но не пробовал использовать. Подскажите как его теперь добавить в текущий код. На текущем этапе у меня ошибка java.lang.NullPointerException. Наверное из-за того что поместил его не в тот класс. Я так же использую AjaxElementLocatorFactory. Структура у меня такая. Основной класс с тестами testex.java, класс с элементами страницы и методами newReq.java и класс AbstractParentPage.java.

Класс AbstractParentPage.java:

public abstract class AbstractParentPage
{
 
    public int DRIVER_WAIT = 30; // 30 seconds
    private WebDriver driver;
 
    /*
               * Constructor
               * @param driver an instance of WebDriver
               */
    public AbstractParentPage(WebDriver driver)
    {
                 ElementLocatorFactory finder =  new AjaxElementLocatorFactory(driver,DRIVER_WAIT);
                 PageFactory.initElements(finder, this);
                 this.driver = driver;
    }
 
 
            // ------ Add services accessible to all pages here ----
 
    public WebElement fluentWait(final By locator) {
        Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
                .withTimeout(30, TimeUnit.SECONDS)
                .pollingEvery(5, TimeUnit.SECONDS)
                .ignoring(NoSuchElementException.class);
 
        WebElement element = wait.until(
                new Function<WebDriver, WebElement>() {
                    public WebElement apply(WebDriver driver) {
                        return driver.findElement(locator);
                    }
                });
 
        return element;
    }
}

класс newReq.java:

public class newReq extends AbstractParentPage
{
 
    private WebDriver driver;
 
    // ** Окно заказчик 2
    @FindBy(css = "#divCustomer button")
    private WebElement CustButton;
 
    @FindBy(css = "div#CustomerID-awepw [name=search]")
    private WebElement CustSearchField;
 
    @FindBy(css = "div#CustomerID-awepw button[type=submit]")
    private WebElement CustSearchButton;
 
    @FindBy(css = "div[aria-labelledby=\"ui-id-6\"] button[role=button]:nth-child(1)")
    private WebElement CustSelectButton;
 
    @FindBy(css = "div#CustomerID-awepw tr.awe-li:nth-child(1)")
    private WebElement CustListElement;
 
public newReq(WebDriver driver) {
        super(driver);
        ElementLocatorFactory finder =  new AjaxElementLocatorFactory(driver, DRIVER_WAIT);
        PageFactory.initElements(finder, this);
        this.driver = driver;
    }
 
 public void setCustomer(String s) throws Exception {
        CustButton.click();
        CustSearchField.sendKeys(s);
        CustSearchButton.click();
        CustListElement.click(); 
        CustSelectButton.click();
        }
 
}

Как в последнем методе заменить CustListElement.click(); на клик но уже с использованием FluentWait?

Сам тест такой:

@Before
    public void setUp() throws Exception {
 
        /* Local Driver  */
        driver = new InternetExplorerDriver();
        baseUrl = "http://test.url/RequestItem/Create";
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
 
    }
 
    @Test
    public void Test1() throws Exception
    {
        driver.get(baseUrl);
        driver.manage().window().maximize(); // fullscreen
        newReq page = new newReq(driver);
        page.setCustomer("Аптека");
    }

Возможно я где-то намудрил со структурой или операторами, т.к. делаю все по примерам и с Java знаком всего неделю.

fluentWait(CustListElement).click();

- при условии, что CustListElement имеет тип By и драйвер уже инициализирован.

- Если объявляю fluentWait как часть WebElement, то тест не идёт и возникает ошибка, что нельзя найти элемент, хотя на начальном этапе он его и не должен искать, а только при обращении.

Если же использую просто private By xxx = By.cssSelector(""); не указывая потом, что private WebElement Z = fluentWait(xxx); то все работает нормально. Почему так?

- Столкнулся с запросами, когда старый элемент не пропадает, просто через некоторое время они заменяются новой выдачей. Допустим тоже поисковое окно, по умолчанию грузятся какие то записи, при нажатии "поиск", старые записи отображаются пока не будут показаны новые. При использовании fluentWait он сразу жмет на старый элемент. Как правильнее дождаться момента обновления? Можно как-то различить старую выдачу и новую? 

Не надо использовать FluentWait, зачем? Чем не устраивает рекомендованный WebDriverWait?

Посмотрите на его реализацию: https://code.google.com/p/selenium/source/browse/java/client/src/org/openqa/selenium/support/ui/WebDriverWait.java#64

А теперь на код с использованием FluetWait, приведённый несколькими постами выше. Правда очень похоже? Так почему не использовать официально рекомендованный WebDriverWait, а строить свой точно такой же?

Спасибо. Подскажите пожалуйста как подружить WebDriverWait, WebElement и Page Object.

В главном классе я написал метод:

public WebElement waitF (By locator){
    Wait<WebDriver> wait = new WebDriverWait(driver, 10);
    WebElement element = wait.until(ExpectedConditions.presenceOfElementLocated(locator));
    return element;
}

Теперь в классе-потомке в котором у меня описана страница я могу объявить элемент через By xxx = By.cssSelector("…");
А потом в методе использовать waitF(xxx).click(); и всё вроде работает.

Но если я захочу объявить WebElement Z = waitF(xxx); тогда тест не работает, т.к. обращение идёт на этапе объявления элемента, но элемента к тому моменту еще нет на странице. Как быть?
Я так же использую AjaxElementLocatorFactory, может быть из-за этого конфликт возникает?

Еще вопрос по поводу использования WebDriverWait. чтобы его использовать его придётся объявлять в каждом методе каждый раз или написать один метод в главном классе? Получается что если я захочу использовать другое условие, допустим ExpectedConditions.textToBePresentInElement, то придётся написать отдельный метод?

Привет!
Я советую вам попробовать библиотеку Selenide. Она основана на Selenium WebDriver и идеально решает как раз такие проблемы, которые вы описываете. С ней ваш код будет выглядеть примерно так:


SelenideElement CustButton = $("#divCustomer button");
SelenideElement CustSearchField = $("div#CustomerID-awepw [name=search]");
SelenideElement CustSearchButton = $("div#CustomerID-awepw button[type=submit]");
SelenideElement CustSelectButton = $("div[aria-labelledby=\"ui-id-6\"] button[role=button]:nth-child(1)");
SelenideElement CustListElement = $("div#CustomerID-awepw tr.awe-li:nth-child(1)");

Вот метод:


public void setCustomer(String s) {
    CustButton.click();
    CustSearchField.sendKeys(s);
    CustSearchButton.click();
    CustListElement.shouldBe(visible);   // ожидает, пока появится элемент
    // или CustListElement.shouldHave(text("Результаты"));   // ожидает, пока у элемента появится нужный текст
    CustListElement.click();
    CustSelectButton.click();
}

Как видите, код заметно проще, и не нужно заморачиваться со всякими там ожиданиями, FluentWait’ами и подобной хренью. Можно сконцентрироваться на бизнес-логике.

3 лайка