@irsprs/mobwright 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/fixtures/device.ts","../src/appium/session.ts","../src/types.ts","../src/appium/capabilities.ts","../src/device/selectors.ts","../src/utils/retry.ts","../src/device/locator.ts","../src/device/tree.ts","../src/ai/errors.ts","../src/device/ai-locator.ts","../src/device/device.ts","../src/ai/openai.ts","../src/ai/prompts.ts","../src/ai/deepseek.ts","../src/ai/anthropic.ts","../src/ai/factory.ts","../src/appium/ios-utils.ts","../src/expect/matchers.ts","../src/config.ts"],"sourcesContent":["// Test runner\nexport { test } from './fixtures/device.js';\nexport { expect } from './expect/matchers.js';\n\n// Config helpers\nexport { defineConfig } from './config.js';\n\n// Types and enums\nexport { Platform } from './types.js';\nexport type {\n MobwrightConfig,\n ProjectConfig,\n DeviceConfig,\n DeviceProvider,\n AIConfig,\n AIProviderName,\n} from './types.js';\n\n// Device + Locator\nexport { Device } from './fixtures/device.js';\nexport { Locator } from './device/locator.js';\nexport type { LocatorOptions } from './device/locator.js';\n\n// AI — interface + providers + factory\nexport type { AIProvider } from './ai/provider.js';\nexport type {\n ResolveLocatorInput,\n ResolveLocatorResult,\n SelectorStrategy,\n ProviderConfig,\n} from './ai/types.js';\nexport {\n AIError,\n AIRequestError,\n AIResponseError,\n AIValidationError,\n} from './ai/errors.js';\nexport { OpenAICompatibleProvider } from './ai/openai.js';\nexport { DeepSeekProvider } from './ai/deepseek.js';\nexport { AnthropicProvider } from './ai/anthropic.js';\nexport { createProvider } from './ai/factory.js';\n\n// AILocator\nexport { AILocator } from './device/ai-locator.js';\nexport type { AILocatorOptions } from './device/ai-locator.js';\n\n// Tree utilities (for advanced users)\nexport {\n getCleanTree,\n cleanTree,\n fitTreeToBudget,\n estimateTokens,\n DEFAULT_TREE_CHAR_BUDGET,\n} from './device/tree.js';","import { test as base } from '@playwright/test';\nimport { createSession, destroySession } from '../appium/session.js';\nimport { Platform, type ProjectConfig } from '../types.js';\nimport { Device } from '../device/device.js';\nimport type { AIConfig, AIProviderName } from '../types.js';\nimport { createProvider } from '../ai/factory.js';\nimport type { AIProvider } from '../ai/provider.js';\nimport { isIOSAppInstalled } from '../appium/ios-utils.js';\n\n/**\n * Project name → ProjectConfig.\n *\n * Until we have a proper config-loading story (Weekend 6), we synthesize the\n * ProjectConfig from environment variables based on the Playwright project name.\n *\n * Required env vars:\n * MOBWRIGHT_APP_PATH - path to .apk or .app\n * MOBWRIGHT_ANDROID_AVD - AVD name for the 'android' project (optional, default: from env or fallback)\n * MOBWRIGHT_IOS_DEVICE - simulator name for the 'ios' project (optional)\n */\nfunction resolveProjectConfig(projectName: string): ProjectConfig {\n if (projectName === 'android') {\n const appPath = process.env.MOBWRIGHT_APP_PATH;\n if (!appPath) {\n throw new Error(\n 'MOBWRIGHT_APP_PATH is not set. Point it at your .apk:\\n' +\n ' MOBWRIGHT_APP_PATH=/absolute/path/to/app.apk',\n );\n }\n\n const avd = process.env.MOBWRIGHT_ANDROID_AVD;\n if (!avd) {\n throw new Error(\n 'MOBWRIGHT_ANDROID_AVD is not set. Set your emulator AVD name:\\n' +\n ' MOBWRIGHT_ANDROID_AVD=Pixel_9_Pro_API_Baklava',\n );\n }\n\n return {\n name: 'android',\n use: {\n platform: Platform.ANDROID,\n device: { provider: 'emulator', name: avd },\n buildPath: appPath,\n appActivity: process.env.MOBWRIGHT_ANDROID_APP_ACTIVITY,\n appPackage: process.env.MOBWRIGHT_ANDROID_APP_PACKAGE,\n },\n };\n }\n\n if (projectName === 'ios') {\n const sim = process.env.MOBWRIGHT_IOS_DEVICE ?? 'iPhone 15';\n const bundleId = process.env.MOBWRIGHT_IOS_BUNDLE_ID;\n const appPath = process.env.MOBWRIGHT_IOS_APP_PATH;\n\n if (!bundleId && !appPath) {\n throw new Error(\n 'iOS needs MOBWRIGHT_IOS_BUNDLE_ID, MOBWRIGHT_IOS_APP_PATH, or both:\\n' +\n ' MOBWRIGHT_IOS_BUNDLE_ID=com.voila.id\\n' +\n ' MOBWRIGHT_IOS_APP_PATH=/absolute/path/to/Voila.app',\n );\n }\n\n return {\n name: 'ios',\n use: {\n platform: Platform.IOS,\n device: { provider: 'simulator', name: sim },\n buildPath: appPath ?? '',\n bundleId,\n platformVersion: process.env.MOBWRIGHT_IOS_PLATFORM_VERSION,\n udid: process.env.MOBWRIGHT_IOS_UDID,\n },\n };\n }\n\n throw new Error(`Unknown mobwright project: ${projectName}. Use 'android' or 'ios'.`);\n}\n\n/**\n * For iOS, decide whether to install the .app or just launch by bundleId.\n * If both are provided and the app is already installed, skip the install\n * (faster + avoids Rosetta re-translation pain).\n */\nasync function reconcileIOSInstall(project: ProjectConfig): Promise<ProjectConfig> {\n if (project.use.platform !== Platform.IOS) return project;\n if (!project.use.bundleId) return project;\n\n const installed = await isIOSAppInstalled(project.use.bundleId, project.use.udid);\n\n if (installed) {\n // Already installed — drop the buildPath so Appium doesn't reinstall.\n // eslint-disable-next-line no-console\n console.log(\n `[mobwright] ${project.use.bundleId} already installed — launching by bundleId`,\n );\n return {\n ...project,\n use: { ...project.use, buildPath: '' },\n };\n }\n\n // Not installed — require a buildPath to install from.\n if (!project.use.buildPath) {\n throw new Error(\n `iOS app ${project.use.bundleId} is not installed on the simulator and ` +\n `MOBWRIGHT_IOS_APP_PATH is not set. Either install the app manually ` +\n `or provide MOBWRIGHT_IOS_APP_PATH.`,\n );\n }\n\n // eslint-disable-next-line no-console\n console.log(\n `[mobwright] ${project.use.bundleId} not installed — installing from ${project.use.buildPath}`,\n );\n return project;\n}\n\nexport const test = base.extend<{ device: Device }>({\n // eslint-disable-next-line no-empty-pattern\n device: async ({}, use, testInfo) => {\n let projectConfig = resolveProjectConfig(testInfo.project.name);\n projectConfig = await reconcileIOSInstall(projectConfig);\n\n const aiConfig = resolveAIConfig();\n const aiProvider: AIProvider | undefined = aiConfig\n ? createProvider(aiConfig)\n : undefined;\n\n const browser = await createSession(projectConfig);\n const device = new Device(browser, projectConfig, aiProvider);\n\n try {\n await use(device);\n } finally {\n await destroySession(browser);\n }\n },\n});\n\nexport { Device };\n\n/**\n * Build an AIConfig from environment variables, if any AI env vars are set.\n * Returns undefined if AI is not configured — making it opt-in.\n *\n * Env vars:\n * MOBWRIGHT_AI_PROVIDER — 'anthropic' | 'openai' | 'deepseek'\n * MOBWRIGHT_AI_API_KEY — provider API key\n * MOBWRIGHT_AI_MODEL — optional override of default model\n * MOBWRIGHT_AI_BASE_URL — optional API base URL override\n */\nfunction resolveAIConfig(): AIConfig | undefined {\n const provider = process.env.MOBWRIGHT_AI_PROVIDER as AIProviderName | undefined;\n const apiKey = process.env.MOBWRIGHT_AI_API_KEY;\n\n if (!provider || !apiKey) return undefined;\n\n if (provider !== 'anthropic' && provider !== 'openai' && provider !== 'deepseek') {\n throw new Error(\n `MOBWRIGHT_AI_PROVIDER must be one of 'anthropic', 'openai', 'deepseek' — got '${provider}'`,\n );\n }\n\n return {\n provider,\n model: process.env.MOBWRIGHT_AI_MODEL ?? '',\n apiKey,\n baseURL: process.env.MOBWRIGHT_AI_BASE_URL,\n };\n}","import { remote, type Browser } from 'webdriverio';\nimport type { ProjectConfig } from '../types.js';\nimport { buildCapabilities } from './capabilities.js';\n\nexport interface SessionOptions {\n /** Appium server hostname. Default: 127.0.0.1 */\n hostname?: string;\n /** Appium server port. Default: 4723 */\n port?: number;\n /** Appium server path. Default: / */\n path?: string;\n /** Log level for wdio. Default: 'warn' to keep test output clean. */\n logLevel?: 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'silent';\n}\n\n/**\n * Create a new Appium session for a project.\n * Caller is responsible for calling destroySession() to clean up.\n */\nexport async function createSession(\n project: ProjectConfig,\n options: SessionOptions = {},\n): Promise<Browser> {\n const capabilities = buildCapabilities(project);\n\n return await remote({\n hostname: options.hostname ?? '127.0.0.1',\n port: options.port ?? 4723,\n path: options.path ?? '/',\n logLevel: options.logLevel ?? 'warn',\n capabilities,\n });\n}\n\n/**\n * Cleanly destroy an Appium session.\n * Swallows errors during teardown so they don't mask test failures.\n */\nexport async function destroySession(browser: Browser): Promise<void> {\n try {\n await browser.deleteSession();\n } catch (err) {\n // eslint-disable-next-line no-console\n console.warn('[mobwright] session teardown error (ignored):', err);\n }\n}","/**\n * Supported mobile platforms.\n */\nexport enum Platform {\n ANDROID = 'android',\n IOS = 'ios',\n}\n\n/**\n * Where the device comes from.\n * v0.1: emulator (Android) | simulator (iOS)\n * v0.2+: real-device, browserstack, lambdatest, etc.\n */\nexport type DeviceProvider = 'emulator' | 'simulator';\n\n/**\n * Device configuration for a project.\n */\nexport interface DeviceConfig {\n provider: DeviceProvider;\n /** Emulator AVD name (Android) or simulator device name (iOS) */\n name: string;\n}\n\n/**\n * Configuration for a single project (platform + device combo).\n */\nexport interface ProjectConfig {\n name: string;\n use: {\n platform: Platform;\n device: DeviceConfig;\n buildPath: string;\n /** Android only: explicit launcher activity */\n appActivity?: string;\n /** Android only: explicit app package */\n appPackage?: string;\n /** iOS only: bundle id (launches installed app without needing buildPath) */\n bundleId?: string;\n /** iOS only: platform version (e.g. \"17.4\") */\n platformVersion?: string;\n /** iOS only: simulator UDID (faster than deviceName matching) */\n udid?: string;\n actionTimeout?: number;\n };\n}\n\n/**\n * AI provider name. v0.1 supports these three.\n */\nexport type AIProviderName = 'anthropic' | 'openai' | 'deepseek';\n\n/**\n * AI configuration.\n */\nexport interface AIConfig {\n provider: AIProviderName;\n model: string;\n apiKey: string;\n /** Optional override of the base URL. */\nbaseURL?: string;\n /** Optional timeout in ms. */\n timeout?: number;\n}\n\n/**\n * Top-level mobwright configuration.\n */\nexport interface MobwrightConfig {\n projects: ProjectConfig[];\n ai?: AIConfig;\n /** Default timeout for all locator actions in ms */\n actionTimeout?: number;\n}\n","import { Platform, type ProjectConfig } from '../types.js';\n\n/**\n * Build Appium capabilities for a project.\n *\n * Reference:\n * - https://appium.io/docs/en/latest/guides/caps/\n * - https://github.com/appium/appium-uiautomator2-driver\n * - https://github.com/appium/appium-xcuitest-driver\n */\nexport function buildCapabilities(project: ProjectConfig): Record<string, unknown> {\n \n const { platform, device, buildPath } = project.use;\n\n const base: Record<string, unknown> = {\n // W3C capabilities require platformName as a top-level property\n platformName: platformNameFor(platform),\n };\n\n if (platform === Platform.ANDROID) {\n const caps: Record<string, unknown> = {\n ...base,\n 'appium:automationName': 'UiAutomator2',\n 'appium:deviceName': device.name,\n 'appium:avd': device.name,\n ...(buildPath ? { 'appium:app': resolveAppPath(buildPath) } : {}),\n 'appium:autoGrantPermissions': true,\n 'appium:avdLaunchTimeout': 120000,\n 'appium:avdReadyTimeout': 120000,\n 'appium:noReset': false,\n 'appium:fullReset': false,\n 'appium:enforceAppInstall': false,\n 'appium:clearSystemFiles': true,\n // Wait for any activity to become foreground (handles onboarding/splash screens)\n 'appium:appWaitActivity': '*',\n 'appium:appWaitDuration': 30000,\n };\n\n if (project.use.appActivity) {\n caps['appium:appActivity'] = project.use.appActivity;\n }\n if (project.use.appPackage) {\n caps['appium:appPackage'] = project.use.appPackage;\n }\n\n return caps;\n }\n\n if (platform === Platform.IOS) {\n const caps: Record<string, unknown> = {\n ...base,\n 'appium:automationName': 'XCUITest',\n 'appium:deviceName': device.name,\n 'appium:autoAcceptAlerts': true,\n 'appium:noReset': false,\n 'appium:newCommandTimeout': 120,\n // Give the simulator time to launch Rosetta-translated apps\n 'appium:appActivateTimeout': 120000,\n 'appium:wdaLaunchTimeout': 180000,\n 'appium:wdaConnectionTimeout': 180000,\n 'appium:waitForQuiescenceTimeout': 60000,\n };\n\n // The fixture decides whether to set 'app' (install) or 'bundleId' (launch only).\n if (buildPath) {\n caps['appium:app'] = resolveAppPath(buildPath);\n }\n if (project.use.bundleId) {\n caps['appium:bundleId'] = project.use.bundleId;\n }\n if (project.use.platformVersion) {\n caps['appium:platformVersion'] = project.use.platformVersion;\n }\n if (project.use.udid) {\n caps['appium:udid'] = project.use.udid;\n }\n\n return caps;\n }\n\n throw new Error(`Unsupported platform: ${String(platform)}`);\n}\n\nfunction platformNameFor(platform: Platform): string {\n switch (platform) {\n case Platform.ANDROID:\n return 'Android';\n case Platform.IOS:\n return 'iOS';\n default:\n throw new Error(`Unknown platform: ${String(platform)}`);\n }\n}\n\n/**\n * Resolve `~` and relative paths in the app path so Appium receives an absolute path.\n */\nfunction resolveAppPath(p: string): string {\n if (p.startsWith('~')) {\n const home = process.env.HOME ?? '';\n return p.replace(/^~/, home);\n }\n if (p.startsWith('./') || p.startsWith('../')) {\n return new URL(p, `file://${process.cwd()}/`).pathname;\n }\n return p;\n}","import { Platform } from '../types.js';\n\nexport type SelectorStrategy = 'accessibility-id' | 'id' | 'xpath' | 'text';\n\nexport interface ParsedSelector {\n strategy: SelectorStrategy;\n value: string;\n}\n\n/**\n * Parse a mobwright selector into a strategy + value.\n *\n * Syntax:\n * '~foo' → accessibility id (content-desc on Android, name on iOS)\n * '#foo' → resource id \"ends with\" match (Android) or name (iOS)\n * '//...' → xpath escape hatch\n * 'foo' → accessibility id by default\n */\nexport function parseSelector(selector: string): ParsedSelector {\n if (selector.startsWith('~')) {\n return { strategy: 'accessibility-id', value: selector.slice(1) };\n }\n if (selector.startsWith('#')) {\n return { strategy: 'id', value: selector.slice(1) };\n }\n if (selector.startsWith('//') || selector.startsWith('(')) {\n return { strategy: 'xpath', value: selector };\n }\n return { strategy: 'accessibility-id', value: selector };\n}\n\n/**\n * Convert a parsed selector into a webdriverio selector string.\n *\n * For Android, '#foo' matches any element whose resource-id ends with \":id/foo\"\n * — so users don't have to type the full package prefix.\n */\nexport function toWdioSelector(parsed: ParsedSelector, platform: Platform): string {\n switch (parsed.strategy) {\n case 'accessibility-id':\n return `~${parsed.value}`;\n\n case 'id':\n if (platform === Platform.ANDROID) {\n // Android resource-id ends-with match (forgives package prefix)\n return `//*[contains(@resource-id, \":id/${parsed.value}\") or @resource-id=\"${parsed.value}\"]`;\n }\n // iOS uses 'name' attribute\n return `//*[@name=\"${parsed.value}\"]`;\n\n case 'xpath':\n return parsed.value;\n\n case 'text':\n if (platform === Platform.ANDROID) {\n return `//*[@text=\"${parsed.value}\"]`;\n }\n return `//*[@label=\"${parsed.value}\" or @name=\"${parsed.value}\" or @value=\"${parsed.value}\"]`;\n\n default:\n throw new Error(`Unknown selector strategy: ${String((parsed as ParsedSelector).strategy)}`);\n }\n}","/**\n * Poll a function until it returns a truthy value or times out.\n *\n * @param fn - async function to retry. Return a non-null value on success.\n * @param options.timeout - max time in ms (default 5000)\n * @param options.interval - delay between polls in ms (default 100)\n * @param options.message - error message on timeout\n *\n * @returns the truthy value the function returned\n * @throws Error if timeout elapses without success\n */\nexport async function pollUntil<T>(\n fn: () => Promise<T | null | undefined | false>,\n options: {\n timeout?: number;\n interval?: number;\n message?: string;\n } = {},\n): Promise<T> {\n const timeout = options.timeout ?? 5000;\n const interval = options.interval ?? 100;\n const message = options.message ?? `pollUntil timed out after ${timeout}ms`;\n\n const deadline = Date.now() + timeout;\n let lastError: unknown;\n\n while (Date.now() < deadline) {\n try {\n const result = await fn();\n if (result) return result as T;\n } catch (err) {\n lastError = err;\n }\n await sleep(interval);\n }\n\n const causeMsg = lastError instanceof Error ? `: ${lastError.message}` : '';\n throw new Error(`${message}${causeMsg}`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}","import type { Browser, ChainablePromiseElement } from 'webdriverio';\nimport { parseSelector, toWdioSelector } from './selectors.js';\nimport { pollUntil } from '../utils/retry.js';\nimport { Platform } from '../types.js';\n\nexport interface LocatorOptions {\n /** Max time in ms to wait for the element to be actionable. Default 5000. */\n timeout?: number;\n}\n\n/**\n * A lazy reference to a UI element.\n *\n * Locators don't query the device when created — only when an action runs.\n * Every action auto-waits up to `timeout` (default 5s) for the element to\n * be present + visible + enabled before failing.\n *\n * @example\n * await device.locator('~loginButton').tap();\n * const text = await device.locator('~welcome').getText();\n */\nexport class Locator {\n readonly selector: string;\n private readonly browser: Browser;\n private readonly platform: Platform;\n private readonly defaultTimeout: number;\n\n constructor(\n browser: Browser,\n platform: Platform,\n selector: string,\n options: LocatorOptions = {},\n ) {\n this.browser = browser;\n this.platform = platform;\n this.selector = selector;\n this.defaultTimeout = options.timeout ?? 5000;\n }\n\n /**\n * Wait for the element to be present in the tree AND displayed.\n * Returns the wdio element handle once ready.\n */\n private async waitForElement(timeout = this.defaultTimeout): Promise<ChainablePromiseElement> {\n const wdioSelector = toWdioSelector(parseSelector(this.selector), this.platform);\n\n return pollUntil(\n async () => {\n const el = this.browser.$(wdioSelector);\n const exists = await el.isExisting();\n if (!exists) return null;\n const displayed = await el.isDisplayed();\n if (!displayed) return null;\n const enabled = await el.isEnabled();\n if (!enabled) return null;\n return el;\n },\n {\n timeout,\n message: `Locator \"${this.selector}\" not found or not visible after ${timeout}ms`,\n },\n );\n }\n\n /**\n * Tap (click) the element. Auto-waits until visible + enabled.\n */\n async tap(): Promise<void> {\n const el = await this.waitForElement();\n await el.click();\n }\n\n /**\n * Type text into the element. Auto-waits until visible.\n * Clears existing content first.\n */\n async fill(text: string): Promise<void> {\n const el = await this.waitForElement();\n await el.clearValue();\n await el.setValue(text);\n }\n\n /**\n * Get the visible text of the element.\n */\n async getText(): Promise<string> {\n const el = await this.waitForElement();\n return (await el.getText()) ?? '';\n }\n\n /**\n * Check if the element is currently visible (no waiting).\n * Returns false if not present.\n */\n async isVisible(): Promise<boolean> {\n try {\n const wdioSelector = toWdioSelector(parseSelector(this.selector), this.platform);\n const el = this.browser.$(wdioSelector);\n if (!(await el.isExisting())) return false;\n return await el.isDisplayed();\n } catch {\n return false;\n }\n }\n\n /**\n * Wait for the element to become visible.\n * Useful for explicit waits without performing an action.\n */\n async waitFor(options: { timeout?: number } = {}): Promise<void> {\n await this.waitForElement(options.timeout);\n }\n\n /**\n * Press and hold the element for `duration` ms (long-press).\n * Default duration: 1000 ms.\n */\n async tapAndHold(options: { duration?: number } = {}): Promise<void> {\n const el = await this.waitForElement();\n const duration = options.duration ?? 1000;\n\n const location = await el.getLocation();\n const size = await el.getSize();\n const x = Math.round(location.x + size.width / 2);\n const y = Math.round(location.y + size.height / 2);\n\n await this.browser.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x, y, origin: 'viewport' },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await this.browser.releaseActions();\n }\n\n // ─── Swipe gestures ────────────────────────────────────────────────────────\n\n /** Swipe left within the element bounds. */\n async swipeLeft(options: { duration?: number; distance?: number } = {}): Promise<void> {\n await this._swipe('left', options);\n }\n\n /** Swipe right within the element bounds. */\n async swipeRight(options: { duration?: number; distance?: number } = {}): Promise<void> {\n await this._swipe('right', options);\n }\n\n /** Swipe up within the element bounds. */\n async swipeUp(options: { duration?: number; distance?: number } = {}): Promise<void> {\n await this._swipe('up', options);\n }\n\n /** Swipe down within the element bounds. */\n async swipeDown(options: { duration?: number; distance?: number } = {}): Promise<void> {\n await this._swipe('down', options);\n }\n\n // ─── Scroll gestures ───────────────────────────────────────────────────────\n\n /**\n * Scroll up inside a scrollable container.\n * Slower and longer than swipeUp — use on lists/pages.\n */\n async scrollUp(options: { duration?: number; distance?: number } = {}): Promise<void> {\n await this._scroll('up', options);\n }\n\n /**\n * Scroll down inside a scrollable container.\n * Slower and longer than swipeDown — use on lists/pages.\n */\n async scrollDown(options: { duration?: number; distance?: number } = {}): Promise<void> {\n await this._scroll('down', options);\n }\n\n /**\n * Check if the element is currently enabled (not greyed out / disabled).\n * Returns false if not present or not enabled.\n * Does NOT auto-wait — for retry behavior, use expect(locator).toBeEnabled().\n */\n async isEnabled(): Promise<boolean> {\n try {\n const wdioSelector = toWdioSelector(parseSelector(this.selector), this.platform);\n const el = this.browser.$(wdioSelector);\n if (!(await el.isExisting())) return false;\n return await el.isEnabled();\n } catch {\n return false;\n }\n }\n\n // ─── Private helpers ───────────────────────────────────────────────────────\n\n private async _swipe(\n direction: 'left' | 'right' | 'up' | 'down',\n options: { duration?: number; distance?: number } = {},\n ): Promise<void> {\n const el = await this.waitForElement();\n const duration = options.duration ?? 400;\n const distance = options.distance ?? 0.7;\n\n if (this.platform === Platform.IOS) {\n const elementId = await el.elementId;\n await this.browser.execute('mobile: swipe', { direction, element: elementId });\n return;\n }\n\n await this._performSwipeAction(el, direction, duration, distance);\n }\n\n private async _scroll(\n direction: 'up' | 'down',\n options: { duration?: number; distance?: number } = {},\n ): Promise<void> {\n const el = await this.waitForElement();\n const duration = options.duration ?? 800;\n const distance = options.distance ?? 0.8;\n\n if (this.platform === Platform.IOS) {\n const elementId = await el.elementId;\n await this.browser.execute('mobile: scroll', { direction, element: elementId });\n return;\n }\n\n await this._performSwipeAction(el, direction, duration, distance);\n }\n\n private async _performSwipeAction(\n el: ChainablePromiseElement,\n direction: 'left' | 'right' | 'up' | 'down',\n duration: number,\n distance: number,\n ): Promise<void> {\n const location = await el.getLocation();\n const size = await el.getSize();\n\n const cx = Math.round(location.x + size.width / 2);\n const cy = Math.round(location.y + size.height / 2);\n const dx = Math.round(size.width * distance * 0.5);\n const dy = Math.round(size.height * distance * 0.5);\n\n const coords: Record<'left' | 'right' | 'up' | 'down', [number, number, number, number]> = {\n left: [cx + dx, cy, cx - dx, cy],\n right: [cx - dx, cy, cx + dx, cy],\n up: [cx, cy + dy, cx, cy - dy],\n down: [cx, cy - dy, cx, cy + dy],\n };\n\n const [startX, startY, endX, endY] = coords[direction];\n\n await this.browser.performActions([\n {\n type: 'pointer',\n id: 'finger1',\n parameters: { pointerType: 'touch' },\n actions: [\n { type: 'pointerMove', duration: 0, x: startX, y: startY, origin: 'viewport' },\n { type: 'pointerDown', button: 0 },\n { type: 'pause', duration: 100 },\n { type: 'pointerMove', duration, x: endX, y: endY, origin: 'viewport' },\n { type: 'pointerUp', button: 0 },\n ],\n },\n ]);\n await this.browser.releaseActions();\n }\n}","import type { Browser } from 'webdriverio';\nimport { Platform } from '../types.js';\n\n/**\n * Get a cleaned-up accessibility tree for AI consumption.\n *\n * Returns serialized XML with:\n * - Layout/coordinate noise stripped\n * - false-valued boolean attributes removed\n * - empty attributes removed\n *\n * Intent: a much smaller payload that still preserves selector-relevant info\n * (text, content-desc, resource-id, class, name, label, value).\n */\nexport async function getCleanTree(browser: Browser, platform: Platform): Promise<string> {\n const raw = await browser.getPageSource();\n return cleanTree(raw, platform);\n}\n\n/**\n * Pure function — exported for unit testing.\n */\nexport function cleanTree(raw: string, platform: Platform): string {\n let cleaned = raw;\n\n if (platform === Platform.ANDROID) {\n // Strip layout-only attributes\n cleaned = cleaned.replace(/\\s+bounds=\"[^\"]*\"/g, '');\n cleaned = cleaned.replace(/\\s+index=\"[^\"]*\"/g, '');\n cleaned = cleaned.replace(/\\s+package=\"[^\"]*\"/g, '');\n cleaned = cleaned.replace(/\\s+(checkable|long-clickable|password|scrollable|selected|checked)=\"false\"/g, '');\n cleaned = cleaned.replace(/\\s+focusable=\"false\"/g, '');\n cleaned = cleaned.replace(/\\s+focused=\"false\"/g, '');\n cleaned = cleaned.replace(/\\s+enabled=\"true\"/g, '');\n cleaned = cleaned.replace(/\\s+displayed=\"true\"/g, '');\n // Empty text attribute\n cleaned = cleaned.replace(/\\s+text=\"\"/g, '');\n cleaned = cleaned.replace(/\\s+content-desc=\"\"/g, '');\n cleaned = cleaned.replace(/\\s+resource-id=\"\"/g, '');\n } else {\n // iOS: strip x/y/width/height/visible/enabled if default\n cleaned = cleaned.replace(/\\s+(x|y|width|height)=\"[^\"]*\"/g, '');\n cleaned = cleaned.replace(/\\s+enabled=\"true\"/g, '');\n cleaned = cleaned.replace(/\\s+visible=\"true\"/g, '');\n cleaned = cleaned.replace(/\\s+accessible=\"true\"/g, '');\n cleaned = cleaned.replace(/\\s+name=\"\"/g, '');\n cleaned = cleaned.replace(/\\s+label=\"\"/g, '');\n cleaned = cleaned.replace(/\\s+value=\"\"/g, '');\n }\n\n // Collapse multiple spaces inside opening tags\n cleaned = cleaned.replace(/\\s{2,}/g, ' ');\n\n // Remove the XML declaration — provider doesn't care\n cleaned = cleaned.replace(/^<\\?xml[^?]*\\?>\\s*/, '');\n\n return cleaned.trim();\n}\n\n/** Approximate chars per token. Rough but useful: 1 token ≈ 4 chars. */\nconst CHARS_PER_TOKEN = 4;\n\n/** Conservative tree budget — leaves room for system prompt + user prompt + response. */\nexport const DEFAULT_TREE_CHAR_BUDGET = 12_000; // ~3000 tokens\n\n/**\n * Truncate a tree to fit within a character budget.\n * Preference: keep the start of the tree (usually contains the main interactive content).\n * Logs a warning if truncation happens.\n */\nexport function fitTreeToBudget(\n tree: string,\n budget: number = DEFAULT_TREE_CHAR_BUDGET,\n): string {\n if (tree.length <= budget) return tree;\n\n // eslint-disable-next-line no-console\n console.warn(\n `[mobwright] accessibility tree (${tree.length} chars) exceeds budget ` +\n `(${budget} chars). Truncating — AI may miss elements beyond the cutoff.`,\n );\n\n return tree.slice(0, budget) + '\\n<!-- ...truncated -->';\n}\n\n/** Estimate tokens for a string. Rough but useful for cost forecasting. */\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / CHARS_PER_TOKEN);\n}","/**\n * Base class for all AI-related errors so callers can catch them generically.\n */\nexport class AIError extends Error {\n constructor(\n message: string,\n public override readonly cause?: unknown,\n ) {\n super(message);\n this.name = 'AIError';\n }\n}\n\n/** The provider's HTTP call failed or timed out. */\nexport class AIRequestError extends AIError {\n constructor(message: string, cause?: unknown) {\n super(message, cause);\n this.name = 'AIRequestError';\n }\n}\n\n/** The provider returned a response we couldn't parse. */\nexport class AIResponseError extends AIError {\n constructor(\n message: string,\n public readonly raw?: string,\n cause?: unknown,\n ) {\n super(message, cause);\n this.name = 'AIResponseError';\n }\n}\n\n/** The provider's response didn't meet our validation criteria. */\nexport class AIValidationError extends AIError {\n constructor(message: string, cause?: unknown) {\n super(message, cause);\n this.name = 'AIValidationError';\n }\n}","import type { Browser } from 'webdriverio';\nimport { Locator, type LocatorOptions } from './locator.js';\nimport { getCleanTree, fitTreeToBudget } from './tree.js';\nimport type { AIProvider } from '../ai/provider.js';\nimport type { ResolveLocatorResult } from '../ai/types.js';\nimport { Platform } from '../types.js';\nimport { AIError, AIValidationError } from '../ai/errors.js';\n\nexport interface AILocatorOptions extends LocatorOptions {\n /**\n * Minimum confidence the AI must return for us to trust the resolution.\n * Below this, we throw with a clear error rather than silently using a bad selector.\n * Default: 0.5.\n */\n minConfidence?: number;\n /**\n * Max characters of the accessibility tree to send to the AI.\n * Default: 12,000 (~3000 tokens).\n */\n treeCharBudget?: number;\n}\n\n/**\n * A natural-language locator. Lazily resolves an English description to a\n * concrete selector via the configured AI provider, then delegates to a\n * regular Locator for the actual action.\n *\n * Resolution happens once per AILocator instance. Subsequent actions reuse\n * the cached selector.\n *\n * @example\n * await device.ai('the Continue button').tap();\n * await device.ai('the email input').fill('me@example.com');\n */\nexport class AILocator {\n readonly description: string;\n private readonly browser: Browser;\n private readonly platform: Platform;\n private readonly provider: AIProvider;\n private readonly options: Required<Omit<AILocatorOptions, 'timeout'>> & {\n timeout: number;\n };\n\n /** Cached resolution. Populated on first action. */\n private resolved: ResolveLocatorResult | null = null;\n /** Cached underlying Locator. Populated on first action. */\n private locator: Locator | null = null;\n\n constructor(\n browser: Browser,\n platform: Platform,\n provider: AIProvider,\n description: string,\n options: AILocatorOptions = {},\n ) {\n this.browser = browser;\n this.platform = platform;\n this.provider = provider;\n this.description = description;\n this.options = {\n timeout: options.timeout ?? 5000,\n minConfidence: options.minConfidence ?? 0.5,\n treeCharBudget: options.treeCharBudget ?? 12_000,\n };\n }\n\n /**\n * Resolve the description to a Locator. Called automatically on first action.\n * Exposed for advanced use / debugging.\n */\n async resolve(): Promise<Locator> {\n if (this.locator) return this.locator;\n\n const tree = await getCleanTree(this.browser, this.platform);\n const bounded = fitTreeToBudget(tree, this.options.treeCharBudget);\n\n const result = await this.provider.resolveLocator({\n description: this.description,\n accessibilityTree: bounded,\n platform: this.platform,\n });\n\n this.validate(result);\n\n this.resolved = result;\n const selector = this.buildSelectorString(result);\n this.locator = new Locator(this.browser, this.platform, selector, {\n timeout: this.options.timeout,\n });\n\n // eslint-disable-next-line no-console\n console.log(\n `[mobwright.ai] \"${this.description}\" → ${selector} ` +\n `(${result.strategy}, confidence ${result.confidence})`,\n );\n\n return this.locator;\n }\n\n /** Validate the AI's response before trusting it. */\n private validate(result: ResolveLocatorResult): void {\n if (result.confidence < this.options.minConfidence) {\n throw new AIValidationError(\n `AI returned low confidence (${result.confidence}) for \"${this.description}\". ` +\n `Rationale: ${result.rationale ?? '(none)'}\\n` +\n `Try a more specific description, or use device.locator() directly.`,\n );\n }\n if (!result.selector || result.selector.trim().length === 0) {\n throw new AIValidationError(\n `AI returned empty selector for \"${this.description}\".`,\n );\n }\n }\n\n /** Build a mobwright selector string from the AI's strategy + value. */\n private buildSelectorString(result: ResolveLocatorResult): string {\n switch (result.strategy) {\n case 'accessibility-id':\n return `~${result.selector}`;\n case 'id':\n return `#${result.selector}`;\n case 'xpath':\n return result.selector;\n case 'text':\n // Convert to a platform-aware xpath inline\n return this.platform === Platform.ANDROID\n ? `//*[@text=\"${result.selector}\"]`\n : `//*[@label=\"${result.selector}\" or @name=\"${result.selector}\" or @value=\"${result.selector}\"]`;\n default: {\n const _exhaustive: never = result.strategy;\n throw new AIError(`Unknown selector strategy from AI: ${String(_exhaustive)}`);\n }\n }\n }\n\n /** Tap the resolved element. Resolves on first call. */\n async tap(): Promise<void> {\n const locator = await this.resolve();\n await locator.tap();\n }\n\n /** Type text into the resolved element. */\n async fill(text: string): Promise<void> {\n const locator = await this.resolve();\n await locator.fill(text);\n }\n\n /** Get the visible text of the resolved element. */\n async getText(): Promise<string> {\n const locator = await this.resolve();\n return locator.getText();\n }\n\n /** Check visibility of the resolved element (snapshot, no waiting). */\n async isVisible(): Promise<boolean> {\n try {\n const locator = await this.resolve();\n return locator.isVisible();\n } catch {\n return false;\n }\n }\n\n /** Wait for the resolved element to be visible. */\n async waitFor(options: { timeout?: number } = {}): Promise<void> {\n const locator = await this.resolve();\n await locator.waitFor(options);\n }\n\n /** Check if the resolved element is enabled (snapshot, no waiting). */\n async isEnabled(): Promise<boolean> {\n try {\n const locator = await this.resolve();\n return locator.isEnabled();\n } catch {\n return false;\n }\n }\n\n /**\n * Inspect the resolved selector. Returns null if not yet resolved.\n * Useful for debugging or \"save back to a stable selector\" workflows.\n */\n getResolved(): ResolveLocatorResult | null {\n return this.resolved;\n }\n}","import type { Browser } from 'webdriverio';\nimport { Platform, type ProjectConfig } from '../types.js';\nimport { Locator, type LocatorOptions } from './locator.js';\nimport type { AIProvider } from '../ai/provider.js';\nimport { AILocator, type AILocatorOptions } from './ai-locator.js';\nimport { AIError } from '../ai/errors.js';\n\nexport class Device {\n readonly stub = false as const;\n readonly project: string;\n readonly platform: Platform;\n readonly browser: Browser;\n /** Optional AI provider — only set if AI is configured. */\n readonly aiProvider: AIProvider | undefined;\n private readonly defaultTimeout: number;\n\n constructor(\n browser: Browser,\n project: ProjectConfig,\n aiProvider?: AIProvider,\n ) {\n this.browser = browser;\n this.project = project.name;\n this.platform = project.use.platform;\n this.defaultTimeout = project.use.actionTimeout ?? 5000;\n this.aiProvider = aiProvider;\n }\n\n /**\n * Create a lazy reference to a UI element by selector string.\n *\n * Selector syntax:\n * ~foo → accessibility id\n * #foo → resource id (ends-with match; package prefix optional)\n * //... → xpath\n * foo → accessibility id (no prefix)\n */\n locator(selector: string, options: LocatorOptions = {}): Locator {\n return new Locator(this.browser, this.platform, selector, {\n timeout: options.timeout ?? this.defaultTimeout,\n });\n }\n\n /**\n * Convenience: locate an element by its visible text or label (exact match).\n */\n getByText(text: string, options: LocatorOptions = {}): Locator {\n // Build platform-specific xpath inline for text matching\n const xpath = this.platform === Platform.ANDROID\n ? `//*[@text=\"${text}\"]`\n : `//*[@label=\"${text}\" or @name=\"${text}\" or @value=\"${text}\"]`;\n return new Locator(this.browser, this.platform, xpath, {\n timeout: options.timeout ?? this.defaultTimeout,\n });\n }\n\n /**\n * Locate an element whose visible text **contains** the substring.\n * Case-insensitive by default.\n *\n * @example\n * device.getByContainingText('get started'); // matches \"Get Started\", \"GET STARTED\", etc.\n * device.getByContainingText('Started', { ignoreCase: false }); // case-sensitive substring\n */\n getByContainingText(\n text: string,\n options: LocatorOptions & { ignoreCase?: boolean } = {},\n ): Locator {\n const ignoreCase = options.ignoreCase ?? true;\n const lc = 'abcdefghijklmnopqrstuvwxyz';\n const uc = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n\n let xpath: string;\n if (this.platform === Platform.ANDROID) {\n xpath = ignoreCase\n ? `//*[contains(translate(@text,\"${uc}\",\"${lc}\"),\"${text.toLowerCase()}\")]`\n : `//*[contains(@text,\"${text}\")]`;\n } else {\n xpath = ignoreCase\n ? `//*[contains(translate(@label,\"${uc}\",\"${lc}\"),\"${text.toLowerCase()}\") or contains(translate(@name,\"${uc}\",\"${lc}\"),\"${text.toLowerCase()}\") or contains(translate(@value,\"${uc}\",\"${lc}\"),\"${text.toLowerCase()}\")]`\n : `//*[contains(@label,\"${text}\") or contains(@name,\"${text}\") or contains(@value,\"${text}\")]`;\n }\n\n return new Locator(this.browser, this.platform, xpath, {\n timeout: options.timeout ?? this.defaultTimeout,\n });\n }\n\n /**\n * Take a screenshot. Returns raw PNG bytes.\n */\n async screenshot(): Promise<Buffer> {\n const base64 = await this.browser.takeScreenshot();\n return Buffer.from(base64, 'base64');\n }\n\n /**\n * Get the current accessibility tree as an XML string.\n * Used later by AI locator resolution.\n */\n async getPageSource(): Promise<string> {\n return await this.browser.getPageSource();\n }\n\n /**\n * Create a natural-language locator. Resolves on first action via AI.\n *\n * Requires AI to be configured (MOBWRIGHT_AI_PROVIDER + MOBWRIGHT_AI_API_KEY).\n * Throws AIError immediately if AI is not configured.\n *\n * @example\n * await device.ai('the blue Continue button at the bottom').tap();\n * await device.ai('the email input field').fill('me@example.com');\n */\n ai(description: string, options: AILocatorOptions = {}): AILocator {\n if (!this.aiProvider) {\n throw new AIError(\n 'device.ai() requires an AI provider. Set MOBWRIGHT_AI_PROVIDER and ' +\n 'MOBWRIGHT_AI_API_KEY in your environment.',\n );\n }\n return new AILocator(this.browser, this.platform, this.aiProvider, description, {\n timeout: options.timeout ?? this.defaultTimeout,\n minConfidence: options.minConfidence,\n treeCharBudget: options.treeCharBudget,\n });\n }\n\n /**\n * Get cumulative token usage from the AI provider (if it tracks tokens).\n * Returns null if AI is not configured or if the provider doesn't support token tracking.\n */\n getAITokenUsage() {\n if (!this.aiProvider || !('getTokenUsage' in this.aiProvider)) {\n return null;\n }\n return (this.aiProvider as unknown as { getTokenUsage: () => unknown }).getTokenUsage();\n }\n}","import OpenAI from 'openai';\nimport type { AIProvider } from './provider.js';\nimport type {\n ProviderConfig,\n ResolveLocatorInput,\n ResolveLocatorResult,\n SelectorStrategy,\n} from './types.js';\nimport { LOCATOR_SYSTEM_PROMPT, buildUserPrompt } from './prompts.js';\nimport { AIRequestError, AIResponseError } from './errors.js';\n\nexport interface OpenAICompatibleProviderOptions extends ProviderConfig {\n /** Override the provider name in logs. Defaults to 'openai'. */\n name?: string;\n}\n\n/**\n * OpenAI-compatible provider.\n *\n * Used directly for OpenAI, and as the base for DeepSeek (which exposes\n * an OpenAI-compatible API at a different base URL).\n */\nexport class OpenAICompatibleProvider implements AIProvider {\n readonly name: string;\n private readonly client: OpenAI;\n private readonly model: string;\n\n constructor(config: OpenAICompatibleProviderOptions) {\n this.name = config.name ?? 'openai';\n this.model = config.model;\n this.client = new OpenAI({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n timeout: config.timeout ?? 30_000,\n });\n }\n\n async resolveLocator(input: ResolveLocatorInput): Promise<ResolveLocatorResult> {\n let response;\n try {\n response = await this.client.chat.completions.create({\n model: this.model,\n messages: [\n { role: 'system', content: LOCATOR_SYSTEM_PROMPT },\n { role: 'user', content: buildUserPrompt(input) },\n ],\n // Many recent OpenAI/DeepSeek models support JSON mode\n response_format: { type: 'json_object' },\n temperature: 0,\n });\n } catch (err) {\n throw new AIRequestError(`${this.name} request failed`, err);\n }\n\n const raw = response.choices[0]?.message?.content;\n if (!raw) {\n throw new AIResponseError(`${this.name} returned empty content`);\n }\n\n return parseLocatorResponse(raw, this.name);\n }\n}\n\n/**\n * Parse and validate the JSON response from a locator-resolution call.\n * Exported for reuse by other providers.\n */\nexport function parseLocatorResponse(\n raw: string,\n providerName: string,\n): ResolveLocatorResult {\n let parsed: unknown;\n try {\n // Some models still wrap in ```json fences. Strip if present.\n const cleaned = raw.replace(/^```(?:json)?\\s*|\\s*```$/g, '').trim();\n parsed = JSON.parse(cleaned);\n } catch (err) {\n throw new AIResponseError(`${providerName} returned non-JSON`, raw, err);\n }\n\n if (!parsed || typeof parsed !== 'object') {\n throw new AIResponseError(`${providerName} returned non-object`, raw);\n }\n\n const obj = parsed as Record<string, unknown>;\n const selector = obj.selector;\n const strategy = obj.strategy;\n const confidence = obj.confidence;\n\n if (typeof selector !== 'string' || selector.length === 0) {\n throw new AIResponseError(`${providerName}: missing or invalid \"selector\"`, raw);\n }\n if (!isValidStrategy(strategy)) {\n throw new AIResponseError(\n `${providerName}: invalid \"strategy\" — got ${String(strategy)}`,\n raw,\n );\n }\n if (typeof confidence !== 'number' || confidence < 0 || confidence > 1) {\n throw new AIResponseError(\n `${providerName}: invalid \"confidence\" — got ${String(confidence)}`,\n raw,\n );\n }\n\n const rationale = typeof obj.rationale === 'string' ? obj.rationale : undefined;\n\n return { selector, strategy, confidence, rationale };\n}\n\nfunction isValidStrategy(value: unknown): value is SelectorStrategy {\n return (\n value === 'accessibility-id' || value === 'id' || value === 'xpath' || value === 'text'\n );\n}","import type { Platform } from '../types.js';\n\n/**\n * System prompt for locator resolution.\n * Reusable across providers.\n */\nexport const LOCATOR_SYSTEM_PROMPT = `\nYou are a mobile UI testing assistant. Given an accessibility tree from\na mobile app and a natural-language description of a target element,\nreturn a concrete selector to find that element.\n\nYour response MUST be valid JSON with this shape:\n{\n \"selector\": \"string — the locator value\",\n \"strategy\": \"accessibility-id\" | \"id\" | \"xpath\" | \"text\",\n \"confidence\": 0.0–1.0,\n \"rationale\": \"string — brief explanation\"\n}\n\nSelector formats by strategy:\n- accessibility-id: just the value (no prefix). Maps to content-desc (Android) or name (iOS).\n- id: just the value (no prefix). Maps to resource-id suffix (Android) or name (iOS).\n- xpath: full xpath expression starting with //\n- text: just the visible text value\n\nPrefer accessibility-id > id > text > xpath, in that order.\nLower confidence (<0.7) if you are guessing.\nReturn ONLY the JSON, no markdown fences, no extra prose.\n`.trim();\n\nexport function buildUserPrompt(input: {\n description: string;\n accessibilityTree: string;\n platform: Platform;\n}): string {\n return [\n `Platform: ${input.platform}`,\n `Target element: ${input.description}`,\n '',\n 'Accessibility tree:',\n input.accessibilityTree,\n ].join('\\n');\n}","import { OpenAICompatibleProvider } from './openai.js';\nimport type { ProviderConfig } from './types.js';\n\n/**\n * DeepSeek uses an OpenAI-compatible API at a different base URL.\n * This is just a thin wrapper for ergonomics + correct defaults.\n *\n * Default model: deepseek-chat (general purpose, cheap).\n * For reasoning-heavy tasks: deepseek-reasoner.\n */\nexport class DeepSeekProvider extends OpenAICompatibleProvider {\n constructor(config: ProviderConfig) {\n super({\n name: 'deepseek',\n apiKey: config.apiKey,\n model: config.model || 'deepseek-chat',\n baseURL: config.baseURL ?? 'https://api.deepseek.com',\n timeout: config.timeout,\n });\n }\n}","import Anthropic from '@anthropic-ai/sdk';\nimport type { TextBlock } from '@anthropic-ai/sdk/resources/messages';\nimport type { AIProvider } from './provider.js';\nimport type {\n ProviderConfig,\n ResolveLocatorInput,\n ResolveLocatorResult,\n} from './types.js';\nimport { LOCATOR_SYSTEM_PROMPT, buildUserPrompt } from './prompts.js';\nimport { AIRequestError, AIResponseError } from './errors.js';\nimport { parseLocatorResponse } from './openai.js';\n\nexport class AnthropicProvider implements AIProvider {\n readonly name = 'anthropic';\n private readonly client: Anthropic;\n private readonly model: string;\n private totalInputTokens = 0;\n private totalOutputTokens = 0;\n\n constructor(config: ProviderConfig) {\n this.model = config.model;\n this.client = new Anthropic({\n apiKey: config.apiKey,\n baseURL: config.baseURL,\n timeout: config.timeout ?? 30_000,\n });\n }\n\n async resolveLocator(input: ResolveLocatorInput): Promise<ResolveLocatorResult> {\n let response;\n try {\n response = await this.client.messages.create({\n model: this.model,\n max_tokens: 1024,\n system: LOCATOR_SYSTEM_PROMPT,\n messages: [{ role: 'user', content: buildUserPrompt(input) }],\n temperature: 0,\n });\n } catch (err) {\n throw new AIRequestError(`${this.name} request failed`, err);\n }\n\n // Concatenate all text blocks (usually just one for our prompts)\n const text = response.content\n .filter((block): block is TextBlock => block.type === 'text')\n .map((block) => block.text)\n .join('')\n .trim();\n\n if (!text) {\n throw new AIResponseError(`${this.name} returned no text content`);\n }\n\n // Track token usage (defensive — usage may be absent in some responses or mocks)\n const usage = response.usage;\n if (!usage) {\n return parseLocatorResponse(text, this.name);\n }\n // From here down, TypeScript knows usage is defined.\n // All token-related code is allowed to use it freely.\n this.totalInputTokens += usage.input_tokens;\n this.totalOutputTokens += usage.output_tokens;\n const totalTokens = usage.input_tokens + usage.output_tokens;\n console.log(\n `[${this.name}] tokens: ${usage.input_tokens} in + ${usage.output_tokens} out = ${totalTokens} total`,\n );\n\n return parseLocatorResponse(text, this.name);\n }\n\n /** Get cumulative token usage across all requests. */\n getTokenUsage() {\n return {\n inputTokens: this.totalInputTokens,\n outputTokens: this.totalOutputTokens,\n totalTokens: this.totalInputTokens + this.totalOutputTokens,\n };\n }\n}","import type { AIConfig } from '../types.js';\nimport type { AIProvider } from './provider.js';\nimport { OpenAICompatibleProvider } from './openai.js';\nimport { DeepSeekProvider } from './deepseek.js';\nimport { AnthropicProvider } from './anthropic.js';\n\n/**\n * Default models per provider, used when AIConfig.model is empty.\n * Picked for cost-effectiveness on locator resolution (not deep reasoning).\n */\nconst DEFAULT_MODELS: Record<AIConfig['provider'], string> = {\n anthropic: 'claude-haiku-4-5-20251001',\n openai: 'gpt-4o-mini',\n deepseek: 'deepseek-chat',\n};\n\n/**\n * Create an AIProvider from a mobwright AIConfig.\n *\n * @example\n * const provider = createProvider({\n * provider: 'deepseek',\n * model: 'deepseek-chat',\n * apiKey: process.env.MOBWRIGHT_AI_API_KEY!,\n * });\n */\nexport function createProvider(config: AIConfig): AIProvider {\n const model = config.model || DEFAULT_MODELS[config.provider];\n\n switch (config.provider) {\n case 'anthropic':\n return new AnthropicProvider({\n apiKey: config.apiKey,\n model,\n baseURL: config.baseURL,\n timeout: config.timeout,\n });\n\n case 'openai':\n return new OpenAICompatibleProvider({\n apiKey: config.apiKey,\n model,\n baseURL: config.baseURL,\n timeout: config.timeout,\n name: 'openai',\n });\n\n case 'deepseek':\n return new DeepSeekProvider({\n apiKey: config.apiKey,\n model,\n baseURL: config.baseURL,\n timeout: config.timeout,\n });\n\n default: {\n const _exhaustive: never = config.provider;\n throw new Error(`Unknown AI provider: ${String(_exhaustive)}`);\n }\n }\n}","import { execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\n\nconst exec = promisify(execFile);\n\n/**\n * Check whether a given bundleId is installed on the booted iOS simulator.\n *\n * Uses `xcrun simctl get_app_container booted <bundleId> app`.\n * If the command succeeds with a path, the app is installed.\n * If it fails with \"No such file\" or non-zero exit, it's not installed.\n *\n * Accepts an optional UDID to target a specific simulator (preferred when\n * multiple simulators are booted).\n */\nexport async function isIOSAppInstalled(\n bundleId: string,\n udid?: string,\n): Promise<boolean> {\n const target = udid ?? 'booted';\n try {\n const { stdout } = await exec('xcrun', [\n 'simctl',\n 'get_app_container',\n target,\n bundleId,\n 'app',\n ]);\n // stdout is the install path when installed; empty/error when not.\n return stdout.trim().length > 0;\n } catch {\n // Non-zero exit code = not installed (or simulator not booted)\n return false;\n }\n}","import { expect as baseExpect } from '@playwright/test';\nimport type { Locator } from '../device/locator.js';\n\n/**\n * Anything our matchers know how to assert on.\n * Both Locator and AILocator have the methods we need.\n */\ntype AssertableLocator = Pick<Locator, 'isVisible' | 'getText' | 'selector'> & {\n // Locator has isEnabled via the underlying wdio element; we'll add it\n isEnabled?: () => Promise<boolean>;\n};\n\ninterface MatcherResult {\n pass: boolean;\n message: () => string;\n name?: string;\n expected?: unknown;\n actual?: unknown;\n}\n\n/**\n * Default timeout for custom matchers. Matches Locator default.\n */\nconst DEFAULT_TIMEOUT = 5000;\nconst POLL_INTERVAL = 100;\n\n/**\n * Generic poll-until-true helper for matchers.\n * Returns whether the condition became true within the timeout.\n */\nasync function pollUntilTrue(\n fn: () => Promise<boolean>,\n timeout: number,\n): Promise<boolean> {\n const deadline = Date.now() + timeout;\n while (Date.now() < deadline) {\n try {\n if (await fn()) return true;\n } catch {\n // swallow — element might not exist yet\n }\n await new Promise((r) => setTimeout(r, POLL_INTERVAL));\n }\n // One last check at the deadline\n try {\n return await fn();\n } catch {\n return false;\n }\n}\n\n/**\n * Match modes for text assertions.\n */\ntype TextMatcher = string | RegExp;\n\nfunction matchText(actual: string, expected: TextMatcher): boolean {\n if (expected instanceof RegExp) return expected.test(actual);\n return actual === expected;\n}\n\n/**\n * Case-insensitive substring match.\n * If `expected` is a RegExp, it is tested as-is (ignoring `ignoreCase` option).\n */\nfunction containsText(\n actual: string,\n expected: TextMatcher,\n options: { ignoreCase?: boolean } = {},\n): boolean {\n if (expected instanceof RegExp) return expected.test(actual);\n const ignoreCase = options.ignoreCase ?? true;\n if (ignoreCase) {\n return actual.toLowerCase().includes(expected.toLowerCase());\n }\n return actual.includes(expected);\n}\n\n/**\n * Mobwright's expect — extends Playwright's with mobile-aware matchers.\n *\n * Each matcher auto-retries up to `timeout` ms (default 5000).\n */\nexport const expect = baseExpect.extend({\n /**\n * Assert the element is currently visible (present + displayed).\n * Auto-retries until visible or timeout.\n */\n async toBeVisible(\n received: AssertableLocator,\n options: { timeout?: number } = {},\n ): Promise<MatcherResult> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n const pass = await pollUntilTrue(() => received.isVisible(), timeout);\n\n return {\n pass,\n name: 'toBeVisible',\n message: () =>\n pass\n ? `Expected locator \"${received.selector}\" NOT to be visible, but it was.`\n : `Expected locator \"${received.selector}\" to be visible within ${timeout}ms, but it was not.`,\n };\n },\n\n /**\n * Assert the element's visible text matches.\n * Pass a string for exact match, or a RegExp for pattern match.\n * Auto-retries until match or timeout.\n */\n async toHaveText(\n received: AssertableLocator,\n expected: TextMatcher,\n options: { timeout?: number } = {},\n ): Promise<MatcherResult> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let lastText = '';\n const pass = await pollUntilTrue(async () => {\n lastText = await received.getText();\n return matchText(lastText, expected);\n }, timeout);\n\n return {\n pass,\n name: 'toHaveText',\n expected,\n actual: lastText,\n message: () =>\n pass\n ? `Expected locator \"${received.selector}\" NOT to have text ${JSON.stringify(expected)}, but it did.`\n : `Expected locator \"${received.selector}\" to have text ${JSON.stringify(expected)} within ${timeout}ms, but got ${JSON.stringify(lastText)}.`,\n };\n },\n\n /**\n * Assert the element's visible text **contains** the expected substring.\n * Case-insensitive by default. Pass `ignoreCase: false` for exact-case check.\n * Also accepts a RegExp.\n * Auto-retries until match or timeout.\n */\n async toContainText(\n received: AssertableLocator,\n expected: TextMatcher,\n options: { timeout?: number; ignoreCase?: boolean } = {},\n ): Promise<MatcherResult> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n let lastText = '';\n const pass = await pollUntilTrue(async () => {\n lastText = await received.getText();\n return containsText(lastText, expected, { ignoreCase: options.ignoreCase });\n }, timeout);\n\n return {\n pass,\n name: 'toContainText',\n expected,\n actual: lastText,\n message: () =>\n pass\n ? `Expected locator \"${received.selector}\" NOT to contain text ${JSON.stringify(expected)}, but it did (got ${JSON.stringify(lastText)}).`\n : `Expected locator \"${received.selector}\" to contain text ${JSON.stringify(expected)} within ${timeout}ms, but got ${JSON.stringify(lastText)}.`,\n };\n },\n\n /**\n * Assert the element is enabled (interactable).\n * Auto-retries until enabled or timeout.\n */\n async toBeEnabled(\n received: AssertableLocator,\n options: { timeout?: number } = {},\n ): Promise<MatcherResult> {\n const timeout = options.timeout ?? DEFAULT_TIMEOUT;\n\n if (!received.isEnabled) {\n throw new Error(\n `Locator \"${received.selector}\" does not support isEnabled(). ` +\n `Make sure you're using the latest mobwright version.`,\n );\n }\n\n const pass = await pollUntilTrue(() => received.isEnabled!(), timeout);\n\n return {\n pass,\n name: 'toBeEnabled',\n message: () =>\n pass\n ? `Expected locator \"${received.selector}\" NOT to be enabled, but it was.`\n : `Expected locator \"${received.selector}\" to be enabled within ${timeout}ms, but it was not.`,\n };\n },\n});","import type { MobwrightConfig } from './types.js';\n\n/**\n * Define mobwright config with full type inference.\n *\n * @example\n * import { defineConfig, Platform } from 'mobwright';\n *\n * export default defineConfig({\n * projects: [\n * {\n * name: 'android',\n * use: {\n * platform: Platform.ANDROID,\n * device: { provider: 'emulator', name: 'Pixel_6_API_34' },\n * buildPath: './app.apk',\n * },\n * },\n * ],\n * });\n */\nexport function defineConfig(config: MobwrightConfig): MobwrightConfig {\n return config;\n}"],"mappings":"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","names":["Platform","base","OpenAI","Anthropic","base","import_test","baseExpect"]}