Files
callstack__agent-device/packages/maestro/src/internal/program-ir.ts
T
Rohit Bansal d11c8cf9d6 feat: support standalone Maestro clearState command (#2366)
* feat: support standalone Maestro clearState command

Accept '- clearState' / '- clearState: <appId>' in Maestro YAML flows.
Unlike launchApp.clearState (clear-then-open), the standalone form clears
app state without relaunching, projecting to 'settings clear-app-state'
on the daemon. Covers the Rocket.Chat login-with-deeplink helper, which
previously failed with 'Maestro command "clearState" is not supported'.

* test(maestro): cover standalone clearState with authored corpus flow

Replace the UNVERIFIED_COMMANDS exemption with an authored
clear-state flow exercising default and explicit appIds, plus the
regenerated upstream parser fixture proving Maestro compatibility.

Live iOS Simulator evidence (iPhone 16, com.apple.mobilesafari):
- marker files in the data container, then replay '- clearState'
  (default) and '- clearState: <appId>' (explicit) via
  'replay --maestro'; both replay 1/1, wipe the container, and leave
  MobileSafari not running (no reopen).
2026-09-08 12:42:44 +02:00

299 lines
7.1 KiB
TypeScript

export type MaestroScalar = string | number | boolean | null;
export type MaestroSourceLocation = {
path?: string;
line: number;
};
export type MaestroPlatform = 'android' | 'ios' | 'web';
export type MaestroDirection = 'up' | 'down' | 'left' | 'right';
export type MaestroCoordinate =
| { space: 'absolute'; x: number; y: number }
| { space: 'percent'; x: number; y: number };
export type MaestroLeafSelector = {
text?: string;
id?: string;
enabled?: boolean;
selected?: boolean;
optional?: boolean;
};
export type MaestroRelationalSelectorFields = {
index?: number | string;
childOf?: MaestroSelector;
below?: MaestroSelector;
above?: MaestroSelector;
leftOf?: MaestroSelector;
rightOf?: MaestroSelector;
containsChild?: MaestroSelector;
containsDescendants?: MaestroSelector[];
};
export type MaestroSelectorMap = MaestroLeafSelector & MaestroRelationalSelectorFields;
export type MaestroSelector = MaestroSelectorMap;
export type MaestroOptionalCommand = {
optional?: boolean;
};
export type MaestroGestureTarget =
| MaestroCoordinate
| { space: 'target'; selector: MaestroSelector };
export type MaestroLaunchArguments =
| { kind: 'scalar'; value: string | number | boolean }
| { kind: 'list'; values: Array<string | number | boolean> }
| { kind: 'map'; values: Record<string, string | number | boolean> };
export type MaestroLaunchAppCommand = {
kind: 'launchApp';
source: MaestroSourceLocation;
appId?: string;
stopApp?: boolean;
clearState?: boolean;
arguments?: MaestroLaunchArguments;
launchArguments?: MaestroLaunchArguments;
};
export type MaestroTapOnCommand = MaestroOptionalCommand & {
kind: 'tapOn';
source: MaestroSourceLocation;
target: MaestroGestureTarget;
retryTapIfNoChange?: boolean;
repeat?: number | string;
delay?: number | string;
label?: string;
};
export type MaestroDoubleTapOnCommand = MaestroOptionalCommand & {
kind: 'doubleTapOn';
source: MaestroSourceLocation;
target: MaestroGestureTarget;
delay?: number | string;
label?: string;
};
export type MaestroLongPressOnCommand = MaestroOptionalCommand & {
kind: 'longPressOn';
source: MaestroSourceLocation;
target: MaestroGestureTarget;
label?: string;
};
export type MaestroSwipeGesture =
| {
kind: 'coordinates';
start: MaestroCoordinate;
end: MaestroCoordinate;
duration?: number | string;
}
| {
kind: 'screen';
direction: MaestroDirection;
duration?: number | string;
}
| {
kind: 'target';
from: MaestroSelector;
direction: MaestroDirection;
duration?: number | string;
};
export type MaestroSwipeCommand = MaestroOptionalCommand & {
kind: 'swipe';
source: MaestroSourceLocation;
gesture: MaestroSwipeGesture;
label?: string;
};
export type MaestroInputTextCommand = {
kind: 'inputText';
source: MaestroSourceLocation;
text: string;
label?: string;
};
export type MaestroEraseTextCommand = {
kind: 'eraseText';
source: MaestroSourceLocation;
charactersToErase?: number | string;
};
export type MaestroOpenLinkCommand = {
kind: 'openLink';
source: MaestroSourceLocation;
link: string;
};
export type MaestroAssertVisibleCommand = MaestroOptionalCommand & {
kind: 'assertVisible';
source: MaestroSourceLocation;
target: MaestroSelector;
label?: string;
};
export type MaestroAssertNotVisibleCommand = MaestroOptionalCommand & {
kind: 'assertNotVisible';
source: MaestroSourceLocation;
target: MaestroSelector;
label?: string;
};
export type MaestroAssertTrueCommand = MaestroOptionalCommand & {
kind: 'assertTrue';
source: MaestroSourceLocation;
condition: string | number | boolean;
label?: string;
};
export type MaestroExtendedWaitUntilCommand = MaestroOptionalCommand & {
kind: 'extendedWaitUntil';
source: MaestroSourceLocation;
visible?: MaestroSelector;
notVisible?: MaestroSelector;
timeout?: number | string;
label?: string;
};
export type MaestroTakeScreenshotCommand = {
kind: 'takeScreenshot';
source: MaestroSourceLocation;
path: string;
};
export type MaestroScrollCommand = {
kind: 'scroll';
source: MaestroSourceLocation;
};
export type MaestroScrollUntilVisibleCommand = MaestroOptionalCommand & {
kind: 'scrollUntilVisible';
source: MaestroSourceLocation;
element: MaestroSelector;
direction?: MaestroDirection;
timeout?: number | string;
label?: string;
};
export type MaestroHideKeyboardCommand = {
kind: 'hideKeyboard';
source: MaestroSourceLocation;
};
export type MaestroPressKeyCommand = {
kind: 'pressKey';
source: MaestroSourceLocation;
key: 'back' | 'enter' | 'return' | 'home';
};
export type MaestroBackCommand = {
kind: 'back';
source: MaestroSourceLocation;
};
export type MaestroWaitForAnimationToEndCommand = {
kind: 'waitForAnimationToEnd';
source: MaestroSourceLocation;
timeout?: number | string;
};
export type MaestroStopAppCommand = {
kind: 'stopApp';
source: MaestroSourceLocation;
appId?: string;
};
export type MaestroClearStateCommand = {
kind: 'clearState';
source: MaestroSourceLocation;
appId?: string;
};
export type MaestroRunScriptCommand = {
kind: 'runScript';
source: MaestroSourceLocation;
file: string;
env?: Record<string, string | number | boolean>;
};
export type MaestroRunFlowCondition = {
platform?: MaestroPlatform;
visible?: MaestroSelector;
notVisible?: MaestroSelector;
true?: boolean | string;
};
export type MaestroRunFlowCommand = {
kind: 'runFlow';
source: MaestroSourceLocation;
include: { kind: 'file'; path: string } | { kind: 'commands'; commands: MaestroCommand[] };
when?: MaestroRunFlowCondition;
env?: Record<string, string | number | boolean>;
label?: string;
};
export type MaestroRepeatCommand = {
kind: 'repeat';
source: MaestroSourceLocation;
times: number | string;
commands: MaestroCommand[];
};
export type MaestroRetryCommand = {
kind: 'retry';
source: MaestroSourceLocation;
maxRetries?: number | string;
commands: MaestroCommand[];
};
export type MaestroCommand =
| MaestroLaunchAppCommand
| MaestroTapOnCommand
| MaestroDoubleTapOnCommand
| MaestroLongPressOnCommand
| MaestroSwipeCommand
| MaestroInputTextCommand
| MaestroEraseTextCommand
| MaestroOpenLinkCommand
| MaestroAssertVisibleCommand
| MaestroAssertNotVisibleCommand
| MaestroAssertTrueCommand
| MaestroExtendedWaitUntilCommand
| MaestroTakeScreenshotCommand
| MaestroScrollCommand
| MaestroScrollUntilVisibleCommand
| MaestroHideKeyboardCommand
| MaestroPressKeyCommand
| MaestroBackCommand
| MaestroWaitForAnimationToEndCommand
| MaestroStopAppCommand
| MaestroClearStateCommand
| MaestroRunScriptCommand
| MaestroRunFlowCommand
| MaestroRepeatCommand
| MaestroRetryCommand;
export type MaestroProgramConfig = {
name?: string;
appId?: string;
tags?: string[];
env?: Record<string, string | number | boolean>;
onFlowStart?: MaestroCommand[];
onFlowComplete?: MaestroCommand[];
};
export type MaestroProgram = {
kind: 'program';
source: MaestroSourceLocation;
config: MaestroProgramConfig;
commands: MaestroCommand[];
};
export type MaestroProgramParseOptions = {
sourcePath?: string;
};