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:
- Chrome: https://chromedriver.chromium.org/downloads or https://googlechromelabs.github.io/chrome-for-testing/
- Firefox (Gecko): https://github.com/mozilla/geckodriver/releases
- Edge: https://developer.microsoft.com/fr-fr/microsoft-edge/tools/webdriver/
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 isform-field
which is rendered ascheckbox
:
<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
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 theStartup
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.