Skip to content

Dynamic applications

A dynamic application is like a regular application, but it is defined and instantiated during the execution of a flow. It can be used to create cross-application flows in a more fluid manner than having to define regular applications and using Flow.run to execute parts of the overall flow. Dynamically created applications are attached to or launched on-the-fly.

The Application constructor takes two arguments. The first argument is the application configuration. It can be either a string in which case we will;

  1. try to find an existing application with the same identifier or name and use its configuration, or
  2. construct a NATIVE app configuration with a titleBarContains match based on the argument.

So if you have a “Notepad” application, the example above will use that configuration, if not then it will try to find a window that contains the text “Notepad” in the titlebar and attach to that window as a native application.

If you provide an object to the constructor it will be used as the configuration for the application. In that case the following properties are supported (the same ones you use in Cuesta to configure an app):

  • type the type of the application (see ApplicationTypes for a list of pre-defined types)
  • launchWith the command or url used to launch the application
  • launchWithArguments the arguments given to the process when launching
  • launchWithFlowId the id of the flow to be used to launch the application (this flow must spin up the application)
  • launchTimeout in seconds
  • workingDirectory the working directory to set for the application when launching
  • titleBarContains a regex to match the titlebar when attaching
  • frameContains a regex to match the frame-type when attaching
  • processContains a regex to match the process name when attaching
  • executablePathContains a regex to match the path to the executable when attaching
  • urlContains a regex to match the url when attaching (for web-based applications)
  • jvmLocation an alternative location to look for a JVM (if Java app)
  • openIn select between “window”, “tab”, “app” and “incognito” for browsers that support these modes
  • switches extra switches for embedded Chrome browser
  • popupHandling use “PopupShowInInternalWindow” to use an alternative popup-handling for embedded browsers
  • killOnDetach close the embedded browser once the flow ends (defaults to true)

Most properties are optional, however you need launchWith to be able to launch an application. Only mandatory options are type and at least one of the *Contains properties.

Attach to an existing notepad window:

javascript
var notepad = new Application({ 
  titleBarContains: "Notepad", 
  type: ApplicationTypes.NATIVE 
});

If no existing “Notepad” instance is running we might want to be able to launch a new instance:

javascript
var notepad = new Application({ 
  launchWith: "notepad.exe",
  titleBarContains: "Notepad", 
  type: ApplicationTypes.NATIVE 
});

The second argument contains configuration options for the behaviour of the Application, it can hold the following properties:

  • noImmediateLaunchOrAttach means that we don’t automatically attach/launch the application - you then have to invoke the launchOrAttach method on the application instance to actually interact with the application.
  • noLaunch disables the launch functionality s.t. the application must be running already

Application methods

Create a field

For convenience, you can invoke the newField(...) function on the Application instance instead of creating new Fields using their own constructor.

javascript
var notepad = new Application("Notepad");
var contentField = notepad.newField("**/(name)RichEdit Control");
// is equivalent to 
contentField = new Field("**/(name)RichEdit Control", notepad);

Then you can interact with the field as any normal field invoking .click(...), .inspect(...) etc.

Launch and/or attach

The launchOrAttach function will attach (if it can find a matching application) or launch the application. It can be invoked if you have set the noImmediateLaunchOrAttach to true when you created the application.

Borrowing an application

If your flow on application A needs to interact with application B where B is already running and attached to manatee, you can request temporary access to it by borrowing it. Note that while A has borrowed B, flows on B will be blocked from running until A releases B.

This is practical since manatee can only attach to a given application once. If you’re not sure if the application is attached, you can use syntax like the following:

javascript
var notepad = new Application({ 
  titleBarContains: "Notepad", 
}, { noImmediateLaunchOrAttach: true });

notepad.attach() || notepad.borrow({ throw: true});

.attach(), .launch() and .borrow()

Fine-grained control over this process comes from use of the launch, attach and borrow methods:

javascript
var notepad = new Application({ 
  titleBarContains: "Notepad", 
  launchWith: "notepad.exe"  
}, { noImmediateLaunchOrAttach: true });

// First try attaching to an unattached notepad window
// If that fails, borrow an already attached instance
// If that fails, try launching a new instance
// If that fails, give up and throw an error
if (!(notepad.attach() || notepad.borrow() || notepad.launch())) {
    throw Error("Could not attach, launch or borrow Notepad");
}
new Field("**/Edit", notepad).input("Hello, dynamic application!");

// Optionally signal that we are done with the application
nodepad.relase();
  • attach() will only succeed if the application is already running and not already attached to any manatee session or other dynamic application.
  • launch() will always launch a new instance of the application and attempt to attach to it before returning.
  • borrow() will only succeed if the application is already running and attached to any manatee session.

Each of these methods return true if they succeeded and false if they failed.

Throwing errors

If you want the methods to throw an error on failure instead of returning false, you can do so as follows:

javascript
notepad.attach({ throw: true });
notepad.borrow({ throw: true });
notepad.launch({ launchArg1: 'value 1' }, { throw: true });

.release()

The dynamically attached or borrowed application is automatically released at the end of the flow. If you want to release it sooner, you can do so with the .release() method.

App methods

The Application instance contains approximately (no session()) the same set of methods as on the global App instance. This means you can read the title(), exit() the application, navigate(...) to other urls if the application is browser-based and so on.

Application types

A list of predefined application types to use in a dynamic app. The following are available:

javascript
ApplicationTypes.JAVA;
ApplicationTypes.NATIVE;
ApplicationTypes.CHROME;
ApplicationTypes.FIREFOX;
ApplicationTypes.EDGE;
ApplicationTypes.IE;
ApplicationTypes.EMBEDDED_CHROME;
ApplicationTypes.EMBEDDED_IE;
ApplicationTypes.PROXIED_IE;

Lifecycle hooks

It is possible to have a function called at various stages of a dynamic application’s lifecycle. The following hooks are available:

  • onLaunch called when manatee has launched the application
  • onAttach called when the dynamic app attaches successfully
  • onAttachExisting called only when the dynamic app attaches successfully to an already running application
  • onDetach called when the dynamic app detaches from the application. This could indicate that the application was closed or that the flow ended
  • onBorrow called when the dynamic app borrows the application from another configured application
  • onReturn called when the borrowed application is returned to the original owner
javascript
var app = new Application({
  launchWith: 'https://dr.dk',
  urlContains: 'dr.dk',
  type: ApplicationTypes.CHROME
});

app.onLaunch = function() { Log.info('', 'onLaunch!'); };
app.onAttach = function() { Log.info('', 'onAttach!'); };
app.onAttachExisting = function() { Log.info('', 'onAttachExisting!'); };
app.onDetach = function() { Log.info('', 'onDetach!'); };
app.onBorrow = function() { Log.info('', 'onBorrow!'); };
app.onReturn = function() { Log.info('', 'onReturn!'); };

app.launchOrAttach();

Limitations

The callbacks are not called on the main thread of the flow, so access shared resources with caution.