Тестирование AngularJS с селеном

У меня есть приложение SPA в стеке ASP MVC + AngularJS, и я хотел бы проверить пользовательский интерфейс. Сейчас я пробую Selenium с драйверами PhantomJS и WebKit.

Это пример страницы тестирования - просмотр с одним элементом. Элементы списка <li> загружаются динамически с сервера и ограничены Angular.

<div id="items">
    <li>text</li>
    <li>text2</li>
</div>

Я пытаюсь пройти тест, и в этой строке есть ошибка:

_driver.FindElements(By.TagName('li'))

На данный момент нет загруженных элементов и _driver.PageSource не содержит элементов.

Как я могу дождаться загрузки товаров? Пожалуйста, не предлагайте Thread.Sleep()

Ответ 1

Это будет ждать загрузки страницы /jquery.ajax(если есть) и $http-звонков, а также любого сопровождающего цикла дайджест/рендеринг, бросить его в функции утилиты и подождать.

/* C# Example
 var pageLoadWait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(timeout));
            pageLoadWait.Until<bool>(
                (driver) =>
                {
                    return (bool)JS.ExecuteScript(
@"*/
try {
  if (document.readyState !== 'complete') {
    return false; // Page not loaded yet
  }
  if (window.jQuery) {
    if (window.jQuery.active) {
      return false;
    } else if (window.jQuery.ajax && window.jQuery.ajax.active) {
      return false;
    }
  }
  if (window.angular) {
    if (!window.qa) {
      // Used to track the render cycle finish after loading is complete
      window.qa = {
        doneRendering: false
      };
    }
    // Get the angular injector for this app (change element if necessary)
    var injector = window.angular.element('body').injector();
    // Store providers to use for these checks
    var $rootScope = injector.get('$rootScope');
    var $http = injector.get('$http');
    var $timeout = injector.get('$timeout');
    // Check if digest
    if ($rootScope.$$phase === '$apply' || $rootScope.$$phase === '$digest' || $http.pendingRequests.length !== 0) {
      window.qa.doneRendering = false;
      return false; // Angular digesting or loading data
    }
    if (!window.qa.doneRendering) {
      // Set timeout to mark angular rendering as finished
      $timeout(function() {
        window.qa.doneRendering = true;
      }, 0);
      return false;
    }
  }
  return true;
} catch (ex) {
  return false;
}
/*");
});*/

Ответ 2

Создайте новый класс, который позволит вам выяснить, завершил ли ваш сайт использование AngularJS вызовом AJAX следующим образом:

import org.openqa.selenium.JavascriptExecutor;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.support.ui.ExpectedCondition;

public class AdditionalConditions {
    public static ExpectedCondition<Boolean> angularHasFinishedProcessing() {
        return new ExpectedCondition<Boolean>() {
            @Override
            public Boolean apply(WebDriver driver) {
                return Boolean.valueOf(((JavascriptExecutor) driver).executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)").toString());
            }
        };
    }
}

Вы можете использовать его в любом месте своего кода, используя следующий код:

WebDriverWait wait = new WebDriverWait(getDriver(), 15, 100);
wait.until(AdditionalConditions.angularHasFinishedProcessing()));

Ответ 3

У нас была аналогичная проблема, когда наша внутренняя структура используется для тестирования нескольких сайтов, некоторые из них используют JQuery, а некоторые используют AngularJS (и у 1 даже есть смесь!). Наша структура написана на С#, поэтому важно, чтобы любой исполняемый JScript выполнялся в минимальных фрагментах (для целей отладки). На самом деле это взяло много ответов и вытолкнуло их вместе (так что кредит, где кредит должен быть @npjohns). Ниже приводится объяснение того, что мы сделали:

Ниже возвращается значение true/false, если загружен HTML DOM:

        public bool DomHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
    {

        var hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
        while (hasThePageLoaded == null || ((string)hasThePageLoaded != "complete" && timeout > 0))
        {
            Thread.Sleep(100);
            timeout--;
            hasThePageLoaded = jsExecutor.ExecuteScript("return document.readyState");
            if (timeout != 0) continue;
            Console.WriteLine("The page has not loaded successfully in the time provided.");
            return false;
        }
        return true;
    }

Затем мы проверяем, используется ли JQuery:

public bool IsJqueryBeingUsed(IJavaScriptExecutor jsExecutor)
    {
        var isTheSiteUsingJQuery = jsExecutor.ExecuteScript("return window.jQuery != undefined");
        return (bool)isTheSiteUsingJQuery;
    }

Если JQuery используется, мы проверяем, что он загружен:

public bool JqueryHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
        {
                var hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
                while (hasTheJQueryLoaded == null || (!(bool) hasTheJQueryLoaded && timeout > 0))
                {
                    Thread.Sleep(100);
                timeout--;
                    hasTheJQueryLoaded = jsExecutor.ExecuteScript("jQuery.active === 0");
                    if (timeout != 0) continue;
                    Console.WriteLine(
                        "JQuery is being used by the site but has failed to successfully load.");
                    return false;
                }
                return (bool) hasTheJQueryLoaded;
        }

Затем мы сделаем то же самое для AngularJS:

    public bool AngularIsBeingUsed(IJavaScriptExecutor jsExecutor)
    {
        string UsingAngular = @"if (window.angular){
        return true;
        }";            
        var isTheSiteUsingAngular = jsExecutor.ExecuteScript(UsingAngular);
        return (bool) isTheSiteUsingAngular;
    }

Если он используется, мы проверяем его загрузку:

public bool AngularHasLoaded(IJavaScriptExecutor jsExecutor, int timeout = 5)
        {
    string HasAngularLoaded =
        @"return (window.angular !== undefined) && (angular.element(document.body).injector() !== undefined) && (angular.element(document.body).injector().get('$http').pendingRequests.length === 0)";            
    var hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
                while (hasTheAngularLoaded == null || (!(bool)hasTheAngularLoaded && timeout > 0))
                {
                    Thread.Sleep(100);
                    timeout--;
                    hasTheAngularLoaded = jsExecutor.ExecuteScript(HasAngularLoaded);
                    if (timeout != 0) continue;
                    Console.WriteLine(
                        "Angular is being used by the site but has failed to successfully load.");
                    return false;

                }
                return (bool)hasTheAngularLoaded;
        }

После проверки того, что DOM успешно загружен, вы можете использовать эти значения bool для выполнения пользовательских ожиданий:

    var jquery = !IsJqueryBeingUsed(javascript) || wait.Until(x => JQueryHasLoaded(javascript));
    var angular = !AngularIsBeingUsed(javascript) || wait.Until(x => AngularHasLoaded(javascript));

Ответ 4

Если вы используете AngularJS, то использование Protractor - хорошая идея.

Если вы используете транспортир, вы можете использовать метод waitForAngular(), который будет ждать завершения HTTP-запросов. По-прежнему хорошей практикой ждать появления элементов перед их действием, в зависимости от вашего языка и реализации, это может выглядеть на синхронном языке.

WebDriverWait wait = new WebDriverWait(webDriver, timeoutInSeconds);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id<locator>));

Или в JS вы можете использовать метод wait, который выполняет функцию до тех пор, пока не вернет true

browser.wait(function () {
    return browser.driver.isElementPresent(elementToFind);
});

Ответ 5

Я сделал следующий код, и это помогло мне в случае сбоев состояния асинхронной гонки.

$window._docReady = function () {
        var phase = $scope.$root.$$phase;
        return $http.pendingRequests.length === 0 && phase !== '$apply' && phase !== '$digest';
    }

Теперь в модели Selenium PageObject вы можете подождать

Object result = ((RemoteWebDriver) driver).executeScript("return _docReady();");
                    return result == null ? false : (Boolean) result;

Ответ 6

Вы можете просто использовать транспортир для полезных фрагментов кода. Эта функция блокируется до тех пор, пока Angular не сделает рендеринг страницы. Это вариант ответа Шахзаиба Салима, за исключением того, что он делает опрос, и я настраиваю обратный вызов.

def wait_for_angular(self, selenium):
    self.selenium.set_script_timeout(10)
    self.selenium.execute_async_script("""
        callback = arguments[arguments.length - 1];
        angular.element('html').injector().get('$browser').notifyWhenNoOutstandingRequests(callback);""")

Замените "html" на любой элемент вашего ng-app.

Он исходит из https://github.com/angular/protractor/blob/71532f055c720b533fbf9dab2b3100b657966da6/lib/clientsidescripts.js#L51

Ответ 7

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

Внутри, Protractor использует свой собственный метод waitForAngular, чтобы гарантировать, что Protractor ждет автоматически до тех пор, пока Angular не закончит модификацию DOM.

Таким образом, в нормальном случае вам никогда не понадобится писать явный wait в ваших тестовых случаях: Protractor делает это для вас.

Вы можете посмотреть учебник Angular Phonecat, чтобы узнать, как настроить транспортир.

Если вы хотите серьезно использовать Protractor, вам нужно будет принять pageobjects. Если вам нужен пример этого, посмотрите на объект для тестирования объектов для Angular Phonecat.

С помощью Protractor вы пишете свои тесты в Javascript (Protractor действительно основан на Node), а не на С#, но в ответ Protractor обрабатывает все, ожидающие вас.

Ответ 8

Для моей конкретной проблемы с HTML-страницей, содержащей iframes и разработанной с помощью AnglularJS, следующий трюк сэкономил мне много времени: В DOM я ясно видел, что есть iframe, который обертывает весь контент. Поэтому следующий код должен работать:

driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");

Но он не работал и сказал мне что-то вроде "Не могу переключиться на фрейм. Окно было закрыто". Затем я изменил код на:

driver.switchTo().defaultContent();
driver.switchTo().frame(0);
waitUntilVisibleByXPath("//h2[contains(text(), 'Creative chooser')]");

После этого все прошло гладко. Таким образом, очевидно, что Angular искал что-то с iframes и сразу после загрузки страницы, когда вы ожидаете, что драйвер ориентирован на контент по умолчанию, он был сфокусирован на некоторых, уже снятых кадром Angular. Надеюсь, это поможет некоторым из вас.

Ответ 9

Если вы не хотите, чтобы весь коммутатор работал в Protractor, но вы хотите дождаться Angular, я рекомендую использовать Paul Hammants ngWebDriver (Java). Он основан на транспортире, но вам не нужно переключаться.

Я исправил проблему, написав класс действий, в котором я ждал Angular (используя ngWebDriver waitForAngularRequestsToFinish()) перед выполнением действий (щелчок, заполнение, проверка и т.д.).

Для фрагмента кода см. мой ответ на этот вопрос

Ответ 10

Я реализовал использование на основе ответа D Sayar, и это может быть полезно для кого-то. Вам просто нужно скопировать все упомянутые там логические функции в один класс, а затем добавить метод PageCallingUtility() ниже. Этот метод вызывает внутреннюю зависимость.

При обычном использовании вам нужно напрямую вызывать метод PageCallingUtility().

public void PageCallingUtility()
{
    if (DomHasLoaded() == true)
    {
        if (IsJqueryBeingUsed() == true)
        {
            JqueryHasLoaded();
        }

        if (AngularIsBeingUsed() == true)
        {
            AngularHasLoaded();
        }
    }
}

Ответ 11

Кроме eddiec, предположим. Если вы протестируете приложение AngularJS, я настоятельно рекомендую вам подумать о транспортирторе

Транспортир поможет вам решить вопрос ожидания (sync, async). Однако есть некоторые примечания

1 - вам нужно разработать свой тест в javascript

2 - Существует несколько разных механизмов обработки потока

Ответ 12

Вот пример того, как ждать Angular, если вы используете WebDriverJS. Первоначально я думал, что вам нужно создать настраиваемое условие, но wait принимает любую функцию.

// Wait for Angular to Finish
function angularReady(): any  {
  return $browser.executeScript("return (window.angular !== undefined) && (angular.element(document).injector() !== undefined) && (angular.element(document).injector().get('$http').pendingRequests.length === 0)")
     .then(function(angularIsReady) {                        
                    return angularIsReady === true;
                  });
}

$browser.wait(angularReady, 5000).then(...);

К сожалению, это не работает с PhantomJS из-за CSP (политика безопасности контента) и unsafe-eval. Невозможно дождаться безголового Chrome 59 в Windows.