@deeeed/metamask-harness 0.47.4 → 0.48.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 +6 -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/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 +43 -13
- package/dist/heal-bounds.js +3 -3
- package/dist/live-adapter-contract.js +4 -0
- package/dist/manifest.js +2 -13
- package/dist/mm-harness-cli.js +2 -1
- package/dist/runner.js +14 -3
- package/docs/RECIPES.md +133 -22
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
- package/library/actions/core/perps/_controller.mjs +10 -0
- package/library/actions/core/perps/close_orders.mjs +13 -17
- package/library/actions/extension/assets/home-token-identity.mjs +30 -0
- package/library/actions/extension/assets/import_custom_token.mjs +61 -79
- package/library/actions/extension/assets/open_details.mjs +18 -7
- package/library/actions/extension/assets/read_details.mjs +61 -29
- package/library/actions/extension/assets/read_visible_state.mjs +4 -1
- package/library/actions/extension/assets/select_network_scope.mjs +16 -9
- package/library/actions/extension/assets/set_token_visibility.mjs +84 -17
- package/library/actions/extension/assets/verify_sorting.mjs +62 -14
- package/library/actions/extension/networks/add_custom.mjs +19 -17
- package/library/actions/extension/networks/custom_network.mjs +8 -3
- package/library/actions/extension/networks/read_visible_state.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +35 -6
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +3 -2
- package/library/actions/extension/performance/measure_detached_dom.mjs +3 -2
- package/library/actions/extension/performance/measure_navigation_memory.mjs +3 -2
- package/library/actions/extension/perps/assert_visible_consistency.mjs +9 -4
- package/library/actions/extension/perps/close_visible_position.mjs +3 -3
- package/library/actions/extension/perps/edit_margin.mjs +1 -1
- package/library/actions/extension/perps/open_balance_action.mjs +16 -7
- package/library/actions/extension/perps/perps.mjs +150 -45
- package/library/actions/extension/perps/read_funds_confirmation.mjs +8 -0
- package/library/actions/extension/perps/read_snapshot.mjs +2 -1
- package/library/actions/extension/perps/read_visible_state.mjs +10 -6
- package/library/actions/extension/perps/search_markets.mjs +7 -2
- package/library/actions/extension/perps/select_activity_filter.mjs +1 -1
- package/library/actions/extension/perps/select_market_filter.mjs +74 -45
- package/library/actions/extension/perps/set_market_favorite.mjs +6 -3
- package/library/actions/extension/perps/state.mjs +1 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +1 -1
- package/library/actions/extension/perps/visible-mutation-identity.mjs +4 -2
- package/library/actions/extension/platform/cdp.mjs +23 -142
- package/library/actions/extension/swap_bridge/quote-state.mjs +54 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +4 -1
- package/library/actions/extension/swap_bridge/select_assets.mjs +11 -1
- package/library/actions/extension/swap_bridge/set_amount.mjs +10 -8
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +17 -18
- package/library/actions/extension/swap_bridge/set_slippage.mjs +0 -9
- package/library/actions/extension/ui/navigate.mjs +58 -14
- package/library/actions/extension/wallet/lock.mjs +2 -1
- package/library/actions/mobile/assets/import_custom_token.mjs +1 -0
- package/library/actions/mobile/assets/read_visible_state.mjs +3 -1
- package/library/actions/mobile/assets/set_token_visibility.mjs +13 -9
- package/library/actions/mobile/networks/network-management.mjs +22 -15
- package/library/actions/mobile/networks/remove_custom.mjs +6 -2
- package/library/actions/mobile/perps/perps.mjs +3 -0
- package/library/actions/mobile/perps/read-visible-state-loop.mjs +1 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +15 -19
- package/library/actions/mobile/platform/native-session.mjs +26 -17
- package/library/actions/mobile/platform/observe-ui.mjs +2 -2
- package/library/actions/mobile/ui/navigate.mjs +1 -1
- package/library/actions/shared/perps/visible-state.mjs +14 -3
- package/library/manifests/core.action-manifest.json +14 -2
- package/library/manifests/extension.action-manifest.json +49 -16
- package/library/manifests/mobile.action-manifest.json +8 -7
- 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.android-smoke.recipe.json +23 -4
- 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 +63 -23
- 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/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,5 +1,7 @@
|
|
|
1
1
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
2
|
import { resolveAmountOptions } from '../../shared/swap-bridge/transaction.mjs';
|
|
3
|
+
import { requireVisibleObservation } from '../../shared/ui/locators.mjs';
|
|
4
|
+
import { readQuoteInputIdentity } from './quote-state.mjs';
|
|
3
5
|
|
|
4
6
|
async function pause(page) {
|
|
5
7
|
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 250))');
|
|
@@ -11,21 +13,11 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
11
13
|
wait_for_quote: input.node?.wait_for_quote,
|
|
12
14
|
timeout_ms: input.node?.timeout_ms,
|
|
13
15
|
});
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const style = getComputedStyle(element);
|
|
20
|
-
const rect = element.getBoundingClientRect();
|
|
21
|
-
if (label !== 'Max' || style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
|
|
22
|
-
element.setAttribute('data-mm-harness-target', marker);
|
|
23
|
-
return '[data-mm-harness-target="' + marker + '"]';
|
|
24
|
-
}
|
|
25
|
-
return null;
|
|
26
|
-
})()`);
|
|
27
|
-
if (!selector) throw new Error('Swap Max control was not visible.');
|
|
28
|
-
await page.click(selector);
|
|
16
|
+
const visible = requireVisibleObservation(await page.observe(['ui.visible']));
|
|
17
|
+
const controls = visible.items.filter((item) =>
|
|
18
|
+
item.label === 'Max' && item.selector && item.enabled !== false);
|
|
19
|
+
if (controls.length !== 1) throw new Error('Swap requires exactly one visible, enabled Max control.');
|
|
20
|
+
await page.click(controls[0].selector);
|
|
29
21
|
const deadline = Date.now() + options.timeoutMs;
|
|
30
22
|
let observed = null;
|
|
31
23
|
while (Date.now() < deadline) {
|
|
@@ -45,14 +37,21 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
45
37
|
noQuote: document.body.innerText.includes("This trade route isn't available right now"),
|
|
46
38
|
};
|
|
47
39
|
})()`);
|
|
40
|
+
if (options.waitForQuote) {
|
|
41
|
+
observed.quoteIdentity = await readQuoteInputIdentity(page);
|
|
42
|
+
if (observed.quoteIdentity.sourceAmount !== observed.sourceAmount || observed.quoteIdentity.destinationAmount !== observed.destinationAmount) observed.quoteIdentity.status = 'stale';
|
|
43
|
+
}
|
|
44
|
+
if (observed.quoteIdentity?.status === 'matching' && !observed.loading && !observed.ctaEnabled && /^Insufficient funds$/iu.test(String(observed.cta))) {
|
|
45
|
+
throw new Error(`Swap Max quote matches the input but cannot be submitted: ${JSON.stringify(observed)}.`);
|
|
46
|
+
}
|
|
48
47
|
if (Number(observed.sourceAmount) > 0
|
|
49
|
-
&& (!options.waitForQuote || observed.noQuote || (!observed.loading && Number(observed.destinationAmount) > 0 && observed.ctaEnabled))) break;
|
|
48
|
+
&& (!options.waitForQuote || observed.noQuote || (observed.quoteIdentity?.status === 'matching' && !observed.loading && Number(observed.destinationAmount) > 0 && observed.ctaEnabled && /^(?:Swap|Bridge)$/iu.test(String(observed.cta))))) break;
|
|
50
49
|
await pause(page);
|
|
51
50
|
}
|
|
52
51
|
if (!(Number(observed?.sourceAmount) > 0)) throw new Error('Swap Max did not populate a positive source amount.');
|
|
53
52
|
if (options.waitForQuote && !observed.noQuote
|
|
54
|
-
&& (observed.loading || !(Number(observed.destinationAmount) > 0) || !observed.ctaEnabled)) {
|
|
55
|
-
throw new Error(`Swap Max quote did not resolve within ${options.timeoutMs}ms.`);
|
|
53
|
+
&& (observed.quoteIdentity?.status !== 'matching' || observed.loading || !(Number(observed.destinationAmount) > 0) || !observed.ctaEnabled || !/^(?:Swap|Bridge)$/iu.test(String(observed.cta)))) {
|
|
54
|
+
throw new Error(`Swap Max quote did not resolve within ${options.timeoutMs}ms: ${JSON.stringify(observed)}.`);
|
|
56
55
|
}
|
|
57
56
|
return { action: input.action, ...observed, proofPath: 'trusted-pointer-visible-ui' };
|
|
58
57
|
}));
|
|
@@ -74,10 +74,8 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
74
74
|
if (!observed?.includes(`${percent}%`)) {
|
|
75
75
|
throw new Error(`Swap quote did not display Slippage: ${percent}%.`);
|
|
76
76
|
}
|
|
77
|
-
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 500))');
|
|
78
77
|
let stableSince = null;
|
|
79
78
|
let quote = null;
|
|
80
|
-
let lastRefreshAt = 0;
|
|
81
79
|
while (Date.now() < deadline) {
|
|
82
80
|
quote = await page.evaluate(`(() => {
|
|
83
81
|
const destination = document.querySelector('[data-testid="to-amount"]');
|
|
@@ -90,13 +88,6 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
90
88
|
ctaEnabled: Boolean(cta && !cta.disabled),
|
|
91
89
|
};
|
|
92
90
|
})()`);
|
|
93
|
-
if (/^(?:Get new quote|Refresh quote)$/iu.test(quote.cta) && quote.ctaEnabled && Date.now() - lastRefreshAt >= 1000) {
|
|
94
|
-
await page.click(dataTestId('bridge-cta-button'));
|
|
95
|
-
lastRefreshAt = Date.now();
|
|
96
|
-
stableSince = null;
|
|
97
|
-
await pause(page);
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
91
|
const ready = !quote.loading && Number(quote.destinationAmount) > 0 && quote.ctaEnabled
|
|
101
92
|
&& /^(?:Swap|Bridge)$/iu.test(quote.cta);
|
|
102
93
|
if (ready) {
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
} from '../platform/cdp.mjs';
|
|
9
9
|
|
|
10
10
|
const BACK_CONTROLS = [
|
|
11
|
+
'custom-token-import-close-button',
|
|
11
12
|
'perps-order-entry-back-button',
|
|
12
13
|
'perps-market-detail-back-button',
|
|
13
14
|
'perps-activity-back-button',
|
|
@@ -23,6 +24,18 @@ const BACK_CONTROLS = [
|
|
|
23
24
|
].map(dataTestId).concat('button[aria-label="Back"]');
|
|
24
25
|
const UI_POLL_MS = 200;
|
|
25
26
|
const PERPS_HOME_SETTLE_MS = 1000;
|
|
27
|
+
const PERPS_MARKET_LIST = [
|
|
28
|
+
'market-list-view',
|
|
29
|
+
'parent-selector-perps-market-list',
|
|
30
|
+
].map(dataTestId).join(',');
|
|
31
|
+
const PERPS_MARKET_DETAIL = [
|
|
32
|
+
'perps-market-detail-page',
|
|
33
|
+
'parent-selector-perps-market-detail',
|
|
34
|
+
].map(dataTestId).join(',');
|
|
35
|
+
const PERPS_ACTIVITY = [
|
|
36
|
+
'perps-activity-page',
|
|
37
|
+
'parent-selector-perps-activity',
|
|
38
|
+
].map(dataTestId).join(',');
|
|
26
39
|
|
|
27
40
|
function text(value) {
|
|
28
41
|
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
@@ -68,6 +81,7 @@ async function hasVisibleSelector(page, selector) {
|
|
|
68
81
|
return page.evaluate(`(() => {
|
|
69
82
|
const element = document.querySelector(${JSON.stringify(selector)});
|
|
70
83
|
if (!element) return false;
|
|
84
|
+
if (element.closest('dialog')?.open === false) return false;
|
|
71
85
|
const style = getComputedStyle(element);
|
|
72
86
|
const rect = element.getBoundingClientRect();
|
|
73
87
|
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
@@ -116,6 +130,13 @@ export async function openHome(page, timeoutMs) {
|
|
|
116
130
|
let pendingActivation;
|
|
117
131
|
|
|
118
132
|
while (Date.now() < deadline) {
|
|
133
|
+
const importNetworkClose = '[data-testid="custom-token-import-network-selector"] button[aria-label="Close"]';
|
|
134
|
+
if (await hasVisibleSelector(page, importNetworkClose)) {
|
|
135
|
+
await activateSemanticControl(page, importNetworkClose);
|
|
136
|
+
steps.push(importNetworkClose);
|
|
137
|
+
await pauseForUi(page);
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
119
140
|
if (await hasVisibleSelector(page, drawerClose)) {
|
|
120
141
|
await activateSemanticControl(page, drawerClose);
|
|
121
142
|
steps.push(drawerClose);
|
|
@@ -274,11 +295,11 @@ export async function openPerpsHome(page, timeoutMs) {
|
|
|
274
295
|
export async function openPerpsMarket(page, market, timeoutMs) {
|
|
275
296
|
const deadline = Date.now() + timeoutMs;
|
|
276
297
|
const normalizedMarket = normalizeMarketSymbol(market.trim());
|
|
277
|
-
if (await hasVisibleSelector(page,
|
|
298
|
+
if (await hasVisibleSelector(page, PERPS_MARKET_LIST)) {
|
|
278
299
|
const marketRow = dataTestId(`market-row-${normalizedMarket.replace(':', '-')}`);
|
|
279
300
|
await page.waitForSelector(marketRow, { timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
280
301
|
const navigation = await activateSemanticControl(page, marketRow);
|
|
281
|
-
await page.waitForSelector(
|
|
302
|
+
await page.waitForSelector(PERPS_MARKET_DETAIL, {
|
|
282
303
|
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
283
304
|
});
|
|
284
305
|
return {
|
|
@@ -308,7 +329,7 @@ export async function openPerpsMarket(page, market, timeoutMs) {
|
|
|
308
329
|
);
|
|
309
330
|
}
|
|
310
331
|
const navigation = await activateSemanticControl(page, marketControl);
|
|
311
|
-
await page.waitForSelector(
|
|
332
|
+
await page.waitForSelector(PERPS_MARKET_DETAIL, {
|
|
312
333
|
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
313
334
|
});
|
|
314
335
|
return {
|
|
@@ -321,7 +342,7 @@ export async function openPerpsMarket(page, market, timeoutMs) {
|
|
|
321
342
|
}
|
|
322
343
|
|
|
323
344
|
export async function openPerpsActivity(page, timeoutMs) {
|
|
324
|
-
if (await hasVisibleSelector(page,
|
|
345
|
+
if (await hasVisibleSelector(page, PERPS_ACTIVITY)) {
|
|
325
346
|
return { method: 'visible-ui', href: await currentHref(page), steps: [] };
|
|
326
347
|
}
|
|
327
348
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -329,7 +350,7 @@ export async function openPerpsActivity(page, timeoutMs) {
|
|
|
329
350
|
const activity = dataTestId('perps-recent-activity-see-all');
|
|
330
351
|
await page.waitForSelector(activity, { timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
331
352
|
const navigation = await activateSemanticControl(page, activity);
|
|
332
|
-
await page.waitForSelector(
|
|
353
|
+
await page.waitForSelector(PERPS_ACTIVITY, {
|
|
333
354
|
timeoutMs: Math.max(1, deadline - Date.now()),
|
|
334
355
|
});
|
|
335
356
|
return {
|
|
@@ -342,7 +363,7 @@ export async function openPerpsActivity(page, timeoutMs) {
|
|
|
342
363
|
}
|
|
343
364
|
|
|
344
365
|
export async function openPerpsMarketList(page, timeoutMs) {
|
|
345
|
-
const marketList =
|
|
366
|
+
const marketList = PERPS_MARKET_LIST;
|
|
346
367
|
if (await hasVisibleSelector(page, marketList)) {
|
|
347
368
|
return { method: 'visible-ui', href: await currentHref(page), steps: [] };
|
|
348
369
|
}
|
|
@@ -417,17 +438,37 @@ export async function openNetwork(page, network, chainId, addIfMissing, timeoutM
|
|
|
417
438
|
throw new Error('Token assets did not become selected before network navigation.');
|
|
418
439
|
}
|
|
419
440
|
}
|
|
420
|
-
await
|
|
421
|
-
|
|
441
|
+
if (!(await hasVisibleSelector(page, dataTestId('home-network-filter-all-default')))) {
|
|
442
|
+
await activateSemanticControl(page, sort);
|
|
443
|
+
steps.push(sort);
|
|
444
|
+
}
|
|
422
445
|
let choice = await waitForFirstVisible(page, [installed, custom, additional, manage], deadline);
|
|
423
446
|
if (!choice) throw new Error(`Network filter did not show ${network} (${chainId}) before timeout.`);
|
|
424
|
-
if (choice
|
|
447
|
+
if (!addIfMissing && (choice === additional || choice === manage)) {
|
|
448
|
+
throw new Error(`Network ${network} (${chainId}) is not added to the wallet.`);
|
|
449
|
+
}
|
|
450
|
+
let added = false;
|
|
451
|
+
if (choice === additional) {
|
|
452
|
+
await page.scroll({ selector: additional, intoView: true });
|
|
453
|
+
await page.waitFor({ selector: additional, expected: 'visible', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
454
|
+
await activateSemanticControl(page, additional);
|
|
455
|
+
steps.push(additional);
|
|
456
|
+
await page.waitFor({ selector: additional, expected: 'hidden', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
457
|
+
await activateSemanticControl(page, sort);
|
|
458
|
+
steps.push(sort);
|
|
459
|
+
choice = await waitForFirstVisible(page, [installed, custom], deadline);
|
|
460
|
+
if (!choice) throw new Error(`Added network ${network} (${chainId}) did not appear in the wallet filter.`);
|
|
461
|
+
await page.scroll({ selector: choice, intoView: true });
|
|
462
|
+
await page.waitFor({ selector: choice, expected: 'visible', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
463
|
+
await activateSemanticControl(page, choice);
|
|
464
|
+
steps.push(choice);
|
|
465
|
+
added = true;
|
|
466
|
+
} else if (choice !== manage) {
|
|
467
|
+
await page.scroll({ selector: choice, intoView: true });
|
|
468
|
+
await page.waitFor({ selector: choice, expected: 'visible', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
425
469
|
await activateSemanticControl(page, choice);
|
|
426
470
|
steps.push(choice);
|
|
427
471
|
} else {
|
|
428
|
-
if (!addIfMissing) {
|
|
429
|
-
throw new Error(`Network ${network} (${chainId}) is not added to the wallet.`);
|
|
430
|
-
}
|
|
431
472
|
await activateSemanticControl(page, manage);
|
|
432
473
|
steps.push(manage);
|
|
433
474
|
const listItem = dataTestId(`network-list-item-eip155:${chainId}`);
|
|
@@ -437,6 +478,7 @@ export async function openNetwork(page, network, chainId, addIfMissing, timeoutM
|
|
|
437
478
|
await activateSemanticControl(page, popular);
|
|
438
479
|
steps.push(popular);
|
|
439
480
|
await page.waitForSelector(listItem, { timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
481
|
+
added = true;
|
|
440
482
|
}
|
|
441
483
|
const back = dataTestId('page-header-back-button');
|
|
442
484
|
await activateSemanticControl(page, back);
|
|
@@ -444,15 +486,17 @@ export async function openNetwork(page, network, chainId, addIfMissing, timeoutM
|
|
|
444
486
|
await page.waitForSelector(sort, { timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
445
487
|
await activateSemanticControl(page, sort);
|
|
446
488
|
steps.push(sort);
|
|
447
|
-
choice = await waitForFirstVisible(page, [installed, custom
|
|
489
|
+
choice = await waitForFirstVisible(page, [installed, custom], deadline);
|
|
448
490
|
if (!choice) throw new Error(`Added network ${network} (${chainId}) did not appear in the wallet filter.`);
|
|
491
|
+
await page.scroll({ selector: choice, intoView: true });
|
|
492
|
+
await page.waitFor({ selector: choice, expected: 'visible', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
449
493
|
await activateSemanticControl(page, choice);
|
|
450
494
|
steps.push(choice);
|
|
451
495
|
}
|
|
452
496
|
while (Date.now() < deadline) {
|
|
453
497
|
const label = await page.evaluate(`String(document.querySelector(${JSON.stringify(sort)})?.innerText || '')`);
|
|
454
498
|
if (label.toLowerCase().includes(network.toLowerCase())) {
|
|
455
|
-
return { method: 'visible-ui', href: await currentHref(page), network, chainId, steps };
|
|
499
|
+
return { method: 'visible-ui', href: await currentHref(page), network, chainId, added, steps };
|
|
456
500
|
}
|
|
457
501
|
await pauseForUi(page);
|
|
458
502
|
}
|
|
@@ -21,6 +21,7 @@ async function clickUncovered(page, selector, deadline) {
|
|
|
21
21
|
point = await page.evaluate(`(() => {
|
|
22
22
|
const element = document.querySelector(${JSON.stringify(selector)});
|
|
23
23
|
if (!element) return null;
|
|
24
|
+
element.scrollIntoView({ block: 'center', inline: 'center' });
|
|
24
25
|
const style = getComputedStyle(element);
|
|
25
26
|
const rect = element.getBoundingClientRect();
|
|
26
27
|
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) return null;
|
|
@@ -74,7 +75,7 @@ export async function lockWallet(page, timeoutMs = 15_000) {
|
|
|
74
75
|
if (Date.now() >= deadline) throw new Error('Wallet lock control did not become visible.');
|
|
75
76
|
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 100))');
|
|
76
77
|
}
|
|
77
|
-
await page
|
|
78
|
+
await clickUncovered(page, LOCK, deadline);
|
|
78
79
|
while (!(await visible(page, UNLOCK_PAGE))) {
|
|
79
80
|
if (Date.now() >= deadline) throw new Error('Wallet did not reach the unlock page after locking.');
|
|
80
81
|
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 100))');
|
|
@@ -17,7 +17,7 @@ runAdapter(async (input) => {
|
|
|
17
17
|
const warning = observed.warnings?.map((entry) => entry.message).filter(Boolean).join('; ');
|
|
18
18
|
throw new Error(`Mobile visible Assets state is unavailable${warning ? `: ${warning}` : '.'}`);
|
|
19
19
|
}
|
|
20
|
-
const items =
|
|
20
|
+
const items = (visible.items ?? [])
|
|
21
21
|
.filter((item) => String(item.test_id ?? '').startsWith('asset-'))
|
|
22
22
|
.map((item) => ({
|
|
23
23
|
testId: item.test_id,
|
|
@@ -32,6 +32,8 @@ runAdapter(async (input) => {
|
|
|
32
32
|
items,
|
|
33
33
|
truncated: visible.truncated,
|
|
34
34
|
}, options, 'mobile'),
|
|
35
|
+
observationProvider: visible.provider ?? screen.provider,
|
|
36
|
+
deviceId: screen.deviceId,
|
|
35
37
|
proofPath: 'native-accessibility',
|
|
36
38
|
};
|
|
37
39
|
});
|
|
@@ -25,25 +25,24 @@ function descendants(snapshot, root) {
|
|
|
25
25
|
|
|
26
26
|
function searchedToken(snapshot, query) {
|
|
27
27
|
const normalized = query.toUpperCase();
|
|
28
|
-
const rows = snapshot.nodes.filter((node) => node.identifier === 'searched-token-result');
|
|
28
|
+
const rows = snapshot.nodes.filter((node) => node.identifier === 'searched-token-result' && node.visibleToUser !== false);
|
|
29
|
+
const matches = [];
|
|
29
30
|
for (const row of rows) {
|
|
30
31
|
const children = descendants(snapshot, row);
|
|
31
|
-
const label = children.map(nativeLabel).find((value) => {
|
|
32
|
+
const label = [nativeLabel(row), ...children.map(nativeLabel)].find((value) => {
|
|
32
33
|
const parts = value.split(',').map((part) => part.trim().toUpperCase());
|
|
33
34
|
return parts.includes(normalized);
|
|
34
35
|
});
|
|
35
36
|
if (label) {
|
|
36
|
-
|
|
37
|
+
matches.push({
|
|
37
38
|
row,
|
|
38
39
|
label,
|
|
39
40
|
imported: row.enabled === false && children.some((node) => /CheckBox$/u.test(String(node.type))),
|
|
40
|
-
};
|
|
41
|
+
});
|
|
41
42
|
}
|
|
42
43
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
);
|
|
46
|
-
return imported ? { row: imported, label: query, imported: true } : null;
|
|
44
|
+
if (matches.length > 1) throw new Error(`Mobile token search matched ${matches.length} rows for ${query}; token identity is ambiguous.\nNext: mm-harness actions --action metamask.assets.set_token_visibility --json`);
|
|
45
|
+
return matches[0] ?? null;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
48
|
function visibleNode(snapshot, identifier) {
|
|
@@ -201,7 +200,9 @@ async function findVisibleAsset(session, query, timeoutMs) {
|
|
|
201
200
|
)).snapshot;
|
|
202
201
|
}
|
|
203
202
|
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
204
|
-
|
|
203
|
+
const matches = snapshot.nodes.filter(node => node.identifier === `asset-${query.toUpperCase()}` && node.visibleToUser !== false);
|
|
204
|
+
if (matches.length > 1) throw new Error(`Mobile token list matched ${matches.length} rows for ${query}; removal is ambiguous.\nNext: mm-harness actions --action metamask.assets.set_token_visibility --json`);
|
|
205
|
+
if (matches.length === 1) return true;
|
|
205
206
|
await session.execute('ui.scroll', { direction: 'down', timeout_ms: timeoutMs, settle: false });
|
|
206
207
|
snapshot = await session.snapshot();
|
|
207
208
|
}
|
|
@@ -271,6 +272,9 @@ async function disableToken(session, query, requireUnchanged, requireChange, tim
|
|
|
271
272
|
}
|
|
272
273
|
|
|
273
274
|
runAdapter(async (input) => {
|
|
275
|
+
if (String(input.node?.asset_id ?? '').trim()) {
|
|
276
|
+
throw new Error('Exact asset-ID corroboration is not supported on Mobile.\nNext: mm-harness actions --adapter mobile --action metamask.assets.set_token_visibility --json');
|
|
277
|
+
}
|
|
274
278
|
const query = String(input.node?.query ?? '').trim();
|
|
275
279
|
const timeoutMs = Number(input.node?.timeout_ms ?? 45_000);
|
|
276
280
|
const requested = input.node?.visible;
|
|
@@ -26,6 +26,10 @@ export async function waitForSnapshot(session, timeoutMs, predicate, message) {
|
|
|
26
26
|
const deadline = Date.now() + timeoutMs;
|
|
27
27
|
while (Date.now() <= deadline) {
|
|
28
28
|
const snapshot = await session.snapshot();
|
|
29
|
+
const labels = snapshot.nodes.map(nativeLabel);
|
|
30
|
+
if (labels.includes('Reload') && labels.includes('Go home') && labels.includes('Tools button')) {
|
|
31
|
+
throw new Error('Mobile network navigation opened the Expo developer menu instead of the requested control.\nNext: mm-harness call ui.key_press key=BACK --adapter mobile');
|
|
32
|
+
}
|
|
29
33
|
const value = predicate(snapshot);
|
|
30
34
|
if (value) return { snapshot, value };
|
|
31
35
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
@@ -42,23 +46,26 @@ async function systemBack(session) {
|
|
|
42
46
|
}
|
|
43
47
|
|
|
44
48
|
export async function openNetworkManager(session, timeoutMs) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (visibleNode(snapshot, IDS.details)) {
|
|
49
|
-
await systemBack(session);
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
if (visibleNode(snapshot, IDS.menu)) {
|
|
53
|
-
await session.execute('ui.press', { test_id: IDS.menu, timeout_ms: timeoutMs, settle: false });
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (IDS.wallet.some((identifier) => nativeNode(snapshot, identifier))) {
|
|
57
|
-
await session.execute('ui.press', { test_id: IDS.hamburger, timeout_ms: timeoutMs, settle: false });
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
49
|
+
const snapshot = await session.snapshot();
|
|
50
|
+
if (visibleNode(snapshot, IDS.manager)) return snapshot;
|
|
51
|
+
if (visibleNode(snapshot, IDS.details)) {
|
|
60
52
|
await systemBack(session);
|
|
53
|
+
return (await waitForSnapshot(
|
|
54
|
+
session, timeoutMs, (current) => visibleNode(current, IDS.manager),
|
|
55
|
+
'Mobile Networks settings did not return from network details.',
|
|
56
|
+
)).snapshot;
|
|
57
|
+
}
|
|
58
|
+
if (!visibleNode(snapshot, IDS.menu)) {
|
|
59
|
+
if (!IDS.wallet.some((identifier) => nativeNode(snapshot, identifier))) {
|
|
60
|
+
throw new Error('Mobile network navigation requires Home, the account menu, or network settings.');
|
|
61
|
+
}
|
|
62
|
+
await session.execute('ui.press', { test_id: IDS.hamburger, timeout_ms: timeoutMs, settle: false });
|
|
63
|
+
await waitForSnapshot(
|
|
64
|
+
session, timeoutMs, (current) => visibleNode(current, IDS.menu),
|
|
65
|
+
'The Mobile account menu did not expose the Networks control after opening.',
|
|
66
|
+
);
|
|
61
67
|
}
|
|
68
|
+
await session.execute('ui.press', { test_id: IDS.menu, timeout_ms: timeoutMs, settle: false });
|
|
62
69
|
return (await waitForSnapshot(
|
|
63
70
|
session,
|
|
64
71
|
timeoutMs,
|
|
@@ -32,6 +32,8 @@ runAdapter(async (input) => {
|
|
|
32
32
|
(snapshot) => {
|
|
33
33
|
const container = nativeNode(snapshot, 'network-details-view-container');
|
|
34
34
|
if (!container) return null;
|
|
35
|
+
const remove = nativeNode(snapshot, 'network-details-view-remove-network-button');
|
|
36
|
+
if (remove?.enabled === true && remove.visibleToUser !== false && remove.hittable === true) return remove;
|
|
35
37
|
const labeled = snapshot.nodes.find((node) =>
|
|
36
38
|
node.hittable === true && /delete|remove|trash/iu.test(nativeLabel(node))
|
|
37
39
|
);
|
|
@@ -84,8 +86,10 @@ runAdapter(async (input) => {
|
|
|
84
86
|
(snapshot) => nativeNode(snapshot, 'networks-settings-container'),
|
|
85
87
|
'Mobile Networks settings did not reopen after deletion.',
|
|
86
88
|
);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
const removalDeadline = Date.now() + timeoutMs;
|
|
90
|
+
while (await findNetwork(session, network, chainId, Math.max(1, removalDeadline - Date.now()))) {
|
|
91
|
+
if (Date.now() >= removalDeadline) throw new Error(`${network} (${chainId}) remained after deletion.`);
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
89
93
|
}
|
|
90
94
|
return {
|
|
91
95
|
action: input.action,
|
|
@@ -1770,6 +1770,9 @@ async function applyTutorialState(input, config) {
|
|
|
1770
1770
|
|
|
1771
1771
|
|
|
1772
1772
|
export async function startState(input) {
|
|
1773
|
+
if (input.node?.account !== undefined && (typeof input.node.account !== 'string' || !input.node.account.trim())) {
|
|
1774
|
+
throw new Error('Perps start state requires a non-empty named account when account is supplied.\nNext: mm-harness actions metamask.perps.start_state --adapter mobile --json');
|
|
1775
|
+
}
|
|
1773
1776
|
const params = paramsForState(input);
|
|
1774
1777
|
const config = applyTradingStatePolicy(
|
|
1775
1778
|
mergeStateConfig(profileDefaults(params.profile), params),
|
|
@@ -11,7 +11,14 @@ const FAVORITE_IDS = [
|
|
|
11
11
|
'perps-pro-market-header-favorite-button',
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
-
function favoriteControl(snapshot) {
|
|
14
|
+
function favoriteControl(snapshot, market) {
|
|
15
|
+
const subtitle = nativeLabel(
|
|
16
|
+
nativeNode(snapshot, 'perps-market-header-subtitle')
|
|
17
|
+
?? nativeNode(snapshot, 'perps-pro-market-header-subtitle'),
|
|
18
|
+
).toUpperCase();
|
|
19
|
+
if (!subtitle.startsWith(`${market}-`)) {
|
|
20
|
+
throw new Error(`Visible Perps market ${JSON.stringify(subtitle)} does not match ${market}.`);
|
|
21
|
+
}
|
|
15
22
|
for (const testId of FAVORITE_IDS) {
|
|
16
23
|
const node = nativeNode(snapshot, testId);
|
|
17
24
|
if (/(?:add to|remove from) watchlist/iu.test(nativeLabel(node))) {
|
|
@@ -39,18 +46,11 @@ runAdapter(async (input) => {
|
|
|
39
46
|
if (typeof desired !== 'boolean') {
|
|
40
47
|
throw new Error('metamask.perps.set_market_favorite requires favorite=true or false.');
|
|
41
48
|
}
|
|
42
|
-
|
|
49
|
+
const session = createNativeSession(input, 'Perps', `favorite-${process.pid}`);
|
|
43
50
|
try {
|
|
44
51
|
await session.open();
|
|
45
52
|
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);
|
|
53
|
+
let control = favoriteControl(snapshot, market);
|
|
54
54
|
const controlDeadline = Date.now() + timeoutMs;
|
|
55
55
|
let lastControls = [];
|
|
56
56
|
while (Date.now() <= controlDeadline && !control) {
|
|
@@ -60,37 +60,33 @@ runAdapter(async (input) => {
|
|
|
60
60
|
.filter((node) => /(?:favorite|mode-toggle|market-header)/iu.test(String(node.identifier ?? '')))
|
|
61
61
|
.map((node) => ({ id: String(node.identifier ?? ''), label: nativeLabel(node) }))
|
|
62
62
|
.slice(0, 20);
|
|
63
|
-
control = favoriteControl(snapshot);
|
|
63
|
+
control = favoriteControl(snapshot, market);
|
|
64
64
|
}
|
|
65
65
|
if (!control) {
|
|
66
66
|
throw new Error(`Perps favorite control is not visible in Lite or Pro mode; observed ${JSON.stringify(lastControls)}.`);
|
|
67
67
|
}
|
|
68
68
|
const before = favoriteState(control);
|
|
69
|
+
if (requireUnchanged && before !== desired) {
|
|
70
|
+
throw new Error(`Perps favorite state for ${market} did not persist across navigation; no change was made.`);
|
|
71
|
+
}
|
|
69
72
|
if (before !== desired) {
|
|
70
73
|
await session.execute('ui.press', {
|
|
71
74
|
test_id: control.testId,
|
|
72
75
|
timeout_ms: timeoutMs,
|
|
73
76
|
settle: false,
|
|
74
77
|
});
|
|
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
78
|
}
|
|
80
79
|
const deadline = Date.now() + timeoutMs;
|
|
81
80
|
let after = before;
|
|
82
81
|
while (Date.now() <= deadline) {
|
|
83
82
|
snapshot = await session.snapshot();
|
|
84
|
-
control = favoriteControl(snapshot);
|
|
83
|
+
control = favoriteControl(snapshot, market);
|
|
85
84
|
if (control) after = favoriteState(control);
|
|
86
85
|
if (after === desired) break;
|
|
87
86
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
88
87
|
}
|
|
89
88
|
if (after !== desired) throw new Error(`Perps favorite state did not become ${desired}.`);
|
|
90
89
|
const changed = before !== after;
|
|
91
|
-
if (requireUnchanged && changed) {
|
|
92
|
-
throw new Error(`Perps favorite state for ${market} did not persist across navigation.`);
|
|
93
|
-
}
|
|
94
90
|
return {
|
|
95
91
|
action: input.action,
|
|
96
92
|
market,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
|
|
2
2
|
import { createAgentDeviceClient } from 'agent-device';
|
|
3
3
|
|
|
4
|
+
import { resolveMobileTarget } from './bridge.mjs';
|
|
5
|
+
import { hideAccessibilityMaskingHud } from './observe-ui.mjs';
|
|
6
|
+
|
|
4
7
|
import {
|
|
5
8
|
nativeAgentDeviceStateDir,
|
|
6
9
|
nativeSessionName,
|
|
@@ -8,17 +11,27 @@ import {
|
|
|
8
11
|
|
|
9
12
|
export function createNativeSession(input, domain, sessionSuffix = '') {
|
|
10
13
|
const env = { ...process.env, ...(input.context?.env ?? {}) };
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
+
const target = resolveMobileTarget(input);
|
|
15
|
+
const platform = target.requestedPlatform || (target.adbSerial ? 'android' : 'ios');
|
|
16
|
+
const device = String(platform === 'android'
|
|
17
|
+
? target.adbSerial ?? ''
|
|
18
|
+
: input.node?.sim_udid ?? env.SIM_UDID ?? target.iosSimulator ?? '').trim();
|
|
19
|
+
if (!device) throw new Error(`Mobile ${domain} native actions require one explicit ${platform} device. Next: pass --device <device>.`);
|
|
20
|
+
const app = String(platform === 'android'
|
|
21
|
+
? env.ANDROID_PACKAGE_ID ?? 'io.metamask'
|
|
22
|
+
: env.IOS_BUNDLE_ID ?? 'io.metamask.MetaMask');
|
|
14
23
|
const baseSession = nativeSessionName(env, device, app);
|
|
15
24
|
const session = sessionSuffix
|
|
16
25
|
? `${baseSession}-${String(sessionSuffix).replace(/[^A-Za-z0-9._-]/gu, '-')}`.slice(0, 120)
|
|
17
26
|
: baseSession;
|
|
18
27
|
const stateDir = nativeAgentDeviceStateDir(env, input.context.projectRoot, device);
|
|
19
|
-
const client = createAgentDeviceClient({ session, stateDir, lockPolicy: 'reject', lockPlatform:
|
|
20
|
-
const selection = {
|
|
21
|
-
|
|
28
|
+
const client = createAgentDeviceClient({ session, stateDir, lockPolicy: 'reject', lockPlatform: platform });
|
|
29
|
+
const selection = {
|
|
30
|
+
platform, target: 'mobile', session,
|
|
31
|
+
...(platform === 'android' ? { serial: device }
|
|
32
|
+
: /^[0-9a-f]{8}(?:-[0-9a-f]{4}){3}-[0-9a-f]{12}$/iu.test(device) ? { udid: device } : { device }),
|
|
33
|
+
};
|
|
34
|
+
const transport = createAgentDeviceUiTransport({ platform, device, app, session, stateDir, client });
|
|
22
35
|
const context = {
|
|
23
36
|
nodeId: String(input.context?.nodeId ?? 'action'),
|
|
24
37
|
projectRoot: input.context.projectRoot,
|
|
@@ -31,23 +44,19 @@ export function createNativeSession(input, domain, sessionSuffix = '') {
|
|
|
31
44
|
selection,
|
|
32
45
|
context,
|
|
33
46
|
app,
|
|
34
|
-
open: () =>
|
|
47
|
+
open: async () => {
|
|
48
|
+
if (platform === 'ios' && env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME !== '1') {
|
|
49
|
+
await hideAccessibilityMaskingHud({ node: input.node }, input.context);
|
|
50
|
+
}
|
|
51
|
+
await transport.open();
|
|
52
|
+
},
|
|
35
53
|
execute: (action, node) => transport.execute(action, node, context),
|
|
36
54
|
snapshot: () => client.capture.snapshot({ ...selection, app, interactiveOnly: false, forceFull: true }),
|
|
37
55
|
};
|
|
38
56
|
}
|
|
39
57
|
|
|
40
58
|
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
|
-
}
|
|
59
|
+
await session.transport.close();
|
|
51
60
|
}
|
|
52
61
|
|
|
53
62
|
export function nativeNode(snapshot, identifier) {
|
|
@@ -45,7 +45,7 @@ export async function observeNativeUi(payload, context) {
|
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
async function hideAccessibilityMaskingHud(payload, context) {
|
|
48
|
+
export async function hideAccessibilityMaskingHud(payload, context) {
|
|
49
49
|
try {
|
|
50
50
|
await bridgeCommand(
|
|
51
51
|
{
|
|
@@ -429,7 +429,7 @@ function parseAndroidHierarchy(output) {
|
|
|
429
429
|
isActionableRole(role) ||
|
|
430
430
|
attributes.clickable === 'true' ||
|
|
431
431
|
attributes['long-clickable'] === 'true';
|
|
432
|
-
if (isActionable) {
|
|
432
|
+
if (isActionable || item.test_id) {
|
|
433
433
|
if (isVisible) {
|
|
434
434
|
visible.push(item);
|
|
435
435
|
} else {
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
const PAGE_ROUTES = {
|
|
13
13
|
home: { route: 'WalletView', params: {} },
|
|
14
14
|
perps: { route: 'PerpsMarketListView', params: {} },
|
|
15
|
-
'perps-market-list': { route: '
|
|
15
|
+
'perps-market-list': { route: 'PerpsTrendingView', params: {} },
|
|
16
16
|
swap: { route: 'BridgeView', params: {} },
|
|
17
17
|
};
|
|
18
18
|
|