一般的に、UIの自動化されたテストを記述することは非常に困難です。絶え間ない問題があり、そこに何かが読み込まれず、リクエストがそれに到達せず、タイムアウトになりました。 少なくとも100のテストを書いた人なら誰でも私を理解するでしょう。 ここで、ページが単純なHTMLだけでなく、多くの異なるフレームとポップアップウィンドウを含むことを想像してください。 セレンをよく知っていれば、これが何を脅かすかを理解できます。 Seleniumは、フレーム、iframe、または別個のモーダルウィンドウなど、単一のドキュメントのコンテキストでのみ同時に機能します。
多くのjavascriptがある同様のプロジェクトの自動テストを作成するタスクを取得すると、すべてが動的に生成され、多くのiframeとajaxリクエストが生成されます。 セレンを勉強した後、テストを書き始めました。 3ダースのテストの後、私は最初は思っていたほど簡単ではないことに気付きました。 テストコードでSwitchTo()定数を成形することはすでに不可能であり、コードは連続パスタに変わりました。 テストのロジックは、コンテキストの絶え間ない変更によって完全に失われました。 一般に、異なるフレームを操作するときにコンテキストを自動的に切り替える小さなフレームワークを作成することにしました。
すべてのコードはC#で記述されており、NUnit、Autofac、そしてもちろんSeleniumを使用しています。
サイトで許可されたユーザーのアクションをテストする必要があり、ログインフォームがiframeにあるとします。 このテストは次のようになります。
[Test] public void SimpleTest() { var driver = new FirefoxDriver(); // driver.SwitchTo().Frame("frmName"); driver.FindElement(By.CssSelector("input.login")).SendKeys("my_login"); driver.FindElement(By.CssSelector("input.pass")).SendKeys("my_pass"); driver.SwitchTo().DefaultContent(); // // ............. // driver.SwitchTo().Frame("frmName"); driver.FindElement(By.CssSelector("a.logout")).Click(); driver.SwitchTo().DefaultContent(); // . // ............. driver.Close(); }
各テストでSwitchTo()定数をドラッグするのは面倒なので、私は真のレイジープログラマとして、テストを次のようにしました。
// var page = _factory.CreatePage<IVacuumPage>(_driver); // page.Header.Login("my_login", "my_pass"); //
私の意見では、利益は明らかであり、穀物をもみ殻から分離し、平均的なプログラマーが理解できるクリーンで、最も重要な読み取り可能なテストを取得します。
この単純さの背後に隠されているものを見てみましょう。
プロジェクト構造
はい、最終的には、多くのコードはありませんでした。 主なものはDomContainerクラスです。これは、論理的に結合されたページ上の要素の基本クラスになります。 私の意見では、サイトrsdn.ruは、フレームワークのすべての利点を簡単に実証できる非常に良い例です。 RSDNサイトのPageObjectは次のようになります。
public interface IRsdnPage : IDomContainer { IRsdnMenuFrame Menu { get; } IRsdnHeaderFrame Header { get; } IRsdnContentFrame Content { get; } } public class RsdnPage : DomContainer, IRsdnPage { public IRsdnMenuFrame Menu { get; private set; } public IRsdnHeaderFrame Header { get; private set; } public IRsdnContentFrame Content { get; private set; } public RsdnPage(IComponentContext context) : base(context) { } // protected override void Init() { base.Init(); Header = _factory.CreateIframeItem<IRsdnHeaderFrame>(this, Driver.FindElement(By.CssSelector("frame[name='frmTop']"))); Menu = _factory.CreateIframeItem<IRsdnMenuFrame>(this, Driver.FindElement(By.CssSelector("frame[name='frmTree']"))); Content = _factory.CreateIframeItem<IRsdnContentFrame>(this, Driver.FindElement(By.CssSelector("frame[name='frmMain']"))); } }
どのフレームにどの要素が含まれているかを考える必要がないように、ページ構造を作成します。 _factory.CreateIframeItem()を一度指定し、このオブジェクトの任意の要素を参照することにより、フレームワークはドライバーコンテキストを目的のフレームに自動的に切り替えます。
次に、RSDN Webサイトのログインフォームインターフェイスがどのようになるかを検討します。
public interface IRsdnHeaderFrameLoggedOutState : IDomContainer { IWebElementWrapper UserName { get; } IWebElementWrapper Password { get; } IWebElementWrapper LoginButton { get; } void Login(string login, string password); }
ここで重要なのはIWebElementWrapperインターフェイスです。これはIWebElementインターフェイスを繰り返し、コンテキストを自動的に置き換える同じ魔法を少し追加します。 前提条件は、メカニズム全体が正しく機能するように、IWebElementではなくIWebElementWrapperのみを公開することです。
メカニズム自体は非常に簡単に機能し、Castle.DynamicProxyアセンブリに含まれるIInterceptorインターフェイスに基づいています。 Autofacを使用して、次のようにタイプを登録します。
builder.RegisterType<WebElementWrapper>().As<IWebElementWrapper>() .EnableInterfaceInterceptors().InterceptedBy(typeof(ContextInterceptor));
したがって、このような呼び出しスキームでは:
page.Header.LoggedOutState.LoginButton.Click();
インターセプターはClick()呼び出しをインターセプトし、オブジェクトのツリーをヘッダーに移動し、コンテキストを目的のフレームに置き換え、このフレームのコンテキスト内の要素をクリックして、コンテキストを返します。 ただし、テストコードでは、これは自動的に行われるため表示されません。 私たちの目標は達成されたので、今やるべきことは、ページのすべての必要な要素のラッパーを作成し、テストでそれらを使用することです。
RSDNサイトのラッパー
次に、次のことを行うRSDNサイトのテスト例を示します。
- ホームページを開きます
- アカウントにログインします
- 検索ページに移動します
- サイトで検索を実行する
- フォーラムのページに移動します
[Test] public void FirstTest() { // var page = _factory.CreatePage<IRsdnPage>(_driver); // page.Header.Login("***", "***"); // page.Header.GoToSearch(); page.Content.Reload(); // Selenium page.Content.Search("Selenium"); // page.Menu.GoToForums(); }
私の意見では、シンプルで簡潔。 Fluentインターフェイスを使用して柔軟性のある種類のDSLを作成し、他のテストで既存の部分を使用できます。
フレームワークが大規模プロジェクトで機能することを証明するために、継続ビルドのスクリーンショットを示します。
ここに310のテストがあります。
内部でどのように配置されているかを知りたい人のために、プロジェクトをgithubに投稿しました。
ご質問がある場合は、コメントで質問してください、私は答えようとします。