文章目錄
使用 Fluent Automation 與 Selenium 打造語意化 Web UI 測試程式
之前文章 使用 Selenium IDE 與 C# 做 Web UI 測試,介紹了如何使用 Selenium IDE 錄製網頁操作並將過程 export 成 c# 測試程式,雖然很方便但 Selenium api 太像程式碼,一般而言測試案例是用來描述需求,這樣會讓測試案例的可讀性降低,一旦測試的可讀性降低,漸漸的就會造成維護不易,時間一久就愈來愈少人願意維護而造成 code coverage 不增反降,這對於程式碼品質絕對有負面影響。
今天來紀錄一下 91 哥在 twMVC 活動中介紹的 Fluent Automation 套件,看它可以如何讓我們的 Web UI 測試有更好的可讀性
基本設定
案例會持續延用 使用 Selenium IDE 與 C# 做 Web UI 測試,錄製登入 GitHub
主要測試程式如下(如果你懶得錄,可以使用下面的版本,但請記得改帳號跟密碼)
using System; using System.Text; using System.Text.RegularExpressions; using System.Threading; using NUnit.Framework; using OpenQA.Selenium; using OpenQA.Selenium.Firefox; using OpenQA.Selenium.Support.UI; using FluentAutomation; namespace SeleniumTests { [TestFixture] public class Ngithub { private IWebDriver driver; private StringBuilder verificationErrors; private string baseURL; private bool acceptNextAlert = true; [SetUp] public void SetupTest() { driver = new FirefoxDriver(); baseURL = "https://github.com/"; verificationErrors = new StringBuilder(); } [TearDown] public void TeardownTest() { try { driver.Quit(); } catch (Exception) { // Ignore errors if unable to close the browser } Assert.AreEqual("", verificationErrors.ToString()); } [Test] public void TheNgithubTest() { driver.Navigate().GoToUrl(baseURL + "/login"); driver.FindElement(By.Id("login_field")).Clear(); driver.FindElement(By.Id("login_field")).SendKeys("{帳號}"); driver.FindElement(By.Id("password")).Clear(); driver.FindElement(By.Id("password")).SendKeys("{密碼}"); driver.FindElement(By.Name("commit")).Click(); // 這邊要稍等一下,讓 github 完成 redirect Thread.Sleep(1000); //驗證網址是否正確 Assert.AreEqual(baseURL, driver.Url); } private bool IsElementPresent(By by) { try { driver.FindElement(by); return true; } catch (NoSuchElementException) { return false; } } private bool IsAlertPresent() { try { driver.SwitchTo().Alert(); return true; } catch (NoAlertPresentException) { return false; } } private string CloseAlertAndGetItsText() { try { IAlert alert = driver.SwitchTo().Alert(); string alertText = alert.Text; if (acceptNextAlert) { alert.Accept(); } else { alert.Dismiss(); } return alertText; } finally { acceptNextAlert = true; } } } }
安裝 NuGet 套件
FluentAutomation.SeleniumWebDriver
專案上按右鍵 –> Manage NuGet Packages
Browse –> 搜尋
FluentAutomation.SeleniumWebDriver
–> Install會一併安裝其他相關套件
Selenium.WebDriver.ChromeDriver
專案上按右鍵 –> Manage NuGet Packages
Browse –> 搜尋
Selenium.WebDriver.ChromeDriver
–> Install這邊留意一下套件名稱,名稱很容易混洧
這個套件我試了好久才試出來,如果沒裝會出現錯誤訊息
訊息內容
Test Name: Test1 Test FullName: TestVSIXN3Tests.SampleTests.Test1 Test Source: C:\Users\yowko.tsai\Documents\Visual Studio 2017\Projects\TestVSIX\TestVSIXN3Tests\SampleTests.cs : line 24 Test Outcome: Failed Test Duration: 0:00:01.732 Result StackTrace: at FluentAutomation.BaseCommandProvider.<>c__DisplayClass9.<WaitUntil>b__8() at FluentAutomation.BaseCommandProvider.Act(CommandType commandType, Action action) at FluentAutomation.BaseCommandProvider.WaitUntil(Expression`1 conditionAction, TimeSpan timeout) at FluentAutomation.BaseCommandProvider.Act(CommandType commandType, Action action) at FluentAutomation.CommandProvider.Navigate(Uri url) at FluentAutomation.ActionSyntaxProvider.Open(Uri url) at FluentAutomation.ActionSyntaxProvider.Open(String url) at TestVSIXN3Tests.SampleTests.Test1() in C:\Users\yowko.tsai\Documents\Visual Studio 2017\Projects\TestVSIX\TestVSIXN3Tests\SampleTests.cs:line 25 --InvalidOperationException at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response errorResponse) at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters) at OpenQA.Selenium.Remote.RemoteCookieJar.DeleteAllCookies() at FluentAutomation.CommandProvider.<>c__DisplayClass2.<.ctor>b__0() at System.Lazy`1.CreateValue() at System.Lazy`1.LazyInitValue() at System.Lazy`1.get_Value() at FluentAutomation.CommandProvider.get_webDriver() at FluentAutomation.CommandProvider.<>c__DisplayClass5.<Navigate>b__4() at lambda_method(Closure ) at FluentAutomation.BaseCommandProvider.<>c__DisplayClass9.<WaitUntil>b__8() Result Message: FluentAutomation.Exceptions.FluentException : An unexpected exception was thrown inside WaitUntil(Action). See InnerException for details. ----> System.InvalidOperationException : unknown error: Runtime.executionContextCreated has invalid 'context': {"auxData":{"frameId":"9064.1","isDefault":true},"id":1,"name":"","origin":"://"} (Session info: chrome=58.0.3029.110) (Driver info: chromedriver=2.9.248315,platform=Windows NT 6.1 SP1 x86_64)
錯誤截圖
如何使用
引用
FluentAutomation
using FluentAutomation;
測試 class 繼承
FluentTest
public class Ngithub : FluentTest
建構式中指定使用的瀏覽器
SeleniumWebDriver.Bootstrap(SeleniumWebDriver.Browser.Firefox);
可指定多個瀏覽器
使用語意化語法
I.Open(baseURL+"login") .Enter("{帳號}") .In("#login_field") .Enter("{密碼}") .In("#password") .Click("input[name='commit']") .Wait(1).Assert.Url(baseURL);
完整程式
using NUnit.Framework; using FluentAutomation; namespace SeleniumTests { [TestFixture] public class Ngithub : FluentTest { private string baseURL; public Ngithub() { baseURL = "https://github.com/"; SeleniumWebDriver.Bootstrap( SeleniumWebDriver.Browser.Firefox ); } [Test] public void TheNgithubTest() { I.Open(baseURL+"login") .Enter("{帳號}}").In("#login_field") .Enter("{密碼}").In("#password") .Click("input[name='commit']") .Wait(1) .Assert.Url(baseURL); } } }
其他驗證方式
驗證在 selector 中是否有特定 css class
I.Assert.Class("{class name}").Of("{selector}");
驗證特定的 selector 數量
I.Assert.Count(1).Of("{selector}");
驗證頁面上是否有特定 selector
I.Assert.Exists("{selector}");
驗證匿名函式結果是否為 false
var element = I.Find("input"); I.Assert.False(() => element().IsSelect);
驗證 selector 內容是否為指定文字
可以用於有
innerHTML
或是可以提供文字
值的 DOM element 上I.Assert.Text("FluentAutomation").In("{selector}");
可以用來驗證回傳
true
|false
的匿名函式I.Assert.Text((text) => text.Length > 50).In("{selector}");
驗證匿名函數應該出現 exception
用來做反向驗證 –> 驗證某個 selector 不該存在
//登入成功沒有這個 div 會 pass ,登入失敗出現這個 div 會 fail I.Assert.Throws(() => I.Assert.Exists("#js-flash-container .flash.flash-full.flash-error .container"));
驗證
value
是否合乎預期可以使用 selector 選定的
<INPUT>
、<TEXTAREA>
、<SELECT>
I.Assert.Value(10).In("{selector}");
可以用來驗證回傳
true
|false
的匿名函式I.Assert.Value((value) => value.StartsWith("M")).In("{selector}");
驗證匿名函數結果應該為
true
var element = I.Find("select"); I.Assert.True(() => element().IsSelect);
驗證瀏覽器是否有 url
可以驗證
string
或是Uri
I.Assert.Url(url);
可以用來驗證回傳
true
|false
的匿名函式I.Assert.Url((uri) => uri.Scheme == "https");
心得
Fluent Automation 從 2015 至今都沒有更新紀錄,而且連 Fluent Automation 的官網已經失效了:http://fluent.stirno.com,幸好 GitHub 上還有相關資訊,官方文件比較不好找 文件可以參考 FluentAutomation Docs,雖然使用上便利性稍差但功能本身非常好用,前後比較一下使用 Fluent Automation 是不是覺得意圖一目瞭然,可讀性超高呀?!
參考資訊
文章作者 Yowko Tsai
上次更新 2021-11-03
授權合約
本部落格 (Yowko's Notes) 所有的文章內容(包含圖片),任何轉載行為,必須通知並獲本部落格作者 (Yowko Tsai) 的同意始得轉載,且轉載皆須註明出處與作者。
Yowko's Notes 由 Yowko Tsai 製作,以創用CC 姓名標示-非商業性-相同方式分享 3.0 台灣 授權條款 釋出。