- 从 GitHub 下载示例
Atata Framework ——基于Selenium WebDriver的C#/.NET Web测试自动化全功能框架。使用流畅的页面对象模式;拥有独特的日志系统;包含触发器功能;有一套随时可用的组件。支持.NET Framework 4.0+和.NET Core/Standard 2.0+。
该框架主要由以下概念组成:
- 组件(控件和页面对象)
- 控件搜索的属性
- 设置属性
- 触发器
- 验证属性和方法
- 网络驱动程序。基于Selenium WebDriver并保留其所有功能。
- 页面对象模型。提供独特的流畅页面对象模式,易于实现和维护。
- 组件。包含一组丰富的即用型组件,用于输入、表格、列表等。
- 整合。适用于任何.NET测试引擎(例如NUnit、xUnit、SpecFlow)以及CI系统,如 Jenkins、Azure DevOps或TeamCity。
- 触发器。一堆触发器与不同的事件绑定以扩展组件行为。
- 验证。一套用于组件和数据验证的流畅断言方法和触发器。
- 可配置。定义默认组件搜索策略以及其他设置。Atata.Configuration.Json提供灵活的JSON配置。
- 报告/日记。内置可定制的日志记录和屏幕截图捕获功能。
- 可扩展。Atata.Bootstrap和Atata.KendoUI包有一组随时可用的组件。框架支持任何类型的扩展。
Atata框架的一个想法是使用Selenium WebDriver和C#/.NET为任何类型的网站创建复杂、可扩展和可定制的Web测试自动化框架。
参考与框架相关的链接列表:
- 文档
- GitHub - Atata
- GitHub - Atata 示例应用程序测试
- Atata 模板Visual Studio 扩展
- NuGet - Atata
要从NuGet包管理器控制台安装它,请运行Install-Package Atata
用法我想使用演示网站展示框架的用法。这是一个简单的网站,包含以下内容:登录页面、用户页面、用户详细信息页面和用户编辑窗口。
测试项目将使用NuGet包:Atata、Atata.Bootstrap、Atata.WebDriverSetup、Selenium.WebDriver、NUnit和NUnit3TestAdapter。
我使用NUnit但它不是必需的,您可以使用任何.NET测试框架,如MSTest或xUnit。但对我来说,NUnit最合适。
让我们尝试为以下测试用例实现自动测试:
- 在https://demo.atata.io/signin页面上登录。
- 单击用户列表页面上的“新建”按钮。
- 创建一个新用户。
- 验证新用户是否出现在用户列表页面上。
- 导航到用户的详细信息。
- 验证用户的详细信息。
任何页面都可以用页面对象表示。我将尝试逐步解释Atata的东西。首先,我们需要为登录页面实现页面对象类。
登录页面using Atata;
namespace SampleApp.UITests
{
using _ = SignInPage;
[Url("signin")]
[VerifyTitle]
[VerifyH1]
public class SignInPage : Page
{
public TextInput Email { get; private set; }
public PasswordInput Password { get; private set; }
public Button SignIn { get; private set; }
}
}
SignInPage.cs
在Atata中,您使用控件而不是IWebElement进行操作。页面对象由控件组成。任何像TextInput包装的控件IWebElement都具有自己的一组方法和属性,用于与其交互。在文档中了解有关组件的更多信息。
请注意上面代码的第5行:
using _ = SignInPage;
它是为了简化控件声明的类类型的使用,因为每个控件都必须知道其所有者页面对象(指定单个或最后一个通用参数)。这只是一个语法糖,当然,您可以通过以下方式声明控件:
public TextInput Email { get; private set; }
如您所见,SignIn按钮定义了2个通用参数:第一个是单击按钮后要导航到的页面对象的类型;另一种是所有者类型。对于不执行任何导航的按钮和链接,只需传递单个通用参数,即所有者页面对象。
可以用属性标记属性以指定查找方法(例如FindById,FindByName)。在当前情况下,不需要它,因为默认搜索输入是FindByLabel和按钮是FindByContentOrValue,它适合我们的需求。在文档中了解有关控件搜索的更多信息。
还有一个[Url]属性指定此页面的相对(可以是绝对)URL。当您导航到此页面对象时可以使用它。
[VerifyTitle]和[VerifyH1]是在当前情况下在页面对象初始化时(导航到页面之后)执行的触发器。如果该string值未传递给这些属性,则它们使用不以“Page”结尾的类名作为“登录”。它可以完全配置。在文档中了解有关触发器的更多信息。
用户页面“用户”页面包含具有CRUD操作的用户表。
using Atata;
namespace SampleApp.UITests
{
using _ = UsersPage;
[VerifyTitle]
[VerifyH1]
public class UsersPage : Page
{
public Button New { get; private set; }
public Table Users { get; private set; }
public class UserTableRow : TableRow
{
public Text FirstName { get; private set; }
public Text LastName { get; private set; }
public Text Email { get; private set; }
public Content Office { get; private set; }
public Link View { get; private set; }
public Button Edit { get; private set; }
[CloseConfirmBox]
public Button Delete { get; private set; }
}
}
}
UsersPage.cs
在UsersPage类上,你可以看到Table和TableRow控件的用法。在UserTableRow类中,类型Text和Content默认情况下的属性由列标题(FindByColumnHeader属性)搜索。也可以配置。例如,FirstName控件将包含第一行的“John”值。该表的用法将在下面的测试方法中显示。
Delete按钮标有CloseConfirmBox触发器,它接受单击按钮后显示的确认窗口。
用户创建/编辑窗口这是一个非常简单的Bootstrap弹出窗口,带有两个选项卡和常规输入控件。
using Atata;
using Atata.Bootstrap;
namespace SampleApp.UITests
{
using _ = UserEditWindow;
public class UserEditWindow : BSModal
{
[FindById]
public GeneralTabPane General { get; private set; }
[FindById]
public AdditionalTabPane Additional { get; private set; }
[Term("Save", "Create")]
public Button Save { get; private set; }
public class GeneralTabPane : BSTabPane
{
public TextInput FirstName { get; private set; }
public TextInput LastName { get; private set; }
[RandomizeStringSettings("{0}@mail.com")]
public TextInput Email { get; private set; }
public Select Office { get; private set; }
[FindByName]
public RadioButtonList Gender { get; private set; }
}
public class AdditionalTabPane : BSTabPane
{
public DateInput Birthday { get; private set; }
public TextArea Notes { get; private set; }
}
}
}
UserEditWindow.cs
UserEditWindow从BSModal页面对象类继承。它是Atata.Bootstrap包的一个组件。
Save按钮标有指定控件搜索值的Term("Save", "Create")属性。这意味着按钮应该通过“Save”或“Cancel”内容找到。
Gender和Office控件使用以下enum:
namespace SampleApp.UITests
{
public enum Gender
{
Male,
Female
}
}
Gender.cs
namespace SampleApp.UITests
{
public enum Office
{
Berlin,
London,
NewYork,
Paris,
Rome,
Tokio,
Washington
}
}
Office.cs
用户详情页面using System;
using Atata;
namespace SampleApp.UITests
{
using _ = UserDetailsPage;
public class UserDetailsPage : Page
{
[FindFirst]
public H1 Header { get; private set; }
[FindByDescriptionTerm]
public Text Email { get; private set; }
[FindByDescriptionTerm]
public Content Office { get; private set; }
[FindByDescriptionTerm]
public Content Gender { get; private set; }
[FindByDescriptionTerm]
public Content Birthday { get; private set; }
[FindByDescriptionTerm]
public Text Notes { get; private set; }
}
}
UserDetailsPage.cs
Atata设置配置Atata的最佳位置是在所有测试之前执行一次的全局设置方法。
using Atata;
using NUnit.Framework;
namespace SampleApp.UITests
{
[SetUpFixture]
public class SetUpFixture
{
[OneTimeSetUp]
public void GlobalSetUp()
{
AtataContext.GlobalConfiguration
.UseChrome()
.WithArguments("start-maximized")
.UseBaseUrl("https://demo.atata.io/")
.UseCulture("en-US")
.UseAllNUnitFeatures()
.Attributes.Global.Add(
new VerifyTitleSettingsAttribute { Format = "{0} - Atata Sample App" });
AtataContext.GlobalConfiguration.AutoSetUpDriverToUse();
}
}
}
SetUpFixture.cs
在这里,我们使用以下内容全局配置Atata:
- 告诉使用Chrome浏览器。
- 设置基本站点URL。
- 设置文化,它由控件使用,如DateInput.
- 告诉使用所有Atata功能与NUnit集成,例如登录到NUnit TestContext,在测试失败时截取屏幕截图等。
- 设置页面标题的格式,因为测试网站上的所有页面都有一个页面标题,如“登录——Atata 示例应用程序”。
- AutoSetUpDriverToUse为我们要使用的浏览器设置驱动程序,在这种情况下是chromedriver.exe。Atata.WebDriverSetup包负责这些内容。
有关更多配置选项,请查看文档中的入门/设置页面。
基本UITestFixture类现在让我们配置NUnit以在测试设置事件上构建AtataContext(启动浏览器并进行额外配置)并在测试拆卸事件上清理Atata(关闭浏览器等)。我们可以创建基础测试工具类来做到这一点。我们也可以在那里放置可重用的Login方法。
using Atata;
using NUnit.Framework;
namespace SampleApp.UITests
{
[TestFixture]
public class UITestFixture
{
[SetUp]
public void SetUp()
{
AtataContext.Configure().Build();
}
[TearDown]
public void TearDown()
{
AtataContext.Current?.CleanUp();
}
protected UsersPage Login()
{
return Go.To()
.Email.Set("admin@mail.com")
.Password.Set("abc123")
.SignIn.ClickAndGo();
}
}
}
UITestFixture.cs
在这里您可以看到AtataContext Build和CleanUp方法的原始用法。
正如您在Login方法中看到的,导航从Go静态类开始。为了让示例保持简单,我在这里使用了硬编码凭据,例如,可以轻松地将其移动到App.config或Atata.json。
用户测试最后,将使用上面创建的所有类和枚举的测试。
using Atata;
using NUnit.Framework;
namespace SampleApp.UITests
{
public class UserTests : UITestFixture
{
[Test]
public void Create()
{
Office office = Office.NewYork;
Gender gender = Gender.Male;
Login() // Returns UsersPage.
.New.ClickAndGo() // Returns UserEditWindow.
.ModalTitle.Should.Equal("New User")
.General.FirstName.SetRandom(out string firstName)
.General.LastName.SetRandom(out string lastName)
.General.Email.SetRandom(out string email)
.General.Office.Set(office)
.General.Gender.Set(gender)
.Save.ClickAndGo() // Returns UsersPage.
.Users.Rows[x => x.Email == email].View.ClickAndGo() // Returns UserDetailsPage.
.AggregateAssert(page => page
.Header.Should.Equal($"{firstName} {lastName}")
.Email.Should.Equal(email)
.Office.Should.Equal(office)
.Gender.Should.Equal(gender)
.Birthday.Should.Not.Exist()
.Notes.Should.Not.Exist());
}
}
}
UserTests.cs
我更喜欢在Atata测试中使用流畅的页面对象模式。如果您不喜欢这种方法,请使用无流畅模式。
您可以根据需要在测试中使用随机值或预定义值。
控制验证从Should属性开始。有一组的扩展方法用于不同的控制,如:Equal,Exist,StartWith,BeGreater,BeEnabled,HaveChecked,等等。
就这样。构建项目,运行测试并验证它是如何工作的。查看文档以了解有关Atata的更多信息。
日志记录Atata可以生成到不同来源的日志。当我们使用UseAllNUnitFeatures配置AtataContext时,Atata会将日志写入NUnit上下文。您还可以使用NLog或log4net的目标将日志写入文件。
这是测试日志的一部分:
2021-03-02 12:50:42.4649 INFO Starting test: Create
2021-03-02 12:50:42.4917 TRACE > Set up AtataContext
2021-03-02 12:50:42.4937 TRACE - Set: BaseUrl=https://demo.atata.io/
2021-03-02 12:50:42.4977 TRACE - Set: ElementFindTimeout=5s; ElementFindRetryInterval=0.5s
2021-03-02 12:50:42.4980 TRACE - Set: WaitingTimeout=5s; WaitingRetryInterval=0.5s
2021-03-02 12:50:42.4982 TRACE - Set: VerificationTimeout=5s; VerificationRetryInterval=0.5s
2021-03-02 12:50:42.4986 TRACE - Set: Culture=en-US
2021-03-02 12:50:42.5067 TRACE - Set: DriverService=ChromeDriverService on port 64593
2021-03-02 12:50:43.4007 TRACE - Set: Driver=ChromeDriver (alias=chrome)
2021-03-02 12:50:43.4029 TRACE < Set up AtataContext (0.910s)
2021-03-02 12:50:43.4917 INFO Go to "Sign In" page
2021-03-02 12:50:43.5439 INFO Go to URL "https://demo.atata.io/signin"
2021-03-02 12:50:44.9231 TRACE > Execute trigger VerifyTitleAttribute { Case=Title, Match=Equals, Format="{0} - Atata Sample App" } on Init against "Sign In" page
2021-03-02 12:50:44.9370 INFO - > Assert: title should equal "Sign In - Atata Sample App"
2021-03-02 12:50:45.9745 INFO - < Assert: title should equal "Sign In - Atata Sample App" (1.037s)
2021-03-02 12:50:45.9752 TRACE < Execute trigger VerifyTitleAttribute { Case=Title, Match=Equals, Format="{0} - Atata Sample App" } on Init against "Sign In" page (1.052s)
2021-03-02 12:50:45.9773 TRACE > Execute trigger VerifyH1Attribute { Index=-1, Case=Title, Match=Equals } on Init against "Sign In" page
2021-03-02 12:50:45.9880 INFO - > Assert: "Sign In" heading should exist
2021-03-02 12:50:46.0225 TRACE - - > Find visible element by XPath ".//h1[normalize-space(.) = 'Sign In']" in ChromeDriver
2021-03-02 12:50:46.0754 TRACE - - < Find visible element by XPath ".//h1[normalize-space(.) = 'Sign In']" in ChromeDriver (0.051s) >> Element { Id=a694ecd2-0874-4ba3-b61f-e4e3eb821f0a }
2021-03-02 12:50:46.0756 INFO - < Assert: "Sign In" heading should exist (0.087s)
2021-03-02 12:50:46.0758 TRACE < Execute trigger VerifyH1Attribute { Index=-1, Case=Title, Match=Equals } on Init against "Sign In" page (0.098s)
2021-03-02 12:50:46.0842 INFO > Set "admin@mail.com" to "Email" text input
2021-03-02 12:50:46.0889 TRACE - > Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Email" text input
2021-03-02 12:50:46.0968 TRACE - - > Find visible element by XPath ".//label[normalize-space(.) = 'Email']" in ChromeDriver
2021-03-02 12:50:46.1321 TRACE - - < Find visible element by XPath ".//label[normalize-space(.) = 'Email']" in ChromeDriver (0.035s) >> Element { Id=bc2450f6-27bb-497b-80aa-ff428b95d440 }
2021-03-02 12:50:46.1501 TRACE - - > Find visible element by XPath ".//*[normalize-space(@id) = 'email']/descendant-or-self::input[@type='text' or not(@type)]" in ChromeDriver
2021-03-02 12:50:46.1803 TRACE - - < Find visible element by XPath ".//*[normalize-space(@id) = 'email']/descendant-or-self::input[@type='text' or not(@type)]" in ChromeDriver (0.030s) >> Element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 }
2021-03-02 12:50:46.1815 TRACE - - > Clear element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 }
2021-03-02 12:50:46.2280 TRACE - - < Clear element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 } (0.046s)
2021-03-02 12:50:46.2291 TRACE - - > Send keys "admin@mail.com" to element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 }
2021-03-02 12:50:46.3052 TRACE - - < Send keys "admin@mail.com" to element { Id=3baa8d49-2ac4-4f69-900e-e6be31daaa14 } (0.076s)
2021-03-02 12:50:46.3055 TRACE - < Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Email" text input (0.216s)
2021-03-02 12:50:46.3057 INFO < Set "admin@mail.com" to "Email" text input (0.221s)
2021-03-02 12:50:46.3059 INFO > Set "abc123" to "Password" password input
2021-03-02 12:50:46.3061 TRACE - > Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Password" password input
2021-03-02 12:50:46.3066 TRACE - - > Find visible element by XPath ".//label[normalize-space(.) = 'Password']" in ChromeDriver
2021-03-02 12:50:46.3378 TRACE - - < Find visible element by XPath ".//label[normalize-space(.) = 'Password']" in ChromeDriver (0.031s) >> Element { Id=461e982a-c6c4-414f-ac9f-c7c7bd16baeb }
2021-03-02 12:50:46.3476 TRACE - - > Find visible element by XPath ".//*[normalize-space(@id) = 'password']/descendant-or-self::input[@type='password']" in ChromeDriver
2021-03-02 12:50:46.3756 TRACE - - < Find visible element by XPath ".//*[normalize-space(@id) = 'password']/descendant-or-self::input[@type='password']" in ChromeDriver (0.027s) >> Element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d }
2021-03-02 12:50:46.3759 TRACE - - > Clear element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d }
2021-03-02 12:50:46.4203 TRACE - - < Clear element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d } (0.044s)
2021-03-02 12:50:46.4205 TRACE - - > Send keys "abc123" to element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d }
2021-03-02 12:50:46.4810 TRACE - - < Send keys "abc123" to element { Id=a92d523a-a4c9-4ab6-9455-477cef964b0d } (0.060s)
2021-03-02 12:50:46.4813 TRACE - < Execute behavior ValueSetUsingClearAndSendKeysAttribute against "Password" password input (0.175s)
2021-03-02 12:50:46.4815 INFO < Set "abc123" to "Password" password input (0.175s)
2021-03-02 12:50:46.4837 INFO > Click "Sign In" button
2021-03-02 12:50:46.4862 TRACE - > Execute behavior ClickUsingClickMethodAttribute against "Sign In" button
2021-03-02 12:50:46.4892 TRACE - - > Find visible element by XPath ".//*[self::input[@type='button' or @type='submit' or @type='reset'] or self::button][normalize-space(.) = 'Sign In' or normalize-space(@value) = 'Sign In']" in ChromeDriver
2021-03-02 12:50:46.5177 TRACE - - < Find visible element by XPath ".//*[self::input[@type='button' or @type='submit' or @type='reset'] or self::button][normalize-space(.) = 'Sign In' or normalize-space(@value) = 'Sign In']" in ChromeDriver (0.028s) >> Element { Id=0994387f-fd82-49f6-ab43-8b90c3aee738 }
2021-03-02 12:50:46.5186 TRACE - - > Click element { Id=0994387f-fd82-49f6-ab43-8b90c3aee738 }
2021-03-02 12:50:46.6419 TRACE - - < Click element { Id=0994387f-fd82-49f6-ab43-8b90c3aee738 } (0.123s)
2021-03-02 12:50:46.6421 TRACE - < Execute behavior ClickUsingClickMethodAttribute against "Sign In" button (0.155s)
2021-03-02 12:50:46.6423 INFO < Click "Sign In" button (0.158s)
2021-03-02 12:50:46.6544 INFO Go to "Users" page
...
下载
在Atata GitHub 页面上查看Atata框架的来源。
在GitHub上获取演示测试项目的来源:Atata Sample App Tests。演示项目包含:
- 20多种不同的自动测试
- 验证验证功能
- 使用NLog记录功能
- 屏幕截图
- 从 GitHub 下载示例
您可以使用atata标签提出有关 Stack Overflow 的问题或选择其他联系方式。欢迎任何反馈、问题和功能请求。
Atata教程- 页面验证
- 验证消息的验证
- 处理确认弹出窗口
- 通过.runsettings文件进行多浏览器配置
- 报告范围报告
https://www.codeproject.com/Articles/1158365/Atata-New-Test-Automation-Framework