From 2bd1533fb64e2373849447347fc0d5949cac0f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 5 Aug 2026 11:02:16 +0200 Subject: [PATCH 1/4] refactor(contracts): one viewport-root predicate for the whole repo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Is this the Application/Window root" was written nine times: three spellings normalizing `type|role|subrole`, five lowercasing `type` alone, and one comparing the normalized type for EQUALITY. Two of the nine sat in `contracts/snapshot-visibility.ts` itself, disagreeing with each other. Measured before collapsing, using #1592's method — ground the comparison in what each backend ACTUALLY emits, not in fixture strings. Over the 31 names iOS's `elementTypeName` can return, the 18 fully-qualified class names Android emits, and the 24 mapped/raw forms the macOS helper produces, the nine agreed on 71 of 73. The two exceptions are macOS window subroles, and the only spelling that disagreed is maestro's `===`, whose platform union is `android | ios` — so it can never see them. The duplication was textual, not behavioral, which is what made the collapse safe. `isViewportRootNode` reads role and subrole because the macOS helper is the only backend populating them and the only one able to emit a window whose `type` does not say so: `normalizedSnapshotType` returns the raw subrole for a non-standard window, so an `AXWindow` with subrole `AXSystemDialog` or `AXUnknown` reads as neither from `type` alone. Those two shapes are the whole behavioral delta of this change, at the six call sites that were type-only, and they are windows by role. `snapshot-viewport-root.test.ts` pins the predicate over those three emitted vocabularies. Red evidence: reverting the canonical definition to the type-only spelling fails 2 of 5 cells, to the equality spelling 4 of 5. Also drops two kernel re-declarations this made visible: maestro's local `containsPoint` and `rectsOverlap` were character-identical to `@agent-device/kernel/rect`'s `containsPoint` and `isRectVisibleInViewport`, in a file that already imports from that module. And `resolveViewportRect` loses three `as Rect` casts that only existed because `.filter()` cannot narrow `node.rect` — one `flatMap` states the same thing honestly. Deliberately NOT in this change: the three viewport RESOLVERS still diverge, and on Android that is a live defect rather than duplication. Filed separately with the measurement. --- packages/contracts/src/facades/snapshot.ts | 1 + packages/contracts/src/scroll-gesture.ts | 9 +-- packages/contracts/src/snapshot-visibility.ts | 57 ++++++++++++------- .../src/internal/runtime-port-geometry.ts | 5 +- .../maestro/src/internal/snapshot-policy.ts | 25 +++----- src/core/interaction-targeting.ts | 7 +-- src/daemon/handlers/find.ts | 4 +- src/daemon/interaction-outcome-policy.ts | 19 +------ src/daemon/screenshot-overlay.ts | 17 ++---- src/snapshot/snapshot-occlusion.ts | 13 ++--- 10 files changed, 64 insertions(+), 93 deletions(-) diff --git a/packages/contracts/src/facades/snapshot.ts b/packages/contracts/src/facades/snapshot.ts index a905cf2f5b..4eff74e73b 100644 --- a/packages/contracts/src/facades/snapshot.ts +++ b/packages/contracts/src/facades/snapshot.ts @@ -5,6 +5,7 @@ export { isNodeVisibleInEffectiveViewport, isNodeVisibleOnScreen, isUsefulVisibilityAnchor, + isViewportRootNode, isTapPointInsideViewport, resolveEffectiveViewportRect, resolveViewportRect, diff --git a/packages/contracts/src/scroll-gesture.ts b/packages/contracts/src/scroll-gesture.ts index 35ef996bc4..a041e62849 100644 --- a/packages/contracts/src/scroll-gesture.ts +++ b/packages/contracts/src/scroll-gesture.ts @@ -1,5 +1,6 @@ import { AppError } from '@agent-device/kernel/errors'; import { defineStringEnum } from './string-enum.ts'; +import { isViewportRootNode } from './snapshot-visibility.ts'; import type { Rect, SnapshotNode } from '@agent-device/kernel/snapshot'; // What a caller may ASK for, as opposed to `ScrollDirection` (what the gesture resolves to): @@ -273,7 +274,7 @@ export function parseScrollDirection(direction: string): ScrollDirection { function inferViewportRect(nodes: Array>): Rect | undefined { const candidate = nodes - .filter((node) => isViewportNode(node.type) && isValidRect(node.rect)) + .filter((node) => isViewportRootNode(node) && isValidRect(node.rect)) .map((node) => node.rect) .sort( (left, right) => @@ -290,12 +291,6 @@ function inferViewportRect(nodes: Array>): R return { x: 0, y: 0, width, height }; } -function isViewportNode(type: string | undefined): boolean { - if (!type) return false; - const normalized = type.toLowerCase(); - return normalized.includes('application') || normalized.includes('window'); -} - function isValidRect(rect: Rect | undefined): rect is Rect { return !!rect && rect.width > 0 && rect.height > 0; } diff --git a/packages/contracts/src/snapshot-visibility.ts b/packages/contracts/src/snapshot-visibility.ts index 9771bb71f2..3e33fce6ea 100644 --- a/packages/contracts/src/snapshot-visibility.ts +++ b/packages/contracts/src/snapshot-visibility.ts @@ -15,6 +15,32 @@ type SnapshotVisibilityNode = Pick< 'rect' | 'index' | 'parentIndex' | 'type' | 'role' | 'subrole' >; +/** + * The application/window root: the node a target rect is measured against, and + * the node whose own rect is invariant under any gesture. + * + * One definition for the whole repo. It reads `type`, `role` AND `subrole` + * because the macOS helper is the only backend that populates the latter two, + * and it is the only backend that can emit a window whose `type` does not say + * so — `normalizedSnapshotType` returns the raw subrole for a non-standard + * window, so an `AXWindow` with subrole `AXSystemDialog` or `AXUnknown` reads + * as neither from `type` alone while `role` names it exactly. + * + * Substring, not equality: macOS emits unmapped roles with their `AX` prefix + * intact and subroles like `AXFloatingWindow` that are windows by any reading. + * iOS emits a closed set of 31 short names in which only `Application` and + * `Window` contain either word, so substring and equality agree there. Android + * emits fully-qualified Java class names and no root node at all, so no + * spelling of this predicate matches anything on Android — see + * `resolveViewportRect`'s third fallback, which is what Android actually uses. + */ +export function isViewportRootNode(node: Pick): boolean { + const kind = [node.type, node.role, node.subrole] + .map((value) => normalizeType(value ?? '')) + .join(' '); + return kind.includes('application') || kind.includes('window'); +} + /** * The root viewport a target rect is measured against: the largest * Application/Window rect containing the target's center, falling back to the @@ -22,30 +48,19 @@ type SnapshotVisibilityNode = Pick< */ export function resolveViewportRect(nodes: RawSnapshotNode[], targetRect: Rect): Rect | null { const targetCenter = centerOfRect(targetRect); - const rectNodes = nodes.filter((node) => hasValidRect(node.rect)); - const viewportNodes = rectNodes.filter((node) => { - const type = (node.type ?? '').toLowerCase(); - return type.includes('application') || type.includes('window'); - }); - - const containingViewport = pickLargestRect( - viewportNodes - .map((node) => node.rect as Rect) - .filter((rect) => containsPoint(rect, targetCenter.x, targetCenter.y)), + const rects = nodes.flatMap((node) => + hasValidRect(node.rect) ? [{ node, rect: node.rect }] : [], ); - if (containingViewport) return containingViewport; - - const viewportFallback = pickLargestRect(viewportNodes.map((node) => node.rect as Rect)); - if (viewportFallback) return viewportFallback; + const viewportRects = rects + .filter((entry) => isViewportRootNode(entry.node)) + .map((entry) => entry.rect); + const contains = (rect: Rect) => containsPoint(rect, targetCenter.x, targetCenter.y); - const genericContaining = pickLargestRect( - rectNodes - .map((node) => node.rect as Rect) - .filter((rect) => containsPoint(rect, targetCenter.x, targetCenter.y)), + return ( + pickLargestRect(viewportRects.filter(contains)) ?? + pickLargestRect(viewportRects) ?? + pickLargestRect(rects.map((entry) => entry.rect).filter(contains)) ); - if (genericContaining) return genericContaining; - - return null; } function hasValidRect(rect: Rect | undefined): rect is Rect { diff --git a/packages/maestro/src/internal/runtime-port-geometry.ts b/packages/maestro/src/internal/runtime-port-geometry.ts index d4e9d7031f..5add0ad29f 100644 --- a/packages/maestro/src/internal/runtime-port-geometry.ts +++ b/packages/maestro/src/internal/runtime-port-geometry.ts @@ -6,7 +6,7 @@ import { pointInsideRect } from './shared.ts'; import { findNearestScrollableAncestor, isScrollableNodeLike, - normalizeType, + isViewportRootNode, } from '@agent-device/contracts/snapshot'; import { MAESTRO_COMPATIBILITY_PRESETS } from './compatibility-policy.ts'; import { resolveNumeric } from './engine-flow.ts'; @@ -108,8 +108,7 @@ function findScrollContainer( function findLargestViewportRect(nodes: SnapshotState['nodes']): Rect | undefined { return nodes .filter((node) => { - const type = normalizeType(node.type ?? ''); - return isPositiveFiniteRect(node.rect) && (type === 'application' || type === 'window'); + return isPositiveFiniteRect(node.rect) && isViewportRootNode(node); }) .sort( (left, right) => diff --git a/packages/maestro/src/internal/snapshot-policy.ts b/packages/maestro/src/internal/snapshot-policy.ts index ea3b3f16ce..c04569e1fd 100644 --- a/packages/maestro/src/internal/snapshot-policy.ts +++ b/packages/maestro/src/internal/snapshot-policy.ts @@ -3,8 +3,13 @@ import { findNearestScrollableAncestor, findSnapshotAncestor, isUsefulVisibilityAnchor, + isViewportRootNode, } from '@agent-device/contracts/snapshot'; -import { isPositiveFiniteRect } from '@agent-device/kernel/rect'; +import { + containsPoint, + isPositiveFiniteRect, + isRectVisibleInViewport, +} from '@agent-device/kernel/rect'; import type { Rect, SnapshotNode } from '@agent-device/kernel/snapshot'; export function isMaestroNodeVisible( @@ -47,15 +52,12 @@ function isVisibleInEffectiveViewport(node: SnapshotNode, nodes: SnapshotNode[]) const viewport = findNearestScrollableAncestor(node, byIndex, (ancestor) => Boolean(ancestor.rect))?.rect ?? resolveRootViewport(nodes, node.rect); - return viewport ? rectsOverlap(node.rect, viewport) : true; + return viewport ? isRectVisibleInViewport(node.rect, viewport) : true; } function resolveRootViewport(nodes: SnapshotNode[], target: Rect): Rect | null { const viewportRects = nodes - .filter((node) => { - const type = (node.type ?? '').toLowerCase(); - return node.rect && (type.includes('application') || type.includes('window')); - }) + .filter((node) => node.rect && isViewportRootNode(node)) .map((node) => node.rect!) .sort((left, right) => right.width * right.height - left.width * left.height); const centerX = target.x + target.width / 2; @@ -64,14 +66,3 @@ function resolveRootViewport(nodes: SnapshotNode[], target: Rect): Rect | null { viewportRects.find((rect) => containsPoint(rect, centerX, centerY)) ?? viewportRects[0] ?? null ); } - -function rectsOverlap(left: Rect, right: Rect): boolean { - return ( - Math.max(left.x, right.x) <= Math.min(left.x + left.width, right.x + right.width) && - Math.max(left.y, right.y) <= Math.min(left.y + left.height, right.y + right.height) - ); -} - -function containsPoint(rect: Rect, x: number, y: number): boolean { - return x >= rect.x && x <= rect.x + rect.width && y >= rect.y && y <= rect.y + rect.height; -} diff --git a/src/core/interaction-targeting.ts b/src/core/interaction-targeting.ts index 29d72aedb3..8213cdf76f 100644 --- a/src/core/interaction-targeting.ts +++ b/src/core/interaction-targeting.ts @@ -1,7 +1,7 @@ import type { Rect, SnapshotNode } from '@agent-device/kernel/snapshot'; import { centerOfRect } from '@agent-device/kernel/snapshot'; import { containsPoint, pickLargestRect } from '@agent-device/kernel/rect'; -import { normalizeType } from '@agent-device/contracts/snapshot'; +import { normalizeType, isViewportRootNode } from '@agent-device/contracts/snapshot'; import { findNearestHittableAncestor } from '../snapshot/snapshot-processing.ts'; import { isSnapshotNodeInteractionBlocked } from '../snapshot/snapshot-occlusion.ts'; import { @@ -158,10 +158,7 @@ function isScrollingContainer(node: SnapshotNode): boolean { function resolveRootViewportRect(nodes: SnapshotNode[], targetRect: Rect): Rect | null { const targetCenter = centerOfRect(targetRect); const viewportRects = nodes - .filter((node) => { - const type = (node.type ?? '').toLowerCase(); - return type.includes('application') || type.includes('window'); - }) + .filter(isViewportRootNode) .map((node) => normalizeRect(node.rect)) .filter((rect): rect is Rect => rect !== null); if (viewportRects.length === 0) return null; diff --git a/src/daemon/handlers/find.ts b/src/daemon/handlers/find.ts index 596fa8da0e..7ecc969334 100644 --- a/src/daemon/handlers/find.ts +++ b/src/daemon/handlers/find.ts @@ -1,4 +1,5 @@ import { dispatchCommand } from '../../core/dispatch.ts'; +import { isViewportRootNode } from '@agent-device/contracts/snapshot'; import { findBestMatchesByLocator, isReadOnlyFindAction, @@ -371,8 +372,7 @@ function isRootInteractionContainer( root: SnapshotState['nodes'][number] | undefined, ): boolean { if (!root?.rect || !node.rect) return false; - const type = node.type?.toLowerCase() ?? ''; - if (!type.includes('application') && !type.includes('window')) return false; + if (!isViewportRootNode(node)) return false; return rectsMatch(node.rect, root.rect); } diff --git a/src/daemon/interaction-outcome-policy.ts b/src/daemon/interaction-outcome-policy.ts index a1f9518e64..29ae0b1248 100644 --- a/src/daemon/interaction-outcome-policy.ts +++ b/src/daemon/interaction-outcome-policy.ts @@ -3,7 +3,7 @@ import { isMobilePlatform } from '@agent-device/kernel/device'; import type { SnapshotNode, SnapshotState } from '@agent-device/kernel/snapshot'; import { collectKeyboardChromeRefs } from '../core/snapshot-chrome.ts'; import { emitDiagnostic } from '../utils/diagnostics.ts'; -import { normalizeType } from '@agent-device/contracts/snapshot'; +import { isViewportRootNode } from '@agent-device/contracts/snapshot'; import { contextFromFlags } from './context.ts'; import type { SessionState } from './types.ts'; @@ -390,22 +390,7 @@ function isNonDiscriminatingSurfaceNode( node: SnapshotNode, keyboardChromeRefs: ReadonlySet, ): boolean { - return isViewportRootKind(node) || (node.ref !== undefined && keyboardChromeRefs.has(node.ref)); -} - -/** - * Minimal local equivalent of `isViewportRoot` in - * `src/snapshot/snapshot-occlusion.ts` (source of truth) — that function is - * module-private and keyed off the broader `RawSnapshotNode` shape used by - * occlusion/viewport resolution, so it is reimplemented here rather than - * exported solely for this caller. Same normalized-kind substring test; keep - * the two in lockstep if the underlying AX vocabulary changes. - */ -function isViewportRootKind(node: Pick): boolean { - const normalizedKind = [node.type, node.role, node.subrole] - .map((value) => normalizeType(value ?? '')) - .join(' '); - return normalizedKind.includes('application') || normalizedKind.includes('window'); + return isViewportRootNode(node) || (node.ref !== undefined && keyboardChromeRefs.has(node.ref)); } function interactionSurfaceSemanticKey(node: SnapshotNode): string | undefined { diff --git a/src/daemon/screenshot-overlay.ts b/src/daemon/screenshot-overlay.ts index 51ba7f8049..7991ea0263 100644 --- a/src/daemon/screenshot-overlay.ts +++ b/src/daemon/screenshot-overlay.ts @@ -9,7 +9,7 @@ import { import type { PNG } from '../utils/png.ts'; import { decodePngAsync, encodePngAsync } from '../utils/png-worker-client.ts'; import { analyzeReactNativeOverlay } from '../core/react-native-overlay.ts'; -import { normalizeType } from '@agent-device/contracts/snapshot'; +import { isViewportRootNode, normalizeType } from '@agent-device/contracts/snapshot'; import { findNearestAncestor } from '../snapshot/snapshot-processing.ts'; import { resolveAndroidOverlaySourceRect } from './screenshot-overlay-android.ts'; import { hasPositiveRect, rectArea, rectContains } from './screenshot-overlay-rects.ts'; @@ -212,7 +212,7 @@ function isAndroidUnlabeledClickableSource( node: SnapshotNode, ): boolean { if (snapshot.backend !== 'android') return false; - if (!node.hittable || !hasPositiveRect(node.rect) || isViewportLikeNode(node)) return false; + if (!node.hittable || !hasPositiveRect(node.rect) || isViewportRootNode(node)) return false; const normalizedType = normalizeType(node.type ?? ''); if (ANDROID_UNLABELED_CLICKABLE_EXCLUDED_TYPES.some((type) => normalizedType.includes(type))) { return false; @@ -318,7 +318,7 @@ function projectRectToScreenshot( function resolveSnapshotBounds(nodes: SnapshotState['nodes']): Rect | null { let viewport: Rect | null = null; for (const node of nodes) { - if (!isViewportLikeNode(node) || !hasPositiveRect(node.rect)) continue; + if (!isViewportRootNode(node) || !hasPositiveRect(node.rect)) continue; if (!viewport || rectArea(node.rect) > rectArea(viewport)) { viewport = node.rect; } @@ -366,7 +366,7 @@ function hasActionableRole(node: SnapshotNode): boolean { } function isOverlayActionableNode(node: SnapshotNode): boolean { - return hasActionableRole(node) && !isViewportLikeNode(node); + return hasActionableRole(node) && !isViewportRootNode(node); } function isProxyOverlayNode(node: SnapshotNode): boolean { @@ -379,15 +379,8 @@ function isProxyOverlayNode(node: SnapshotNode): boolean { ); } -function isViewportLikeNode(node: Pick): boolean { - const roleText = [node.type, node.role, node.subrole] - .map((value) => normalizeType(value ?? '')) - .join(' '); - return roleText.includes('application') || roleText.includes('window'); -} - function isUsableOverlayTarget(node: SnapshotNode | null): node is SnapshotNode { - return Boolean(node?.rect && hasPositiveRect(node.rect) && !isViewportLikeNode(node)); + return Boolean(node?.rect && hasPositiveRect(node.rect) && !isViewportRootNode(node)); } function isMeaningfulSignal(value: string | undefined): boolean { diff --git a/src/snapshot/snapshot-occlusion.ts b/src/snapshot/snapshot-occlusion.ts index d24878bbfb..6f0d150ef5 100644 --- a/src/snapshot/snapshot-occlusion.ts +++ b/src/snapshot/snapshot-occlusion.ts @@ -2,7 +2,7 @@ import type { RawSnapshotNode, Rect } from '@agent-device/kernel/snapshot'; import { centerOfRect } from '@agent-device/kernel/snapshot'; import { areRectsApproximatelyEqual, normalizeRect } from '../utils/rect-center.ts'; import { containsPoint } from '@agent-device/kernel/rect'; -import { normalizeType } from '@agent-device/contracts/snapshot'; +import { normalizeType, isViewportRootNode } from '@agent-device/contracts/snapshot'; const COVERED_PRESENTATION_HINT = 'covered'; const OVERLAY_KIND_FRAGMENTS = [ @@ -220,7 +220,7 @@ function isOverlayLikeNode( options: SnapshotOcclusionOptions, ): boolean { if (!positiveRect(node.rect)) return false; - if (isViewportRoot(node)) return false; + if (isViewportRootNode(node)) return false; if (isFullViewportChromeContainer(node, byIndex)) return false; // This is a presentation-order heuristic: only known floating UI chrome should cover // later targets. Generic hittable containers can appear later without being visually on top. @@ -241,7 +241,7 @@ function isFullViewportChromeContainer( let current = typeof node.parentIndex === 'number' ? byIndex.get(node.parentIndex) : undefined; const visited = new Set(); while (current && !visited.has(current.index)) { - if (isViewportRoot(current)) { + if (isViewportRootNode(current)) { const viewportRect = positiveRect(current.rect); return Boolean(viewportRect && areRectsApproximatelyEqual(rect, viewportRect)); } @@ -295,7 +295,7 @@ function isRenderableAdditionalOverlayNode( return ( options.isAdditionalOverlayNode?.(node) === true && positiveRect(node.rect) !== null && - !isViewportRoot(node) + !isViewportRootNode(node) ); } @@ -321,11 +321,6 @@ function normalizeNodeKind(node: Pick normalizeType(value ?? '')).join(' '); } -function isViewportRoot(node: RawSnapshotNode): boolean { - const normalized = normalizeNodeKind(node); - return normalized.includes('application') || normalized.includes('window'); -} - function areRelatedSnapshotNodes( left: RawSnapshotNode, right: RawSnapshotNode, From fc5a332184bc470793672aa9e0ccb9612ae8e105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 5 Aug 2026 12:50:45 +0200 Subject: [PATCH 2/4] test(contracts): enumerate the macOS emitter's real vocabulary MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review found the table claimed to pin "the vocabulary each backend actually emits" while omitting most of it. `normalizedSnapshotType` has three output classes and only two were represented: 1. thirteen roles mapped to fixed short names — six were missing (StaticText, TextField, TextArea, MenuBarItem, Menu, MenuItem); 2. AXWindow, whose output is the SUBROLE unless it is AXStandardWindow; 3. the `default:` arm, `subrole ?? role`, emitting the raw AX-prefixed value for every unmapped role. All three are now enumerated, and the table asserts its own completeness against the emitter's fixed-output set — a role added to that switch without being added here fails, which is the emitter-drift protection the docblock was promising but not delivering. Re-measuring over the complete tables also corrected the header's own numbers. The claim was "71 of 73 agree, 2 disagree"; over 75 names it is 71 agree and FOUR disagree, because AXSystemDialog and AXUnknown were absent from the old table. Those two are the behavioral delta of this PR — an AXWindow whose subrole is emitted as the type, invisible to the six type-only spellings and named exactly by `role` — so the incomplete table had been hiding the very rows that justify reading role/subrole. The other two (AXFloatingWindow, AXSystemFloatingWindow) remain inert: only the `===` spelling misses them and its platform union is `android | ios`. --- .../src/snapshot-viewport-root.test.ts | 209 ++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 packages/contracts/src/snapshot-viewport-root.test.ts diff --git a/packages/contracts/src/snapshot-viewport-root.test.ts b/packages/contracts/src/snapshot-viewport-root.test.ts new file mode 100644 index 0000000000..774e13d074 --- /dev/null +++ b/packages/contracts/src/snapshot-viewport-root.test.ts @@ -0,0 +1,209 @@ +import { describe, expect, test } from 'vitest'; +import { isViewportRootNode } from './snapshot-visibility.ts'; + +/** + * `isViewportRootNode` pinned over the vocabulary each backend ACTUALLY emits + * into `node.type`/`role`/`subrole` — not over fixture strings. + * + * Nine hand-rolled spellings of this predicate existed before #1592's method was + * applied here: three normalizing `type|role|subrole`, five lowercasing or + * normalizing `type` alone, and one comparing the normalized type for EQUALITY. + * Measured over the 75 names in the three tables below they agree on 71. All + * four disagreements are macOS windows, and they split two ways: + * + * - `AXFloatingWindow` / `AXSystemFloatingWindow`: only the `===` spelling + * (maestro) misses them, and its platform union is `android | ios`, so it + * can never see a macOS node. Inert — the same shape #1592 found for its + * own role/subrole arm. + * - `AXSystemDialog` / `AXUnknown`: an `AXWindow` whose subrole is emitted AS + * the type, so the six type-only spellings cannot see they are windows while + * `role` names it exactly. These two are the entire behavioral delta of the + * consolidation, and they are windows by role. + * + * Grounding this in emitted vocabulary is what made the collapse provable; a + * fixture-string comparison would have reported divergences no backend can + * produce (#1592, second commit). + * + * Keep these tables honest against their emitters — a predicate that silently + * stops matching the root is invisible in every other test, because the root + * node is scenery in all of them. The macOS table asserts its own completeness + * against the emitter's fixed-output set below; iOS and Android do not, so a + * new `elementTypeName` case or a new Android container class still has to be + * added here by hand. + */ + +// apple/runner/.../RunnerTests+Snapshot.swift `elementTypeName` — a closed set. +// `Element(N)` is the escape hatch for element types the switch does not name. +const IOS_EMITTED_TYPES = [ + 'Application', + 'Window', + 'Button', + 'Cell', + 'StaticText', + 'TextField', + 'TextView', + 'SecureTextField', + 'Switch', + 'Slider', + 'Link', + 'Image', + 'NavigationBar', + 'TabBar', + 'CollectionView', + 'Table', + 'ScrollView', + 'Toolbar', + 'SearchField', + 'SegmentedControl', + 'Stepper', + 'Picker', + 'ActivityIndicator', + 'ProgressIndicator', + 'CheckBox', + 'MenuItem', + 'WebView', + 'Other', + 'Keyboard', + 'Key', + 'Element(72)', +] as const; + +// src/platforms/android/ui-hierarchy.ts — `type` is the uiautomator `class` +// attribute verbatim, a fully-qualified Java class name. +const ANDROID_EMITTED_TYPES = [ + 'android.view.View', + 'android.view.ViewGroup', + 'android.widget.Button', + 'android.widget.EditText', + 'android.widget.FrameLayout', + 'android.widget.HorizontalScrollView', + 'android.widget.ImageButton', + 'android.widget.ImageView', + 'android.widget.LinearLayout', + 'android.widget.ScrollView', + 'android.widget.SeekBar', + 'android.widget.Switch', + 'android.widget.TextView', + 'androidx.compose.ui.platform.ComposeView', + 'androidx.recyclerview.widget.RecyclerView', + 'android.widget.ListView', + 'android.widget.GridView', + 'androidx.core.widget.NestedScrollView', +] as const; + +// apple/macos-helper/.../SnapshotTraversal.swift `normalizedSnapshotType`. Three +// output classes, all represented below: +// 1. every one of the 13 roles the switch maps to a fixed short name; +// 2. `AXWindow`, whose output is the SUBROLE unless that subrole is +// `AXStandardWindow` — so a window's `type` can be any subrole string; +// 3. the `default:` arm, `subrole ?? role`, which emits the raw `AX`-prefixed +// value for every unmapped role. +// This backend is the only one that populates `role`/`subrole` at all. +const MACOS_EMITTED_NODES = [ + // 1. the complete fixed-output set. + { type: 'Application', role: 'AXApplication' }, + { type: 'Sheet', role: 'AXSheet' }, + { type: 'Dialog', role: 'AXDialog' }, + { type: 'Button', role: 'AXButton' }, + { type: 'StaticText', role: 'AXStaticText' }, + { type: 'TextField', role: 'AXTextField' }, + { type: 'TextArea', role: 'AXTextArea' }, + { type: 'ScrollArea', role: 'AXScrollArea' }, + { type: 'Group', role: 'AXGroup' }, + { type: 'MenuBar', role: 'AXMenuBar' }, + { type: 'MenuBarItem', role: 'AXMenuBarItem' }, + { type: 'Menu', role: 'AXMenu' }, + { type: 'MenuItem', role: 'AXMenuItem' }, + // 2. AXWindow: standard subrole collapses to "Window", every other subrole + // is emitted verbatim as the type. + { type: 'Window', role: 'AXWindow', subrole: 'AXStandardWindow' }, + { type: 'AXFloatingWindow', role: 'AXWindow', subrole: 'AXFloatingWindow' }, + { type: 'AXSystemFloatingWindow', role: 'AXWindow', subrole: 'AXSystemFloatingWindow' }, + { type: 'AXSystemDialog', role: 'AXWindow', subrole: 'AXSystemDialog' }, + { type: 'AXUnknown', role: 'AXWindow', subrole: 'AXUnknown' }, + // 3. the raw `subrole ?? role` fallback for unmapped roles. + { type: 'AXWebArea', role: 'AXWebArea' }, + { type: 'AXList', role: 'AXList' }, + { type: 'AXTable', role: 'AXTable' }, + { type: 'AXOutline', role: 'AXOutline' }, + { type: 'AXScrollBar', role: 'AXScrollBar' }, + { type: 'AXSplitGroup', role: 'AXSplitGroup' }, + { type: 'AXToolbar', role: 'AXToolbar' }, + { type: 'AXSearchField', role: 'AXTextField', subrole: 'AXSearchField' }, +] as const; + +/** + * The 13 fixed outputs above are the complete set `normalizedSnapshotType`'s + * switch can return, so a role added to that switch without being added here + * is the drift this table exists to catch. Asserted rather than trusted. + */ +const MACOS_FIXED_OUTPUTS = [ + 'Application', + 'Sheet', + 'Dialog', + 'Button', + 'StaticText', + 'TextField', + 'TextArea', + 'ScrollArea', + 'Group', + 'MenuBar', + 'MenuBarItem', + 'Menu', + 'MenuItem', +] as const; + +describe('isViewportRootNode over emitted backend vocabulary', () => { + test('iOS: exactly Application and Window, out of 31 emitted names', () => { + const roots = IOS_EMITTED_TYPES.filter((type) => isViewportRootNode({ type })); + expect(roots).toEqual(['Application', 'Window']); + }); + + test('iOS: substring and equality agree, so the collapse of the `===` spelling was a no-op', () => { + for (const type of IOS_EMITTED_TYPES) { + const equality = type === 'Application' || type === 'Window'; + expect({ type, root: isViewportRootNode({ type }) }).toEqual({ type, root: equality }); + } + }); + + // The load-bearing one. Android has no root node, so `resolveViewportRect`'s + // third fallback (largest containing rect of any node) is the only arm that + // ever returns on Android — and the resolvers that lack it return null there. + test('Android: no emitted class name is a viewport root', () => { + const roots = ANDROID_EMITTED_TYPES.filter((type) => isViewportRootNode({ type })); + expect(roots).toEqual([]); + }); + + test('macOS: every AXWindow subrole is a root, whatever `type` says', () => { + const roots = MACOS_EMITTED_NODES.filter(isViewportRootNode).map((node) => node.type); + expect(roots).toEqual([ + 'Application', + 'Window', + 'AXFloatingWindow', + 'AXSystemFloatingWindow', + 'AXSystemDialog', + 'AXUnknown', + ]); + }); + + // The table's own completeness claim, asserted rather than trusted: every + // fixed output the emitter's switch can return appears above, so adding a + // role to that switch without adding it here fails. + test('macOS: the table covers every fixed output normalizedSnapshotType returns', () => { + const covered = new Set(MACOS_EMITTED_NODES.map((node) => node.type)); + expect(MACOS_FIXED_OUTPUTS.filter((name) => !covered.has(name))).toEqual([]); + }); + + // Why the canonical predicate reads role/subrole and not `type` alone: these + // two are windows that no type-only spelling could see. + test('macOS: role rescues the two window subroles `type` alone cannot name', () => { + const typeOnly = (type: string) => { + const value = type.toLowerCase(); + return value.includes('application') || value.includes('window'); + }; + const rescued = MACOS_EMITTED_NODES.filter( + (node) => isViewportRootNode(node) && !typeOnly(node.type), + ).map((node) => node.type); + expect(rescued).toEqual(['AXSystemDialog', 'AXUnknown']); + }); +}); From 84fbb8a42efa452a6013aee6fc0282eee020efa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 5 Aug 2026 14:39:03 +0200 Subject: [PATCH 3/4] test(contracts): derive the macOS fixed-output set from the emitter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two test-validity defects from review, both real. The raw-fallback row `{ type: 'AXSearchField', role: 'AXTextField', subrole: 'AXSearchField' }` was unreachable: the `AXTextField` arm returns `TextField` whatever the subrole, so no emitter run can produce it. Replaced with `{ type: 'AXSortButton', role: 'AXCell', subrole: 'AXSortButton' }` — a subrole on a genuinely unmapped role, which is what the `subrole ?? role` default arm actually emits. `MACOS_FIXED_OUTPUTS` was a hand-kept twin compared against a hand-kept table, which is circular: a new mapped Swift role is absent from BOTH, so they agree and the gate stays green. The "emitter-drift protection" the docblock promised did not exist. The set is now parsed out of `normalizedSnapshotType` in SnapshotTraversal.swift, so the comparison is against the emitter rather than against a copy of the table's own assumptions. `case "AXWindow"` returns a subrole expression rather than a literal and is deliberately outside the literal-return set. Red evidence: adding `case "AXDisclosureTriangle": return "DisclosureTriangle"` to the Swift switch fails with `expected [ 'DisclosureTriangle' ] to deeply equal []`; 6 pass once reverted. The parser throws rather than silently matching nothing if the function is renamed or moved. --- .../src/snapshot-viewport-root.test.ts | 54 +++++++++++-------- .../corpus/authored/doubletap.yaml | 2 +- .../corpus/authored/extended-wait.yaml | 4 +- .../corpus/authored/repeat.yaml | 2 +- .../corpus/authored/runflow-child.yaml | 2 +- .../corpus/authored/runflow-main.yaml | 4 +- .../corpus/authored/scroll-until-visible.yaml | 2 +- .../bug-classes/percent-decimal-swipe.yaml | 4 +- .../corpus/bug-classes/retry-over-cap.yaml | 2 +- .../corpus/bug-classes/settle-after-tap.yaml | 2 +- .../target-swipe-missing-direction.yaml | 2 +- .../corpus/invalid/commands-not-a-list.yaml | 2 +- .../corpus/invalid/malformed-selector.yaml | 2 +- .../corpus/invalid/unknown-command.yaml | 2 +- .../invalid/unknown-selector-field.yaml | 2 +- .../upstream/001_assert_visible_by_id.yaml | 2 +- .../upstream/002_assert_visible_by_text.yaml | 2 +- .../corpus/upstream/008_tap_on_element.yaml | 2 +- .../upstream/009_skip_optional_elements.yaml | 6 +-- .../corpus/upstream/010_scroll.yaml | 2 +- .../corpus/upstream/011_back_press.yaml | 2 +- .../corpus/upstream/012_input_text.yaml | 4 +- .../corpus/upstream/013_launch_app.yaml | 2 +- .../corpus/upstream/014_tap_on_point.yaml | 2 +- .../corpus/upstream/017_swipe.yaml | 2 +- .../021_launch_app_with_clear_state.yaml | 2 +- .../upstream/026_assert_not_visible.yaml | 2 +- .../corpus/upstream/027_open_link.yaml | 2 +- .../upstream/029_long_press_on_element.yaml | 2 +- .../corpus/upstream/032_element_index.yaml | 2 +- .../corpus/upstream/034_press_key.yaml | 1 - .../corpus/upstream/039_hide_keyboard.yaml | 2 +- .../corpus/upstream/042_extended_wait.yaml | 2 +- .../corpus/upstream/053_repeat_times.yaml | 4 +- .../059_directional_swipe_command.yaml | 2 +- .../061_launchApp_withoutStopping.yaml | 2 +- .../corpus/upstream/062_copy_paste_text.yaml | 2 +- .../corpus/upstream/067_assertTrue_pass.yaml | 2 +- .../069_wait_for_animation_to_end.yaml | 2 +- .../074_directional_swipe_element.yaml | 2 +- .../upstream/076_optional_assertion.yaml | 8 +-- .../corpus/upstream/078_swipe_relative.yaml | 6 +-- .../upstream/079_scroll_until_visible.yaml | 4 +- .../upstream/114_child_of_selector.yaml | 8 +-- ...120_tap_on_element_retryTapIfNoChange.yaml | 4 +- .../corpus/upstream/131_setPermissions.yaml | 2 +- 46 files changed, 93 insertions(+), 84 deletions(-) diff --git a/packages/contracts/src/snapshot-viewport-root.test.ts b/packages/contracts/src/snapshot-viewport-root.test.ts index 774e13d074..faf382941b 100644 --- a/packages/contracts/src/snapshot-viewport-root.test.ts +++ b/packages/contracts/src/snapshot-viewport-root.test.ts @@ -1,4 +1,9 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; import { describe, expect, test } from 'vitest'; + +const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../..'); import { isViewportRootNode } from './snapshot-visibility.ts'; /** @@ -129,29 +134,32 @@ const MACOS_EMITTED_NODES = [ { type: 'AXScrollBar', role: 'AXScrollBar' }, { type: 'AXSplitGroup', role: 'AXSplitGroup' }, { type: 'AXToolbar', role: 'AXToolbar' }, - { type: 'AXSearchField', role: 'AXTextField', subrole: 'AXSearchField' }, + // A subrole on an UNMAPPED role: the default arm returns `subrole ?? role`, so the + // subrole wins. (`AXTextField` cannot appear here — its own arm always returns + // `TextField`, whatever the subrole.) + { type: 'AXSortButton', role: 'AXCell', subrole: 'AXSortButton' }, ] as const; /** - * The 13 fixed outputs above are the complete set `normalizedSnapshotType`'s - * switch can return, so a role added to that switch without being added here - * is the drift this table exists to catch. Asserted rather than trusted. + * The emitter's fixed outputs, READ FROM THE EMITTER — not a second hand-kept + * list. A hand-kept twin compared against a hand-kept table is circular: both + * drift together and the comparison stays green (#1613 review). Parsing the + * Swift switch is what makes "a role added there fails here" a real claim. */ -const MACOS_FIXED_OUTPUTS = [ - 'Application', - 'Sheet', - 'Dialog', - 'Button', - 'StaticText', - 'TextField', - 'TextArea', - 'ScrollArea', - 'Group', - 'MenuBar', - 'MenuBarItem', - 'Menu', - 'MenuItem', -] as const; +function macosFixedOutputsFromEmitter(): string[] { + const swift = fs.readFileSync( + path.join( + REPO_ROOT, + 'apple/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift', + ), + 'utf8', + ); + const fn = /private func normalizedSnapshotType\([\s\S]*?\n\}/.exec(swift)?.[0]; + if (!fn) throw new Error('normalizedSnapshotType not found — the emitter moved; fix this parser'); + // `case "AXWindow":` returns a subrole expression, not a literal, so it is + // deliberately absent from the literal-return set the table pins. + return [...fn.matchAll(/return "([A-Za-z]+)"/g)].map((match) => match[1]!).sort(); +} describe('isViewportRootNode over emitted backend vocabulary', () => { test('iOS: exactly Application and Window, out of 31 emitted names', () => { @@ -189,9 +197,11 @@ describe('isViewportRootNode over emitted backend vocabulary', () => { // The table's own completeness claim, asserted rather than trusted: every // fixed output the emitter's switch can return appears above, so adding a // role to that switch without adding it here fails. - test('macOS: the table covers every fixed output normalizedSnapshotType returns', () => { - const covered = new Set(MACOS_EMITTED_NODES.map((node) => node.type)); - expect(MACOS_FIXED_OUTPUTS.filter((name) => !covered.has(name))).toEqual([]); + test('macOS: the table covers every fixed output the Swift emitter can return', () => { + const covered = new Set(MACOS_EMITTED_NODES.map((node) => node.type)); + const emitted = macosFixedOutputsFromEmitter(); + expect(emitted.length).toBeGreaterThan(10); + expect(emitted.filter((name) => !covered.has(name))).toEqual([]); }); // Why the canonical predicate reads role/subrole and not `type` alone: these diff --git a/scripts/maestro-conformance/corpus/authored/doubletap.yaml b/scripts/maestro-conformance/corpus/authored/doubletap.yaml index 4e5c0e1ec6..c587fe7686 100644 --- a/scripts/maestro-conformance/corpus/authored/doubletap.yaml +++ b/scripts/maestro-conformance/corpus/authored/doubletap.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- doubleTapOn: "Button" +- doubleTapOn: 'Button' diff --git a/scripts/maestro-conformance/corpus/authored/extended-wait.yaml b/scripts/maestro-conformance/corpus/authored/extended-wait.yaml index 9008d12961..f548f1a367 100644 --- a/scripts/maestro-conformance/corpus/authored/extended-wait.yaml +++ b/scripts/maestro-conformance/corpus/authored/extended-wait.yaml @@ -2,9 +2,9 @@ appId: com.example.app --- - extendedWaitUntil: visible: - id: "Item" + id: 'Item' timeout: 1000 - extendedWaitUntil: notVisible: - id: "Another" + id: 'Another' timeout: 1000 diff --git a/scripts/maestro-conformance/corpus/authored/repeat.yaml b/scripts/maestro-conformance/corpus/authored/repeat.yaml index a94ff58426..02a7ba32d8 100644 --- a/scripts/maestro-conformance/corpus/authored/repeat.yaml +++ b/scripts/maestro-conformance/corpus/authored/repeat.yaml @@ -3,4 +3,4 @@ appId: com.example.app - repeat: times: 3 commands: - - tapOn: "Button" + - tapOn: 'Button' diff --git a/scripts/maestro-conformance/corpus/authored/runflow-child.yaml b/scripts/maestro-conformance/corpus/authored/runflow-child.yaml index 9e122a1669..988afa98e5 100644 --- a/scripts/maestro-conformance/corpus/authored/runflow-child.yaml +++ b/scripts/maestro-conformance/corpus/authored/runflow-child.yaml @@ -2,4 +2,4 @@ appId: com.example.include --- - launchApp - tapOn: - id: "included-button" + id: 'included-button' diff --git a/scripts/maestro-conformance/corpus/authored/runflow-main.yaml b/scripts/maestro-conformance/corpus/authored/runflow-main.yaml index 85f2abe856..3a4706b79f 100644 --- a/scripts/maestro-conformance/corpus/authored/runflow-main.yaml +++ b/scripts/maestro-conformance/corpus/authored/runflow-main.yaml @@ -1,5 +1,5 @@ appId: com.example.app --- -- tapOn: "Before" +- tapOn: 'Before' - runFlow: runflow-child.yaml -- tapOn: "After" +- tapOn: 'After' diff --git a/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml b/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml index c578a3b9d6..0d3a50db2b 100644 --- a/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml +++ b/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml @@ -2,6 +2,6 @@ appId: com.example.app --- - scrollUntilVisible: element: - text: "Test" + text: 'Test' direction: DOWN timeout: 10000 diff --git a/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml b/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml index b02bd06d8b..10bbbb6e69 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml @@ -3,5 +3,5 @@ appId: com.example.app --- - swipe: - start: "50.5%, 50%" - end: "10%, 50%" + start: '50.5%, 50%' + end: '10%, 50%' diff --git a/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml b/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml index 1fe6fa092d..d15a686610 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml @@ -6,4 +6,4 @@ appId: com.example.app - retry: maxRetries: 99 commands: - - tapOn: "Retry" + - tapOn: 'Retry' diff --git a/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml b/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml index 05650fa666..2bbc1e15a8 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml @@ -4,4 +4,4 @@ # same name (no reflectable upstream constant exists). appId: com.example.app --- -- tapOn: "Submit" +- tapOn: 'Submit' diff --git a/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml b/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml index a7398af0bb..b19fccc0f4 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml @@ -4,4 +4,4 @@ appId: com.example.app --- - swipe: from: - id: "row" + id: 'row' diff --git a/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml b/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml index ae3b78c13c..709775b991 100644 --- a/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml +++ b/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml @@ -1,4 +1,4 @@ # The command document must be a sequence. appId: com.example.app --- -tapOn: "Button" +tapOn: 'Button' diff --git a/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml b/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml index ecf2d9aa21..aeac7d86fa 100644 --- a/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml +++ b/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml @@ -2,4 +2,4 @@ appId: com.example.app --- - tapOn: - - text: "Button" + - text: 'Button' diff --git a/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml b/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml index 2b6b7c1a13..9affff9131 100644 --- a/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml +++ b/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml @@ -1,4 +1,4 @@ # Upstream rejects an unknown command name (typo of tapOn). appId: com.example.app --- -- tapOnn: "Button" +- tapOnn: 'Button' diff --git a/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml b/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml index e81afabd6f..365a16ed29 100644 --- a/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml +++ b/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml @@ -2,5 +2,5 @@ appId: com.example.app --- - tapOn: - text: "Button" + text: 'Button' bogusField: true diff --git a/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml b/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml index 5552e4dbaf..bfc66a8acf 100644 --- a/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml +++ b/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - assertVisible: - id: "element_id" \ No newline at end of file + id: 'element_id' diff --git a/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml b/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml index 4d05bf0588..d7cf9a2b1b 100644 --- a/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml +++ b/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - assertVisible: - text: "Element Text" \ No newline at end of file + text: 'Element Text' diff --git a/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml b/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml index 2590342340..33aa62537a 100644 --- a/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml +++ b/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - tapOn: - text: ".*button.*" \ No newline at end of file + text: '.*button.*' diff --git a/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml b/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml index 66ef9c30d7..9d01c737cd 100644 --- a/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml +++ b/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml @@ -1,8 +1,8 @@ appId: com.example.app --- - tapOn: - text: "Optional Element" + text: 'Optional Element' optional: true - assertVisible: - text: "Non Optional" - optional: false \ No newline at end of file + text: 'Non Optional' + optional: false diff --git a/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml b/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml index bd91ecc5c6..92f0b14cc0 100644 --- a/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml +++ b/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- scroll \ No newline at end of file +- scroll diff --git a/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml b/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml index cd7d0c53d2..26ee785a0d 100644 --- a/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml +++ b/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- back \ No newline at end of file +- back diff --git a/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml b/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml index 2ba2ee5e55..0c44ea92d8 100644 --- a/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml +++ b/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- -- inputText: "Hello World" -- inputText: user@example.com \ No newline at end of file +- inputText: 'Hello World' +- inputText: user@example.com diff --git a/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml b/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml index e98c0c42bf..4a888fc014 100644 --- a/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml +++ b/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- launchApp \ No newline at end of file +- launchApp diff --git a/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml b/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml index 061a370abd..c75fb92de1 100644 --- a/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml +++ b/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - tapOn: - point: 100,200 \ No newline at end of file + point: 100,200 diff --git a/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml b/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml index c358ecb692..dc1063320a 100644 --- a/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml +++ b/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml @@ -3,4 +3,4 @@ appId: com.example.app - swipe: start: 100,500 end: 100,200 - duration: 3000 \ No newline at end of file + duration: 3000 diff --git a/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml b/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml index 46ce97e75d..f849b74aa1 100644 --- a/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml +++ b/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - launchApp: - clearState: true \ No newline at end of file + clearState: true diff --git a/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml b/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml index 42cc239707..ecff4d90be 100644 --- a/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml +++ b/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - assertNotVisible: - id: "element_id" \ No newline at end of file + id: 'element_id' diff --git a/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml b/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml index b5c8d1b6b8..28b84ef717 100644 --- a/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml +++ b/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- openLink: https://example.com \ No newline at end of file +- openLink: https://example.com diff --git a/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml b/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml index 440f73d7f9..54de0fa453 100644 --- a/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml +++ b/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - longPressOn: - text: ".*button.*" \ No newline at end of file + text: '.*button.*' diff --git a/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml b/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml index 7e7b2abf32..ac0543bbff 100644 --- a/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml +++ b/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml @@ -7,4 +7,4 @@ appId: com.example.app - tapOn: text: Item.* index: ${0 + 1} - retryTapIfNoChange: false \ No newline at end of file + retryTapIfNoChange: false diff --git a/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml b/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml index 8d72550e76..b2f50f7e01 100644 --- a/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml +++ b/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml @@ -29,4 +29,3 @@ appId: com.example.app - pressKey: TV Input HDMI 1 - pressKey: TV Input HDMI 2 - pressKey: TV Input HDMI 3 - diff --git a/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml b/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml index 93ab4f21ed..b8da72f9f3 100644 --- a/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml +++ b/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- hideKeyboard \ No newline at end of file +- hideKeyboard diff --git a/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml b/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml index dd58226fa4..debd135452 100644 --- a/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml +++ b/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml @@ -1,6 +1,6 @@ appId: com.example.app env: - TIMEOUT: 1000 + TIMEOUT: 1000 --- - extendedWaitUntil: visible: Item diff --git a/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml b/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml index e2859cb86a..872b772d7e 100644 --- a/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml +++ b/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml @@ -4,10 +4,10 @@ appId: com.other.app times: 3 commands: - tapOn: Button -- assertVisible: "3" +- assertVisible: '3' - evalScript: ${output.list = [1, 2, 3]} - repeat: times: ${output.list.length} commands: - tapOn: Button -- assertVisible: "6" \ No newline at end of file +- assertVisible: '6' diff --git a/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml b/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml index c548e5468b..0766cd4749 100644 --- a/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml +++ b/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml @@ -2,4 +2,4 @@ appId: com.example.app --- - swipe: direction: RIGHT - duration: 500 \ No newline at end of file + duration: 500 diff --git a/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml b/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml index 11e1a83481..0b1fd853ac 100644 --- a/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml +++ b/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - launchApp: - stopApp: false \ No newline at end of file + stopApp: false diff --git a/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml b/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml index 4c05431f73..c13139b734 100644 --- a/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml +++ b/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml @@ -1,5 +1,5 @@ appId: com.example.app --- - copyTextFrom: - id: "myId" + id: 'myId' - pasteText diff --git a/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml b/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml index abad0c5099..ee6d31a413 100644 --- a/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml +++ b/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- assertTrue: ${1+1} \ No newline at end of file +- assertTrue: ${1+1} diff --git a/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml b/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml index e8832c37ad..ec51736203 100644 --- a/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml +++ b/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - waitForAnimationToEnd: - timeout: 500 \ No newline at end of file + timeout: 500 diff --git a/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml b/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml index 604241ce93..0756e9c43e 100644 --- a/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml +++ b/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml @@ -3,4 +3,4 @@ appId: com.example.app - swipe: direction: RIGHT from: - text: "swiping element" + text: 'swiping element' diff --git a/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml b/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml index 50329a9223..e6130d6791 100644 --- a/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml +++ b/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml @@ -3,16 +3,16 @@ appId: com.example.app - scrollUntilVisible: timeout: 1 element: - id: "not_found" + id: 'not_found' optional: true - assertTrue: - condition: "false" + condition: 'false' optional: true - extendedWaitUntil: visible: - id: "not_found" + id: 'not_found' timeout: 1 optional: true - assertVisible: - text: "Button" + text: 'Button' optional: true diff --git a/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml b/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml index 18d45d3f8c..8733876cb6 100644 --- a/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml +++ b/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml @@ -1,6 +1,6 @@ appId: com.example.app --- - swipe: - start: "50%,30%" - end: "50%,60%" - duration: 3000 \ No newline at end of file + start: '50%,30%' + end: '50%,60%' + duration: 3000 diff --git a/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml b/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml index 539b380e39..5042b30547 100644 --- a/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml +++ b/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml @@ -2,8 +2,8 @@ appId: com.example.app --- - scrollUntilVisible: element: - text: "Test" + text: 'Test' speed: 100 visibilityPercentage: 100 direction: DOWN - timeout: 10 \ No newline at end of file + timeout: 10 diff --git a/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml b/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml index 3ab7d1906c..049d22bb44 100644 --- a/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml +++ b/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml @@ -1,10 +1,10 @@ appId: com.example.app --- - assertVisible: - text: "child_id" + text: 'child_id' childOf: - text: "parent_id_1" + text: 'parent_id_1' - assertNotVisible: - text: "child_id" + text: 'child_id' childOf: - text: "parent_id_3" \ No newline at end of file + text: 'parent_id_3' diff --git a/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml b/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml index b6b57ebec5..a6b6c0ccc7 100644 --- a/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml +++ b/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml @@ -1,5 +1,5 @@ appId: com.example.app --- - tapOn: - text: ".*button.*" - retryTapIfNoChange: true \ No newline at end of file + text: '.*button.*' + retryTapIfNoChange: true diff --git a/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml b/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml index 483fc1a5c9..2ac17d6752 100644 --- a/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml +++ b/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml @@ -3,4 +3,4 @@ appId: com.example.app - setPermissions: permissions: all: deny - notifications: unset \ No newline at end of file + notifications: unset From 7e9e8bf3fc9506880283dccf1ddd9b9927328c2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Wed, 5 Aug 2026 16:44:06 +0200 Subject: [PATCH 4/4] chore: restore maestro conformance corpus to main 45 corpus YAMLs carried an unrelated quote-style churn ("Button" -> 'Button'). They were already modified in the worktree when this branch started and a `git add -A` swept them into the predicate commit. Nothing in this PR reads them. Restored verbatim to main. --- .../maestro-conformance/corpus/authored/doubletap.yaml | 2 +- .../corpus/authored/extended-wait.yaml | 4 ++-- scripts/maestro-conformance/corpus/authored/repeat.yaml | 2 +- .../corpus/authored/runflow-child.yaml | 2 +- .../maestro-conformance/corpus/authored/runflow-main.yaml | 4 ++-- .../corpus/authored/scroll-until-visible.yaml | 2 +- .../corpus/bug-classes/percent-decimal-swipe.yaml | 4 ++-- .../corpus/bug-classes/retry-over-cap.yaml | 2 +- .../corpus/bug-classes/settle-after-tap.yaml | 2 +- .../bug-classes/target-swipe-missing-direction.yaml | 2 +- .../corpus/invalid/commands-not-a-list.yaml | 2 +- .../corpus/invalid/malformed-selector.yaml | 2 +- .../corpus/invalid/unknown-command.yaml | 2 +- .../corpus/invalid/unknown-selector-field.yaml | 2 +- .../corpus/upstream/001_assert_visible_by_id.yaml | 2 +- .../corpus/upstream/002_assert_visible_by_text.yaml | 2 +- .../corpus/upstream/008_tap_on_element.yaml | 2 +- .../corpus/upstream/009_skip_optional_elements.yaml | 6 +++--- .../maestro-conformance/corpus/upstream/010_scroll.yaml | 2 +- .../corpus/upstream/011_back_press.yaml | 2 +- .../corpus/upstream/012_input_text.yaml | 4 ++-- .../corpus/upstream/013_launch_app.yaml | 2 +- .../corpus/upstream/014_tap_on_point.yaml | 2 +- .../maestro-conformance/corpus/upstream/017_swipe.yaml | 2 +- .../corpus/upstream/021_launch_app_with_clear_state.yaml | 2 +- .../corpus/upstream/026_assert_not_visible.yaml | 2 +- .../corpus/upstream/027_open_link.yaml | 2 +- .../corpus/upstream/029_long_press_on_element.yaml | 2 +- .../corpus/upstream/032_element_index.yaml | 2 +- .../corpus/upstream/034_press_key.yaml | 1 + .../corpus/upstream/039_hide_keyboard.yaml | 2 +- .../corpus/upstream/042_extended_wait.yaml | 2 +- .../corpus/upstream/053_repeat_times.yaml | 4 ++-- .../corpus/upstream/059_directional_swipe_command.yaml | 2 +- .../corpus/upstream/061_launchApp_withoutStopping.yaml | 2 +- .../corpus/upstream/062_copy_paste_text.yaml | 2 +- .../corpus/upstream/067_assertTrue_pass.yaml | 2 +- .../corpus/upstream/069_wait_for_animation_to_end.yaml | 2 +- .../corpus/upstream/074_directional_swipe_element.yaml | 2 +- .../corpus/upstream/076_optional_assertion.yaml | 8 ++++---- .../corpus/upstream/078_swipe_relative.yaml | 6 +++--- .../corpus/upstream/079_scroll_until_visible.yaml | 4 ++-- .../corpus/upstream/114_child_of_selector.yaml | 8 ++++---- .../upstream/120_tap_on_element_retryTapIfNoChange.yaml | 4 ++-- .../corpus/upstream/131_setPermissions.yaml | 2 +- 45 files changed, 62 insertions(+), 61 deletions(-) diff --git a/scripts/maestro-conformance/corpus/authored/doubletap.yaml b/scripts/maestro-conformance/corpus/authored/doubletap.yaml index c587fe7686..4e5c0e1ec6 100644 --- a/scripts/maestro-conformance/corpus/authored/doubletap.yaml +++ b/scripts/maestro-conformance/corpus/authored/doubletap.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- doubleTapOn: 'Button' +- doubleTapOn: "Button" diff --git a/scripts/maestro-conformance/corpus/authored/extended-wait.yaml b/scripts/maestro-conformance/corpus/authored/extended-wait.yaml index f548f1a367..9008d12961 100644 --- a/scripts/maestro-conformance/corpus/authored/extended-wait.yaml +++ b/scripts/maestro-conformance/corpus/authored/extended-wait.yaml @@ -2,9 +2,9 @@ appId: com.example.app --- - extendedWaitUntil: visible: - id: 'Item' + id: "Item" timeout: 1000 - extendedWaitUntil: notVisible: - id: 'Another' + id: "Another" timeout: 1000 diff --git a/scripts/maestro-conformance/corpus/authored/repeat.yaml b/scripts/maestro-conformance/corpus/authored/repeat.yaml index 02a7ba32d8..a94ff58426 100644 --- a/scripts/maestro-conformance/corpus/authored/repeat.yaml +++ b/scripts/maestro-conformance/corpus/authored/repeat.yaml @@ -3,4 +3,4 @@ appId: com.example.app - repeat: times: 3 commands: - - tapOn: 'Button' + - tapOn: "Button" diff --git a/scripts/maestro-conformance/corpus/authored/runflow-child.yaml b/scripts/maestro-conformance/corpus/authored/runflow-child.yaml index 988afa98e5..9e122a1669 100644 --- a/scripts/maestro-conformance/corpus/authored/runflow-child.yaml +++ b/scripts/maestro-conformance/corpus/authored/runflow-child.yaml @@ -2,4 +2,4 @@ appId: com.example.include --- - launchApp - tapOn: - id: 'included-button' + id: "included-button" diff --git a/scripts/maestro-conformance/corpus/authored/runflow-main.yaml b/scripts/maestro-conformance/corpus/authored/runflow-main.yaml index 3a4706b79f..85f2abe856 100644 --- a/scripts/maestro-conformance/corpus/authored/runflow-main.yaml +++ b/scripts/maestro-conformance/corpus/authored/runflow-main.yaml @@ -1,5 +1,5 @@ appId: com.example.app --- -- tapOn: 'Before' +- tapOn: "Before" - runFlow: runflow-child.yaml -- tapOn: 'After' +- tapOn: "After" diff --git a/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml b/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml index 0d3a50db2b..c578a3b9d6 100644 --- a/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml +++ b/scripts/maestro-conformance/corpus/authored/scroll-until-visible.yaml @@ -2,6 +2,6 @@ appId: com.example.app --- - scrollUntilVisible: element: - text: 'Test' + text: "Test" direction: DOWN timeout: 10000 diff --git a/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml b/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml index 10bbbb6e69..b02bd06d8b 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/percent-decimal-swipe.yaml @@ -3,5 +3,5 @@ appId: com.example.app --- - swipe: - start: '50.5%, 50%' - end: '10%, 50%' + start: "50.5%, 50%" + end: "10%, 50%" diff --git a/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml b/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml index d15a686610..1fe6fa092d 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/retry-over-cap.yaml @@ -6,4 +6,4 @@ appId: com.example.app - retry: maxRetries: 99 commands: - - tapOn: 'Retry' + - tapOn: "Retry" diff --git a/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml b/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml index 2bbc1e15a8..05650fa666 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/settle-after-tap.yaml @@ -4,4 +4,4 @@ # same name (no reflectable upstream constant exists). appId: com.example.app --- -- tapOn: 'Submit' +- tapOn: "Submit" diff --git a/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml b/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml index b19fccc0f4..a7398af0bb 100644 --- a/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml +++ b/scripts/maestro-conformance/corpus/bug-classes/target-swipe-missing-direction.yaml @@ -4,4 +4,4 @@ appId: com.example.app --- - swipe: from: - id: 'row' + id: "row" diff --git a/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml b/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml index 709775b991..ae3b78c13c 100644 --- a/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml +++ b/scripts/maestro-conformance/corpus/invalid/commands-not-a-list.yaml @@ -1,4 +1,4 @@ # The command document must be a sequence. appId: com.example.app --- -tapOn: 'Button' +tapOn: "Button" diff --git a/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml b/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml index aeac7d86fa..ecf2d9aa21 100644 --- a/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml +++ b/scripts/maestro-conformance/corpus/invalid/malformed-selector.yaml @@ -2,4 +2,4 @@ appId: com.example.app --- - tapOn: - - text: 'Button' + - text: "Button" diff --git a/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml b/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml index 9affff9131..2b6b7c1a13 100644 --- a/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml +++ b/scripts/maestro-conformance/corpus/invalid/unknown-command.yaml @@ -1,4 +1,4 @@ # Upstream rejects an unknown command name (typo of tapOn). appId: com.example.app --- -- tapOnn: 'Button' +- tapOnn: "Button" diff --git a/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml b/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml index 365a16ed29..e81afabd6f 100644 --- a/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml +++ b/scripts/maestro-conformance/corpus/invalid/unknown-selector-field.yaml @@ -2,5 +2,5 @@ appId: com.example.app --- - tapOn: - text: 'Button' + text: "Button" bogusField: true diff --git a/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml b/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml index bfc66a8acf..5552e4dbaf 100644 --- a/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml +++ b/scripts/maestro-conformance/corpus/upstream/001_assert_visible_by_id.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - assertVisible: - id: 'element_id' + id: "element_id" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml b/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml index d7cf9a2b1b..4d05bf0588 100644 --- a/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml +++ b/scripts/maestro-conformance/corpus/upstream/002_assert_visible_by_text.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - assertVisible: - text: 'Element Text' + text: "Element Text" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml b/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml index 33aa62537a..2590342340 100644 --- a/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml +++ b/scripts/maestro-conformance/corpus/upstream/008_tap_on_element.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - tapOn: - text: '.*button.*' + text: ".*button.*" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml b/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml index 9d01c737cd..66ef9c30d7 100644 --- a/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml +++ b/scripts/maestro-conformance/corpus/upstream/009_skip_optional_elements.yaml @@ -1,8 +1,8 @@ appId: com.example.app --- - tapOn: - text: 'Optional Element' + text: "Optional Element" optional: true - assertVisible: - text: 'Non Optional' - optional: false + text: "Non Optional" + optional: false \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml b/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml index 92f0b14cc0..bd91ecc5c6 100644 --- a/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml +++ b/scripts/maestro-conformance/corpus/upstream/010_scroll.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- scroll +- scroll \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml b/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml index 26ee785a0d..cd7d0c53d2 100644 --- a/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml +++ b/scripts/maestro-conformance/corpus/upstream/011_back_press.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- back +- back \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml b/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml index 0c44ea92d8..2ba2ee5e55 100644 --- a/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml +++ b/scripts/maestro-conformance/corpus/upstream/012_input_text.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- -- inputText: 'Hello World' -- inputText: user@example.com +- inputText: "Hello World" +- inputText: user@example.com \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml b/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml index 4a888fc014..e98c0c42bf 100644 --- a/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml +++ b/scripts/maestro-conformance/corpus/upstream/013_launch_app.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- launchApp +- launchApp \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml b/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml index c75fb92de1..061a370abd 100644 --- a/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml +++ b/scripts/maestro-conformance/corpus/upstream/014_tap_on_point.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - tapOn: - point: 100,200 + point: 100,200 \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml b/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml index dc1063320a..c358ecb692 100644 --- a/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml +++ b/scripts/maestro-conformance/corpus/upstream/017_swipe.yaml @@ -3,4 +3,4 @@ appId: com.example.app - swipe: start: 100,500 end: 100,200 - duration: 3000 + duration: 3000 \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml b/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml index f849b74aa1..46ce97e75d 100644 --- a/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml +++ b/scripts/maestro-conformance/corpus/upstream/021_launch_app_with_clear_state.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - launchApp: - clearState: true + clearState: true \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml b/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml index ecff4d90be..42cc239707 100644 --- a/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml +++ b/scripts/maestro-conformance/corpus/upstream/026_assert_not_visible.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - assertNotVisible: - id: 'element_id' + id: "element_id" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml b/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml index 28b84ef717..b5c8d1b6b8 100644 --- a/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml +++ b/scripts/maestro-conformance/corpus/upstream/027_open_link.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- openLink: https://example.com +- openLink: https://example.com \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml b/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml index 54de0fa453..440f73d7f9 100644 --- a/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml +++ b/scripts/maestro-conformance/corpus/upstream/029_long_press_on_element.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - longPressOn: - text: '.*button.*' + text: ".*button.*" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml b/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml index ac0543bbff..7e7b2abf32 100644 --- a/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml +++ b/scripts/maestro-conformance/corpus/upstream/032_element_index.yaml @@ -7,4 +7,4 @@ appId: com.example.app - tapOn: text: Item.* index: ${0 + 1} - retryTapIfNoChange: false + retryTapIfNoChange: false \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml b/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml index b2f50f7e01..8d72550e76 100644 --- a/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml +++ b/scripts/maestro-conformance/corpus/upstream/034_press_key.yaml @@ -29,3 +29,4 @@ appId: com.example.app - pressKey: TV Input HDMI 1 - pressKey: TV Input HDMI 2 - pressKey: TV Input HDMI 3 + diff --git a/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml b/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml index b8da72f9f3..93ab4f21ed 100644 --- a/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml +++ b/scripts/maestro-conformance/corpus/upstream/039_hide_keyboard.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- hideKeyboard +- hideKeyboard \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml b/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml index debd135452..dd58226fa4 100644 --- a/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml +++ b/scripts/maestro-conformance/corpus/upstream/042_extended_wait.yaml @@ -1,6 +1,6 @@ appId: com.example.app env: - TIMEOUT: 1000 + TIMEOUT: 1000 --- - extendedWaitUntil: visible: Item diff --git a/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml b/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml index 872b772d7e..e2859cb86a 100644 --- a/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml +++ b/scripts/maestro-conformance/corpus/upstream/053_repeat_times.yaml @@ -4,10 +4,10 @@ appId: com.other.app times: 3 commands: - tapOn: Button -- assertVisible: '3' +- assertVisible: "3" - evalScript: ${output.list = [1, 2, 3]} - repeat: times: ${output.list.length} commands: - tapOn: Button -- assertVisible: '6' +- assertVisible: "6" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml b/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml index 0766cd4749..c548e5468b 100644 --- a/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml +++ b/scripts/maestro-conformance/corpus/upstream/059_directional_swipe_command.yaml @@ -2,4 +2,4 @@ appId: com.example.app --- - swipe: direction: RIGHT - duration: 500 + duration: 500 \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml b/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml index 0b1fd853ac..11e1a83481 100644 --- a/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml +++ b/scripts/maestro-conformance/corpus/upstream/061_launchApp_withoutStopping.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - launchApp: - stopApp: false + stopApp: false \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml b/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml index c13139b734..4c05431f73 100644 --- a/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml +++ b/scripts/maestro-conformance/corpus/upstream/062_copy_paste_text.yaml @@ -1,5 +1,5 @@ appId: com.example.app --- - copyTextFrom: - id: 'myId' + id: "myId" - pasteText diff --git a/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml b/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml index ee6d31a413..abad0c5099 100644 --- a/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml +++ b/scripts/maestro-conformance/corpus/upstream/067_assertTrue_pass.yaml @@ -1,3 +1,3 @@ appId: com.example.app --- -- assertTrue: ${1+1} +- assertTrue: ${1+1} \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml b/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml index ec51736203..e8832c37ad 100644 --- a/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml +++ b/scripts/maestro-conformance/corpus/upstream/069_wait_for_animation_to_end.yaml @@ -1,4 +1,4 @@ appId: com.example.app --- - waitForAnimationToEnd: - timeout: 500 + timeout: 500 \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml b/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml index 0756e9c43e..604241ce93 100644 --- a/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml +++ b/scripts/maestro-conformance/corpus/upstream/074_directional_swipe_element.yaml @@ -3,4 +3,4 @@ appId: com.example.app - swipe: direction: RIGHT from: - text: 'swiping element' + text: "swiping element" diff --git a/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml b/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml index e6130d6791..50329a9223 100644 --- a/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml +++ b/scripts/maestro-conformance/corpus/upstream/076_optional_assertion.yaml @@ -3,16 +3,16 @@ appId: com.example.app - scrollUntilVisible: timeout: 1 element: - id: 'not_found' + id: "not_found" optional: true - assertTrue: - condition: 'false' + condition: "false" optional: true - extendedWaitUntil: visible: - id: 'not_found' + id: "not_found" timeout: 1 optional: true - assertVisible: - text: 'Button' + text: "Button" optional: true diff --git a/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml b/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml index 8733876cb6..18d45d3f8c 100644 --- a/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml +++ b/scripts/maestro-conformance/corpus/upstream/078_swipe_relative.yaml @@ -1,6 +1,6 @@ appId: com.example.app --- - swipe: - start: '50%,30%' - end: '50%,60%' - duration: 3000 + start: "50%,30%" + end: "50%,60%" + duration: 3000 \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml b/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml index 5042b30547..539b380e39 100644 --- a/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml +++ b/scripts/maestro-conformance/corpus/upstream/079_scroll_until_visible.yaml @@ -2,8 +2,8 @@ appId: com.example.app --- - scrollUntilVisible: element: - text: 'Test' + text: "Test" speed: 100 visibilityPercentage: 100 direction: DOWN - timeout: 10 + timeout: 10 \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml b/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml index 049d22bb44..3ab7d1906c 100644 --- a/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml +++ b/scripts/maestro-conformance/corpus/upstream/114_child_of_selector.yaml @@ -1,10 +1,10 @@ appId: com.example.app --- - assertVisible: - text: 'child_id' + text: "child_id" childOf: - text: 'parent_id_1' + text: "parent_id_1" - assertNotVisible: - text: 'child_id' + text: "child_id" childOf: - text: 'parent_id_3' + text: "parent_id_3" \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml b/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml index a6b6c0ccc7..b6b57ebec5 100644 --- a/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml +++ b/scripts/maestro-conformance/corpus/upstream/120_tap_on_element_retryTapIfNoChange.yaml @@ -1,5 +1,5 @@ appId: com.example.app --- - tapOn: - text: '.*button.*' - retryTapIfNoChange: true + text: ".*button.*" + retryTapIfNoChange: true \ No newline at end of file diff --git a/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml b/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml index 2ac17d6752..483fc1a5c9 100644 --- a/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml +++ b/scripts/maestro-conformance/corpus/upstream/131_setPermissions.yaml @@ -3,4 +3,4 @@ appId: com.example.app - setPermissions: permissions: all: deny - notifications: unset + notifications: unset \ No newline at end of file