UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,109 lines (1,035 loc) 66.3 kB
/*! * OpenUI5 * (c) Copyright 2026 SAP SE or an SAP affiliate company. * Licensed under the Apache License, Version 2.0 - see LICENSE.txt. */ sap.ui.define([ 'sap/ui/test/Opa', 'sap/ui/test/OpaPlugin', 'sap/ui/test/PageObjectFactory', 'sap/ui/base/Object', 'sap/ui/test/launchers/iFrameLauncher', 'sap/ui/test/launchers/componentLauncher', 'sap/ui/core/routing/HashChanger', 'sap/ui/test/matchers/Matcher', 'sap/ui/test/matchers/AggregationFilled', 'sap/ui/test/matchers/PropertyStrictEquals', 'sap/ui/test/pipelines/ActionPipeline', 'sap/ui/test/_ParameterValidator', 'sap/ui/test/_OpaLogger', 'sap/ui/thirdparty/URI', 'sap/ui/base/EventProvider', 'sap/ui/qunit/QUnitUtils', 'sap/ui/test/autowaiter/_autoWaiter', 'sap/ui/dom/includeStylesheet', 'sap/ui/thirdparty/jquery', 'sap/ui/test/_OpaUriParameterParser', 'sap/ui/test/_ValidationParameters', 'sap/base/util/extend', 'sap/base/util/isPlainObject' ], function (Opa, OpaPlugin, PageObjectFactory, Ui5Object, iFrameLauncher, componentLauncher, HashChanger, Matcher, AggregationFilled, PropertyStrictEquals, ActionPipeline, _ParameterValidator, _OpaLogger, URI, EventProvider, QUnitUtils, _autoWaiter, includeStylesheet, $, _OpaUriParameterParser, _ValidationParameters, extend, isPlainObject) { "use strict"; var oLogger = _OpaLogger.getLogger("sap.ui.test.Opa5"), oActionPipeline = new ActionPipeline(), sFrameId = "OpaFrame", oValidator = new _ParameterValidator({ errorPrefix: "sap.ui.test.Opa5#waitFor" }), aConfigValuesForWaitFor = Object.keys(_ValidationParameters.OPA5_WAITFOR_CONFIG), aPropertiesThatShouldBePassedToOpaWaitFor = Object.keys(_ValidationParameters.OPA_WAITFOR), aExtensions = [], aEventProvider = new EventProvider(); /** * @class * UI5 extension of the OPA framework. * * Helps you when writing tests for UI5 apps. * Provides convenience to wait and retrieve for UI5 controls without relying on global IDs. * Makes it easy to wait until your UI is in the state you need for testing, for example waiting for back-end data. * * @extends sap.ui.base.Object * @param {object} [extensionObject] An object containing properties and functions. The newly created Opa will be extended by these properties and functions using jQuery.extend. * @public * @alias sap.ui.test.Opa5 * @see {@link topic:2696ab50faad458f9b4027ec2f9b884d Opa5} * @author SAP SE * @since 1.22 */ var Opa5 = Ui5Object.extend("sap.ui.test.Opa5", extend({}, Opa.prototype, { constructor: function (extensionObject) { Opa.apply(this, arguments); } })); /** * "and" property for chaining * @name sap.ui.test.Opa5.prototype.and * @member * @type {this} * @public */ Opa5._appUriParams = _OpaUriParameterParser._getAppParams(); Opa5._allUriParams = new URI().search(true); Opa5._oPlugin = new OpaPlugin(); function iStartMyAppInAFrame() { var oOptions = {}; var aOptions = ["source", "timeout", "autoWait", "width", "height"]; // allow separate arguments for backwards compatibility if (arguments.length === 1 && isPlainObject(arguments[0])) { oOptions = arguments[0]; } else { var aValues = arguments; aOptions.forEach(function (sOption, index) { oOptions[sOption] = aValues[index]; }); } // merge appParams over sSource search params if (oOptions.source && typeof oOptions.source !== "string") { oOptions.source = oOptions.source.toString(); } var uri = new URI(oOptions.source ? oOptions.source : ''); uri.search(extend( uri.search(true), Opa.config.appParams)); // kick starting the frame var oCreateFrameOptions = createWaitForObjectWithoutDefaults(); oCreateFrameOptions.success = function () { addFrame({ source: uri.toString(), width: oOptions.width || Opa.config.frameWidth, height: oOptions.height || Opa.config.frameHeight }); }; this.waitFor(oCreateFrameOptions); // wait till the frame is started var oFrameCreatedOptions = createWaitForObjectWithoutDefaults(); oFrameCreatedOptions.check = iFrameLauncher.hasLaunched; oFrameCreatedOptions.timeout = oOptions.timeout || 80; oFrameCreatedOptions.errorMessage = "unable to load the IFrame with the url: " + oOptions.source; this.waitFor(oFrameCreatedOptions); // load extensions var oLoadExtensionOptions = createWaitForObjectWithoutDefaults(); oLoadExtensionOptions.success = function () { this._loadExtensions(iFrameLauncher.getWindow()); }.bind(this); this.waitFor(oLoadExtensionOptions); // wait for the app to load var oWaitApplicationLoadOptions = createWaitForObjectWithoutDefaults(); oWaitApplicationLoadOptions.autoWait = oOptions.autoWait || false; oWaitApplicationLoadOptions.timeout = oOptions.timeout || 80; return this.waitFor(oWaitApplicationLoadOptions); } /** * Starts a UIComponent. * @param {object} oOptions An Object that contains the configuration for starting up a UIComponent. * @param {object} oOptions.componentConfig Will be passed to {@link sap.ui.core.UIComponent UIComponent}, please read the respective documentation. * @param {string} [oOptions.hash] Sets the hash {@link sap.ui.core.routing.HashChanger#setHash} to the given value. * If this parameter is omitted, the hash will always be reset to the empty hash - "". * @param {number} [oOptions.timeout=15] The timeout for loading the UIComponent in seconds - {@link sap.ui.test.Opa5#waitFor}. * @param {boolean} [oOptions.autoWait=false] Since 1.53, activates autoWait while the application is starting up. * This allows more time for application startup and stabilizes tests for slow-loading applications. * This parameter is false by default, regardless of the global autoWait value, to prevent issues in existing tests. * @returns {jQuery.promise} A promise that gets resolved on success. * * @since 1.48 If appParams are provided in {@link sap.ui.test.Opa.config}, they are * applied to the current URL. * * @public * @function */ Opa5.prototype.iStartMyUIComponent = function iStartMyUIComponent(oOptions) { var that = this; oOptions = oOptions || {}; // apply the appParams to this frame URL so the application under test uses appParams var oParamsWaitForOptions = createWaitForObjectWithoutDefaults(); oParamsWaitForOptions.success = function () { var uri = new URI(); uri.search(extend( uri.search(true), Opa.config.appParams)); window.history.replaceState({}, "", uri.toString()); }; this.waitFor(oParamsWaitForOptions); // kick starting the component var oStartComponentOptions = createWaitForObjectWithoutDefaults(); oStartComponentOptions.success = function () { // include stylesheet var sComponentStyleLocation = sap.ui.require.toUrl("sap/ui/test/OpaCss") + ".css"; includeStylesheet(sComponentStyleLocation); HashChanger.getInstance().setHash(oOptions.hash || ""); // wait till component is started var oComponentStartedOptions = createWaitForObjectWithoutDefaults(); oComponentStartedOptions.errorMessage = "Unable to load the component with the name: " + oOptions.componentConfig.name; if (oOptions.timeout) { oComponentStartedOptions.timeout = oOptions.timeout; } Opa.prototype._schedulePromiseOnFlow.call(that, componentLauncher.start(oOptions.componentConfig), oComponentStartedOptions); }; this.waitFor(oStartComponentOptions); // load extensions var oLoadExtensionOptions = createWaitForObjectWithoutDefaults(); oLoadExtensionOptions.success = function () { this._loadExtensions(window); }.bind(this); this.waitFor(oLoadExtensionOptions); // wait for the entire app to load var oWaitApplicationLoadOptions = createWaitForObjectWithoutDefaults(); oWaitApplicationLoadOptions.autoWait = oOptions.autoWait || false; oWaitApplicationLoadOptions.timeout = oOptions.timeout || 80; return this.waitFor(oWaitApplicationLoadOptions); }; /** * Destroys the UIComponent and removes the div from the dom like all the references on its objects. * Use {@link sap.ui.test.Opa5#hasUIComponentStarted} to ensure that a UIComponent has been started and teardown can be safely performed. * * @since 1.48 If appParams were applied to the current URL, they will be removed * after UIComponent is destroyed * * @returns {jQuery.promise} a promise that gets resolved on success. * If no UIComponent has been started or an error occurs, the promise is rejected with the options object. * A detailed error message containing the stack trace and Opa logs is available in options.errorMessage. * @public * @function */ Opa5.prototype.iTeardownMyUIComponent = function iTeardownMyUIComponent() { var oOptions = createWaitForObjectWithoutDefaults(), _this = extend({}, this); oOptions.success = function () { componentLauncher.teardown(); }; // restore URL after component teardown in order to remove any appParams added by extendConfig var oParamsWaitForOptions = createWaitForObjectWithoutDefaults(); oParamsWaitForOptions.success = function () { var uri = new URI(); uri.search(Opa5._allUriParams); window.history.replaceState({}, "", uri.toString()); }; if (this._getQueue().length) { return $.when(this.waitFor(oOptions), this.waitFor(oParamsWaitForOptions)); } else { try { oOptions.success(); oParamsWaitForOptions.success(); return $.Deferred().resolve().promise(_this); } catch (oError) { this._handleErrorMessage(oError, oOptions); return $.Deferred().reject(oOptions); } finally { this._ensureNewlyAddedWaitForStatementsPrepended(Opa._getWaitForCounter(), oOptions); } } }; /** * Tears down the started application regardless of how it was started. * Removes the iframe launched by {@link sap.ui.test.Opa5#iStartMyAppInAFrame} * or destroys the UIComponent launched by {@link sap.ui.test.Opa5#iStartMyUIComponent}. * This function is designed to make the test's teardown independent of the startup. * Use {@link sap.ui.test.Opa5#hasAppStarted} to ensure that the application has been started and teardown can be safely performed. * @returns {jQuery.promise} A promise that gets resolved on success. * If nothing has been started or an error occurs, the promise is rejected with the options object. * A detailed error message containing the stack trace and Opa logs is available in options.errorMessage. * @public * @function */ Opa5.prototype.iTeardownMyApp = function () { var that = this, _this = extend({}, this); // unload all extensions, schedule unload on flow so to be synchronized with waitFor's var oExtensionOptions = createWaitForObjectWithoutDefaults(); oExtensionOptions.success = function () { return that._unloadExtensions(Opa5.getWindow()); }; var oOptions = createWaitForObjectWithoutDefaults(); oOptions.success = function () { if (iFrameLauncher.hasLaunched()) { this.iTeardownMyAppFrame(); } else if (componentLauncher.hasLaunched()) { this.iTeardownMyUIComponent(); } else { var sErrorMessage = "A teardown was called but there was nothing to tear down use iStartMyComponent or iStartMyAppInAFrame"; oLogger.error(sErrorMessage, "Opa"); throw new Error(sErrorMessage); } }.bind(this); if (this._getQueue().length) { return $.when(this.waitFor(oExtensionOptions), this.waitFor(oOptions)); } else { try { oOptions.success(); return oExtensionOptions.success().then(function() { // Once the oExtensionOptions.success() promise is resolved, resolve the returned promise with _this return $.Deferred().resolve(_this).promise(); }); } catch (oError) { this._handleErrorMessage(oError, oOptions); return $.Deferred().reject(oOptions); } finally { this._ensureNewlyAddedWaitForStatementsPrepended(Opa._getWaitForCounter(), oOptions); } } }; /** * Starts an app in an iframe. Only works reliably if running on the same server. * * @since 1.48 If appParams are provided in {@link sap.ui.test.Opa.config}, they are * merged in the query params of app URL * * @param {string|object} vSourceOrOptions The source URL of the iframe or, since 1.53, you can provide a * startup configuration object as the only parameter. * @param {number} [iTimeout=80] The timeout for loading the iframe in seconds - default is 80. * @param {boolean} [autoWait=false] Since 1.53, activates autoWait while the application is starting up. * This allows more time for application startup and stabilizes tests for slow-loading applications. * This parameter is false by default, regardless of the global autoWait value, to prevent issues in existing tests. * @param {string|number} [width=Opa.config.frameWidth] Since 1.57, sets a fixed width for the iframe. * @param {string|number} [height=Opa.config.frameHeight] Since 1.57, sets a fixed height for the iframe. * Setting width and/or height is useful when testing responsive applications on screens of varying sizes. * By default, the iframe dimensions are 60% of the outer window dimensions. * @param {string} vSourceOrOptions.source The source of the iframe * @param {number} [vSourceOrOptions.timeout=80] The timeout for loading the iframe in seconds - default is 80 * @param {boolean} [vSourceOrOptions.autoWait=false] Since 1.53, activates autoWait while the application is starting up. * This allows more time for application startup and stabilizes tests for slow-loading applications. * This parameter is false by default, regardless of the global autoWait value, to prevent issues in existing tests. * @param {string|number} [vSourceOrOptions.width=Opa.config.frameWidth] Since 1.57, sets a fixed width for the iframe. * @param {string|number} [vSourceOrOptions.height=Opa.config.frameHeight] Since 1.57, sets a fixed height for the iframe. * Setting width and/or height is useful when testing responsive applications on screens of varying sizes. * Since 1.65, by default, the iframe dimensions are 60% of the default screen size, considered to be 1280x1024. * @returns {jQuery.promise} A promise that gets resolved on success * @public * @function */ Opa5.iStartMyAppInAFrame = iStartMyAppInAFrame; /** * Starts an app in an iframe. Only works reliably if running on the same server. * * @since 1.48 If appParams are provided in {@link sap.ui.test.Opa.config}, they are * merged in the query params of app URL * * @param {string|object} vSourceOrOptions The source URL of the iframe or, since 1.53, you can provide a * startup configuration object as the only parameter. * @param {number} [iTimeout=80] The timeout for loading the iframe in seconds - default is 80 * @param {boolean} [autoWait=false] Since 1.53, activates autoWait while the application is starting up. * This allows more time for application startup and stabilizes tests for slow-loading applications. * This parameter is false by default, regardless of the global autoWait value, to prevent issues in existing tests. * @param {string|number} [width=Opa.config.frameWidth] Since 1.57, sets a fixed width for the iframe. * @param {string|number} [height=Opa.config.frameHeight] Since 1.57, sets a fixed height for the iframe. * Setting width and/or height is useful when testing responsive applications on screens of varying sizes. * By default, the iframe dimensions are 60% of the outer window dimensions. * @param {string} vSourceOrOptions.source The source of the iframe * @param {number} [vSourceOrOptions.timeout=80] The timeout for loading the iframe in seconds - default is 80 * @param {boolean} [vSourceOrOptions.autoWait=false] Since 1.53, activates autoWait while the application is starting up. * This allows more time for application startup and stabilizes tests for slow-loading applications. * This parameter is false by default, regardless of the global autoWait value, to prevent issues in existing tests. * @param {string|number} [vSourceOrOptions.width=Opa.config.frameWidth] Since 1.57, sets a fixed width for the iframe. * @param {string|number} [vSourceOrOptions.height=Opa.config.frameHeight] Since 1.57, sets a fixed height for the iframe. * Setting width and/or height is useful when testing responsive applications on screens of varying sizes. * By default, the iframe dimensions are 60% of the outer window dimensions. * @returns {jQuery.promise} A promise that gets resolved on success * @public * @function */ Opa5.prototype.iStartMyAppInAFrame = iStartMyAppInAFrame; function iTeardownMyAppFrame() { var oWaitForObject = createWaitForObjectWithoutDefaults(), _this = extend({}, this); oWaitForObject.success = function () { iFrameLauncher.teardown(); }; if (this._getQueue().length) { return this.waitFor(oWaitForObject); } else { try { oWaitForObject.success(); return $.Deferred().resolve().promise(_this); } catch (oError) { this._handleErrorMessage(oError, oWaitForObject); return $.Deferred().reject(oWaitForObject); } finally { this._ensureNewlyAddedWaitForStatementsPrepended(Opa._getWaitForCounter(), oWaitForObject); } } } /** * Removes the iframe from the DOM and removes all the references to its objects. * Use {@link sap.ui.test.Opa5#hasAppStartedInAFrame} to ensure that an iframe has been started and teardown can be safely performed. * @returns {jQuery.promise} A promise that gets resolved on success. * If no iframe has been created or an error occurs, the promise is rejected with the options object. * A detailed error message containing the stack trace and Opa logs is available in options.errorMessage. * @public * @function */ Opa5.iTeardownMyAppFrame = iTeardownMyAppFrame; /** * Removes the iframe from the DOM and removes all the references to its objects * Use {@link sap.ui.test.Opa5#hasAppStartedInAFrame} to ensure that an iframe has been started and teardown can be safely performed. * @returns {jQuery.promise} A promise that gets resolved on success. * If no iframe has been created or an error occurs, the promise is rejected with the options object. * A detailed error message containing the stack trace and Opa logs is available in options.errorMessage. * @public * @function */ Opa5.prototype.iTeardownMyAppFrame = iTeardownMyAppFrame; /** * Checks if the application has been started using {@link sap.ui.test.Opa5#iStartMyAppInAFrame} * @returns {boolean} A boolean indicating whether the application has been started in an iframe * @public * @function */ Opa5.prototype.hasAppStartedInAFrame = function () { return iFrameLauncher.hasLaunched(); }; /** * Checks if the application has been started using {@link sap.ui.test.Opa5#iStartMyUIComponent} * @returns {boolean} A boolean indicating whether the application has been started as a UIComponent * @public * @function */ Opa5.prototype.hasUIComponentStarted = function () { return componentLauncher.hasLaunched(); }; /** * Checks if the application has been started using {@link sap.ui.test.Opa5#iStartMyAppInAFrame} or {@link sap.ui.test.Opa5#iStartMyUIComponent} * @returns {boolean} A boolean indicating whether the application has been started regardless of how it was started * @public * @function */ Opa5.prototype.hasAppStarted = function () { return iFrameLauncher.hasLaunched() || componentLauncher.hasLaunched(); }; /** * @typedef {sap.ui.test.Opa.BaseParameters} sap.ui.test.Opa5.BaseParameters * @description Configuration parameters for Opa5. * @property {string} [viewNamespace] viewName prefix. Recommended to be set in {@link sap.ui.test.Opa5.extendConfig} instead. * @property {boolean} [visible=true] If set to false, Opa5 will also look for unrendered and invisible controls. * @property {boolean} [enabled=false] @since 1.66 If set to false, Opa5 will look for both enabled and disabled controls. * Note that this option's default value is related to the autoWait mechanism: * <ul> * <li> When autoWait is enabled globally or in the current waitFor, the default value for options.enabled is true. </li> * <li> When autoWait is not used, the default value for options.enabled is false.</li> * </ul> * This means that if you use autoWait and you want to find a disabled control, you need to explicitly set options.enabled to false. * @property {boolean} [editable=false] @since 1.80 If set to true, Opa5 will match only editable controls. * If set to false, Opa5 will match both editable and non-editable controls. * @property {boolean} [autoWait=false] @since 1.42 Only has an effect if set to true. Since 1.53 it can also be a plain object. * When autoWait is true, the waitFor statement will not execute success callbacks as long as there is pending asynchronous work such as for example: * open XMLHTTPRequests (requests to a server), scheduled delayed work and promises, unfinished UI navigation. * In addition, the control state will be checked with the {@link sap.ui.test.matchers.Interactable} matcher, and the control will have to be enabled. * So when autoWait is enabled, success behaves like an action in terms of waiting. * It is recommended to set this value to true for all your waitFor statements using: * <pre> * Opa5.extendConfig({ * autoWait: true * }); * </pre> * Why it is recommended: * When writing a huge set of tests and executing them frequently you might face tests that are sometimes successful but sometimes they are not. * Setting the autoWait to true should stabilize most of those tests. * The default "false" could not be changed since it causes existing tests to fail. * There are cases where you do not want to wait for controls to be "Interactable": * For example when you are testing the Busy indication of your UI during the sending of a request. * But these cases are the exception so it is better to explicitly adding autoWait: false to this waitFor. * <pre> * this.waitFor({ * id: "myButton", * autoWait: false, * success: function (oButton) { * Opa5.assert.ok(oButton.getBusy(), "My Button was busy"); * } * }); * </pre> * This is also the easiest way of migrating existing tests. First extend the config, then see which waitFors * will time out and finally disable autoWait in these tests. * * @since 1.53 autoWait option can be a plain object used to configure what autoWait will consider pending, for example: * <ul> * <li> Maximum depth of a timeout chain. Longer chains are considered polling and are discarded as irrelevant to the application state in testing scenarios. </li> * <li> Maximum delay, in milliseconds, of tracked timeouts and promises. Long runners are discarded as they do not influence application state.</li> * </ul> * This is the default autoWait configuration: * autoWait: { * timeoutWaiter: { * maxDepth: 3, * maxDelay: 1000 * } * } * If autoWait is set to true or the object doesn't contain the recognized keys, the default autoWait configuration will be used. * @public */ /** * @typedef {sap.ui.test.Opa5.BaseParameters} sap.ui.test.Opa5.Config * @description The global configuration of Opa5. * @property {sap.ui.test.Opa5} [arrangements] A new Opa5 instance * @property {sap.ui.test.Opa5} [actions] A new Opa5 instance * @property {sap.ui.test.Opa5} [assertions] A new Opa5 instance * @property {int} [executionDelay] The value is a number representing milliseconds. The default values are 0 or 50 (depending on the browser). * The executionDelay will slow down the execution of every single waitFor statement to be delayed by the number of milliseconds. * This does not effect the polling interval it just adds an initial pause. * Use this parameter to slow down OPA when you want to watch your test during development or checking the UI of your app. * It is not recommended to use this parameter in any automated test executions. * @property {Object<string,string>} [appParams] object with URI parameters for the tested app - since 1.48 * @public */ /** * @typedef {function(sap.ui.core.Element)|Object<string,object>|sap.ui.test.matchers.Matcher} sap.ui.test.Opa5.Matcher * @description Matchers used to filter controls. * @public */ /** * @typedef {function((sap.ui.core.Element|null))|sap.ui.test.actions.Action} sap.ui.test.Opa5.Action * @description An action simulates user interaction on a control * @public */ /** * @typedef {sap.ui.test.Opa5.BaseParameters} sap.ui.test.Opa5.ControlsBaseSelector * @description Configuration parameters for an individual {@link sap.ui.test.Opa5#waitFor} call. * @property {string} [viewName] The name of a view. * If viewName is set, controls will be searched only inside this view. If control ID is given, it will be considered to be relative to the view. * * @property {string} [viewId] @since 1.62 The ID of a view. Can be used alone or in combination with viewName and viewNamespace. * * Always set view ID if there are multiple views with the same name. * * @property {string} [fragmentId] @since 1.63 The ID of a fragment. If set, controls will match only if their IDs contain the fragment ID prefix. * * @property {sap.ui.test.Opa5.Matcher|sap.ui.test.Opa5.Matcher[]} [matchers] Matchers used to filter controls. * Could be a function, a single matcher instance, an array of matcher instances, or, since version 1.72, a plain * object to specify matchers declaratively. For a full list of built-in matchers, see {@link sap.ui.test.matchers}. * * Matchers are applied to each control found by the <code>waitFor</code> function. * The matchers are a pipeline: the first matcher gets a control as an input parameter, each subsequent matcher gets * the same input as the previous one, if the previous output is <code>true</code>. * * If the previous output is a truthy value, the next matcher will receive this value as an input parameter. * If there is a matcher that does not match a control (for example, returns a falsy value), then the control is filtered out. * * Check function is only called if the matchers matched at least one control, for example, it is not called if matchers filter out all controls/values. * Check and success are be called with all matching controls as an input parameter. * A matcher inline function has one parameter - an array of controls, and returns an array of the filtered controls. * * A matcher instance could extend <code>sap.ui.test.matchers.Matcher</code> and must have a method with name <code>isMatching</code>, * that accepts an array of controls and returns an array of the filtered controls. * * A declarative matcher object is a set of key-value pairs created by the object literal notation, such that: * <ul> * <li>Every key is a name of an OPA5 built-in matcher, starting with a lower case letter. The following example declares * an <code>sap.ui.test.matchers.Properties</code> matcher: * <pre><code> matchers: { * properties: {<...>} * } * </code></pre> * </li> * <li>Every value is an object or an array or objects. Each object represents the properties that will be fed * to one instance of the declared matcher. The following example declares one <code>sap.ui.test.matchers.Properties</code> * matcher for property "text" and value "hello": * <pre><code> matchers: { * properties: {text: "hello"} * } * </code></pre> * * The following example declares two <code>sap.ui.test.matchers.Properties</code> matchers * (the <code>text</code> property with value <code>hello</code> and the <code>number</code> property with value <code>0</code>): * <pre><code> matchers: { * properties: [ * {text: "hello"}, * {number: 0} * ]} * </code></pre> * </li></ul> * * @property {string} [controlType] Selects all control by their type. * It is usually combined with a viewName or searchOpenDialogs. If no control is matching the type, an empty * array will be returned. Here are some samples: * <pre> * this.waitFor({ * controlType: "sap.m.Button", * success: function (aButtons) { * // aButtons is an array of all visible buttons * } * }); * * // control type will also return controls that extend the control type * // this will return an array of visible sap.m.List and sap.m.Table since both extend List base * this.waitFor({ * controlType: "sap.m.ListBase", * success: function (aLists) { * // aLists is an array of all visible Tables and Lists * } * }); * * // control type is often combined with viewName - only controls that are inside of the view * // and have the correct type will be returned * this.waitFor({ * viewName: "my.View" * controlType: "sap.m.Input", * success: function (aInputs) { * // aInputs are all sap.m.Input controls inside of a view called 'my.View' * } * }); * </pre> * @property {boolean} [searchOpenDialogs=false] If set to true, Opa5 will only look in open dialogs. All the other values except control type will be ignored * @property {boolean} [interactable=false] @since 1.80 If set to true, the {@link sap.ui.test.matchers.Interactable} matcher will be applied. * If autoWait is true, this option has no effect and interactable will always be true. * If autoWait is false, which is the default state, the value of the interactable property will have an effect. * When interactable is true, enabled will also be set to true, unless declared otherwise. * @property {function} [error] Invoked when the timeout is reached and the check never returned true. * @property {string} [errorMessage] Will be displayed as an errorMessage depending on your unit test framework. * Currently the only adapter for Opa5 is QUnit. * This message is displayed if Opa5 has reached its timeout before QUnit has reached it. * @property {sap.ui.test.Opa5.Action|sap.ui.test.Opa5.Action[]} [actions] * Available since 1.34.0. An array of functions or Actions or a mixture of both. * An action has an 'executeOn' function that will receive a single control as a parameter. * If there are multiple actions defined all of them * will be executed (first in first out) on each control of, similar to the matchers. * Here is one of the most common usages: * <code> * function (sButtonId) { * // executes a Press on a button with a specific id * new Opa5().waitFor({ * id: sButtonId, * actions: new Press() * }); * }; * </code> * But actions will only be executed once and only after the check function returned true. * Before actions are executed the {@link sap.ui.test.matchers.Interactable} matcher * and the internal autoWait logic will check if the Control is currently able to perform actions if it is not, * Opa5 will try again after the 'pollingInterval'. * That means actions will only be executed if: * <ul> * <li> * Controls and their parents are visible, not busy and not hidden behind a blocking layer * </li> * <li> * The controls are not hidden behind static elements such as dialogs * </li> * <li> * There is no pending asynchronous work performed by the application * </li> * </ul> * If there are multiple controls in Opa5's result set the action will be executed on all of them. * The actions will be invoked directly before success is called. * In the documentation of the success parameter there is a list of conditions that have to be fulfilled. * They also apply for the actions. * There are some predefined actions in the {@link sap.ui.test.actions} namespace. * since 1.42 an Action may add other waitFors. * The next action or the success handler will not be executed until the waitFor of the action has finished. * An example: * <pre> * this.waitFor({ * id: "myButton", * actions: function (oButton) { * // this action is executed first * this.waitFor({ * id: "anotherButton", * actions: function () { * // This is the second function that will be executed * // Opa will also wait until anotherButton is Interactable before executing this function * }, * success: function () { * // This is the third function that will be executed * } * }) * }, * success: function () { * // This is the fourth function that will be executed * } * }); * </pre> * Executing multiple actions will not wait between actions for a control to become "Interactable" again. * If you need waiting between actions you need to split the actions into multiple 'waitFor' statements. * @public */ /** * @typedef {sap.ui.test.Opa5.ControlsBaseSelector} sap.ui.test.Opa5.MultiControlSelector * @description Configuration parameters for an individual {@link sap.ui.test.Opa5#waitFor} call. Contain criteria for selecting one or multiple controls. * @property {RegExp} [id] Regex for matching either the ID of a control, or the ID of a control inside a view. * * If both a regex and a viewName are provided, Opa5 only looks for controls in the view with a matching ID. * * Example of a waitFor: * <pre> * <code> * this.waitFor({ * id: /my/, * viewName: "myView" * }); * </code> * </pre> * The view that is searched in: * <pre> * <code> * &lt;mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m"&gt; * &lt;Button id="myButton"&gt; * &lt;/Button&gt; * &lt;Button id="bar"&gt; * &lt;/Button&gt; * &lt;Button id="baz"&gt; * &lt;/Button&gt; * &lt;Image id="myImage"&gt;&lt;/Image&gt; * &lt;/mvc:View&gt; * </code> * </pre> * * Will result in matching two controls, the image with the effective ID myView--myImage and the button myView--myButton. * Although the IDs of the controls myView--bar and myView--baz contain a my, * they will not be matched since only the part you really write in your views will be matched. * * @property {function((sap.ui.core.Element[])):boolean} [check] Will get invoked in every polling interval. If it returns true, the check is successful and the polling will stop. * The first parameter passed into the function is the same value that gets passed to the success function. * Returning something other than boolean in check will not change the first parameter of success. * @property {function((sap.ui.core.Element[]))} [success] Will get invoked after the following conditions are met: * <ol> * <li> * One or multiple controls were found using controlType, Id, viewName. If visible is true (it is by default), the controls also need to be rendered. * </li> * <li> * The whole matcher pipeline returned true for at least one control, or there are no matchers * </li> * <li> * The check function returned true, or there is no check function * </li> * </ol> * The first parameter passed into the function is an array of controls (viewName, controlType, multiple ID's, regex ID's) that matched all matchers. * Matchers can alter the array to something different. Please read the documentation of waitFor's matcher parameter. * @public */ /** * @typedef {sap.ui.test.Opa5.ControlsBaseSelector} sap.ui.test.Opa5.SingleControlSelector * @description Configuration parameters for an individual {@link sap.ui.test.Opa5#waitFor} call. * @property {string} id The global ID of a control, or the ID of a control inside a view. * @property {function(sap.ui.core.Element):boolean} [check] Will get invoked in every polling interval. If it returns true, the check is successful and the polling will stop. * The first parameter passed into the function is the same value that gets passed to the success function. * Returning something other than boolean in check will not change the first parameter of success. * @property {function(sap.ui.core.Element)} [success] Will get invoked if the following conditions are met: * <ol> * <li> * A control was found using viewName and Id that maches any addiotnally specified criteria e.g. controlType, matchers. If visible is true (it is by default), the control also needs to be rendered. * </li> * <li> * The check function returned true, or there is no check function * </li> * </ol> * @public */ /** * @param {sap.ui.test.Opa5.SingleControlSelector|sap.ui.test.Opa5.MultiControlSelector} options a superset of the parameters of {@link sap.ui.test.Opa#waitFor} * @returns {this} an object extending a jQuery promise. * The object is essentially a jQuery promise with an additional "and" method that can be used for chaining waitFor statements. * The promise is resolved when the waitFor completes successfully. * The promise is rejected with the options object, if an error occurs. In this case, options.errorMessage will contain a detailed error message containing the stack trace and Opa logs. * @public */ Opa5.prototype.waitFor = function (options) { // if there are any declarative matchers, first, find the ancestors and descendants. // do this recursively, until every expanded declaration is resolved, // and then continue to finding the dependent control. // the actual queueing of waitFors will be ensured by sap.ui.test.Opa#waitFor (see function ensureNewlyAddedWaitForStatementsPrepended) var aPath = _getPathToExpansion(options); var mExpansion = _getExpansion(options, aPath); if (mExpansion) { mExpansion.success = function (vControl) { // right now, we assume that every declarative matcher matches exactly one control var oControl = Array.isArray(vControl) ? vControl[0] : vControl; var optionsForDependant = _substituteExpansion(options, aPath, oControl); return Opa5.prototype.waitFor.call(this, optionsForDependant); }; return Opa5.prototype.waitFor.call(this, mExpansion); } var vActions = options.actions, oFilteredConfig = Opa._createFilteredConfig(aConfigValuesForWaitFor), // only take the allowed properties from the config oOptionsPassedToOpa; options = extend({}, oFilteredConfig, options); options.actions = vActions; oValidator.validate({ validationInfo: _ValidationParameters.OPA5_WAITFOR, inputToValidate: options }); var fnOriginalCheck = options.check, vControl = null, fnOriginalSuccess = options.success, vResult, bPluginLooksForControls; oOptionsPassedToOpa = Opa._createFilteredOptions(aPropertiesThatShouldBePassedToOpaWaitFor, options); oOptionsPassedToOpa.check = function () { var bApplyAutoWait = !!options.actions || options.autoWait; var oAutoWaiter = Opa5._getAutoWaiter(); oAutoWaiter.extendConfig(options.autoWait); if (bApplyAutoWait && oAutoWaiter.hasToWait()) { return false; } // Create a new options object for the plugin to keep the original one as is var oPlugin = Opa5.getPlugin(); var oPluginOptions = extend({}, options, { // ensure Interactable matcher is applied if autoWait is true or actions are specified interactable: bApplyAutoWait || options.interactable }); // even if we have no control the matchers may provide a value for vControl vResult = oPlugin._getFilteredControls(oPluginOptions, vControl); if (iFrameLauncher.hasLaunched() && Array.isArray(vResult)) { // People are using instanceof Array in their check so i need to make sure the Array // comes from the current document. I cannot use slice(0) or map because the original array is kept // so i need to use the slowest way to create a shallow copy of the array var aResult = []; vResult.forEach(function (oControl) { aResult.push(oControl); }); vResult = aResult; } if (vResult === OpaPlugin.FILTER_FOUND_NO_CONTROLS) { oLogger.debug("Matchers found no controls so check function will be skipped"); return false; } if (fnOriginalCheck) { return this._executeCheck(fnOriginalCheck, vResult); } //no check defined - continue return true; }; oOptionsPassedToOpa.success = function () { var oWaitForCounter = Opa._getWaitForCounter(); // If the plugin does not look for controls execute actions even if vControl is falsy if (vActions && (vResult || !bPluginLooksForControls)) { oActionPipeline.process({ actions: vActions, control: vResult }); } // no success from the application. // waitFors added by the actions will then be the next waitFors anyways. // that means modifying the queue is not necessary if (!fnOriginalSuccess) { return; } var aArgs = []; if (vResult) { aArgs.push(vResult); } if (oWaitForCounter.get() === 0) { oLogger.timestamp("opa.waitFor.success"); oLogger.debug("Execute success handler"); // No waitFors added by actions - directly execute the success fnOriginalSuccess.apply(this, aArgs); return; } // Delay the current waitFor after a waitFor added by the actions. // So waitFors added by an action will block the current execution of success var oWaitForObject = createWaitForObjectWithoutDefaults(); // preserve the autoWait value if (isPlainObject(options.autoWait)) { oWaitForObject.autoWait = extend({}, options.autoWait); } else { oWaitForObject.autoWait = options.autoWait; } oWaitForObject.success = function () { fnOriginalSuccess.apply(this, aArgs); }; // the delay is achieved by just not executing the waitFor and wrapping it into a new waitFor // the new waitFor does not have any checks just directly executes the success result this.waitFor(oWaitForObject); }; return Opa.prototype.waitFor.call(this, oOptionsPassedToOpa); }; // we don't delegate to the respective selected launcher because // these utils should be defined before and during launcher startup. // in addition, principally, OPA5 could be used without a launched application /** * Returns the Opa plugin used for retrieving controls. If an iframe is launched, it will return the iframe's plugin. * @returns {sap.ui.test.OpaPlugin} The plugin instance * @public */ Opa5.getPlugin = function () { return iFrameLauncher.getPlugin() || Opa5._oPlugin; }; /** * Returns the jQuery object in the current context. If an iframe is launched, it will return the iframe's jQuery object. * @returns {jQuery} The jQuery object * @public */ Opa5.getJQuery = function () { return iFrameLauncher.getJQuery() || $; }; /** * Returns the window object in the current context. If an iframe is launched, it will return the iframe's window. * @returns {Window} The window of the iframe * @public */ Opa5.getWindow = function () { return iFrameLauncher.getWindow() || window; }; /** * Returns the QUnit utils object in the current context. If an iframe is launched, it will return the iframe's QUnit utils. * @public * @returns {sap.ui.test.qunit.QUnitUtils} The QUnit utils */ Opa5.getUtils = function () { return iFrameLauncher.getUtils() || QUnitUtils; }; /** * Returns the HashChanger object in the current context. If an iframe is launched, it will return the iframe's HashChanger. * @public * @returns {sap.ui.core.routing.HashChanger} The HashChanger instance */ Opa5.getHashChanger = function () { return iFrameLauncher.getHashChanger() || HashChanger.getInstance(); }; /* * @private */ Opa5._getAutoWaiter = function () { return iFrameLauncher._getAutoWaiter() || _autoWaiter; }; /** * * Extends and overwrites default values of the {@link sap.ui.test.Opa.Config}. * Most frequent usecase: * <pre> * <code> * // Every waitFor will append this namespace in front of your viewName * Opa5.extendConfig({ * viewNamespace: "namespace.of.my.views." * }); * * var oOpa = new Opa5(); * * // Looks for a control with the id "myButton" in a View with the name "namespace.of.my.views.Detail" * oOpa.waitFor({ * id: "myButton", * viewName: "Detail" * }); * * // Looks for a control with the id "myList" in a View with the name "namespace.of.my.views.Master" * oOpa.waitFor({ * id: "myList", * viewName: "Master" * }); * </code> * </pre> * * Sample usage: * <pre> * <code> * var oOpa = new Opa5(); * * // this statement will will time out after 15 seconds and poll every 400ms. * // those two values come from the defaults of {@link sap.ui.test.Opa.config}. * oOpa.waitFor({ * }); * * // All wait for statements added after this will take other defaults * Opa5.extendConfig({ * timeout: 10, * pollingInterval: 100 * }); * * // this statement will time out after 10 seconds and poll every 100 ms * oOpa.waitFor({ * }); * * // this statement will time out after 20 seconds and poll every 100 ms * oOpa.waitFor({ * timeout: 20; * }); * </code> * </pre> * * @since 1.40 The own properties of 'arrangements, actions and assertions' will be kept. * Here is an example: * <pre> * <code> * // An opa action with an own property 'clickMyButton' * var myOpaAction = new Opa5(); * myOpaAction.clickMyButton = // function that clicks MyButton * Opa.config.actions = myOpaAction; * * var myExtension = new Opa5(); * Opa5.extendConfig({ * actions: myExtension * }); * * // The clickMyButton function is still available - the function is logged out * console.log(Opa.config.actions.clickMyButton); * * // If * var mySecondExtension = new Opa5(); * mySecondExtension.clickMyButton = // a different function than the initial one * Opa.extendConfig({ * actions: mySecondExtension * }); * * // Now clickMyButton function is the function of the second extension not the first one. * console.log(Opa.config.actions.clickMyButton); * </code> * </pre> * * @since 1.48 Application config parameters could be overwritten from URL. * Every parameter that is not prefixed with 'opa' and is allowed as QUnit * parameter is parsed and overwrites respective 'appParams' value. * * @since 1.49 Declarative configuration of test libraries is supported * <pre> * <code> * // in your app * Opa5.extendConfig({ * testLibs: { * someAwesomeTestLib: { * key: 'value' * } * } * }); * * // so the test library could do * var key = Opa5.getTestLibConfig('someAwesomeTestLib').key; * * </code> * </pre> * * @param {sap.ui.test.Opa5.Config} options The values to be added to the existing config * @public * @function */ Opa5.extendConfig = function (options) { Opa.extendConfig(options); // URL app params overwrite extendConfig app params Opa.extendConfig({ appParams: Opa5._appUriParams }); Opa5._getAutoWaiter().extendConfig(options.autoWait); }; /** * Resets Opa.config to its default values. * See {@link sap.ui.test.Opa5#waitFor} for the description * Default values for OPA5 are: * <ul> * <li>viewNamespace: empty string</li> * <li>arrangements: instance of OPA5</li> * <li>actions: instance of OPA5</li> * <li>assertions: instance of OPA5</li> * <li>visible: true</li> * <li>enabled: false</li> * <li>editable: false</li> * <li>timeout : 15 seconds, 0 for infinite timeout</li> * <li>pollingInterval: 400 milliseconds</li> * <li>debugTimeout: 0 seconds, infinite timeout by default. This will be used instead of timeout if running in debug mode.</li> * <li>autoWait: false -