The capabilities described on this page are included in Appian's standard capability tier. Usage limits may apply. |
The Client module provides basic interfaces, classes, and methods you'll use to develop robotic tasks. Because it contains essential functionality, this module is available for every robotic task.
This page describes the Client module's functions in a robotic task. The Client module comprises many core functions, such as initializing other modules, communicating with the RPA console, interacting with the screen, using the keyboard, and verifying or resetting conditions on the host machine.
This page describes how to integrate Client module methods into your robotic task. Appian RPA's low-code Client methods provide an easier and more robust development experience. For more experienced developers, this page also discusses the options available using the Java module.
The Client module includes many methods that provide essential functionality to robotic tasks. In the Appian RPA console, you won't find a single module labeled Client. Instead, the methods are grouped by their common functionality:
Because the Client module is used by every robotic task, it's added as a dependency by default. In the Maven pom.xml
file, the dependency is listed as jidoka-client-api-x.y.z.jar
, where x.y.z
represents the version number.
The Client module's most important interfaces, classes, and methods are grouped in these packages:
Every robotic task must implement the IRobot interface, located in the com.novayre.jidoka.client.api.IRobot
package, as well as use the annotation @Robot. A class implementing this interface and annotated with @Robot is a robotic task. One project can have several robotic tasks if there are several classes implementing the interface.
There are several methods that can be called to verify the conditions of your host machine and your robotic task before execution. Some of these methods can be particularly helpful in preparing your robotic task for execution, like the startUp(), beforeGetInstructions(), and beforeGetSupportFiles() methods. Note that these methods return a boolean value. If the result is false
, the execution will not start.
The iRobot interface publishes the startUp() method, which will always called at the beginning of the execution of a robotic task, prior to executing the actions of the workflow. This method can be useful to verify any condition on the host machine, such as determining if a certain application is available, the screen resolution, or if the robotic task can be executed. The method can also initialize the different instances needed by the robotic task, such as IJidokaServer or IWindows.
The code sample below shows how these instances are initialized, in addition to how to check if the robotic task can be executed or not. If possible, then the robotic task continues and starts executing the workflow.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean startUp() throws Exception {
// Initialization of the robotic task components
server = JidokaFactory.getServer();
windows = IJidokaRobot.getInstance(this);
String robotName = server.getExecution(0).getRobotName();
boolean robotAllowedNow = server.getExecution(0).isRobotAllowedNow(robotName);
server.info(String.format("Is this robotic task allowed now?: %s", robotAllowedNow));
return robotAllowedNow;
}
Another method called before execution is beforeGetInstructions(), which is invoked before the server gets the parameters from the console. In the following code, you can see how the beforeGetInstructions() method is used to calculate the available free space.
1
2
3
4
5
6
7
8
9
10
11
@Override
public boolean beforeGetInstructions() throws Exception {
server = (IJidokaServer< ? >) JidokaFactory.getServer();
File file = new File(server.getCurrentDir());
long freeSpace = file.getFreeSpace();
server.info(String.format("Free space: %d bytes", freeSpace));
return IRobot.super.beforeGetInstructions();
}
Another method to invoke before starting the execution is beforeGetSupportFiles(), which is invoked before obtaining the robotic task's support files from the console. This method can be used to remove previously downloaded support files.
The following code example shows how this method is used to list the previously downloaded support files.
1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public boolean beforeGetSupportFiles() throws Exception {
server = (IJidokaServer< ? >) JidokaFactory.getServer();
File folder = new File(server.getCurrentDir());
File[] files = folder.listFiles();
for(File file:files) {
server.info(file.getPath());
}
return IRobot.super.beforeGetSupportFiles();
}
As in any Java program, error handling consists of handling exceptions thrown during the execution that may cause a change in the execution sequence. By default, a robotic task's execution will end if an exception is thrown from the action that is currently running.
There are two strategies for exceptions: catch and treat exceptions when they occur, or raise exceptions so that they can be treated in the code that called the method where the exception was detected.
Appian RPA allows you to cluster the code that handles the exceptions, and allows the robotic task to continue processing items from any point of the workflow immediately after treating the exception. This eliminates the need to build conditional blocks in the workflow for each possible error that may happen.
All these advantages are obtained by overwriting the manageException() method from the IRobot interface:
1
2
3
4
5
@Override
public String manageException(String action, Exception exception)
throws Exception {
...
}
Appian RPA will invoke this method whenever an action throws an exception without catching it. It passes the method name associated to the action and the exception as parameters, encapsulated in a InvocationTargetException(). To retrieve the original exception, use the getCause() method from Exception. The method must return the name of the method associated with the action where the execution will resume once the exception handling finishes. If it isn't returned, it will throw an exception and end the robotic task's execution. The default method's implementation throws the exception received as a parameter, without processing it, which causes the corresponding end of the robotic task's execution. It is recommended to call this default implementation at the end of the manageException() method as a fallback to prevent the loss of exceptions that have not been caught due to having overwritten the method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
public String manageException(String action, Exception exception) throws Exception {
// We get the message of the exception
String errorMessage = ExceptionUtils.getRootCause(exception).getMessage();
// We send a screenshot to the log so the user can see the screen in the moment
server.sendScreen("Screenshot at the moment of the error");
// If we have a FatalException we should abort the execution.
if (ExceptionUtils.indexOfThrowable(exception, JidokaFatalException.class) >= 0) {
server.error(StringUtils.isBlank(errorMessage) ? "Fatal error" : errorMessage);
return IRobot.super.manageException(action, exception);
}
// If the error is processing one items we must mark it as a warning and go on with the next item
if (ExceptionUtils.indexOfThrowable(exception, JidokaItemException.class) >= 0) {
server.warn(StringUtils.isBlank(errorMessage) ? "Item error" : errorMessage);
server.setCurrentItemResultToWarn(errorMessage);
return "moreItems";
}
server.warn("Unknown exception!");
// If we have any other exception we must abort the execution, we don't know what has happened
return IRobot.super.manageException(action, exception);
}
Even though this method ends by throwing an exception, Appian RPA calls the cleanUp() method from the relevant workflow libraries and the robotic task itself regardless.
When managing exceptions globally, carefully consider whether the same method is called by more than one action in the workflow. Under these circumstances, the action that should continue the execution of the workflow would be undetermined. To avoid this, encapsulate the code in a method, which will be called from the methods associated with the actions involved. This way, the robotic task can have several actions, each calling different methods, but finally executing the same piece of code.
In short, when you use global error handling, avoid using the same method in different actions to prevent the robotic task from resuming on an undetermined point of the workflow.
The IRobot interface publishes the cleanUp() method. Appian RPA will always call this method, regardless of the result of the robotic task's execution. Whether it has ended successfully or it has failed throwing some exception, the end of an execution is a good moment to close the applications that the robotic task has opened or send the files that have been generated during the execution to the server.
In the example below, the String[]
returned by the method must contain the local paths to the generated files in order to send the files to the server.
1
2
3
4
5
6
@Override
public String[] cleanUp() throws Exception {
// Output file of the robot. Will be shown in the Appian RPA console
return new String[] { inputFile };
}
Every Appian RPA workflow library — also known as a nanorobot — must implement the interface INano and use the annotation @Nano at class level. A single project can have several nanorobots.
1
2
3
4
@Nano
public class NanoRobot implements INano {
...
}
The INano interface publishes the methods init() to initiate the nanorobot (before any other action) and cleanUp() to end the execution of the robotic task that is using the Appian RPA workflow library. The Appian RPA platform will always call these methods.
Libraries use the @FieldLink annotation to share values between the Appian RPA libraries and the robotic tasks. This annotation automatically shares the value associated with the annotation's attribute between a robotic task and an Appian RPA library. The annotation receives one parameter, which defines the value name in the Appian RPA context.
When you use the annotation in a robotic task, you must specify the name of the library followed by ::
, and the name of the attribute. If you use the annotation within the library, you must only specify the name of the attribute.
In the following code sample, we can see how a nanorobot's attribute is defined. Below it, another snippet shows how that attribute would be used within a robot.
1
2
3
4
5
/**
* Application directory location
*/
@FieldLink("NanoRobot::applicationDirectory")
private String applicationDirectory;
When specifying the name of the library, you can indicate the version between two colons (::
). If you don't indicate the version, any version of the library with the specified name will receive the attribute.
You can also define the annotation within the robotic task using ::
followed by the name of the attribute, which makes the value global so it can be used in any Appian RPA library.
The following code sample shows the definition of an attribute that is published globally in the Appian RPA platform:
1
2
3
4
5
/**
* Server instance
*/
@FieldLink("::server")
private IJidokaServer<?> server;
Further on, this page describes how you can interact with the robotic tasks from the Appian RPA API in the IJidokaContext. For now, here is a brief reference to the use of the Appian RPA API to see how the literal :
works.
When you call a library method from the Appian RPA API using the callNano() method (described later in more detail), you use construction similar to the one specified for @FieldLink. For methods, indicate the library name followed by the method name, with :
as a separator. You can also add the library version between the library and method name, also separated by :
.
The JidokaFactory class allows you to obtain an instance of an Appian RPA module. If any error occurs, the returned value will be null
.
To get an instance of a particular class, use the method: getInstance(IRobot robot, String className);
where className
is the name of the class to get.
Additionally, you can get a reference of IJidokaServer by calling the method getServer()
.
An object implementing the IJidokaServer interface communicates with the Appian RPA server and console. In addition to sending log messages to the console, this object does three important things to monitor a robotic task in the Appian RPA console:
The Item Data section explains how to implement these actions.
Data regarding a robotic task execution is important for the system to work properly, so be sure to use these operations and this API appropriately.
The IJidokaServer interface can use generics, IJidokaServer<T extends Serializable>, although this is optional. The specified parameter is a class implementing Serializable. In Appian RPA, the generic class associated to IJidokaServer is called a context class.
IJidokaServer<?> inherits from IJidokaStatistics, IJidokaContext, and Log. These interfaces are described in more detail in the following sections.
Methods directly present in the IJidokaServer interface override those in the Log interface of apache-commons, except the activeThirdPartyLog() method. This method allows you to send third-party logs to the execution trace using the org.apache.commons.logging.Log interface. Be aware that this way of sending log to the trace will work if the classes sending it are not inside com.novayre.jidoka package.
IJidokaStatistics allows you to notify the server about the number of items to be processed, the beginning of the item processing, and its result. When the result is sent, the console considers item processing to be finished.
The methods included in IJidokaStatistics are:
The setCurrentItemResult methods inform the server about a result with no error or a process with some warning. The developer chooses what kind of results to send to the server. These methods are overloaded and allow you to send a description of the processing result. This description (contained in String detail below) is shown in the execution log.
These methods have been overloaded to allow you to include information about statistics per item. This is aimed to be able to provide additional information about the result of an item processing. For example, you could add a parameter to include the cause of a warning. The overloaded methods are as follows:
The example below shows how to add the statistics per item within the map in the second parameter. The code uses a Map in which the keys RESULT and DETAIL and the values OK and PASSED are added. This way, the console is informed about the properties defined in the Map for each item.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* Set the result of the process of the current item to OK.
*/
private void itemOk() {
Map<String, String> map = new TreeMap<>();
map.put(ETestCaseProperty.RESULT.name(), EResult.OK.name());
map.put(ETestCaseProperty.DETAIL.name(),
ETestCasePropertyDetail.PASSED.name());
Enum<?> currenTestCase = tests.get(currentItem - 1);
server.setCurrentItemResultToOK(currenTestCase.name(),
map);
currentItem++;
}
Previous methods can be emulated with setCurrentItemResult(ItemData item), which extends the possibilities for notifying the server about results of an item.
The support class ItemData is used to specify different options:
In developing a robotic task in Appian RPA, remember that it is very important to notify the server about these three key aspects:
The IJidokaContext context class enables you to save persistent information between executions, such as the status or the value of the context class from a previous execution. This context class is helpful for those cases in which you are expecting that the robotic task won't be able to process all the items. Sometimes, the list of items is too large and the robotic task may need hours to process them all.
In such cases, external factors may interrupt the robotic task. For example, the applications it is using are not prepared for long processing times, machine performance, etc. Through the context class, you can specify that the robotic task resume its operation on the item where it was interrupted. This is helpful because if it starts again from the first item, it could lead to the same situation as long as the conditions that caused the interruption don't change. This would lead to never completely processing the list of items.
If the list of items to be processed is obtained from an Excel file or any other input provided by the user, the context class may not be necessary. Tracking of what has been processed and what hasn't may be registered in the Excel file or the specified input itself. The file serves as an output of the process and can be used in turn as input for the next execution.
Sometimes, the list of items to be processed is not known when the process starts, because it is dynamically gathered by the robotic task. In such cases, the context class can indicate which item is being processed from the list of items.
As mentioned earlier, the context class must be serializable because it's sent to the server to be stored and reused in a later execution. Remember that a serializable class is every class implementing the interface Serializable, and whose members are also Serializable. Typically, the context class stores values that allow our robotic task to resume at the point where it was interrupted.
Suppose a robotic task must email the purchase invoice in PDF format to a list of customers of a shop. This list is stored in an Excel file and it contains the name, ID card, email, and details of the purchase made. Each row in the Excel file contains one purchase, so there may be more than one row per client and not necessarily consecutive, depending on the number of purchases and the moment when a customer has made them. Imagine the file contains 1000 rows. The robotic task should read the Excel file, starting at the first row, retrieve the name, the email address, the ID card, and the purchase data, generate the PDF with the invoice, and send it by email.
If for some reason the robotic task is interrupted, the next time the robotic task is launched, it will have to start again. This could result in a lot of time wasted, assuming a long processing time per row. Moreover, it doesn't seem like a good idea to send repeated emails to customers who have already received their invoices.
This can be avoided by one of two ways:
You could write the result of each item processed (each invoice) in the input Excel file and use it as an output Excel file. In turn, you could use this file again as input for the next execution.
Alternatively, you could create a context class called ClientsContext, which would have a property called lastRowProcessed. It will store the number of the last row of the Excel file that has been processed correctly. The class would look like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.io.Serializable;
public class ClientsContext implements Serializable {
private static final long serialVersionUID = 1L;
private int lastRowProcessed = 0;
public int getLastRowProcessed() {
return lastRowProcessed;
}
public void setLastRowProcessed(int lastRowProcessed) {
this. lastRowProcessed = lastRowProcessed;
}
}
The object server would be declared as follows:
1
2
private IJidokaServer<ClientsContext> server;
server = (IJidokaServer<ClientsContext>) JidokaFactory.getServer();
Later, when the item is processed, the robotic task sends an instance of ClientsContext to the server to create the persistent context. The methods that retrieve and set the context class are:
null
.Context is only read once at the beginning of the execution. This way, any change made on it will not be reflected until next execution. This class was meant for that purpose, to get it at the start and update it at the end. Additionally, it is a best practice to set the context class every time an item is correctly processed.
The following code snippet lets you set which row was the last row to be successfully processed. Here row
is the position of the last processed row:
1
2
clientsContext.setLastRowProcessed(row);
server.setPersistentContextValue(clientsContext);
When all rows have been processed, it is a good practice to send an empty context class:
1
server.setPersistentContextValue(new ClientsContext());
Then, when a new execution starts, the robotic task would obtain that empty class as the context class, thus managing the case of receiving null:
1
2
3
4
5
clientsContext = server.getPersistentContextValue();
if(clientsContext == null) {
clientsContext = new ClientsContext();
}
Another option for more complex context classes is to send a String as a class, whose value is a JSON obtained through the serialization of an object. Appian RPA will store the String and it's up to the developer to restore (deserialize) the object once retrieved through server.getPersistentContextValue().
This section describes the methods the IJidokaContext interface provides to IJidokaServer<?>. The interface allows you to manage the context between robotic task executions and obtain parameters.
Suppose you're developing a robotic task to get a list of books from a bookstore's web page. Create a parameter called "author", whose default value could be **
. This parameter will allow you to filter the books by author, so that if your robotic task receives the parameter with the value **
, it will retrieve all the books. If it receives any other value, for example Carl Sagan
, it will only retrieve the books whose author is Carl Sagan.
The function retrieves the parameter this way:
1
String paramAuthor = server.getWorkflowParameters().get("author");
String getCurrentDir(): returns the robotic task's folder. Every robotic task has its own folder within the client's folder. This page assumes that the client is on C:\AppianRPA
, though it can be any other folder. If, for example, the robotic task is called RobotLibrary, this method will return:
1
C:\AppianRPA\AppianRPA-workspace\RobotLibrary
This method is helpful to find the folder where the workFlow variables of type File
are stored, and to save the files generated by the robotic task, which should be in the robotic task's working directory. You can also create subfolders to save the files by using the class Paths
:
1
2
3
4
5
6
7
// Creating the path for file.txt in the robotic task local working directory
File file = Paths.get(server.getCurrentDir(), "file.txt").toFile();
// Creating the path for file.txt in a subdirectory of the robotic task local
// working directory
File file = Paths.get(server.getCurrentDir(), "subdir",
"file.txt").toFile();
BufferedImage
(java.awt.image.BufferedImage
), so you can perform some operation on it.@FieldLink
in the library. With that key, you can set or get its value. The value of the elements in the Map is updated between the different calls to the robotic task or library actions. Likewise, if the value is updated directly on the Map, it will be available for the next execution within the attribute with the associated FieldLink
.1
2
3
4
// Update a value in the map with the shared attributes
server.getFieldLinks()
.put("Aeb3411BancoPastorLibrary::applicationPassword",
server.getWorkflowParameters().get("applicationPassword"));
If you're using linked attributes, it is not recommended to manage the map programmatically. Instead, assign a new value for the attribute.
void callNano(String nanoAction): allows you to call an action defined in an Appian RPA library. This action does not accept input or output parameters.
1
2
// Call nanorobot with API method to close application
server.callNano("Aeb3411BancoPastorLibrary:closeApplication");
Object...
as a parameter.List<IUsernamePassword> getCredentials(String application): allows you to get a list of credentials associated with an application name.
1
2
// Get first credentials for Banco Pastor
IUsernamePassword credentials = server.getCredentials("BANCO_PASTOR").get(0);
boolean updateCredential(String application, String username, String password): allows you to update a password associated with a specific username and application previously registered in the console. It returns true
if the update is possible and false
if not.
This method is useful when a password is about to expire. In that situation, the robotic task can update the password, so that in future executions it can still log in.
1
2
3
if (mustUpdate) {
server.updateCredential("BANCO_PASTOR", "admin", "NEW_PASSWORD");
}
ROBOT_EVENT
in the console.null
.numberOfOlderExecutionsToGet
indicates the number of previous executions that will be retrieved: 0
means that only the information about the current execution will be retrieved, -1
means that the data about all the existing executions will be retrieved. This option should be avoided or used carefully, since it may impact console performance.EXECUTION_NEEDLESS
event, generated at the same time. Note that this type of execution will always be removed, so only the event remains for later queries.Another important functionality provided by this interface is accessing methods that can be used to encrypt or decrypt sensitive information before displaying it in places such as the execution log or the functional data of the queue items.
The IJidokaGlobalContext interface exposes the methods needed to save and retrieve data from the global context, which is a persistent key-value type storage. You can persist objects of any type only if they are serializable.
The global context is shared between every robotic task in the console, regardless of the permissions they have assigned.
<T>
readAndWrite(String id, Function<T, T> function): This is an atomic operation that reads the object ID passed as the first parameter and writes it after applying the function specified in the lambda expression (passed in the second parameter). The method returns an aggregation with the original object and the modified one. If the specified identifier refers to an object that is locked by another execution, this method will wait until the lock ends to return the requested information.JidokaGlobalContextBlockedByAnotherExecutionException
.true
only if the object is actually removed.The IExecution interface provides methods to get the values from executions, as well as capabilities such as programmatically launching robotic tasks. It is necessary to differentiate between a current execution and previous executions, for which you must use the ICurrentExecutionDetail and IPreviousExecutionDetail interfaces.
Use the server.getExection(int executionNumber) method to request a specific execution. In addition to the ICurrentExecutionDetail and IPreviousExecutionDetail interfaces, there are a few other methods that allow you to retrieve information from the robotic task.
The ICurrentExecutionDetail interface gets information about the current execution. The following code shows how to get an instance of this interface for a specific robotic task:
1
ICurrentExecutionDetail currentExecution = server.getExecution(0).getCurrentExecution();
A value of 0
is passed in to the getExecution(int n) method to obtain an object of IExecution. Then, we will be able to get a reference to ICurrentExecutionDetail. This interface provides information through the following methods:
The IPreviousExecutionDetail interface gets information about a finished execution. For this reason, it will return more information because data about the execution results is already known.
To get information about previous executions, use the getExecution(int n) method of IJidokaContext, indicating the number of finished executions to retrieve. As in the previous example, the code to get this information is as follows:
1
List<IPreviousExecutionDetail> previousExecutions = server.getExecution(10).getPreviousExecutions();
This method could potentially get information about several executions, so the getPreviousExecutions() method returns a list of objects of type IPreviousExecutionDetail. If you pass 10
as the parameter value, the method obtains the last 10 executions of the robot, if that many exist, from the most recent to the oldest.
IPreviousExecutionDetail contains all the same methods as the ICurrentExecutionDetail interface, plus:
The IRobotExecutionDetail interface shows information about executions currently in progress or queued waiting for a robot.
To obtain this information, invoke the robotExecutions() method of the execution obtained through the method getExecution(int n):
1
List<IRobotExecutionDetail> actualExecutions = server.getExecution(0).robotExecutions();
IRobotExecutionDetail contains all the same methods as the IPreviousExecutionDetail interface, plus:
To programmatically launch a robotic task, the interface exposes an overloaded method launchRobot(). Depending on the parameters passed, it allows you to launch the robot, launch it on the same robot as the current robot, and launch the robotic task with parameters. These parameters can be used to pass values for the robotic task's parameters.
robotName
.mustBeHere
is true
or false
.null
, the robotic task will run on the first available robot. You can specify execution parameters.All these methods can be emulated with the next one:
With the LaunchOptions support class, it is possible to define execution options, except for the robotic task and robot. The options include:
Remember that an execution is subject to the availability of a free robot. Therefore, the execution will queue awaiting a robot, just as it does when manually launched from the console.
The following methods are included in the IExecution interface:
null
is passed, the method gets the information for the current robot. This method is useful to know what robots are compatible with a robotic task.The Log interface provides methods that allow you to send log information to the server. This information will be shown in the execution log and it can be of type trace, debug, info, warn, error, or fatal. The log messages are sent through the following methods, in order of increasing severity:
Messages should be sent to the server depending on the message they are trying to convey. For example, the debug messages help with development, while the info messages help monitor and understand the log.
The IJidokaContext, IJidokaStatistics, and Log interfaces provide great versatility to the interface IJidokaServer when it comes to communicating with the server, resuming a robotic task at a certain task (avoiding starting an interrupted task over again), and other actions that help monitoring the robotic task's operation (such as sending screenshots of steps that may be of interest).
The IJidokaRobot, IClient, and IWindows interfaces represent the robotic task.
IJidokaRobot offers methods that allow you to use screen elements such as the keyboard and the mouse. Its methods also help adapt the working speed to a human's working pace when necessary through actions such as pausing the execution.
IWindows is suitable for robotic tasks running on Windows operating systems and inherits all its functions and capabilities from IJidokaRobot. It offers Windows low-level handling and control support. A robotic task running on Windows systems could use:
1
IWindows windows = IWindows.getInstance(this);
This accesses methods for Windows handling and control offered by IWindows. However, if you need to ensure that your automation can be executed on any operating system, use the IClient interface. This interface also inherits from IJidokaRobot, but the available methods are valid on any operating system:
1
IClient client = IClient.getInstance(this);
Additionally, you can use an ILinux instance. Appian RPA instantiates the following interfaces:
The next section defines the methods provided by IJidokaRobot, which are inherited by IWindows and ILinux. Additional methods in IWindows are described later.
IJidokaRobot provides other methods to help us in other common tasks.
It's very common to copy or paste information using a computer's clipboard. Appian RPA can do these tasks with two methods: clipboardSet(String value) to save the value to the clipboard, and clipboardGet() to return a String with the information contained in the clipboard.
The repeat() method allows you to perform an action repeatedly. It receives three arguments: a lambda expression with the action, the number of repetitions, and the pause duration between iterations. An example is shown below:
1
client.repeat(IWorker action, int repetitions, long millis)
This method can be helpful to perform repeated keystrokes and then pause for each iteration. For example:
1
client.repeat(() -> keyboard.tab(), 3, 1000);
This will press the tab key three times, waiting one second between each keystroke. IJidokaRobot has more methods, such as antiScreenSaver() to perform a slight mouse movement to prevent the screensaver to start or stop it if it is active. Along with this method there are activateScreenSaver() and deactivateAntiScreenSaver(), used to activate and deactivate this capability.
Other methods, like waitCondition, are detailed in a later section.
The IWindows interface inherits many methods from IJidokaRobot and provides a lot of Windows-specific functionalities. If your robotic task is going to run on a Windows operating system and operate applications which run on Windows, use this interface. The UIAutomation module is preferred for handling Windows over this interface.
The example below shows how to get an instance of IWindows on Windows environments:
1
IWindows windows = IWindows.getInstance(this);
IWindows uses JNA (Java Native Access) for easier windows handling.
IWindows contains the following methods:
boolean activateWindow(String regexp): activates the window specified by regular expression to find the window by its title. For example, when you open the Notepad, the application window title is "Untitled - Notepad". Use this method:
1
robot.activateWindow("Untitled - No.*");
WindowInfo getWindow(String regexp): gets the object WindowInfo, from a regular expression. WindowInfo implements IActionable, so you can use the IActionable methods in the window. For example, this code sample shows how to click with the left mouse button on the coordinates (100, 50) relative to the window that we have just obtained with the method getWindow("Untitled – N.*"):
1
robot.getWindow("Untitled - No.*").mouseLeftClick(new Point(100,50));
Conversely, this example show how to click with the left mouse button on the coordinates (100, 50) of the screen, which most likely won't match the previous coordinate:
1
robot.mouseLeftClick(new Point(100,50));
com.sun.jna.platform.win32.WinDef.RECT
) from the window specified by hWnd.com.sun.jna.platform.win32.WinDef.WINDOWINFO
) from the
window specified by hWnd.java.awt.Rectangle
) for the window, perform mouse operations on the window, and obtain and modify the object WINDOWINFO (com.sun.jna.platform.win32.WinUser.WINDOWINFO
).The following methods are related to Windows controls. Although, as mentioned above, we recommend using the UIAutomation module:
The following methods are related to the mouse and the cursor:
com.sun.jna.Structure
.true
if the current pointer type is the one specified by cursorType. This is useful to know if the mouse pointer has changed after moving it to a specific position.Ctrl + C
. It copies the current selection to the clipboard. If it can't access the clipboard or the selection is empty, it will return null
.null
under the same conditions as the previous method.Next, let's have a look at the process-related methods:
.exe
extension.Other methods related to the IWindows interface are:
java.awt.event.KeyEvent
. It only takes effect when scanCode is 0.For example, to press the intro key in the numeric keypad, use the following code snippet:
1
2
windows.sendRawKey(0, true, 0xe01c);
windows.sendRawKey(0, false, 0xe01c);
alt
, shift
, and control
if any of them are pressed.alt
, shift
, or control
keys are pressed. The parameter may correspond with the values of the Java class java.awt.event.KeyEvent
, though this Java class does not cover all the possibilities and you can also indicate some other values.true
, activates caps lock. If false
, deactivates caps lock.For more methods provided by this interface, consult the Javadocs in the Appian RPA Console. In the console, click Help > Javadoc in the left menu.
To emulate keyboard use, Appian RPA uses scan codes, also known as the "physical codes" associated with the keys.
Due to the diversity of keyboard distributions for different languages on the operating system, the scan codes may not represent the key that you want to press. For example, the character "?". The key assigned to the question mark is different for English and Spanish. If the operating system is working in English, the received character would be _
, which is in the same keyboard position.
To account for this, you can change the keyboard distribution to use, so that the emulation is done through the key combination ALT+ number. This option is available by using the method setVariant of IJidokaRobot.
An exception is thrown when a character isn't supported, regardless of the keyboard distribution you are using.
IJidokaRobot provides the following methods for keyboard usage:
As you can see, the method typeText is overloaded. It accepts both text (String text) and IKeyboardSequence (IKeyboardSequence sequence). The IKeyboardSequence and IKeyboard interfaces are described below.
Every time you press Ctrl + C to copy, Ctrl + V to paste or Alt + F4 to close a window in most applications, you are executing keystroke sequences. The IKeyboardSequence interface lets you enter such sequences using the robotic task. You can also accomplish other actions such as entering text or caps locking. You can chain calls to this interface's methods so that several methods make up a full key sequence or text typing.
To get an instance of IKeyboardSequence, enter the following:
1
robot.getKeyboardSequence();
Once you obtain the instance, add the necessary methods to build the key sequence. Use the type() method to enter text and keystrokes. Let's see some examples.
In this example, the goal is to enter some text or one keystroke by using IKeyboardSequence. This example uses the type(String text) method of IKeyboardSequence. The following code applies the sequence just as a human would:
1
robot.getKeyboardSequence().type("Example text).apply();
Using IKeyboardSequence to enter text is recommended if it is part of a keystroke sequence or other key combination. If you only want to enter text, it is more appropriate to use the typeText() method of IJidokaRobot:
1
robot.typeText("Example text);
The type() method is an emulation of a keystroke. This code will engage the "r" key, for example:
1
robot.getKeyboardSequence().type("r").apply();
One of the overloaded versions of the type method allows you to specify how many times you want to engage the key:
type(String text, int repetition):
For example, this code will engage the "r" key three times.
1
robot.getKeyboardSequence().type("r", 3).apply();
IKeyboardSequence also provides a number of methods that simplify certain keystrokes.
The press(int key) and release (int key) methods respectively press and release the specified key using the key code. When using the press method, the key will remain pressed until it is released with the release() method. The integer representing the key in these methods must match the values provided by the Java class java.awt.event.KeyEvent
.
Some frequently used combinations, such as pressing Alt + key or Ctrl + key, are made easier with specific methods. For example, you could use pressAlt() and releaseAlt(), which respectively press and release the Alt key. The following example shows the sequence with Alt + E:
1
robot.getKeyboardSequence().pressAlt().type("e").releaseAlt().apply();
The alternative is:
1
robot.typeText(robot.getKeyboardSequence().pressAlt().type("e").releaseAlt());
There are additional methods for specific actions, such as pressShift and releaseShift, or pressControl and releaseControl. Remember that it is important to release the key that have been previously pressed.
There are also some methods to press and release keys in the same action, such as typeReturn(), typeTab(), typeControl(), and typeEscape(). There is even a method that emulates the sequence Alt + Fx: typeAltF(int numberF). The following example shows the Alt + F2 key sequence:
1
robot.getKeyboardSequence().typeAltF(2).apply();
Additionally, these methods are overloaded to specify how many keystrokes you want. This way, you can easily move throughout a form, for example, by using the method typeTab( int repetitions). Other methods resolve some common tasks. For example, the following line is equivalent to "select all".
1
robot.getKeyboardSequence().selectAll().apply();
There are also methods to emulate other keystrokes such as Home, End, Page Up, Page Down, and the up, down, left and right arrows. The following code sample emulates a keystroke of the up arrow key and two keystrokes of the right arrow key. The second line presses the Page Down key.
1
2
robot.getKeyboardSequence().up().right(2).apply();
robot.getKeyboardSequence().pageDown().apply();
Many of these methods are overloaded and accept an integer parameter to indicate the number of keystrokes needed for the specified key.
Sometimes you may need the robotic task to pause within a sequence. To make IKeyboardSequence do so, use the pause method. The following code emulates a keystroke on the space bar, followed by a pause, a keystroke on the E key, and the Return key:
1
robot.getKeyboardSequence().typeSpace().pause().type("e").typeReturn().apply();
The IKeyboardSequence interface uses key scan codes to interact with the less common keys or those which are not represented by a single character. For example, the keys located on the numeric pad can't be represented like their counterparts on the conventional keyboard. The following methods work properly only on Windows systems:
For example, the following code shows how to press the intro key on the numeric pad:
1
windows.getKeyboardSequence().typeScanCode(0x0e01c).apply();
IKeyboard is very similar to IKeyboardSequence but it simplifies even more the emulation of the most common keystrokes. The main difference is that each method emulates the keystroke directly. To get an instance of IKeyboard, use:
1
robot.keyboard();
Some methods are similar to their counterparts in IKeyboardSequence. For example, type (String text) or type (String text, int repetition), and some others simplify the simultaneous keystrokes, such as alt (String letter), altF (int numberF) or control (String letter).
The pause method in IKeyboard is overloaded and allows you to set the pause duration in milliseconds: pause (long millis).
IKeyboard aims to simplify some keyboard functions already provided by IKeyboardSequence.
For example, with IKeyboardSequence you could emulate the key sequence Ctrl + E using this code:
1
robot.getKeyboardSequence().pressControl().type("e").releaseControl();
But with IKeyboard, you can simplify it to:
1
robot.keyboard().control("e");
The IKeyboard interface uses the same methods and scan codes as IKeyboardSequence to let you work with the keys at a low level. For example, the following code shows how to press the intro key on the numeric pad:
1
windows.getKeyboard().scanCode(0xe01c);
IJidokaRobot inherits methods from IActionable, which is an interface that provides methods for mouse handling as well as other capabilities and access to objects that we will need to manage the mouse.
For example, the getRectangle() method returns the Rectangle (java.awt.Rectangle) of the screen. A Rectangle object represents a rectangular area which is determined by x/y coordinates representing its upper left-hand corner, its width and height, as well as many other properties and useful methods.
The mouseMove() and mouseMove(Point coordinates) methods move the cursor to the object's upper left-hand corner or to the position specified by coordinates. The coordinates are relative to the object's upper left-hand corner. The mouseMoveToCenter() method moves the cursor to the object's center.
There are several methods for clicking an object:
There are also methods to emulate double-clicking an object: mouseDoubleLeftClick() and mouseDoubleLeftClick(Point coordinates).
IJidokaRobot also contains a pause method to adapt the robot's working speed to the applications it is using. You can specify the pause duration with pause(long millis).
The defaultPause(long millis) method sets the pause's default duration in milliseconds. The robotic task will pause for this time each time it calls the pause() method without a specific duration parameter.
You can also use the characterPause(long millis) method, which allows you to set a pause between characters while typing a text string to mimic how long it might take a human to type the words.
Appian RPA robotic tasks must adapt their working pace to the applications they're using. This means that it should consider that applications have been designed to be used by a human. You can use methods to set the robotic task's pace so it doesn't overwhelm or work faster than the applications it interacts with.
This waiting time will depend on a number of factors such as the performance of the machine on which the robotic task is running (processor, RAM, virtual environment), the execution speed of the application itself (it must previously load certain modules or files), or the machine overload at that time.
The pause (pause() and pause(long millis)) methods pause a robotic task execution for a fixed amount of time. However, there may be occasions when the developer needs a way to optimize this waiting time. The waitCondition method and the IWaitFor interface address this issue.
The waitCondition method makes the robotic task pause and wait for a certain condition to resume. The method's signatures are:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<T> boolean waitCondition(int numberOfAttempts,
long pauseInMillisBetweenAttempts,
String logName,
T context,
ICondition<T> condition)
<T> boolean waitCondition(int numberOfAttempts,
long pauseInMillisBetweenAttempts,
String logName,
T context,
boolean throwExceptionIfNotSatisfied,
ICondition<T> condition)
throws JidokaUnsatisfiedConditionException
<T> boolean waitCondition(Date untilDate,
long pauseInMillisBetweenAttempts,
String logName,
T context,
ICondition<T> condition);
<T> boolean waitCondition(Date untilDate,
long pauseInMillisBetweenAttempts,
String logName,
T context,
boolean throwExceptionIfNotSatisfied,
ICondition<T> condition)
throws JidokaUnsatisfiedConditionException;
The two first methods are similar, except that the second one throws an exception of type JidokaUnsatisfiedConditionException if the condition has not been met after a specified number of attempts. The third and fourth methods are also similar, except for the exception. The main difference between both versions is that the two first methods base the wait on a specified number of attempts (int numberOfAttempts), whereas the two last methods use a specific moment in time (Date untilDate):
Additional parameters are:
Here is an example of a robotic task that wants to open WordPad to work with it:
1
2
3
4
5
6
7
robot.waitCondition(10,
3000,
"WordPad opened",
null,
true,
(i, context) ->
robot.getWindow("Document - WordPad.*") != null);
The robotic task will wait until WordPad is open. In this case, it will make 10 checks (or attempts), waiting for 3 seconds (3000 milliseconds) after each attempt. The condition is identified in the log by the text "WordPad opened". An exception of type JidokaUnsatisfiedConditionException should be thrown if the condition is not met after all attempts have been made. Additionally, if the condition is not met after all the attempts, a screenshot will be sent to the execution log to check why the condition has not been met.
The condition is represented by the Lambda Expression. It refers to the following method, found in the ICondition<\T> functional interface:
1
boolean satisfied(int numberOfAttempt, T context);
A functional interface is any interface with only one abstract method and may be annotated with @FunctionalInterface.
Lambda expressions allow you to simplify the code and perform in-line implementations of functional interfaces. In the example, the code snippet is passed as a parameter. A lambda expression encapsulates a specific functionality.
Since the interface ICondition has only one abstract method, the example passes a lambda expression as a parameter to the waitCondition method:
1
(i,context) -> robot.getWindow("Document - WordPad.*") != null
The condition is that robot.getWindow("Document - WordPad.")* is not null, which means that WordPad has started properly and is available to interact with it.
The getWindow(String regexp) method belongs to the IWindows interface. It returns a WindowInfo object corresponding with the window whose title matches the regular expression in the regexp parameter. This example uses the pattern Document - WordPad.*
.
The IWaitFor interface simplifies managing the robotic task's waits. This interface has pre-implemented conditions to search for images, files, or windows. There is no need to implement the satisfied method described in the previous section. Access it through IJidokaRobot using the methods:
Both methods are similar and return an instance of the interface IWaitFor.
Let's analyze this interface:
false
.0f
and 1f
. Typically, tolerance values of .05f
give good results.Other versions of these methods differ only on the type of object used as a pattern for the image to be searched. For example, you can use:
An InputStream object:
1
2
3
4
5
6
7
8
9
10
11
boolean image(InputStream inputStream) throws IOException;
boolean image(InputStream inputStream, int timeOutSeconds)
throws IOException;
boolean image(InputStream inputStream, float tolerance)
throws IOException;
boolean image(InputStream inputStream, float tolerance,
int timeOutSeconds) throws IOException;
A URL object:
1
2
3
4
5
6
7
8
9
boolean image(URL url) throws IOException;
boolean image(URL url, int timeOutSeconds) throws IOException;
boolean image(URL url, float tolerance) throws IOException;
boolean image(URL url, float tolerance,
int timeOutSeconds) throws IOException;
An ImageInputStream (javax.imageio.stream.ImageInputStream) object:
1
2
3
4
5
6
7
8
9
10
11
boolean image(ImageInputStream imageInputStream) throws IOException;
boolean image(ImageInputStream imageInputStream, int timeOutSeconds)
throws IOException;
boolean image(ImageInputStream imageInputStream, float tolerance)
throws IOException;
boolean image(ImageInputStream imageInputStream, float tolerance,
int timeOutSeconds) throws IOException;
There are also several methods to wait for an image to appear on screen, among a set of candidate images. When one of them is detected, the method will return that image. Once an image has been found, its description will be sent to the execution log.
1
2
IImageResource image(IImageResource... images) throws IOException
IImageResource image(int timeOutSeconds, IImageResource... images) throws IOException
There are also methods that wait for a window to appear:
null
otherwise.The following methods allow you to wait for window to be activated:
null
.You can also wait for the mouse cursor to change appearance. The Appian RPA enum type ECursorType represents the possible kinds of cursors available. For further information please refer to LoadCursor function.
null
if no match was found after the default waiting timeout.With these methods you can wait for files to exist:
null
if no file has been found after the default timeout.In addition to the predefined conditions explained above, IWaitFor provides the wait() method. This method enables the evaluation of an IEasyCondition. IEasyCondition is a functional interface to build conditions that should be met, implementing the boolean satisfied() method in the same way as the interface ICondition.
You can specify the descriptive text for the execution log through the logName parameter. In the second version of the method, you can also set the timeout (timeOutSeconds), expressed in seconds, which is the maximum time the robotic task will wait for the condition to be met.
Suppose you want your robotic task to wait 20 seconds for a window named "New document" to be active. To get the active window's title, use getActiveWindowTitle() of the interface IWindows, which returns the active window's title.
1
2
3
4
IWaitFor wf = robot.getWaitFor(this);
wf.wait(20,
"Waiting until the window "New document" is in the foreground ",
()-> robot.getActiveWindowTitle().matches("New document"));
In this example, you have set:
20
"Waiting until the window \\"Title example\\" is in the foreground"
() -> robot.getActiveWindowTitle().matches("New document")
The condition (for which the robotic task will wait for 20 seconds) is the active window's title is or contains "New document". This example builds it using a lambda expression:
1
()-> robot.getActiveWindowTitle().matches("New document");
IWaitFor provides the following methods:
Lastly, you can combine IEasyCondition conditions using logical operators AND, OR, and NOT. To do so, the IWaitFor interface includes three methods whose parameters are one or more IEasyCondition conditions:
true
).true
).false
).To demonstrate how to use IEasyCondition, consider these three conditions:
int currentItem
stores the current item).Condition A will be met if the file "example.txt" exists, so you can use the fileExists(File file) method provided by IWaitFor:
1
wf.fileExists(new File("example.txt"));
Condition B will be met if a window with the title "example.txt – Notepad" is visible. Now, you'll need to create the IEasyCondition. Use the isWindowVisible(HWND hWnd) method of IWindows. This method accepts Windows handler as a parameter to identify a window's visibility. To get this handler, use the getWindow(String regexp) method. These methods and the interface IWindows are described in detail in IWindows methods.
Build the condition as a lambda expression, which only works in the proper context:
1
2
() -> robot.isWindowVisible(
robot.getWindow("example.txt.*").gethWnd())
Condition C is quite simpler. Assuming that there is an integer type variable that stores the current item: int currentItem
, IEasyCondition, as a lambda expression, will appear as:
1
() -> currentItem > 100
Now let's code several "nested" IEasyCondition examples:
Example 1: At least one of the three conditions, A, B, C must be met, that is, it's enough for us if any of them is met. The three conditions are passed as parameters to the or method (IEasyCondition… conditions):
1
2
3
4
5
6
7
wf.wait(
20, "At least one of A, B, or C",
wf.or(
wf.fileExists(new File("example.txt")),
() -> robot.isWindowVisible(robot.getWindow("example.txt.*").gethWnd()),
() -> currentItem > 100)
);
For clarity, this example puts each IEasyCondition in a different line.
Example 2: The three conditions A, B, and C must be met. In this case, we just need to use the and method:
1
2
3
4
5
6
7
8
wf.wait(20,
"At least one of A, B, or C",
wf.and(
wf.fileExists(new File("example.txt")),
() -> robot.isWindowVisible(
robot.getWindow("example.txt.*").gethWnd()),
() -> currentItem > 100)
);
You can create as many combinations as needed, of greater or lesser complexity. If you need more complex conditions, you can create your own classes to implement the IEasyCondition interface:
1
2
3
4
5
6
7
8
private class MyCondition implements IEasyCondition {
@Override
public boolean satisfied() {
// Implement condition here
return false;
}
}
And in use:
1
2
3
4
5
IWaitFor wf = robot.getWaitFor(this);
MyCondition cond = new MyCondition();
wf.wait(20, "My condition", cond);
The IMail interface allows you to send and receive mails. You can send mail from a host machine or from the Appian RPA server. You can also attach files to the mail or send the content in HTML format or plain text.
It is worth highlighting that sending mail from the server or the host machine only has an impact on which machine should have access to the mail server we are using from the robot. In other words, the only difference is the machine from which mail are sent.
To get an instance of the interface, use the following method:
1
IMail mail = IMail.getInstance(this);
The IMail interface has the following methods:
These methods use the MailSendOptions and MailReceiveOptions classes, which have the necessary information to connect with the mail server. These classes provide a fluid API that allows you to easily define all the necessary information.
The MailSendOptions class allows you to send mail in HTML format or plain text. It also allows you to specify whether you want to use a cached session to send the mail from the server. Here are some of its methods:
An example to send a mail with HTML content would be:
1
2
3
4
5
6
7
8
9
10
11
12
// Create mail options from parameters
mailSendOptions = new MailSendOptions();
mailSendOptions.toAddress(server.getWorkflowParameters().get("notificationTo"))
.fromAddress(server.getWorkflowParameters().get("notificationFrom"))
.subject(server.getWorkflowParameters().get("notificationSubject"))
.host(server.getWorkflowParameters().get("mailServerHost"))
.port(Integer.parseInt(server.getWorkflowParameters().get("mailServerPort")))
.username(server.getWorkflowParameters().get("mailServerUser"))
.password(server.getWorkflowParameters().get("mailServerPassword"));
mail.sendMail(mailOptions);
In the workflow, it is possible to call specific methods of the API to execute code. Currently, only Groovy scripts are supported.
When you add an action to a step in the workflow, you can select an option to execute code.
In the Modules menu, select either Execute code or Execute code with result in the Robot module area.
After you select a method, the right-side of the screen shows a few options:
Before giving an example of what code could be added, keep in mind that Appian RPA provides a context with several variables to be used, such us:
Before giving an example of what code could be added, keep in mind that Appian RPA provides a context with several variables to be used, such as:
Using these variables, you can invoke their attributes and methods. Additionally, it is possible to access to the robotic task parameters, which are available through the variable parameters, of type Map.
For example, you can send a log message of type info to the execution trace with the value of a robotic task attribute:
1
server.info(robot.negations[1])
In this case, the log would be written with the value of the position 1 of the negations attribute of the robot class.
To make this code work, the server and robot instances must exist in our robot, as well as an attribute in the robotic task called negations of type array.
Make sure that the apply() method has been called at the end of the sequence. Otherwise, the sequence will be created but won't be executed.
In a robotic task, the keyboard behaves in the same way that it does when a person uses it. Therefore, if the numerical or uppercase block is active, a sequence of keys may not behave as expected.
It could also happen that after the completion of a robotic task, a key remains pressed by accident, such as Alt or Control. The best way to ensure the desired behavior is to reset the special keys before launching any combination of keys. For that, in the IWindows robotic task object, use the method windows.resetModKeys();
Special characters may not be available on all machines.
For the robotic task to enter special characters, use scan codes, which are codes sent by the keyboard to the computer to indicate which key is pressed.
Scan codes are dependent on the selected language, so the same machine using the same scan code can produce different outputs depending on the selected language. For example, scan code 0x27 is ";" when language is English and "ñ" when language is Spanish. Refer to the constants defined in the KeyEvent class, to get the values of each Key.
In order to use the scan codes in your robotic task, use the following code snippet:
windows.getKeyboardSequence().typeScanCode(ENTER SCAN CODE HERE).apply();
Client Java Module