agent-device 0.21.20 → 0.21.22
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/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.20.apk → agent-device-android-ime-helper-0.21.22.apk} +0 -0
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.22.apk.sha256 +1 -0
- package/android/ime-helper/dist/{agent-device-android-ime-helper-0.21.20.manifest.json → agent-device-android-ime-helper-0.21.22.manifest.json} +4 -4
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.22.apk +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.22.apk.sha256 +1 -0
- package/android/snapshot-helper/dist/{agent-device-android-snapshot-helper-0.21.20.manifest.json → agent-device-android-snapshot-helper-0.21.22.manifest.json} +6 -6
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/AppWindowScreenshot.swift +73 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/BackgroundInteraction.swift +557 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/GhostCursor.swift +141 -0
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/SnapshotTraversal.swift +107 -3
- package/apple/macos-helper/Sources/AgentDeviceMacOSHelper/main.swift +136 -22
- package/apple/macos-helper/Tests/AgentDeviceMacOSHelperTests/BackgroundInteractionTests.swift +88 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunner/AgentDeviceRunnerApp.m +121 -4
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h +10 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m +36 -9
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift +28 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift +27 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedCommitDeadline.swift +6 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedGesturePolicy.swift +7 -2
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedInteraction.swift +21 -7
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+SynthesizedTextEntry.swift +121 -17
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntry.swift +179 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryConfirmation.swift +119 -0
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryFocus.swift +8 -1
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextEntryReadiness.swift +23 -11
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextInputProbe.swift +59 -6
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TextTyping.swift +112 -23
- package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+TypeExecution.swift +25 -12
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/RecordingExportSupport.swift +6 -1
- package/dist/apple/runner/AgentDeviceRunner/RecordingScripts/recording-overlay.swift +43 -13
- package/dist/src/adapter.js +1 -1
- package/dist/src/adb-executor.js +2 -2
- package/dist/src/adb-failure.js +1 -1
- package/dist/src/adb-transport.js +1 -1
- package/dist/src/agent-browser-lifecycle.js +1 -1
- package/dist/src/agent-browser-process-record.js +1 -1
- package/dist/src/agent-browser-provider.js +1 -1
- package/dist/src/agent-browser-tool.js +1 -1
- package/dist/src/agent-device-client.js +2 -2
- package/dist/src/ai-sdk.js +1 -1
- package/dist/src/alert-runtime.js +1 -1
- package/dist/src/alert.js +1 -1
- package/dist/src/android-adb.d.ts +12 -0
- package/dist/src/android-input-ownership.js +1 -1
- package/dist/src/app-backend.js +1 -0
- package/dist/src/app-catalog.js +1 -1
- package/dist/src/app-deployment-resolution.js +1 -1
- package/dist/src/app-inventory.js +1 -1
- package/dist/src/app-launch.js +1 -1
- package/dist/src/app-lifecycle.js +2 -2
- package/dist/src/app-lifecycle2.js +1 -1
- package/dist/src/app-log-files.js +1 -1
- package/dist/src/app-log-reconnect.js +1 -1
- package/dist/src/app-log-runtime2.js +3 -3
- package/dist/src/app-log-session-resource.js +1 -0
- package/dist/src/app-resolution.js +2 -2
- package/dist/src/apple-runner-platform.js +1 -1
- package/dist/src/application-lifecycle-interaction.js +1 -1
- package/dist/src/archive.js +2 -2
- package/dist/src/artifact-download.js +1 -1
- package/dist/src/auth-session.js +1 -1
- package/dist/src/auth.js +1 -1
- package/dist/src/back-mode.js +1 -1
- package/dist/src/backend-snapshot-options.js +1 -1
- package/dist/src/boot-diagnostics.js +1 -1
- package/dist/src/cache.js +1 -1
- package/dist/src/capabilities.js +1 -1
- package/dist/src/capture.js +1 -1
- package/dist/src/cli-config.js +1 -1
- package/dist/src/cli-help.js +13 -5
- package/dist/src/cli.js +4 -4
- package/dist/src/click-button.js +1 -1
- package/dist/src/client-metro-companion.js +1 -1
- package/dist/src/client-metro.js +1 -1
- package/dist/src/client-options.js +3 -0
- package/dist/src/client-types.d.ts +88 -5
- package/dist/src/clipboard-shell-response.js +1 -1
- package/dist/src/cloud-artifacts.d.ts +7 -2
- package/dist/src/code-signature-cache.js +1 -1
- package/dist/src/code-signature.js +1 -1
- package/dist/src/command-schema.js +1 -1
- package/dist/src/command-tools.js +2 -2
- package/dist/src/connection-runtime.js +1 -1
- package/dist/src/connection.js +5 -5
- package/dist/src/contracts.d.ts +1 -1
- package/dist/src/contracts.js +1 -1
- package/dist/src/daemon-artifacts.js +1 -0
- package/dist/src/daemon-client-lease-beat.js +1 -1
- package/dist/src/daemon-client-lifecycle.js +1 -1
- package/dist/src/daemon-client-liveness-probe.js +1 -0
- package/dist/src/daemon-client-transport.js +1 -1
- package/dist/src/daemon-http.js +1 -0
- package/dist/src/daemon-policy-file.js +1 -1
- package/dist/src/daemon-registration-owner.js +1 -0
- package/dist/src/daemon-registration.js +1 -1
- package/dist/src/daemon-resolution.js +1 -1
- package/dist/src/daemon-shutdown-report.js +1 -1
- package/dist/src/daemon.js +2 -2
- package/dist/src/debug-symbols-facade.js +1 -1
- package/dist/src/device-claim-inspection.js +1 -1
- package/dist/src/device-claim-rule.js +1 -1
- package/dist/src/device-inventory-context.js +1 -1
- package/dist/src/device-readiness-runtime.js +1 -1
- package/dist/src/device-release.js +1 -1
- package/dist/src/device-rotation.js +1 -1
- package/dist/src/device-selection-resolver.js +1 -1
- package/dist/src/device-session.js +1 -1
- package/dist/src/device-shell.js +1 -1
- package/dist/src/device.js +1 -1
- package/dist/src/device2.js +1 -3
- package/dist/src/device3.js +3 -0
- package/dist/src/devicectl.js +1 -1
- package/dist/src/diagnostics.js +3 -3
- package/dist/src/dispatch-resolve.js +4 -1
- package/dist/src/display-inventory.js +1 -1
- package/dist/src/doctor-facade.js +1 -1
- package/dist/src/doctor2.js +1 -1
- package/dist/src/durable-capture-resource.js +1 -0
- package/dist/src/engine-eval-script.js +1 -1
- package/dist/src/error.js +1 -1
- package/dist/src/exec.js +1 -1
- package/dist/src/file.js +1 -1
- package/dist/src/find.js +1 -1
- package/dist/src/generic-settle.js +1 -1
- package/dist/src/generic.js +1 -1
- package/dist/src/gesture-admission.js +1 -1
- package/dist/src/gesture-plan.js +1 -1
- package/dist/src/gesture-runtime.js +1 -1
- package/dist/src/harmonyos.js +1 -1
- package/dist/src/hdc.js +1 -1
- package/dist/src/helper.js +2 -2
- package/dist/src/host-file.js +1 -1
- package/dist/src/host.js +1 -1
- package/dist/src/human-control-contract.js +1 -1
- package/dist/src/human-control.js +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/input-actions.js +1 -1
- package/dist/src/input-actions2.js +2 -2
- package/dist/src/install-artifact-facade.js +1 -1
- package/dist/src/install-source-network-transport.js +1 -1
- package/dist/src/install-source.d.ts +5 -0
- package/dist/src/install-source2.js +1 -1
- package/dist/src/instance-client.js +1 -0
- package/dist/src/interaction-positionals.js +1 -1
- package/dist/src/interaction-ref-policy.js +1 -1
- package/dist/src/interaction.js +1 -1
- package/dist/src/interaction2.js +1 -1
- package/dist/src/interactor-operation-binding.js +1 -1
- package/dist/src/interactor.js +5 -5
- package/dist/src/interactor2.js +1 -1
- package/dist/src/interactors.js +1 -1
- package/dist/src/internal/bin.js +1 -1
- package/dist/src/internal/daemon.js +1 -2
- package/dist/src/internal/png-worker.js +1 -1
- package/dist/src/internal/run-script-http-child.js +1 -1
- package/dist/src/inventory.js +4 -4
- package/dist/src/inventory2.js +1 -1
- package/dist/src/inventory3.js +1 -1
- package/dist/src/inventory5.js +1 -1
- package/dist/src/ios-client.js +4 -30
- package/dist/src/ios-settings.js +1 -0
- package/dist/src/ios-snapshot-engine.js +1 -1
- package/dist/src/ios-snapshot-runtime.js +1 -1
- package/dist/src/launch-confirmation.js +1 -1
- package/dist/src/lease-context.js +1 -1
- package/dist/src/lease-registry-scope.js +1 -0
- package/dist/src/lease-scope.js +1 -1
- package/dist/src/lease.js +1 -1
- package/dist/src/legacy-xctest-device-set.js +1 -1
- package/dist/src/limrun-download-file.js +1 -1
- package/dist/src/limrun-runtime-dependencies.js +1 -1
- package/dist/src/limrun.d.ts +28 -2
- package/dist/src/limrun.js +1 -1
- package/dist/src/linux.js +1 -1
- package/dist/src/load.js +1 -0
- package/dist/src/location-coordinates.js +1 -1
- package/dist/src/macos-facade.js +1 -1
- package/dist/src/managed-device-scope.js +1 -1
- package/dist/src/mechanics.js +16 -16
- package/dist/src/observation.js +1 -1
- package/dist/src/observe-until.js +1 -1
- package/dist/src/overlay.js +2 -2
- package/dist/src/owner-identity.js +3 -3
- package/dist/src/path-resolution.js +1 -1
- package/dist/src/perf-facade.js +1 -1
- package/dist/src/perf-runtime-operation-builder.js +1 -1
- package/dist/src/perf-runtime-plan.js +1 -1
- package/dist/src/perf-target.js +1 -1
- package/dist/src/perf.js +1 -1
- package/dist/src/physical-device-control.js +1 -1
- package/dist/src/platform-request-scope.js +1 -1
- package/dist/src/platform-runtime-android-adb-host.js +1 -1
- package/dist/src/platform-runtime-app-log-process.js +1 -1
- package/dist/src/platform-runtime-host.js +1 -1
- package/dist/src/platform-runtime-managed-owner.js +1 -1
- package/dist/src/platform-runtime-open-target.js +1 -1
- package/dist/src/platform-runtime-operation-host.js +2 -2
- package/dist/src/platform-runtime-runtime-hints.js +2 -2
- package/dist/src/platform-runtime-screen-recording-android-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-apple-runner-transport.js +1 -1
- package/dist/src/platform-runtime-screen-recording-apple-simulator-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-finalizer-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-output-host.js +1 -1
- package/dist/src/platform-runtime-screen-recording-process-host.js +1 -1
- package/dist/src/platform-runtime-unavailable.js +1 -1
- package/dist/src/platform-runtime.js +1 -1
- package/dist/src/platform-runtime2.js +1 -1
- package/dist/src/plugins.d.ts +9 -0
- package/dist/src/plugins.js +1 -0
- package/dist/src/plugins2.js +2 -0
- package/dist/src/png-crop-bytes.js +1 -1
- package/dist/src/png-size.js +1 -1
- package/dist/src/png-worker-client.js +1 -1
- package/dist/src/png.js +1 -1
- package/dist/src/prepare-kind.js +1 -1
- package/dist/src/process.js +1 -1
- package/dist/src/project-runtime.js +1 -1
- package/dist/src/provider-credential-fingerprint.js +1 -0
- package/dist/src/provider-device-admission.js +1 -1
- package/dist/src/provider-device-runtimes.js +1 -0
- package/dist/src/provider-limrun-credentials.js +1 -0
- package/dist/src/provider-profile-fields.js +1 -0
- package/dist/src/provider-session-ownership.js +2 -2
- package/dist/src/provider-webdriver.js +2 -1
- package/dist/src/providers.js +1 -1
- package/dist/src/proxy.js +1 -1
- package/dist/src/quality-warnings.js +1 -1
- package/dist/src/react-native.js +1 -1
- package/dist/src/record-runtime.js +1 -1
- package/dist/src/record-trace.js +1 -1
- package/dist/src/recording.js +1 -1
- package/dist/src/rect-center.js +1 -1
- package/dist/src/register-builtins.js +1 -1
- package/dist/src/registry.js +39 -39
- package/dist/src/remote-config2.js +1 -1
- package/dist/src/replay-divergence.js +1 -1
- package/dist/src/replay.js +2 -2
- package/dist/src/reporting.js +5 -5
- package/dist/src/request-admission.js +1 -1
- package/dist/src/request-cancel.js +1 -1
- package/dist/src/request-cancellation.js +1 -1
- package/dist/src/request-runtime-binding.js +1 -1
- package/dist/src/result-serialization.js +1 -1
- package/dist/src/retry.js +1 -1
- package/dist/src/runner-artifact-manifest.js +1 -1
- package/dist/src/runner-cache-metadata.js +3 -3
- package/dist/src/runner-client.js +1 -1
- package/dist/src/runner-dev-tools-security.js +1 -1
- package/dist/src/runner-device-readiness.js +1 -1
- package/dist/src/runner-disposal.js +1 -1
- package/dist/src/runner-error-classification.js +2 -2
- package/dist/src/runner-exchange.js +1 -1
- package/dist/src/runner-operations-facade.js +1 -1
- package/dist/src/runner-session-types.js +1 -1
- package/dist/src/runner-start-budget.js +1 -1
- package/dist/src/runner.js +1 -1
- package/dist/src/runtime-admission.js +1 -1
- package/dist/src/runtime-transport-hints.js +1 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/runtime10.js +2 -2
- package/dist/src/runtime2.js +3 -3
- package/dist/src/runtime3.js +1 -1
- package/dist/src/runtime4.js +5 -5
- package/dist/src/runtime5.js +1 -1
- package/dist/src/runtime6.js +1 -1
- package/dist/src/runtime7.js +1 -1
- package/dist/src/runtime8.js +1 -1
- package/dist/src/runtime9.js +1 -1
- package/dist/src/screen-recording-admission-ledger.js +1 -1
- package/dist/src/screen-recording-completion.js +1 -1
- package/dist/src/screen-recording-options.js +1 -1
- package/dist/src/screen-recording-resource-recovery.js +1 -1
- package/dist/src/screen-recording-transport.js +1 -1
- package/dist/src/screenshot-density.js +1 -1
- package/dist/src/screenshot-image.js +1 -1
- package/dist/src/screenshot-result.js +1 -1
- package/dist/src/screenshot-runtime.js +1 -1
- package/dist/src/screenshot.js +1 -1
- package/dist/src/screenshot3.js +1 -1
- package/dist/src/script.js +1 -1
- package/dist/src/scroll-command.js +1 -1
- package/dist/src/scroll-gesture.js +1 -1
- package/dist/src/sdk-batch-runner.js +2 -2
- package/dist/src/sdk-batch.js +2 -2
- package/dist/src/sdk-contracts.d.ts +45 -2
- package/dist/src/sdk-contracts.js +2 -2
- package/dist/src/sdk-device.js +1 -1
- package/dist/src/sdk-io.js +1 -1
- package/dist/src/sdk-remote-config.js +1 -1
- package/dist/src/sdk-selectors.d.ts +33 -4
- package/dist/src/selection.js +0 -0
- 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-allocation.js +1 -1
- package/dist/src/session-artifact-paths.js +1 -1
- package/dist/src/session-repair-tombstone.js +1 -1
- package/dist/src/session-routing.js +1 -1
- package/dist/src/session-runtime-admission.js +1 -1
- package/dist/src/session-snapshot.js +1 -1
- package/dist/src/session-stream.js +27 -0
- package/dist/src/session.js +1 -1
- package/dist/src/session2.js +5 -5
- package/dist/src/settings.js +1 -1
- package/dist/src/settings2.js +1 -1
- package/dist/src/simctl-settings-facade.js +1 -0
- package/dist/src/simctl-settings.js +1 -0
- package/dist/src/simctl.js +1 -1
- package/dist/src/simulator.js +1 -1
- package/dist/src/snapshot-diagnostics.js +1 -1
- package/dist/src/snapshot-runtime-binding.js +1 -1
- package/dist/src/snapshot-runtime.js +1 -1
- package/dist/src/snapshot-state.js +1 -1
- package/dist/src/snapshot2.js +1 -1
- package/dist/src/snapshot3.js +1 -1
- package/dist/src/snapshot4.js +1 -1
- package/dist/src/src.js +1 -1
- package/dist/src/src7.js +1 -1
- package/dist/src/src8.js +2 -2
- package/dist/src/store.js +1 -0
- package/dist/src/string-enum.js +1 -1
- package/dist/src/surface-snapshot.js +1 -1
- package/dist/src/takeover.js +1 -1
- package/dist/src/target-evidence.js +1 -1
- package/dist/src/telemetry.js +1 -1
- package/dist/src/text-entry-focus.js +1 -1
- package/dist/src/tool-provider.js +1 -1
- package/dist/src/tool-provider2.js +1 -1
- package/dist/src/toolchain-probe.js +2 -2
- package/dist/src/touch-runtime.js +1 -1
- package/dist/src/touch-runtime2.js +1 -1
- package/dist/src/transport.js +2 -2
- package/dist/src/tv-remote.js +1 -1
- package/dist/src/type-text-runtime.js +1 -1
- package/dist/src/update-check.js +1 -1
- package/dist/src/vars.js +1 -1
- package/dist/src/verified-file.js +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/video.js +1 -1
- package/dist/src/viewport.js +1 -1
- package/dist/src/web.js +1 -1
- package/dist/src/web2.js +1 -1
- package/dist/src/webdriver-source.js +1 -1
- package/package.json +5 -1
- package/server.json +2 -2
- package/android/ime-helper/dist/agent-device-android-ime-helper-0.21.20.apk.sha256 +0 -1
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.20.apk +0 -0
- package/android/snapshot-helper/dist/agent-device-android-snapshot-helper-0.21.20.apk.sha256 +0 -1
- package/dist/src/api.js +0 -3
- package/dist/src/daemon-process.js +0 -1
- package/dist/src/multitouch-support.js +0 -1
- package/dist/src/parameterized-recorded-fill.js +0 -1
- package/dist/src/runner-startup-transport.js +0 -1
- package/dist/src/runner-xctestrun.js +0 -1
- package/dist/src/session-store.js +0 -4
|
@@ -66,6 +66,10 @@ int main(int argc, const char *argv[]) {
|
|
|
66
66
|
@property(nonatomic, assign) NSUInteger firstAlertActions;
|
|
67
67
|
@property(nonatomic, assign) NSUInteger replacementAlertActions;
|
|
68
68
|
@property(nonatomic, strong) UILabel *textEntryWriteBackStatus;
|
|
69
|
+
@property(nonatomic, strong) UILabel *textEntryDigitSlots;
|
|
70
|
+
// Filled from the fixture field's own delegate callbacks so a test can tell that a gesture really
|
|
71
|
+
// took focus from an input that has stopped answering the accessibility query that found it.
|
|
72
|
+
@property(nonatomic, strong) UILabel *textEntryFocusWitness;
|
|
69
73
|
@property(nonatomic, assign) NSUInteger textEntryWriteBacks;
|
|
70
74
|
@property(nonatomic, copy, nullable) NSString *textEntryRenderedValue;
|
|
71
75
|
@property(nonatomic, assign) NSTimeInterval textEntryLastEditTime;
|
|
@@ -73,6 +77,7 @@ int main(int argc, const char *argv[]) {
|
|
|
73
77
|
@property(nonatomic, assign) NSUInteger textEntryBurstEdits;
|
|
74
78
|
@property(nonatomic, assign) NSTimeInterval textEntryBurstMinGap;
|
|
75
79
|
@property(nonatomic, assign) NSTimeInterval textEntryAcknowledgeWindowSeconds;
|
|
80
|
+
@property(nonatomic, strong) NSLayoutConstraint *textEntryFieldTop;
|
|
76
81
|
@property(nonatomic, assign) BOOL alertFixtureStarted;
|
|
77
82
|
@property(nonatomic, strong) NSTimer *alertActivationBusyBackstop;
|
|
78
83
|
@property(nonatomic, strong) NSTimer *alertBannerRepost;
|
|
@@ -81,6 +86,17 @@ int main(int argc, const char *argv[]) {
|
|
|
81
86
|
#if TARGET_OS_IOS
|
|
82
87
|
@interface AgentDeviceRunnerViewController () <UNUserNotificationCenterDelegate>
|
|
83
88
|
@end
|
|
89
|
+
|
|
90
|
+
// A one-time-code field that announces how many digits it holds instead of the digits, the way
|
|
91
|
+
// React Native OTP inputs set `accessibilityValue` on their hidden text input.
|
|
92
|
+
@interface AgentDeviceDigitCountTextField : UITextField
|
|
93
|
+
@end
|
|
94
|
+
|
|
95
|
+
@implementation AgentDeviceDigitCountTextField
|
|
96
|
+
- (NSString *)accessibilityValue {
|
|
97
|
+
return [NSString stringWithFormat:@"%lu of 6 digits", (unsigned long)self.text.length];
|
|
98
|
+
}
|
|
99
|
+
@end
|
|
84
100
|
#endif
|
|
85
101
|
|
|
86
102
|
@implementation AgentDeviceRunnerViewController
|
|
@@ -265,6 +281,11 @@ static NSTimeInterval AgentDeviceTextEntryAcknowledgeWindow(void) {
|
|
|
265
281
|
// Edits further apart than this belong to different bursts: one runner command's characters arrive
|
|
266
282
|
// well inside it, and two commands are separated by at least a commit-wait poll and a status read.
|
|
267
283
|
static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
|
|
284
|
+
static const NSUInteger AgentDeviceTextEntryAutoSubmitLength = 6;
|
|
285
|
+
|
|
286
|
+
static const CGFloat AgentDeviceTextEntryFieldTopInset = 24;
|
|
287
|
+
static const CGFloat AgentDeviceTextEntryFieldHeight = 44;
|
|
288
|
+
static const CGFloat AgentDeviceTextEntryNeighbourGap = 16;
|
|
268
289
|
|
|
269
290
|
- (void)agentDeviceTextEntryDidChange:(UITextField *)textField {
|
|
270
291
|
// A field whose app owns its value, the way a controlled React Native `TextInput` does. A burst
|
|
@@ -293,10 +314,56 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
|
|
|
293
314
|
}
|
|
294
315
|
[self updateTextEntryWriteBackStatus];
|
|
295
316
|
}
|
|
317
|
+
self.textEntryDigitSlots.text = textField.text;
|
|
296
318
|
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-disappear-after-input"] &&
|
|
297
319
|
textField.text.length > 0) {
|
|
298
320
|
[textField removeFromSuperview];
|
|
299
321
|
}
|
|
322
|
+
// An auto-submitting one-time-code field: the last digit navigates away, to a screen whose own
|
|
323
|
+
// input takes the code field's place or to one without an input. `replace-after-input` is the
|
|
324
|
+
// same navigation landing before the code is complete.
|
|
325
|
+
NSArray<NSString *> *arguments = NSProcessInfo.processInfo.arguments;
|
|
326
|
+
BOOL codeComplete = textField.text.length >= AgentDeviceTextEntryAutoSubmitLength;
|
|
327
|
+
if (textField.superview == nil) {
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if ((codeComplete && [arguments containsObject:@"--agent-device-text-entry-auto-submit"]) ||
|
|
331
|
+
(textField.text.length > 0 && [arguments containsObject:@"--agent-device-text-entry-replace-after-input"])) {
|
|
332
|
+
UITextField *nextScreenField = [[UITextField alloc] initWithFrame:textField.frame];
|
|
333
|
+
if (![arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
|
|
334
|
+
nextScreenField.accessibilityIdentifier = @"agent-device-auto-submit-next-screen-input";
|
|
335
|
+
}
|
|
336
|
+
nextScreenField.borderStyle = UITextBorderStyleRoundedRect;
|
|
337
|
+
[textField.superview addSubview:nextScreenField];
|
|
338
|
+
[textField removeFromSuperview];
|
|
339
|
+
[nextScreenField becomeFirstResponder];
|
|
340
|
+
} else if (codeComplete &&
|
|
341
|
+
[arguments containsObject:@"--agent-device-text-entry-auto-submit-without-successor"]) {
|
|
342
|
+
[textField removeFromSuperview];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Stops answering the element-type query that found it, the moment it takes focus, under
|
|
347
|
+
// `--agent-device-text-entry-unqueryable-on-focus`. #3060's Flutter password field reaches this shape
|
|
348
|
+
// because its two accessibility channels disagree once focused — `TextField` through the legacy
|
|
349
|
+
// attributes, `Other` through the modern ones — and what either channel leaves behind is the same:
|
|
350
|
+
// the tap's element answers the query that resolved it up to the gesture and refuses it after, so a
|
|
351
|
+
// read of it after dispatching records an XCTest failure instead of describing the tap that already
|
|
352
|
+
// landed. The field keeps first-responder, so a test still needs a witness that is not the
|
|
353
|
+
// accessibility tree to tell the gesture landed: `agent-device-text-entry-focus` is a label this
|
|
354
|
+
// method fills in from the field's own delegate callback.
|
|
355
|
+
- (void)agentDeviceTextEntryBecomesUnqueryable:(UITextField *)textField {
|
|
356
|
+
self.textEntryFocusWitness.text = @"focus";
|
|
357
|
+
textField.hidden = YES;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Moves the field up by its own height plus the gap below it when it gains focus, the way keyboard
|
|
361
|
+
// avoidance or a bottom sheet extending above the keyboard does, so the neighbouring field slides
|
|
362
|
+
// into the point the focus tap hit.
|
|
363
|
+
- (void)agentDeviceTextEntryDidBeginEditing:(UITextField *)textField {
|
|
364
|
+
self.textEntryFieldTop.constant =
|
|
365
|
+
AgentDeviceTextEntryFieldTopInset - AgentDeviceTextEntryFieldHeight - AgentDeviceTextEntryNeighbourGap;
|
|
366
|
+
[self.view layoutIfNeeded];
|
|
300
367
|
}
|
|
301
368
|
#endif
|
|
302
369
|
|
|
@@ -339,8 +406,13 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
|
|
|
339
406
|
}
|
|
340
407
|
|
|
341
408
|
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-regression"]) {
|
|
342
|
-
|
|
343
|
-
|
|
409
|
+
BOOL digitCountValue =
|
|
410
|
+
[NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-digit-count-value"];
|
|
411
|
+
UITextField *textField =
|
|
412
|
+
digitCountValue ? [[AgentDeviceDigitCountTextField alloc] init] : [[UITextField alloc] init];
|
|
413
|
+
if (![NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unnamed-input"]) {
|
|
414
|
+
textField.accessibilityIdentifier = @"agent-device-hardware-keyboard-input";
|
|
415
|
+
}
|
|
344
416
|
textField.borderStyle = UITextBorderStyleRoundedRect;
|
|
345
417
|
// An empty input view keeps the software keyboard down, which is the hardware-keyboard responder
|
|
346
418
|
// these routes are addressed to. `--agent-device-text-entry-soft-keyboard` leaves the real input
|
|
@@ -354,12 +426,46 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
|
|
|
354
426
|
forControlEvents:UIControlEventEditingChanged];
|
|
355
427
|
textField.translatesAutoresizingMaskIntoConstraints = NO;
|
|
356
428
|
[self.view addSubview:textField];
|
|
429
|
+
self.textEntryFieldTop = [textField.topAnchor constraintEqualToAnchor:label.bottomAnchor
|
|
430
|
+
constant:AgentDeviceTextEntryFieldTopInset];
|
|
357
431
|
[NSLayoutConstraint activateConstraints:@[
|
|
358
432
|
[textField.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
|
359
|
-
|
|
433
|
+
self.textEntryFieldTop,
|
|
360
434
|
[textField.widthAnchor constraintEqualToConstant:240],
|
|
361
|
-
[textField.heightAnchor constraintEqualToConstant:
|
|
435
|
+
[textField.heightAnchor constraintEqualToConstant:AgentDeviceTextEntryFieldHeight],
|
|
362
436
|
]];
|
|
437
|
+
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-unqueryable-on-focus"]) {
|
|
438
|
+
self.textEntryFocusWitness = [[UILabel alloc] init];
|
|
439
|
+
self.textEntryFocusWitness.accessibilityIdentifier = @"agent-device-text-entry-focus";
|
|
440
|
+
self.textEntryFocusWitness.translatesAutoresizingMaskIntoConstraints = NO;
|
|
441
|
+
[self.view addSubview:self.textEntryFocusWitness];
|
|
442
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
443
|
+
[self.textEntryFocusWitness.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
|
444
|
+
[self.textEntryFocusWitness.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:24],
|
|
445
|
+
]];
|
|
446
|
+
[textField addTarget:self
|
|
447
|
+
action:@selector(agentDeviceTextEntryBecomesUnqueryable:)
|
|
448
|
+
forControlEvents:UIControlEventEditingDidBegin];
|
|
449
|
+
}
|
|
450
|
+
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-moves-on-focus"]) {
|
|
451
|
+
textField.text = @"stale";
|
|
452
|
+
[textField addTarget:self
|
|
453
|
+
action:@selector(agentDeviceTextEntryDidBeginEditing:)
|
|
454
|
+
forControlEvents:UIControlEventEditingDidBegin];
|
|
455
|
+
UITextField *neighbour = [[UITextField alloc] init];
|
|
456
|
+
neighbour.accessibilityIdentifier = @"agent-device-text-entry-neighbour";
|
|
457
|
+
neighbour.borderStyle = UITextBorderStyleRoundedRect;
|
|
458
|
+
neighbour.text = @"neighbour";
|
|
459
|
+
neighbour.translatesAutoresizingMaskIntoConstraints = NO;
|
|
460
|
+
[self.view addSubview:neighbour];
|
|
461
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
462
|
+
[neighbour.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
|
463
|
+
[neighbour.topAnchor constraintEqualToAnchor:textField.bottomAnchor
|
|
464
|
+
constant:AgentDeviceTextEntryNeighbourGap],
|
|
465
|
+
[neighbour.widthAnchor constraintEqualToConstant:240],
|
|
466
|
+
[neighbour.heightAnchor constraintEqualToConstant:AgentDeviceTextEntryFieldHeight],
|
|
467
|
+
]];
|
|
468
|
+
}
|
|
363
469
|
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-text-entry-app-owned-value"]) {
|
|
364
470
|
self.textEntryAcknowledgeWindowSeconds = AgentDeviceTextEntryAcknowledgeWindow();
|
|
365
471
|
// Reports how many edits this app rendered and how many writes it had to make because a
|
|
@@ -375,6 +481,17 @@ static const NSTimeInterval AgentDeviceTextEntryBurstBreakSeconds = 1.0;
|
|
|
375
481
|
]];
|
|
376
482
|
[self updateTextEntryWriteBackStatus];
|
|
377
483
|
}
|
|
484
|
+
if (digitCountValue) {
|
|
485
|
+
// The slots an OTP screen renders next to its input: the digits the field really holds.
|
|
486
|
+
self.textEntryDigitSlots = [[UILabel alloc] init];
|
|
487
|
+
self.textEntryDigitSlots.accessibilityIdentifier = @"agent-device-text-entry-digit-slots";
|
|
488
|
+
self.textEntryDigitSlots.translatesAutoresizingMaskIntoConstraints = NO;
|
|
489
|
+
[self.view addSubview:self.textEntryDigitSlots];
|
|
490
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
491
|
+
[self.textEntryDigitSlots.centerXAnchor constraintEqualToAnchor:self.view.centerXAnchor],
|
|
492
|
+
[self.textEntryDigitSlots.topAnchor constraintEqualToAnchor:textField.bottomAnchor constant:12],
|
|
493
|
+
]];
|
|
494
|
+
}
|
|
378
495
|
}
|
|
379
496
|
|
|
380
497
|
if ([NSProcessInfo.processInfo.arguments containsObject:@"--agent-device-crowded-screen"]) {
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.h
CHANGED
|
@@ -5,6 +5,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
5
5
|
FOUNDATION_EXPORT NSInteger RunnerControlledScrollFrameCount(double durationMs);
|
|
6
6
|
FOUNDATION_EXPORT double RunnerControlledScrollProgress(double t);
|
|
7
7
|
|
|
8
|
+
typedef NS_ENUM(NSInteger, RunnerTapSynthesisStatus) {
|
|
9
|
+
RunnerTapSynthesisStatusSucceeded,
|
|
10
|
+
RunnerTapSynthesisStatusDeadlineExceeded,
|
|
11
|
+
RunnerTapSynthesisStatusFailed,
|
|
12
|
+
};
|
|
13
|
+
|
|
8
14
|
@interface RunnerSynthesizedGesture : NSObject
|
|
9
15
|
|
|
10
16
|
// `resolvedWindow` is the app window the caller already resolved for the gesture's reference
|
|
@@ -26,10 +32,12 @@ FOUNDATION_EXPORT double RunnerControlledScrollProgress(double t);
|
|
|
26
32
|
y2:(double)y2
|
|
27
33
|
durationMs:(double)durationMs;
|
|
28
34
|
|
|
29
|
-
+ (
|
|
35
|
+
+ (RunnerTapSynthesisStatus)synthesizeTapWithApplication:(id)application
|
|
30
36
|
resolvedWindow:(id _Nullable)resolvedWindow
|
|
31
37
|
x:(double)x
|
|
32
|
-
y:(double)y
|
|
38
|
+
y:(double)y
|
|
39
|
+
deadline:(NSDate * _Nullable)deadline
|
|
40
|
+
errorMessage:(NSString * _Nullable * _Nullable)errorMessage;
|
|
33
41
|
|
|
34
42
|
// Each pointer is an ordered array of { x, y, offsetMs } samples. The first sample
|
|
35
43
|
// starts contact; subsequent samples move it; all pointers lift at their final offset.
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerSynthesizedGesture.m
CHANGED
|
@@ -75,7 +75,13 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
|
|
|
75
75
|
NSString *recordName,
|
|
76
76
|
RunnerDragPointerPathFactory pathFactory
|
|
77
77
|
);
|
|
78
|
-
static
|
|
78
|
+
static RunnerTapSynthesisStatus RunnerTrySynthesizeTap(
|
|
79
|
+
id application,
|
|
80
|
+
id _Nullable resolvedWindow,
|
|
81
|
+
CGPoint point,
|
|
82
|
+
NSDate * _Nullable deadline,
|
|
83
|
+
NSString * _Nullable * _Nullable errorMessage
|
|
84
|
+
);
|
|
79
85
|
// XCTest's proven swipe profile reaches the endpoint in 100 ms, then holds for the planned
|
|
80
86
|
// fling duration. Fast movement is what lets UIKit distinguish a fling from a timed pan.
|
|
81
87
|
static const NSTimeInterval RunnerSwipeMovementDurationSeconds = 0.1;
|
|
@@ -132,14 +138,20 @@ static id RunnerTapPointerPath(
|
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
140
|
|
|
135
|
-
+ (
|
|
141
|
+
+ (RunnerTapSynthesisStatus)synthesizeTapWithApplication:(id)application
|
|
136
142
|
resolvedWindow:(id _Nullable)resolvedWindow
|
|
137
143
|
x:(double)x
|
|
138
|
-
y:(double)y
|
|
144
|
+
y:(double)y
|
|
145
|
+
deadline:(NSDate * _Nullable)deadline
|
|
146
|
+
errorMessage:(NSString * _Nullable * _Nullable)errorMessage {
|
|
147
|
+
if (errorMessage != NULL) *errorMessage = nil;
|
|
139
148
|
@try {
|
|
140
|
-
return RunnerTrySynthesizeTap(application, resolvedWindow, CGPointMake(x, y));
|
|
149
|
+
return RunnerTrySynthesizeTap(application, resolvedWindow, CGPointMake(x, y), deadline, errorMessage);
|
|
141
150
|
} @catch (NSException *exception) {
|
|
142
|
-
|
|
151
|
+
if (errorMessage != NULL) {
|
|
152
|
+
*errorMessage = RunnerFormatXCTestException(exception, @"private XCTest event synthesis failed");
|
|
153
|
+
}
|
|
154
|
+
return RunnerTapSynthesisStatusFailed;
|
|
143
155
|
}
|
|
144
156
|
}
|
|
145
157
|
|
|
@@ -227,7 +239,13 @@ static NSString * _Nullable RunnerTrySynthesizeDrag(
|
|
|
227
239
|
return RunnerSynthesizeEventRecord(&bridge, record);
|
|
228
240
|
}
|
|
229
241
|
|
|
230
|
-
static
|
|
242
|
+
static RunnerTapSynthesisStatus RunnerTrySynthesizeTap(
|
|
243
|
+
id application,
|
|
244
|
+
id _Nullable resolvedWindow,
|
|
245
|
+
CGPoint point,
|
|
246
|
+
NSDate * _Nullable deadline,
|
|
247
|
+
NSString * _Nullable * _Nullable errorMessage
|
|
248
|
+
) {
|
|
231
249
|
RunnerGestureEventBridge bridge;
|
|
232
250
|
id record = nil;
|
|
233
251
|
NSString *error = RunnerCreateEventRecord(
|
|
@@ -237,14 +255,23 @@ static NSString * _Nullable RunnerTrySynthesizeTap(id application, id _Nullable
|
|
|
237
255
|
&bridge,
|
|
238
256
|
&record
|
|
239
257
|
);
|
|
240
|
-
if (error != nil)
|
|
258
|
+
if (error != nil) {
|
|
259
|
+
if (errorMessage != NULL) *errorMessage = error;
|
|
260
|
+
return RunnerTapSynthesisStatusFailed;
|
|
261
|
+
}
|
|
241
262
|
|
|
242
263
|
id path = RunnerTapPointerPath(&bridge, point);
|
|
243
264
|
if (path == nil) {
|
|
244
|
-
|
|
265
|
+
if (errorMessage != NULL) *errorMessage = @"private XCTest event synthesis failed: could not create pointer path";
|
|
266
|
+
return RunnerTapSynthesisStatusFailed;
|
|
245
267
|
}
|
|
246
268
|
((RunnerMsgSendAddPath)objc_msgSend)(record, bridge.core.addPathSelector, path);
|
|
247
|
-
|
|
269
|
+
if (deadline != nil && deadline.timeIntervalSinceNow <= 0) {
|
|
270
|
+
return RunnerTapSynthesisStatusDeadlineExceeded;
|
|
271
|
+
}
|
|
272
|
+
error = RunnerSynthesizeEventRecord(&bridge, record);
|
|
273
|
+
if (errorMessage != NULL) *errorMessage = error;
|
|
274
|
+
return error == nil ? RunnerTapSynthesisStatusSucceeded : RunnerTapSynthesisStatusFailed;
|
|
248
275
|
}
|
|
249
276
|
|
|
250
277
|
static NSString * _Nullable RunnerResolveGestureEventBridge(
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Alert.swift
CHANGED
|
@@ -138,12 +138,40 @@ extension RunnerTests {
|
|
|
138
138
|
#if !os(tvOS)
|
|
139
139
|
guard !frame.isEmpty else { return }
|
|
140
140
|
#endif
|
|
141
|
+
#if os(iOS)
|
|
142
|
+
let postEventWaitSelector = NSSelectorFromString("_waitForQuiescenceAsPreEvent:")
|
|
143
|
+
guard alert.ownerApp.responds(to: postEventWaitSelector) else {
|
|
144
|
+
outcome = .unsupported(
|
|
145
|
+
message: "alert activation could not wait for post-event quiescence",
|
|
146
|
+
hint: "Inspect the current alert before deciding whether to act again."
|
|
147
|
+
)
|
|
148
|
+
return
|
|
149
|
+
}
|
|
150
|
+
switch synthesizedTapAt(app: alert.ownerApp, x: frame.midX, y: frame.midY, deadline: deadline) {
|
|
151
|
+
case .performed?:
|
|
152
|
+
typealias WaitForQuiescence = @convention(c) (NSObject, Selector, Bool) -> Void
|
|
153
|
+
let waitForQuiescence = unsafeBitCast(
|
|
154
|
+
alert.ownerApp.method(for: postEventWaitSelector),
|
|
155
|
+
to: WaitForQuiescence.self
|
|
156
|
+
)
|
|
157
|
+
waitForQuiescence(alert.ownerApp, postEventWaitSelector, false)
|
|
158
|
+
outcome = .performed
|
|
159
|
+
case .unsupported(let message, _)?:
|
|
160
|
+
outcome = .unsupported(
|
|
161
|
+
message: message,
|
|
162
|
+
hint: "Inspect the current alert before deciding whether to act again."
|
|
163
|
+
)
|
|
164
|
+
case nil:
|
|
165
|
+
break
|
|
166
|
+
}
|
|
167
|
+
#else
|
|
141
168
|
outcome = activateElement(
|
|
142
169
|
app: alert.ownerApp,
|
|
143
170
|
element: button,
|
|
144
171
|
action: "alert \(action)",
|
|
145
172
|
resolvedFrame: frame
|
|
146
173
|
)
|
|
174
|
+
#endif
|
|
147
175
|
}
|
|
148
176
|
}
|
|
149
177
|
return outcome
|
package/dist/apple/runner/AgentDeviceRunner/AgentDeviceRunnerUITests/RunnerTests+Models.swift
CHANGED
|
@@ -470,6 +470,13 @@ struct DataPayload: Codable {
|
|
|
470
470
|
var keyboardMinY: Double?
|
|
471
471
|
var maestroNonHittableCoordinateFallbackUsed: Bool?
|
|
472
472
|
var textEntryRoute: String?
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
var verification: String?
|
|
476
|
+
var requested: String?
|
|
477
|
+
var before: String?
|
|
478
|
+
var after: String?
|
|
479
|
+
var target: TextEntryVerificationTargetPayload?
|
|
473
480
|
var runnerFatal: Bool?
|
|
474
481
|
var runnerFatalReason: String?
|
|
475
482
|
|
|
@@ -487,6 +494,26 @@ struct DataPayload: Codable {
|
|
|
487
494
|
}
|
|
488
495
|
|
|
489
496
|
|
|
497
|
+
struct TextEntryVerificationTargetPayload: Codable, Equatable {
|
|
498
|
+
let resourceId: String?
|
|
499
|
+
let className: String?
|
|
500
|
+
let packageName: String?
|
|
501
|
+
let rect: SnapshotRect
|
|
502
|
+
|
|
503
|
+
enum CodingKeys: String, CodingKey {
|
|
504
|
+
case resourceId, className, packageName, rect
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
func encode(to encoder: Encoder) throws {
|
|
508
|
+
var container = encoder.container(keyedBy: CodingKeys.self)
|
|
509
|
+
try container.encode(resourceId, forKey: .resourceId)
|
|
510
|
+
try container.encode(className, forKey: .className)
|
|
511
|
+
try container.encode(packageName, forKey: .packageName)
|
|
512
|
+
try container.encode(rect, forKey: .rect)
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
|
|
490
517
|
|
|
491
518
|
|
|
492
519
|
|
|
@@ -68,6 +68,7 @@ extension RunnerTests {
|
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
|
|
71
|
+
|
|
71
72
|
func awaitSynthesizedReplacementCommit(
|
|
72
73
|
app: XCUIApplication,
|
|
73
74
|
target: TextEntryTarget,
|
|
@@ -82,6 +83,7 @@ extension RunnerTests {
|
|
|
82
83
|
let outcome = Self.awaitSynthesizedReplacementCommitOutcome(
|
|
83
84
|
expectedText: expectedText,
|
|
84
85
|
placeholder: ingredients.placeholder,
|
|
86
|
+
fieldIsSecure: ingredients.fieldIsSecure,
|
|
85
87
|
now: { Date() },
|
|
86
88
|
observe: ingredients.observe,
|
|
87
89
|
waitForNextObservation: ingredients.waitForNextObservation
|
|
@@ -132,13 +134,16 @@ extension RunnerTests {
|
|
|
132
134
|
expectedText: String
|
|
133
135
|
) -> (
|
|
134
136
|
placeholder: String?,
|
|
137
|
+
fieldIsSecure: Bool,
|
|
135
138
|
observe: () -> String?,
|
|
136
139
|
waitForNextObservation: () -> Void
|
|
137
140
|
) {
|
|
138
|
-
let
|
|
141
|
+
let field = resolveTextEntryElement(app: app, target: target)
|
|
142
|
+
let placeholder = field?.placeholderValue
|
|
139
143
|
let waitStartedAt = Date()
|
|
140
144
|
return (
|
|
141
145
|
placeholder: placeholder,
|
|
146
|
+
fieldIsSecure: field?.elementType == .secureTextField,
|
|
142
147
|
observe: {
|
|
143
148
|
let observedText = self.editableTextValue(
|
|
144
149
|
for: self.resolveTextEntryElement(app: app, target: target),
|
|
@@ -143,9 +143,14 @@ extension RunnerTests {
|
|
|
143
143
|
_ app: XCUIApplication,
|
|
144
144
|
kind: SynthesizedGesturePolicyKind,
|
|
145
145
|
context: SynthesizedCoordinateContext?,
|
|
146
|
-
synthesize: () -> RunnerInteractionOutcome
|
|
146
|
+
synthesize: () -> RunnerInteractionOutcome?
|
|
147
147
|
) -> SynthesizedGestureAttempt {
|
|
148
|
-
|
|
148
|
+
var outcome: RunnerInteractionOutcome?
|
|
149
|
+
let timing = measureGesture { outcome = synthesize() }
|
|
150
|
+
guard let outcome else {
|
|
151
|
+
logSynthesizedGesturePolicyDecision(kind: kind, context: context, fallbackAttempted: false)
|
|
152
|
+
return .refused(timing: timing, message: "synthesized gesture deadline expired before dispatch", hint: nil)
|
|
153
|
+
}
|
|
149
154
|
guard case .unsupported(let message, let hint) = outcome else {
|
|
150
155
|
logSynthesizedGesturePolicyDecision(kind: kind, context: context, fallbackAttempted: false)
|
|
151
156
|
return .performed(timing: timing)
|
|
@@ -95,8 +95,9 @@ extension RunnerTests {
|
|
|
95
95
|
app: XCUIApplication,
|
|
96
96
|
x: Double,
|
|
97
97
|
y: Double,
|
|
98
|
-
context: SynthesizedCoordinateContext? = nil
|
|
99
|
-
|
|
98
|
+
context: SynthesizedCoordinateContext? = nil,
|
|
99
|
+
deadline: Date? = nil
|
|
100
|
+
) -> RunnerInteractionOutcome? {
|
|
100
101
|
#if os(iOS)
|
|
101
102
|
guard x.isFinite, y.isFinite else {
|
|
102
103
|
return .unsupported(
|
|
@@ -126,18 +127,31 @@ extension RunnerTests {
|
|
|
126
127
|
referenceFrame: context.referenceFrame,
|
|
127
128
|
orientation: orientation
|
|
128
129
|
)
|
|
129
|
-
|
|
130
|
+
var message: NSString?
|
|
131
|
+
let status = RunnerSynthesizedGesture.synthesizeTap(
|
|
130
132
|
withApplication: app,
|
|
131
133
|
resolvedWindow: context.resolvedWindow,
|
|
132
134
|
x: Double(point.x),
|
|
133
|
-
y: Double(point.y)
|
|
134
|
-
|
|
135
|
+
y: Double(point.y),
|
|
136
|
+
deadline: deadline,
|
|
137
|
+
errorMessage: &message
|
|
138
|
+
)
|
|
139
|
+
switch status {
|
|
140
|
+
case .succeeded:
|
|
141
|
+
return .performed
|
|
142
|
+
case .deadlineExceeded:
|
|
143
|
+
return nil
|
|
144
|
+
case .failed:
|
|
135
145
|
return .unsupported(
|
|
136
|
-
message: message,
|
|
146
|
+
message: message as String? ?? "private XCTest event synthesis failed",
|
|
137
147
|
hint: "Falling back to XCTest coordinate tap may be slower and can still need a healthy accessibility tree."
|
|
138
148
|
)
|
|
149
|
+
@unknown default:
|
|
150
|
+
return .unsupported(
|
|
151
|
+
message: "private XCTest event synthesis returned an unknown status",
|
|
152
|
+
hint: "Inspect the current app before deciding whether to act again."
|
|
153
|
+
)
|
|
139
154
|
}
|
|
140
|
-
return .performed
|
|
141
155
|
#elseif os(tvOS)
|
|
142
156
|
return .unsupported(
|
|
143
157
|
message: "coordinate tap is not supported on tvOS; move focus with swipe or scroll, then select the focused element",
|
|
@@ -101,8 +101,27 @@ extension RunnerTests {
|
|
|
101
101
|
var warmsUpField = false
|
|
102
102
|
|
|
103
103
|
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
static let maxClearPassCount = 4
|
|
109
|
+
|
|
110
|
+
static let unreadableClearPassCount = 2
|
|
111
|
+
|
|
112
|
+
|
|
104
113
|
var synthesizeCallCount: Int {
|
|
105
|
-
replacesExistingText ? 2 : 1
|
|
114
|
+
replacesExistingText ? 2 * Self.maxClearPassCount + 1 : 1
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
var keystrokeCount: Int {
|
|
119
|
+
characterCount + clearReadCount
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
var clearReadCount: Int {
|
|
124
|
+
replacesExistingText ? Self.maxClearPassCount : 0
|
|
106
125
|
}
|
|
107
126
|
}
|
|
108
127
|
|
|
@@ -129,8 +148,9 @@ extension RunnerTests {
|
|
|
129
148
|
var seconds: TimeInterval {
|
|
130
149
|
steps.reduce(0) { total, step in
|
|
131
150
|
total
|
|
132
|
-
+ Double(step.
|
|
151
|
+
+ Double(step.keystrokeCount) * TextEntryTiming.synthesizedCharacterInterval
|
|
133
152
|
+ Double(step.synthesizeCallCount) * TextEntryTiming.synthesizeCallOverhead
|
|
153
|
+
+ Double(step.clearReadCount) * TextEntryTiming.synthesizedClearReadAllowance
|
|
134
154
|
+ step.pauseAfterSeconds
|
|
135
155
|
}
|
|
136
156
|
}
|
|
@@ -226,6 +246,48 @@ extension RunnerTests {
|
|
|
226
246
|
case stop
|
|
227
247
|
}
|
|
228
248
|
|
|
249
|
+
|
|
250
|
+
enum ClearedFieldRead: Equatable {
|
|
251
|
+
case text(String)
|
|
252
|
+
|
|
253
|
+
case unreadable
|
|
254
|
+
|
|
255
|
+
case unavailable
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
enum SynthesizedClearOutcome: Equatable {
|
|
260
|
+
case cleared
|
|
261
|
+
|
|
262
|
+
case unverified
|
|
263
|
+
|
|
264
|
+
case notCleared
|
|
265
|
+
|
|
266
|
+
case stopped
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
static func clearForSynthesizedReplacement(
|
|
272
|
+
clearOnce: () -> SynthesizedStepDispatch,
|
|
273
|
+
read: () -> ClearedFieldRead
|
|
274
|
+
) -> SynthesizedClearOutcome {
|
|
275
|
+
for pass in 1...SynthesizedTextPlan.Step.maxClearPassCount {
|
|
276
|
+
guard clearOnce() == .posted else { return .stopped }
|
|
277
|
+
switch read() {
|
|
278
|
+
case .text(let value) where value.isEmpty:
|
|
279
|
+
return .cleared
|
|
280
|
+
case .text:
|
|
281
|
+
continue
|
|
282
|
+
case .unreadable:
|
|
283
|
+
if pass >= SynthesizedTextPlan.Step.unreadableClearPassCount { return .unverified }
|
|
284
|
+
case .unavailable:
|
|
285
|
+
return .notCleared
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return .notCleared
|
|
289
|
+
}
|
|
290
|
+
|
|
229
291
|
struct SynthesizedPlanRun {
|
|
230
292
|
let postedCharacterCount: Int
|
|
231
293
|
|
|
@@ -298,32 +360,68 @@ extension RunnerTests {
|
|
|
298
360
|
)
|
|
299
361
|
)
|
|
300
362
|
}
|
|
363
|
+
var clearOutcome: SynthesizedClearOutcome?
|
|
301
364
|
|
|
302
365
|
|
|
303
366
|
let synthesisAvailable = runSynthesizedTextPlan(
|
|
304
367
|
plan,
|
|
305
368
|
text: request.text,
|
|
306
369
|
post: { slice, step in
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
370
|
+
func enter(_ text: String, replacingExistingText: Bool) -> SynthesizedStepDispatch {
|
|
371
|
+
switch request.synthesizer.enterText(
|
|
372
|
+
app: request.app,
|
|
373
|
+
text: text,
|
|
374
|
+
replacingExistingText: replacingExistingText
|
|
375
|
+
) {
|
|
376
|
+
case .continueTyping:
|
|
377
|
+
return .posted
|
|
378
|
+
case .fallback:
|
|
379
|
+
return .stop
|
|
380
|
+
case .raise(let message):
|
|
381
|
+
NSException(
|
|
382
|
+
name: NSExceptionName.internalInconsistencyException,
|
|
383
|
+
reason: message ?? "private XCTest text synthesis failed"
|
|
384
|
+
).raise()
|
|
385
|
+
return .stop
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if step.replacesExistingText {
|
|
389
|
+
var passes = 0
|
|
390
|
+
let outcome = Self.clearForSynthesizedReplacement(
|
|
391
|
+
clearOnce: {
|
|
392
|
+
passes += 1
|
|
393
|
+
return enter(XCUIKeyboardKey.delete.rawValue, replacingExistingText: true)
|
|
394
|
+
},
|
|
395
|
+
read: { self.clearedFieldRead(app: request.app, target: request.target) }
|
|
396
|
+
)
|
|
397
|
+
clearOutcome = outcome
|
|
398
|
+
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_CLEAR passes=%d outcome=%@", passes, String(describing: outcome))
|
|
399
|
+
guard outcome == .cleared || outcome == .unverified else { return .stop }
|
|
322
400
|
}
|
|
401
|
+
return enter(slice, replacingExistingText: false)
|
|
323
402
|
},
|
|
324
403
|
|
|
325
404
|
waitAfterWarmupCharacter: { _ in }
|
|
326
405
|
)
|
|
406
|
+
if clearOutcome == .notCleared {
|
|
407
|
+
logTextEntryPhase(
|
|
408
|
+
commandId: request.commandId,
|
|
409
|
+
phase: "total",
|
|
410
|
+
startedAt: request.startedAt,
|
|
411
|
+
chars: request.text.count,
|
|
412
|
+
mode: .replacement
|
|
413
|
+
)
|
|
414
|
+
return .completed(
|
|
415
|
+
TextEntryResult(
|
|
416
|
+
verified: nil,
|
|
417
|
+
repaired: false,
|
|
418
|
+
expectedText: request.text,
|
|
419
|
+
observedText: nil,
|
|
420
|
+
textEntryRoute: "synthesized-first-responder-replacement",
|
|
421
|
+
failure: .clearNotObserved
|
|
422
|
+
)
|
|
423
|
+
)
|
|
424
|
+
}
|
|
327
425
|
if synthesisAvailable.stoppedEarly {
|
|
328
426
|
NSLog("AGENT_DEVICE_RUNNER_TEXT_ENTRY_ROUTE route=verified-fallback reason=synthesis-unavailable")
|
|
329
427
|
guard let point = request.target.refreshPoint else { return .notApplicable }
|
|
@@ -432,6 +530,7 @@ extension RunnerTests {
|
|
|
432
530
|
static func awaitSynthesizedReplacementCommitOutcome(
|
|
433
531
|
expectedText: String,
|
|
434
532
|
placeholder: String?,
|
|
533
|
+
fieldIsSecure: Bool = false,
|
|
435
534
|
stallBudget: TimeInterval = TextEntryTiming.synthesizedCommitStallTimeout,
|
|
436
535
|
ceiling: TimeInterval = TextEntryTiming.synthesizedCommitCeiling,
|
|
437
536
|
now: () -> Date = { Date() },
|
|
@@ -440,6 +539,11 @@ extension RunnerTests {
|
|
|
440
539
|
) -> SynthesizedTextCommitOutcome {
|
|
441
540
|
|
|
442
541
|
|
|
542
|
+
if fieldIsSecure {
|
|
543
|
+
return .unobservable
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
|
|
443
547
|
|
|
444
548
|
if Self.textMatchesPlaceholder(expectedText, placeholder: placeholder) {
|
|
445
549
|
return .notObserved
|