Click here to Skip to main content
15,884,176 members
Articles / DevOps / Automation

Atata - C# Web Test Automation Framework

Rate me:
Please Sign up or sign in to vote.
4.88/5 (47 votes)
23 Apr 2024CPOL8 min read 168.3K   1.1K   96   20
An introduction to Atata C#/.NET web UI test automation full-featured framework based on Selenium WebDriver
In this article you will learn about a C# web test automation framework called Atata.

Introduction

Atata Framework - C#/.NET web test automation full-featured framework based on Selenium WebDriver. It uses a fluent page object pattern; has a built-in logging system; contains a unique triggers functionality; has a set of ready-to-use components. One of the key ideas of the framework is to provide a simple and intuitive syntax for defining and using page objects. A page object implementation requires as less code as possible. You can describe a page object class without any methods and only have a set of properties marked with attributes representing page components.

The framework basically consists of the following concepts:

  • AtataContext - configures test sessions.
  • Components - controls and page objects.
  • Attributes of control search - element locators, like [FindById], [FindByName], [FindByXPath], etc.
  • Trigger attributes - a functionality that is automatically executed in response to certain events on a particular component.
  • Behavior attributes - change the way how particular actions are executed.
  • Verification functionality - .Should.* assertion, .ExpectTo.* expectation as warning, .WaitTo.* waiting.

Features

  • WebDriver. Based on Selenium WebDriver and preserves all its features.
  • Page Object Model. Provides a unique fluent page object pattern, which is easy to implement and maintain.
  • Components. Contains a rich set of ready-to-use components for inputs, tables, lists, etc.
  • Integration. Works on any .NET test engine (e.g. NUnit, xUnit, SpecFlow) as well as on CI systems like Jenkins, GitHub Actions, or TeamCity.
  • Triggers. A bunch of triggers to bind with different events to extend component behavior.
  • Verification. A set of fluent assertion methods and triggers for a component and data verification.
  • Configurable. Defines the default component search strategies as well as additional settings. Atata.Configuration.Json provides flexible JSON configurations.
  • Reporting/Logging. Built-in customizable logging; screenshots and snapshots capturing functionality.
  • Extensible. Atata.HtmlValidation adds HTML page validation. Atata.Bootstrap and Atata.KendoUI provide extra components.

Background

An idea of the Atata Framework is to create complex, extensible and customizable web test automation framework for any kind of websites using Selenium WebDriver and C#/.NET.

References

A list of links related to the framework:

Usage

I would like to show the usage of the framework using the demo website. It is a simple website that contains the following: "Sign In" page, "Users" page, "User Details" page and "User Create/Edit" window.

The test project will use the NuGet packages: Atata, Atata.Bootstrap, Atata.WebDriverSetup, and NUnit.

I use NUnit but it is not required, you can use any .NET testing framework like MSTest or xUnit. But for me, NUnit fits the best.

Let's try to implement an auto-test for the following test case:

  1. Sign in on https://demo.atata.io/signin page.
  2. Click "New" button on the user list page.
  3. Create a new user.
  4. Verify that the new user is present on the user list page.
  5. Navigate to the user's details.
  6. Verify the user's details.

Any page can be represented with the page object. I will try to explain the Atata's stuff step by step. To start, we need to implement a page object class for "Sign In" page.

Sign In page

Sign In page

C#
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

In Atata, you operate with controls, rather than IWebElement's. A page object consists of controls. Any control like TextInput wraps IWebElement and has its own set of methods and properties for element interaction. Find out more about the components in the documentation.

Please note the 5th line of the above code:

C#
using _ = SignInPage;

It is made to simplify the use of class type for the declaration of the controls, as every control has to know its owner page object (specify single or last generic argument). It's just syntactic sugar and, of course, you can declare the controls this way:

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

SignIn button, as you can see, is defined with 2 generic arguments: the first one is the type of the page object to navigate to, after the button is clicked; the other one is the owner type. For buttons and links that don't perform any navigation, just pass single generic argument, the owner page object.

It is possible to mark properties with attributes to specify a finding approach (e.g. FindById, FindByName). In current case, it is not needed, as the default search for inputs is FindByLabel and for buttons is FindByContentOrValue, and it suits our needs. Find out more about the control search in the documentation.

There is also [Url] attribute which specifies relative (can be absolute) URL of this page. It can be used when you navigate to the page object.

[VerifyTitle] and [VerifyH1] are the triggers that, in the current case, are executed upon the page object initialization (after the navigation to the page). If the string value is not passed to these attributes, they use class name without the "Page" ending in title case, as "Sign In". It can be totally configured. Find out more about the triggers in the documentation.

Users page

Users page

The "Users" page contains a table of users with CRUD actions.

C#
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

In the UsersPage class, you can see the usage of Table<TRow, TOwner> and TableRow<TOwner> controls. In UserTableRow class, the properties of type Text and Content by default are being searched by the column header (FindByColumnHeader attribute). It can also be configured. For example, the FirstName control will contain "John" value for the first row. The usage of the table will be shown in the test method below.

Delete button is marked with CloseConfirmBox trigger which accepts the confirmation window shown after the click on the button.

User Create/Edit window

User Create/Edit window

It is quite a simple Bootstrap popup window with two tabs and regular input controls.

C#
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

The UserEditWindow is inherited from BSModal<TOwner> page object class. It is a component of Atata.Bootstrap package.

Save button is marked with Term("Save", "Create") attribute that specifies the values for the control search. It means that the button should be found by "Save" or "Cancel" text content.

Gender and Office controls use the following enums:

C#
namespace SampleApp.UITests;

public enum Gender
{
    Male,
    Female
}
Gender.cs
C#
namespace SampleApp.UITests;

public enum Office
{
    Berlin,
    London,
    NewYork,
    Paris,
    Rome,
    Tokio,
    Washington
}
Office.cs

User Details page

User Details page

C#
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 setup

The best place to configure Atata is a global set-up method that is executed once before all test.

C#
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

Here we globally configure Atata with the following:

  1. Tell to use Chrome browser.
  2. Set the base site URL.
  3. Set the culture, which is used by the controls like DateInput.
  4. Tell to use all Atata features for integration with NUnit, like logging to NUnit TestContext, taking screenshot and snapshots on test failure, etc.
  5. Set format of the page title, as all the pages on the testing website have a page title like "Sign In - Atata Sample App".
  6. AutoSetUpDriverToUse sets up driver for the browser that we want to use, which is chromedriver.exe in this case. Atata.WebDriverSetup package is responsible for that.

For more configuration options, please check the Getting Started / Configuration page in the docs.

Base UITestFixture class

Now let's configure NUnit to build AtataContext (start browser and do extra configuration) on test setup event and clean-up Atata (close browser, etc.) on test tear down event. We can create base test fixture class that will do that. Also we can put reusable Login method there.

C#
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?.Dispose();

    protected static UsersPage Login() =>
        Go.To<SignInPage>()
            .Email.Set("admin@mail.com")
            .Password.Set("abc123")
            .SignIn.ClickAndGo();
}
UITestFixture.cs

Here you can see a primitive usage of AtataContext Build and Dispose methods.

As you can see in Login method, navigation starts from Go static class. To keep the example simple, I use hard-coded credentials here, that can easily be moved to Atata.json config, for example.

User test

And finally, the test that will use all of the created above classes and enums.

C#
using Atata;
using NUnit.Framework;

namespace SampleApp.UITests;

public class UserTests : UITestFixture
{
    [Test]
    public void Create() =>
        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.SetRandom(out Office office)
                .General.Gender.SetRandom(out Gender 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.BePresent()
                    .Notes.Should.Not.BePresent());
}
UserTests.cs

I prefer to use fluent page object pattern in the Atata tests. If you don't like such approach, use without fluent pattern.

You can use random or predefined values in the test, as you like.

The control verification starts with Should property. There is a set of extension methods for different controls like: Equal, Exist, StartWith, BeGreater, BeEnabled, HaveChecked, etc.

That's all. Build project, run test and verify how it works.

Logging

Atata can generate log to different sources. As we configured AtataContext with UseAllNUnitFeatures, Atata will write logs to NUnit context. You can also use targets of NLog or log4net to write logs to files.

Image 5

Here is a part of the test log:

2024-04-23 09:03:16.015 DEBUG Starting test: SampleApp.UITests.UserTests.Create
2024-04-23 09:03:16.025 TRACE > Initialize AtataContext
2024-04-23 09:03:16.025 TRACE - Set: BaseUrl=https://demo.atata.io/
2024-04-23 09:03:16.026 TRACE - Set: ElementFindTimeout=5s; ElementFindRetryInterval=0.5s
2024-04-23 09:03:16.026 TRACE - Set: WaitingTimeout=5s; WaitingRetryInterval=0.5s
2024-04-23 09:03:16.026 TRACE - Set: VerificationTimeout=5s; VerificationRetryInterval=0.5s
2024-04-23 09:03:16.026 TRACE - Set: Culture=en-US
2024-04-23 09:03:16.027 TRACE - Set: Artifacts=D:\dev\atata-samples\SampleApp.UITests\SampleApp.UITests\bin\Debug\net6.0\artifacts\20240423T090315\UserTests\Create
2024-04-23 09:03:16.027 TRACE - > Initialize Driver
2024-04-23 09:03:16.031 TRACE - - Created ChromeDriverService { Port=56159, ExecutablePath=D:\dev\atata-samples\SampleApp.UITests\SampleApp.UITests\bin\Debug\net6.0\drivers\chrome\124.0.6367.60\chromedriver.exe }
2024-04-23 09:03:16.658 TRACE - - Created ChromeDriver { Alias=chrome, SessionId=066ae9f79b9c545bc7f5d948b24f8027 }
2024-04-23 09:03:16.661 TRACE - < Initialize Driver (0.631s)
2024-04-23 09:03:16.662 TRACE < Initialize AtataContext (0.636s)
2024-04-23 09:03:16.696  INFO > Go to "Sign In" page by URL https://demo.atata.io/signin
2024-04-23 09:03:16.845  INFO < Go to "Sign In" page by URL https://demo.atata.io/signin (0.148s)
2024-04-23 09:03:16.854 TRACE > Execute trigger VerifyTitleAttribute { Case=Title, Match=Equals, Timeout=5, RetryInterval=0.5 } on Init against "Sign In" page
2024-04-23 09:03:16.858  INFO - > Assert: title should equal "Sign In - Atata Sample App"
2024-04-23 09:03:17.372  INFO - < Assert: title should equal "Sign In - Atata Sample App" (0.513s)
2024-04-23 09:03:17.373 TRACE < Execute trigger VerifyTitleAttribute { Case=Title, Match=Equals, Timeout=5, RetryInterval=0.5 } on Init against "Sign In" page (0.518s)
2024-04-23 09:03:17.373 TRACE > Execute trigger VerifyH1Attribute { Index=-1, Case=Title, Match=Equals, Timeout=5, RetryInterval=0.5 } on Init against "Sign In" page
2024-04-23 09:03:17.379  INFO - > Assert: "Sign In" <h1> heading should be present
2024-04-23 09:03:17.390 TRACE - - > Find visible element by XPath ".//h1[normalize-space(.) = 'Sign In']" in ChromeDriver
2024-04-23 09:03:17.418 TRACE - - < Find visible element by XPath ".//h1[normalize-space(.) = 'Sign In']" in ChromeDriver (0.027s) >> Element { Id=f.53D2E25CB3A6D1C5A23D224BA6F201A5.d.6D6B02A18C9BC5DF2012E42B9862A3F5.e.7 }
2024-04-23 09:03:17.419  INFO - < Assert: "Sign In" <h1> heading should be present (0.040s)
2024-04-23 09:03:17.419 TRACE < Execute trigger VerifyH1Attribute { Index=-1, Case=Title, Match=Equals, Timeout=5, RetryInterval=0.5 } on Init against "Sign In" page (0.046s)
2024-04-23 09:03:17.423  INFO > Set "admin@mail.com" to "Email" text input
2024-04-23 09:03:17.425 TRACE - > Execute behavior SetsValueUsingClearAndTypeBehaviorsAttribute against "Email" text input
2024-04-23 09:03:17.426 TRACE - - > Execute behavior ClearsValueUsingClearMethodAttribute against "Email" text input
2024-04-23 09:03:17.429 TRACE - - - > Find element by XPath "(.//*[@id = //label[normalize-space(.) = 'Email']/@for]/descendant-or-self::input[@type='text' or not(@type)] | .//label[normalize-space(.) = 'Email']/descendant-or-self::input[@type='text' or not(@type)])" in ChromeDriver
2024-04-23 09:03:17.441 TRACE - - - < Find element by XPath "(.//*[@id = //label[normalize-space(.) = 'Email']/@for]/descendant-or-self::input[@type='text' or not(@type)] | .//label[normalize-space(.) = 'Email']/descendant-or-self::input[@type='text' or not(@type)])" in ChromeDriver (0.012s) >> Element { Id=f.53D2E25CB3A6D1C5A23D224BA6F201A5.d.6D6B02A18C9BC5DF2012E42B9862A3F5.e.6 }
2024-04-23 09:03:17.443 TRACE - - - > Clear element { Id=f.53D2E25CB3A6D1C5A23D224BA6F201A5.d.6D6B02A18C9BC5DF2012E42B9862A3F5.e.6 }
2024-04-23 09:03:17.476 TRACE - - - < Clear element { Id=f.53D2E25CB3A6D1C5A23D224BA6F201A5.d.6D6B02A18C9BC5DF2012E42B9862A3F5.e.6 } (0.033s)
2024-04-23 09:03:17.477 TRACE - - < Execute behavior ClearsValueUsingClearMethodAttribute against "Email" text input (0.050s)
2024-04-23 09:03:17.477 TRACE - - > Execute behavior TypesTextUsingSendKeysAttribute against "Email" text input
2024-04-23 09:03:17.479 TRACE - - - > Send keys "admin@mail.com" to element { Id=f.53D2E25CB3A6D1C5A23D224BA6F201A5.d.6D6B02A18C9BC5DF2012E42B9862A3F5.e.6 }
2024-04-23 09:03:17.548 TRACE - - - < Send keys "admin@mail.com" to element { Id=f.53D2E25CB3A6D1C5A23D224BA6F201A5.d.6D6B02A18C9BC5DF2012E42B9862A3F5.e.6 } (0.069s)
2024-04-23 09:03:17.549 TRACE - - < Execute behavior TypesTextUsingSendKeysAttribute against "Email" text input (0.071s)
2024-04-23 09:03:17.549 TRACE - < Execute behavior SetsValueUsingClearAndTypeBehaviorsAttribute against "Email" text input (0.123s)
2024-04-23 09:03:17.549  INFO < Set "admin@mail.com" to "Email" text input (0.125s)
...
2024-04-23 09:03:19.691 DEBUG Finished test
      Total time: 3.736s
  Initialization: 0.707s | 18.9 %
       Test body: 2.883s | 77.2 %
Deinitialization: 0.144s |  3.9 %

Download

Check out the sources of Atata on Atata GitHub page. Check the docs to find out more about Atata.

Get the sources of the demo test project on GitHub: Atata Sample App Tests. The demo project contains:

  • 20+ different UI auto-tests.
  • Atata configuration and settings set-up.
  • Data input and verification.
  • Validation verification functionality.
  • Usage of triggers.
  • Logging, screenshots and snapshots.
  • Page HTML validation.

Contact

You can ask a question on Stack Overflow using atata tag or choose another contact option. Any feedback, issues and feature requests are welcome.

Atata tutorials

History

  • 1st December, 2016: Initial version posted
  • 2nd December, 2016: Sample sources added
  • 4th April, 2017: Updated article content; added links to other Atata articles; updated sample sources
  • 26th September, 2017: Updated sample sources to use Atata v0.14.0; updated article content
  • 7th November, 2017: Updated sample sources to use Atata v0.15.0; updated article content
  • 5th June, 2018: Updated sample sources to use Atata v0.17.0; updated article content
  • 25th October, 2018: Updated sample sources to use Atata v1.0.0; updated "Features" and "Usage" sections content
  • 15th May, 2019: Updated sample sources to use Atata v1.1.0; updated links to documentation that was moved to a new domain
  • 2nd March, 2021: Updated sample sources to use Atata v1.10.0; updated the article content
  • 23rd April, 2024: Updated sample sources to use Atata v3.0.0; updated the article content

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Poland Poland
C#/.NET Developer with 17+ years of experience in software development: enterprise, web, desktop, automated testing. An author of Atata Framework.
GitHub: https://github.com/YevgeniyShunevych

Comments and Discussions

 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA56mins ago
professionalȘtefan-Mihai MOGA56mins ago 
QuestionPopup for Basic Authentication Pin
mgbloomfield5-Mar-21 9:59
mgbloomfield5-Mar-21 9:59 
QuestionVideos Pin
Satish Kumar Thirumalagiri16-Apr-19 9:25
Satish Kumar Thirumalagiri16-Apr-19 9:25 
QuestionLoad testing? Pin
Member 105605568-Jun-18 0:18
professionalMember 105605568-Jun-18 0:18 
AnswerRe: Load testing? Pin
Yevgeniy Shunevych8-Jun-18 11:01
Yevgeniy Shunevych8-Jun-18 11:01 
QuestionError on loading Atata project Pin
waleedbadri18-Dec-17 23:56
waleedbadri18-Dec-17 23:56 
AnswerRe: Error on loading Atata project Pin
Yevgeniy Shunevych22-Dec-17 5:22
Yevgeniy Shunevych22-Dec-17 5:22 
QuestionSingle Page Apps (SPAs)? Pin
J Snyman26-Sep-17 23:30
J Snyman26-Sep-17 23:30 
AnswerRe: Single Page Apps (SPAs)? Pin
Yevgeniy Shunevych27-Sep-17 1:45
Yevgeniy Shunevych27-Sep-17 1:45 
QuestionWhat about windows desktop app? Pin
Win32nipuh6-Apr-17 2:08
professionalWin32nipuh6-Apr-17 2:08 
AnswerRe: What about windows desktop app? Pin
Yevgeniy Shunevych6-Apr-17 3:22
Yevgeniy Shunevych6-Apr-17 3:22 
GeneralRe: What about windows desktop app? Pin
Win32nipuh6-Apr-17 3:49
professionalWin32nipuh6-Apr-17 3:49 
GeneralMy vote of 5 Pin
Fawad Raza5-Apr-17 3:21
Fawad Raza5-Apr-17 3:21 
GeneralRe: My vote of 5 Pin
Yevgeniy Shunevych5-Apr-17 11:18
Yevgeniy Shunevych5-Apr-17 11:18 
Question2 вопрос Pin
garinov@mail.ru9-Feb-17 7:14
garinov@mail.ru9-Feb-17 7:14 
AnswerRe: 2 вопрос Pin
Yevgeniy Shunevych12-Feb-17 6:12
Yevgeniy Shunevych12-Feb-17 6:12 
QuestionА как это запустить? Pin
garinov@mail.ru9-Feb-17 5:08
garinov@mail.ru9-Feb-17 5:08 
AnswerRe: А как это запустить? Pin
Yevgeniy Shunevych12-Feb-17 6:09
Yevgeniy Shunevych12-Feb-17 6:09 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.