Developer Day

Alfa eCare — March 2026

© 2026 Alfa eCare

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

© 2026 Alfa eCare

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)
© 2026 Alfa eCare

How to structure your code

  • Maintainability
    • DRY / reuse
    • Code stewardship
    • Readability
    • Clear intent
© 2026 Alfa eCare

Don’t

Repeat

Yourself

(It’s a spectrum)

© 2026 Alfa eCare

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
© 2026 Alfa eCare

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);
© 2026 Alfa eCare

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);
© 2026 Alfa eCare

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);
© 2026 Alfa eCare

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],
]);
© 2026 Alfa eCare

Promoting functions to flow modules

  • 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
© 2026 Alfa eCare

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 };
© 2026 Alfa eCare

Readability / Intent

  • Well named flow modules, functions, variables
// Don't be lazy and do this!
function input(f, v) {
    var _f = Fields[f];
    // Loop over fields and input the text
    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
© 2026 Alfa eCare

Using Fields

  • Static vs dynamic Fields
  • Finding fields with .find / .findAll
  • Using the Field finder
© 2026 Alfa eCare
Static vs. dynamic Fields

Static Fields

    const myButtonField = Fields['Button'];

© 2026 Alfa eCare
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.
© 2026 Alfa eCare
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.
© 2026 Alfa eCare
Static vs. dynamic Fields

Dynamic Fields

    const myButtonField = new Field('**/path/to/button');
© 2026 Alfa eCare
Static vs. dynamic Fields

Dynamic Fields

Pros

  • Quick prototyping
    • Spin up the Debug.ger REPL or in a Flow to test some your logic.
        const buttonField = new Field('**/path/to/button')
        // I need focus or else I won't click!
        buttonField.focus();
        buttonField.click();
      
  • Flexible and parameterized
    • You can build paths at runtime using variables, e.g. new Field('**/Table/**/Row#' + rowIndex + '/Edit').
© 2026 Alfa eCare
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.
© 2026 Alfa eCare

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

© 2026 Alfa eCare
Finding Fields with find / findAll

.find()

// Find the first button in the UI with a predicate

// match with string
new Field("**/Panel").find("OK").click();       
// match with regex
new Field("**/Panel").find(/OK(AY)?/).click();
// match with custom predicate
new Field("**/Panel").find(function(field) {
    return field["type"] === "button" && field["name"] === "OKAY";
}).click();
© 2026 Alfa eCare
Finding Fields with find / findAll

.findAll()

Same with findAll

// Find ALL the buttons in the UI with a predicate

// match with string
new Field("**/Panel").findAll("OK").forEach(field => field.click());       
// match with regex
new Field("**/Panel").findAll(/OK(AY)?/).forEach(field => field.click());
// match with custom predicate
new Field("**/Panel").findAll(function(field) {
    return field["type"] === "button" && field["name"] === "OKAY";
}).forEach(field => field.click());
© 2026 Alfa eCare
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) {
    // I will return null if the label was not found
    return form.find({ byPath: '**/field/**/' + label + '<above>input' });
});

res.forEach(field => field.highlight());
© 2026 Alfa eCare
Field Finder

How to use the Field Finder

Start the field finder
Click the green crosshair button — it will automatically focus the target application
Step 1
Point and click
Hover over the desired element and click to select it
Step 2
Inspect generated paths
Cuesta suggests multiple path alternatives — some may be more robust than the default
Step 3
Test the field
Use the toolbar to inspect, read, click — or run inline code directly
Step 4
© 2026 Alfa eCare
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:
© 2026 Alfa eCare
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.
© 2026 Alfa eCare

Defensive programming

  1. Enter data
  2. Validate data
  3. If possible—check data that has been transformed
  • Consider making an alternative field definitions to read data for validation
© 2026 Alfa eCare
Defensive programming

© 2026 Alfa eCare
Defensive programming

© 2026 Alfa eCare

Interacting with the user

  • Dialogs
  • Notifications
  • Stickies
  • Notifications
  • Tips
  • Progress
© 2026 Alfa eCare

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.

© 2026 Alfa eCare

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");
© 2026 Alfa eCare

Compress module

Compress and extract files within automation flows.

Key capabilities

  • Compress one or multiple files
  • Create archives in different formats
  • Extract existing archives

Supported formats

  • zip, tar, tar.gz, brotli
© 2026 Alfa eCare

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");
© 2026 Alfa eCare

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);
© 2026 Alfa eCare

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");
© 2026 Alfa eCare

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"});
© 2026 Alfa eCare

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!"
});
© 2026 Alfa eCare

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;
© 2026 Alfa eCare
overview

Lodash

v4.17.10 - search for ‘lodash docs’

© 2026 Alfa eCare
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')
    });
}
© 2026 Alfa eCare
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');
© 2026 Alfa eCare
Lodash

Examples

Manipulate objects

var merged = _.merge({}, obj1, obj2, { id: 10});
var withoutId = _.omit(obj, 'id');
if (_.isArray(value)) {
    
} else if (_.isObject(value)) {
    
}
© 2026 Alfa eCare

Debugging and Logging with Analytics

  • Why is it useful?
  • How to use it?
  • If you want to dig deeper
© 2026 Alfa eCare
Debugging and Logging with Analytics

Why is it useful?

Lots of users
Lots of robots
Lots of executions
Lots of data!
Analytics Flow
Structure
Log
© 2026 Alfa eCare
Debugging and Logging with Analytics

How to use it?

  • Visualize
  • Dashboard
  • Discover ← This is the most important part
Analytics Flow
© 2026 Alfa eCare
Debugging and Logging with Analytics

Visualize

Analytics Flow
Analytics Flow
© 2026 Alfa eCare
Debugging and Logging with Analytics

Dashboard

A bundle of visualizations!

Analytics Flow
© 2026 Alfa eCare
Debugging and Logging with Analytics

Discover

  • Inspect all the Logs
  • Investigate
  • Filters
© 2026 Alfa eCare
Debugging and Logging with Analytics

If you want to dig deeper

© 2026 Alfa eCare

Unattended Automation

A separate discipline of automation with its own problems

  • Authentication (Secrets)
  • Full desktop environments (Desktops)
  • Flow activation via triggers (Cron/Mail/…)
© 2026 Alfa eCare
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.
© 2026 Alfa eCare
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.

© 2026 Alfa eCare
Unattended Automation

Secrets api

The Secrets module can be used to programmatically interact with and use stored secrets.

// Retrieve and decrypt a secret
var revealed = Secrets.reveal("MySecret");

// No one else may know this
Secrets.forget("MySecret");

// Store another secret
Secrets.keep("Another", { Password: "f00b4r" }, { type: Secrets.User });
© 2026 Alfa eCare
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.

© 2026 Alfa eCare

Cosmic module

General Functions

patientId

setPatient

checkPatient

getIdFromTitle

getIdFromTitleAlt

selectMenu

selectMenuKeys

minMaxWindow

selectItems

writeMessage

getPrinters

getItems

setSwitch

focusCosmicPage

© 2026 Alfa eCare
Cosmic module

© 2026 Alfa eCare
Cosmic module

Shared functions module (from Sussa migrations)

checkAvliden

selectPatient

checkPatient

checkSkydade

testForAllvarligtFel

changeProfile

selectDd

selectCleared

© 2026 Alfa eCare
Cosmic module

Note module Java

checkEncounter

createEncounter

nyVardkontakt (Cosmic 5.0)

© 2026 Alfa eCare
Cosmic module

Note module html

checkEncounter

checkEncounterCleaned2

signHtmlNote

© 2026 Alfa eCare
Cosmic module
  • Medication module
  • Vaccination module

© 2026 Alfa eCare
Cosmic module
  • htmlLib (html journal anteckning)
© 2026 Alfa eCare

🎉 New features 🎉

  • Group expressions
  • Job queues
© 2026 Alfa eCare

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.

© 2026 Alfa eCare

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
© 2026 Alfa eCare

Group expressions

(AKM_MALMO OR AKM_LUND) AND (DOKTORER OR SYSTRAR)

© 2026 Alfa eCare

Job queues

  • New work scheduling system
  • Unattended automation on steroids
  • Manage/monitor workloads across many Manatees
© 2026 Alfa eCare
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)
© 2026 Alfa eCare
Job queues

Key concepts

  • Queues (name)
  • Jobs (topic, priority, payload, …)
  • Job runs (status, result, …)
  • Job logs
  • Job queue alerts
  • Job queue triggers
© 2026 Alfa eCare
© 2026 Alfa eCare
© 2026 Alfa eCare
© 2026 Alfa eCare
Job queues

Flow api

Minimal job consumer example

var queue = JobQueue.byName('DischargePatient');

var job = queue.claimJob({ autoRelease: true });

var patientId = job.payload;

// ... discharge the patient

© 2026 Alfa eCare
Job queues

Flow api

Minimal job producer example

var queue = JobQueue.byName('DischargePatient');

function dischargeLater(patientId) {

  queue.addJob({ payload: patientId });

}

// ... find patients to discharge

© 2026 Alfa eCare

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?

Two more steps

This can take many forms

Obsolete comment wasn't deleted, indentation is a mess, variables are poorly named

Thomas presents this

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?

Thomas presents this

Thomas presents this

DEMO PATH: **/path/to/(text)^button^#2<below>(type)*label*

Freddy Presents this

Freddy Presents this

Freddy Presents this

Freddy Presents this

Peter presents this

Peter presents this

Peter presents this

Peter presents this

Peter presents this

Peter presents this

Peter presents this

Peter presents this

Peter presents this

los presents this

Cuesta auto-complete knows the lodash api and can explain the arguments. Just type '_.' to see the api

Thomas presents this

los presents this

Peter presents this

Peter presents this

Peter presents this

los presents this