An automation is a TypeScript file in automations/ that default-exports a defineAutomation call. The framework
reads that object and runs it. This page opens process-orders, the example the repository ships. It reads the file from
the top.
#What the file imports
Three imports do the work.
import { defineAutomation, BusinessException, SystemException } from '../framework';
import type { RunContext, Transaction } from '../framework';
import { useTarget } from '../targets';
import { config } from '../config/tasksultan.config';
defineAutomation is an identity helper: it types the object you hand it and returns the same object. The two
exception classes are how a step says a unit of work failed. useTarget resolves a named Target from the object
repository. config supplies the base URL.
Notice what is missing. There is no Playwright import, no fs and no selector. Driving the screen and reading secrets
is the Robot's job, so the automation reaches those through the context the framework passes in.
#The definition object
The default export is a plain object. process-orders opens like this.
export default defineAutomation({
id: 'process-orders',
displayName: 'Process Orders',
description: 'Logs in, pulls pending orders from the demo queue, updates and submits each order.',
version: '0.1.0',
capabilities: ['web'],
url: config.demo.defaultUrl,
retry: { maxAttempts: 3, baseDelayMs: 500, maxDelayMs: 2500, backoffFactor: 2 },
});
id is the stable name. It becomes the folder under artifacts/, the manifest id in a package and the identifier the
CLI shows. The file stem must match it. displayName and description are for people. version is the author's own
handle, bumped when the source changes.
capabilities is the declared list of engines the automation needs. ['web'] says the Runner must provide a browser,
which it always does. A capability such as files or desktop behaves differently: the facade exists, but every call
refuses by name until the automation declares it and the Robot supplies a provider.
url is the target application's base. retry is the policy for system failures. There is also an optional dialogs
policy for native alert, confirm and prompt windows. When dialogs is absent, a dialog refuses the transaction by
name rather than letting Playwright dismiss it with no decision recorded.
#The lifecycle functions
The rest of the object is the REFramework lifecycle, written as ordinary methods. init runs once before any work and
is where this example resets demo data and signs in. getTransaction returns the next unit of work or null to end the
run. processTransaction does the work for one transaction. onBusinessException and onSystemException run after a
failure. onEnd runs once when the loop is over.
Inside processTransaction, process-orders calls four small named functions.
async processTransaction(ctx, tx) {
ctx.log.info(`Processing order ${tx.id}`, { tx: tx.id });
await openOrder(ctx, tx);
await updateOrder(ctx, tx);
await submitOrder(ctx, tx);
await verifySubmitted(ctx, tx);
}
Those functions live below the definition, at module scope. Keeping each step as a named top-level call is deliberate:
the Studio derives the workflow diagram from their order, so the diagram and the code cannot disagree. submitOrder
shows both failure lanes in one place. A 500 from the order service throws a SystemException, which the framework
retries. A 400 throws a BusinessException, which is never retried.
#Things that catch people out
- The file must default-export the definition. A named export is ignored and the Robot refuses the run by name.
- The
idand the file stem have to match, or the registry will not load the file. process-orderscontains no selectors at all. Its rows, buttons and banners are addressed by Target name. Every target is registered intargets/index.ts, so adding one means registering it there.initis also wrapped in the retry policy. A flaky login is retried, but a wrong credential is not: that refusal is non-retryable.