Skip to main content

An application's own automation interface

Some applications offer an object for scripts beside their windows: a COM automation interface, as many line-of-business applications register one. Through it a test arranges data and checks results through the application's own model, where the screen shows them poorly: the rows of a table, a document's body, a value deep in a form. app.automation() reaches that object through SPART-Desk's helper executable, by COM late binding, and walks it along a path of members. This page is for anyone whose application has such an interface.

import { Desktop, expectDesk } from "@spartaficial/spart-desk";

await using desktop = await Desktop.create();
const app = await desktop.launch({ executable: "C:/Apps/Orders/Orders.exe", args: ["C:\\Data\\orders.odb"] });

const file = app.automation({ document: "C:\\Data\\orders.odb" });
const orders = file.at('Tables("Orders")');
await orders.set('Rows(1).Cells("Customer").Value', "Acme Ltd");
expectDesk(await orders.get('Rows(1, 2).Values')).toEqual([
["Order 1042", 10.5],
["Order 1043", 21],
]);
await app.window().getByRole("button", { name: "Recalculate" }).click();

Sources​

SourceWhat it finds
{ progId: "Orders.Application" }The object the application registered in the Running Object Table under that ProgID, its application object. Found when the application registers it, within the call's timeout: an application registers it once its window is up.
{ document: "C:\\Data\\orders.odb" }The object holding a document the application has open, registered under the moniker of its full path, as such applications register each open document. A document that is not open is not opened: the call fails with automation_unavailable.

Only the application's own object is used. The process serving the object must be one of the application's processes (its own, or one showing its windows). An instance the user had open, or another application's, fails with not_application, so a test never writes into someone's own document.

Paths​

Members are joined by dots, each with its arguments in parentheses, as a macro language writes them: Documents(1).Tables("Orders").Rows(1, 3).Values.

  • Arguments are literals: numbers, strings in double quotes (\" and \\ inside), true, false, null. A path names what to reach; it does not compute.
  • Default members: a member that takes no arguments but is given some is got, and its default member is given them. Documents(1) is Documents, then its Item(1).
  • Each call walks the path again from the running object. at(path) is a place further along it, so a place stays valid however the application changes, and nothing is held between calls.

Calls​

CallWhat it doesAnswers
get(path, { timeout?, signal? })Gets the member the path ends atits value
set(path, value, { timeout?, signal? })Sets it—
call(path, args?, { timeout?, signal? })Calls the method the path ends at; an argument undefined is left out, as an optional onewhat it answered

Values (AutomationValue):

  • text, numbers, booleans and null;
  • dates as Date, in local time, as COM keeps them;
  • arrays: a block of values is rows of values, [["Order 1042", 10.5], ["Order 1043", 21]], whatever its lower bound;
  • an object answered as { kind: "object", type } (Table, Document). Walk to it with a longer path rather than passing it back.

Timeouts: 30 s by default (AUTOMATION_TIMEOUT_MS).

  • A get that runs out fails as timeout.
  • A set or a call that runs out fails as delivery_unknown, since it may have been done, and is not sent again.
  • A server that does not answer is let go at the deadline. The helper stops calling it after four calls held that way (not_responding), until it answers.
  • A server that rejects calls while busy (a modal dialog open in the application) is asked again until the timeout.

Records: each call is in the action log (automation.get, automation.set, automation.call) with its source and path, and what kind of value it read or set (text of 12 characters, 3 × 2 cells), never the values. A get is an observation, so it leaves what the screen showed valid and is not pictured as a step.

Errors​

BackendError codeMeaning
automation_unavailableNo running object under that ProgID, or none holding that document, within the timeout; or the ProgID is not registered for the user
not_applicationAnother process than the application's serves the object, or which one serves it cannot be told
automation_failedThe object refused the call: an unknown member, a wrong argument, an error of its own. The message keeps the server's words and the HRESULT
timeout, delivery_unknownA get, or a set or a call, that ran out of time
not_respondingFour calls are still held by a server that does not answer
not_supportedA backend without the helper (a runtime of one's own)

Where it runs​

  • Locally, and inside the Windows Sandbox guest (experimental) through its agent, as every helper request does.
  • Through the agent only when its operator allows it. Calling any member of any server is code execution, so the agent offers its desktop_automation tool only for the interfaces named with --allow-automation <progId|document> (repeatable; allowAutomation in its policy), and for no other (the agent's README).