19 Test and Debug Oracle JET Apps
Test Oracle JET Apps
Use third-party tools such as QUnit or Selenium WebDriver to test your Oracle JET app.
Test Apps
You can use virtually any testing tool that tests client-side HTML apps written in JavaScript for testing Oracle JET apps.
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
div
element 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/v14.0.0/redwood/oj-redwood-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 apps 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.
Use BusyContext API in Automated Testing
Use BusyContext
to wait for a component or other condition to complete some action before interacting with it.
The purpose of the
BusyContext API is to accommodate sequential dependencies of asynchronous operations. Typically, you use 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 app 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 app having multiple Oracle JET components rather than a single component.
var busyContext = Context.getPageContext().getBusyContext();
-
Scoped for the nearest DOM element
Choose a busy context scoped for a DOM node when your app must wait until a specific component’s operation completes. For example, you may want to wait until an
ojPopup
completes an open or close animation before initiating the next task in the app flow. Use thedata-oj-context
marker attribute to define a busy context for a DOM subtree.<div id="mycontext" data-oj-context> ... <!-- JET content --> ... </div> var node = document.querySelector("#mycontext"); var busyContext = Context.getContext(node).getBusyContext();
Determining the Ready State
After obtaining a busy context, the next step is to inquire the busy state. 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 Context.getPageContext().getBusyContext().getBusyStates().join('\\n');";
Object busyStatesLog = ((JavascriptExecutor)webDriver).executeScript(evalString);
String retValue = "";
if (busyStatesLog != null){
retValue = busyStatesLog.toString();
Assert.fail("waitForJetPageReady failed - !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 app opt'd in on the whenReady wait for bootstrap?
// 2) If the app 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'] && 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'] ? " +
"(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
Context.setBusyContextDefaultTimeout(18000);
var done = assert.async();
assert.expect(1);
var popup = document.getElementById("popup1");
// busy context scoped for the popup
var busyContext = 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.
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 app flow depends on its completion.
-
Resolve the busy state when the operation completes.
The app is responsible for releasing the busy state. The app 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.
Debug Oracle JET Applications
Since Oracle JET web apps are client-side HTML5 applications written in JavaScript, you can use your favorite browser's debugging facilities.
Debug Web Applications
Use your source code editor and browser's developer tools to debug your Oracle JET app.
Developer tools for widely used browsers like Chrome, Edge, and Firefox provide a range of features that assist you in inspecting and debugging your Oracle JET app as it runs in the browser. Read more about the usage of these developer tools in the documentation for your browser.
By default, the ojet build
and ojet serve
commands use
debug versions of the Oracle JET libraries. If you build or serve your Oracle JET app in
release mode (by appending the --release
parameter to the ojet
build
or ojet serve
command), your app uses minified versions of
the Oracle JET libraries. If you choose to debug an Oracle JET app that you built in release
mode, you can use the --optimize=none
parameter to make the minified output
more readable by preserving line breaks and white space:
ojet build --release --optimize=none
ojet serve --release --optimize=none
Note that browser developer tools offer the option to "pretty print" minified source files to
make them more readable, if you choose not to use the --optimize=none
parameter.
You may also be able to install browser extensions that further assist you in debugging your app.
Finally, if you use a source code editor, such as Visual Studio Code, familiarize yourself with the debugging tools that it provides to assist you as develop and debug your Oracle JET app.