Previous slide Next slide Toggle fullscreen Open presenter view
Developer Day
Alfa eCare — March 2026
Agenda
09:30 Coffee
10:00 Plan for the day (FL)
10:05 Introductions (all)
10:30 Automation Methodology and Architecture++ #1
12:00 Lunch
12:45 Robot inventory: Status on robots from all regions (Magnus)
14:00 Coffee break
14:15 Automation Methodology and Architecture++ #2
15:15 Coffee break
16:30 Automation Methodology and Architecture++ #3
Slides are available at http://slides.sirenia.io/sweden-2026
Cuesta at https://queues.dk.sirenia.cloud
Automation Methodology and Architecture
How to structure your code
Static vs. dynamic fields
Defensive programming: validating and double-checking application state
User interaction
Manatee API modules
Logging and debugging via analytics
Principles for unattended automation
Cosmic modules
Upcoming features (group expressions, job queues, MCP)
How to structure your code
Maintainability
DRY / reuse
Code stewardship
Readability
Clear intent
D on’t
R epeat
Y ourself
(It’s a spectrum)
Why dry?
Copy and paste duplicates code - and duplicates bugs
More code to write
More code to read
More code to test
More code to maintain
Example
Mouse .moveToField (Fields ['firstName' ]);
Fields ['firstName' ].click ();
Wait .forMilliseconds (150 );
Fields ['firstName' ].input (firstName);
Mouse .moveToField (Fields ['lastName' ]);
Fields ['lastName' ].click ();
Wait .forMilliseconds (170 );
Fields ['lastName' ].input (lastName);
Mouse .moveToField (Fields ['userName' ]);
Fields ['userName' ].click ();
Wait .forMilliseconds (200 );
Fields ['userName' ].input (userName);
More dry
function fillInField (field, value ) {
Mouse .moveToField (field);
field.click ();
Wait .forMilliseconds (200 );
field.input (value);
}
fillInField (Fields ['firstName' ], firstName);
fillInField (Fields ['lastName' ], lastName);
fillInField (Fields ['userName' ], userName);
More dry?
function fillInField (fieldName, value ) {
var field = Fields [fieldName];
Mouse .moveToField (field);
field.click ();
Wait .forMilliseconds (200 );
field.input (value);
}
fillInField ('firstName' , firstName);
fillInField ('lastName' , lastName);
fillInField ('userName' , userName);
Taking it too far
function fillInFields (fieldInputs ) {
for (var i = 0 ; fieldInputs && i < fieldInputs.length ; i++) {
var fieldName = fieldInputs[i][0 ];
var value = fieldInputs[i][1 ];
var field = Fields [fieldName];
Mouse .moveToField (field);
field.click ();
Wait .forMilliseconds (200 );
field.input (value);
}
}
fillInFields ([
['firstName' , firstName],
['lastName' , lastName],
['userName' , userName],
]);
Avoid duplication between flows
var inputFunctions = Flow .include ('input-functions' );
inputFunctions.fillInField ('firstName' , firstName);
inputFunctions.fillInField ('lastName' , lastName);
inputFunctions.fillInField ('userName' , userName);
Who maintains the module?
Procedure for changing the module
Check usage under flow settings
Safe changes in shared functions
Backwards compatible change
function formatDate (date ) {
return [date.getFullYear (), date.getMonth () + 1 , date.getDate ()].join ('-' );
}
exports = { formatDate : formatDate };
=>
function formatDate (date, includeTime ) {
var timeOfDay = includeTime ? ' ' + date.getHours () + ':' + date.getMinutes () : '' ;
return [date.getFullYear (), date.getMonth () + 1 , date.getDate ()].join ('-' ) + timeOfDay;
}
exports = { formatDate : formatDate };
Readability / Intent
Well named flow modules, functions, variables
function input (f, v ) {
var _f = Fields [f];
Mouse .moveToField (f);
_f.click ();
Wait .forMilliseconds (200 );
_f.input (v);
}
Get used to the auto-formatting and other agreed upon code formatting guidelines
Comments only where you expect a reader (eg future you) may misunderstand or overlook hidden complexity
Using Fields
Static vs dynamic Fields
Finding fields with .find / .findAll
Using the Field finder
Static vs. dynamic Fields
Static Fields
const myButtonField = Fields ['Button' ];
Static vs. dynamic Fields
Static Fields
Pros
Centralized overview
All fields for an application are listed in one place in Cuesta Application, making it easy to audit, rename, and manage them.
Reusable across flows
Define once, reference by name with Fields[‘Name’] in any flow for that application. No duplication.
Easier maintenance
If a UI element path changes, you update it in one place rather than searching through code.
Static vs. dynamic Fields
Static Fields
Cons
Less flexible
If the path needs to be constructed dynamically at runtime (e.g. clicking row N in a table, or a path that depends on a variable), a static field can’t do that.
Hard to prototype
You can’t quickly test a Field that requires extra logic. Every field needs to be created through Cuesta’s UI first.
Static vs. dynamic Fields
Dynamic Fields
const myButtonField = new Field ('**/path/to/button' );
Static vs. dynamic Fields
Dynamic Fields
Pros
Quick prototyping
Flexible and parameterized
You can build paths at runtime using variables, e.g. new Field('**/Table/**/Row#' + rowIndex + '/Edit').
Static vs. dynamic Fields
Dynamic Fields
Cons
Scattered across code
Fields live inside flow scripts, so there’s no single place to see all the fields an application uses.
Duplication risk
The same path might be written in multiple flows. If the UI changes, you need to find and fix every instance.
Finding Fields with .find / .findAll
What are they
Convenience methods for searching inside a field’s structure.
think querySelector / querySelectorAll but for Manatee fields.
find → returns the first match
findAll → returns an array of ALL matches
Finding Fields with find / findAll
.find()
new Field ("**/Panel" ).find ("OK" ).click ();
new Field ("**/Panel" ).find (/OK(AY)?/ ).click ();
new Field ("**/Panel" ).find (function (field ) {
return field["type" ] === "button" && field["name" ] === "OKAY" ;
}).click ();
Finding Fields with find / findAll
.findAll()
Same with findAll
new Field ("**/Panel" ).findAll ("OK" ).forEach (field => field.click ());
new Field ("**/Panel" ).findAll (/OK(AY)?/ ).forEach (field => field.click ());
new Field ("**/Panel" ).findAll (function (field ) {
return field["type" ] === "button" && field["name" ] === "OKAY" ;
}).forEach (field => field.click ());
Finding Fields with find / findAll
DEMO
What if I want to find all input fields with a certain labels?
const form = new Field ('**/ui form' );
const labels = ['Name' , 'Billing Address' , 'Card Number' , 'Card Type' , 'Country' ];
const res = _.map (labels, function (label ) {
return form.find ({ byPath : '**/field/**/' + label + '<above>input' });
});
res.forEach (field => field.highlight ());
Field Finder
How to use the Field Finder
Start the field finder Click the green crosshair button — it will automatically focus the target application
↓
Point and click Hover over the desired element and click to select it
↓
Inspect generated paths Cuesta suggests multiple path alternatives — some may be more robust than the default
↓
Test the field Use the toolbar to inspect, read, click — or run inline code directly
Field Finder
Neat tricks part 1
Right-click path editing
Don’t manually edit path strings — right-click on any segment in the path editor to get a visual menu for editing. There are three editors:
Field Finder
Neat tricks part 2
Ctrl to pause
Hold Ctrl during field finding to freeze it. Navigate freely, open menus, switch tabs — then release to resume. Useful for elements hidden behind interactions.
Test with Highlight
Always click the Highlight button after defining a field. It highlights the matched element in the live app — if the wrong element lights up, your path needs tweaking.
Defensive programming
Enter data
Validate data
If possible—check data that has been transformed
Consider making an alternative field definitions to read data for validation
Defensive programming
Defensive programming
Interacting with the user
Dialogs
Notifications
Stickies
Notifications
Tips
Progress
Plugin modules
Built-in modules
Flow-modules
Plugin modules
const PdfBuilder = Module .load ('PdfBuilder' , { version : "v2.2.0" } );
Examples of modules you have to “load”:
Audio, Compress, Image, Network, Ocr, OpenAI, Outlook, Pdf, PdfBuilder, Screens
Use Module.list() to see all the modules, and all versions that are available.
Audio module
Play sounds and synthesize speech from automation flows.
Key capabilities
Speak text using built-in speech synthesis
Play .wav audio files on the local machine
Generate speech in multiple languages
Select voices and audio output options
var audio = Module .load ("Audio" );
audio.say ("Automation completed successfully" );
Compress module
Compress and extract files within automation flows.
Key capabilities
Compress one or multiple files
Create archives in different formats
Extract existing archives
PdfBuilder module
Create and manipulate PDF documents in automation flows.
Key capabilities
Generate PDF documents from HTML
Fill out PDF forms programmatically
Extract form fields from existing PDFs
Flatten PDFs to make them non-editable
var pdfBuilder = Module .load ("PdfBuilder" );
var pdf = pdfBuilder.create ("<h1>Hello World</h1>" );
pdf.saveAs ("invoice.pdf" );
Image module
Process and manipulate images within automation flows.
Key capabilities
Load and save image files
Resize and crop images
Convert between image formats
Apply basic transformations
var imageModule = Module .load ("Image" );
var croppedImage = imageModule.crop (imageBytes, 10 , 10 , 100 , 100 );
Network module
Interact with external systems and services over HTTP.
Key capabilities
Send HTTP requests (GET, POST, etc.)
Upload and download files
Handle request headers and parameters
Parse responses from APIs
var network = Module .load ("Network" , { version : "v1.0.0" });
var result = network.ping ("sirenia.eu" );
OCR module
Extract text from images and scanned documents.
Key capabilities
Recognize text from images
Process screenshots and scanned files
Support multiple languages
Return recognized text for further processing
var ocr = Module .load ("Ocr" , {version : "vX.Y.Z" });
var result = ocr.readPdf ("/Users/robot/Desktop/test.pdf" , {lang : "Danish" });
Outlook module
Interact with Microsoft Outlook from automation flows.
Key capabilities
Read emails from Outlook folders
Send new emails
Access attachments
Manage folders and messages
const outlook = Module .load ("Outlook" , {version : "2.0.4" });
outlook.send ({
to : "john@doe.org" ,
subject : "Hello" ,
body : "Hello John!"
});
Screens module
Interact with the computer screen and capture screenshots.
Key capabilities
Capture screenshots
Capture specific screen regions
Work with multiple displays
Provide images for OCR or Image processing
var screens = Module .load ("Screens" , { version : "v1.0.0" });
var primaryScreen = screens.primary ;
var bounds = primaryScreen.bounds ;
Lodash
v4.17.10 - search for ‘lodash docs’
Lodash
Examples
Text formatting by template
var dateTemplate = _.template ('<%= year %>-<%= month %>-<%= day %>' );
function formatDate (date ) {
return dateTemplate ({
year : _.padStart (date.getFullYear () , 2 , '0' ),
month : _.padStart (date.getMonth () + 1 , 2 , '0' ),
day : _.padStart (date.getDate () , 2 , '0' )
});
}
Lodash
Examples
Safely picking data out of eg inspect data
var panelData = panelField.inspect ();
var selectedText = _.get (elementData, 'children[0].children[3].selectedText' );
if (!selectedText) throw Error ('No selected text' );
Lodash
Examples
Manipulate objects
var merged = _.merge ({}, obj1, obj2, { id : 10 });
var withoutId = _.omit (obj, 'id' );
if (_.isArray (value)) {
} else if (_.isObject (value)) {
}
Debugging and Logging with Analytics
Why is it useful?
How to use it?
If you want to dig deeper
Debugging and Logging with Analytics
Why is it useful?
Lots of users
Lots of robots
Lots of executions
→
Lots of data!
→
Debugging and Logging with Analytics
How to use it?
Visualize
Dashboard
Discover ← This is the most important part
Debugging and Logging with Analytics
Visualize
↓
Debugging and Logging with Analytics
Dashboard
A bundle of visualizations!
Debugging and Logging with Analytics
Discover
Inspect all the Logs
Investigate
Filters
Debugging and Logging with Analytics
If you want to dig deeper
Kibana Guide
Training-how-to-series
Unattended Automation
A separate discipline of automation with its own problems
Authentication (Secrets)
Full desktop environments (Desktops)
Flow activation via triggers (Cron/Mail/…)
Unattended Automation
Secrets
Securely store and retrieve secrets in flows
Four different types of encryption methods:
Global. All Manatee instances can decrypt the secret.
Machine. Manatee instances on the specified machine can decrypt the secret.
User. Only a specific user can decrypt it.
Password. Anyone with the password can decrypt it.
Unattended Automation
Secrets
Valid date can be set to match e.g. password expiration.
Secrets can be restricted to certain groups and flows to prevent leaks.
Usage reporting.
Unattended Automation
Secrets api
The Secrets module can be used to programmatically interact with and use stored secrets.
var revealed = Secrets .reveal ("MySecret" );
Secrets .forget ("MySecret" );
Secrets .keep ("Another" , { Password : "f00b4r" }, { type : Secrets .User });
Unattended Automation
Remote Desktops
Most automation requires active Windows desktop environment
Unattended servers don’t have this out of the box
Pistia keeps your Windows login sessions alive!
Configure in Cuesta under the desktops page.
Cosmic module
General Functions
patientId
setPatient
checkPatient
getIdFromTitle
getIdFromTitleAlt
selectMenu
selectMenuKeys
minMaxWindow
selectItems
writeMessage
getPrinters
getItems
setSwitch
focusCosmicPage
Cosmic module
Cosmic module
Shared functions module (from Sussa migrations)
checkAvliden
selectPatient
checkPatient
checkSkydade
testForAllvarligtFel
changeProfile
selectDd
selectCleared
Cosmic module
Note module Java
checkEncounter
createEncounter
nyVardkontakt (Cosmic 5.0)
Cosmic module
Note module html
checkEncounter
checkEncounterCleaned2
signHtmlNote
Cosmic module
Medication module
Vaccination module
Cosmic module
htmlLib (html journal anteckning)
New features
Group expressions
Job queues
Group expressions
Groups are used by Manatee to determine what application and flow are available in a specific instance of Manatee.
To make it easier to control this behavior we are now introducing Group expressions .
With Group expressions you can set up rules like:
All of the conditions must be met (and condition)
One of the conditions must be met (or condition)
All of the conditions must not be met (not condition)
These conditions can be nested to create more complex rules.
Group expressions
Use cases
Define a group and use it in several flows. Change the group in one place
Define groups from different perspective and combine them
Create a group that is a combination of several groups
‘Subtract’ a few users from a large AD-group
Group expressions
(AKM_MALMO OR AKM_LUND) AND (DOKTORER OR SYSTRAR)
Job queues
New work scheduling system
Unattended automation on steroids
Manage/monitor workloads across many Manatees
Job queues
Why?
Handle errors from unattended automation
Data flowing to multiple destinations
Reach applications that don’t co-exist
Queue based ‘automation api’
Discharge patient
Add note X
Add note Y
Modular automation design (job producers, consumers)
Job queues
Key concepts
Queues (name)
Jobs (topic, priority, payload, …)
Job runs (status, result, …)
Job logs
Job queue alerts
Job queue triggers
Job queues
Flow api
Minimal job consumer example
var queue = JobQueue .byName ('DischargePatient' );
var job = queue.claimJob ({ autoRelease : true });
var patientId = job.payload ;
Job queues
Flow api
Minimal job producer example
var queue = JobQueue .byName ('DischargePatient' );
function dischargeLater (patientId ) {
queue.addJob ({ payload : patientId });
}
Automation Methodology and Architecture
Structure (module flows, cross-app flows, DRY, CS101) (LOS, FL) - 10 min
Lodash (LOS) - 5 min
Static (global) vs dynamic (local) Fields/Apps - (TT) - 10 min
.find/.findAll
How to use the static field finder?
Defensive programming (check and double check) (FL) - 10 min
Manatee API Modules (PL, ++?) - (if we run out of content) - >10 min
Less common modules
User interaction: Dialogs, Notifications, Stickies, Tips, Progress (FL or? (I have some cases that can be used as examples) - 20 min
Logging: analytics, simple debugging via analytics (TT) - 10 min
Quick rundown of analytics
Cosmic module (FL) - 15 min
Unattended principles (pistia, desktops, passwords) (los) - 10 min
Reuse slides
Upcoming features:
Job queues - an introduction (los) - 20 min
Simple consumer and producer example
Group expressions (PL) - 5 min
Context Management (maybe so small that it can be mentioned during the introduction - quick intro) - 10 min
AI assisted robot development??? (do we dare to mention this?) - 20 min
Input from the swedes
Group talk
The goal of these sessions is to cover topics of automation methodology. These topics fall roughly into the following categories:
los presents this
Don't go in-depth here - these are headlines
This slide is soaking wet!
With a screen full of this, it is hard to spot if there are other variations than the obvious ones
Much better! Can/should we do more?
Obsolete comment wasn't deleted, indentation is a mess, variables are poorly named
NOTE - You can find static fields in Global Search!
NOTE - Easy to create fallbacks of static fields - even optical fallbacks
NOTE Extra: For complex applications with many elements, the field list can grow large and hard to navigate
NOTE: Ever since we got the update to edit a dynamic field in Cuesta, there have not been that many advantages between the two. Remember to turn on: 'Show code links'
NOTE: Flexible and parameterized: Great for tables, lists, or any repeated UI structures
NOTE: If the user wants to find a dynamic field, they can use the Flows section in Cuesta to find the flow that uses it. Demostrate this?
DEMO PATH: **/path/to/(text)^button^#2<below>(type)*label*
los presents this
Cuesta auto-complete knows the lodash api and can explain the arguments. Just type '_.' to see the api