Atata——C# Web测试自动化框架

 

介绍

Atata Framework ——基于Selenium WebDriverC#/.NET Web测试自动化全功能框架。使用流畅的页面对象模式;拥有独特的日志系统;包含触发器功能;有一套随时可用的组件。支持.NET Framework 4.0+.NET Core/Standard 2.0+

该框架主要由以下概念组成:

  • 组件(控件和页面对象)
  • 控件搜索的属性
  • 设置属性
  • 触发器
  • 验证属性和方法

特征

  • 网络驱动程序。基于Selenium WebDriver并保留其所有功能。
  • 页面对象模型。提供独特的流畅页面对象模式,易于实现和维护。
  • 组件。包含一组丰富的即用型组件,用于输入、表格、列表等。
  • 整合。适用于任何.NET测试引擎(例如NUnitxUnitSpecFlow)以及CI系统,如 JenkinsAzure DevOpsTeamCity
  • 触发器。一堆触发器与不同的事件绑定以扩展组件行为。
  • 验证。一套用于组件和数据验证的流畅断言方法和触发器。
  • 可配置。定义默认组件搜索策略以及其他设置。Atata.Configuration.Json提供灵活的JSON配置。
  • 报告/日记。内置可定制的日志记录和屏幕截图捕获功能。
  • 可扩展Atata.BootstrapAtata.KendoUI包有一组随时可用的组件。框架支持任何类型的扩展。

背景

Atata框架的一个想法是使用Selenium WebDriverC#/.NET为任何类型的网站创建复杂、可扩展和可定制的Web测试自动化框架。

参考

与框架相关的链接列表:

要从NuGet包管理器控制台安装它,请运行Install-Package Atata

用法

我想使用演示网站展示框架的用法。这是一个简单的网站,包含以下内容:登录页面、用户页面、用户详细信息页面和用户编辑窗口。

测试项目将使用NuGet包:AtataAtata.BootstrapAtata.WebDriverSetupSelenium.WebDriverNUnitNUnit3TestAdapter

我使用NUnit但它不是必需的,您可以使用任何.NET测试框架,如MSTestxUnit。但对我来说,NUnit最合适。

让我们尝试为以下测试用例实现自动测试:

  1. https://demo.atata.io/signin页面上登录。
  2. 单击用户列表页面上的新建按钮。
  3. 创建一个新用户。
  4. 验证新用户是否出现在用户列表页面上。
  5. 导航到用户的详细信息。
  6. 验证用户的详细信息。

任何页面都可以用页面对象表示。我将尝试逐步解释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<UsersPage, _> SignIn { get; private set; }
    }
}

 

SignInPage.cs

Atata中,您使用控件而不是IWebElement进行操作。页面对象由控件组成。任何像TextInput包装的控件IWebElement都具有自己的一组方法和属性,用于与其交互。在文档中了解有关组件的更多信息。

请注意上面代码的第5行:

using _ = SignInPage;

 

它是为了简化控件声明的类类型的使用,因为每个控件都必须知道其所有者页面对象(指定单个或最后一个通用参数)。这只是一个语法糖,当然,您可以通过以下方式声明控件:

public TextInput<SignInPage> Email { get; private set; }

 

如您所见,SignIn按钮定义了2个通用参数:第一个是单击按钮后要导航到的页面对象的类型;另一种是所有者类型。对于不执行任何导航的按钮和链接,只需传递单个通用参数,即所有者页面对象。

可以用属性标记属性以指定查找方法(例如FindByIdFindByName)。在当前情况下,不需要它,因为默认搜索输入是FindByLabel和按钮是FindByContentOrValue,它适合我们的需求。在文档中了解有关控件搜索的更多信息。

还有一个[Url]属性指定此页面的相对(可​​以是绝对)URL。当您导航到此页面对象时可以使用它。

[VerifyTitle][VerifyH1]是在当前情况下在页面对象初始化时(导航到页面之后)执行的触发器。如果该string值未传递给这些属性,则它们使用不以Page结尾的类名作为登录。它可以完全配置。在文档中了解有关触发器的更多信息。

用户页面

用户页面包含具有CRUD操作的用户表。

using Atata;

namespace SampleApp.UITests
{
    using _ = UsersPage;

    [VerifyTitle]
    [VerifyH1]
    public class UsersPage : Page<_>
    {
        public Button<UserEditWindow, _> New { get; private set; }

        public Table<UserTableRow, _> 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, _> Office { get; private set; }

            public Link<UserDetailsPage, _> View { get; private set; }

            public Button<UserEditWindow, _> Edit { get; private set; }

            [CloseConfirmBox]
            public Button<_> Delete { get; private set; }
        }
    }
}

 

UsersPage.cs

UsersPage类上,你可以看到Table<TRow, TOwner>TableRow<TOwner>控件的用法。在UserTableRow类中,类型TextContent默认情况下的属性由列标题(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<UsersPage, _> 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?, _> Office { get; private set; }

            [FindByName]
            public RadioButtonList<Gender?, _> Gender { get; private set; }
        }

        public class AdditionalTabPane : BSTabPane<_>
        {
            public DateInput<_> Birthday { get; private set; }

            public TextArea<_> Notes { get; private set; }
        }
    }
}

 

UserEditWindow.cs

UserEditWindowBSModal<TOwner>页面对象类继承。它是Atata.Bootstrap包的一个组件。

Save按钮标有指定控件搜索值的Term("Save", "Create")属性。这意味着按钮应该通过SaveCancel内容找到。

GenderOffice控件使用以下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, _> Office { get; private set; }

        [FindByDescriptionTerm]
        public Content<Gender, _> Gender { get; private set; }

        [FindByDescriptionTerm]
        public Content<DateTime?, _> 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

  1. 告诉使用Chrome浏览器。
  2. 设置基本站点URL
  3. 设置文化,它由控件使用,如DateInput.
  4. 告诉使用所有Atata功能与NUnit集成,例如登录到NUnit TestContext,在测试失败时截取屏幕截图等。
  5. 设置页面标题的格式,因为测试网站上的所有页面都有一个页面标题,如登录——Atata 示例应用程序
  6. AutoSetUpDriverToUse为我们要使用的浏览器设置驱动程序,在这种情况下是chromedriver.exeAtata.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<SignInPage>()
                .Email.Set("admin@mail.com")
                .Password.Set("abc123")
                .SignIn.ClickAndGo();
        }
    }
}

 

UITestFixture.cs

在这里您可以看到AtataContext BuildCleanUp方法的原始用法。

正如您在Login方法中看到的,导航从Go静态类开始。为了让示例保持简单,我在这里使用了硬编码凭据,例如,可以轻松地将其移动到App.configAtata.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属性开始。有一组的扩展方法用于不同的控制,如:EqualExistStartWithBeGreaterBeEnabledHaveChecked,等等。

就这样。构建项目,运行测试并验证它是如何工作的。查看文档以了解有关Atata的更多信息。

日志记录

Atata可以生成到不同来源的日志。当我们使用UseAllNUnitFeatures配置AtataContext时,Atata会将日志写入NUnit上下文。您还可以使用NLoglog4net的目标将日志写入文件。

这是测试日志的一部分:

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" <h1> 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" <h1> 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记录功能
  • 屏幕截图

 

联系

您可以使用atata标签提出有关 Stack Overflow 的问题或选择其他联系方式。欢迎任何反馈、问题和功能请求。

Atata教程

https://www.codeproject.com/Articles/1158365/Atata-New-Test-Automation-Framework

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值