利用 Selenium、NUnit 和 C# 进行 UI 测试





5.00/5 (1投票)
如何利用 Selenium 进行 UI 测试?
引言
请参阅此处的原始帖子,以全面了解本文。
测试驱动开发(TDD)目前已得到广泛采用,并因其通过自动化繁琐的测试和防止回归问题来提高生产力而受到认可。然而,测试通常仅在后端服务器上进行,有时也会在前端进行以验证简单功能。另一方面,真实的 UI 测试由 QA 团队手动执行(或经常被忽略),这种手动过程会消耗大量时间,阻碍开发和部署速度。值得注意的是,Gerard Meszaros 在他著名的《xUnit 测试模式》一书中(篇幅长达 900 页),也仅对该主题进行了简要阐述。
我们必须承认,进行 UI 测试是一项艰巨的任务:真实地重现人类在屏幕上的行为变得尤为困难,特别是考虑到无数种可能的配置(Linux 上的 Firefox、Windows 10 上的 Edge 等等)。虽然不可能模拟所有这些配置,但我们仍然可以在部署完成后检查基本条件,以确保没有引入回归。例如,我们可以验证一个按钮是否仍然存在且可点击,或者表单在提交时是否已正确填写。
重要
这不包括检查元素的精确布局或布局的正确性。自动化此类检查是一个高度复杂的问题,可能需要大量的人工智能。
在本系列文章中,我们旨在通过利用 Selenium 和 NUnit 来提供部分解决方案。我们将演示如何相对容易地自动化一些冒烟测试,以快速验证 UI 在部署后是否正常运行。我们的方法将是实用的:我们将快速介绍 Selenium 的功能,指导如何在 Visual Studio 和 C# 中设置环境,并提供几个实际示例。
什么是 Selenium?
Selenium 是一套用于自动化 Web 浏览器的工具。它允许开发人员使用包括 Java、C#、Python 等在内的多种编程语言编写脚本,以自动化与 Web 应用程序的交互。Selenium 支持多种浏览器,包括 Chrome、Firefox、Safari 和 Internet Explorer,使其成为浏览器自动化和测试的通用工具。
重要
Selenium 不仅限于 UI 测试,还可以用于各种任务,例如定期截取特定网页的屏幕截图。它本质上是一个浏览器自动化工具。
Selenium 包含三个主要组件:Selenium WebDriver(本文讨论)、Selenium IDE(下文讨论)和 Selenium Grid。
-
Selenium WebDriver 是一组特定于语言的绑定,允许与 Web 浏览器进行交互,并为浏览器提供要遵循的指令。
-
Selenium IDE 是一系列 Chrome、Firefox 和 Edge 扩展,可以对浏览器交互进行简单的录制和回放。
-
Selenium Grid 使我们能够通过在多台计算机上分发和运行测试来扩展规模并管理多个环境。本文将不讨论此功能。
这些初步定义乍一看可能有些抽象,本文旨在阐明这些概念。
如何设置 Selenium?
Selenium 可与多种编程语言配合使用,为方便起见,我们在此使用 C#。然而,讨论的原理和概念也适用于 Java 或 Python 等语言。
我们还将使用 NUnit 作为测试运行器,但您可以随意选择您熟悉的任何其他运行器。我们假设读者熟悉该工具。
- 在 Visual Studio 中,创建一个空白解决方案,例如命名为
EOCS.SeleniumTests
。 - 在此解决方案中,添加一个新的 NUnit 测试项目,并命名为
EOCS.SeleniumTests.Tests
。 - 在此项目中,添加
Selenium.WebDriver
和Selenium.Support
NuGet 包。
我们将使用哪个示例来实施?
在实际场景中,我们通常会测试自己的应用程序。虽然这似乎很明显,但为了演示,我们将使用一个现有网站来说明我们的示例。我们将对 Staples(https://www.staples.com/) 进行一些 UI 测试。我们选择它是因为它是全球最大的电子商务网站之一。
与如今的许多网站类似,Staples 在我们首次访问时会显示一个 Cookie 同意声明,询问我们是否要接受或拒绝所有 Cookie。虽然这对人类用户来说似乎很简单,但在进行测试时,我们需要指示 Selenium 来处理此操作。
重要
当执行新测试时,Selenium 会将其视为首次访问该网站。因此,Cookie 同意声明将始终显示,我们需要指示 Selenium 忽略它。
理论够了,上代码!
- 在 NUnit 项目中,添加一个名为 ChromeTests.cs 的新类,并添加以下代码:
[TestFixture] public class ChromeTests { IWebDriver driver; [SetUp] public void Setup() { driver = new ChromeDriver(); driver.Navigate().GoToUrl("https://www.staples.com/"); } [TearDown] public void TearDown() { driver.Quit(); } }
在这里,我们指示 Selenium 打开一个新的 Chrome 浏览器(在 Selenium 术语中称为驱动程序),该浏览器将用于此文件中进行的*所有*测试,并导航到指定的 URL(https://www.staples.com/)。
我们还指定,当所有测试执行完毕后,必须终止浏览器实例(NUnit 中的 TearDown
方法)。
信息
在这里,我们目前使用的是 Chrome 实例,但完全可以创建 Firefox 或 Edge 的实例。
[SetUp]
public void Setup()
{
driver = new FirefoxDriver();
driver.Navigate().GoToUrl("https://www.staples.com/");
}
测试1
我们将从添加一个非常简单的测试开始:检查页面标题是否符合预期。
[TestFixture]
public class ChromeTests
{
IWebDriver driver;
[SetUp]
public void Setup()
{
driver = new ChromeDriver();
driver.Navigate().GoToUrl("https://www.staples.com/");
}
[TearDown]
public void TearDown()
{
driver.Quit();
}
[Test]
public void CheckPageTitle()
{
var title = driver.Title;
Assert.AreEqual("Staples® Official Online Store", title);
}
}
当我们运行此测试时,可以看到它通过了。
测试2
接下来,我们将进行一个更实用的测试:检查主页加载时购物车按钮是否正确显示。
购物车按钮可以通过一个 id
为“cartMenuHolder
”的 div
访问,如下图所示的 DOM 截图。
信息
这个查找要交互的正确元素的过程非常繁琐,我们将在下一篇文章中介绍。
- 将以下代码添加到类中
[Test] public void CheckCartButtonIsDisplayed() { var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2)); var a = wait.Until<IWebElement>(d => { var divCart = driver.FindElement(By.Id("cartMenuHolder")); var a = divCart.FindElement(By.TagName("a")); if (a.Displayed) return a; return null; }); Assert.IsNotNull(a); }
使用此代码,我们指示 Selenium 在 2 秒的超时时间内查找购物车按钮,并只要未找到该元素就反复执行此操作。在 Selenium 的术语中,此过程涉及建立等待策略。
重要
UI 测试之所以特别具有挑战性,正是出于这个原因:元素并非总是以确定的方式显示,我们需要等待它们出现。然而,确定等待多长时间却相当困难。在这种情况下,我们认为购物车必须在 2 秒内显示;否则,将被视为对用户不可接受的延迟,并应视为错误。
同步代码与浏览器当前状态是 Selenium 的最大挑战之一,而将其做好是一个高级主题。
当我们运行此测试时,可以看到它通过了。
测试 3
我们现在将进行最后一个测试,并验证搜索功能是否按预期工作:搜索一个项目时,网站会正确重定向到相应页面。此测试更具挑战性,涉及与浏览器的直接交互:我们需要将搜索字符串发送到输入文本框,并检查我们是否被正确重定向。事实上,这正是 Selenium 擅长的。
[Test]
public void CheckSearchRedirects()
{
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
wait.IgnoreExceptionTypes(typeof(NoSuchElementException),
typeof(ElementNotVisibleException));
// Find the search input text
var searchInput = wait.Until<IWebElement>(d =>
{
var e = d.FindElement(By.Id("searchInput"));
if (e.Displayed)
return e;
return null;
});
// Find the button to perform the search
var searchButton = wait.Until<IWebElement>(d =>
{
var e = d.FindElement(By.CssSelector(".huFWMd"));
if (e.Displayed)
return e;
return null;
});
// Fill the search input text with "card"
searchInput.SendKeys("card");
// Clicks on the button
searchButton.Click();
var expectedUrl = "https://www.staples.com/card/directory_card";
wait.Until(d =>
{
return d.Url == expectedUrl;
});
var redirectURL = driver.Url;
Assert.AreEqual("https://www.staples.com/card/directory_card", redirectURL);
}
此代码非常直观,并用简单的英语重现了我们描述的步骤。我们可以观察 Selenium 如何使用 SendKeys
和 Click
方法与浏览器进行交互。
但是,当我们运行此测试时,可以看到它失败了。
错误消息非常明确(*The Other element would receive the click: <iframe src="https://consent...*)。另一个元素接收了点击,而这个元素是一个 iframe
。实际上,正是我们之前提到的 Cookie 同意声明。由于没有人验证此弹出窗口,它始终处于前景并获得焦点,而不是我们的按钮。因此,我们的任务将是模拟对该弹出窗口的验证。
在检查 DOM 后,我们可以实现以下代码。它必须放在 SetUp
方法中,因为每个测试都必须验证同意声明。
[SetUp]
public void Setup()
{
driver = new ChromeDriver();
driver.Navigate().GoToUrl("https://www.staples.com/");
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromMilliseconds(5000);
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(2));
wait.IgnoreExceptionTypes(typeof(NoSuchElementException),
typeof(ElementNotVisibleException));
var iframe = wait.Until<IWebElement>(d =>
{
var disclaimer = d.FindElement(By.XPath("//iframe[@name='trustarc_cm']"));
if (disclaimer.Displayed) return disclaimer;
return null;
});
// Switch to the frame
driver.SwitchTo().Frame(iframe);
// Now we can click the button.
var button = wait.Until<IWebElement>(d =>
{
var b = d.FindElement(By.XPath("//a[@class='required']"));
if (b.Displayed) return b;
return null;
});
// Simulate a click
button.Click();
driver.SwitchTo().DefaultContent();
wait.Until(d => !d.FindElements(By.ClassName("truste_overlay")).Any());
}
在此场景中,我们需要首先定位 iframe
,切换到它(因为其中的按钮无法通过我们当前的文档直接访问),模拟点击按钮,最后切换回默认文档。这些操作中的每一步都使用等待策略来防止竞态条件。
重要
顺便提一下,请注意 FindElement
方法如何可以使用各种选择器,例如按 ID、按标签、按名称,以及按 XPath,如上所示。
如果我们再次运行测试,可以看到它现在通过了。
到目前为止,一切顺利。我们现在可以使用以下过程来执行各种 UI 测试。
-
使用
FindElement
方法在 DOM 中查找元素(如有必要,请建立等待策略) -
使用
SendKeys
或Click
方法对该元素执行操作 -
检查此操作是否触发了预期的行为
然而,我们承认第一步(在 DOM 中查找元素)可能很乏味。手动检查源代码以搜索 div
、按钮等元素是繁琐的。拥有一个工具来轻松执行此任务会更实用。幸运的是,这正是 Selenium IDE 的目标。
请点击此处查看有关如何进行的逐步说明。
历史
- 2023 年 12 月 14 日:初稿