スタック上にSPAアプリケーションがありますASP MVC +AngularJS。UIをテストしたいと思います。今のところPhantomJSとWebKitドライバーでSeleniumを試しています。
これはテストページのサンプルです。単一の要素で表示します。リスト項目_<li>
_はサーバーから動的にロードされ、Angularによって制限されます。
_<div id="items">
<li>text</li>
<li>text2</li>
</div>
_
私はテストに合格しようとしていますが、この行にエラーがあります:
__driver.FindElements(By.TagName('li'))
_
この時点では、ロードされた要素はなく、_driver.PageSourceには要素が含まれていません。
アイテムがロードされるのを待つにはどうすればよいですか? Thread.Sleep()
を提案しないでください
これは、ページの読み込み/ 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;
}
/*");
});*/
次のように、AngularJSを使用するWebサイトが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()));
社内フレームワークを使用して複数のサイトをテストしている場合、同様の問題が発生しました。これらの一部はJQueryを使用しており、一部はAngularJSを使用しています(さらに1つが混在しています)。フレームワークはC#で記述されているため、実行中のJScriptは最小限のチャンクで実行することが重要でした(デバッグ目的)。実際には、上記の回答の多くを取り、それらを一緒にマッシュアップしました(クレジットが@npjohnsであるクレジット)。以下は、私たちがしたことの説明です。
以下は、HTML DOMがロードされた場合にtrue/falseを返します。
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));
AngularJSを使用している場合は、Protractorを使用することをお勧めします。
分度器を使用する場合、httpリクエストの完了を待つwaitForAngular()メソッドを使用できます。言語と実装によっては、要素が表示されるのを待ってから要素を操作することをお勧めします。これは同期言語で表示される場合があります
WebDriverWait wait = new WebDriverWait(webDriver, timeoutInSeconds);
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id<locator>));
または、JSでは、trueを返すまで関数を実行するwaitメソッドを使用できます
browser.wait(function () {
return browser.driver.isElementPresent(elementToFind);
});
便利なコードスニペットのために分度器をマイニングするだけです。この関数は、Angularがページのレンダリングを完了するまでブロックします。これはShahzaib Salimの回答の変形ですが、彼がポーリングしてコールバックを設定していることを除きます。
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);""")
あなたのng-app
。
私は次のコードを実行し、非同期の競合状態の失敗を助けました。
$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;
あなたが言うようにあなたのWebアプリがAngularで実際に作成された場合、エンドツーエンドのテストを行う最良の方法は Protractor を使用することです。
内部的に、Protractorは独自のwaitForAngular
メソッドを使用して、AngularがDOMの変更を完了するまで、Protractorがautomaticallyを待機するようにします。
したがって、通常の場合、テストケースでnever明示的なwait
を記述する必要があります。分度器はそれを行います。
Angular Phonecat チュートリアルを見て、分度器をセットアップする方法を学ぶことができます。
Protractorを真剣に使用する場合は、 pageobjects を採用します。その例が必要な場合は、Angular Phonecatのmy pageオブジェクトテストスイート をご覧ください。
Protractorを使用すると、C#ではなくJavascript(Protractorは実際にNodeに基づいています)でテストを記述しますが、代わりにProtractorがすべての待機を処理します。
Iframeを含み、AnglularJSを使用して開発されたHTMLページに関する私の特定の問題について、次のトリックにより多くの時間を節約できました。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はiframeで何かをマングリングしていたが、ページをロードした直後にドライバーがデフォルトのコンテンツに焦点を当てていると予想するとき、Angular =フレームこれがあなたの一部を助けることを願っています。
全体を分度器に切り替えたくないが、Angularをお勧めします Paul Hammants ngWebDriver (Java)を使用することをお勧めします。分度器ですが、切り替える必要はありません。
アクション(クリック、塗りつぶし、チェックなど)を実行する前にAngular(ngWebDriverのwaitForAngularRequestsToFinish()を使用))待機するアクションクラスを記述することで問題を修正しました。
コードスニペットについては、 この質問に対する私の答え を参照してください
D Sayarの answer に基づいて使用方法を実装しました。そして、それは誰かに役立つかもしれません。そこに記載されているすべてのブール関数を単一のクラスにコピーするだけで、次にPageCallingUtility()メソッドを追加します。このメソッドは内部依存関係を呼び出しています。
通常の使用では、PageCallingUtility()メソッドを直接呼び出す必要があります。
public void PageCallingUtility()
{
if (DomHasLoaded() == true)
{
if (IsJqueryBeingUsed() == true)
{
JqueryHasLoaded();
}
if (AngularIsBeingUsed() == true)
{
AngularHasLoaded();
}
}
}