Table of Contents

UI Automation

Introduction

Automation tests are written on the basis of a UI view template. They are independent of the device and the rendering technology of the UI.

An automation test is associated to a UI scenario of a feature.
This test is written in C# as a xUnit test in a Visual Studio project.

A visual studio solution is created per module. This solution includes one project per feature group. This project has one test class per Feature. This class contains one test per UI scenario of the Feature.

Synchronization between Neos Studio and Visual Studio

Scenarios are synchronized with Visual Studio projects when Visual Studio is opened or when the neos sync uitests command is run.
Synchronization consists of creating the test class if it does not exist or updating the test class according to the UI scenario defined in NeosStudio.
Synchronization may also be necessary when updating the Neos Framework.

  • Synchronization of all cluster tests
neos sync uitests
  • Synchronization of the MyModule tests of the cluster
neos sync uitests --module MyModule
  • Synchronization of the cluster's MyFeature tests
neos sync uitests --feature MyFeature

Choice of the URL of the cluster to test

By default the URL of the application is https://localhost/neos/[ClusterName]. The environment variable NEOS_AUTOMATION_URL allows to define the URL.

Choice of browser

The tests can be executed by the Chrome or Firefox browser. The default browser is Chrome. The environment variable NEOS_AUTOMATION_BROWSER allows to define the browser used:

Values of NEOS_AUTOMATION_BROWSER:

  • CHROME
  • FIREFOX

Browser language

By default, The browser language is configured with the cluster default language. The environment variable NEOS_AUTOMATION_LANG allows to define the language to use.

Browser version

From Neos Version 1.17, for Chrome and Firefox a browser version can be specified. The environment variable NEOS_AUTOMATION_BROWSER_VERSION allows to define the browser version. If the version is not specified the current installed version is used. Under the hood selenium-manager (4.12) is used to manage the web-drivers.

For Chrome you must specify a version greater than or equal to 113. The version format is the major version number.

For example :

  • Chrome : 113, 114, 115, 116
  • Firefox: 110, 111, 112, 113
Note

Edge does not support the browser version specification.

Note

When the browser version is specified, the environment variables CHROMEWEBDRIVER or GECKOWEBDRIVER are not used to find the web-driver location.

Installation of the web-drivers

For the automation of the web application, web drivers compatible with the installed browser version are required.

Automatic installation

If the web driver of the current browser installation is not found the web driver is automatically downloaded and installed when the first test is run (except if a web-driver is specified by a environment variable).

If the browser version is specified, the web driver is automatically downloaded and installed too.

For others cases, the web driver must be installed manually :

Manual installation

The web drivers can be downloaded at the following addresses:

It is necessary to download the web-driver for the target OS and browser version.

The environment variable CHROMEWEBDRIVER defines the path to the folder containing the Chrome webdriver. The environment variable GECKOWEBDRIVER defines the path to the folder containing the Firefox webdriver. The environment variable EDGEWEBDRIVER defines the path to the folder containing the Edge webdriver.

Example for Windows:
The c:\webdrivers folder contains the files chromedriver.exe and geckodriver.exe. The value of the environment variables CHROMEWEBDRIVER and GECKOWEBDRIVER is c:\webdrivers.

xUnit test

The unit test class inherits from the class GroupeIsa.Neos.EndToEndTests.UI.Test.AutomationTest.

The test created by NeosStudio is a method with the attribute [Fact] if the scenario is active.

The xml help includes the Given-When-Then template.

Example :

/// <summary>
/// The order list UI
/// When I open the UI view
/// Then The input date "Date begin" is focused by default.
/// </summary>
[Fact]
public async Task DefaultFocusTest()
{
    await Automation.Launch();

    IFrameComponent frame = await Automation.OpenUIViewByMenuEntry("SalesAndCatalog/Sales/Orders", false);

    IContentComponent root = await frame.GetContent();

    IInputDateComponent inputDateBegin = root.GetComponent<IInputDateComponent>(p => p.Attribute("label")?.Value == "@Resources.Sales.OrderDateFilterBegin");

    inputDateBegin.IsActiveElement.Should().Be(true);
}

Automation API

Launching the application

The Automation property (IAutomationDevice) of the AutomationTest class allows access to the basic automation features of the device.

await Automation.Launch();

Opening a UI view

The opening of a UI view of the cluster is done by simulating the use of the menu with the method IAutomationDevice.OpenUIViewByMenuEntry

IFrameComponent frame = await Automation.OpenUIViewByMenuEntry("SalesAndCatalog/Sales/Orders");

The first parameter is the menu entry path. This path is built with the syntax {MenuName}/{menuItemName}/{childMenuItemName}/....

In the web application this path corresponds to the data-path attribute, you can use the browser development tools to view this path:

The frame containing the UI view is returned by this method.

Component search by LinqToXml

Components that can contain other components implement the [IContentComponent] interface (xref:GroupeIsa.Neos.EndToEndTests.UI.Components.IContentComponent).

This interface provides methods to search for a component.

GetComponent

See IContentComponent.GetComponent.

Returns the first component of the given type present in the tree of child components of this component.
The optional argument predicate allows to perform a search in the xml tree of the template by a LinqToXml condition (condition of the where clause): see Basic Queries (LINQ to XML)

If no match is found an exception AutomationException is thrown.

Examples

  • Search for a checkbox, the Abstract property of the entity is of type boolean, the abstract component is form-field which is rendered as checkbox :
 <group-box-content>
    <vertical-layout>
        <form-field property-name="Abstract" />
        <form-field property-name="BaseEntityName" />
    </vertical-layout>
</group-box-content>
frameContent.GetComponent<ICheckboxComponent>(c => c.Attribute("property-name")?.Value == "Abstract"
  • Search a data grid by the path from the root element :
<vertical-layout layout:height="fill">
  <group-box>
      <group-box-header><text>@Resources.Sales.OrderFilter</text></group-box-header>
      <group-box-content>
          <horizontal-layout>
              <input-date label="@Resources.Sales.OrderDateFilterBegin" value="@Computeds.FilterBeginDate"/>
              <input-date label="@Resources.Sales.OrderDateFilterEnd" value="@Computeds.FilterEndDate"/>
              <lookup label="@Resources.Sales.CustomerFilter" value="@Fields.CustomerIdFilter" name="customerLookup"/>
              <button type="refresh"/>
         </horizontal-layout>
       </group-box-content>
  </group-box>
  <heading level="1">@Title</heading>
  <toolbar />
  <datagrid />
  <pagination-bar />
</vertical-layout>
IDatagridComponent grid = frameContent.GetComponent<IDatagridComponent>(c => c.GetRootPath() ==  "vertical-layout/datagrid");

FindComponent

See IContentComponent.FindComponent.

Performs the same search as GetComponent, but returns null if no match was found.

GetComponents

See IContentComponent.GetComponents

Searches for all components satisfying the LinqToXml condition.

Warning

This method should not be used to search for a particular element, for example by chaining its call with a LinqToObject query. Searching for components requiring DOM inspection.

Examples

IEnumerable<IGroupBoxComponent> groupBoxes = frameContent.GetComponents(c => c.GetRootPath() == "vertical-layout/grid-layout/grid-layout-content/horizontal-layout/if/vertical-layout/group-box").OfType<IGroupBoxComponent>();

FindFocusedComponent

See IAutomationDevice.FindFocusedComponent

Finds the focused component.
Returns the component whose child element is the active element.
If the active element does not belong to a component, returns null.

Examples

ITextBoxComponent textbox = frameContent.GetComponent<ITextBoxComponent>(c => c.Attribute("property-name")?.Value == "Name");
textbox.SendKeys(KeyboardKeys.Tab);

ITextBoxComponent? focusedTextBox = Automation.FindFocusedComponent<ITextBoxComponent>();

Component search by Id

To uniquely identify an Xml node in the template, it is possible to add a unique id for its automation. The id value is defined in an attribute named id in the automation namespace (since version 1.21, a default automation id is generated whenever possible).

Example

The datagrid node is uniquely identified by the id mainDatagrid:

<vertical-layout layout:height="fill">
  <group-box>
      <group-box-header><text>@Resources.Sales.OrderFilter</text></group-box-header>
      <group-box-content>
          <horizontal-layout>
              <input-date label="@Resources.Sales.OrderDateFilterBegin" value="@Computeds.FilterBeginDate"/>
              <input-date label="@Resources.Sales.OrderDateFilterEnd" value="@Computeds.FilterEndDate"/>
              <lookup label="@Resources.Sales.CustomerFilter" value="@Fields.CustomerIdFilter" name="customerLookup"/>
              <button type="refresh"/>
         </horizontal-layout>
       </group-box-content>
  </group-box>
  <heading level="1">@Title</heading>
  <toolbar />
  <datagrid automation:id="mainDatagrid"/>
  <pagination-bar />
</vertical-layout>

GetComponentById

See GetComponentById

IDatagridComponent grid = frameContent.GetComponentId<IDatagridComponent>("mainDataGrid");

If no match was found an AutomationException is thrown.

FindComponentById

See FindComponentById

IDatagridComponent? grid = frameContent.FindComponentById<IDatagridComponent>("mainDataGrid");

Performs the same search as GetComponentById, but returns null if no match was found.

Keyboard events

To simulate keyboard events, first create an instance of IAutomationAction via the method IAutomationDevice. CreateActions.

We can then chain the available actions into IAutomationAction.
The actions are executed only when calling the IAutomationAction.Perform

Example :
Simulation of Alt+N keyboard shortcut (creation)

IAutomationAction actions = Automation.CreateActions();
actions.KeyDown(KeyboardKeys.Alt).SendKeys("n").KeyUp(KeyboardKeys.Alt).Perform();

Referring an external project

A reference to an external project can be added for all automation tests of the cluster. This project is added as a project reference to the Visual Studio solutions of the modules.

This project must be configured in the cluster configuration YAML file. The entry Csprojs.AutomationTest contains the array of .csproj files. The path of the .csproj file is relative to the path of the cluster.

Csprojs:
  AutomationTest:
    - ./projects/EndToEndTests/GroupeIsa.Neos.Northwind.AutomationTestHelpers/GroupeIsa.Neos.Northwind.AutomationTestHelpers/GroupeIsa.Neos.Northwind.AutomationTestHelpers.csproj

Authentication

For the automation tests to work on a cluster with authentication, you must indicate the information of the user with whom you want to run the tests. There are several ways to do this:

  • The user information can be set by the environment variables NEOS_AUTOMATION_USER_IDENTIFIER.
  • The user information can be set in the automation options initialized in the ConfigureServices method of the Startup class.
public void ConfigureServices(IServiceCollection services)
{
    AutomationOptions options = new("https://localhost/neos/Northwind")
    {
        WebBrowserLanguage = "en",
        UserIdentifier = "user@email.com",
    };
    options.ConfigureServices(services);
}
  • The user information can be set in the launch options in the test methods.
IContentComponent root = await Automation.Launch(new LaunchOptions() { UserIdentifier = "user@email.com" })

Under the hood

The automation tests for the Web application use Selenium WebDriver.