agent-device 0.17.5 → 0.17.7
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.
- package/README.md +5 -5
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.5.apk → agent-device-android-multitouch-helper-0.17.7.apk} +0 -0
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.7.apk.sha256 +1 -0
- package/android-multitouch-helper/dist/{agent-device-android-multitouch-helper-0.17.5.manifest.json → agent-device-android-multitouch-helper-0.17.7.manifest.json} +4 -4
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.5.apk → agent-device-android-snapshot-helper-0.17.7.apk} +0 -0
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.7.apk.sha256 +1 -0
- package/android-snapshot-helper/dist/{agent-device-android-snapshot-helper-0.17.5.manifest.json → agent-device-android-snapshot-helper-0.17.7.manifest.json} +6 -6
- package/dist/src/2133.js +1 -0
- package/dist/src/2748.js +1 -0
- package/dist/src/{4012.js → 2892.js} +1 -1
- package/dist/src/2948.js +1 -0
- package/dist/src/2954.js +2 -0
- package/dist/src/{7651.js → 3006.js} +1 -1
- package/dist/src/314.js +1 -0
- package/dist/src/{8806.js → 3340.js} +5 -5
- package/dist/src/368.js +1 -0
- package/dist/src/4291.js +1 -0
- package/dist/src/{7599.js → 432.js} +1 -1
- package/dist/src/4418.js +1 -0
- package/dist/src/4428.js +3 -0
- package/dist/src/4723.js +1 -0
- package/dist/src/{113.js → 5448.js} +1 -1
- package/dist/src/5484.js +1 -0
- package/dist/src/5628.js +1 -0
- package/dist/src/587.js +1 -0
- package/dist/src/5910.js +2 -0
- package/dist/src/6311.js +27 -0
- package/dist/src/6918.js +1 -0
- package/dist/src/7244.js +1 -0
- package/dist/src/7394.js +6 -0
- package/dist/src/8184.js +1 -0
- package/dist/src/8225.js +1 -0
- package/dist/src/{1974.js → 8275.js} +1 -1
- package/dist/src/8875.js +1 -0
- package/dist/src/9581.js +11 -0
- package/dist/src/9624.js +1 -0
- package/dist/src/9722.js +38 -0
- package/dist/src/9836.js +1 -0
- package/dist/src/9873.js +1 -0
- package/dist/src/9919.js +6 -0
- package/dist/src/android-adb.d.ts +3 -3
- package/dist/src/android-adb.js +1 -1
- package/dist/src/android-snapshot-helper.js +1 -1
- package/dist/src/android.js +1 -1
- package/dist/src/apple.js +1 -1
- package/dist/src/apps.js +6 -6
- package/dist/src/artifacts.js +1 -1
- package/dist/src/batch.d.ts +5 -5
- package/dist/src/batch.js +1 -1
- package/dist/src/{args.js → cli-help.js} +135 -36
- package/dist/src/cli-output.js +4 -30
- package/dist/src/cli.js +18 -17
- package/dist/src/command-surface.js +1 -1
- package/dist/src/contracts.d.ts +7 -7
- package/dist/src/contracts.js +1 -1
- package/dist/src/devices.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/finders.d.ts +1 -1
- package/dist/src/finders.js +1 -1
- package/dist/src/generic.js +8 -14
- package/dist/src/index.d.ts +34 -28
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +1 -1
- package/dist/src/input-actions~1.js +1 -1
- package/dist/src/install-source.js +1 -1
- package/dist/src/interaction.js +1 -1
- package/dist/src/internal/bin.js +5 -5
- package/dist/src/internal/companion-tunnel.js +1 -1
- package/dist/src/internal/daemon.js +1 -1
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/internal/update-check-entry.js +1 -1
- package/dist/src/io.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/metro.js +1 -1
- package/dist/src/notifications.js +1 -1
- package/dist/src/provider.js +1 -0
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-trace-recording.js +3 -3
- package/dist/src/record-trace.js +1 -1
- package/dist/src/recording-provider.js +1 -1
- package/dist/src/remote-config.d.ts +6 -6
- package/dist/src/remote-config.js +1 -1
- package/dist/src/rslib-runtime.js +1 -1
- package/dist/src/selector-runtime.js +1 -1
- package/dist/src/selectors.d.ts +2 -2
- package/dist/src/selectors.js +1 -1
- package/dist/src/server.js +1 -1
- package/dist/src/session.js +8 -8
- package/dist/src/snapshot.js +2 -2
- package/dist/src/web.js +1 -0
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift +6 -6
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +1 -1
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift +9 -5
- package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests.swift +5 -2
- package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +44 -8
- package/ios-runner/AgentDeviceRunner/RecordingScripts/recording-resize.swift +59 -16
- package/ios-runner/RUNNER_PROTOCOL.md +1 -1
- package/package.json +18 -14
- package/server.json +3 -3
- package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.5.apk.sha256 +0 -1
- package/android-snapshot-helper/dist/agent-device-android-snapshot-helper-0.17.5.apk.sha256 +0 -1
- package/dist/src/1010.js +0 -1
- package/dist/src/123.js +0 -1
- package/dist/src/1231.js +0 -1
- package/dist/src/1352.js +0 -1
- package/dist/src/208.js +0 -1
- package/dist/src/221.js +0 -6
- package/dist/src/2403.js +0 -3
- package/dist/src/2415.js +0 -38
- package/dist/src/3675.js +0 -1
- package/dist/src/5611.js +0 -1
- package/dist/src/5827.js +0 -1
- package/dist/src/6088.js +0 -1
- package/dist/src/6133.js +0 -1
- package/dist/src/6629.js +0 -1
- package/dist/src/7556.js +0 -1
- package/dist/src/7719.js +0 -1
- package/dist/src/7847.js +0 -1
- package/dist/src/8133.js +0 -1
- package/dist/src/8656.js +0 -1
- package/dist/src/9010.js +0 -1
- package/dist/src/9471.js +0 -1
- package/dist/src/9542.js +0 -6
- package/dist/src/9616.js +0 -2
- package/dist/src/9639.js +0 -2
- package/dist/src/9671.js +0 -1
- package/dist/src/9818.js +0 -1
- package/dist/src/989.js +0 -1
- /package/dist/src/{2301.js → 4069.js} +0 -0
- /package/dist/src/{4829.js → 5560.js} +0 -0
- /package/dist/src/{4778.js → 6024.js} +0 -0
- /package/dist/src/{9152.js → 733.js} +0 -0
- /package/dist/src/{3267.js → 9612.js} +0 -0
package/dist/src/snapshot.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{__webpack_require__ as e}from"./rslib-runtime.js";import{promises as t}from"node:fs";import a from"node:os";import n from"node:path";import{centerOfRect as s}from"./
|
|
2
|
-
${a.hint??""}`,/Android UI hierarchy dump timed out/i.test(n)||/Stock UIAutomator fallback was skipped/i.test(n)||/Android accessibility snapshots can be blocked/i.test(n)||function(e){if(!e||"object"!=typeof e)return!1;let t=String(e.errorType??""),a=String(e.message??"");return/TimeoutException/i.test(t)||/timed out/i.test(a)}(t.details?.helper)||function(e){var t;if(!e)return!1;let a=e?.timeoutMs,n=e?.cmd,s=Array.isArray(t=e?.args)?t.map(String):"string"==typeof t?t.split(/\s+/):[];return"number"==typeof a&&"adb"===n&&s.includes("uiautomator")&&s.includes("dump")}(t.details)))return{ok:!1,error:{...s,details:{...s.details??{},androidSnapshotTimeoutScreenshot:await ep(e)}}}}async function ep(e){try{var s,r,i;let o=await t.mkdtemp(n.join(a.tmpdir(),"agent-device-android-snapshot-timeout-")),d=n.join(o,"snapshot-timeout-overlay-refs.png"),p=(s=await f(e.device,"screenshot",[d],void 0,{...S(e.logPath,{screenshotNoStabilize:!0},e.session?.appBundleId,e.session?.trace?.outPath),surface:e.session?.surface}),r=d,(i=s,"object"==typeof i&&null!==i&&"path"in i&&"string"==typeof i.path)?s.path:r);await t.access(p);let u=await eu(p,e.session);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_captured",data:{path:p,overlayRefCount:"overlayRefCount"in u?u.overlayRefCount:void 0,overlayRefsAnnotated:"overlayRefsAnnotated"in u?u.overlayRefsAnnotated:void 0}}),u}catch(t){let e=o(t);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_failed",data:{error:e.message}}),{captureFailed:!0,error:e.message}}}async function eu(e,t){if(!t?.snapshot)return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"unavailable",overlayRefCount:0};try{let a=await v({screenshotPath:e,snapshot:t.snapshot});return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:a.length>0,overlayRefCount:a.length,overlayRefSource:"session-snapshot",overlayRefs:a}}catch(a){let t=o(a);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_overlay_failed",data:{path:e,error:t.message}}),{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"session-snapshot",overlayRefCount:0,overlayAnnotationError:t.message}}}async function ec(e){return await em({...e,command:"snapshot",unsupportedMessage:"snapshot is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.snapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw,forceFull:a.flags?.snapshotForceFull});return{data:s,record:{kind:"snapshot",nodes:s.nodes.length,truncated:s.truncated}}}})}async function ef(e){return await em({...e,command:"diff",unsupportedMessage:"diff is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.diffSnapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw});return{data:s,record:{kind:"diff",mode:"snapshot",baselineInitialized:s.baselineInitialized,summary:s.summary}}}})}async function em(e){var t,a;let{req:n,sessionName:s,logPath:o,sessionStore:i}=e,{session:l,device:d}=await O(i,s,n.flags);if(!y(e.command,d))return w("UNSUPPORTED_OPERATION",e.unsupportedMessage);let p=m(n.flags?.snapshotScope,l);if(!p.ok)return p;let f=(t=e.command,a=l,"ios"!==d.platform||a?.appBundleId?null:w("SESSION_NOT_FOUND",`iOS ${t} requires an active app session on the target device. Run open first (for example: open --session ${a?.name??"sim"} --platform ios --device "<name>" <app>).`));return f||await K(l,d,async()=>{var t,a;let f,m,h=function(e){let{req:t,sessionName:a,logPath:n,sessionStore:s,session:o,device:i,snapshotScope:l}=e;return U({backend:function(e){let{req:t,logPath:a,session:n,device:s,snapshotScope:o}=e;return{platform:s.platform,captureSnapshot:async(e,r)=>{let i=await c({device:s,session:n,flags:t.flags,outPath:r?.outPath??t.flags?.out,logPath:a,snapshotScope:o}),l=x(n);return{snapshot:i.snapshot,...N(i),...l?{snapshotDiagnostics:l}:{},appName:n?.appBundleId?n.appName??n.appBundleId:void 0,appBundleId:n?.appBundleId}}}}({req:t,logPath:n,session:o,device:i,snapshotScope:l}),...
|
|
1
|
+
import{__webpack_require__ as e}from"./rslib-runtime.js";import{promises as t}from"node:fs";import a from"node:os";import n from"node:path";import{centerOfRect as s}from"./4291.js";import{normalizeError as o,AppError as r}from"./733.js";import{withDiagnosticTimer as i,emitDiagnostic as l}from"./432.js";import{pressAndroid as d,backAndroid as p}from"./input-actions.js";import{createDaemonRuntimeSessionStore as u,captureSnapshot as c,dispatchCommand as f,resolveSnapshotScope as m,runMacOsAlertAction as h,errorResponse as w,runIosRunnerCommand as g,isCommandSupportedOnDevice as y,snapshotAndroid as _,annotateScreenshotWithRefs as v,context_contextFromFlags as S}from"./9722.js";import{SETTINGS_INVALID_ARGS_MESSAGE as b,parseTimeout as I,getUnsupportedMacOsSettingMessage as A,successText as R,isMacOsSettingSupported as C,snapshotCaptureAnnotationsFrom as N}from"./9873.js";import{sleep as D}from"./5560.js";import{buildSnapshotSession as P,dispatchWaitViaRuntime as k,createDaemonRuntimePolicy as M,recordIfSession as E,resolveSessionDevice as O,withSessionlessRunnerCleanup as K}from"./selector-runtime.js";import{createAgentDevice as U}from"./368.js";import{summarizeSnapshotDiagnostics as x}from"./2892.js";var L={};e.r(L),e.d(L,{handleSnapshotCommands:()=>ew});let B=new Set(["com.android.permissioncontroller","com.google.android.permissioncontroller","com.google.android.packageinstaller","com.android.packageinstaller"]),$=new Set(["android","com.android.systemui"]),H=/^android:id\/(?:alertTitle|message|button[123]|parentPanel|buttonPanel|contentPanel)$/i,q=/^android:id\/button[123]$/i,F=/(?:^|:)id\/permission_/i,T=/\b(?:is(?:n't| not) responding|keeps stopping|has stopped|close app|app info)\b/i,j=/^(?:ok|allow|allow all|while using the app|only this time|yes|continue|save|confirm|turn on|open settings)$/i,X=/^(?:cancel|deny|don.t allow|don’t allow|not now|no|dismiss|close|close app|later|skip)$/i;function z(e,t){let a=t[0];if(!a)return;let n=G(e,a.index);for(let a of t.slice(1)){let t=new Set(G(e,a.index));for(let e=n.length-1;e>=0;e-=1)t.has(n[e])||n.splice(e,1)}return n[n.length-1]}function G(e,t){let a=new Map(e.map(e=>[e.index,e])),n=[],s=a.get(t);for(;s;)n.push(s.index),s=void 0===s.parentIndex?void 0:a.get(s.parentIndex);return n.reverse()}function V(e,t){let a=new Map;for(let t of e){if(void 0===t.parentIndex)continue;let e=a.get(t.parentIndex)??[];e.push(t),a.set(t.parentIndex,e)}let n=new Set([t]),s=[t];for(let e of s)for(let t of a.get(e)??[])n.has(t.index)||(n.add(t.index),s.push(t.index));return e.filter(e=>n.has(e.index))}function W(e){let t=e.type??"",a=e.identifier??"";return!!(e.hittable||/\bbutton\b/i.test(t)||q.test(a)||/(?:^|:)id\/permission_(?:allow|deny)/i.test(a))}function J(e){if(!e)return"";let t=[e.label,e.value].filter(e=>"string"==typeof e&&e.trim().length>0);return t[0]?.trim()??""}async function Q(e,t,a={}){if("wait"===t)return await Y(e,a.timeoutMs??1e4);if("get"===t){var n;let t=await et(e);return{kind:"alertStatus",platform:"android",action:"get",alert:n=t?.alert??null,...n?R("Alert visible"):R("No alert visible")}}return await Z(e,t)}async function Y(e,t){let a=Date.now(),n=await ee(e,t);if(!n)throw new r("COMMAND_FAILED","alert wait timed out");return{kind:"alertWait",platform:"android",action:"wait",alert:n.alert,waitedMs:Date.now()-a,...R("Alert visible")}}async function Z(e,t){var a;let n,s,o=await ee(e,2e3);if(!o)throw new r("COMMAND_FAILED","alert not found",{hint:"If a sheet is visible in snapshot but alert reports no alert, it is likely app-owned UI. Use snapshot -i and press the visible label/ref."});let i=(a=o.buttons,n="accept"===t?"accept":"dismiss",(s=a.find(e=>e.role===n))||("dismiss"===t?a.find(e=>"neutral"===e.role)??null:1===a.length?a[0]??null:null));if(i)return await d(e,i.x,i.y),ea(t,o.alert,i.label);if("dismiss"===t)return await p(e),ea(t,o.alert,"Back");throw new r("COMMAND_FAILED","alert accept found an alert but no accept button",{alert:o.alert,hint:"Inspect alert get --json for visible buttons, then use press by visible label/ref if needed."})}async function ee(e,t){let a=Date.now();for(;Date.now()-a<t;){let t=await et(e);if(t)return t;await D(300)}return null}async function et(e){return function(e){var t,a,n,o;let r,i=function(e){let t=e.filter(e=>{var t;let a;return a=(t=e).bundleId??"",B.has(a)||F.test(t.identifier??"")});if(t.length)return{nodes:t,source:"permission"};let a=function(e){let t=e.filter(e=>{var t;let a;return a=(t=e).bundleId??"",$.has(a)&&T.test(J(t))});if(0===t.length)return[];let a=z(e,t);return void 0===a?t:V(e,a).filter(e=>e.bundleId&&$.has(e.bundleId))}(e);return a.length?{nodes:a,source:"system-dialog"}:{nodes:function(e){var t;let a,n,s=e.filter(e=>{var t;return t=e.type??"",/(?:^|[.$])[^.]*Dialog$/i.test(t)}),o=e.filter(e=>H.test(e.identifier??"")),r=s.length?[...s,...o]:(a=(t=o).some(e=>q.test(e.identifier??"")),n=t.some(e=>!q.test(e.identifier??"")),a&&n?t:[]);if(0===r.length)return[];let i=z(e,r);return void 0===i?r:V(e,i)}(e),source:"native-dialog"}}(e),l=i.nodes;if(0===l.length)return null;let d=function(e){let t=new Set,a=[];for(let n of e){let e=J(n);if(!e||!n.rect||!W(n))continue;let o=e.trim().toLowerCase();if(!o||t.has(o))continue;t.add(o);let r=s(n.rect);a.push({label:e,x:r.x,y:r.y,role:function(e,t){var a;let n=(a=e.identifier??"",/(?:^|:)id\/button1$/i.test(a)?"accept":/(?:^|:)id\/button2$/i.test(a)?"dismiss":/(?:^|:)id\/button3$/i.test(a)?"neutral":/(?:^|:)id\/permission_allow/i.test(a)?"accept":/(?:^|:)id\/permission_deny/i.test(a)?"dismiss":null);return n||(j.test(t.trim())?"accept":X.test(t.trim())?"dismiss":"neutral")}(n,e)})}return a}(l),p=l.filter(e=>J(e)&&!W(e)),u=J((t=p).find(e=>/(?:^|:)id\/(?:alertTitle|permission_message)$/i.test(e.identifier??"")))||J(t[0]),c=(a=p,n=u,(r=a.map(e=>J(e)).filter(e=>e&&e!==n)).length?[...new Set(r)].join("\n"):void 0),f=(o=l,o.find(e=>e.bundleId)?.bundleId);return{alert:{...u?{title:u}:{},...c?{message:c}:{},buttons:d.map(e=>e.label),platform:"android",source:i.source,...f?{packageName:f}:{}},buttons:d}}((await i("snapshot_capture",async()=>await _(e,{helperWaitForIdleTimeoutMs:0,includeHiddenContentHints:!1}),{backend:"android",purpose:"alert"})).nodes)}function ea(e,t,a){return{kind:"alertHandled",platform:"android",action:e,handled:!0,alert:t,button:a,...R(`Alert ${e}ed`)}}async function en(e){var t;let{req:a,logPath:n,session:s,device:o}=e,r="accept"===(t=a.positionals?.[0])||"dismiss"===t||"wait"===t?t:"get",i=s?"frontmost-app"===s.surface?{surface:"frontmost-app"}:{bundleId:s.appBundleId,surface:s.surface}:{};if(!y("alert",o))return w("UNSUPPORTED_OPERATION","alert is not supported on this device");if("android"===o.platform){let t=I(a.positionals?.[1])??1e4;return ei(e,await Q(o,r,{timeoutMs:t}))}if("macos"===o.platform){let t=async e=>await h(e,i);return await es(e,r,t)}let l={verbose:a.flags?.verbose,logPath:n,traceLogPath:s?.trace?.outPath,requestId:a.meta?.requestId,iosXctestrunFile:a.flags?.iosXctestrunFile,iosXctestDerivedDataPath:a.flags?.iosXctestDerivedDataPath,iosXctestEnvDir:a.flags?.iosXctestEnvDir},d=async e=>await g(o,{command:"alert",action:e,appBundleId:s?.appBundleId},l);return await es(e,r,d)}async function es(e,t,a){if("wait"===t)return await eo(e,a);let n="accept"===t||"dismiss"===t?t:"get";return"accept"===n||"dismiss"===n?await er(e,n,a):ei(e,await a("get"))}async function eo(e,t){let a=I(e.req.positionals?.[1])??1e4,n=Date.now();for(;Date.now()-n<a;){try{return ei(e,await t("get"))}catch{}await D(300)}return w("COMMAND_FAILED","alert wait timed out")}async function er(e,t,a){var n,s;let o,i,l=Date.now();for(;Date.now()-l<2e3;){try{return ei(e,await a(t))}catch(t){i=t;let e=String(t?.message??"").toLowerCase();if(!e.includes("alert not found")&&!e.includes("no alert"))break}await D(300)}throw(n=i)instanceof r&&(s=n,(o=String(s?.message??"").toLowerCase()).includes("alert not found")||o.includes("no alert"))?new r(n.code,n.message,{...n.details??{},hint:"If the permission sheet is visible in snapshot or screenshot but alert reports no alert, take a scoped snapshot around the visible button label and use press @ref."}):n}function ei(e,t){return E(e.sessionStore,e.session,e.req,t),{ok:!0,data:t}}async function el(e){let{req:t,logPath:a,sessionStore:n,session:s,device:o,parsed:r}=e,{setting:i,state:l,appBundleId:d,permissionTarget:p,latitude:u,longitude:c}=r;if(!y("settings",o))return w("UNSUPPORTED_OPERATION","settings is not supported on this device");if("macos"===o.platform&&!C(i))return w("INVALID_ARGS",A(i));let m=d??s?.appBundleId;if("clear-app-state"===i&&!m)return w("INVALID_ARGS","settings clear-app-state requires an app id when no app is bound to the session");let h="clear-app-state"===i?[i,l,m??""]:"permission"===i?[i,l,p??"",t.positionals?.[3]??"",m??""]:"location"===i&&"set"===l?[i,l,u??"",c??"",m??""]:[i,l,m??""],g=await f(o,"settings",h,t.flags?.out,{...S(a,t.flags,m,s?.trace?.outPath)});return E(n,s,t,g??{setting:i,state:l}),{ok:!0,data:g??{setting:i,state:l}}}async function ed(e){var t,a;let n;if("snapshot"!==e.command||"android"!==e.device.platform)return;let s=o(e.error);if("COMMAND_FAILED"===(t=s).code&&(a=t,n=`${a.message}
|
|
2
|
+
${a.hint??""}`,/Android UI hierarchy dump timed out/i.test(n)||/Stock UIAutomator fallback was skipped/i.test(n)||/Android accessibility snapshots can be blocked/i.test(n)||function(e){if(!e||"object"!=typeof e)return!1;let t=String(e.errorType??""),a=String(e.message??"");return/TimeoutException/i.test(t)||/timed out/i.test(a)}(t.details?.helper)||function(e){var t;if(!e)return!1;let a=e?.timeoutMs,n=e?.cmd,s=Array.isArray(t=e?.args)?t.map(String):"string"==typeof t?t.split(/\s+/):[];return"number"==typeof a&&"adb"===n&&s.includes("uiautomator")&&s.includes("dump")}(t.details)))return{ok:!1,error:{...s,details:{...s.details??{},androidSnapshotTimeoutScreenshot:await ep(e)}}}}async function ep(e){try{var s,r,i;let o=await t.mkdtemp(n.join(a.tmpdir(),"agent-device-android-snapshot-timeout-")),d=n.join(o,"snapshot-timeout-overlay-refs.png"),p=(s=await f(e.device,"screenshot",[d],void 0,{...S(e.logPath,{screenshotNoStabilize:!0},e.session?.appBundleId,e.session?.trace?.outPath),surface:e.session?.surface}),r=d,(i=s,"object"==typeof i&&null!==i&&"path"in i&&"string"==typeof i.path)?s.path:r);await t.access(p);let u=await eu(p,e.session);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_captured",data:{path:p,overlayRefCount:"overlayRefCount"in u?u.overlayRefCount:void 0,overlayRefsAnnotated:"overlayRefsAnnotated"in u?u.overlayRefsAnnotated:void 0}}),u}catch(t){let e=o(t);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_failed",data:{error:e.message}}),{captureFailed:!0,error:e.message}}}async function eu(e,t){if(!t?.snapshot)return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"unavailable",overlayRefCount:0};try{let a=await v({screenshotPath:e,snapshot:t.snapshot});return{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:a.length>0,overlayRefCount:a.length,overlayRefSource:"session-snapshot",overlayRefs:a}}catch(a){let t=o(a);return l({level:"warn",phase:"android_snapshot_timeout_screenshot_overlay_failed",data:{path:e,error:t.message}}),{path:e,overlayRefsRequested:!0,overlayRefsAnnotated:!1,overlayRefSource:"session-snapshot",overlayRefCount:0,overlayAnnotationError:t.message}}}async function ec(e){return await em({...e,command:"snapshot",unsupportedMessage:"snapshot is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.snapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw,forceFull:a.flags?.snapshotForceFull});return{data:s,record:{kind:"snapshot",nodes:s.nodes.length,truncated:s.truncated}}}})}async function ef(e){return await em({...e,command:"diff",unsupportedMessage:"diff is not supported on this device",execute:async({runtime:e,sessionName:t,req:a,snapshotScope:n})=>{let s=await e.capture.diffSnapshot({session:t,interactiveOnly:a.flags?.snapshotInteractiveOnly,depth:a.flags?.snapshotDepth,scope:n,raw:a.flags?.snapshotRaw});return{data:s,record:{kind:"diff",mode:"snapshot",baselineInitialized:s.baselineInitialized,summary:s.summary}}}})}async function em(e){var t,a;let{req:n,sessionName:s,logPath:o,sessionStore:i}=e,{session:l,device:d}=await O(i,s,n.flags);if(!y(e.command,d))return w("UNSUPPORTED_OPERATION",e.unsupportedMessage);let p=m(n.flags?.snapshotScope,l);if(!p.ok)return p;let f=(t=e.command,a=l,"ios"!==d.platform||a?.appBundleId?null:w("SESSION_NOT_FOUND",`iOS ${t} requires an active app session on the target device. Run open first (for example: open --session ${a?.name??"sim"} --platform ios --device "<name>" <app>).`));return f||await K(l,d,async()=>{var t,a;let f,m,h=function(e){let{req:t,sessionName:a,logPath:n,sessionStore:s,session:o,device:i,snapshotScope:l}=e;return U({backend:function(e){let{req:t,logPath:a,session:n,device:s,snapshotScope:o}=e;return{platform:s.platform,captureSnapshot:async(e,r)=>{let i=await c({device:s,session:n,flags:t.flags,outPath:r?.outPath??t.flags?.out,logPath:a,snapshotScope:o}),l=x(n);return{snapshot:i.snapshot,...N(i),...l?{snapshotDiagnostics:l}:{},appName:n?.appBundleId?n.appName??n.appBundleId:void 0,appBundleId:n?.appBundleId}}}}({req:t,logPath:n,session:o,device:i,snapshotScope:l}),...M("snapshot"),sessions:u({sessionName:a,getSession:()=>s.get(a),recordOptions:{includeSnapshot:!0},setRecord:e=>{var n;let o=function(e){if(!e.snapshot)throw new r("UNKNOWN","snapshot runtime did not produce session state");return e}(e),l=s.get(a);s.set(a,function(e){var t,a;let{current:n,sessionName:s,device:o,record:r,refScopedSnapshot:i}=e,l=(t=n,a=r,i&&a.snapshot?.nodes.length===0&&t?.snapshot!==void 0),d=l?n.snapshot:r.snapshot,p=P({session:n,sessionName:s,device:o,snapshot:d,appBundleId:r.appBundleId});return p.snapshotScopeSource=function(e){let{current:t,keepCurrentSnapshot:a,refScopedSnapshot:n}=e;if(n)return a?t?.snapshotScopeSource:t?.snapshotScopeSource??t?.snapshot}({current:n,keepCurrentSnapshot:l,refScopedSnapshot:i}),r.appName&&(p.appName=r.appName),p}({current:l,sessionName:a,device:i,record:o,refScopedSnapshot:(n=t,n.flags?.snapshotScope?.trim().startsWith("@")===!0)}))}})})}({req:n,sessionName:s,logPath:o,sessionStore:i,session:l,device:d,snapshotScope:p.scope});try{m=await e.execute({runtime:h,sessionName:s,req:n,snapshotScope:p.scope})}catch(a){let t=await ed({error:a,command:e.command,logPath:o,session:l,device:d});if(!t)throw a;return t}return(f=(t={req:n,sessionName:s,sessionStore:i,result:m.record}).sessionStore.get(t.sessionName))&&t.sessionStore.recordAction(f,{command:t.req.command,positionals:t.req.positionals??[],flags:t.req.flags??{},result:"snapshot"===(a=t.result).kind?{nodes:a.nodes,truncated:a.truncated}:{mode:a.mode,baselineInitialized:a.baselineInitialized,summary:a.summary}}),{ok:!0,data:m.data}})}let eh={snapshot:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await ec({req:e,sessionName:t,logPath:a,sessionStore:n}),diff:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>e.positionals?.[0]!=="snapshot"?w("INVALID_ARGS","diff currently supports only: diff snapshot"):await ef({req:e,sessionName:t,logPath:a,sessionStore:n}),wait:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>await k({req:e,sessionName:t,logPath:a,sessionStore:n}),alert:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>{let{session:s,device:o}=await O(n,t,e.flags);return await K(s,o,async()=>await en({req:e,logPath:a,sessionStore:n,session:s,device:o}))},settings:async({req:e,sessionName:t,logPath:a,sessionStore:n})=>{let s,o,r,i=(s=e.positionals?.[0]?.toLowerCase(),o=e.positionals?.[1]?.toLowerCase(),r=e.positionals?.[2]?.toLowerCase(),"clear-app-state"===s?{ok:!0,parsed:{setting:s,state:"clear",appBundleId:"clear"===o?e.positionals?.[2]:e.positionals?.[1]}}:s&&o&&("permission"!==s||r)&&("location"!==s||"set"!==o||e.positionals?.[2]&&e.positionals?.[3])?{ok:!0,parsed:{setting:s,state:o,permissionTarget:r,latitude:e.positionals?.[2],longitude:e.positionals?.[3]}}:w("INVALID_ARGS",b));if(!i.ok)return i;let{session:l,device:d}=await O(n,t,e.flags);return await K(l,d,async()=>await el({req:e,logPath:a,sessionStore:n,session:l,device:d,parsed:i.parsed}))}};async function ew(e){let t=eh[e.req.command];return t?await t(e):null}export{L as snapshot_namespaceObject};
|
package/dist/src/web.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{AppError as e}from"./733.js";import{withDiagnosticTimer as t}from"./432.js";import{resolveWebProvider as r}from"./provider.js";function o(){return{open:(e,t)=>r().open(t?.url??e,{url:t?.url}),openDevice:()=>r().open("about:blank"),close:e=>r().close(e),tap:(e,t)=>r().click(e,t),doubleTap:()=>n("doubleTap"),swipe:()=>n("swipe"),pan:()=>n("pan"),fling:()=>n("fling"),longPress:()=>n("longPress"),focus:(e,t)=>r().click(e,t),type:(e,t)=>r().typeText(e,{delayMs:t}),fill:(e,t,o,n)=>r().fill(e,t,o,{delayMs:n}),scroll:(e,t)=>r().scroll(e,t),pinch:()=>n("pinch"),screenshot:(e,t)=>r().screenshot(e,t),snapshot:async e=>{let o=await t("snapshot_capture",async()=>await r().snapshot(e),{backend:"web"});return{nodes:o.nodes,truncated:o.truncated??!1,backend:"web"}},back:()=>n("back"),home:()=>n("home"),rotate:()=>n("rotate"),rotateGesture:()=>n("rotateGesture"),transformGesture:()=>n("transformGesture"),appSwitcher:()=>n("appSwitcher"),readClipboard:()=>n("readClipboard"),writeClipboard:()=>n("writeClipboard"),setSetting:()=>n("setSetting")}}async function n(t){throw new e("UNSUPPORTED_OPERATION",`${t} is not supported on web`)}export{o as createWebInteractor};
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+CommandExecution.swift
CHANGED
|
@@ -420,24 +420,24 @@ extension RunnerTests {
|
|
|
420
420
|
if let requestedFps = command.fps, (requestedFps < minRecordingFps || requestedFps > maxRecordingFps) {
|
|
421
421
|
return Response(ok: false, error: ErrorPayload(message: "recordStart fps must be between \(minRecordingFps) and \(maxRecordingFps)"))
|
|
422
422
|
}
|
|
423
|
-
if let
|
|
424
|
-
return Response(ok: false, error: ErrorPayload(message: "recordStart
|
|
423
|
+
if let requestedMaxSize = command.maxSize, requestedMaxSize < 1 {
|
|
424
|
+
return Response(ok: false, error: ErrorPayload(message: "recordStart maxSize must be a positive integer"))
|
|
425
425
|
}
|
|
426
426
|
do {
|
|
427
427
|
let resolvedOutPath = resolveRecordingOutPath(requestedOutPath)
|
|
428
428
|
let fpsLabel = command.fps.map(String.init) ?? String(RunnerTests.defaultRecordingFps)
|
|
429
|
-
let
|
|
429
|
+
let maxSizeLabel = command.maxSize.map(String.init) ?? "native"
|
|
430
430
|
NSLog(
|
|
431
|
-
"AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@
|
|
431
|
+
"AGENT_DEVICE_RUNNER_RECORD_START requestedOutPath=%@ resolvedOutPath=%@ fps=%@ maxSize=%@",
|
|
432
432
|
requestedOutPath,
|
|
433
433
|
resolvedOutPath,
|
|
434
434
|
fpsLabel,
|
|
435
|
-
|
|
435
|
+
maxSizeLabel
|
|
436
436
|
)
|
|
437
437
|
let recorder = ScreenRecorder(
|
|
438
438
|
outputPath: resolvedOutPath,
|
|
439
439
|
fps: command.fps.map { Int32($0) },
|
|
440
|
-
|
|
440
|
+
maxSize: command.maxSize
|
|
441
441
|
)
|
|
442
442
|
try recorder.start { [weak self] in
|
|
443
443
|
return self?.captureRunnerFrame()
|
package/ios-runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+ScreenRecorder.swift
CHANGED
|
@@ -7,7 +7,7 @@ extension RunnerTests {
|
|
|
7
7
|
final class ScreenRecorder {
|
|
8
8
|
private let outputPath: String
|
|
9
9
|
private let fps: Int32?
|
|
10
|
-
private let
|
|
10
|
+
private let maxSize: Int?
|
|
11
11
|
private var effectiveFps: Int32 {
|
|
12
12
|
max(1, fps ?? RunnerTests.defaultRecordingFps)
|
|
13
13
|
}
|
|
@@ -26,10 +26,10 @@ extension RunnerTests {
|
|
|
26
26
|
private var startedSession = false
|
|
27
27
|
private var startError: Error?
|
|
28
28
|
|
|
29
|
-
init(outputPath: String, fps: Int32?,
|
|
29
|
+
init(outputPath: String, fps: Int32?, maxSize: Int?) {
|
|
30
30
|
self.outputPath = outputPath
|
|
31
31
|
self.fps = fps
|
|
32
|
-
self.
|
|
32
|
+
self.maxSize = maxSize
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
func start(captureFrame: @escaping () -> RunnerImage?) throws {
|
|
@@ -262,10 +262,14 @@ extension RunnerTests {
|
|
|
262
262
|
}
|
|
263
263
|
|
|
264
264
|
private func scaledDimensions(width: Int, height: Int) -> CGSize {
|
|
265
|
-
guard let
|
|
265
|
+
guard let maxSize, maxSize > 0 else {
|
|
266
266
|
return CGSize(width: width, height: height)
|
|
267
267
|
}
|
|
268
|
-
let
|
|
268
|
+
let longest = max(width, height)
|
|
269
|
+
guard longest > maxSize else {
|
|
270
|
+
return CGSize(width: width, height: height)
|
|
271
|
+
}
|
|
272
|
+
let scale = Double(maxSize) / Double(longest)
|
|
269
273
|
return CGSize(
|
|
270
274
|
width: scaledEvenDimension(width, scale: scale),
|
|
271
275
|
height: scaledEvenDimension(height, scale: scale)
|
|
@@ -49,8 +49,6 @@ final class RunnerTests: XCTestCase {
|
|
|
49
49
|
let xctestIdleKeepaliveInterval: TimeInterval = 5.0
|
|
50
50
|
let minRecordingFps = 1
|
|
51
51
|
let maxRecordingFps = 120
|
|
52
|
-
let minRecordingQuality = 5
|
|
53
|
-
let maxRecordingQuality = 10
|
|
54
52
|
var needsPostSnapshotInteractionDelay = false
|
|
55
53
|
var needsFirstInteractionDelay = false
|
|
56
54
|
var activeRecording: ScreenRecorder?
|
|
@@ -91,6 +89,11 @@ final class RunnerTests: XCTestCase {
|
|
|
91
89
|
|
|
92
90
|
@MainActor
|
|
93
91
|
func testCommand() throws {
|
|
92
|
+
if RunnerEnv.isTruthy("AGENT_DEVICE_RUNNER_NOOP_STARTUP") {
|
|
93
|
+
NSLog("AGENT_DEVICE_RUNNER_NOOP_STARTUP=1")
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
|
|
94
97
|
doneExpectation = expectation(description: "agent-device command handled")
|
|
95
98
|
NSLog("AGENT_DEVICE_RUNNER_HEADLESS_STARTUP=1")
|
|
96
99
|
let desiredPort = RunnerEnv.resolvePort()
|
|
@@ -146,11 +146,9 @@ func run() throws {
|
|
|
146
146
|
)
|
|
147
147
|
|
|
148
148
|
// Overlay burn-in forces a full re-encode; medium quality keeps simulator videos readable
|
|
149
|
-
// while avoiding very slow highest-quality exports.
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
? AVAssetExportPresetMediumQuality
|
|
153
|
-
: AVAssetExportPresetHighestQuality
|
|
149
|
+
// while avoiding very slow highest-quality exports. Pass --quality high to opt into
|
|
150
|
+
// the slower highest-quality export.
|
|
151
|
+
let presetName = exportPresetName(for: parsedArgs.exportQuality, compatibleWith: composition)
|
|
154
152
|
guard let exporter = AVAssetExportSession(asset: composition, presetName: presetName) else {
|
|
155
153
|
throw OverlayError.exportFailed("Failed to create export session.")
|
|
156
154
|
}
|
|
@@ -174,10 +172,20 @@ func run() throws {
|
|
|
174
172
|
}
|
|
175
173
|
}
|
|
176
174
|
|
|
177
|
-
|
|
175
|
+
enum ExportQuality: String {
|
|
176
|
+
case medium
|
|
177
|
+
case high
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
func parseArguments(
|
|
181
|
+
_ arguments: [String]
|
|
182
|
+
) throws -> (inputPath: String, outputPath: String, eventsPath: String, exportQuality: ExportQuality) {
|
|
178
183
|
var inputPath: String?
|
|
179
184
|
var outputPath: String?
|
|
180
185
|
var eventsPath: String?
|
|
186
|
+
// Export quality defaults to medium so existing callers keep the fast, simulator-friendly
|
|
187
|
+
// export. Pass --quality high to opt into a slower highest-quality export.
|
|
188
|
+
var exportQuality: ExportQuality = .medium
|
|
181
189
|
var index = 0
|
|
182
190
|
|
|
183
191
|
while index < arguments.count {
|
|
@@ -196,15 +204,43 @@ func parseArguments(_ arguments: [String]) throws -> (inputPath: String, outputP
|
|
|
196
204
|
guard nextIndex < arguments.count else { throw OverlayError.invalidArgs("--events requires a value") }
|
|
197
205
|
eventsPath = arguments[nextIndex]
|
|
198
206
|
index += 2
|
|
207
|
+
case "--quality":
|
|
208
|
+
guard nextIndex < arguments.count else {
|
|
209
|
+
throw OverlayError.invalidArgs("--quality requires a value")
|
|
210
|
+
}
|
|
211
|
+
guard let parsed = ExportQuality(rawValue: arguments[nextIndex]) else {
|
|
212
|
+
throw OverlayError.invalidArgs("--quality must be one of: medium, high")
|
|
213
|
+
}
|
|
214
|
+
exportQuality = parsed
|
|
215
|
+
index += 2
|
|
199
216
|
default:
|
|
200
217
|
throw OverlayError.invalidArgs("Unknown argument: \(argument)")
|
|
201
218
|
}
|
|
202
219
|
}
|
|
203
220
|
|
|
204
221
|
guard let inputPath, let outputPath, let eventsPath else {
|
|
205
|
-
throw OverlayError.invalidArgs(
|
|
222
|
+
throw OverlayError.invalidArgs(
|
|
223
|
+
"Usage: recording-overlay.swift --input <video> --output <video> --events <json> [--quality <medium|high>]"
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
return (inputPath, outputPath, eventsPath, exportQuality)
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
func exportPresetName(
|
|
230
|
+
for exportQuality: ExportQuality,
|
|
231
|
+
compatibleWith asset: AVAsset
|
|
232
|
+
) -> String {
|
|
233
|
+
switch exportQuality {
|
|
234
|
+
case .high:
|
|
235
|
+
return AVAssetExportPresetHighestQuality
|
|
236
|
+
case .medium:
|
|
237
|
+
// Prefer the faster medium preset, falling back to highest quality only when medium is
|
|
238
|
+
// not available for this composition.
|
|
239
|
+
let compatible = AVAssetExportSession.exportPresets(compatibleWith: asset)
|
|
240
|
+
return compatible.contains(AVAssetExportPresetMediumQuality)
|
|
241
|
+
? AVAssetExportPresetMediumQuality
|
|
242
|
+
: AVAssetExportPresetHighestQuality
|
|
206
243
|
}
|
|
207
|
-
return (inputPath, outputPath, eventsPath)
|
|
208
244
|
}
|
|
209
245
|
|
|
210
246
|
func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
|
|
@@ -40,7 +40,13 @@ func run() throws {
|
|
|
40
40
|
throw ResizeError.missingVideoTrack
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
let
|
|
43
|
+
let sourceRenderSize = resolvedRenderSize(for: sourceVideoTrack)
|
|
44
|
+
if max(sourceRenderSize.width, sourceRenderSize.height) <= CGFloat(parsedArgs.maxSize) {
|
|
45
|
+
try FileManager.default.copyItem(at: inputURL, to: outputURL)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let renderSize = scaledRenderSize(sourceRenderSize, maxSize: parsedArgs.maxSize)
|
|
44
50
|
let composition = AVMutableComposition()
|
|
45
51
|
let fullRange = CMTimeRange(start: .zero, duration: asset.duration)
|
|
46
52
|
|
|
@@ -60,7 +66,7 @@ func run() throws {
|
|
|
60
66
|
try? compositionAudioTrack.insertTimeRange(fullRange, of: sourceAudioTrack, at: .zero)
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
let scale =
|
|
69
|
+
let scale = renderSize.width / sourceRenderSize.width
|
|
64
70
|
let videoComposition = AVMutableVideoComposition()
|
|
65
71
|
videoComposition.renderSize = renderSize
|
|
66
72
|
videoComposition.frameDuration = resolvedFrameDuration(for: sourceVideoTrack)
|
|
@@ -74,7 +80,8 @@ func run() throws {
|
|
|
74
80
|
instruction.layerInstructions = [layerInstruction]
|
|
75
81
|
videoComposition.instructions = [instruction]
|
|
76
82
|
|
|
77
|
-
|
|
83
|
+
let presetName = exportPresetName(for: parsedArgs.exportQuality, compatibleWith: composition)
|
|
84
|
+
guard let exporter = AVAssetExportSession(asset: composition, presetName: presetName) else {
|
|
78
85
|
throw ResizeError.exportFailed("Failed to create export session.")
|
|
79
86
|
}
|
|
80
87
|
|
|
@@ -97,10 +104,20 @@ func run() throws {
|
|
|
97
104
|
}
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
|
|
107
|
+
enum ExportQuality: String {
|
|
108
|
+
case medium
|
|
109
|
+
case high
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func parseArguments(
|
|
113
|
+
_ arguments: [String]
|
|
114
|
+
) throws -> (inputPath: String, outputPath: String, maxSize: Int, exportQuality: ExportQuality) {
|
|
101
115
|
var inputPath: String?
|
|
102
116
|
var outputPath: String?
|
|
103
|
-
var
|
|
117
|
+
var maxSize: Int?
|
|
118
|
+
// Export quality defaults to medium so re-encoded recordings stay fast by default.
|
|
119
|
+
// Pass --quality high to opt into a slower highest-quality export.
|
|
120
|
+
var exportQuality: ExportQuality = .medium
|
|
104
121
|
var index = 0
|
|
105
122
|
|
|
106
123
|
while index < arguments.count {
|
|
@@ -115,24 +132,50 @@ func parseArguments(_ arguments: [String]) throws -> (inputPath: String, outputP
|
|
|
115
132
|
guard nextIndex < arguments.count else { throw ResizeError.invalidArgs("--output requires a value") }
|
|
116
133
|
outputPath = arguments[nextIndex]
|
|
117
134
|
index += 2
|
|
135
|
+
case "--max-size":
|
|
136
|
+
guard nextIndex < arguments.count else { throw ResizeError.invalidArgs("--max-size requires a value") }
|
|
137
|
+
guard let parsed = Int(arguments[nextIndex]), parsed >= 1 else {
|
|
138
|
+
throw ResizeError.invalidArgs("--max-size must be a positive integer")
|
|
139
|
+
}
|
|
140
|
+
maxSize = parsed
|
|
141
|
+
index += 2
|
|
118
142
|
case "--quality":
|
|
119
|
-
guard nextIndex < arguments.count else {
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
guard nextIndex < arguments.count else {
|
|
144
|
+
throw ResizeError.invalidArgs("--quality requires a value")
|
|
145
|
+
}
|
|
146
|
+
guard let parsed = ExportQuality(rawValue: arguments[nextIndex]) else {
|
|
147
|
+
throw ResizeError.invalidArgs("--quality must be one of: medium, high")
|
|
122
148
|
}
|
|
123
|
-
|
|
149
|
+
exportQuality = parsed
|
|
124
150
|
index += 2
|
|
125
151
|
default:
|
|
126
152
|
throw ResizeError.invalidArgs("Unknown argument: \(argument)")
|
|
127
153
|
}
|
|
128
154
|
}
|
|
129
155
|
|
|
130
|
-
guard let inputPath, let outputPath, let
|
|
156
|
+
guard let inputPath, let outputPath, let maxSize else {
|
|
131
157
|
throw ResizeError.invalidArgs(
|
|
132
|
-
"Usage: recording-resize.swift --input <video> --output <video> --quality <
|
|
158
|
+
"Usage: recording-resize.swift --input <video> --output <video> --max-size <px> [--quality <medium|high>]"
|
|
133
159
|
)
|
|
134
160
|
}
|
|
135
|
-
return (inputPath, outputPath,
|
|
161
|
+
return (inputPath, outputPath, maxSize, exportQuality)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
func exportPresetName(
|
|
165
|
+
for exportQuality: ExportQuality,
|
|
166
|
+
compatibleWith asset: AVAsset
|
|
167
|
+
) -> String {
|
|
168
|
+
switch exportQuality {
|
|
169
|
+
case .high:
|
|
170
|
+
return AVAssetExportPresetHighestQuality
|
|
171
|
+
case .medium:
|
|
172
|
+
// Mirror the touch-overlay export: prefer the faster medium preset, falling back to
|
|
173
|
+
// highest quality only when medium is not available for this composition.
|
|
174
|
+
let compatible = AVAssetExportSession.exportPresets(compatibleWith: asset)
|
|
175
|
+
return compatible.contains(AVAssetExportPresetMediumQuality)
|
|
176
|
+
? AVAssetExportPresetMediumQuality
|
|
177
|
+
: AVAssetExportPresetHighestQuality
|
|
178
|
+
}
|
|
136
179
|
}
|
|
137
180
|
|
|
138
181
|
func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
|
|
@@ -140,10 +183,10 @@ func resolvedRenderSize(for track: AVAssetTrack) -> CGSize {
|
|
|
140
183
|
return CGSize(width: abs(transformed.width), height: abs(transformed.height))
|
|
141
184
|
}
|
|
142
185
|
|
|
143
|
-
func scaledRenderSize(
|
|
144
|
-
let
|
|
145
|
-
guard
|
|
146
|
-
let scale = CGFloat(
|
|
186
|
+
func scaledRenderSize(_ renderSize: CGSize, maxSize: Int) -> CGSize {
|
|
187
|
+
let longest = max(renderSize.width, renderSize.height)
|
|
188
|
+
guard longest > CGFloat(maxSize) else { return renderSize }
|
|
189
|
+
let scale = CGFloat(maxSize) / longest
|
|
147
190
|
return CGSize(
|
|
148
191
|
width: scaledDimension(renderSize.width, scale: scale),
|
|
149
192
|
height: scaledDimension(renderSize.height, scale: scale)
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-device",
|
|
3
|
-
"version": "0.17.
|
|
4
|
-
"description": "Agent-native CLI for AI
|
|
3
|
+
"version": "0.17.7",
|
|
4
|
+
"description": "Agent-native CLI for AI app automation across iOS, Android, tvOS, Android TV, macOS, Linux, and web.",
|
|
5
5
|
"mcpName": "io.github.callstackincubator/agent-device",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Callstack",
|
|
8
8
|
"homepage": "https://agent-device.dev/",
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
11
|
+
"url": "git+https://github.com/callstack/agent-device.git"
|
|
12
12
|
},
|
|
13
13
|
"bugs": {
|
|
14
|
-
"url": "https://github.com/
|
|
14
|
+
"url": "https://github.com/callstack/agent-device/issues"
|
|
15
15
|
},
|
|
16
16
|
"type": "module",
|
|
17
17
|
"packageManager": "pnpm@11.1.2",
|
|
@@ -134,6 +134,7 @@
|
|
|
134
134
|
"test:integration:provider": "vitest run --project provider-integration",
|
|
135
135
|
"test:integration:progress": "node --experimental-strip-types scripts/integration-progress.ts",
|
|
136
136
|
"test:integration:progress:check": "node --experimental-strip-types scripts/integration-progress.ts --check",
|
|
137
|
+
"test:smoke:web": "pnpm build && node --test test/integration/smoke-web-platform.test.ts",
|
|
137
138
|
"test:skillgym": "node test/skillgym/runner-environment.ts && pnpm build && skillgym run ./test/skillgym/suites/agent-device-smoke-suite.ts --config ./test/skillgym/skillgym.config.ts",
|
|
138
139
|
"test:skillgym:case": "node test/skillgym/runner-environment.ts && pnpm build && skillgym run ./test/skillgym/suites/agent-device-smoke-suite.ts --config ./test/skillgym/skillgym.config.ts --case",
|
|
139
140
|
"test:smoke": "node --test test/integration/smoke-*.test.ts",
|
|
@@ -178,6 +179,8 @@
|
|
|
178
179
|
"android",
|
|
179
180
|
"tvos",
|
|
180
181
|
"macos",
|
|
182
|
+
"web",
|
|
183
|
+
"browser",
|
|
181
184
|
"react-native",
|
|
182
185
|
"observability",
|
|
183
186
|
"diagnostics",
|
|
@@ -211,16 +214,17 @@
|
|
|
211
214
|
"yaml": "^2.9.0"
|
|
212
215
|
},
|
|
213
216
|
"devDependencies": {
|
|
214
|
-
"@microsoft/api-extractor": "^7.58.
|
|
215
|
-
"@rslib/core": "0.
|
|
216
|
-
"@types/node": "^22.
|
|
217
|
-
"@
|
|
218
|
-
"
|
|
217
|
+
"@microsoft/api-extractor": "^7.58.8",
|
|
218
|
+
"@rslib/core": "0.22.1",
|
|
219
|
+
"@types/node": "^22.19.21",
|
|
220
|
+
"@typescript/native-preview": "7.0.0-dev.20260613.1",
|
|
221
|
+
"@vitest/coverage-v8": "4.1.8",
|
|
222
|
+
"fallow": "^2.95.0",
|
|
219
223
|
"oxfmt": "^0.42.0",
|
|
220
|
-
"oxlint": "^1.
|
|
221
|
-
"skillgym": "^0.
|
|
222
|
-
"typescript": "^6.0.
|
|
223
|
-
"vite": "^8.0.
|
|
224
|
-
"vitest": "^4.1.
|
|
224
|
+
"oxlint": "^1.69.0",
|
|
225
|
+
"skillgym": "^0.9.1",
|
|
226
|
+
"typescript": "^6.0.3",
|
|
227
|
+
"vite": "^8.0.16",
|
|
228
|
+
"vitest": "^4.1.8"
|
|
225
229
|
}
|
|
226
230
|
}
|
package/server.json
CHANGED
|
@@ -4,15 +4,15 @@
|
|
|
4
4
|
"title": "agent-device",
|
|
5
5
|
"description": "Let AI agents inspect, control, and debug real iOS, Android, desktop, and TV apps",
|
|
6
6
|
"repository": {
|
|
7
|
-
"url": "https://github.com/
|
|
7
|
+
"url": "https://github.com/callstack/agent-device",
|
|
8
8
|
"source": "github"
|
|
9
9
|
},
|
|
10
|
-
"version": "0.17.
|
|
10
|
+
"version": "0.17.7",
|
|
11
11
|
"packages": [
|
|
12
12
|
{
|
|
13
13
|
"registryType": "npm",
|
|
14
14
|
"identifier": "agent-device",
|
|
15
|
-
"version": "0.17.
|
|
15
|
+
"version": "0.17.7",
|
|
16
16
|
"transport": {
|
|
17
17
|
"type": "stdio"
|
|
18
18
|
}
|
package/android-multitouch-helper/dist/agent-device-android-multitouch-helper-0.17.5.apk.sha256
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
45a85cddbdca10a23a6245bf8e6786342551bf61f9d02bd8fdbf419e785dc1f9 agent-device-android-multitouch-helper-0.17.5.apk
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
94e26dc456437cf7a2112450e8638b5fac62565b4b17e86dc3834f6eedcd3ced agent-device-android-snapshot-helper-0.17.5.apk
|
package/dist/src/1010.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{AppError as e}from"./9152.js";let r=new Set(["1","true","yes","on"]),n=new Set(["0","false","no","off"]);function t(e){return`AGENT_DEVICE_${e.replace(/([A-Z])/g,"_$1").replace(/[^A-Za-z0-9_]/g,"_").toUpperCase()}`}function i(r,n,t,f){if(r.multiple)return(Array.isArray(n)?n:[n]).map(e=>i({...r,multiple:!1},e,t,f));if("boolean"===r.type){var a=n,u=t,l=f;if("boolean"==typeof a)return a;if("string"==typeof a){let e=o(a);if(void 0!==e)return e}throw new e("INVALID_ARGS",`Invalid value for "${l}" in ${u}. Expected boolean.`)}if("booleanOrString"===r.type){if("boolean"==typeof n)return n;if("string"==typeof n&&void 0!==o(n))return o(n);if("string"==typeof n&&n.trim().length>0)return n;throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected boolean or non-empty string.`)}if("string"===r.type){if("string"==typeof n&&n.trim().length>0)return n;throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected non-empty string.`)}if("enum"===r.type){if(void 0!==r.setValue){var p=r,m=n,s=t,I=f;let i=p.setValue;if(m===i)return i;if("string"==typeof m){let e=m.trim();if(""===e||"true"===e||"1"===e)return i;if("false"===e||"0"===e)return}if(!0===m)return i;if(!1!==m)throw new e("INVALID_ARGS",`Invalid value for "${I}" in ${s}. Expected boolean-like value for enum flag.`);return}if("string"!=typeof n||!r.enumValues?.includes(n))throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected one of: ${r.enumValues?.join(", ")}.`);return n}let v="number"==typeof n?n:"string"==typeof n?Number(n):NaN;if(!Number.isFinite(v)||!Number.isInteger(v))throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Expected integer.`);if("number"==typeof r.min&&v<r.min)throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Must be >= ${r.min}.`);if("number"==typeof r.max&&v>r.max)throw new e("INVALID_ARGS",`Invalid value for "${f}" in ${t}. Must be <= ${r.max}.`);return v}function o(e){let t=e.trim().toLowerCase();return!!r.has(t)||!n.has(t)&&void 0}export{t as buildPrimaryEnvVarName,i as parseSourceValue};
|