Configuration
spart-desk.config.ts is one file at the project root with the defaults
every script and test shares. npx spart-desk init writes it.
import { defineConfig } from "@spartaficial/spart-desk";
export default defineConfig({
runtime: "local",
timeout: 10_000,
expect: { timeout: 15_000, toPass: { timeout: 30_000, intervals: [250, 500, 1_000] } },
intervals: [100, 250, 500, 1000],
diagnostics: { directory: "test-results/spart-desk" },
ocr: { languages: ["en-GB"] },
images: { directory: "tests/images" },
});
Who reads it
| Reader | What it takes from the file |
|---|---|
Desktop.create(options) | everything. Options given in code win; expect, resolution, ocr, images, grounding (experimental) and diagnostics merge one level deep, so Desktop.create({ expect: { timeout: 1_000 } }) keeps the file's expect.toPass. |
expectDesk.poll / expectDesk(callback).toPass | expect.timeout, intervals and expect.toPass as defaults when neither the call nor expectDesk.configure (its toPass, then its timeout/intervals) set them. A desktop's own, those given to Desktop.create({ expect }) included, reach them through expectDesk.configure({ ...desktop.config.expect, intervals: desktop.config.intervals }) |
spart-desk-mcp / spart-desk-cli hosts | as Desktop.create; the runtime stays what --runtime says (agentic-mcp.md) |
Discovery
Desktop.create({ configFile }): a path, orfalseto read nothing.SPART_DESK_CONFIG=<path>, relative to the working directory.- The first of
spart-desk.config.ts,spart-desk.config.mts,spart-desk.config.js,spart-desk.config.mjsin the working directory.
Nothing found means the built-in defaults. The file is an ES module loaded
with a plain import(): Node 24 strips the TypeScript types itself, so there
is no build step and no extra dependency. Only erasable syntax works in it (no
enum, namespace or constructor parameter properties). A file is evaluated
once per process, like any module.
The file is an ES module whatever type the project's package.json
declares. On its own, Node reads a .ts or .js file as CommonJS unless the
nearest package.json says "type": "module", so SPART-Desk imports
spart-desk.config.ts and .js through a module hook scoped to that one
file, which declares it an ES module before Node parses it (Node still strips
the types). import/export default therefore work in a
"type": "commonjs" package and in one without a type field, without
renaming the file or changing the package; .mts/.mjs are accepted too.
CommonJS idioms (require, module.exports, __dirname) are not available
in it: use import, export default and import.meta.dirname.
ConfigError says so when one is used.
Settings
| Setting | Default | Meaning |
|---|---|---|
runtime | "local" | "local", "sandbox" (experimental, runtime-windows-sandbox.md) or a DesktopRuntime instance |
timeout | 5000 | budget of auto-waiting actions, in milliseconds |
expect.timeout | timeout | budget of locator assertions, poll and toPass |
expect.toPass.timeout | expect.timeout | budget of toPass |
expect.toPass.intervals | intervals | pauses between toPass attempts |
expect.snapshots.directory | <test file>-snapshots beside each test | where toHaveScreenshot and toMatchTreeSnapshot keep their golden files; relative to the configuration file |
expect.snapshots.update | missing (none when CI is set) | which golden files are written: missing, all or none; SPART_DESK_UPDATE_SNAPSHOTS overrides it |
intervals | [100, 250, 500, 1000] | pauses between two observations of the UI while waiting (actions, waitFor, assertions, poll); the last entry repeats. Every auto-waiting call accepts its own intervals. |
resolution | { screen: { interval: 1000, upscale: 2, parkPointer: true } } | how often the screen is read while a locator waits, how small text is enlarged before it is read, and whether the mouse a click left resting on a window is taken off it before its picture is read (parkPointer). Which resolver answers a locator is the locator's own choice: the accessibility tree, unless a step names the screen (read(), getByOcr, getByImage, getByVision (experimental); resolution.md) |
ocr | the recognizer built into Windows | { enabled?, languages?, minimumConfidence?, provider?, paddle? }. languages are tags to read in, best first; paddle is what the reader named "paddle" is made with ({ model?, characters?, threads?, maxSide?, batch?, runtime? }); it reads PP-OCRv6's languages, English among them, whatever languages says. provider is a reader (e.g. createPaddleOcrProvider() from @spartaficial/spart-desk-ocr-paddle), a reader's name ("windows", or "paddle" with that package installed), or a list of either tried in order: provider: ["windows", "paddle"]. A reader handed in as an object is the caller's: dispose() leaves it running, so one reader serves several desktops, and the caller disposes it once; a reader made from a name, and the built-in one, go with the desktop. Anything else, such as a misspelt name, is a ConfigError naming the file when it loads (resolution.md) |
images | none | { directory?, threshold?, scale?, interval? } for getByImage; directory is relative to the configuration file, and interval (250 ms) is how often a picture is looked for while a locator waits. Studio's Pictures tool saves into it, as <app>/<screen>/<name>.png, with the typed index (index.mjs, index.d.mts) beside the pictures (studio.md); npx spart-desk images writes the index for pictures added by hand |
grounding (experimental) | none | { minimumConfidence? } in the file; provider in code (resolution.md) |
diagnostics | screenshot and UI tree on failure, in <tmpdir>/spart-desk; no trace file; no step pictures; no trace kept | directory, traceFile and traceDirectory are relative to the config file; trace: "on" | "retain-on-failure" keeps a trace of the run as one trace.zip in traceDirectory (default test-results/spart-desk), each step with its pictures, tree and waits, and puts trace-viewer.html, the page that opens them, at the top of traceDirectory; traceFile appends every trace entry to a JSON-lines file as it happens; steps: "on" | "on-failure" pictures the window after each step beside it, which spart-desk report puts in an HTML report; redact: true | { patterns } tokenises the data texts and paints over their places in everything written (the trace file, the failure tree and picture, the step pictures, the report), as a support bundle is redacted |
foreground | "activate" | what an action that needs real input does when its window is not in front and no way in the background is left: "activate" brings the window to the front, "require" fails with foreground_required (input-engine.md) |
An unknown setting or a default export that is not an object raises
ConfigError (its file names the file). Numeric ranges are checked
when the desktop is created (RangeError), as for options given in code.
The engine reads one environment variable for where it keeps files:
SPART_DESK_CACHE_DIR, the folder the Windows Sandbox runtime (experimental)
stages the guest's files in (%LOCALAPPDATA%\spart-desk by default,
runtime-windows-sandbox.md).
SPART_DESK_CONFIG names the configuration file (Discovery).
Precedence
- Engine: options given in code > configuration file > defaults.
--config <file>names the file for thespart-deskcommand;--no-configignores it. expectDesk: per-call options >expectDesk(subject, { … })>expectDesk.configure({ … })(itstoPassfirst, fortoPass) > the desktop'sexpect(locator assertions) or the configuration file (pollandtoPass, which take no locator; a desktop's own reach them throughexpectDesk.configure({ ...desktop.config.expect, intervals: desktop.config.intervals }))defaults.
npx spart-desk init
npx spart-desk init
npx spart-desk init --yes # every default, no questions
npx spart-desk init --js --runtime sandbox --tests e2e # the sandbox runtime is experimental
Run in a terminal it asks, in this order: TypeScript or JavaScript
(TypeScript), local or sandbox runtime (local; the sandbox runtime is
experimental, see runtime-windows-sandbox.md)
and where the example test goes (tests; none skips it). Nothing has to be installed: the Windows
backend ships inside the package. Without a terminal, or with --yes, the
defaults apply; --js, --runtime and --tests/--no-tests answer a
question ahead of time.
It writes spart-desk.config.ts (spart-desk.config.mjs with --js),
<tests>/example.test.ts for Vitest when the project depends on it and for
node:test otherwise, and adds test-results/ to .gitignore. A node:test
example is example.test.mts unless the package.json has
"type": "module", because node --test reads that file itself (the
configuration file has no such constraint, see Discovery).
Existing files are left alone unless --force.
Programmatic access
import { defineConfig, findConfigFile, loadConfig } from "@spartaficial/spart-desk";
const loaded = await loadConfig({ cwd: process.cwd() }); // { path, config } or undefined
loadConfig({ cwd?, file?, env? }) applies the discovery above and validates
the file; findConfigFile only locates it. SpartDeskConfig is the type of
the default export (DesktopOptions without configFile).