UNPKG

@openui5/sap.ui.core

Version:

OpenUI5 Core Library sap.ui.core

1,130 lines (1,057 loc) 61.1 kB
/*! * OpenUI5 * (c) Copyright 2009-2021 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" ], function (Opa, OpaPlugin, PageObjectFactory, Ui5Object, iFrameLauncher, componentLauncher, HashChanger, Matcher, AggregationFilled, PropertyStrictEquals, ActionPipeline, _ParameterValidator, _OpaLogger, URI, EventProvider, QUnitUtils, _autoWaiter, includeStylesheet, $, _OpaUriParameterParser, _ValidationParameters) { "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 * @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 () { Opa.apply(this, arguments); } })); 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; var bComponentLoaded = false; 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 || ""); componentLauncher.start(oOptions.componentConfig).then(function () { bComponentLoaded = true; }); }; this.waitFor(oStartComponentOptions); // wait till component is started var oComponentStartedOptions = createWaitForObjectWithoutDefaults(); oComponentStartedOptions.errorMessage = "Unable to load the component with the name: " + oOptions.name; oComponentStartedOptions.check = function () { return bComponentLoaded; }; if (oOptions.timeout) { oComponentStartedOptions.timeout = oOptions.timeout; } that.waitFor(oComponentStartedOptions); // 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(); 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()); }; return $.when(this.waitFor(oOptions), this.waitFor(oParamsWaitForOptions)); }; /** * 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; // unload all extensions, schedule unload on flow so to be synchronized with waitFor's var oExtensionOptions = createWaitForObjectWithoutDefaults(); oExtensionOptions.success = function () { 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); return $.when(this.waitFor(oExtensionOptions), this.waitFor(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(); oWaitForObject.success = function () { iFrameLauncher.teardown(); }; return this.waitFor(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(); }; /** * Takes a superset of the parameters of {@link sap.ui.test.Opa#waitFor}. * * @param {object} options An object containing conditions for waiting and callbacks. * * The allowed keys are listed below. If a key is not allowed, an error is thrown, stating that * "the parameter is not defined in the API". * * As of version 1.72, in addition to the listed keys, declarative matchers are also allowed. * Any matchers declared on the root level of the options object are merged with those declared in <code>options.matchers</code>. * For details on declarative matchers, see the <code>options.matchers</code> property. * * @param {string|RegExp} [options.id] The global ID of a control, or the ID of a control inside a view. * * If a regex and a viewName is 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. * * @param {string} [options.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. * * @param {string} [options.viewNamespace] viewName prefix. Recommended to be set in {@link sap.ui.test.Opa5.extendConfig} instead. * * @param {string} [options.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. * * @param {string} [options.fragmentId] @since 1.63 The ID of a fragment. If set, controls will match only if their IDs contain the fragment ID prefix. * * @param {function|array|object|sap.ui.test.matchers.Matcher} [options.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> * * @param {string} [options.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.Inputs inside of a view called 'my.View' * } * }); * </pre> * @param {boolean} [options.searchOpenDialogs=false] If set to true, Opa5 will only look in open dialogs. All the other values except control type will be ignored * @param {boolean} [options.visible=true] If set to false, Opa5 will also look for unrendered and invisible controls. * @param {boolean} [options.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. * @param {boolean} [options.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. * @param {boolean} [options.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. * @param {int} [options.timeout=15] (seconds) Specifies how long the waitFor function polls before it fails.O means it will wait forever. * @param {int} [options.debugTimeout=0] @since 1.47 (seconds) Specifies how long the waitFor function polls before it fails in debug mode.O means it will wait forever. * @param {int} [options.pollingInterval=400] (milliseconds) Specifies how often the waitFor function polls. * @param {function} [options.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. * @param {function} [options.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 either a single control (when a single string ID was used), * or an array of controls (viewName, controlType, multiple ID's, regex ID's) that matched all matchers. * Matchers can alter the array or single control to something different. Please read the documentation of waitFor's matcher parameter. * @param {function} [options.error] Invoked when the timeout is reached and the check never returned true. * @param {string} [options.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. * @param {function|function[]|sap.ui.test.actions.Action|sap.ui.test.actions.Action[]} options.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. * @param {boolean} [options.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. * * @since 1.48 All config parameters could be overwritten from URL. Should be prefixed with 'opa' * and have uppercase first character. Like 'opaExecutionDelay=1000' will overwrite 'executionDelay' * * @returns {object} 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 dependant 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() && $.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 swallow 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} 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 {object} 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 - since 1.42</li> * <li>appParams: object with URI parameters for the tested app - since 1.48</li> * </ul> * @public * @since 1.25 */ Opa5.resetConfig = function () { Opa.resetConfig(); Opa.extendConfig({ viewNamespace: "", arrangements: new Opa5(), actions: new Opa5(), assertions: new Opa5(), visible: true, enabled: undefined, editable: undefined, autoWait: false, _stackDropCount: 1 }); Opa.extendConfig({ appParams: Opa5._appUriParams }); }; /** * Return particular test lib config object. * This method is intended to be used by test libraries to * access their configuration provided by the test in * the testLibs section in {@link sap.ui.test.Opa5.extendConfig} * @param {string} sTestLibName test library name * @returns {object} this test library config object or empty object if * configuration is not provided * @public * @since 1.49 * @function */ Opa5.getTestLibConfig = function (sTestLibName) { return Opa.config.testLibs && Opa.config.testLibs[sTestLibName] ? Opa.config.testLibs[sTestLibName] : {}; }; /** * Waits until all waitFor calls are done * See {@link sap.ui.test.Opa.emptyQueue} for the description * @returns {jQuery.promise} If the waiting was successful, the promise will be resolved. If not it will be rejected * @public * @function */ Opa5.emptyQueue = Opa.emptyQueue; /** * Clears the queue and stops running tests so that new tests can be run. * This means all waitFor statements registered by {@link sap.ui.test.Opa5#waitFor} will not be invoked anymore and * the promise returned by {@link sap.ui.test.Opa5.emptyQueue} will be rejected. * When its called inside of a check in {@link sap.ui.test.Opa5#waitFor} * the success function of this waitFor will not be called. * @public * @function */ Opa5.stopQueue = Opa.stopQueue; /** * Gives access to a singleton object you can save values in. * See {@link sap.ui.test.Opa.getContext} for the description * @since 1.29.0 * @returns {object} the context object * @public * @function */ Opa5.getContext = Opa.getContext; //Dont document these as public they are just for backwards compatibility Opa5.matchers = {}; Opa5.matchers.Matcher = Matcher; Opa5.matchers.AggregationFilled = AggregationFilled; Opa5.matchers.PropertyStrictEquals = PropertyStrictEquals; /** * A map of QUnit-style assertions to be used in an opaTest. * * Contains all methods available on <code>QUnit.assert</code> for the running QUnit version. * Available assertions are: <code>ok</code>, <code>equal</code>, <code>propEqual</code>, * <code>deepEqual</code>, <code>strictEqual</code> and their negative counterparts. * You can define custom OPA5 assertions in the extensions section of {@link sap.ui.test.Opa5.extendConfig}. * * Example usage: * <pre> * oOpa5.waitFor({ * success: function () { * Opa5.assert.ok(true, "Should be true"); * } * }); * </pre> * * For more information, see {@link module:sap/ui/test/opaQunit}. * * @name sap.ui.test.Opa5.assert * @public * @static * @type QUnit.Assert */ /** * Settings for a new page object, consisting of actions and assertions. * * @typedef {Object} sap.ui.test.PageObjectDefinition * * @property {string} [viewName] * When a <code>viewName</code> is given, all <code>waitFor</code> calls inside of the page object * will get a <code>viewName</code> parameter. * * Example: * <pre> * Opa5.createPageObjects({ * viewName: "myView", * onMyPageWithViewName: { * assertions: { *