Getting Started

Atata Framework - C#/.NET web test automation full featured framework based on Selenium WebDriver. It uses fluent page object pattern. Atata repository is open source and hosted on GitHub under the Apache License 2.0.

Framework basically consists of the following concepts:

  • Components (controls and page objects)
  • Attributes of the control search
  • Settings attributes
  • Triggers
  • Verification attributes and methods

There are 2 options to create a project in Visual Studio for automated testing using Atata Framework.

Via Atata Templates

To get started install Atata Templates Visual Studio extension.

The extension provides the following templates:

  • Project templates:
    • Atata NUnit Test Project (.NET Framework)
    • Atata NUnit Test Project (.NET Core)
    • Atata Components Library (.NET Framework)
    • Atata Components Library (.NET Standard)
  • Item templates:
    • Atata Page Object
    • Atata Base Page Object
    • Atata Control
    • Atata Trigger
    • Atata NUnit Test Fixture
    • Atata NUnit Base Test Fixture

When extension is installed, you can create a project of one of Atata project types. In Visual Studio:

  1. Go to File/New/Project…
  2. Select Installed/Visual C#/Atata category
  3. Choose template (e.g.: Atata NUnit Test Project (.NET Framework)) and specify project name and location

Atata Templates project

The project is created with NuGet package references:

You might also need to install a driver package for specific browser: WebDriver.ChromeDriver.win32, WebDriver.IEDriverServer.win32, etc.

In the created project you can specify your testing site base URL and apropriate driver in UITestFixture.cs class, e.g.:

AtataContext.Configure().
    UseChrome().
    UseBaseUrl("SITE_URL").
    //...

Further test fixture classes are recommended to inherit from UITestFixture, or just choose “Atata NUnit Test Fixture” item template in “Add New Item Window”.

Via NuGet

It is more custom approach to create Atata testing project. To get started just add Atata NuGet package to the project of Class Library type.

PM> Install-Package Atata

The Atata package depends on the following packages (that are added automatically):

You might also need to install a driver package for specific browser: WebDriver.ChromeDriver.win32, WebDriver.IEDriverServer.win32, etc.

You are also free to select any test engine framework like: NUnit, Xunit, MSTest, etc.

Below is simple example for Sign In page.

Create Project

In Visual Studio create project of Class Library type. Add NuGet references to the following packages:

Define Settings

Global settings are applied via assembly attributes. Add AtataSettings.cs file to the project.

AtataSettings.cs

using Atata;

[assembly: Culture("en-us")]
[assembly: VerifyTitleSettings(Format = "{0} - Atata Sample App")]

Define Page Object Class

SignInPage.cs

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 is a page object class that is marked with several attributes:

  • [Url("signin")] - sets the relative URL of the page to be navigated to.
  • [VerifyTitle] - upon page initialization the check if the page title is equal to “Sign In - Atata Sample App” is conducted. “Sign In” value is taken from the class type name while ignoring “Page” word and formatted with Title case. The second part is taken from global [assembly: VerifyTitleSettings(Format = "{0} - Atata Sample App")] settings attribute.
  • [VerifyH1] - upon page initialization the check if the page contains <h1> HTML element with “Sign In” text is conducted.

Default element search of Email and Password controls is performed by the label. Default search of SignIn button is performed by the content and value (<button> by content text and <input> by value attribute).

Implement Test

The following sample uses additional NUnit and ChromeDriver NuGet packages.

SignInTests.cs

using Atata;
using NUnit.Framework;

namespace SampleApp.UITests
{
    [TestFixture]
    public class SignInTests
    {
        [SetUp]
        public void SetUp()
        {
            AtataContext.Configure().
                UseChrome().
                UseBaseUrl("https://atata-framework.github.io/atata-sample-app/#!/").
                UseNUnitTestName().
                AddNUnitTestContextLogging().
                AddScreenshotFileSaving().
                LogNUnitError().
                TakeScreenshotOnNUnitError().
                Build();
        }

        [TearDown]
        public void TearDown()
        {
            AtataContext.Current.CleanUp();
        }

        [Test]
        public void SignIn()
        {
            Go.To<SignInPage>().
                Email.Set("admin@mail.com").
                Password.Set("abc123").
                SignIn.Click();
        }
    }
}

View Log

The above sample SignIn test generates the following log:

2016-09-26 15:50:51.7688 INFO Starting test: SignIn
2016-09-26 15:50:51.7858 INFO Starting: Init WebDriver
2016-09-26 15:50:54.3819 INFO Finished: Init WebDriver (2.596s)
2016-09-26 15:50:54.4073 INFO Go to "Sign In" page
2016-09-26 15:50:54.4088 INFO Go to URL "https://atata-framework.github.io/atata-sample-app/#!/signin"
2016-09-26 15:50:55.4865 INFO Starting: Verify title should equal "Sign In - Atata Sample App"
2016-09-26 15:50:55.4975 INFO Finished: Verify title should equal "Sign In - Atata Sample App" (0.010s)
2016-09-26 15:50:55.5035 INFO Starting: Verify "Sign In" <h1> heading content should equal "Sign In"
2016-09-26 15:50:55.5813 INFO Finished: Verify "Sign In" <h1> heading content should equal "Sign In" (0.077s)
2016-09-26 15:50:55.5818 INFO Starting: Set "admin@mail.com" to "Email" text input
2016-09-26 15:50:55.7342 INFO Finished: Set "admin@mail.com" to "Email" text input (0.152s)
2016-09-26 15:50:55.7342 INFO Starting: Set "abc123" to "Password" password input
2016-09-26 15:50:55.8625 INFO Finished: Set "abc123" to "Password" password input (0.128s)
2016-09-26 15:50:55.8630 INFO Starting: Click "Sign In" button
2016-09-26 15:50:55.9568 INFO Finished: Click "Sign In" button (0.093s)
2016-09-26 15:50:55.9578 INFO Starting: Clean up test context
2016-09-26 15:50:56.0173 INFO Finished: Clean up test context (0.059s)
2016-09-26 15:50:56.0178 INFO Finished test (4.249s)
2016-09-26 15:50:56.0178 INFO Pure test execution time: 1.575s

Demo atata-framework/atata-sample-app-tests UI tests application demonstrates different testing approaches and features of Atata Framework. It uses Atata Sample App (repository) as a testing website and NUnit 3 as a test engine.

Features

  • Atata test initialization and settings set-up
  • Page navigation
  • Controls finding
  • Data input and verification
  • Validation messages verification
  • Use of the triggers
  • Interaction with the pop-ups (Bootstrap modal) and alerts
  • Work with the tables
  • Logging

Sample Test

[Test]
public void User_Create()
{
    string firstName, lastName, email;
    Office office = Office.NewYork;
    Gender gender = Gender.Male;

    Login().
        New().
            ModalTitle.Should.Equal("New User").
            General.FirstName.SetRandom(out firstName).
            General.LastName.SetRandom(out lastName).
            General.Email.SetRandom(out email).
            General.Office.Set(office).
            General.Gender.Set(gender).
            Save().
        Users.Rows[x => x.FirstName == firstName && x.LastName == lastName && x.Email == email && x.Office == office].View().
            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();
}

In SetUp method just invoke AtataContext.Configure() method that returns AtataContextBuilder instance, then invoke configuration methods and finally invoke Build() method:

[SetUp]
public void SetUp()
{
    AtataContext.Configure().
        // TODO: Invoke configuration methods.
        Build();
}

To clean up the AtataContext do the following in the TearDown method:

[TearDown]
public void TearDown()
{
    AtataContext.Current.CleanUp();
}

It also closes web driver instance as well as a browser.

Driver

The list of driver creational methods of AtataContextBuilder:

public ChromeAtataContextBuilder

UseChrome()

Use the ChromeDriver.

public FirefoxAtataContextBuilder

UseFirefox()

Use the FirefoxDriver.

public InternetExplorerAtataContextBuilder

UseInternetExplorer()

Use the InternetExplorerDriver.

public EdgeAtataContextBuilder

UseEdge()

Use the EdgeDriver.

public SafariAtataContextBuilder

UseSafari()

Use the SafariDriver.

public OperaAtataContextBuilder

UseOpera()

Use the OperaDriver.

public PhantomJSAtataContextBuilder

UsePhantomJS()

Use the PhantomJSDriver.

public RemoteDriverAtataContextBuilder

UseRemoteDriver()

Use the RemoteWebDriver.

public CustomDriverAtataContextBuilder

UseDriver(Func<RemoteWebDriver> driverFactory)

Use custom driver factory method.

public TDriverFactory

UseDriver<TDriverFactory>(TDriverFactory driverFactory)

Use the driver factory.

public AtataContextBuilder

UseDriver(string alias)

Sets the alias of the driver to use.

Driver Configuration

It is possible to configure the driver using the following methods:

public {DriverAtataContextBuilder}

WithArguments(params string[] arguments)

Adds arguments to be appended to the Chrome.exe/Opera.exe command line.

public {DriverAtataContextBuilder}

WithAlias(string alias)

Specifies the driver alias.

public {DriverAtataContextBuilder}

WithOptions(Func<{DriverOptions}> optionsCreator)

Specifies the driver options factory method.

public {DriverAtataContextBuilder}

WithOptions(Action<{DriverOptions}> optionsInitializer)

Specifies the driver options initialization method.

public {DriverAtataContextBuilder}

WithOptions(Dictionary<string, object> optionsPropertiesMap)

Specifies the properties map for the driver options.

public {DriverAtataContextBuilder}

WithCapability(string capabilityName, object capabilityValue)

Adds additional capability to the driver options.

public {DriverAtataContextBuilder}

WithDriverService(Func<{DriverService}> driverServiceCreator)

Specifies the driver service factory method.

public {DriverAtataContextBuilder}

WithDriverService(Action<{DriverService}> serviceInitializer)

Specifies the driver service initialization method.

public {DriverAtataContextBuilder}

WithDriverService(Dictionary<string, object> servicePropertiesMap)

Specifies the properties map for the driver service.

public {DriverAtataContextBuilder}

WithDriverPath(string driverPath)

Specifies the directory containing the driver executable file.

public {DriverAtataContextBuilder}

WithLocalDriverPath()

Specifies that local/current directory should be used as the directory containing the driver executable file. Uses AppDomain.CurrentDomain.BaseDirectory as driver folder path. This configuration options makes sense for .NET Core 2.0 project that uses driver as a project package (hosted in the same build directory).

public {DriverAtataContextBuilder}

WithDriverExecutableFileName(string driverExecutableFileName)

Specifies the name of the driver executable file.

public {DriverAtataContextBuilder}

WithCommandTimeout(TimeSpan commandTimeout)

Specifies the command timeout (the maximum amount of time to wait for each command).

public {DriverAtataContextBuilder}

WithFixOfCommandExecutionDelay()

Specifies that the fix of driver’s HTTP command execution delay should be applied. There is a bug in Selenium.WebDriver v3.6.0 for .NET Core 2.0: each WebDriver request takes extra 1 second. Link to the bug: https://github.com/dotnet/corefx/issues/24104. The fix does: finds HttpCommandExecutor instance of RemoteWebDriver instance and updates its remoteServerUri field by replacing “locahost” with “127.0.0.1”.

Usage

AtataContext.Configure().
    UseChrome().
        WithArguments("disable-extensions", "no-sandbox", "start-maximized").
    Build();

Logging

The list of logging methods of AtataContextBuilder:

public AtataContextBuilder<TLogConsumer>

AddLogConsumer<TLogConsumer>(TLogConsumer consumer)

where TLogConsumer : ILogConsumer

Adds the log consumer.

public AtataContextBuilder<ILogConsumer>

AddLogConsumer(string typeNameOrAlias)

Adds the log consumer. typeNameOrAlias can accept full type name, custom ILogConsumer alias (registered via LogConsumerAliases.Register method) or one of the predefined aliases: “debug”, “trace”, “nunit” and “nlog”.

public AtataContextBuilder<TraceLogConsumer>

AddTraceLogging()

Adds the TraceLogConsumer instance that uses System.Diagnostics.Trace class for logging.

public AtataContextBuilder<DebugLogConsumer>

AddDebugLogging()

Adds the DebugLogConsumer instance that uses System.Diagnostics.Debug class for logging.

public AtataContextBuilder<NUnitTestContextLogConsumer>

AddNUnitTestContextLogging()

Adds the NUnitTestContextLogConsumer instance that uses NUnit.Framework.TestContext class for logging.

public AtataContextBuilder<NLogConsumer>

AddNLogLogging(string loggerName = null)

Adds the NLogConsumer instance that uses NLog.Logger class for logging.

Logging Configuration

The list of extension methods to configure ILogConsumer:

public AtataContextBuilder<TTLogConsumer>

WithoutSectionFinish<TTLogConsumer>()

Defines that the logging should not use section-like pair messages (not “Starting: {action}” and “Finished: {action} {time elapsed}”, but just “{action}”).

public AtataContextBuilder<TTLogConsumer>

WithMinLevel<TTLogConsumer>(LogLevel level)

Specifies the minimum level of the log event to write to the log. The default value is Trace.

Usage

AtataContext.Configure().
    UseChrome().
    UseNUnitTestContextLogging().
        WithoutSectionFinish().
        WithMinLevel(LogLevel.Info).
    UseDebugLogging().
        WithMinLevel(LogLevel.Debug).
    Build();

Screenshots

The list of screenshot taking methods of AtataContextBuilder:

public AtataContextBuilder<TScreenshotConsumer>

AddScreenshotConsumer<TScreenshotConsumer>(TScreenshotConsumer consumer)

where TScreenshotConsumer : IScreenshotConsumer

Adds the screenshot consumer. Is used for custom screenshot processing.

public AtataContextBuilder<IScreenshotConsumer>

AddScreenshotConsumer(string typeNameOrAlias)

Adds the screenshot consumer. typeNameOrAlias can accept full type name, custom IScreenshotConsumer alias (registered via ScreenshotConsumerAliases.Register method) or predefined “file” alias.

public AtataContextBuilder<FileScreenshotConsumer>

AddScreenshotFileSaving()

Adds the FileScreenshotConsumer instance for the screenshot saving to file.

File Screenshots Configuration

By default AddScreenshotFileSaving method configures FileScreenshotConsumer with the following settings:

  • Folder path: $@"Logs\{AtataContext.BuildStart:yyyy-MM-dd HH_mm_ss}\{AtataContext.Current.TestName}"
  • File name format: $"{screenshotInfo.Number:D2} - {screenshotInfo.PageObjectFullName}{screenshotInfo.Title?.Prepend(" - ")}"
  • Image format: Png.

The list of extension methods to configure FileScreenshotConsumer:

public AtataContextBuilder<FileScreenshotConsumer>

With(ScreenshotImageFormat imageFormat)

Specifies the image format of the file screenshot consumer.

public AtataContextBuilder<FileScreenshotConsumer>

WithFolderPath(Func<string> folderPathBuilder)

Specifies the folder path builder of the file screenshot consumer.

public AtataContextBuilder<FileScreenshotConsumer>

WithFileName(Func<ScreenshotInfo, string> fileNameBuilder)

Specifies the file name builder of the file screenshot consumer.

public AtataContextBuilder<FileScreenshotConsumer>

WithFilePath(Func<ScreenshotInfo, string> filePathBuilder)

Specifies the file path builder of the file screenshot consumer.

Usage

The below example configures FileScreenshotConsumer to use separate folder for each test:

AtataContext.Configure().
    // Do some initialization.
    AddScreenshotFileSaving().
        WithFolderPath(() => $@"Logs\{AtataContext.BuildStart:yyyy-MM-dd HH_mm_ss}\{AtataContext.Current.TestName}").
        WithFileName(screenshotInfo => $"{screenshotInfo.Number:D2} - {screenshotInfo.PageObjectFullName}{screenshotInfo.Title?.Prepend(" - ")}").
    Build();

If you need to take a screenshot only on test failure, you may configure FileScreenshotConsumer to save all screenshot files to the same folder:

AtataContext.Configure().
    // Do some initialization.
    TakeScreenshotOnNUnitError().
    AddScreenshotFileSaving().
        WithFolderPath(() => $@"Logs\{AtataContext.BuildStart:yyyy-MM-dd HH_mm_ss}").
        WithFileName(screenshotInfo => $"{AtataContext.Current.TestName} - {screenshotInfo.PageObjectFullName}").
    Build();

Other

public AtataContextBuilder

UseBaseUrl(string baseUrl)

Sets the base URL.

public AtataContextBuilder

UseCulture(CultureInfo culture)

Sets the culture. The default value is CultureInfo.CurrentCulture.

public AtataContextBuilder

UseCulture(string cultureName)

Sets the culture by the name. The default value is CultureInfo.CurrentCulture.

public AtataContextBuilder

UseTestName(string name)

Sets the name of the test.

public AtataContextBuilder

UseTestName(Func<string> testNameFactory)

Sets the factory method of the test name.

public AtataContextBuilder

UseNUnitTestName()

Defines that the name of the test should be taken from the NUnit test.

public AtataContextBuilder

UseRetryTimeout(TimeSpan timeout)

Sets the retry timeout for a search of element/control. The default value is 5 seconds.

public AtataContextBuilder

UseRetryInterval(TimeSpan interval)

Sets the retry interval for a search of element/control. The default value is 500 milliseconds.

public AtataContextBuilder

OnCleanUp(Action action)

Adds the action to perform during AtataContext cleanup.

public AtataContextBuilder

LogNUnitError()

Defines that an error occurred during the NUnit test execution should be added to the log during the cleanup.

public AtataContextBuilder

TakeScreenshotOnNUnitError(string title = "Failed")

Defines that an error occurred during the NUnit test execution should be captured by a screenshot during the cleanup.

public AtataContextBuilder

UseAssertionExceptionType(Type exceptionType)

Sets the type of the assertion exception. The default value is typeof(Atata.AssertionException).

public AtataContextBuilder

UseAssertionExceptionType<TException>()

Sets the type of the assertion exception. The default value is typeof(Atata.AssertionException).

public AtataContextBuilder

Clear()

Clears the BuildingContext.

public AtataContext

Build()

Builds the AtataContext instance and sets it to AtataContext.Current property.

Go

The page object navigation starts with Go static class. It provides a set of static methods for navigation.

Methods

public static T

To<T>(T pageObject = null, string url = null, bool navigate = true, bool temporarily = false)

where T : PageObject<T>

Navigates to the specified page object.

public static T

ToWindow<T>(T pageObject, string windowName, bool temporarily = false)

where T : PageObject<T>

Navigates to the window with the specified page object by name.

public static T

ToWindow<T>(string windowName, bool temporarily = false)

where T : PageObject<T>

Navigates to the window by name.

public static T

ToNextWindow<T>(T pageObject = null, bool temporarily = false)

where T : PageObject<T>

Navigates to the next window with the specified page object.

public static T

ToPreviousWindow<T>(T pageObject = null, bool temporarily = false)

where T : PageObject<T>

Navigates to the previous window with the specified page object.

public static void

ToUrl(string url)

Navigates to the specified URL.

Usage

Go.To<HomePage>().
    Header.Should.Equal("Home");

Go.To<AboutPage>(url: "about").
    Header.Should.Equal("About");

Transition

The transition from one page object to another is implemented via controls: Button, Link and Clickable. Also the transition can be specified via adding INavigable<,> interface to the custom control.

Example

For example, having 3 simple pages on the site:

  • Users” page with users table and “New” link that navigates to the user editor page. Clicking on the user row redirects to the “User Details” page.
  • User Editor” page that contains “Name” input field, “Save” and “Cancel” buttons that redirect back to “Users” page.
  • User Details” page containing the name of the user.

UsersPage.cs

using Atata;

namespace SampleApp.UITests
{
    using _ = UsersPage;

    [Url("users")]
    public class UsersPage : Page<_>
    {
        public Link<UserEditorPage, _> New { get; private set; }

        public Table<UserTableRow, _> Users { get; private set; }

        public class UserTableRow : TableRow<_>, INavigable<UserDetailsPage, _>
        {
            public Text<_> Name { get; private set; }
        }
    }
}

UserEditorPage.cs

using Atata;

namespace SampleApp.UITests
{
    using _ = UserEditorPage;

    public class UserEditorPage : Page<_>
    {
        public TextInput<_> Name { get; private set; }

        public Button<UsersPage, _> Save { get; private set; }

        public Button<UsersPage, _> Cancel { get; private set; }
    }
}

UserDetailsPage.cs

using Atata;

namespace SampleApp.UITests
{
    using _ = UserDetailsPage;

    public class UserDetailsPage : Page<_>
    {
        public H1<_> Name { get; private set; }
    }
}

Usage

string userName;

Go.To<UsersPage>().
    New.ClickAndGo(). // Navigates to UserEditorPage.
        Name.SetRandom(out userName). // Sets the random value to Name field and stores it to userName variable.
        Save.ClickAndGo(). // Clicking the Save button navigates back to UsersPage.
    Users.Rows[x => x.Name == userName].ClickAndGo(). // Clicking the row navigates to UserDetailsPage.
        Name.Should.Equal(userName);