Cmobilecom AF 5.19 Developer Guide

25 Test Automation

Tests are written using Cmobilecom AF testing API which is platform independent test automation API to simulate user interaction with applications. It is based on Selenium for web.

Directory Structure

Add test under application root directory.
	application-root-dir/
		web/
			settings.gradle
			build.gradle
		test/
			src/test/
				java/
					com/example/test/
						MyInstanceTypeTest.java
						MyInstanceTest.java
				resources/
					test.properties
			test-automation/
				webdriver/
				fileupload/
					<module_name>/
			build.gradle
If running tests on host without docker, put web drivers under the directory test/test-automation/webdriver/.
	                        Web Driver Executables
	Driver Types     Windows                  Linux/Mac
	-------------------------------------------------------
	chrome           chromedriver.exe         chromedriver
	firefox          geckodriver.exe          geckodriver
	edge             edgedriver.exe           edgedriver
	sarafi           safaridriver.exe         safaridriver
	opera            operadriver.exe          operadriver
The fileupload directory is the place to put files for upload.

Build and Run

Add test project to application multi-project build.

settings.gradle under root project directory web/:

	include 'test'
	project(':test').projectDir = new File('../test')
test project build.gradle
	apply from : "${rootProject.cmobilecomAFBuildCommonDir}/test.gradle"
Run tests:
	gradlew :test:test [options]
options: See Project Build for running tests with docker and without docker.

InstanceType Test

An InstanceType test is a JUnit test consisting of a number of instance tests. For example,

@RunWith(Parameterized.class)
public class HRTest extends InstanceTypeTest {

	public HRTest(TestConfig testConfig) {
		super(testConfig);
	}

	@Override
	public String getInstanceTypeId() {
		return "hr";
	}

	@Override
	protected InstanceTest createInstanceTest(InstanceIdentifier instanceIdentifier, ManageCenter manageCenter) {
		return new HRInstanceTest(this, instanceIdentifier, manageCenter);
	}

	@Test
	public void testHR() {
		Locale mainUserLocale = getLocale(InstanceIdentifier.Main);

		prepare();

		// register user (instance creator) for main instance
		registerUser(UserIdentifier.Main, true, mainUserLocale, InstanceIdentifier.Main);

		// register regular user
		registerUser(userEmployeeOne, false, mainUserLocale, null);

		HRInstanceTest mainInstanceTest = (HRInstanceTest) loginOrCreateInstance(
				InstanceIdentifier.Main, null, this.pageResource, false);
		mainInstanceTest.run();
	}
}
The example HR InstanceType test above registers two users, create the main instance and then login to run the main instance test. Each instance is assigned a unique InstanceIdentifier, and each user is assigned a unique UserIdentifier. InstanceIdentifier.MAIN is predefined for the main instance, and UserIdentifier.Main is predefined for the main user.

Override the following method to provide profile for each user, which is used to fill out User Registration form. For example,


	public static UserIdentifier userEmployeeOne = UserIdentifier.valueOf("EmployeeOne");

	@Override
	public UserProfile getUserProfile(UserIdentifier userIdentifier) {
		String usernamePrefix = getUsernamePrefix(userIdentifier);
		String name = userIdentifier.getName() + " Test User";
		String username = Configurations.getNextUsername(usernamePrefix, false);
		String email = username + "@" + Configurations.getDomain(getInstanceTypeId());
		String password = Configurations.getInstancePassword();
		return new UserProfile(name, username, email, password);
	}

	private String getUsernamePrefix(UserIdentifier userIdentifier) {
		String usernamePrefix;
		if (userIdentifier == UserIdentifier.Main)
			usernamePrefix = "main-user-";
		else if (userIdentifier == userEmployeeOne)
			usernamePrefix = "emp1-user-";
		else
			throw new IllegalArgumentException("unknown user identifier: " + userIdentifier);

		return usernamePrefix;
	}
Each time when an InstanceType test is run, new users will be created. A username consists of prefix and number: usernamePrefix-userNo. If userNo is not specified, the test will search user table to find the next available user number. Override the following method to specify the username prefix for the query.

	@Override
	protected String getUsernamePrefixForAvailableUserNumber() {
		return "main-user-"
	}
Login to an existing instance for re-running a previous test or create a new instance. For example,

	HRInstanceTest mainInstanceTest = (HRInstanceTest) loginOrCreateInstance(
			InstanceIdentifier.Main, null, this.pageResource, false);
The MAIN user is registered as the creator of the MAIN instance.

	registerUser(UserIdentifier.Main, true, mainUserLocale, InstanceIdentifier.Main);
So if the instance created by the MAIN user is not found, a new instance will be created.

Instance Test

An instance test is invoked by its InstanceType test to run a number of module tests. For example,

public class HRInstanceTest extends InstanceTest {

	public HRInstanceTest(InstanceTypeTest instanceTypeTest,
			InstanceIdentifier instanceIdentifier, ManageCenter manageCenter) {
		super(instanceTypeTest, instanceIdentifier, manageCenter);
	}

	@Override
	protected List<Class<? extends ModuleTest>> getModuleTests() {
		return Arrays.asList(SystemTest.class, HRModuleTest.class);
	}
}
An InstanceTest needs to override getModuleTest() method to provide the list of ModuleTest classes. The order is the run order of module tests.

By default, An InstanceTest will call run() method of all module tests, which then call run(ExecPointRange) method. Override the following method to change this default behavior. For example,

	@Override
	protected void runModuleTest(ModuleTest moduleTest) {
		if (moduleTest instanceof SystemTest) {
			SystemTest systemTest = (SystemTest) moduleTest;
			Parameters parameters = getParameters();
			systemTest.setParameters(parameters);
			return true;
		}

		super.runModuleTest(moduleTest);
	}

Module Test

Add test sourceSet to module project:
	moduleRootDir/src
			main/
			test/
				java/com/example/test/MyModuleTest.java
				resources/bundle/
					messages*.properties
Module test jars will be built and added to the compilation and runtime classpath of InstanceType tests.

If one module test(say Module "A") has dependency on another module test(say Module "B"), add the following dependencies in the build.gradle of module A.

dependencies {
	testImplementation project(path: ':module-b', configuration: 'moduleTestJar')
}
Module main jar will be added to the runtime classpath of InstanceType tests, which include module resource bundles.

A module test must extend ModuleTest, define ExecPoint enum type and specify the ExecPoint type as actual generic type parameter. For example,


public class HRModuleTest extends ModuleTest<ExecPoint> {

	public enum ExecPoint {
		create_employees,
		create_expenseClaims,
		show_expenseClaims,
		verify_expenseReport,
		change_theme_and_locale,
		batchPrint_expenseClaims
	}

	public HRModuleTest(InstanceTest instanceTest) {
		super(instanceTest);
	}

	@Override
	public String getModuleName() {
		return "HR";
	}

	@Override
	public void run(ExecPointRange execPointRange) {
		// init timeZone and currencyCode
		ManageCenter manageCenter = getManageCenter();
		manageCenter.getTimeZone();
		manageCenter.getCurrencyCode();

		// run the exec points within the range
		for (int i = execPointRange.getStart().ordinal(); i <= execPointRange.getEnd().ordinal(); i++) {
			ExecPoint execPoint = ExecPoint.values()[i];
			if (execPoint == ExecPoint.create_employees)
				createEmployees();
			else if (execPoint == ExecPoint.create_expenseClaims)
				createExpenseClaims();
			else if (execPoint == ExecPoint.show_expenseClaims)
				showExpenseClaims();
			else if (execPoint == ExecPoint.verify_expenseReport)
				verifyExpenseReport();
			else if (execPoint == ExecPoint.change_theme_and_locale)
				changeThemeAndLocale();
			else if (execPoint == ExecPoint.batchPrint_expenseClaims)
				batchPrint_expenseClaims();
		}
	}
The module name must be the same as the module name on server side, case-sensitive.

Exec Points

A module test can take a long time. ExecPoints are of enum type and defined for test to start to run from a certain ExecPoint of a module instead of from the beginning. The command line option -PgotoExec is used to define the starting ExecPoint. All the module tests and exec points before the starting ExecPoint will be skipped. See Build And Run section for ExecPoint format on command line.

The ExecPointRange of the run() method is the range after -PgotoExec is applied. The ordinal values of ExecPoints is the run order. Module test run() method goes through all the ExecPoints in the range, and call corresponding methods.

Id Rule

For those entity types that support ID rule, their IdRules must be defined, and they will be created in persistence. For example,

	public static IdRuleEntityType typeEmployee = IdRuleEntityType.valueOf("HR", "Employee");
	public static IdRuleEntityType typeExpenseClaim = IdRuleEntityType.valueOf("HR", "ExpenseClaim");

	@Override
	protected Map<IdRuleEntityType, IdRule> getIdRules() {
		IdRuleEntityType[] idRuleEntityTypes = new IdRuleEntityType[] {
				typeEmployee, typeExpenseClaim
		};

		Map<IdRuleEntityType, IdRule> idRules = new HashMap<>();
		for (IdRuleEntityType entityType : idRuleEntityTypes) {
			int idLen = (entityType == typeEmployee)? 5 : 10;
			IdRule idRule = new IdRule(entityType, InstanceTest.BarcodeType.CODE128, null, idLen,
					"#{sn:" + idLen + "}", 1);

			idRules.put(entityType, idRule);
		}

		return idRules;
	}
Entity type names will be translated using the resource bundle of current user locale, and they must match the entity type display names on server side. For example, entity type Employee's name is Employee on both server side and test code.

Test Clock

The default clock for testing is the server host clock. If the timestamp for an entity needs to be adjusted for testing, for example, change transaction date (e.g., createdDate that is not editable) to a timestamp in next month, then test clock needs to be adjusted. For example,

	TestClock testClock = moduleTest.getTestClock();
	testClock.advanceDays(days, reset);
	testClock.advanceHours(hours, reset);
	testClock.advanceMillis(millis, reset);
	testClock.advanceToNextMonth(timeZoneId, dayOfMonth);
minus values will adjust test clock backward. The parameter reset (boolean) indicates whether to remove all previous adjustments. See javadoc for details.

Test clock can be taken a snapshot at a certain timestamp, and restored at later time.


	testClock.takeSnapshot(name, millsToAdd);
	// do something, then restore the clock
	testClock.advanceToSnapshot(name, millisToAdvance);
The millsToAdd and millisToAdvance are adjustment to the snapshot.

If an InstanceType test needs to create entities with different timestamps across a number of days in current month, call the following method at the start of the test to adjust the test clock.


	testClock.ensureDaysAvailableInCurrentMonth(timeZoneId, daysAvailable, hoursAvailableInDay);
For example, a test needs to create 10 Orders on 10 consecutive days in current month, and today in Mar 25. So there is not enough days left on current month. Suppose it takes less than 2 hours to run the test.

	testClock.ensureDaysAvailableInCurrentMonth(timeZoneId, 10, 2);
The timeZoneId is the time zone of current instance test. The hour adjustment is to make sure the test will not run to the next day for the time zone. Otherwise, days available in current month can not be guaranteed.

One test clock for an InstanceType test and its instance tests. That is, if a module test adjusted test clock, it actually adjusted the test clock for all other module tests in the same InstanceType test. Note that server host clock is not affected, and test clock is not available for production.

Component Hierarchy

Test API component class hierarchy:
                                    Component
           ---------------------------------------------------------------
           |                            |                  |             |
          Bean                   EntityProperty      ContainerBean    MenuNode
         /    \                                            |
  MenuBean    PersistenceDataBean                      DialogBean
                /        \
         EntityBean    EntityListBean
Test component hierarchy and composition are the same as logical view API for server.

EntityProperties and MenuNodes are leaf components for inputs and actions. For an EntityProperty, user(test) can input value, select single or multiple options, open dialog to select or create entities, click property value as command link, respond to captcha, etc. Property value change or action can trigger partial behavior events (send request to server).

For a MenuNode, user(test) can click menu node to trigger partial behavior events (send request to server), open its inputDataBean, open child menu nodes, etc.

The ways to get root ContainerBean:

	// ModuleTest
	ContainerBean containerBean = getManageCenter().getContainerBean();

	// Load a page using PageResource
	ContainerBean containerBean = instanceTypeTest.pageResource.getPage(dataAccessUnit, url, locale);
One PageResource is mapped to one driver instance (e.g., web browser instance). To create a new PageResource (e.g., open a new web browser) to load pages:

	PageResource pageResource = new PageResource(instanceTypeTest.getTestConfig());
	// create pageLoader
	pageResource.before();

	ContainerBean containerBean = pageResource.getPage(dataAccessUnit, url, locale);

Property Value Input

User(test) inputs property value based on view component types.

Set Value

Set property value for inputText view component. For example,

	//EntityProperty:
	//    void setValue(String query);

	employeeBean.getEntityProperty("name").setValue("Employee One" + "\t");
Special keys RETURN(\n) and TAB(\t) are supported at the end of input value, which can trigger partial behavior events (action or value change).

Auto Complete

Select one or more items from dropdown suggestions of an auto complete view component. For example,

	//EntityProperty:
	//    void autoComplete(String query, int selectItemIndex);
	//    void autoComplete(String query, String pattern, boolean exactMatch, boolean visibleText);

	expenseClaimBean.getEntityProperty("employee").autoComplete("123456", 0);
	expenseClaimBean.getEntityProperty("employee").autoComplete("123", "Employee One", false, true);

Boolean Checkbox

Set property value to true/false for a checkbox view component. For example,

	//EntityProperty:
	//    void setChecked(boolean checked);

	EntityProperty resetPasswordEP = parametersBean.getEntityProperty("resetPassword");
	resetPasswordEP.setChecked(true);

Select One

Select one option from a selectOne view component. For example,

	//EntityProperty:
	//    void selectOption(String pattern, boolean exactMatch, boolean visibleText);
	//    void selectOptionNull();

	addressBean.getEntityProperty("country").selectOption("US", true, false);
	addressBean.getEntityProperty("country").selectOptionNull();

Select Many

Select multiple options from a selectMany view component. For example,

	//EntityProperty:
	//    void selectOptions(List<String> patterns, boolean exactMatch, boolean visibleText);
	//    void selectAllOptions();

	EntityProperty groupByEP = queryBean.getEntityProperty("groupByProperties");
	groupByEP.selectOptions(Arrays.asList("employee", "code"), true, false);

Select/Upload Files

Select files for upload. For example,

	//EntityProperty:
	//    	void selectFiles(List<String> files);

	EntityProperty uploadedFilesEP = fileUploadBean.getEntityProperty("uploadedFiles");
	uploadedFilesEP.selectFiles(files);

	MenuNode uploadFilesMenuNode = fileUploadBean.getFooterMenu().getMenuNodeWithCommand("Upload");
	uploadFilesMenuNode.click();
Upload files as attachments of an entity. For example, upload all image files with a filter under a directory as employee photos.

	//EntityProperty:
	//    void uploadFiles(List<String> files);
	//    void uploadFiles(File dir, FilenameFilter filter);

	FilenameFilter filenameFilter = new FilenameFilter() {
		@Override
		public boolean accept(File dir, String name) {
			return name.startsWith("employee-") && name.endsWith(".jpg");
		}
	};

	EntityProperty photosEP = employeeBean.getEntityProperty("photos");
	photosEP.uploadFiles(dir, filenameFilter);

Open Input Dialog

Open input dialog for selecting or creating associated entities.
Select existing entities
For example, open dialog to select an existing user as the employee's user.

	//EntityProperty:
	//    DialogBean openInputDialog(boolean showCreateEntityForm);

	EntityProperty userEP = employeeBean.getEntityProperty("user");
	// select user
	DialogBean userDialogBean = userEP.openInputDialog(false);
	userDialogBean.selectEntity("username", "employee-user-1"); // dialog closed
Create and select new entities
For example, open dialog to create a Department entity and select it as the employee's department.

	//EntityProperty:
	//    DialogBean openInputDialog(boolean showCreateEntityForm);

	EntityProperty departmentEP = employeeBean.getEntityProperty("department");
	DialogBean deptDialogBean = departmentEP.openInputDialog(true);
	EntityBean departmentBean = (EntityBean) deptDialogBean.getDefaultContentBean();
	departmentBean.getEntityProperty("name").setValue(bundle.getString("HR"));
	departmentBean.getEntityProperty("manager").setValue(bundle.getString("HRManager"));
	departmentBean.create(false);

	// select department
	departmentBean.selectAsValueOf(departmentEP);

Click Captcha

Click captcha for responding to captcha challenge. Captcha keys for testing will be used for test automation in Security Settings. For example,

	EntityProperty captchaProperty = userBean.getEntityCaptchaProperty(false);
	if (captchaProperty != null)
		captchaProperty.clickCaptcha();

Scan Barcode

Scan barcode to add an entity to a EntityListBackingBean. For example, add a product to an order.

	//EntityListBean:
	//    void scanBarcode(String barcode);

	EntityListBean orderItemListBean = (EntityListBean) orderBean.getFormBean("orderItems");
	orderItemListBean.scanBarcode(barcode);
	orderItemListBean.waitUntilStale(true);

Wait Until Condition

For each interaction, test needs to wait until an expected condition becomes true.

Wait Until Stale

After sending a request to server, wait until a component becomes stale, which means that the client view has been updated and the request is completed.

When a bean is added to (or removed from) a Region, the region will become stale. For example,


	MenuBean containerMenuBean = getContainerMenuBean();
	Region defaultContentRegion = getDefaultContentRegion();

	MenuNode employeesMenuNode = containerMenuBean.getMenuNode("HR", "Settings", "Employees");
	employeesMenuNode.click();

	EntityListBean employeeListBean =  (EntityListBean) defaultContentRegion.waitUntilStale(true).getBean(0);

After clicking the menu node (HR > Settings > Employees), wait until the content region becomes stale and updated. Then get the rendered employee list bean.

The Component base class has method waitUntilStale(refresh). For example,


	EntityListBean expenseClaimItemListBean = (EntityListBean) expenseClaimBean.getFormBean("expenseClaimItems");
	EntityProperty statisticsExpenseEP = expenseClaimItemListBean.getStatisticsEntityProperty("expense");

	expenseClaimItemListBean.getEntityProperty(0, "expense").setValue("1000\t");
	statisticsExpenseEP.waitUntilStale(true);
	statisticsExpenseEP.assertEquals(BigDecimal.valueOf(1000), currencyNumberFormat);
After entering the expense amount and press TAB key, the total expense will be updated.

Wait Until Visible/Clickable

In addition to waitUntilStale(), the Component base class has the following wait methods:

	component.waitUntilVisible() // wait until the component becomes visible
	component.waitUntilInvisible() // wait until the component becomes invisible
	component.waitUntilClickable() // wait until the component becomes clickable
For example,

	MenuNode changeThemeMenuNode = pageHeaderMenuBean.getMenuNodeWithCommand("ChangeTheme");
	changeThemeMenuNode.click();
	EntityBean inputDataBean = changeThemeMenuNode.getInputDataBean();
	inputDataBean.waitUntilVisible();

Wait Until Dialog Open/Close

If a dialog is opened (or closed) by server, test needs to wait util the dialog is opened (or closed) on client. For example,

	// dialog opened by server
	DialogBean dialogBean = containerBean.waitUntilDialogOpen();

	// dialog closed by server
	containerBean.waitUntilDialogClose();

	// e.g., create an entity in dialogBean

	// dialog closed by user (test)
	dialogBean.close();
If dialog is closed by user(test), do not need to explicitly call wait method. A dialog is opened because of a request (e.g., click a menu node), so user(test) can not open a dialog directly.

Wait Until Window Open/Close

A window is equivalent to a browser tab for web. After a child window is open, switch page loader context to the child window to get its ContainerBean. When a child window is closed, it will be switched back to its parent window.

	// click to open window
	openWindowLink.click();
	ContainerBean childWindowContainerBean = containerBean.switchToChildWindow();

	// close and switch to parent window
	childWindowContainerBean.closeWindow();
To wait util a child window is open or closed, pass the expected number of windows after the child window is open or closed.

	// numberOfWindows is the number of windows before opening child window
	String windowHandle = containerBean.waitUntilWindowOpen(numberOfWindows + 1);
	ContainerBean childWindowContainerBean = containerBean.switchToChildWindow(windowHandle);

	// wait until child window to close and switch to parent window
	childWindowContainerBean.waitUntilWindowClose(numberOfWindows);

Wait Until Value Matches

If a EntityProperty's value is changed after a partial behavior is completed, test needs to wait for the property to become stale and optionally verify its new value. For example,

	EntityProperty nameEP = employeeBean.getEntityProperty("name");
	nameEP = nameEP.waitUntilValueToBe(true/*waitUntilStale*/, "Hello World", true/*visibleText*/);

Wait Until Value Editable

If a EntityProperty editable mode is changed after a partial behavior is completed, test needs to wait for the property to become editable or read-only. For example,

	// toggle edit row
	EntityProperty quantityEP = orderItemListBean.getEntityProperty(rowIndex, "quantity");
	orderItemListBean.toggleRowEditMode(rowIndex);

	// wait quantity property to become editable
	quantityEP.waitUntilEditable(true/*refresh*/);

	// wait quantity property to become not editable
	quantityEP.waitUntilReadOnly(true/*refresh*/);

Wait Until Message Matches

If the response of a request shows a message, test can wait for the message and assert the message content.

ContainerBean:


	public MessageItem waitUntilMessageContains(int messageItemIndex, String text, boolean title);
	public MessageItem waitUntilMessageMatches(int messageItemIndex, Pattern pattern, boolean title);

A containerBean has a MessagePanel that contains a number of MessageItem. Each MessageItem has message title and body.

For example,

		MessageItem messageItem = containerBean.getMessagePanel().getMessageItem(1);
		messageItem.close();

Assert Methods

All assert methods will be ignored if -PassertEnabled=false is specified on command line.

Assert an EntityProperty

Assert a property value to be expected: For example, assert order values when adding an order item.

	String currencyCode = manageCenter.getCurrencyCode();
	NumberFormat currencyNumberFormat = orderBean.getCurrencyNumberFormat(currencyCode, 2);
	EntityListBean orderItemListBean = (EntityListBean) orderBean.getFormBean("orderItems);

	// assert the subTotal value of the 2nd order item
	EntityProperty subTotalEP = orderItemListBean.getEntityProperty(1, "subTotal");
	subTotalEP.assertEquals(BigDecimal.valueOf(1000), currencyNumberFormat);

	// assert sum(subTotal) of all order items
	EntityProperty totalEP = orderItemListBean.getStatisticsEntityProperty("subTotal");
	totalEP.assertEquals(BigDecimal.valueOf(2000), currencyNumberFormat);
Both EntityBean and EntityListBean have convenient methods for asserting property values. For example,

	orderItemListBean.assertEquals(0, "subTotal", BigDecimal.valueOf(1000), currencyNumberFormat);

Assert Entity Count

Assert the size of pageable entity list. For example,

	EntityListBean employeeListBean = containerBean.showEntityList("HR", "Settings", "Employees");
	employeeListBean.assertEntityCount(100);

Re-run Test

To rerun a previous test that has created users, the registered users need to be added to the InstanceType test. For example,

@RunWith(Parameterized.class)
public class HRTest extends InstanceTypeTest {
	@Test
	public void testHR() {
		Locale mainUserLocale = getLocale(InstanceIdentifier.Main);

		boolean rerunTest = isRerunTest();
		if (!rerunTest) { // run tests from scratch
			prepare();

			// register user for main instance
			registerUser(UserIdentifier.Main, true, mainUserLocale, InstanceIdentifier.Main);
			registerUser(userEmployeeOne, false, mainUserLocale, null);
		}
		else { // use existing instances
		
			addRegisteredUser(UserIdentifier.Main, InstanceIdentifier.Main);
			addRegisteredUser(userEmployeeOne, null);
		
		}

		HRInstanceTest mainInstanceTest = (HRInstanceTest) loginOrCreateInstance(
				InstanceIdentifier.Main, null, this.pageResource, false);
		mainInstanceTest.run();
	}
}

Test Properties

Test properties include domains, ports, contextPath, usernames, passwords, 2FA, etc. For example.

	domain.system=system.Cmobilecom-AF-Examples.com
	domain.hr=hr.Cmobilecom-AF-Examples.com

	# subdomain=test/3

	protocol=http
	port.http=8080
	port.https=8443

	# contextPath: default [software-name]-[software-version]
	contextPath=

	system.username=system
	system.password=123456
	instance.password=Welcome123&

	auth.twoStepAuth=true

	# whether to open print preview dialog for printing test
	printView.openPrintDialog=false
The contextPath for page URLs defaults to <software-name>-<software-version> that are defined in build.properties. If contextPath is root, set it to empty.

Domains

The domains for instance types must be the same as those in conf/system-config.xml. The subdomain is used to transform the domains. Add a subdomain to all the domains of instance types so that domains for test automation are different from production. For test automation, a JVM property (test.subdomain) will be added to server startup. For wildcard domain SSL, the number of domain components supported by the wildcard domain SSL can be specified after the subdomain name separated by forward slash. If a resultant domain after adding the subdomain exceeds the number, the subdomain will be a prefix. For example,
	domain.system = example-domain.com
	domain.app1 = app1.example-domain.com
	subdomain = test/3
The domain example-domain.com will become test.example-domain.com, and the domain app1.example-domain.com will become test-app1.example-domain.com.
	example-domain.com       --->  test.example-domain.com
	app1.example-domain.com  --->  test-app1.example-domain.com
Default is unlimited levels of subdomains. That is, if subdomain=test, then
	example-domain.com       --->  test.example-domain.com
	app1.example-domain.com  --->  test.app1.example-domain.com

Passwords

The system username and password must match those for the installation. default is system/123456.

The instance password will be used as the password of the user registrations. It must follow the Password Policy set in conf/system-config.xml.

Two-Step Auth

If property auth.twoStepAuth is set to true, two-step auth for login will be enabled. A verification code will be shown as a message for test automation.

Resource Bundles

To find menu nodes by visible text, select options by visible text or verify visible text value on view, the following resource bundles will be loaded.

Examples

See Cmobilecom AF examples on writing tests and javadoc for API references.
User AgentFrames / No Frames