@deeeed/metamask-harness 0.47.4 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -2
- package/adapters/extension/lib/macos-focus.cjs +2 -2
- package/adapters/extension/live.sh +6 -8
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
- package/adapters/shared/ensure-runner-deps.sh +6 -0
- package/dist/adapters/extension/runtime-decision.js +18 -1
- package/dist/adapters/extension/runtime.js +5 -5
- package/dist/adapters/extension/surface.js +1 -0
- package/dist/app-lifecycle.js +3 -1
- package/dist/command-contract.js +2 -0
- package/dist/commands/call.js +1 -8
- package/dist/commands/device-target.js +12 -3
- package/dist/commands/doctor.js +7 -5
- package/dist/commands/fixtures.js +1 -1
- package/dist/commands/help.js +17 -5
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/manifest.js +3 -1
- package/dist/commands/mobile-device-view.js +13 -3
- package/dist/commands/parse-args.js +2 -0
- package/dist/commands/run-engine.js +29 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/runtime-launch.js +10 -1
- package/dist/commands/status.js +8 -4
- package/dist/devices.js +31 -9
- package/dist/execution-provenance.js +8 -7
- package/dist/funding-execution-context.js +62 -17
- package/dist/heal-bounds.js +3 -3
- package/dist/live-adapter-contract.js +4 -0
- package/dist/manifest.js +2 -13
- package/dist/metamask-action-validation.js +3 -2
- package/dist/mm-harness-cli.js +4 -2
- package/dist/runner.js +14 -3
- package/docs/RECIPES.md +135 -24
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
- package/library/actions/core/perps/_controller.mjs +24 -24
- package/library/actions/core/perps/assert_orders.mjs +3 -5
- package/library/actions/core/perps/assert_positions.mjs +3 -5
- package/library/actions/core/perps/close_orders.mjs +39 -19
- package/library/actions/core/perps/close_positions.mjs +53 -12
- package/library/actions/core/perps/edit_order.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +3 -4
- package/library/actions/core/perps/ensure_positions.mjs +4 -5
- package/library/actions/core/perps/place_order.mjs +7 -14
- package/library/actions/core/perps/read_account.mjs +2 -0
- package/library/actions/core/perps/read_orders.mjs +2 -0
- package/library/actions/core/perps/read_positions.mjs +3 -1
- package/library/actions/core/perps/read_snapshot.mjs +3 -0
- package/library/actions/core/perps/start_state.mjs +3 -14
- package/library/actions/core/perps/teardown_state.mjs +3 -14
- package/library/actions/core/perps/update_position_tpsl.mjs +13 -19
- package/library/actions/core/wallet/list_accounts.mjs +3 -0
- package/library/actions/extension/analytics/consent.mjs +2 -0
- package/library/actions/extension/analytics/set_consent.mjs +2 -0
- package/library/actions/extension/assets/home-token-identity.mjs +31 -0
- package/library/actions/extension/assets/import_custom_token.mjs +63 -79
- package/library/actions/extension/assets/open_details.mjs +19 -7
- package/library/actions/extension/assets/prepare_send.mjs +1 -0
- package/library/actions/extension/assets/read_details.mjs +62 -29
- package/library/actions/extension/assets/read_visible_state.mjs +5 -1
- package/library/actions/extension/assets/review_send.mjs +1 -0
- package/library/actions/extension/assets/select_network_scope.mjs +17 -9
- package/library/actions/extension/assets/send.mjs +2 -0
- package/library/actions/extension/assets/set_custom_gas.mjs +1 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +86 -17
- package/library/actions/extension/assets/verify_sorting.mjs +63 -14
- package/library/actions/extension/deeplink/open.mjs +1 -0
- package/library/actions/extension/networks/add_chainlist.mjs +2 -0
- package/library/actions/extension/networks/add_custom.mjs +21 -17
- package/library/actions/extension/networks/custom_network.mjs +10 -3
- package/library/actions/extension/networks/read_visible_state.mjs +2 -0
- package/library/actions/extension/networks/remove_custom.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +37 -6
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +5 -2
- package/library/actions/extension/performance/measure_detached_dom.mjs +5 -2
- package/library/actions/extension/performance/measure_navigation_memory.mjs +5 -2
- package/library/actions/extension/perps/assert_orders.mjs +1 -0
- package/library/actions/extension/perps/assert_positions.mjs +1 -0
- package/library/actions/extension/perps/assert_visible_consistency.mjs +11 -4
- package/library/actions/extension/perps/close_orders.mjs +4 -1
- package/library/actions/extension/perps/close_positions.mjs +1 -0
- package/library/actions/extension/perps/close_visible_position.mjs +160 -11
- package/library/actions/extension/perps/compare_provider_market.mjs +2 -0
- package/library/actions/extension/perps/edit_margin.mjs +3 -1
- package/library/actions/extension/perps/ensure_orders.mjs +1 -0
- package/library/actions/extension/perps/ensure_positions.mjs +1 -0
- package/library/actions/extension/perps/mutation-receipt.mjs +54 -3
- package/library/actions/extension/perps/open_balance_action.mjs +17 -7
- package/library/actions/extension/perps/open_position_action.mjs +1 -0
- package/library/actions/extension/perps/perps.mjs +550 -69
- package/library/actions/extension/perps/place_order.mjs +1 -0
- package/library/actions/extension/perps/read_eligibility.mjs +1 -0
- package/library/actions/extension/perps/read_funds_confirmation.mjs +10 -0
- package/library/actions/extension/perps/read_orders.mjs +1 -0
- package/library/actions/extension/perps/read_positions.mjs +1 -0
- package/library/actions/extension/perps/read_snapshot.mjs +4 -1
- package/library/actions/extension/perps/read_visible_state.mjs +12 -6
- package/library/actions/extension/perps/search_markets.mjs +8 -2
- package/library/actions/extension/perps/select_activity_filter.mjs +2 -1
- package/library/actions/extension/perps/select_market_filter.mjs +75 -45
- package/library/actions/extension/perps/set_market_favorite.mjs +7 -3
- package/library/actions/extension/perps/state.mjs +2 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +3 -1
- package/library/actions/extension/perps/visible-mutation-identity.mjs +6 -2
- package/library/actions/extension/platform/cdp.mjs +50 -142
- package/library/actions/extension/settings/set_basic_functionality.mjs +1 -0
- package/library/actions/extension/swap_bridge/quote-state.mjs +56 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +5 -1
- package/library/actions/extension/swap_bridge/select_assets.mjs +12 -1
- package/library/actions/extension/swap_bridge/set_amount.mjs +11 -8
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +18 -18
- package/library/actions/extension/swap_bridge/set_slippage.mjs +1 -9
- package/library/actions/extension/ui/locators.mjs +1 -0
- package/library/actions/extension/ui/navigate.mjs +60 -14
- package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -0
- package/library/actions/extension/wallet/import.mjs +2 -0
- package/library/actions/extension/wallet/list_accounts.mjs +1 -0
- package/library/actions/extension/wallet/lock.mjs +4 -1
- package/library/actions/extension/wallet/read_state.mjs +1 -0
- package/library/actions/extension/wallet/reset.mjs +2 -0
- package/library/actions/extension/wallet/secret-input.mjs +2 -0
- package/library/actions/extension/wallet/select_account.mjs +2 -0
- package/library/actions/extension/wallet/setup.mjs +1 -0
- package/library/actions/extension/wallet/state.mjs +2 -0
- package/library/actions/extension/wallet/visible-session.mjs +2 -0
- package/library/actions/mobile/analytics/consent-settings.mjs +1 -0
- package/library/actions/mobile/analytics/set_consent.mjs +1 -0
- package/library/actions/mobile/app/network-control.mjs +2 -0
- package/library/actions/mobile/app/network.mjs +1 -0
- package/library/actions/mobile/app/network_assert.mjs +1 -0
- package/library/actions/mobile/app/network_capture.mjs +1 -0
- package/library/actions/mobile/assets/import_custom_token.mjs +3 -0
- package/library/actions/mobile/assets/open_details.mjs +1 -0
- package/library/actions/mobile/assets/read_details.mjs +1 -0
- package/library/actions/mobile/assets/read_visible_state.mjs +4 -1
- package/library/actions/mobile/assets/set_token_visibility.mjs +15 -9
- package/library/actions/mobile/assets/verify_sorting.mjs +90 -7
- package/library/actions/mobile/deeplink/open.mjs +1 -0
- package/library/actions/mobile/networks/add_custom.mjs +1 -0
- package/library/actions/mobile/networks/network-management.mjs +23 -15
- package/library/actions/mobile/networks/read_visible_state.mjs +1 -0
- package/library/actions/mobile/networks/remove_custom.mjs +7 -2
- package/library/actions/mobile/perps/assert_orders.mjs +1 -0
- package/library/actions/mobile/perps/assert_positions.mjs +1 -0
- package/library/actions/mobile/perps/capture_performance.mjs +1 -0
- package/library/actions/mobile/perps/close_orders.mjs +1 -0
- package/library/actions/mobile/perps/close_positions.mjs +1 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +1 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +1 -0
- package/library/actions/mobile/perps/measure_homepage_visible.mjs +1 -0
- package/library/actions/mobile/perps/performance-capture.mjs +2 -0
- package/library/actions/mobile/perps/perps.mjs +8 -1
- package/library/actions/mobile/perps/place_order.mjs +1 -0
- package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +2 -0
- package/library/actions/mobile/perps/read-visible-state-loop.mjs +3 -0
- package/library/actions/mobile/perps/read_orders.mjs +1 -0
- package/library/actions/mobile/perps/read_positions.mjs +1 -0
- package/library/actions/mobile/perps/read_visible_state.mjs +1 -0
- package/library/actions/mobile/perps/search_markets.mjs +1 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +17 -19
- package/library/actions/mobile/platform/bridge.mjs +16 -4
- package/library/actions/mobile/platform/native-session-name.mjs +1 -0
- package/library/actions/mobile/platform/native-session.mjs +28 -17
- package/library/actions/mobile/platform/observe-ui.mjs +4 -2
- package/library/actions/mobile/platform/tool-paths.mjs +1 -0
- package/library/actions/mobile/swap_bridge/native-session.mjs +1 -0
- package/library/actions/mobile/swap_bridge/read_visible_state.mjs +1 -0
- package/library/actions/mobile/swap_bridge/select_assets.mjs +2 -0
- package/library/actions/mobile/swap_bridge/set_amount.mjs +1 -0
- package/library/actions/mobile/swap_bridge/set_max_amount.mjs +1 -0
- package/library/actions/mobile/swap_bridge/set_slippage.mjs +1 -0
- package/library/actions/mobile/swap_bridge/submit_transaction.mjs +2 -0
- package/library/actions/mobile/ui/locators.mjs +1 -0
- package/library/actions/mobile/ui/native-navigation.mjs +2 -0
- package/library/actions/mobile/ui/navigate.mjs +3 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +2 -0
- package/library/actions/mobile/wallet/home.mjs +1 -0
- package/library/actions/mobile/wallet/import.mjs +2 -0
- package/library/actions/mobile/wallet/list_accounts.mjs +1 -0
- package/library/actions/mobile/wallet/lock.mjs +1 -0
- package/library/actions/mobile/wallet/native-ui.mjs +2 -0
- package/library/actions/mobile/wallet/read_state.mjs +2 -0
- package/library/actions/mobile/wallet/reset-helper.mjs +1 -0
- package/library/actions/mobile/wallet/reset.mjs +1 -0
- package/library/actions/mobile/wallet/select_account.mjs +2 -0
- package/library/actions/mobile/wallet/setup.mjs +2 -0
- package/library/actions/shared/analytics/assert_events.mjs +3 -0
- package/library/actions/shared/analytics/consent.mjs +3 -0
- package/library/actions/shared/analytics/read_events.mjs +2 -0
- package/library/actions/shared/analytics/start_capture.mjs +3 -0
- package/library/actions/shared/app/network-artifact.mjs +2 -0
- package/library/actions/shared/app/network-assert.mjs +2 -0
- package/library/actions/shared/assets/visible-state.mjs +2 -0
- package/library/actions/shared/deeplink/url.mjs +3 -0
- package/library/actions/shared/networks/visible-state.mjs +2 -0
- package/library/actions/shared/perps/visible-state.mjs +16 -3
- package/library/actions/shared/swap-bridge/transaction.mjs +3 -0
- package/library/actions/shared/swap-bridge/visible-state.mjs +2 -0
- package/library/actions/shared/ui/locators.mjs +2 -0
- package/library/actions/shared/wallet/import-source.mjs +3 -0
- package/library/manifests/core.action-manifest.json +34 -2
- package/library/manifests/extension.action-manifest.json +50 -17
- package/library/manifests/mobile.action-manifest.json +11 -10
- package/library/recipes/core/perps/smoke.recipe.json +23 -0
- package/library/recipes/core/perps/snapshot.recipe.json +26 -1
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
- package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
- package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
- package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
- package/library/recipes/mobile/app/lifecycle-smoke.recipe.json +89 -0
- package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
- package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
- package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
- package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
- package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
- package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
- package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
- package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
- package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
- package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
- package/package.json +3 -3
- package/site/assets/help-recipes.json +220 -27
- package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
- package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
- package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
- package/library/recipes/core/perps/read-markets.recipe.json +0 -36
- package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
- package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
- package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
- package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
- package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
- package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
- package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +0 -69
- package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
- package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
- package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
- package/library/recipes/mobile/perps/performance.recipe.json +0 -722
- package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
- package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
- package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
- package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
- package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
- package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
- package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
- package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
- package/library/recipes/perps/lifecycle.recipe.json +0 -144
- package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
- package/library/recipes/perps/release-watchlist.recipe.json +0 -111
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
- /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Delete a custom network through native Settings and wait for its row to disappear.
|
|
1
2
|
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
3
|
import {
|
|
3
4
|
closeNativeSession,
|
|
@@ -32,6 +33,8 @@ runAdapter(async (input) => {
|
|
|
32
33
|
(snapshot) => {
|
|
33
34
|
const container = nativeNode(snapshot, 'network-details-view-container');
|
|
34
35
|
if (!container) return null;
|
|
36
|
+
const remove = nativeNode(snapshot, 'network-details-view-remove-network-button');
|
|
37
|
+
if (remove?.enabled === true && remove.visibleToUser !== false && remove.hittable === true) return remove;
|
|
35
38
|
const labeled = snapshot.nodes.find((node) =>
|
|
36
39
|
node.hittable === true && /delete|remove|trash/iu.test(nativeLabel(node))
|
|
37
40
|
);
|
|
@@ -84,8 +87,10 @@ runAdapter(async (input) => {
|
|
|
84
87
|
(snapshot) => nativeNode(snapshot, 'networks-settings-container'),
|
|
85
88
|
'Mobile Networks settings did not reopen after deletion.',
|
|
86
89
|
);
|
|
87
|
-
|
|
88
|
-
|
|
90
|
+
const removalDeadline = Date.now() + timeoutMs;
|
|
91
|
+
while (await findNetwork(session, network, chainId, Math.max(1, removalDeadline - Date.now()))) {
|
|
92
|
+
if (Date.now() >= removalDeadline) throw new Error(`${network} (${chainId}) remained after deletion.`);
|
|
93
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
89
94
|
}
|
|
90
95
|
return {
|
|
91
96
|
action: input.action,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Capture a bounded section of Perps console logs and write performance measurements and validation artifacts.
|
|
2
|
+
// Persist the start offset and file identity to reject replaced or rewritten logs when the capture ends.
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { mkdir, open, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Implement Mobile Perps navigation, trading mutations, assertions, and setup or cleanup through the bridge.
|
|
2
|
+
// Strategy getters used during cleanup can settle tracked collateral.
|
|
1
3
|
import { pathToFileURL } from 'node:url';
|
|
2
4
|
import {
|
|
3
5
|
MOBILE_BRIDGE_RESULT_ERROR_CODES,
|
|
@@ -1770,6 +1772,9 @@ async function applyTutorialState(input, config) {
|
|
|
1770
1772
|
|
|
1771
1773
|
|
|
1772
1774
|
export async function startState(input) {
|
|
1775
|
+
if (input.node?.account !== undefined && (typeof input.node.account !== 'string' || !input.node.account.trim())) {
|
|
1776
|
+
throw new Error('Perps start state requires a non-empty account name or address when account is supplied.\nNext: mm-harness actions metamask.perps.start_state --adapter mobile --json');
|
|
1777
|
+
}
|
|
1773
1778
|
const params = paramsForState(input);
|
|
1774
1779
|
const config = applyTradingStatePolicy(
|
|
1775
1780
|
mergeStateConfig(profileDefaults(params.profile), params),
|
|
@@ -1828,7 +1833,9 @@ export async function startState(input) {
|
|
|
1828
1833
|
node: {
|
|
1829
1834
|
...accountInput.node,
|
|
1830
1835
|
action: 'metamask.wallet.select_account',
|
|
1831
|
-
|
|
1836
|
+
...(/^0x[0-9a-f]{40}$/iu.test(config.account.trim())
|
|
1837
|
+
? { address: config.account.trim() }
|
|
1838
|
+
: { name: config.account.trim() }),
|
|
1832
1839
|
},
|
|
1833
1840
|
})
|
|
1834
1841
|
: { skipped: true };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Poll native screen and accessibility observations until Perps state satisfies the requested checks.
|
|
2
|
+
// Return normalized state or report the last assertion or observation failure at the deadline.
|
|
1
3
|
import { observeNativeUi } from '../platform/observe-ui.mjs';
|
|
2
4
|
import {
|
|
3
5
|
normalizeVisiblePerpsState,
|
|
@@ -47,6 +49,7 @@ export async function readVisiblePerpsState(
|
|
|
47
49
|
truncated: visible.truncated,
|
|
48
50
|
}, options, 'mobile'),
|
|
49
51
|
proofPath: 'native-accessibility',
|
|
52
|
+
observationProvider: visible.provider,
|
|
50
53
|
};
|
|
51
54
|
} catch (error) {
|
|
52
55
|
lastAssertionError = error;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Set a visible market's watchlist state through native controls and verify the result.
|
|
2
|
+
// With require_unchanged, fail on a mismatched state without toggling the control.
|
|
1
3
|
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
4
|
import {
|
|
3
5
|
closeNativeSession,
|
|
@@ -11,7 +13,14 @@ const FAVORITE_IDS = [
|
|
|
11
13
|
'perps-pro-market-header-favorite-button',
|
|
12
14
|
];
|
|
13
15
|
|
|
14
|
-
function favoriteControl(snapshot) {
|
|
16
|
+
function favoriteControl(snapshot, market) {
|
|
17
|
+
const subtitle = nativeLabel(
|
|
18
|
+
nativeNode(snapshot, 'perps-market-header-subtitle')
|
|
19
|
+
?? nativeNode(snapshot, 'perps-pro-market-header-subtitle'),
|
|
20
|
+
).toUpperCase();
|
|
21
|
+
if (!subtitle.startsWith(`${market}-`)) {
|
|
22
|
+
throw new Error(`Visible Perps market ${JSON.stringify(subtitle)} does not match ${market}.`);
|
|
23
|
+
}
|
|
15
24
|
for (const testId of FAVORITE_IDS) {
|
|
16
25
|
const node = nativeNode(snapshot, testId);
|
|
17
26
|
if (/(?:add to|remove from) watchlist/iu.test(nativeLabel(node))) {
|
|
@@ -39,18 +48,11 @@ runAdapter(async (input) => {
|
|
|
39
48
|
if (typeof desired !== 'boolean') {
|
|
40
49
|
throw new Error('metamask.perps.set_market_favorite requires favorite=true or false.');
|
|
41
50
|
}
|
|
42
|
-
|
|
51
|
+
const session = createNativeSession(input, 'Perps', `favorite-${process.pid}`);
|
|
43
52
|
try {
|
|
44
53
|
await session.open();
|
|
45
54
|
let snapshot = await session.snapshot();
|
|
46
|
-
|
|
47
|
-
nativeNode(snapshot, 'perps-market-header-subtitle')
|
|
48
|
-
?? nativeNode(snapshot, 'perps-pro-market-header-subtitle'),
|
|
49
|
-
).toUpperCase();
|
|
50
|
-
if (!subtitle.startsWith(`${market}-`)) {
|
|
51
|
-
throw new Error(`Visible Perps market ${JSON.stringify(subtitle)} does not match ${market}.`);
|
|
52
|
-
}
|
|
53
|
-
let control = favoriteControl(snapshot);
|
|
55
|
+
let control = favoriteControl(snapshot, market);
|
|
54
56
|
const controlDeadline = Date.now() + timeoutMs;
|
|
55
57
|
let lastControls = [];
|
|
56
58
|
while (Date.now() <= controlDeadline && !control) {
|
|
@@ -60,37 +62,33 @@ runAdapter(async (input) => {
|
|
|
60
62
|
.filter((node) => /(?:favorite|mode-toggle|market-header)/iu.test(String(node.identifier ?? '')))
|
|
61
63
|
.map((node) => ({ id: String(node.identifier ?? ''), label: nativeLabel(node) }))
|
|
62
64
|
.slice(0, 20);
|
|
63
|
-
control = favoriteControl(snapshot);
|
|
65
|
+
control = favoriteControl(snapshot, market);
|
|
64
66
|
}
|
|
65
67
|
if (!control) {
|
|
66
68
|
throw new Error(`Perps favorite control is not visible in Lite or Pro mode; observed ${JSON.stringify(lastControls)}.`);
|
|
67
69
|
}
|
|
68
70
|
const before = favoriteState(control);
|
|
71
|
+
if (requireUnchanged && before !== desired) {
|
|
72
|
+
throw new Error(`Perps favorite state for ${market} did not persist across navigation; no change was made.`);
|
|
73
|
+
}
|
|
69
74
|
if (before !== desired) {
|
|
70
75
|
await session.execute('ui.press', {
|
|
71
76
|
test_id: control.testId,
|
|
72
77
|
timeout_ms: timeoutMs,
|
|
73
78
|
settle: false,
|
|
74
79
|
});
|
|
75
|
-
await closeNativeSession(session);
|
|
76
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
77
|
-
session = createNativeSession(input, 'Perps', `favorite-proof-${process.pid}`);
|
|
78
|
-
await session.open();
|
|
79
80
|
}
|
|
80
81
|
const deadline = Date.now() + timeoutMs;
|
|
81
82
|
let after = before;
|
|
82
83
|
while (Date.now() <= deadline) {
|
|
83
84
|
snapshot = await session.snapshot();
|
|
84
|
-
control = favoriteControl(snapshot);
|
|
85
|
+
control = favoriteControl(snapshot, market);
|
|
85
86
|
if (control) after = favoriteState(control);
|
|
86
87
|
if (after === desired) break;
|
|
87
88
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
88
89
|
}
|
|
89
90
|
if (after !== desired) throw new Error(`Perps favorite state did not become ${desired}.`);
|
|
90
91
|
const changed = before !== after;
|
|
91
|
-
if (requireUnchanged && changed) {
|
|
92
|
-
throw new Error(`Perps favorite state for ${market} did not persist across navigation.`);
|
|
93
|
-
}
|
|
94
92
|
return {
|
|
95
93
|
action: input.action,
|
|
96
94
|
market,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Run Mobile actions and device-targeted bridge commands with deadlines and classified errors.
|
|
2
|
+
// Provide runtime evaluation, verified navigation, external deeplink launch, and screenshot capture.
|
|
1
3
|
import { randomUUID } from 'node:crypto';
|
|
2
4
|
import { constants as fsConstants, existsSync } from 'node:fs';
|
|
3
5
|
import { mkdir, mkdtemp, open, readFile, rename, rm, writeFile } from 'node:fs/promises';
|
|
@@ -666,8 +668,16 @@ async function waitForNextRouteProbe(deadline) {
|
|
|
666
668
|
|
|
667
669
|
export async function navigate(input, route, params = {}, expectedRoute) {
|
|
668
670
|
const timeoutMs = Number(input.node?.navigation_timeout_ms ?? 15000);
|
|
669
|
-
|
|
670
|
-
|
|
671
|
+
let requestedRoute = String(expectedRoute ?? route);
|
|
672
|
+
let requestedParams = requestedRoute === String(route) ? params : undefined;
|
|
673
|
+
const nestedTarget = expectedRoute == null && typeof params?.screen === 'string';
|
|
674
|
+
if (nestedTarget) {
|
|
675
|
+
// React Navigation reports the active leaf, not its enclosing navigator.
|
|
676
|
+
while (typeof requestedParams?.screen === 'string') {
|
|
677
|
+
requestedRoute = requestedParams.screen;
|
|
678
|
+
requestedParams = requestedParams.params ?? {};
|
|
679
|
+
}
|
|
680
|
+
}
|
|
671
681
|
const transitionCodes = [];
|
|
672
682
|
let lastTransitionError = null;
|
|
673
683
|
let navigateAttempts = 0;
|
|
@@ -754,13 +764,15 @@ export async function navigate(input, route, params = {}, expectedRoute) {
|
|
|
754
764
|
input,
|
|
755
765
|
['navigate', route, JSON.stringify(params)],
|
|
756
766
|
);
|
|
757
|
-
const verifiedRoute = String(
|
|
767
|
+
const verifiedRoute = nestedTarget ? requestedRoute : String(
|
|
758
768
|
expectedRoute ??
|
|
759
769
|
(navigation && typeof navigation === 'object' && navigation.navigated
|
|
760
770
|
? navigation.navigated
|
|
761
771
|
: route),
|
|
762
772
|
);
|
|
763
|
-
const verifiedParams =
|
|
773
|
+
const verifiedParams = nestedTarget
|
|
774
|
+
? requestedParams
|
|
775
|
+
: verifiedRoute === String(route) ? params : undefined;
|
|
764
776
|
const currentRoute = await waitForRoute(
|
|
765
777
|
input,
|
|
766
778
|
verifiedRoute,
|
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
// Create device-targeted native UI sessions for actions and accessibility snapshots.
|
|
2
|
+
// Opening an instrumented iOS session hides the accessibility-masking HUD.
|
|
1
3
|
import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
|
|
2
4
|
import { createAgentDeviceClient } from 'agent-device';
|
|
3
5
|
|
|
6
|
+
import { resolveMobileTarget } from './bridge.mjs';
|
|
7
|
+
import { hideAccessibilityMaskingHud } from './observe-ui.mjs';
|
|
8
|
+
|
|
4
9
|
import {
|
|
5
10
|
nativeAgentDeviceStateDir,
|
|
6
11
|
nativeSessionName,
|
|
@@ -8,17 +13,27 @@ import {
|
|
|
8
13
|
|
|
9
14
|
export function createNativeSession(input, domain, sessionSuffix = '') {
|
|
10
15
|
const env = { ...process.env, ...(input.context?.env ?? {}) };
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
16
|
+
const target = resolveMobileTarget(input);
|
|
17
|
+
const platform = target.requestedPlatform || (target.adbSerial ? 'android' : 'ios');
|
|
18
|
+
const device = String(platform === 'android'
|
|
19
|
+
? target.adbSerial ?? ''
|
|
20
|
+
: input.node?.sim_udid ?? env.SIM_UDID ?? target.iosSimulator ?? '').trim();
|
|
21
|
+
if (!device) throw new Error(`Mobile ${domain} native actions require one explicit ${platform} device. Next: pass --device <device>.`);
|
|
22
|
+
const app = String(platform === 'android'
|
|
23
|
+
? env.ANDROID_PACKAGE_ID ?? 'io.metamask'
|
|
24
|
+
: env.IOS_BUNDLE_ID ?? 'io.metamask.MetaMask');
|
|
14
25
|
const baseSession = nativeSessionName(env, device, app);
|
|
15
26
|
const session = sessionSuffix
|
|
16
27
|
? `${baseSession}-${String(sessionSuffix).replace(/[^A-Za-z0-9._-]/gu, '-')}`.slice(0, 120)
|
|
17
28
|
: baseSession;
|
|
18
29
|
const stateDir = nativeAgentDeviceStateDir(env, input.context.projectRoot, device);
|
|
19
|
-
const client = createAgentDeviceClient({ session, stateDir, lockPolicy: 'reject', lockPlatform:
|
|
20
|
-
const selection = {
|
|
21
|
-
|
|
30
|
+
const client = createAgentDeviceClient({ session, stateDir, lockPolicy: 'reject', lockPlatform: platform });
|
|
31
|
+
const selection = {
|
|
32
|
+
platform, target: 'mobile', session,
|
|
33
|
+
...(platform === 'android' ? { serial: device }
|
|
34
|
+
: /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(device) ? { udid: device } : { device }),
|
|
35
|
+
};
|
|
36
|
+
const transport = createAgentDeviceUiTransport({ platform, device, app, session, stateDir, client });
|
|
22
37
|
const context = {
|
|
23
38
|
nodeId: String(input.context?.nodeId ?? 'action'),
|
|
24
39
|
projectRoot: input.context.projectRoot,
|
|
@@ -31,23 +46,19 @@ export function createNativeSession(input, domain, sessionSuffix = '') {
|
|
|
31
46
|
selection,
|
|
32
47
|
context,
|
|
33
48
|
app,
|
|
34
|
-
open: () =>
|
|
49
|
+
open: async () => {
|
|
50
|
+
if (platform === 'ios' && env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME !== '1') {
|
|
51
|
+
await hideAccessibilityMaskingHud({ node: input.node }, input.context);
|
|
52
|
+
}
|
|
53
|
+
await transport.open();
|
|
54
|
+
},
|
|
35
55
|
execute: (action, node) => transport.execute(action, node, context),
|
|
36
56
|
snapshot: () => client.capture.snapshot({ ...selection, app, interactiveOnly: false, forceFull: true }),
|
|
37
57
|
};
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
export async function closeNativeSession(session) {
|
|
41
|
-
|
|
42
|
-
await session.transport.close();
|
|
43
|
-
} catch (error) {
|
|
44
|
-
if (!/No active session|SESSION_NOT_FOUND/iu.test(String(error?.message ?? error))) throw error;
|
|
45
|
-
}
|
|
46
|
-
try {
|
|
47
|
-
await session.client.sessions.close();
|
|
48
|
-
} catch (error) {
|
|
49
|
-
if (!/No active session|SESSION_NOT_FOUND/iu.test(String(error?.message ?? error))) throw error;
|
|
50
|
-
}
|
|
61
|
+
await session.transport.close();
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
export function nativeNode(snapshot, identifier) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Read and normalize native accessibility hierarchies into screen and visible-item observations.
|
|
2
|
+
// Attempt to hide the masking HUD first and return observation failures as warnings.
|
|
1
3
|
import { execFile } from 'node:child_process';
|
|
2
4
|
import { promisify } from 'node:util';
|
|
3
5
|
import { createAgentDeviceClient } from 'agent-device';
|
|
@@ -45,7 +47,7 @@ export async function observeNativeUi(payload, context) {
|
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
async function hideAccessibilityMaskingHud(payload, context) {
|
|
50
|
+
export async function hideAccessibilityMaskingHud(payload, context) {
|
|
49
51
|
try {
|
|
50
52
|
await bridgeCommand(
|
|
51
53
|
{
|
|
@@ -429,7 +431,7 @@ function parseAndroidHierarchy(output) {
|
|
|
429
431
|
isActionableRole(role) ||
|
|
430
432
|
attributes.clickable === 'true' ||
|
|
431
433
|
attributes['long-clickable'] === 'true';
|
|
432
|
-
if (isActionable) {
|
|
434
|
+
if (isActionable || item.test_id) {
|
|
433
435
|
if (isVisible) {
|
|
434
436
|
visible.push(item);
|
|
435
437
|
} else {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Select source and destination assets through native token pickers and verify their symbols.
|
|
2
|
+
// Exact asset-ID selection is unsupported.
|
|
1
3
|
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
4
|
import { resolveAssetSelectionOptions } from '../../shared/swap-bridge/transaction.mjs';
|
|
3
5
|
import {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Navigate Android Home, Swap, and Perps through native controls and verify visible destinations.
|
|
2
|
+
// Switch Perps Lite or Pro mode through visible controls on Android and iOS.
|
|
1
3
|
import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
|
|
2
4
|
import { createAgentDeviceClient } from 'agent-device';
|
|
3
5
|
import {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Resolve page intents or raw routes into Mobile navigation, using native controls in opaque mode.
|
|
2
|
+
// Verify requested Perps market mode through visible controls.
|
|
1
3
|
import {
|
|
2
4
|
navigate,
|
|
3
5
|
resolveMobileTarget,
|
|
@@ -12,7 +14,7 @@ import {
|
|
|
12
14
|
const PAGE_ROUTES = {
|
|
13
15
|
home: { route: 'WalletView', params: {} },
|
|
14
16
|
perps: { route: 'PerpsMarketListView', params: {} },
|
|
15
|
-
'perps-market-list': { route: '
|
|
17
|
+
'perps-market-list': { route: 'PerpsTrendingView', params: {} },
|
|
16
18
|
swap: { route: 'BridgeView', params: {} },
|
|
17
19
|
};
|
|
18
20
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Unlock an existing wallet with a supplied or fixture password through the bridge or native UI.
|
|
2
|
+
// Verify wallet readiness and, for bridge sessions, a stable unlocked controller state.
|
|
1
3
|
import { readFile } from 'node:fs/promises';
|
|
2
4
|
import { pathToFileURL } from 'node:url';
|
|
3
5
|
import {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Import a recovery phrase through onboarding or unlock an existing profile, then verify the expected account.
|
|
2
|
+
// Fresh imports also handle consent, interests, and supported onboarding prompts.
|
|
1
3
|
import { createHash } from 'node:crypto';
|
|
2
4
|
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
3
5
|
import os from 'node:os';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Drive Android wallet onboarding, lock, unlock, and reset through native accessibility.
|
|
2
|
+
// Wallet observations can navigate home and open the address list to verify the selected account.
|
|
1
3
|
import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
|
|
2
4
|
import { createAgentDeviceClient } from 'agent-device';
|
|
3
5
|
import {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Report the selected account and route from bridge status or native accessibility.
|
|
2
|
+
// Native reads navigate to wallet home and may open the address list to observe the account.
|
|
1
3
|
import { bridgeCommand, runAdapter, selectBridgeStatusEntry } from '../platform/bridge.mjs';
|
|
2
4
|
import { isOpaqueMobileRuntime, readWalletThroughNativeUi } from './native-ui.mjs';
|
|
3
5
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Reset the wallet through visible UI and verify fresh onboarding using the bridge or native accessibility.
|
|
1
2
|
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
3
|
import { resetMobileWalletThroughUi } from './reset-helper.mjs';
|
|
3
4
|
import { isOpaqueMobileRuntime, resetWalletThroughNativeUi } from './native-ui.mjs';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Validate the wallet fixture, run the Mobile setup script, and verify a selected account.
|
|
2
|
+
// Unlock with the fixture password when setup leaves no selected account observable.
|
|
1
3
|
import { readFile } from 'node:fs/promises';
|
|
2
4
|
import { spawn } from 'node:child_process';
|
|
3
5
|
import path from 'node:path';
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Assert captured event counts and property values within the requested flow window.
|
|
2
|
+
// Observe the settling interval before deciding whether duplicate emissions occurred.
|
|
3
|
+
|
|
1
4
|
import { runAdapter } from './_adapter.mjs';
|
|
2
5
|
import { readCollected } from './collector.mjs';
|
|
3
6
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Normalize analytics consent inputs and reject marketing consent without participation.
|
|
2
|
+
// This helper validates options; platform actions perform the settings changes.
|
|
3
|
+
|
|
1
4
|
export function consentParams(node) {
|
|
2
5
|
const optional = (value, fallback) =>
|
|
3
6
|
value === undefined || value === null || value === '' ? fallback : value;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Normalize platform asset observations and enforce symbol, count and display requirements.
|
|
2
|
+
|
|
1
3
|
function boundedInteger(value, fallback, minimum, maximum, name) {
|
|
2
4
|
const resolved = value == null ? fallback : Number(value);
|
|
3
5
|
if (!Number.isInteger(resolved) || resolved < minimum || resolved > maximum) {
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Validate external HTTPS deeplink inputs and their bounded timeout.
|
|
2
|
+
// Signature authorization belongs to the receiving product, not this URL-shape check.
|
|
3
|
+
|
|
1
4
|
export function externalDeeplinkUrl(node) {
|
|
2
5
|
const raw = node?.url;
|
|
3
6
|
if (typeof raw !== 'string' || raw.trim().length === 0) {
|