Add a New App
Register your Application-Under-Test (AUT) in GlobalConfig, choose the right platform tool, and verify it resolves before you model a single Control.
← Back to overview · 🇩🇪 Deutsch · ← Concepts · Add Controls →
Decide which platform tool fits your app
Every App-Under-Test (AUT) is one entry in GlobalConfig.apps — a type + tool pair that tells the framework which runtime Strategy to resolve calls through. Pick the row that matches your app:
| Your app is a … | type | tool | Status |
|---|---|---|---|
| Web app (browser-rendered) | 'Web' | 'Playwright' | Available now |
| Windows desktop app | 'Desktop' | 'Appium-Windows' | Available now |
| macOS desktop app | 'Desktop' | 'Mac2' | Type defined, not yet shipped |
| Android app | 'Mobile' | 'UiAutomator2' | Type defined, not yet shipped |
| iOS app | 'Mobile' | 'XCUITest' | Type defined, not yet shipped |
💡 What “type defined, not yet shipped” means. The
AppConfigunion already has a shape for macOS/Android/iOS entries — TypeScript accepts one, and nothing stops you from writing it. But no runtime Strategy is registered forMac2/UiAutomator2/XCUITesttoday: the first Action or Check against such an entry fails at runtime withStrategyRegistry: No factory registered for tool "...". See FAQ — Does the framework support mobile testing? for the current status. Web and Windows-Desktop are the two platforms with a working runtime behind them right now — the rest of this page covers both.
Register a Web app
Add an entry to 2_Apps/1_Global/GlobalConfig.ts — the minimal shape needs only type, tool, and baseUrl:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
YourApp: {
type: 'Web',
tool: 'Playwright',
baseUrl: 'https://<your-app-domain>',
},
} as const;
A realistic entry usually separates the URL into its own constant, so switching between staging and production touches one line instead of the whole object:
// 2_Apps/1_Global/GlobalConfig.ts
export const URL_YOUR_APP = process.env.YOUR_APP_URL ?? 'https://staging.<your-app-domain>';
export const apps = {
YourApp: {
type: 'Web',
tool: 'Playwright',
baseUrl: URL_YOUR_APP,
headless: true,
},
} as const;
headless only matters when a Control or a standalone Inspector run self-bootstraps a browser without an injected Playwright page — a TestCase running through the normal Playwright test-runner fixture ignores it. Once the entry exists, Core.defineExecutionStep('YourApp', 'Start', (env) => ...) resolves env.page/env.url from it automatically — see Build TestSteps.
Scaffold a Web app automatically with the CLI
The steps above — copy _Skeleton, substitute its App-name placeholder, add the GlobalConfig.apps entry, wire the global barrel — are also available as one command:
npx cc-testframework create-web-app --name DemoWeb --url http://localhost:8080
This copies the shipped _Skeleton template (12 Controls, TS_Main/TS_Dialog/TS_Message/TS_Execution/TS_Custom plus their .i18n.json catalogs) into 2_Apps/<N>_DemoWeb/ — <N> is the next free numeric prefix, auto-detected from your existing 2_Apps/ folders — replaces every __APP_NAME__ placeholder in the copied files with DemoWeb, and adds the matching entry to 2_Apps/1_Global/GlobalConfig.ts:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
DemoWeb: {
type: 'Web',
tool: 'Playwright',
baseUrl: 'http://localhost:8080',
},
} as const;
It then also wires the app into 2_Apps/1_Global/References.ts — the global barrel every TestCase imports through (import * as Project from '@GlobalRef') — so Project.DemoWeb.* is importable right away, with no manual edit needed:
// 2_Apps/1_Global/References.ts
export * as DemoWeb from '../2_DemoWeb/References';
The re-export line mirrors whichever import style your barrel already uses predominantly — a relative specifier as shown above, or @Apps/2_DemoWeb/References when your existing app lines already use that path-alias form.
| Flag | Required | Description |
|---|---|---|
--name <Name> | Yes | The App name — a valid identifier (letters, digits, underscore; can’t start with a digit). Used as both the 2_Apps/<N>_<Name>/ folder suffix and the apps/barrel export key. |
--url <url> | Yes | Base URL of the app under test — http://, https://, or file://. |
--project-dir <path> | No | Project root containing 2_Apps/ (auto-detected by default — the command walks upward from your current directory looking for 2_Apps/1_Global/GlobalConfig.ts under ., ./pm, and ./tests). |
--dry-run | No | Print the plan — target folder, files that would be written, the GlobalConfig.ts entry, and the References.ts plan — without touching disk. |
--json | No | Additionally emit a machine-parseable JSON summary (including a referencesUpdated field, see below) alongside the human-readable output, for scripting or CI. |
--help | No | Show usage. |
The command exits 0 on success and 2 on a user error (invalid --name/--url, no project root found, or the target already registered). It’s idempotent: it refuses to overwrite an existing 2_Apps/<N>_<Name>/ folder or a duplicate apps key, and it leaves References.ts untouched if a re-export for that name already exists — re-running it against an already-scaffolded App fails safely instead of clobbering anything.
The --json summary’s referencesUpdated field reports what happened to the barrel: true (the re-export line was written), false (nothing to write — the app already had one, or no barrel file exists at all), or "skipped-example" for the pre-onboarding case below.
💡 The one case that still needs a manual step. If your project’s global barrel is still the un-activated
2_Apps/1_Global/References.ts.example(never renamed/filled in since scaffolding), the command soft-skips the barrel wiring — it never mutates a.examplefile — and prints the exact re-export line to add once you’ve activated it. RenameReferences.ts.exampletoReferences.ts, fill in the required re-exports described inside it, then add the printed line yourself. This is the only scenario whereProject.DemoWeb.*isn’t importable immediately after the command finishes.
Run it with --dry-run first to see exactly what would land on disk (and what the References.ts plan looks like) without writing anything:
npx cc-testframework create-web-app --name DemoWeb --url http://localhost:8080 --dry-run
💡 Building against a Windows Desktop AUT instead? See Scaffold a Desktop app automatically with the CLI below — the Appium-Windows sibling of this command, same workflow.
Register an Electron app
An Electron app — Calculator-style packaged desktop apps built on Electron, or any app you build on it yourself — is a Web app tested via Playwright, not a Desktop app tested via Appium. Its UI is a Chromium renderer, plain DOM, not a native UIA tree, so it registers with the same type/tool pair as a browser web app. The only thing that changes is how the app gets launched: point executablePath at the packaged app’s executable instead of giving baseUrl a URL to open.
💡 One-command shortcut.
npx cc-testframework create-electron-app --name <YourApp> --executable-path <path>scaffolds and registers this in one step — see Scaffold a New App — Electron app.
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
MyElectronApp: {
type: 'Web',
tool: 'Playwright',
executablePath: 'C:\\path\\to\\MyElectronApp.exe', // the packaged Electron app's .exe
// args: ['--flag'], // optional launch args
},
} as const;
type stays 'Web', tool stays 'Playwright' — it’s one config shape, not a separate type/tool combination. The presence of executablePath (instead of baseUrl) is what marks the entry as Electron.
An Electron app is scaffolded from its own dedicated _Skeleton_Electron root, not from the browser _Skeleton. Its lifecycle steps use the same plain names as a browser web app — TS_Execution_Start/_Close/_Restart — since a scaffolded app is always exactly one of the two (browser web or Electron), never both at once, so there’s no naming collision to avoid and no *Electron suffix. These steps call Core.I_ElectronHandler internally, which launches the process via Playwright’s _electron.launch() instead of chromium.launch(), then uses the app’s first window as the session page.
💡 After launch, it’s a normal web page. Once
TS_Execution_Starthas run, the Electron window is an ordinary Playwright page — a Chromium-rendered DOM. Every existing Web Control and everyTS_Main/TS_Dialog/TS_Messagestep works against it unchanged; there is no separate Electron-flavored Control API to learn.
Prerequisite: executablePath must point at the packaged Electron app’s .exe (or platform-equivalent binary) on the machine running the test process — the same app you’d otherwise double-click to open it.
Register a Windows Desktop app
💡 Electron-based Windows app? Register it as a Web app instead — see Register an Electron app above. Its renderer is a Chromium DOM, not a native UIA tree, so it goes through Playwright, not Appium/WinAppDriver.
A Windows Desktop AUT needs an executable path instead of a baseUrl, plus an optional appiumUrl for the Appium/WinAppDriver server:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
YourDesktopApp: {
type: 'Desktop',
tool: 'Appium-Windows',
executable: 'C:\\Users\\Public\\<YourApp>\\<YourApp>.exe',
appiumUrl: 'http://host.docker.internal:4723',
},
} as const;
executableis the absolute path WinAppDriver passes as theappium:appcapability — it must exist on the machine the Appium server itself runs on, not necessarily the one running the test process.appiumUrlis optional. Omit it and the framework resolves a sensible default through a three-tier priority chain (explicit value →APPIUM_URLenvironment variable → platform default) — see API Reference — Appium URL resolution and FAQ for the full chain and troubleshooting.capabilitiesaccepts an optionalRecord<string, unknown>merged over the framework’s default WinAppDriver capabilities, for anything app-specific the two fields above don’t cover.
Scaffold a Desktop app automatically with the CLI
The Desktop sibling of create-web-app — same one-command workflow, but for an Appium-Windows Desktop AUT instead of a Playwright Web AUT:
npx cc-testframework create-desktop-app --name DemoDesktop --executable "C:\apps\Demo.exe"
create-web-app | create-desktop-app | |
|---|---|---|
| Skeleton copied | _Skeleton | _Skeleton_Desktop |
| Target flag | --url <url> | --executable <path> (+ optional --appium-url <url>) |
GlobalConfig.apps entry | type: 'Web', tool: 'Playwright', baseUrl | type: 'Desktop', tool: 'Appium-Windows', executable, appiumUrl |
| Skeleton contents | 12 Controls, TS_Main/Dialog/Message/Execution/Custom | 12 Controls, TS_Main/Dialog/Message/Execution (no TS_Custom) |
This copies the shipped _Skeleton_Desktop template into 2_Apps/<N>_DemoDesktop/ — same numeric-prefix auto-detection and __APP_NAME__ substitution as create-web-app — and adds the matching entry to GlobalConfig.ts:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
DemoDesktop: {
type: 'Desktop',
tool: 'Appium-Windows',
executable: 'C:\\apps\\Demo.exe',
appiumUrl: 'http://host.docker.internal:4723',
},
} as const;
It wires 2_Apps/1_Global/References.ts exactly like create-web-app — same alias-style mirroring, same idempotency, same .example pre-onboarding soft-skip — see Scaffold a Web app automatically with the CLI above for the full explanation of that mechanism; it isn’t repeated here.
| Flag | Required | Description |
|---|---|---|
--name <Name> | Yes | Same rule as create-web-app — a valid identifier, used as the folder suffix and the apps/barrel export key. |
--executable <path> | Yes | Path to the app’s .exe on the Appium/Windows host, not on the machine running this command — not checked against the local filesystem. |
--appium-url <url> | No | Appium server URL. Default: http://host.docker.internal:4723 (the devcontainer→Windows-host default). |
--project-dir <path> | No | Same as create-web-app. |
--dry-run | No | Same as create-web-app — prints the plan (target folder, GlobalConfig.ts entry, References.ts plan) without touching disk. |
--json | No | Same as create-web-app — the JSON summary adds executable/appiumUrl alongside referencesUpdated. |
--help | No | Show usage. |
Same exit codes (0/2) and the same idempotency guarantees as create-web-app.
💡 Scaffolding runs anywhere; running the tests needs the Appium host. The command never touches the path in
--executable— it only writes a string intoGlobalConfig.ts, so it works from any machine, including a devcontainer with no Windows host attached. Actually running the generated TestCases needs a reachable Appium/WinAppDriver server at--appium-url, with the.exepresent on that host — see FAQ — How does the framework know where to find the Appium server for Desktop tests? and theappiumUrlexplanation above.
Run it with --dry-run first, same as create-web-app:
npx cc-testframework create-desktop-app --name DemoDesktop --executable "C:\apps\Demo.exe" --dry-run
UWP / Windows-Store apps: executable also accepts an AUMID
Not every Windows app is a classic Win32 .exe. UWP apps — Calculator and most Microsoft Store apps — have no plain executable file on disk to point at. For these, executable takes an AppUserModelId (AUMID) instead of a file path. It’s the same field either way — WinAppDriver’s app capability launches a .exe path and an AUMID transparently, with no extra field and no extra step:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
Calc: {
type: 'Desktop',
tool: 'Appium-Windows',
executable: 'Microsoft.WindowsCalculator_8wekyb3d8bbwe!App', // AUMID
},
} as const;
💡 Finding an app’s AUMID. Run
Get-StartAppsin a PowerShell session on the machine the Appium server runs on — it lists every installed Start-menu app together with its AUMID. Narrow it down for a specific app withGet-StartApps | Where-Object AppID -like "*Calculator*". If you scaffold the App entry through the framework’s setup tooling, its Store-app picker looks the AUMID up for you — the manualGet-StartAppsroute above is the one to fall back on, or to use directly.
Prerequisites: the Store app must be installed on the machine WinAppDriver runs on, and a reachable Appium/WinAppDriver session is required either way — see FAQ — How does the framework know where to find the Appium server for Desktop tests?.
Register a Mobile app
GlobalConfig.apps accepts a type: 'Mobile' entry today, shaped like this:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
YourMobileApp: {
type: 'Mobile',
tool: 'UiAutomator2', // or 'XCUITest' for iOS
appPath: '/path/to/YourApp.apk', // or a bundle id for an already-installed iOS app
},
} as const;
TypeScript accepts this entry and GlobalConfig.apps type-checks — but as the table above notes, no runtime Strategy is registered for UiAutomator2/XCUITest yet, so a Control action against this entry throws at runtime rather than at compile time. Register it now if you want the shape ready for later; don’t rely on it resolving a real session today.
Register more than one App
GlobalConfig.apps is a single object — most projects grow more than one entry over time, mixing families freely:
// 2_Apps/1_Global/GlobalConfig.ts
export const apps = {
YourWebApp: {
type: 'Web',
tool: 'Playwright',
baseUrl: URL_YOUR_WEB_APP,
},
YourDesktopApp: {
type: 'Desktop',
tool: 'Appium-Windows',
executable: 'C:\\Users\\Public\\<YourApp>\\<YourApp>.exe',
},
} as const;
Each key is independent — a TestCase composing steps from YourWebApp and YourDesktopApp in the same file works exactly like composing steps from one App, since every defineTestStep/defineExecutionStep call already carries its own App key. Add a new key any time a new AUT joins your test suite; existing entries and the TestCases built on them are unaffected.
Verify your App is reachable
A one-line TS_Execution_Start/TS_Execution_Close pair — see Build TestSteps for the full file — confirms the entry resolves before you invest in Controls:
// 3_Cases/TC_Smoke.spec.ts
import * as Project from '@GlobalRef';
Project.Core.test('TC_Smoke', async () => {
Project.Core.Step.setCurrentTestCaseName('TC_Smoke');
await Project.YourApp.TS_Execution_Start();
await Project.YourApp.TS_Execution_Close();
});
Run it with npx playwright test 3_Cases/TC_Smoke.spec.ts from inside pm/ — see Run and Debug for flags and options.
Common failures and their fix:
| Error | Cause | Fix |
|---|---|---|
unknown appName 'YourApp' | Typo, or the entry is missing from GlobalConfig.apps | Check the key spelling matches exactly, including case |
Web app 'YourApp' has no 'baseUrl' | type: 'Web' entry missing the baseUrl field | Add baseUrl to the entry |
Desktop app 'YourApp' has no 'executable' | type: 'Desktop', tool: 'Appium-Windows' entry missing executable | Add the absolute .exe path |
StrategyRegistry: No factory registered for tool "..." | Entry uses Mac2, UiAutomator2, or XCUITest | Not runnable yet — see Decide which platform tool fits your app above |
Playwright times out navigating to baseUrl | App unreachable from the machine running the tests (VPN, wrong environment URL, container networking) | Verify the URL opens in a plain browser from the same machine/container the tests run in |
Next step: model your Controls
With your App registered and reachable, model the interactive elements on its screens as Controls.
Next: Add Controls — locators, actions, and where to place the Control file.
📧 Questions? Contact: jens.szelag@itsbusiness.ch
itsbusiness AG · Bern · Switzerland