创建混合测试自动化框架——接口契约





5.00/5 (2投票s)
构建一个混合测试自动化框架。了解如何根据 SOLID 原则创建其抽象 Selenium WebDriver 实现。
引言
伙计们,我非常高兴地宣布推出一个新的博客文章系列——设计与架构。其主要思想是,我将展示您可以为测试自动化项目带来的更抽象、更具前瞻性的改进,这些改进不依赖于所使用的测试自动化框架,例如 WebDriver 或测试框架。设计与架构系列的第一篇文章将致力于创建混合测试自动化框架。通过这种类型的测试自动化框架,您可以通过不同的测试自动化框架快速执行测试,而无需更改一行代码,只需使用配置开关即可。
正如您可能猜测的,创建混合测试自动化框架并非易事。需要编写大量代码,因此我无法在单个帖子中解释所有内容。因此,我将以最佳方式在逻辑上分离内容。在这第一篇文章中,我将向您解释如何创建核心接口契约,您的测试页面和测试将使用这些契约,以便它们不依赖于具体的实现,同时遵循最佳实践和SOLID 原则。
混合测试自动化框架——接口
主要 IDriver 接口
这是您将在代码中使用的主要接口。正如您从下面的行中可以发现的,它不包含任何方法,它只继承了几个其他重要的契约。这背后的主要思想是遵循接口隔离 SOLID 原则。如果您需要查找元素,您将使用IElementFinder
接口;如果您需要更新 cookie,您将使用ICookieService
等等。该原则指出,任何客户端都不应被迫依赖它不使用的方法,因此我们将大接口分成几个更小的逻辑上分离的部分。
public interface IDriver :
IElementFinder,
INavigationService,
ICookieService,
IDialogService,
IJavaScriptInvoker,
IBrowser
{
}
IElementFinder 接口
public interface IElementFinder
{
TElement Find<TElement>(By by) where TElement : class, IElement;
IEnumerable<TElement> FindAll<TElement>(By by) where TElement : class, IElement;
bool IsElementPresent(Core.By by);
}
IElementFinder
契约包含用于在页面上定位元素的方法。此外,它还包含用于检查元素是否存在的逻辑。
这些方法返回IElement
接口,该接口表示一个基本的 HTML 页面元素。
public interface IElement : IElementFinder
{
string GetAttribute(string name);
void WaitForExists();
void WaitForNotExists();
void Click();
void MouseClick();
bool IsVisible { get; }
int Width { get; }
string CssClass { get; }
string Content { get; }
}
为了支持在其他容器项(如DIV
)中搜索元素,IElement
继承自IElementFinder
接口。所有不同的控件都将继承自IElement
契约。您将在本系列的后续文章中找到更多相关信息。
与 WebDriver 实现类似,我们有一个abstract static
类By
,用于设置元素的定位策略。
public class By
{
public By(SearchType type, string value) :
this(type, value, null)
{
}
public SearchType Type { get; private set; }
public string Value { get; private set; }
public static By Id(string id)
{
return new By(SearchType.Id, id);
}
public static By InnerTextContains(string innerText)
{
return new By(SearchType.InnerTextContains, innerText);
}
public static By Xpath(string xpath)
{
return new By(SearchType.XPath, xpath);
}
public static By Id(string id, IElement parentElement)
{
return new By(SearchType.Id, id, parentElement);
}
public static By CssClass(string cssClass)
{
return new By(SearchType.CssClass, cssClass);
}
public static By Name(string name)
{
return new By(SearchType.Name, name);
}
}
IJavaScriptInvoker 接口
它包含 JavaScript 执行的逻辑。
public interface IJavaScriptInvoker
{
string InvokeScript(string script);
}
INavigationService 接口
INavigationService
接口有几个关于导航的方法,例如通过相对 URL 或绝对 URL 导航。此外,它还包含等待特定 URL 的逻辑。
public interface INavigationService
{
event EventHandler<PageEventArgs> Navigated;
string Url { get; }
string Title { get; }
void Navigate(string relativeUrl, string currentLocation, bool sslEnabled = false);
void NavigateByAbsoluteUrl(string absoluteUrl, bool useDecodedUrl = true);
void Navigate(string currentLocation, bool sslEnabled = false);
void WaitForUrl(string url);
void WaitForPartialUrl(string url);
}
IDialogService 接口
通过它,您可以处理不同的对话框。
public interface IDialogService
{
void Handle(System.Action action = null, DialogButton dialogButton = DialogButton.OK);
void HandleLogonDialog(string userName, string password);
void Upload(string filePath);
}
IBrowser 接口
这是最重要的接口之一,是主要IDriver
接口的一部分。通过IBrowser
契约,您可以执行浏览器特定的操作,例如切换框架、刷新、点击后退/前进按钮等等。
public interface IBrowser
{
BrowserSettings BrowserSettings { get; }
string SourceString { get; }
void SwitchToFrame(IFrame newContainer);
IFrame GetFrameByName(string frameName);
void SwitchToDefault();
void Quit();
void WaitForAjax();
void WaitUntilReady();
void FullWaitUntilReady();
void RefreshDomTree();
void ClickBackButton();
void ClickForwardButton();
void LaunchNewBrowser();
void MaximizeBrowserWindow();
void ClickRefresh();
}
混合测试自动化框架在测试中
这将是您的混合测试自动化框架所有页面的基页外观。
混合测试自动化框架基页
public abstract class BasePage
{
private readonly IElementFinder elementFinder;
private readonly INavigationService navigationService;
public BasePage(IElementFinder elementFinder, INavigationService navigationService)
{
this.elementFinder = elementFinder;
this.navigationService = navigationService;
}
protected IElementFinder ElementFinder
{
get
{
return this.elementFinder;
}
}
protected INavigationService NavigationService
{
get
{
return this.navigationService;
}
}
}
如您所见,基页不需要IDriver
契约的所有接口。大多数页面只需要一种查找元素和导航的方法。
非混合基页
为了查看差异,您可以在下面找到非混合版本的BasePage
类的代码。
public abstract class BasePage
{
protected IWebDriver driver;
public BasePage(IWebDriver driver)
{
this.driver = driver;
}
public abstract string Url { get; }
public virtual void Open(string part = "")
{
this.driver.Navigate().GoToUrl(string.Concat(this.Url, part));
}
}
如您所见,我们传递了整个IWebDriver
接口。然而,我们通常不需要它公开的所有方法。
混合页面对象
public partial class BingMainPage : BasePage
{
public BingMainPage(
IElementFinder elementFinder,
INavigationService navigationService)
: base(elementFinder, navigationService)
{
}
public void Navigate()
{
this.NavigationService.NavigateByAbsoluteUrl(@"http://www.bing.com/");
}
public void Search(string textToType)
{
// It is going to be implemented in the next article.
////this.SearchBox.Clear();
////this.SearchBox.SendKeys(textToType);
this.GoButton.Click();
}
public int GetResultsCount()
{
int resultsCount = default(int);
resultsCount = int.Parse(this.ResultsCountDiv.Content);
return resultsCount;
}
与基页类似,这里我们只传递抽象混合测试自动化框架的契约。此外,我们还需要手动实现Navigate
方法。
非混合页面对象
public partial class BingMainPage : BasePage
{
public BingMainPage(IWebDriver driver) : base(driver)
{
}
public override string Url
{
get
{
return @"http://www.bing.com/";
}
}
public void Search(string textToType)
{
this.SearchBox.Clear();
this.SearchBox.SendKeys(textToType);
this.GoButton.Click();
}
public int GetResultsCount()
{
int resultsCount = default(int);
resultsCount = int.Parse(this.ResultsCountDiv.Text);
return resultsCount;
}
}
与混合版本的唯一区别是BingMainPage
与具体的IWebDriver
实现耦合。
混合页面映射
public partial class BingMainPage
{
public IElement SearchBox
{
get
{
return this.ElementFinder.Find<IElement>(By.Id("sb_form_q"));
}
}
public IElement GoButton
{
get
{
return this.ElementFinder.Find<IElement>(By.Id("sb_form_go"));
}
}
public IElement ResultsCountDiv
{
get
{
return this.ElementFinder.Find<IElement>(By.Id("b_tween"));
}
}
}
这里,我使用了页面对象模式的改进版本——元素映射被实现为主要页面对象类的部分类。我们使用来自BasePage
类的ElementFinder
属性来定位不同的元素。正如您可能已经注意到的,不同的属性返回IElement
接口,因此映射不与控件的具体实现耦合。
非混合页面映射
public partial class BingMainPage : BasePage
{
public IWebElement SearchBox
{
get
{
return this.driver.FindElement(By.Id("sb_form_q"));
}
}
public IWebElement GoButton
{
get
{
return this.driver.FindElement(By.Id("sb_form_go"));
}
}
public IWebElement ResultsCountDiv
{
get
{
return this.driver.FindElement(By.Id("b_tween"));
}
}
}
与页面对象类类似,非混合元素映射与WebDriver
的具体实现耦合。
混合测试自动化框架测试示例
[TestClass]
public class BingTests
{
private BingMainPage bingMainPage;
private IDriver driver;
[TestInitialize]
public void SetupTest()
{
this.driver = new SeleniumDriver();
this.bingMainPage = new BingMainPage(this.driver, this.driver);
}
[TestCleanup]
public void TeardownTest()
{
this.driver.Quit();
}
[TestMethod]
public void SearchForAutomateThePlanet()
{
this.bingMainPage.Navigate();
this.bingMainPage.Search("Automate The Planet");
this.bingMainPage.AssertResultsCountIsAsExpected(264);
}
}
从这两个示例中可以看出,代码几乎相同,唯一的区别是如果将另一个具体实现分配给IDriver
接口变量,则在第一个版本中可以切换我们的测试自动化框架。
非混合测试自动化框架测试示例
[TestClass]
public class BingTests
{
private BingMainPage bingMainPage;
private IWebDriver driver;
[TestInitialize]
public void SetupTest()
{
this.driver = new FirefoxDriver();
this.bingMainPage = new BingMainPage(this.driver);
}
[TestCleanup]
public void TeardownTest()
{
this.driver.Quit();
}
[TestMethod]
public void SearchForAutomateThePlanet()
{
this.bingMainPage.Open();
this.bingMainPage.Search("Automate The Planet");
this.bingMainPage.AssertResultsCountIsAsExpected(264);
}
}
引言
这是设计与架构系列的最新文章。在该系列的第一篇文章中,我向您展示了如何基于abstract
类创建用于查找元素的通用接口。然而,我们可以进一步扩展这个想法。在这里,我将向您展示如何为ElementFinder
接口创建扩展,以便您可以用更少的代码和更复杂的定位器来定位元素。
混合测试框架——创建高级元素 - 查找扩展
ElementFinder 的基本实现
下面,您可以找到IElementFinder
接口的基本实现。您可以通过By
定位器配置通用Find
方法来定位 Web 元素。然而,我认为定位单个元素需要编写太多代码。我将向您展示如何创建不需要By
配置甚至包含更高级定位器的Find
方法。
public partial class SeleniumDriver : IElementFinder
{
public TElement Find<TElement>(Core.By by)
where TElement : class, Core.Controls.IElement
{
return this.elementFinderService.Find<TElement>(this.driver, by);
}
public IEnumerable<TElement> FindAll<TElement>(Core.By by)
where TElement : class, Core.Controls.IElement
{
return this.elementFinderService.FindAll<TElement>(this.driver, by);
}
public bool IsElementPresent(Core.By by)
{
return this.elementFinderService.IsElementPresent(this.driver, by);
}
}
基本 By
By
类的基本实现只包括最重要的定位器,例如按ID
、类、CSS、链接文本和标签名查找。大多数情况下,在我的测试中,我使用更复杂的定位器策略,例如按 ID 结尾、ID 包含、XPath、XPath 包含等。所以我认为拥有这些定位器很有用。
public class By
{
public By(SearchType type, string value) : this(type, value, null)
{
}
public By(SearchType type, string value, IElement parent)
{
this.Type = type;
this.Value = value;
this.Parent = parent;
}
public SearchType Type { get; private set; }
public string Value { get; private set; }
public IElement Parent { get; private set; }
public static By Id(string id)
{
return new By(SearchType.Id, id);
}
public static By Id(string id, IElement parentElement)
{
return new By(SearchType.Id, id, parentElement);
}
public static By LinkText(string linkText)
{
return new By(SearchType.LinkText, linkText);
}
public static By CssClass(string cssClass, IElement parentElement)
{
return new By(SearchType.CssClass, cssClass, parentElement);
}
public static By Tag(string tagName)
{
return new By(SearchType.Tag, tagName);
}
public static By Tag(string tagName, IElement parentElement)
{
return new By(SearchType.Tag, tagName, parentElement);
}
public static By CssSelector(string cssSelector)
{
return new By(SearchType.CssSelector, cssSelector);
}
public static By CssSelector(string cssSelector, IElement parentElement)
{
return new By(SearchType.CssSelector, cssSelector, parentElement);
}
public static By Name(string name)
{
return new By(SearchType.Name, name);
}
public static By Name(string name, IElement parentElement)
{
return new By(SearchType.Name, name, parentElement);
}
}
高级 By
大多数人可能不需要使用更高级的定位器,所以我将它们放在By
类的一个专用子类中,名为AdvancedBy
。当然,您需要在SearchType enum
中添加新的定位器策略。
public class AdvancedBy : By
{
public AdvancedBy(SearchType type, string value, IElement parent)
: base(type, value, parent)
{
}
public static By IdEndingWith(string id)
{
return new By(SearchType.IdEndingWith, id);
}
public static By ValueEndingWith(string valueEndingWith)
{
return new By(SearchType.ValueEndingWith, valueEndingWith);
}
public static By Xpath(string xpath)
{
return new By(SearchType.XPath, xpath);
}
public static By LinkTextContaining(string linkTextContaing)
{
return new By(SearchType.LinkTextContaining, linkTextContaing);
}
public static By CssClass(string cssClass)
{
return new By(SearchType.CssClass, cssClass);
}
public static By CssClassContaining(string cssClassContaining)
{
return new By(SearchType.CssClassContaining, cssClassContaining);
}
public static By InnerTextContains(string innerText)
{
return new By(SearchType.InnerTextContains, innerText);
}
public static By NameEndingWith(string name)
{
return new By(SearchType.NameEndingWith, name);
}
public static By XPathContaining(string xpath)
{
return new By(SearchType.XPathContaining, xpath);
}
public static By IdContaining(string id)
{
return new By(SearchType.IdContaining, id);
}
}
ElementFinder 扩展 - AdvancedElementFinder
我决定增强基本ElementFinder
的最佳方法是为其创建扩展方法。您可以进一步扩展这个想法,并将包含扩展方法的类放在一个专用项目中,这样只有当有人需要它们时才添加引用。Selenium.WebDriver.Support
NuGet 的工作方式相同。我们为每个新的高级本地化策略创建一个额外的方法。
public static class AdvancedElementFinder
{
public static TElement FindByIdEndingWith<TElement>(
this IElementFinder finder, string idEnding)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.IdEndingWith(idEnding));
}
public static TElement FindByIdContaining<TElement>(
this IElementFinder finder, string idContaining)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.IdContaining(idContaining));
}
public static TElement FindByValueEndingWith<TElement>(
this IElementFinder finder, string valueEnding)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.ValueEndingWith(valueEnding));
}
public static TElement FindByXpath<TElement>(
this IElementFinder finder, string xpath)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.Xpath(xpath));
}
public static TElement FindByLinkTextContaining<TElement>(
this IElementFinder finder, string linkTextContaining)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.LinkTextContaining(linkTextContaining));
}
public static TElement FindByClass<TElement>(
this IElementFinder finder, string cssClass)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.CssClass(cssClass));
}
public static TElement FindByClassContaining<TElement>(
this IElementFinder finder, string cssClassContaining)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.CssClassContaining(cssClassContaining));
}
public static TElement FindByInnerTextContaining<TElement>(
this IElementFinder finder, string innerText)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.InnerTextContains(innerText));
}
public static TElement FindByNameEndingWith<TElement>(
this IElementFinder finder, string name)
where TElement : class, IElement
{
return finder.Find<TElement>(AdvancedBy.NameEndingWith(name));
}
}
测试中的高级元素查找扩展
页面对象映射
为了能够使用新的高级定位器方法,您只需要向其命名空间添加一个using
语句。之后,您将能够通过 Visual Studio 的 IntelliSense 在它们之间进行选择。
public partial class BingMainPage
{
public ITextBox SearchBox
{
get
{
////return this.ElementFinder.Find<ITextBox>(By.Id("sb_form_q"));
return this.ElementFinder.FindByIdEndingWith<ITextBox>("sb_form_q");
}
}
public IButton GoButton
{
get
{
return this.ElementFinder.Find<IButton>(By.Id("sb_form_go"));
}
}
public IDiv ResultsCountDiv
{
get
{
return this.ElementFinder.Find<IDiv>(By.Id("b_tween"));
}
}
}
测试示例
[TestMethod]
public void SearchForAutomateThePlanet()
{
var bingMainPage = this.container.Resolve<BingMainPage>();
bingMainPage.Navigate();
bingMainPage.Search("Automate The Planet");
bingMainPage.AssertResultsCountIsAsExpected(264);
}
测试主体没有变化。所有必要的更改都需要放在页面的元素映射中。
设计与架构
- 创建混合测试框架 – Selenium 驱动控件
- 创建混合测试框架 – Selenium Driver 实现
- 创建混合测试自动化框架——接口契约
- 创建混合测试框架 – 测试框架驱动实现
- 创建混合测试框架 – 测试框架驱动控件
- 创建混合测试框架 – 高级元素查找扩展
- 创建混合测试框架 – 动态配置执行引擎
文章创建混合测试自动化框架 – 接口契约最初发布于Automate The Planet。
所有图片均从 DepositPhotos.com 购买,不可免费下载和使用。
许可协议