# Desktop apps

> Test macOS apps and Electron apps on a Mac, and set up the permissions they need.

Source: https://rehearsal.dev/retest/platforms/desktop

Retest tests two kinds of desktop app. A native macOS app is driven through XCTest. An Electron app is driven through its own Chromium, like a browser.

## macOS apps

```ts filename="retest.config.ts"
const config = defineConfig({
  apps: {
    desk: {
      platform: 'macos',
      appPath: './TaskDesk.app',
      arguments: ['-reset', '-windowFrame', '20,60,700,480'],
      environment: { TASK_MODE: 'test' },
    },
  },
})
```

A macOS app needs three things on the Mac that runs it:

- The macOS executor, built with `npx retest install mac2`. It is pinned to Xcode 26.5 on Apple silicon.
- Automation Mode enabled without a prompt. A prompt counts as a refusal. On a Mac that runs tests unattended, run `sudo automationmodetool enable-automationmode-without-authentication` once.
- Screen Recording for the terminal or agent that runs Retest, so it can capture the app's window.

Elements of a macOS app take `click()`, `fill()`, `press()` and `scroll()`, and the page has `alert.accept()` and `alert.dismiss()`. There is no `goto`, `swipe` or software keyboard, and calling one is a type error.

## Window capture

Retest captures the app's own window, by its window number. Another app's window over it is neither in the picture nor a reason to refuse it.

For a config with a macOS app, `retest doctor` adds a row for Screen Recording. It asks macOS without a prompt and takes no picture. An iOS simulator app or a browser needs no such permission.

## One desktop at a time

A Mac has one desktop, and native input goes through it. Retest holds the desktop for one macOS app at a time, across every Retest process on the Mac. A test that needs two macOS apps is refused before it starts.

> [!NOTE]
> While a test drives the desktop, macOS shows its Automation Mode overlay over the screen. Run desktop tests on a Mac nobody is using at the time.

## What a macOS test keeps

- The app's data and keychain are kept from one test to the next.
- An argument such as `-reset` asks the app for its own reset, when it has one.
- Retest never takes over or ends a copy of the app it did not launch.

## Electron apps

```ts filename="retest.config.ts"
import { defineConfig, electron } from '@rehearsal-labs/retest'

export default defineConfig({
  apps: {
    desktop: electron({ executablePath: 'electron/dist/Electron.app/Contents/MacOS/Electron', appPath: 'desktop' }),
  },
})
```

- `executablePath` is the Electron binary. `appPath` is the app's folder, which holds its `package.json`, or its entry file.
- Each test launches the app afresh and quits it when the test ends.
- The first window the app opens is the test's page. A test cannot reach any window after the first.
- Each launch gets a new data folder, removed afterwards. With `userDataDir`, every launch uses that folder, so the app keeps its data.
- The app's windows open on your screen. Electron has no headless mode.

Retest refuses by name what it cannot reach: `goto`, which is also a type error, the main process, native menus and native dialogs. A sign-in state cannot be restored into an Electron app, so use `userDataDir` to keep one.

A secret is typed into an Electron window only on an http or https origin that `secretOrigins` lists for it. Naming an origin there means trusting the app with the secret.

Electron support was checked with Electron 44.5.1 on macOS on Apple silicon. Linux, Windows and packaged apps have not been run.
