Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 8895

Qiita に「いいね」がついたことを検証する自動UIテスト

$
0
0

Qiita に「いいね」がついたときの喜びとつかないときの悲しみを別の形で味わう試みです。

Webブラウザの自動操作には一般的には Selenium WebDriver を使いますが、好奇心から Appium + WinAppDriver でも挑戦してみました。
期待に違わずはまったものの、薄い爪痕は残しましたので、共有させていただきます。

この記事で得られるかもしれない知見

  • Selenium WebDriver と WinAppDriver のテストコードの差異
  • Selenium WebDriver/WinAppDriver で色を検証する方法
  • WinAppDriver の限界
  • 珍しめ以上アクロバティック以下の小ネタ
  • Microsoft Edge(EdgeHTML/Chrominium )のUIオートメーション対応に関するプチ情報

シナリオ

  1. Qiita にログインします。
  2. 右上の通知ボックスで新着通知の有無を確認します。
  3. 新着通知があったら通知一覧ページを開きます1
  4. 新しい順に10件ずつ表示されるので、新着件数分の内訳を確認します。

期待結果

新たに「いいね」がついていること。

想定パターン

  1. 新着通知がない
  2. 新着通知はあるが、その中に「いいね」はない
  3. 新着通知の中に「いいね」がある

検証環境

テスト実行環境

UI実行環境

テストコード

コードは C# で記述しますが、ほかの言語でも同じロジックで実現できると思います。

Selenium WebDriver 版

Edge で Qiita に自動ログインできるようにしておきます。
実行前に Edge は閉じてください。
開いていると Url設定時に NoSuchWindowExceptionが発生してしまいます。

テキストで検証

[TestMethod]publicvoidSomebodyShouldGiveMeLike_WebDriver_ByText(){conststringQiitaRootUrl="https://qiita.com/";conststringQiitaNotificationsUrl="https://qiita.com/notifications";varedgeDriver=newEdgeDriver();// Qiita にアクセスtry{edgeDriver.Url=QiitaRootUrl;}catch(NoSuchWindowExceptionex){Console.WriteLine(ex.Message);Assert.Fail("Edge を閉じてから再試行してください。");}newWebDriverWait(edgeDriver,TimeSpan.FromSeconds(20)).Until(SeleniumExtras.WaitHelpers.ExpectedConditions.UrlToBe(QiitaRootUrl));// 新着はあるかvarnotificationDiv=edgeDriver.FindElement(By.ClassName("st-Header_notifications"));intnotificationCount=int.Parse(notificationDiv.Text);if(notificationCount==0){Assert.Fail("なぜだろう、誰もいいねをつけていない。");return;}Console.WriteLine($"通知{notificationCount}件、いいねだといいね。");// 新着の中に「いいね」はあるかconstintPageItemCount=10;intnewLikeCount=0;for(intpageIndex=0;pageIndex<=notificationCount/PageItemCount;pageIndex++){stringurl=QiitaNotificationsUrl+$"?page={pageIndex+1}";edgeDriver.Url=url;newWebDriverWait(edgeDriver,TimeSpan.FromSeconds(20)).Until(SeleniumExtras.WaitHelpers.ExpectedConditions.UrlToBe(url));newLikeCount+=edgeDriver.FindElements(By.CssSelector("li.notification")).Take(notificationCount-(pageIndex*PageItemCount)).Count(notification=>{// <span class="bold">いいね</span> を探す。returnnotification.FindElements(By.CssSelector("span.bold")).Any(e=>e.Text=="いいね");});}if(newLikeCount==0){Assert.Fail("いいねではないね。");}Console.WriteLine($"{newLikeCount}件のいいねがついたよ!");}

通知ボックスが赤いからといって「いいね」があるとは限りません。
ボックス内の数字から新着件数を取得し、通知一覧を件数分参照して「いいね」が含まれているかを検証します。

起動したブラウザを検証後に閉じる場合は Driver を CloseDisposeしましょう。

色で検証

新着があるかどうは色でも判定できます。

[TestMethod]publicvoidSomebodyShouldGiveMeLike_WebDriver_ByColor(){conststringQiitaUrl="https://qiita.com/";vararrivalBackgroundArgb=ColorTranslator.FromHtml("#E14B22").ToArgb();varedgeDriver=newEdgeDriver();// Qiita にアクセスtry{edgeDriver.Url=QiitaUrl;}catch(NoSuchWindowExceptionex){Console.WriteLine(ex.Message);Assert.Fail("Edge を閉じてから再試行してください。");}newWebDriverWait(edgeDriver,TimeSpan.FromSeconds(20)).Until(SeleniumExtras.WaitHelpers.ExpectedConditions.UrlToBe(QiitaUrl));// 新着はあるかvarnotificationDiv=edgeDriver.FindElement(By.ClassName("st-Header_notifications"));stringbackgroundColorCssValue=notificationDiv.GetCssValue("background-color");varmatch=Regex.Match(backgroundColorCssValue,@"rgb\((\d+),\s*(\d+),\s*(\d+)\)");Assert.IsTrue(match.Success,$"想定と違う色書式:{backgroundColorCssValue}");varbackgroundColor=Color.FromArgb(int.Parse(match.Groups[1].Value),int.Parse(match.Groups[2].Value),int.Parse(match.Groups[3].Value));if(backgroundColor.ToArgb()!=arrivalBackgroundArgb){Assert.Fail("なぜだろう、誰もいいねをつけていない。");}Console.WriteLine("いいねの赤だといいね。");// 新着の中に「いいね」はあるかは色では判定できません。// 上のテキストによる検証と同じになるので省略します。}

div要素のスタイル background-colorRemoteWebElement.GetCssValueメソッドで参照すると、"rgb(88,29,13)" のような書式で色指定が返ってきます。
ここから正規表現を使ってRGB値をそれぞれ抜き出し、Color構造体オブジェクトに変換しています。
同一色かどうかは ToArgb()した結果の int値で判定します。
理由については別記事『[.NET] コードを見直したくなる「値型」等価判定の思わぬ落とし穴(特殊編)』をご参照ください。


ここまでは普通ですね。
ここからが冒険です。

WinAppDriver 版

一般的には WinAppDriver はデスクトップアプリケーションを扱うためのものであり、Webアプリケーションのテストには Selenium が使用されますが、Microsoft Edge(ここでは EdgeHTML 版を使用)はHTMLドキュメント部分も「UIオートメーション」に対応しており、WinAppDriver で扱うことができます。

※Chrome はHTMLドキュメント部分が「UIオートメーション」に対応しておらず2、WinAppDriver では扱うことができません。

テキストで検証

[TestMethod]publicvoidSomebodyShouldGiveMeLike_WinAppDriver_ByText(){conststringEdgeAppId="Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge";conststringQiitaUrl="https://qiita.com/";conststringQiitaNotificationsUrl="https://qiita.com/notifications";varremoteAddress=newUri("http://127.0.0.1:4723");varedgeCapabilities=newAppiumOptions();edgeCapabilities.AddAdditionalCapability("app",EdgeAppId);varedgeWinDriver=newWindowsDriver<WindowsElement>(remoteAddress,edgeCapabilities);// Qiita にアクセスvaraddressEditBox=edgeWinDriver.FindElementByAccessibilityId("addressEditBox");addressEditBox.Clear();addressEditBox.SendKeys(QiitaUrl);Thread.Sleep(TimeSpan.FromSeconds(2));addressEditBox.SendKeys(Keys.Delete+Keys.Enter);Thread.Sleep(TimeSpan.FromSeconds(5));// 新着はあるかvarqiitaPane=edgeWinDriver.FindElementByXPath($".//Pane[@ClassName=\"Internet Explorer_Server\"][@Name=\"{QiitaUrl}\"]/Pane[@Name=\"Qiita\"]");// [投稿する] の次の要素が通知ボックス// ※ダイレクトに特定するための情報が得られない。//  Inspect.exe で見ると Name が "投稿する" だが、テスト実行時の Text 値は href 属性値だった。//  following-sibling も効かないので全子要素コレクションから Skip 取得。varnotificationGroup=qiitaPane.FindElementsByXPath("*/*").Cast<WindowsElement>().SkipWhile(e=>e.Text!="https://qiita.com/drafts/new").Skip(1).First();intnotificationCount=int.Parse(notificationGroup.FindElementByTagName("Text").Text);if(notificationCount==0){Assert.Fail("なぜだろう、誰もいいねをつけていない。");}Console.WriteLine($"通知{notificationCount}件、いいねだといいね。");// 新着の中に「いいね」はあるかconstintPageItemCount=10;intnewLikeCount=0;// ※USキーボードレイアウトしかサポートされておらず、'=' は '^' に変わってしまうのでASCIIコードで入力。//  Keys.Shift + "-" + Keys.Shift でも '=' になる。stringusKeyboardEqual=Keys.Alt+Keys.NumberPad6+Keys.NumberPad1+Keys.Alt;for(intpageIndex=0;pageIndex<=notificationCount/PageItemCount;pageIndex++){stringpageUrl=$"{QiitaNotificationsUrl}?page{usKeyboardEqual}{pageIndex+1}";addressEditBox.Clear();addressEditBox.SendKeys(pageUrl);Thread.Sleep(TimeSpan.FromSeconds(2));addressEditBox.SendKeys(Keys.Delete+Keys.Enter);Thread.Sleep(TimeSpan.FromSeconds(5));varnotifications=edgeWinDriver.FindElementsByXPath("//Pane[@Name=\"通知一覧 - Qiita\"]/List/ListItem");newLikeCount+=notifications.Take(notificationCount-(pageIndex*PageItemCount)).Count(n=>n.Text.Contains("」 に いいね しました。"));}if(newLikeCount==0){Assert.Fail("いいねではないね。");}Console.WriteLine($"{newLikeCount}件のいいねがついたよ!");}

コメントにあるように、通知ボックスをダイレクトに取得する情報が WindowsElementから得られず、この点に苦労しました。
左隣りの [投稿する] リンクが Textプロパティに収められた href属性値から特定できたので、仕方ありません、そこから相対位置で取得することにしました。
XPath の following-siblingは効かなかったので、親要素から FindElementsByXPath("*/*")で全子要素コレクションを取得し、[投稿する] の次の要素を通知ボックスとして取得しています。

色で検証

Selenium のようにHTMLのスタイルを参照するわけにはいきません。
RemoteWebDriverにスクリーンショットを取得するメソッドが用意されていますので、レンダリングされた色から判定してみましょう。

notify.png

これが RemoteWebDriver.GetScreenshot()メソッドでキャプチャした通知画像です。
2桁とかあるとかっこよかったのですが、これでも1日待ちました。
いつも真っ赤に熟れて見えたのは背景色との対比のせいで(渇望のせいではない)、意外とオレンジなんですね。

いいね色.png

ブラウザのデバッグツールで色指定を確認すると "#E14B22" でした。

ウィンドウのスクリーンショットから通知ボックス部分を抜き出し、そこに "#E14B22" のピクセルが含まれているかどうかを検証します。

[TestMethod]publicvoidSomebodyShouldGiveMeLike_WinAppDriver_ByColor(){conststringEdgeAppId="Microsoft.MicrosoftEdge_8wekyb3d8bbwe!MicrosoftEdge";conststringQiitaUrl="https://qiita.com/";conststringQiitaNewPostHref="https://qiita.com/drafts/new";vararrivalBackgroundArgb=ColorTranslator.FromHtml("#E14B22").ToArgb();varremoteAddress=newUri("http://127.0.0.1:4723");varedgeCapabilities=newAppiumOptions();edgeCapabilities.AddAdditionalCapability("app",EdgeAppId);varedgeWinDriver=newWindowsDriver<WindowsElement>(remoteAddress,edgeCapabilities);// Qiita にアクセスvaraddressEditBox=edgeWinDriver.FindElementByAccessibilityId("addressEditBox");addressEditBox.Clear();addressEditBox.SendKeys(QiitaUrl);Thread.Sleep(TimeSpan.FromSeconds(2));addressEditBox.SendKeys(Keys.Delete+Keys.Enter);Thread.Sleep(TimeSpan.FromSeconds(5));// 新着はあるかvarqiitaPane=edgeWinDriver.FindElementByXPath($"//Pane[@ClassName=\"Internet Explorer_Server\"][@Name=\"{QiitaUrl}\"]/Pane[@Name=\"Qiita\"]");// [投稿する] の次の要素が通知ボックスvarnotificationGroup=qiitaPane.FindElementsByXPath("*/*").Cast<WindowsElement>().SkipWhile(e=>e.Text!="https://qiita.com/drafts/new").Skip(1).First();varnotificationRect=newRectangle(notificationGroup.Location,notificationGroup.Size);varedgeScreen=edgeWinDriver.GetScreenshot();using(varedgeScreenStream=newMemoryStream(edgeScreen.AsByteArray))using(varedgeScreenBitmap=newBitmap(edgeScreenStream))using(varnotificationBitmap=edgeScreenBitmap.Clone(notificationRect,edgeScreenBitmap.PixelFormat)){// 左上から右下に向けて赤を探す(背景が赤なら見つかるはず)boolhasNew=Enumerable.Range(0,Math.Min(notificationBitmap.Width,notificationBitmap.Height)).Any(xy=>notificationBitmap.GetPixel(xy,xy).ToArgb()==arrivalBackgroundArgb);if(!hasNew){Assert.Fail("なぜだろう、誰もいいねをつけていない。");}}Console.WriteLine("いいねの赤だといいね。");// 新着の中に「いいね」はあるかは色では判定できません。// 上のテキストによる検証と同じになるので省略します。}

できましたね。
「いいね」はあったでしょうか。

Microsoft Edge のUIオートメーション対応

Edge(EdgeHTML)は「UIオートメーション」に対応しているので上のように WinAppDriver で扱うことができましたが、Windows フォームなどデスクトップアプリケーションと比べると、Inspect.exe に出てくる Name に Appium + WinAppDriver でアクセスできない、AccessibilityId が提供されないなど、機能的に物足りないところがあり、実装に苦労しました。
あまり実用的とは言えませんね。
HTMLドキュメントの操作には素直に Selenium WebDriver を使用した方がよいでしょう。

なお、レンダリングエンジンが Chrominium に変わった後の Edge でUIオートメーションがサポートされるかどうか、気になるところですが、現時点でベータ版を Inspect.exe で確認したところ、基本的にサポートされているようでした。


いかがでしたかって、こんなことしなくても見ればわかりますよね。
はい、ちょっと面白そうなのでやってみただけです。
実用価値のあるロジックは WebDriver、WinAppDriver のそれぞれ色検証くらいでしょうか。
SendKeysのUSキーボード対策、アドレス欄の入力候補対策など小ネタは挟まっていますので、よかったら参考にしてください(よりスマートな手法のコメントも歓迎します)。


  1. 通知一覧を開いた瞬間に新着の赤い背景は消えてしまいます。自分の手で赤い通知ボックスをクリックしたい方、新着ありの状態を長めに味わいたい方はご注意ください。 

  2. Windows 版 Chrome は IAccessible, IAccessible2対応しているようです
    chrome://accessibility/で開いているページのツリー構造を確認できます。 


Viewing all articles
Browse latest Browse all 8895

Trending Articles