Running tests elevated
Some windows are out of reach of an ordinary test run. Windows keeps them from a process that runs at a lower integrity level. To automate them, the test runner itself has to run elevated ("as administrator"). This page explains when that is needed, how to do it for a local run, an editor, an agent and CI, and what it costs in safety.
SPART-Desk never elevates itself. It does not ask for elevation, does not start anything as administrator, and does not work around Windows' rule. It tells you when the rule is in the way.
When you need it
Windows gives every process an integrity level. A process you start normally runs at medium; one started "as administrator" runs at high. User Interface Privilege Isolation (UIPI) keeps a process from reading the UI Automation tree of a process above its own level, and from sending it input: clicks, keys and window messages are dropped without an error.
That affects:
| What | Why it is above an ordinary runner |
|---|---|
| An application started "as administrator", or one that asks for it itself (Registry Editor, most MMC consoles, many installers and setup tools) | It runs at high. |
Windows' credential prompt ("Windows Security", shown by CredentialUIBroker for CredUIPromptForWindowsCredentials) | It runs slightly above medium (0x200A), so a normal runner (0x2000) cannot reach it, even though the prompt belongs to your application's window. |
What SPART-Desk does with such a window when the runner is not elevated:
- Actions fail at once with
BackendErrorcodeelevation_required(not retryable). The message names the window or application and says to run the test runner elevated. The hint points to this page. The failure covers clicks, fills and key presses on controls in that window, and coordinate input inside its bounds: the mouse, a finger and a pen. Without it, Windows would drop the input and the test would wait until it timed out. - Snapshots and searches say why they come back empty.
app.snapshot()carries a note for each such window. A locator that finds nothing reports the same reason in its timeout message. - What the window shows can still be read off the screen.
getByOcrreads it, so a test can check that the prompt appeared and what it asks for, without elevation. Such a window does not paint itself into a picture for your runner, so the screen is copied where it is, and it must be in front: Windows may raise it behind the window in use. While another application's window covers a quarter of it or more, the read waits and then says which window lies over it. CallbringToFront()on the prompt first.
The window itself is still listed by app.windows(), with its processId.
The credential prompt
app.credentialPrompt() is the credential prompt your application raised,
as a scope of its own:
const main = app.window();
await main.getByRole("button", { name: "Sign in" }).click();
const prompt = app.credentialPrompt();
await prompt.window().getByAutomationId("EditField_0").fill(userName);
await prompt.window().getByAutomationId("PasswordField_1").fill(password);
await prompt.window().getByAutomationId("OkButton").click();
The fields and buttons carry AutomationIds that do not depend on the display
language: EditField_0 (user name), PasswordField_1 (password), OkButton,
CancelButton and CloseButton.
It is always your application's prompt. A prompt counts only when Windows itself ties it to the application:
- Runner not elevated: Windows owns the prompt to your application's window. On Windows 10 the prompt runs in your application's own process, at its integrity level, so its fields are in reach without elevation.
- Runner elevated: Windows shows the prompt without an owner, so the owner can't be used. Instead, SPART-Desk's helper checks that one of your application's threads is waiting on the prompt's process (Wait Chain Traversal), or that the prompt's process holds your application's process.
Two applications' prompts open at the same place on screen, and neither time nor place tells them apart, so neither is ever used. When a prompt is showing but Windows ties it to no application of yours, the scope stays empty and says so. It never takes another application's prompt, even when that is the only one showing.
Without elevation the scope still finds the prompt and reads its text off the
screen, but acting in it fails with elevation_required. Closing the scope
stops nothing.
Running elevated
Everything the test runner starts inherits its integrity level: SPART-Desk's helper and the application under test. Starting the runner elevated is therefore enough.
From a terminal
-
Open Windows Terminal (or PowerShell) with Run as administrator: right- click it in Start or on the taskbar. Its title bar then reads "Administrator:".
-
Go to the project and run the tests as usual:
cd C:\src\my-desktop-testsnpx vitest run test/elevated
With Sudo for Windows
Windows 11 24H2 and later include sudo. Turn it on under Settings >
System > Advanced > Enable sudo (on older builds: For developers). To see
the output in the same terminal, choose the inline mode:
sudo config --enable normal
Then prefix only the command that needs it:
sudo npx vitest run test/elevated
Windows shows its consent prompt each time. In the default mode ("in a new
window"), the tests run in a separate console that closes when they finish,
so the output is lost. Use normal (inline), or write a report file.
From an editor
An editor's test explorer runs tests at the editor's own level. To run them elevated there, start the editor itself with Run as administrator. Every extension then runs elevated too, so prefer the terminal for a one-off run.
An agent session
The MCP server and the agent command line run the engine in their own process, so they need the same:
- MCP: start the MCP client (the program that launches
spart-desk-mcp) elevated. The server inherits its level. - Command line: run
spart-desk-cli startfrom an elevated terminal. The session's host keeps that level for every later command.
An elevated agent session can drive administrative tools. Keep its policy
narrow: an application allow-list, sensitiveActions and a beforeAction
hook (security.md).
Checking that the run is elevated
net session succeeds only in an elevated administrator's process:
import { spawnSync } from "node:child_process";
export const elevated = spawnSync("net", ["session"], { stdio: "ignore", windowsHide: true }).status === 0;
Use it to skip tests that need elevation in an ordinary run, instead of letting them fail:
import { it } from "vitest";
import { elevated } from "./elevated.js";
it.skipIf(!elevated)("changes a value in Registry Editor", async () => {
// ...
});
Keeping these tests in a folder of their own (test/elevated) lets the
ordinary run leave them out, and the elevated run include only them.
In CI
- GitHub-hosted Windows runners run jobs as an administrator with UAC turned off, so every process is already elevated. Real input also needs an interactive desktop, which hosted runners do not offer (ci.md).
- A self-hosted, interactive runner: start the runner agent (
run.cmd) from an elevated terminal, or as a scheduled task at logon with Run with highest privileges. Only jobs that need it should go to that runner (a label such aselevated).
What elevation does not reach
- Windows' own consent prompt (UAC) and the sign-in and lock screens run on the secure desktop. No process in the user's session can read or drive them, elevated or not. A test that would start one should run where UAC does not ask: a disposable machine, or a CI runner configured for it.
- Processes of the SYSTEM account's integrity level that show windows in the user's session stay out of reach of an administrator too.
Safety
An elevated test run is an administrator on the machine. Every test file,
every dependency the tests import, and every script npm runs can change
the system.
- Run only the tests that need it elevated; keep the rest in the ordinary run.
- Install dependencies from an ordinary terminal (
npm install), never elevated. - Prefer a disposable machine or virtual machine for elevated runs, and never point them at real customer data.
- An elevated agent session is an agent with administrator rights over the desktop: see security.md.