読者です 読者をやめる 読者になる 読者になる

ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

Selenium + COM でIE対応

IEのテスト自動化をしていて、IEDriverServerが落ちる現象が多発していました。
こんなのですね。
f:id:ishikawa-tatsuya:20160604001847p:plain

ポストバックが発生してその後重たいJavaScriptが動くような処理のトリガをSeleniumで実行したとき、もしくはその直後の操作で発生していました。とは言え、この情報はググってもあんまり見かけなかったし、たまたま今回のテスト対象が相性悪かっただけなのかな・・・。
で、どうにも解決しなかったので、その処理だけCOMでやることにしました。
IEはアウトプロセスCOMなので、Friendly使わなくとも別プロセスから操作できますね。(残念w
このサイトがよくまとまってて参考になりました。ありがとうございました。
IE - ClockAhead 開発Blog
参照させてもらいつつ、差分で書いていきます。

Seleniumで起動したIEを見つける

基本はSeleniumで操作するので、もちろん起動はSeleniumで行います。
なので、起動したIEを見つけてくる必要があります。
ShellWindowsというクラスを使うとIEを列挙することができます。
で、その中からURLを使って検索する。
こんな関数を作っておくと便利ですね。

static InternetExplorer FindIE(string url)
{
    var windows = new ShellWindows();
    InternetExplorer ie = null;
    for (int i = 0; i < windows.Count; i++)
    {
        ie = windows.Item(i) as InternetExplorer;
        if (ie != null && ie.LocationURL == url)
        {
            return ie;
        }
    }
    throw new NotFoundException();
}

これでSeleniumと相互運用できます。

//IWebDriver driver;
var ie = FindIE(driver.Url)

でもこれでは、同一のURLを持ったIEが複数あると破綻しますね。
なので、起動する前にIEを全部終了させます。
ついでにIEDriverServerのゾンビがいたらそれも消します。

Process.GetProcessesByName("IEDriverServer").ToList().ForEach(e => e.Kill());
Process.GetProcessesByName("iexplore").ToList().ForEach(e => e.Kill());

んー、他に特定するいい方法ないかな?知ってる人いたら教えて下さい。

待ち合わせ

C#でIEを自動制御しよう (6) ページの読み込み完了まで待機する - ClockAhead 開発Blog
これは非常に助かりました。InternetExplorerDriverだけ、ページの読み込みを待ってくれないことがあったのですよね。そんな時はこれを併用することで上手くまつことができました。

Script実行

こんな感じでJavaScriptを実行できます。

static void ExecuteScript(IWebDriver driver, string script)
{
    var ie = FindIE(driver.Url);
    var doc = ie.Document as IHTMLDocument2;
    doc.parentWindow.execScript(script);
}

でも、このJavaScript実行はSeleniumと比べて不便ですね。
Seleniumのいいところは、IWebElementをJavaScript内でも使えるところです。

//IWebDriver driver;
//IWebElement element;
driver.ExecuteScript("arguments[0].click()", elemnt);

これができるようにしたいですね。
document.allとsourceIndexを使えば近いことができそうです。
まずはSeleniumを使って、document.all内のインデックスを見つけてきます。

static long FindIndexInAlls(IWebDriver driver, IWebElement element)
{
    return (long)driver.ExecuteScript(
        "var index = arguments[0].sourceIndex;" +
        "for (var i = 0; i < document.all.length; i++) {" +
            "if (document.all[i].sourceIndex == index) {" +
                "return i;" +
            "}" +
        "}" +
        "return -1;"
        , element);
}

そのインデックスを使えば、COMのJavaScript実行からもそのelementにアクセスすることができます。

//IWebDriver driver;
//IWebElement element;
var index = FindIndexInAlls(driver, element);
ExecuteScript(driver, "document.all[" + index + "].click()");

それからSystem.Windows.Forms.SendKeysも使いました。

COM経由の操作ではSendKeysがないのですよね。
SendKeysは理論的には100%ではないので最終手段なのですが、Selenium経由でやってればIEはアクティブになってるし、その最中にPC触らなければ、今のところ確実に成功しています。

element.Click();
System.Windows.Forms.SendKeys.SendWait("{ENTER}");

もちろんラップしてつかってくださいね。

テストシナリオの中にこんな複雑な構文入れるのはNGです。それに「IEだったら」とかいう分岐もなしにしたいですね。なのでドライバレイヤに隠ぺいすることがおすすめです。

問題の処理以外はSeleniumでやる方がいいですね。

ポイントはSeleniumの方が便利なのでIEDriverServer.exeにとどめをさす一撃以外はSeleniumでやるということですね。問題の処理のみCOMの方で実行するということです。
あんまり、この現象聞かないのですが、もし遭遇することがあったら、この辺のテクニックも使ってみてください。