@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
|
+
// Reads asset details from the DOM, rejecting malformed values and waiting for requested chart data.
|
|
1
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
|
|
3
4
|
function clean(value) {
|
|
@@ -6,17 +7,29 @@ function clean(value) {
|
|
|
6
7
|
return normalized ? normalized.slice(0, 500) : null;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
runAdapter(async (input) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
runAdapter(async (input) =>
|
|
11
|
+
withExtensionPage(input, async (page) => {
|
|
12
|
+
const expectedSymbol = clean(input.node?.expected_symbol)?.toUpperCase() ?? null;
|
|
13
|
+
const requireChart = input.node?.require_chart === true;
|
|
14
|
+
const requireMarketCap = input.node?.require_market_cap === true;
|
|
15
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 30000);
|
|
16
|
+
const deadline = Date.now() + timeoutMs;
|
|
17
|
+
let samples = 0;
|
|
18
|
+
while (true) {
|
|
19
|
+
const raw = await page.evaluate(`(() => {
|
|
14
20
|
const text = (selector) => {
|
|
15
21
|
const value = document.querySelector(selector)?.innerText;
|
|
16
22
|
return typeof value === 'string' ? value.replace(/\\s+/gu, ' ').trim().slice(0, 500) : null;
|
|
17
23
|
};
|
|
18
24
|
const body = String(document.body?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
19
25
|
const name = text('[data-testid="asset-name"]');
|
|
26
|
+
const chart = document.querySelector('[data-testid="asset-price-chart"]');
|
|
27
|
+
const price = document.querySelector('[data-testid="asset-hovered-price"]');
|
|
28
|
+
const priceText = String(price?.innerText || '').trim();
|
|
29
|
+
const chartLabels = [...(chart?.querySelectorAll('p') || [])]
|
|
30
|
+
.filter(element => !element.closest('button, [role="button"]'))
|
|
31
|
+
.map(element => String(element.innerText || '').trim())
|
|
32
|
+
.filter(value => /[0-9]/u.test(value));
|
|
20
33
|
return {
|
|
21
34
|
route: String(location.hash),
|
|
22
35
|
name,
|
|
@@ -25,33 +38,53 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
25
38
|
marketCap: text('[data-testid="asset-market-cap"]'),
|
|
26
39
|
chartRendered: Boolean(document.querySelector('[data-testid="asset-price-chart"] canvas')),
|
|
27
40
|
chartEmpty: Boolean(document.querySelector('[data-testid="asset-chart-empty-state"]')),
|
|
41
|
+
chartHasPriceLabels: chartLabels.length >= 2,
|
|
42
|
+
priceSettled: Boolean(price && /[0-9]/u.test(priceText) && Number(getComputedStyle(price).opacity) === 1),
|
|
28
43
|
malformed: /(?:\\bNaN\\b|\\bundefined\\b|\\bnull\\b)/u.test(body),
|
|
29
44
|
hasActivity: /\\bYour activity\\b/u.test(body),
|
|
30
45
|
};
|
|
31
46
|
})()`);
|
|
47
|
+
samples += 1;
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
if (raw.malformed) {
|
|
46
|
-
throw new Error('Asset details contain a malformed visible value.');
|
|
47
|
-
}
|
|
49
|
+
if (!raw.route.startsWith('#/asset/')) {
|
|
50
|
+
throw new Error(`Expected an asset details route, but observed ${raw.route || 'no route'}.`);
|
|
51
|
+
}
|
|
52
|
+
if (expectedSymbol && raw.symbol && raw.symbol !== expectedSymbol) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Expected ${expectedSymbol} asset details, but observed ${raw.symbol || 'no symbol'}.`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
if (raw.malformed) {
|
|
58
|
+
throw new Error('Asset details contain a malformed visible value.');
|
|
59
|
+
}
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
const pending = [];
|
|
62
|
+
if (expectedSymbol && !raw.symbol) pending.push('asset symbol');
|
|
63
|
+
if (
|
|
64
|
+
requireChart &&
|
|
65
|
+
(!raw.chartRendered || raw.chartEmpty || !raw.chartHasPriceLabels || !raw.priceSettled)
|
|
66
|
+
)
|
|
67
|
+
pending.push('settled chart data and price');
|
|
68
|
+
if (requireMarketCap && !raw.marketCap) pending.push('formatted market cap');
|
|
69
|
+
if (pending.length) {
|
|
70
|
+
const remaining = deadline - Date.now();
|
|
71
|
+
if (remaining <= 0)
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Asset details did not show ${pending.join(', ')} within ${timeoutMs}ms. Last state: ${JSON.stringify(raw)}\nNext: inspect the asset page and captured failure; do not refresh to manufacture readiness.`,
|
|
74
|
+
);
|
|
75
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(100, remaining)));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
action: input.action,
|
|
81
|
+
source: 'visible-ui',
|
|
82
|
+
platform: 'extension',
|
|
83
|
+
surface: 'asset-details',
|
|
84
|
+
...raw,
|
|
85
|
+
samples,
|
|
86
|
+
proofPath: 'visible-dom-accessibility',
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}),
|
|
90
|
+
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Captures token rows within the viewport, including displayed balances and price-change tones.
|
|
1
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
import {
|
|
3
4
|
normalizeVisibleAssetsState,
|
|
@@ -12,7 +13,8 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
12
13
|
.filter((element) => {
|
|
13
14
|
const style = getComputedStyle(element);
|
|
14
15
|
const rect = element.getBoundingClientRect();
|
|
15
|
-
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
16
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
17
|
+
&& rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth;
|
|
16
18
|
})
|
|
17
19
|
.slice(0, maximum)
|
|
18
20
|
.map((element) => {
|
|
@@ -38,6 +40,7 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
38
40
|
return {
|
|
39
41
|
route: location.hash,
|
|
40
42
|
title: document.title,
|
|
43
|
+
lowValueExpanded: document.querySelector('[data-testid="low-value-assets-toggle"]')?.getAttribute('aria-expanded') ?? null,
|
|
41
44
|
items: rows,
|
|
42
45
|
truncated: document.querySelectorAll('[data-testid="multichain-token-list-button"]').length > rows.length,
|
|
43
46
|
};
|
|
@@ -45,6 +48,7 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
45
48
|
return {
|
|
46
49
|
action: input.action,
|
|
47
50
|
...normalizeVisibleAssetsState(raw, options, 'extension'),
|
|
51
|
+
lowValueExpanded: raw.lowValueExpanded,
|
|
48
52
|
proofPath: 'visible-dom-accessibility',
|
|
49
53
|
};
|
|
50
54
|
}));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Opens Home and selects the all-networks token filter, then checks the label and closed menu.
|
|
1
2
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
import { openHome } from '../ui/navigate.mjs';
|
|
3
4
|
import { visible, waitFor } from './send.mjs';
|
|
@@ -11,16 +12,23 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
11
12
|
await openHome(page, timeoutMs);
|
|
12
13
|
const filter = dataTestId('sort-by-networks');
|
|
13
14
|
const all = dataTestId('home-network-filter-all-default');
|
|
15
|
+
const deadline = Date.now() + timeoutMs;
|
|
16
|
+
const remaining = () => Math.max(1, deadline - Date.now());
|
|
17
|
+
const readLabel = () => page.evaluate(`String(document.querySelector(${JSON.stringify(filter)})?.innerText || '')`);
|
|
18
|
+
const alreadySelected = /All (?:default )?networks/iu.test(await readLabel());
|
|
19
|
+
if (await visible(page, all)) {
|
|
20
|
+
await page.waitFor({ selector: all, expected: 'visible', timeoutMs: remaining() });
|
|
21
|
+
await page.click(all);
|
|
22
|
+
} else if (!alreadySelected) {
|
|
23
|
+
await waitFor(page, () => visible(page, filter), 'Network filter did not become visible.', remaining());
|
|
24
|
+
await page.click(filter);
|
|
25
|
+
await page.waitFor({ selector: all, expected: 'visible', timeoutMs: remaining() });
|
|
26
|
+
await page.click(all);
|
|
27
|
+
}
|
|
14
28
|
const selected = await waitFor(page, async () => {
|
|
15
|
-
const label = await
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
await page.click(all);
|
|
19
|
-
} else if (await visible(page, filter)) {
|
|
20
|
-
await page.click(filter);
|
|
21
|
-
}
|
|
22
|
-
return null;
|
|
23
|
-
}, 'All networks did not become selected.', timeoutMs);
|
|
29
|
+
const label = await readLabel();
|
|
30
|
+
return /All (?:default )?networks/iu.test(label) && !(await visible(page, all)) ? label : null;
|
|
31
|
+
}, 'All networks did not become selected with its menu closed.', remaining());
|
|
24
32
|
return {
|
|
25
33
|
action: input.action,
|
|
26
34
|
scope,
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
// Sets a token-management toggle and checks Home visibility, with optional exact ERC-20 identity.
|
|
2
|
+
// Can require a change or an already-matching preference.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
4
|
+
import { verifyHomeIdentity } from './home-token-identity.mjs';
|
|
2
5
|
|
|
3
6
|
const OPTIONS = dataTestId('asset-list-control-bar-action-button');
|
|
4
7
|
const MANAGE = dataTestId('manageTokens__button');
|
|
5
|
-
const PAGE = dataTestId('token-management-page')
|
|
8
|
+
const PAGE = `${dataTestId('parent-selector-token-management-page')}, ${dataTestId('token-management-page')}`;
|
|
6
9
|
const SEARCH = dataTestId('token-management-search-input');
|
|
7
10
|
const BACK = dataTestId('token-management-header-back-button');
|
|
8
11
|
const LOW_VALUE = dataTestId('low-value-assets-toggle');
|
|
@@ -27,12 +30,15 @@ async function waitFor(page, predicate, message, timeoutMs) {
|
|
|
27
30
|
throw new Error(message);
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
async function matchingToggle(page, query) {
|
|
33
|
+
async function matchingToggle(page, query, assetId) {
|
|
31
34
|
return page.evaluate(`(() => {
|
|
32
35
|
const query = ${JSON.stringify(query.toLowerCase())};
|
|
36
|
+
const assetId = ${JSON.stringify(assetId)};
|
|
33
37
|
const candidates = [...document.querySelectorAll('[data-testid^="token-management-cell-search-"][data-testid$="-toggle-control"]')];
|
|
38
|
+
const matches = [];
|
|
34
39
|
for (const element of candidates) {
|
|
35
40
|
const testId = element.getAttribute('data-testid');
|
|
41
|
+
if (assetId && testId !== 'token-management-cell-search-' + assetId + '-toggle-control') continue;
|
|
36
42
|
const row = document.querySelector('[data-testid="' + testId.slice(0, -'-toggle-control'.length) + '"]');
|
|
37
43
|
const label = String(row?.innerText || element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500);
|
|
38
44
|
const words = label.toLowerCase().split(/[^a-z0-9]+/u).filter(Boolean);
|
|
@@ -40,28 +46,48 @@ async function matchingToggle(page, query) {
|
|
|
40
46
|
const style = getComputedStyle(element);
|
|
41
47
|
const rect = element.getBoundingClientRect();
|
|
42
48
|
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
|
|
43
|
-
|
|
49
|
+
matches.push({
|
|
44
50
|
testId,
|
|
45
51
|
label,
|
|
46
52
|
enabled: Boolean(element.querySelector('.toggle-button--on')),
|
|
47
|
-
|
|
53
|
+
busy: element.getAttribute('aria-busy') === 'true',
|
|
54
|
+
});
|
|
48
55
|
}
|
|
49
|
-
|
|
56
|
+
if (matches.length > 1) throw new Error('Token management matched ' + matches.length + ' rows; query is ambiguous.\\nNext: mm-harness actions --action metamask.assets.set_token_visibility --json');
|
|
57
|
+
return matches[0] || null;
|
|
50
58
|
})()`);
|
|
51
59
|
}
|
|
52
60
|
|
|
53
|
-
async function homeToken(page, query) {
|
|
61
|
+
async function homeToken(page, query, verifyIdentity = false) {
|
|
54
62
|
return page.evaluate(`(() => {
|
|
55
63
|
const query = ${JSON.stringify(query.toLowerCase())};
|
|
64
|
+
const verifyIdentity = ${JSON.stringify(verifyIdentity)};
|
|
65
|
+
const matches = [];
|
|
56
66
|
for (const row of document.querySelectorAll('[data-testid="multichain-token-list-button"]')) {
|
|
57
67
|
const name = String(row.querySelector('[data-testid="multichain-token-list-item-token-name"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
58
68
|
const amount = String(row.querySelector('[data-testid="multichain-token-list-item-value"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
59
69
|
const symbol = amount.match(/\\b([A-Za-z][A-Za-z0-9]{1,11})$/u)?.[1] || '';
|
|
60
70
|
if (symbol.toLowerCase() === query || name.toLowerCase().includes(query)) {
|
|
61
|
-
|
|
71
|
+
const rect = row.getBoundingClientRect();
|
|
72
|
+
const style = getComputedStyle(row);
|
|
73
|
+
if (rect.width <= 0 || rect.height <= 0 || style.display === 'none' || style.visibility === 'hidden') continue;
|
|
74
|
+
matches.push({ row, name, symbol: symbol.toUpperCase(), amount });
|
|
62
75
|
}
|
|
63
76
|
}
|
|
64
|
-
|
|
77
|
+
if (verifyIdentity && matches.length > 1) throw new Error('Home token query is ambiguous; cannot verify asset identity.');
|
|
78
|
+
const match = matches[0];
|
|
79
|
+
if (!match) return null;
|
|
80
|
+
const { row, ...token } = match;
|
|
81
|
+
if (verifyIdentity) {
|
|
82
|
+
row.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
83
|
+
const rect = row.getBoundingClientRect();
|
|
84
|
+
const x = rect.left + rect.width / 2;
|
|
85
|
+
const y = rect.top + rect.height / 2;
|
|
86
|
+
const hit = document.elementFromPoint(x, y);
|
|
87
|
+
if (!hit || !row.contains(hit)) return null;
|
|
88
|
+
token.point = { x, y };
|
|
89
|
+
}
|
|
90
|
+
return token;
|
|
65
91
|
})()`);
|
|
66
92
|
}
|
|
67
93
|
|
|
@@ -77,6 +103,31 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
77
103
|
const requireUnchanged = input.node?.require_unchanged === true;
|
|
78
104
|
const requireChange = input.node?.require_change === true;
|
|
79
105
|
const timeoutMs = Number(input.node?.timeout_ms ?? 45_000);
|
|
106
|
+
const assetId = String(input.node?.asset_id ?? '').trim().toLowerCase();
|
|
107
|
+
if (assetId && !/^eip155:[1-9][0-9]{0,19}\/erc20:0x[0-9a-f]{40}$/u.test(assetId)) {
|
|
108
|
+
throw new Error('asset_id requires an ERC-20 CAIP asset ID.\nNext: mm-harness actions --action metamask.assets.set_token_visibility --json');
|
|
109
|
+
}
|
|
110
|
+
if (assetId && !requested && (!requireChange || requireUnchanged)) {
|
|
111
|
+
throw new Error('Exact token hiding requires require_change=true and require_unchanged=false.\nNext: mm-harness actions --action metamask.assets.set_token_visibility --json');
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let identity;
|
|
115
|
+
if (assetId && !requested) {
|
|
116
|
+
if (await visible(page, LOW_VALUE)) {
|
|
117
|
+
const expanded = await page.evaluate(`document.querySelector(${JSON.stringify(LOW_VALUE)})?.getAttribute('aria-expanded') === 'true'`);
|
|
118
|
+
if (!expanded) await page.click(LOW_VALUE);
|
|
119
|
+
}
|
|
120
|
+
const token = await waitFor(
|
|
121
|
+
page,
|
|
122
|
+
() => homeToken(page, query, true),
|
|
123
|
+
`${query} must be visible on Home before its exact identity can be hidden.`,
|
|
124
|
+
timeoutMs,
|
|
125
|
+
);
|
|
126
|
+
identity = {
|
|
127
|
+
...await verifyHomeIdentity(page, token, assetId, timeoutMs),
|
|
128
|
+
verifiedAt: 'before-hide',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
80
131
|
|
|
81
132
|
if (!(await visible(page, MANAGE))) await page.click(OPTIONS);
|
|
82
133
|
await waitFor(page, () => visible(page, MANAGE), 'Manage tokens did not become visible.', timeoutMs);
|
|
@@ -85,8 +136,11 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
85
136
|
await page.setInput(SEARCH, query);
|
|
86
137
|
const before = await waitFor(
|
|
87
138
|
page,
|
|
88
|
-
() =>
|
|
89
|
-
|
|
139
|
+
async () => {
|
|
140
|
+
const state = await matchingToggle(page, query, assetId);
|
|
141
|
+
return state && !state.busy ? state : null;
|
|
142
|
+
},
|
|
143
|
+
`Token management returned no settled visible result for ${query}.`,
|
|
90
144
|
timeoutMs,
|
|
91
145
|
);
|
|
92
146
|
if (requireUnchanged && before.enabled !== requested) {
|
|
@@ -101,8 +155,8 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
101
155
|
const after = await waitFor(
|
|
102
156
|
page,
|
|
103
157
|
async () => {
|
|
104
|
-
const state = await matchingToggle(page, query);
|
|
105
|
-
return state
|
|
158
|
+
const state = await matchingToggle(page, query, assetId);
|
|
159
|
+
return state && !state.busy && state.enabled === requested ? state : null;
|
|
106
160
|
},
|
|
107
161
|
`${query} token visibility did not become ${requested ? 'enabled' : 'disabled'}.`,
|
|
108
162
|
timeoutMs,
|
|
@@ -110,14 +164,26 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
110
164
|
await page.click(BACK);
|
|
111
165
|
await waitFor(page, () => visible(page, OPTIONS), 'Token list did not reopen.', timeoutMs);
|
|
112
166
|
|
|
113
|
-
if (requested && (await visible(page, LOW_VALUE))) {
|
|
167
|
+
if ((requested || assetId) && (await visible(page, LOW_VALUE))) {
|
|
114
168
|
const expanded = await page.evaluate(`document.querySelector(${JSON.stringify(LOW_VALUE)})?.getAttribute('aria-expanded') === 'true'`);
|
|
115
169
|
if (!expanded) await page.click(LOW_VALUE);
|
|
116
170
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
171
|
+
let token = null;
|
|
172
|
+
if (requested) {
|
|
173
|
+
token = await waitFor(page, () => homeToken(page, query, Boolean(assetId)), `${query} did not appear in the token list.`, timeoutMs);
|
|
174
|
+
if (assetId) identity = await verifyHomeIdentity(page, token, assetId, timeoutMs);
|
|
175
|
+
} else if (assetId) {
|
|
176
|
+
await waitFor(
|
|
177
|
+
page,
|
|
178
|
+
async () => !(await homeToken(page, query)),
|
|
179
|
+
`${query} remained visible after disabling it.`,
|
|
180
|
+
timeoutMs,
|
|
181
|
+
);
|
|
182
|
+
identity.homeAbsent = true;
|
|
183
|
+
} else {
|
|
184
|
+
token = await homeToken(page, query);
|
|
185
|
+
if (token) throw new Error(`${query} remained visible after disabling it.`);
|
|
186
|
+
}
|
|
121
187
|
|
|
122
188
|
return {
|
|
123
189
|
action: input.action,
|
|
@@ -126,8 +192,11 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
126
192
|
requireUnchanged,
|
|
127
193
|
requireChange,
|
|
128
194
|
changed: before.enabled !== after.enabled,
|
|
195
|
+
beforeEnabled: before.enabled,
|
|
196
|
+
afterEnabled: after.enabled,
|
|
129
197
|
searchResult: after.label,
|
|
130
198
|
toggleTestId: after.testId,
|
|
199
|
+
...(identity ? { identity } : {}),
|
|
131
200
|
token,
|
|
132
201
|
proofPath: 'trusted-pointer-visible-ui',
|
|
133
202
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Checks Home token order under alphabetical and declining-balance sorts, then restores the preference.
|
|
1
2
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
|
|
3
4
|
const TOGGLE = dataTestId('sort-by-popover-toggle');
|
|
@@ -45,9 +46,9 @@ function assertDeclining(rows) {
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
async function
|
|
49
|
+
async function openMenu(page) {
|
|
49
50
|
const isVisible = async () => page.evaluate(`(() => {
|
|
50
|
-
const element = document.querySelector(${JSON.stringify(
|
|
51
|
+
const element = document.querySelector(${JSON.stringify(ALPHABETICAL)});
|
|
51
52
|
if (!element) return false;
|
|
52
53
|
const style = getComputedStyle(element);
|
|
53
54
|
const rect = element.getBoundingClientRect();
|
|
@@ -57,24 +58,71 @@ async function choose(page, selector) {
|
|
|
57
58
|
const deadline = Date.now() + 5_000;
|
|
58
59
|
while (!(await isVisible())) {
|
|
59
60
|
if (Date.now() >= deadline) throw new Error('Asset sort menu did not become visible.');
|
|
60
|
-
await
|
|
61
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function selectedSort(page) {
|
|
66
|
+
await openMenu(page);
|
|
67
|
+
const selected = await page.evaluate(`(() => ${JSON.stringify([ALPHABETICAL, DECLINING])}
|
|
68
|
+
.filter(selector => document.querySelector(selector)?.classList.contains('selectable-list-item--selected')))()`);
|
|
69
|
+
if (selected.length !== 1) throw new Error('Asset sort preference is unavailable or ambiguous; no sort will be changed.');
|
|
70
|
+
return selected[0];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function choose(page, selector) {
|
|
74
|
+
const current = await selectedSort(page);
|
|
75
|
+
await page.click(current === selector ? TOGGLE : selector);
|
|
76
|
+
await page.waitForExpression(`(() => {
|
|
77
|
+
const element = document.querySelector(${JSON.stringify(ALPHABETICAL)});
|
|
78
|
+
if (!element) return true;
|
|
79
|
+
const rect = element.getBoundingClientRect();
|
|
80
|
+
const style = getComputedStyle(element);
|
|
81
|
+
return rect.width === 0 || rect.height === 0 || style.display === 'none' || style.visibility === 'hidden';
|
|
82
|
+
})()`, { timeoutMs: 5000 });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function waitForRows(page, assertion) {
|
|
86
|
+
const deadline = Date.now() + 5000;
|
|
87
|
+
while (true) {
|
|
88
|
+
const rows = await readRows(page);
|
|
89
|
+
try {
|
|
90
|
+
assertion(rows);
|
|
91
|
+
return rows;
|
|
92
|
+
} catch (error) {
|
|
93
|
+
if (Date.now() >= deadline) throw error;
|
|
94
|
+
}
|
|
95
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
61
96
|
}
|
|
62
|
-
await page.click(selector);
|
|
63
|
-
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 500))');
|
|
64
97
|
}
|
|
65
98
|
|
|
66
99
|
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
67
|
-
if (!String(await page.evaluate('location.hash'))
|
|
100
|
+
if (!['#/', '#', ''].includes(String(await page.evaluate('location.hash')))) {
|
|
68
101
|
throw new Error('metamask.assets.verify_sorting requires the wallet home token list.');
|
|
69
102
|
}
|
|
70
103
|
|
|
71
|
-
await
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
104
|
+
const initialSort = await selectedSort(page);
|
|
105
|
+
let alphabetical;
|
|
106
|
+
let declining;
|
|
107
|
+
let proofError;
|
|
108
|
+
try {
|
|
109
|
+
await choose(page, ALPHABETICAL);
|
|
110
|
+
alphabetical = await waitForRows(page, assertAlphabetical);
|
|
111
|
+
await choose(page, DECLINING);
|
|
112
|
+
declining = await waitForRows(page, assertDeclining);
|
|
113
|
+
} catch (error) {
|
|
114
|
+
proofError = error;
|
|
115
|
+
}
|
|
116
|
+
try {
|
|
117
|
+
await choose(page, initialSort);
|
|
118
|
+
await openMenu(page);
|
|
119
|
+
await page.waitForExpression(`document.querySelector(${JSON.stringify(initialSort)})?.classList.contains('selectable-list-item--selected') === true`, { timeoutMs: 5000 });
|
|
120
|
+
await page.click(TOGGLE);
|
|
121
|
+
} catch (restoreError) {
|
|
122
|
+
if (proofError) throw new AggregateError([proofError, restoreError], 'Asset sorting proof and preference restoration both failed.');
|
|
123
|
+
throw restoreError;
|
|
124
|
+
}
|
|
125
|
+
if (proofError) throw proofError;
|
|
78
126
|
|
|
79
127
|
return {
|
|
80
128
|
action: input.action,
|
|
@@ -83,7 +131,8 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
83
131
|
surface: 'token-list',
|
|
84
132
|
alphabetical: alphabetical.map((row) => row.name),
|
|
85
133
|
decliningFiat: declining.map((row) => row.fiat),
|
|
86
|
-
|
|
134
|
+
initialSort: initialSort === ALPHABETICAL ? 'alphabetical' : 'declining-balance',
|
|
135
|
+
restoredSort: initialSort === ALPHABETICAL ? 'alphabetical' : 'declining-balance',
|
|
87
136
|
proofPath: 'trusted-pointer-visible-ui',
|
|
88
137
|
};
|
|
89
138
|
}));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Adds a missing network through ChainList and selects it in the Home network filter.
|
|
2
|
+
// Can reject existing networks so later cleanup does not delete prior configuration.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
assertNetworkAdditionAllowed,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Adds a missing custom RPC network through the UI and selects it on Home.
|
|
2
|
+
// Can reject existing networks so later cleanup does not delete prior configuration.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
assertNetworkAdditionAllowed,
|
|
@@ -37,17 +39,19 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
37
39
|
await page.setInput(dataTestId('network-form-chain-id'), chainId);
|
|
38
40
|
await page.setInput(dataTestId('network-form-ticker-input'), symbol);
|
|
39
41
|
|
|
40
|
-
await page.
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
await page.click(dataTestId('test-add-rpc-drop-down'));
|
|
43
|
+
const addRpc = await waitFor(
|
|
44
|
+
page,
|
|
45
|
+
() => semanticButtonOrNull(page, 'Add RPC URL'),
|
|
46
|
+
'Add RPC URL did not become visible after opening the menu.',
|
|
47
|
+
timeoutMs,
|
|
48
|
+
);
|
|
49
|
+
await page.waitFor({ selector: addRpc, expected: 'visible', timeoutMs });
|
|
48
50
|
await page.click(addRpc);
|
|
49
51
|
await page.setInput(dataTestId('rpc-url-input-test'), rpcUrl);
|
|
50
|
-
|
|
52
|
+
const rpcSubmit = `${dataTestId('page-container-footer-next')}:not(:disabled):not([aria-disabled="true"])`;
|
|
53
|
+
await page.waitFor({ selector: rpcSubmit, expected: 'visible', timeoutMs });
|
|
54
|
+
await page.click(rpcSubmit);
|
|
51
55
|
await waitFor(
|
|
52
56
|
page,
|
|
53
57
|
() => page.evaluate(`!document.querySelector(${JSON.stringify(dataTestId('rpc-url-input-test'))})`),
|
|
@@ -56,14 +60,14 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
56
60
|
);
|
|
57
61
|
|
|
58
62
|
if (explorerUrl) {
|
|
59
|
-
await page.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
await page.click(dataTestId('test-explorer-drop-down'));
|
|
64
|
+
const addExplorer = await waitFor(
|
|
65
|
+
page,
|
|
66
|
+
() => semanticButtonOrNull(page, 'Add a block explorer URL'),
|
|
67
|
+
'Add a block explorer URL did not become visible after opening the menu.',
|
|
68
|
+
timeoutMs,
|
|
69
|
+
);
|
|
70
|
+
await page.waitFor({ selector: addExplorer, expected: 'visible', timeoutMs });
|
|
67
71
|
await page.click(addExplorer);
|
|
68
72
|
await page.setInput(dataTestId('explorer-url-input'), explorerUrl);
|
|
69
73
|
await page.click(await semanticButton(page, 'Add URL'));
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Shares network-manager navigation, chain/name matching, and Home network selection.
|
|
2
|
+
// Button lookup marks DOM targets; network additions can reject existing configuration.
|
|
1
3
|
import { dataTestId } from '../platform/cdp.mjs';
|
|
2
4
|
|
|
3
5
|
async function visible(page, selector) {
|
|
@@ -90,7 +92,7 @@ export async function openNetworkManager(page, timeoutMs) {
|
|
|
90
92
|
const networks = dataTestId('global-menu-networks');
|
|
91
93
|
const deadline = Date.now() + timeoutMs;
|
|
92
94
|
await clickUncovered(page, menu, deadline);
|
|
93
|
-
await waitFor(
|
|
95
|
+
await page.waitFor({ selector: networks, expected: 'visible', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
94
96
|
await page.click(networks);
|
|
95
97
|
await waitFor(
|
|
96
98
|
page,
|
|
@@ -115,7 +117,9 @@ export async function findNetworkItem(page, network, chainId) {
|
|
|
115
117
|
const label = String(element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500);
|
|
116
118
|
const nameMatches = [...element.querySelectorAll('[data-testid]')]
|
|
117
119
|
.some((child) => String(child.getAttribute('data-testid') || '').toLowerCase() === network);
|
|
118
|
-
if (chainIds.has(suffix)
|
|
120
|
+
if (!chainIds.has(suffix)) continue;
|
|
121
|
+
if (!nameMatches) throw new Error('Chain ' + ${JSON.stringify(chainId)} + ' is already configured under a different name.\\nNext: mm-harness call ui.locators --adapter extension --json');
|
|
122
|
+
return { testId, label, suffix };
|
|
119
123
|
}
|
|
120
124
|
return null;
|
|
121
125
|
})()`);
|
|
@@ -128,6 +132,7 @@ export function assertNetworkAdditionAllowed(item, requireAbsent, network, chain
|
|
|
128
132
|
}
|
|
129
133
|
|
|
130
134
|
export async function selectHomeNetwork(page, network, chainId, timeoutMs) {
|
|
135
|
+
const deadline = Date.now() + timeoutMs;
|
|
131
136
|
const sort = dataTestId('sort-by-networks');
|
|
132
137
|
const chainHex = `0x${chainId.toString(16)}`;
|
|
133
138
|
const choices = [
|
|
@@ -135,13 +140,15 @@ export async function selectHomeNetwork(page, network, chainId, timeoutMs) {
|
|
|
135
140
|
dataTestId(`home-network-filter-custom-${chainHex}`),
|
|
136
141
|
dataTestId(`home-network-filter-additional-${chainHex}`),
|
|
137
142
|
];
|
|
138
|
-
await page.click(sort);
|
|
143
|
+
if (!(await visible(page, dataTestId('home-network-filter-all-default')))) await page.click(sort);
|
|
139
144
|
const choice = await waitFor(page, async () => {
|
|
140
145
|
for (const candidate of choices) {
|
|
141
146
|
if (await visible(page, candidate)) return candidate;
|
|
142
147
|
}
|
|
143
148
|
return null;
|
|
144
149
|
}, `${network} did not appear in the home network filter.`, timeoutMs);
|
|
150
|
+
await page.scroll({ selector: choice, intoView: true });
|
|
151
|
+
await page.waitFor({ selector: choice, expected: 'visible', timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
145
152
|
await page.click(choice);
|
|
146
153
|
await waitFor(page, async () => {
|
|
147
154
|
const label = await page.evaluate(`String(document.querySelector(${JSON.stringify(sort)})?.innerText || '')`);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Samples the displayed network, token rows, and errors, with an optional settling interval.
|
|
1
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
import {
|
|
3
4
|
normalizeVisibleNetworkState,
|
|
@@ -40,6 +41,7 @@ async function capture(page, maximum) {
|
|
|
40
41
|
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
41
42
|
const options = resolveVisibleNetworkOptions(input.node);
|
|
42
43
|
const before = await capture(page, options.assets.maxItems);
|
|
44
|
+
normalizeVisibleNetworkState(before, before, options, 'extension');
|
|
43
45
|
if (options.settleMs > 0) await sleep(options.settleMs);
|
|
44
46
|
const after = options.settleMs > 0 ? await capture(page, options.assets.maxItems) : before;
|
|
45
47
|
return {
|