Testing Oracle JET Applications
Use third-party tools such as QUnit or Selenium WebDriver to test your Oracle JET application. Google, Apple, and Microsoft also provide instructions for testing Andoid, iOS, and Windows applications.
Testing Applications
You can use virtually any testing tool that tests client-side HTML applications written in JavaScript for testing Oracle JET applications.
For internal development, Oracle JET uses the following tools for testing Oracle JET components and toolkit features:
-
QUnit: JavaScript unit testing framework capable of testing any generic JavaScript project and used by the jQuery, jQuery UI, and jQuery Mobile projects.
QUnit requires configuration on your test page to include library and CSS references. You must also add the HTML
divelement to your page. In the example below, the highlighted code shows additions required by QUnit. The actual paths will vary, depending upon where you install QUnit.<!doctype html> <html lang="en"> <head> <link rel="stylesheet" href="../../../../code/css/libs/oj/v5.2.0/alta/oj-alta-min.css"></link><link rel="stylesheet" href="../../../css/qunit.css"><script type="text/javascript" src="../../../js/qunit.js"></script><script>QUnit.config.autoload = false;QUnit.config.autostart = false;</script><script data-main="js/main" src="../../../../code/js/libs/require/require.js"></script> </head> <body><div id="qunit"></div><div id="qunit-fixture"> <oj-slider id="slider1"></oj-slider> </div> </body> </html>For more information about QUnit, see http://qunitjs.com.
-
Selenium WebDriver: Alternative method of testing applications that you do not want to modify for QUnit or that contain multiple HTML pages.
For additional information, see http://docs.seleniumhq.org/projects/webdriver.
Testing Hybrid Mobile Applications
You can test your hybrid mobile application in a local browser, using an emulator or simulator, and on an attached device.
-
Testing in a local browser
When you invoke
ojet servewith the–-browseroption to serve your hybrid mobile application in a local browser, you can use the same method for testing Android, iOS, and Windows applications that you would use for testing any web application. However, this method can only approximate the end user experience and is most useful early in the development process.Depending upon your use case, you may need to add Cordova plugins to add functionality to your mobile hybrid app. Many Cordova plugins provide mock data when deploying to the browser platform. If, however, you add a Cordova plugin that doesn’t have browser platform support, you can add objects that represent mock data to
cordovaMock.jsinsrc/js. -
Testing in an emulator or simulator
You can invoke
ojet servewith the--emulatoroption to test the functionality of your application in the iOS Simulator, Windows emulator, or Android Virtual Devices (AVDs) using the Android emulator. These methods can approximate the look and feel of an actual device, but you won’t be able to test performance or responsiveness to touch reliably. For additional information, see -
Testing on attached physical devices
You can invoke
ojet servewith thedestination=deviceoption to test the functionality on attached physical devices. This provides the most reliable form of testing, but you may not have access to all the devices that your users might use to run your application.If you want to serve your application to an iOS device, you must take additional steps as described in Packaging and Publishing Hybrid Mobile Applications.
-
Working around cross-origin resource sharing (CORS) issues
Hybrid mobile applications that communicate with remote services may encounter issues if they request resources that originate in difference domains. An example includes a response such as the following:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
To work around these types of issue, your server-side administrator may need to modify the policy used by the remote server to allow cross-site requests. For example, to access a remote service managed by Oracle’s Mobile Cloud Service (MCS), an MCS administrator configures MCS’s
Security_AllowOriginenvironment policy with a comma separated list of URL patterns that identify the remote services that serve a resource from different domains.
For additional information about testing hybrid mobile applications on specific platforms, see the following resources:
Using oj.BusyContext API in Automated Testing
Use oj.BusyContext to wait for a component or other condition to complete some action before interacting with it.
The purpose of the oj.BusyContext API is to accommodate sequential dependencies of asynchronous operations. Typically, you use oj.BusyContext in test automation when you want to wait for an animation to complete, a JET page to load, or a data fetch to complete.
Wait Scenarios
The Busy Context API will block until all the busy states resolve or a timeout period lapses. There are four primary wait scenarios:
-
Components that implement animation effects
-
Components that fetch data from a REST endpoint
-
Pages that load bootstrap files, such as the Oracle JET libraries loaded with RequireJS
-
Customer-defined scenarios that are not limited to Oracle JET, such as blocking conditions associated with application domain logic
Determining the Busy Context’s Scope
The first step for waiting on a busy context is to determine the wait condition. You can scope the granularity of a busy context for the entirety of the page or limit the scope to a specific DOM element. Busy contexts have hierarchical dependencies mirroring the document's DOM structure with the root being the page context. Depending on your particular scenario, target one of the following busy context scopes:
-
Scoped for the page
Choose the page busy context to represent the page as a whole. Automation developers commonly need to wait until the page is fully loaded before starting automation. Also, automation developers are usually interested in testing the functionality of an application having multiple Oracle JET components rather than a single component.
var busyContext = oj.Context.getPageContext().getBusyContext(); -
Scoped for the nearest DOM element
Choose a busy context scoped for a DOM node when your application must wait until a specific component’s operation completes. For example, you may want to wait until an
ojPopupcompletes an open or close animation before initiating the next task in the application flow. Use thedata-oj-contextmarker attribute to define a busy context for a DOM subtree.<div id="mycontext" data-oj-context> ... <!-- JET content --> ... </div> var node = $("#mycontext"); var busyContext = oj.Context.getContext(node[0]).getBusyContext();
Determining the Ready State
After obtaining a busy context, the next step is to inquire the busy state. oj.BusyContext has two operations for inquiring the ready state: isReady() and whenReady(). The isReady() method immediately returns the state of the busy context. The whenReady() method returns a Promise that resolves when the busy states resolve or a timeout period lapses.
The following example shows how you can use isReady() with WebDriver.
public static void waitForJetPageReady(WebDriver webDriver, long timeoutInMillis)
{
try
{
final WebDriverWait wait = new WebDriverWait(webDriver, timeoutInMillis / _THOUSAND_MILLIS);
// Eat any WebDriverException
// "ExpectedConditions.jsReturnsValue" will continue to be called if it doesn't return a value.
// /ExpectedConditions.java#L1519
wait.ignoring(WebDriverException.class);
wait.until(ExpectedConditions.jsReturnsValue(_PAGE_WHEN_READY_SCRIPT));
}
catch (TimeoutException toe)
{
String evalString = "return oj.Context.getPageContext().getBusyContext().getBusyStates().join('\\n');";
Object busyStatesLog = ((JavascriptExecutor)webDriver).executeScript(evalString);
String retValue = "";
if busyStatesLog != null)
retValue = busyStatesLog.toString();
Assert.fail("waitForJetPageReady failed - !oj.Context.getPageContext().getBusyContext().isReady() - busyStates: " +
retValue); }
}
// The assumption with the page when ready script is that it will continue to execute until a value is returned or
// reached the timeout period.
//
// There are three areas of concern:
// 1) Has the application opt'd in on the whenReady wait for bootstrap?
// 2) If the application has opt'd in on the jet whenReady strategy for bootstrap "('oj_whenReady' in window)",
// wait until jet core is loaded and have a ready state.
// 3) If not opt-ing in on jet whenReady bootstrap, make the is ready check if jet core has loaded. If jet core is
// not loaded, we assume it is not a jet page.
// Check to determine if the page is participating in the jet whenReady bootstrap wait period.
static private final String _BOOTSTRAP_WHEN_READY_EXP = "(('oj_whenReady' in window) && window['oj_whenReady'])";
// Assumption is we must wait until jet core is loaded and the busy state is ready.
static private final String _WHEN_READY_WITH_BOOTSTRAP_EXP =
"(window['oj'] && window['oj']['Context'] && oj.Context.getPageContext().getBusyContext().isReady() ?" +
" 'ready' : '')";
// Assumption is the jet libraries have already been loaded. If they have not, it's not a Jet page.
// Return jet missing in action "JetMIA" if jet core is not loaded.
static private final String _WHEN_READY_NO_BOOTSTRAP_EXP =
"(window['oj'] && window['oj']['Context'] ? " +
"(oj.Context.getPageContext().getBusyContext().isReady() ? 'ready' : '') : 'JetMIA')";
// Complete when ready script
static private final String _PAGE_WHEN_READY_SCRIPT =
"return (" + _BOOTSTRAP_WHEN_READY_EXP + " ? " + _WHEN_READY_WITH_BOOTSTRAP_EXP + " : " +
_WHEN_READY_NO_BOOTSTRAP_EXP + ");";The following example shows how you can use whenReady() with QUnit.
// Utility function for creating a promise error handler
function getExceptionHandler(assert, done, busyContext)
{
return function (reason)
{
if (reason && reason['busyStates'])
{
// whenReady timeout
assert.ok(false, reason.toString());
}
else
{
// Unhandled JS Exception
var msg = reason ? reason.toString() : "Unknown Reason";
if (busyContext)
msg += "\n" + busyContext;
assert.ok(false, msg);
}
// invoke done callback
if (done)
done();
};
};
QUnit.test("popup open", function (assert)
{
// default whenReady timeout used when argument is not provided
oj.BusyContext.setDefaultTimeout(18000);
var done = assert.async();
assert.expect(1);
var popup = document.getElementById("popup1");
// busy context scoped for the popup
var busyContext = oj.Context.getContext(popup).getBusyContext();
var errorHandler = getExceptionHandler(assert, done, busyContext);
popup.open("#showPopup1");
busyContext.whenReady().then(function ()
{
assert.ok(popup.isOpen(), "popup is open");
popup.close();
busyContext.whenReady().then(function ()
{
done();
}).catch(errorHandler);
}).catch(errorHandler);
});Creating Wait Conditions
Jet components utilize the busy context to communicate blocking operations. You can add busy states to any scope of the busy context to block operations such as asynchronous data fetch.
The following high level steps describe how to add a busy context:
-
Create a Scoped Busy Context.
-
Add a busy state to the busy context. You must add a description that describes the purpose of the busy state. The busy state returns a resolve function which is called when it’s time to remove the busy state.
Note:
Busy Context dependency relationships are determined at the point the first busy state is added. If the DOM node is re-parented after a busy context was added, the context will maintain dependencies with any parent DOM contexts. -
Perform the operation that needs to be guarded with a busy state. These are usually asynchronous operations that some other application flow depends on its completion.
-
Resolve the busy state when the operation completes.
Important:
The application is responsible for releasing the busy state. The application must manage a reference to the resolve function associated with a busy state, and it must be called to release the busy state. If the DOM node that the busy context is applied to is removed in the document before the busy state is resolved, the busy state will be orphaned and will never resolve.