@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,6 @@
|
|
|
1
|
+
// Provide checkout-bound Extension CDP sessions, page actions and screenshot helpers.
|
|
2
|
+
// Bind browser and service-worker observation to the selected runtime.
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
constants,
|
|
3
6
|
access,
|
|
@@ -240,32 +243,31 @@ async function captureHelperBrowserPid(context, port) {
|
|
|
240
243
|
);
|
|
241
244
|
}
|
|
242
245
|
|
|
243
|
-
async function
|
|
246
|
+
async function captureCdpSnapshot(page, context, relPath, metadata, captureHelperError = null) {
|
|
244
247
|
const { relative, absolute, artifactsRoot } = resolveRelativeArtifactPath(context.artifactsDir, relPath);
|
|
245
248
|
const destination = await preparePrivateEvidenceDestination(absolute, artifactsRoot);
|
|
246
249
|
let result;
|
|
247
250
|
try {
|
|
248
|
-
const timeoutMs = Number(metadata?.cdpTimeoutMs ?? 5000);
|
|
249
|
-
result = await
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}),
|
|
255
|
-
new Promise((_, reject) => setTimeout(() => reject(new Error(`Chrome Page.captureScreenshot timed out after ${timeoutMs}ms.`)), timeoutMs)),
|
|
256
|
-
]);
|
|
257
|
-
if (typeof result?.data !== 'string' || result.data.length === 0) {
|
|
251
|
+
const timeoutMs = Number(metadata?.cdpTimeoutMs ?? metadata?.timeoutMs ?? 5000);
|
|
252
|
+
result = await CdpWebPage.prototype.screenshot.call(page, undefined, undefined, {
|
|
253
|
+
...metadata,
|
|
254
|
+
timeoutMs,
|
|
255
|
+
});
|
|
256
|
+
if (typeof result !== 'string' || result.length === 0) {
|
|
258
257
|
throw new Error('Chrome Page.captureScreenshot returned no image data.');
|
|
259
258
|
}
|
|
260
259
|
} catch (error) {
|
|
261
260
|
await cleanupEvidenceStaging(destination.stagingDir);
|
|
262
261
|
const cdpError = error instanceof Error ? error.message : String(error);
|
|
263
|
-
|
|
262
|
+
throw new Error(
|
|
263
|
+
`Extension screenshot capture failed: ${captureHelperError ? 'native=' + captureHelperError + '; ' : ''}CDP=${cdpError}.\n` +
|
|
264
|
+
'Next: mm-harness doctor --adapter extension --json',
|
|
265
|
+
);
|
|
264
266
|
}
|
|
265
267
|
try {
|
|
266
268
|
await writeFile(
|
|
267
269
|
destination.stagedPath,
|
|
268
|
-
Buffer.from(result
|
|
270
|
+
Buffer.from(result, 'base64'),
|
|
269
271
|
{ flag: 'wx', mode: 0o600 },
|
|
270
272
|
);
|
|
271
273
|
} catch (error) {
|
|
@@ -283,89 +285,15 @@ async function captureCdpViewportSnapshot(page, context, relPath, metadata, capt
|
|
|
283
285
|
metadata: {
|
|
284
286
|
provider: 'cdp',
|
|
285
287
|
mode: 'Page.captureScreenshot',
|
|
288
|
+
fullPage: metadata?.fullPage === true,
|
|
286
289
|
...(captureHelperError ? { fallbackFrom: 'capture-helper', captureHelperError } : {}),
|
|
287
290
|
},
|
|
288
291
|
};
|
|
289
292
|
}
|
|
290
293
|
|
|
291
|
-
async function captureDomRasterSnapshot(page, context, relPath, metadata, captureHelperError, cdpError) {
|
|
292
|
-
const { relative, absolute, artifactsRoot } = resolveRelativeArtifactPath(context.artifactsDir, relPath);
|
|
293
|
-
const destination = await preparePrivateEvidenceDestination(absolute, artifactsRoot);
|
|
294
|
-
const dataUrlPromise = page.evaluate(`(async () => {
|
|
295
|
-
const width = Math.max(1, window.innerWidth);
|
|
296
|
-
const height = Math.max(1, window.innerHeight);
|
|
297
|
-
const source = document.documentElement;
|
|
298
|
-
const clone = source.cloneNode(true);
|
|
299
|
-
const sourceNodes = [source, ...source.querySelectorAll('*')];
|
|
300
|
-
const cloneNodes = [clone, ...clone.querySelectorAll('*')];
|
|
301
|
-
for (let index = 0; index < sourceNodes.length; index += 1) {
|
|
302
|
-
const sourceNode = sourceNodes[index];
|
|
303
|
-
const cloneNode = cloneNodes[index];
|
|
304
|
-
if (!(sourceNode instanceof Element) || !(cloneNode instanceof Element)) continue;
|
|
305
|
-
const computed = getComputedStyle(sourceNode);
|
|
306
|
-
cloneNode.setAttribute('style', Array.from(computed).map((name) => name + ':' + computed.getPropertyValue(name) + ';').join(''));
|
|
307
|
-
if ('value' in sourceNode && typeof sourceNode.value === 'string') cloneNode.setAttribute('value', sourceNode.value);
|
|
308
|
-
if (sourceNode instanceof HTMLCanvasElement) {
|
|
309
|
-
const image = document.createElement('img');
|
|
310
|
-
try { image.src = sourceNode.toDataURL('image/png'); } catch { image.alt = 'canvas'; }
|
|
311
|
-
image.setAttribute('style', cloneNode.getAttribute('style') || '');
|
|
312
|
-
cloneNode.replaceWith(image);
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
clone.querySelectorAll('script').forEach((node) => node.remove());
|
|
316
|
-
clone.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml');
|
|
317
|
-
clone.style.width = width + 'px';
|
|
318
|
-
clone.style.height = height + 'px';
|
|
319
|
-
clone.style.overflow = 'hidden';
|
|
320
|
-
const serialized = new XMLSerializer().serializeToString(clone);
|
|
321
|
-
const svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + width + '" height="' + height + '"><foreignObject width="100%" height="100%">' + serialized + '</foreignObject></svg>';
|
|
322
|
-
const image = new Image();
|
|
323
|
-
const loaded = new Promise((resolve, reject) => { image.onload = resolve; image.onerror = () => reject(new Error('DOM raster image load failed.')); });
|
|
324
|
-
image.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
|
|
325
|
-
await loaded;
|
|
326
|
-
const canvas = document.createElement('canvas');
|
|
327
|
-
canvas.width = width;
|
|
328
|
-
canvas.height = height;
|
|
329
|
-
const context = canvas.getContext('2d');
|
|
330
|
-
context.drawImage(image, 0, 0, width, height);
|
|
331
|
-
return canvas.toDataURL('image/png');
|
|
332
|
-
})()`);
|
|
333
|
-
let dataUrl;
|
|
334
|
-
try {
|
|
335
|
-
dataUrl = await dataUrlPromise;
|
|
336
|
-
if (typeof dataUrl !== 'string' || !dataUrl.startsWith('data:image/png;base64,')) {
|
|
337
|
-
throw new Error(`Extension screenshot fallbacks failed: capture-helper=${captureHelperError ?? 'not attempted'}; cdp=${cdpError}; DOM raster returned no PNG.`);
|
|
338
|
-
}
|
|
339
|
-
await writeFile(
|
|
340
|
-
destination.stagedPath,
|
|
341
|
-
Buffer.from(dataUrl.slice('data:image/png;base64,'.length), 'base64'),
|
|
342
|
-
{ flag: 'wx', mode: 0o600 },
|
|
343
|
-
);
|
|
344
|
-
} catch (error) {
|
|
345
|
-
await cleanupEvidenceStaging(destination.stagingDir);
|
|
346
|
-
throw error;
|
|
347
|
-
}
|
|
348
|
-
await publishPrivateEvidence(destination);
|
|
349
|
-
return {
|
|
350
|
-
path: relative,
|
|
351
|
-
type: 'screenshot',
|
|
352
|
-
nodeId: context.nodeId,
|
|
353
|
-
label: metadata?.label ?? `${context.nodeId} screenshot`,
|
|
354
|
-
category: metadata?.category ?? 'evidence',
|
|
355
|
-
mimeType: 'image/png',
|
|
356
|
-
metadata: {
|
|
357
|
-
provider: 'dom-raster',
|
|
358
|
-
mode: 'computed-style-viewport',
|
|
359
|
-
fallbackFrom: captureHelperError ? 'capture-helper+cdp' : 'cdp',
|
|
360
|
-
...(captureHelperError ? { captureHelperError } : {}),
|
|
361
|
-
cdpError,
|
|
362
|
-
},
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
|
|
366
294
|
export async function captureHelperSnapshot(page, context, relPath, metadata) {
|
|
367
|
-
if (process.platform !== 'darwin') {
|
|
368
|
-
return
|
|
295
|
+
if (metadata?.fullPage === true || process.platform !== 'darwin') {
|
|
296
|
+
return captureCdpSnapshot(page, context, relPath, metadata);
|
|
369
297
|
}
|
|
370
298
|
const { relative, absolute, artifactsRoot } = resolveRelativeArtifactPath(context.artifactsDir, relPath);
|
|
371
299
|
const destination = await preparePrivateEvidenceDestination(absolute, artifactsRoot);
|
|
@@ -388,7 +316,7 @@ export async function captureHelperSnapshot(page, context, relPath, metadata) {
|
|
|
388
316
|
parsePositivePid(process.env.METAMASK_RECIPE_EXTENSION_ACTIVE_RECORDING_PID) === pid
|
|
389
317
|
) {
|
|
390
318
|
await cleanupEvidenceStaging(destination.stagingDir);
|
|
391
|
-
return
|
|
319
|
+
return captureCdpSnapshot(
|
|
392
320
|
page,
|
|
393
321
|
context,
|
|
394
322
|
relPath,
|
|
@@ -414,7 +342,7 @@ export async function captureHelperSnapshot(page, context, relPath, metadata) {
|
|
|
414
342
|
} catch (error) {
|
|
415
343
|
await cleanupEvidenceStaging(destination.stagingDir);
|
|
416
344
|
const message = error instanceof Error ? error.message : String(error);
|
|
417
|
-
return
|
|
345
|
+
return captureCdpSnapshot(page, context, relPath, metadata, message);
|
|
418
346
|
}
|
|
419
347
|
await publishPrivateEvidence(destination);
|
|
420
348
|
return {
|
|
@@ -811,45 +739,22 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
811
739
|
});
|
|
812
740
|
}
|
|
813
741
|
|
|
814
|
-
async
|
|
815
|
-
const
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
})()`);
|
|
831
|
-
await this.session.call('Input.dispatchMouseEvent', {
|
|
832
|
-
type: 'mouseMoved',
|
|
833
|
-
x: target.x,
|
|
834
|
-
y: target.y,
|
|
835
|
-
});
|
|
836
|
-
await this.session.call('Input.dispatchMouseEvent', {
|
|
837
|
-
type: 'mousePressed',
|
|
838
|
-
x: target.x,
|
|
839
|
-
y: target.y,
|
|
840
|
-
button: 'left',
|
|
841
|
-
buttons: 1,
|
|
842
|
-
clickCount: 1,
|
|
843
|
-
});
|
|
844
|
-
await this.session.call('Input.dispatchMouseEvent', {
|
|
845
|
-
type: 'mouseReleased',
|
|
846
|
-
x: target.x,
|
|
847
|
-
y: target.y,
|
|
848
|
-
button: 'left',
|
|
849
|
-
buttons: 0,
|
|
850
|
-
clickCount: 1,
|
|
851
|
-
});
|
|
852
|
-
return { clicked: true, selector, tagName: target.tagName };
|
|
742
|
+
async connectServiceWorker() {
|
|
743
|
+
const browserVersion = await jsonGet(`http://127.0.0.1:${this.port}/json/version`);
|
|
744
|
+
const current = extensionRuntimeIdentity({ port: this.port, target: this.target, browserVersion });
|
|
745
|
+
if (current.runtimeSessionId !== this.runtimeIdentity.runtimeSessionId) {
|
|
746
|
+
throw new Error('Extension browser changed before service-worker observation.');
|
|
747
|
+
}
|
|
748
|
+
const targets = await jsonGet(`http://127.0.0.1:${this.port}/json/list`);
|
|
749
|
+
const workers = targets.filter((target) => target.type === 'service_worker'
|
|
750
|
+
&& String(target.url).startsWith(`${this.origin}/`));
|
|
751
|
+
if (workers.length !== 1) throw new Error('Extension requires one matching service worker for order observation.');
|
|
752
|
+
const worker = workers[0];
|
|
753
|
+
const socket = new URL(worker.webSocketDebuggerUrl);
|
|
754
|
+
if (socket.protocol !== 'ws:' || socket.hostname !== '127.0.0.1' || Number(socket.port) !== this.port) {
|
|
755
|
+
throw new Error('Extension service-worker observation requires this browser localhost endpoint.');
|
|
756
|
+
}
|
|
757
|
+
return { session: await CdpSession.connect(socket.href), targetId: worker.id };
|
|
853
758
|
}
|
|
854
759
|
|
|
855
760
|
async setInput(selector, value) {
|
|
@@ -863,8 +768,14 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
863
768
|
if (typeof element.select === 'function') element.select();
|
|
864
769
|
return { tagName: element.tagName, previousLength: String(element.value ?? '').length };
|
|
865
770
|
})()`);
|
|
866
|
-
await this.session.call('Input.dispatchKeyEvent', {
|
|
867
|
-
|
|
771
|
+
await this.session.call('Input.dispatchKeyEvent', {
|
|
772
|
+
type: 'keyDown', key: 'Backspace', code: 'Backspace',
|
|
773
|
+
windowsVirtualKeyCode: 8, nativeVirtualKeyCode: 8,
|
|
774
|
+
});
|
|
775
|
+
await this.session.call('Input.dispatchKeyEvent', {
|
|
776
|
+
type: 'keyUp', key: 'Backspace', code: 'Backspace',
|
|
777
|
+
windowsVirtualKeyCode: 8, nativeVirtualKeyCode: 8,
|
|
778
|
+
});
|
|
868
779
|
for (const character of input) {
|
|
869
780
|
const upper = character.toUpperCase();
|
|
870
781
|
const isLetter = /^[A-Z]$/u.test(upper);
|
|
@@ -947,22 +858,19 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
947
858
|
}
|
|
948
859
|
|
|
949
860
|
async screenshot(contextOrInput, relPath, metadata = {}) {
|
|
950
|
-
|
|
951
|
-
|
|
861
|
+
const context = contextOrInput?.context ?? contextOrInput;
|
|
862
|
+
const options = contextOrInput?.context
|
|
863
|
+
? {
|
|
952
864
|
label: contextOrInput.node?.description || `${contextOrInput.action} screenshot`,
|
|
953
865
|
category: 'evidence',
|
|
954
866
|
timeoutMs: contextOrInput.node?.timeout_ms,
|
|
955
867
|
...metadata,
|
|
956
|
-
};
|
|
957
|
-
if (contextOrInput.node?.screenshot_mode === 'dom_raster' || process.env.METAMASK_RECIPE_EXTENSION_SCREENSHOT_MODE === 'dom-raster') {
|
|
958
|
-
throw new Error('Extension ui.screenshot no longer supports DOM raster screenshots. Use capture-helper snapshot evidence.');
|
|
959
868
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
if (process.env.METAMASK_RECIPE_EXTENSION_SCREENSHOT_MODE === 'dom-raster') {
|
|
869
|
+
: metadata;
|
|
870
|
+
if (contextOrInput?.node?.screenshot_mode === 'dom_raster' || process.env.METAMASK_RECIPE_EXTENSION_SCREENSHOT_MODE === 'dom-raster') {
|
|
963
871
|
throw new Error('Extension screenshots no longer support DOM raster mode. Use capture-helper snapshot evidence.');
|
|
964
872
|
}
|
|
965
|
-
return captureHelperSnapshot(this,
|
|
873
|
+
return captureHelperSnapshot(this, context, relPath, options);
|
|
966
874
|
}
|
|
967
875
|
}
|
|
968
876
|
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Compares exposed bridge quote state with the selected assets and displayed amounts.
|
|
2
|
+
// Requires debug store access to distinguish matching, stale, loading, and unavailable quotes.
|
|
3
|
+
export async function readQuoteInputIdentity(page) {
|
|
4
|
+
return page.evaluate(`(() => {
|
|
5
|
+
const state = globalThis.stateHooks?.store?.getState?.();
|
|
6
|
+
if (!state?.bridge || !state?.metamask) {
|
|
7
|
+
throw new Error('Swap quote identity requires exposed bridge quote state on this build; UI amounts alone cannot bind a quote to its request.');
|
|
8
|
+
}
|
|
9
|
+
const value = (id) => {
|
|
10
|
+
const root = document.querySelector('[data-testid="' + id + '"]');
|
|
11
|
+
return String((root?.matches('input,textarea') ? root : root?.querySelector('input,textarea'))?.value ?? '').trim();
|
|
12
|
+
};
|
|
13
|
+
const amount = value('from-amount');
|
|
14
|
+
const destinationAmount = value('to-amount');
|
|
15
|
+
const from = state.bridge.fromToken, to = state.bridge.toToken;
|
|
16
|
+
const decimals = from?.decimals;
|
|
17
|
+
if (!from?.assetId || !to?.assetId || !Number.isInteger(decimals) || decimals < 0 || decimals > 36 || !/^\\d+(?:\\.\\d*)?$/u.test(amount)) {
|
|
18
|
+
return { status: 'unavailable', reason: 'Exact token-unit input and asset identities are required.' };
|
|
19
|
+
}
|
|
20
|
+
const [integer, fraction = ''] = amount.split('.');
|
|
21
|
+
if (fraction.length > decimals) return { status: 'unavailable', reason: 'Input exceeds token precision.' };
|
|
22
|
+
const sourceBaseUnits = BigInt(integer + fraction.padEnd(decimals, '0')).toString();
|
|
23
|
+
const request = state.metamask.quoteRequest?.[0];
|
|
24
|
+
const quotes = state.metamask.quotes ?? [];
|
|
25
|
+
const sameAsset = (left, right) => left === right || (
|
|
26
|
+
/^eip155:/u.test(String(left)) && String(left).toLowerCase() === String(right).toLowerCase()
|
|
27
|
+
);
|
|
28
|
+
const matches = quotes.length > 0 && quotes.every(({ quote }) =>
|
|
29
|
+
quote?.requestId && quote.src?.amount === sourceBaseUnits &&
|
|
30
|
+
sameAsset(quote.src?.asset?.assetId, from.assetId) &&
|
|
31
|
+
sameAsset(quote.dest?.asset?.assetId, to.assetId)
|
|
32
|
+
);
|
|
33
|
+
const destinationMatches = /^\\d+(?:\\.\\d*)?$/u.test(destinationAmount) && quotes.some(({ quote }) => {
|
|
34
|
+
const precision = quote?.dest?.asset?.decimals;
|
|
35
|
+
if (!Number.isInteger(precision) || precision < 0 || precision > 36 || !/^\\d+$/u.test(String(quote?.dest?.amount))) return false;
|
|
36
|
+
const [whole, part = ''] = destinationAmount.split('.');
|
|
37
|
+
if (part.length > precision) return false;
|
|
38
|
+
const displayed = BigInt(whole + part.padEnd(precision, '0'));
|
|
39
|
+
const quoted = BigInt(quote.dest.amount);
|
|
40
|
+
const difference = displayed > quoted ? displayed - quoted : quoted - displayed;
|
|
41
|
+
const displayUnit = 10n ** BigInt(precision - part.length);
|
|
42
|
+
return displayed > 0n && difference * 2n <= displayUnit;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
status: state.metamask.quotesLoadingStatus === 0 ? 'loading'
|
|
46
|
+
: request?.srcTokenAmount === sourceBaseUnits && matches && destinationMatches ? 'matching' : 'stale',
|
|
47
|
+
sourceAmount: amount,
|
|
48
|
+
destinationAmount,
|
|
49
|
+
destinationMatches,
|
|
50
|
+
sourceBaseUnits,
|
|
51
|
+
sourceAssetId: from.assetId,
|
|
52
|
+
destinationAssetId: to.assetId,
|
|
53
|
+
quoteCount: quotes.length,
|
|
54
|
+
};
|
|
55
|
+
})()`);
|
|
56
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
// Reads displayed swap/bridge inputs and quote details, optionally checking quote identity in the store.
|
|
1
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
3
|
+
import { readQuoteInputIdentity } from './quote-state.mjs';
|
|
2
4
|
import {
|
|
3
5
|
normalizeVisibleSwapBridgeState,
|
|
4
6
|
resolveVisibleSwapBridgeOptions,
|
|
@@ -54,7 +56,8 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
54
56
|
})()`);
|
|
55
57
|
const state = normalizeVisibleSwapBridgeState(raw, options, 'extension');
|
|
56
58
|
const requireQuote = input.node?.require_quote === true;
|
|
57
|
-
|
|
59
|
+
const quoteIdentity = requireQuote ? await readQuoteInputIdentity(page) : undefined;
|
|
60
|
+
if (requireQuote && (quoteIdentity.status !== 'matching' || quoteIdentity.sourceAmount !== state.sourceAmount || quoteIdentity.destinationAmount !== state.destinationAmount || !raw.quote.ctaEnabled || !(Number(state.destinationAmount) > 0)
|
|
58
61
|
|| !/^(?:Swap|Bridge)$/iu.test(String(raw.quote.cta)))) {
|
|
59
62
|
throw new Error('Swap quote is not ready for review.');
|
|
60
63
|
}
|
|
@@ -62,6 +65,7 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
62
65
|
action: input.action,
|
|
63
66
|
...state,
|
|
64
67
|
quote: raw.quote,
|
|
68
|
+
...(quoteIdentity ? { quoteIdentity } : {}),
|
|
65
69
|
proofPath: 'visible-dom-accessibility',
|
|
66
70
|
};
|
|
67
71
|
}));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Opens Swap and selects source and destination assets by exact ID or unambiguous chain and symbol.
|
|
1
2
|
import { runAdapter, withExtensionPage, dataTestId } from '../platform/cdp.mjs';
|
|
2
3
|
import { openSwap } from '../ui/navigate.mjs';
|
|
3
4
|
import { resolveAssetSelectionOptions } from '../../shared/swap-bridge/transaction.mjs';
|
|
@@ -57,7 +58,17 @@ async function selectAsset(page, role, asset, deadline) {
|
|
|
57
58
|
if (!(await isVisible(page, search))) {
|
|
58
59
|
throw new Error(`Swap ${role} asset picker did not open.`);
|
|
59
60
|
}
|
|
60
|
-
await page.
|
|
61
|
+
const existing = await page.evaluate(`(() => {
|
|
62
|
+
const prefix = ${JSON.stringify(`bridge-asset--${asset.chain}/`)};
|
|
63
|
+
const exactId = ${JSON.stringify(asset.assetId ? `bridge-asset--${asset.assetId}` : null)};
|
|
64
|
+
return [...document.querySelectorAll('[data-testid^="bridge-asset--"]')]
|
|
65
|
+
.filter((row) => exactId
|
|
66
|
+
? row.getAttribute('data-testid') === exactId
|
|
67
|
+
: String(row.getAttribute('data-testid') || '').startsWith(prefix)
|
|
68
|
+
&& String(row.querySelector('[data-testid="bridge-asset-symbol"]')?.textContent || '').trim().toUpperCase() === ${JSON.stringify(asset.symbol)})
|
|
69
|
+
.map((row) => row.getAttribute('data-testid'));
|
|
70
|
+
})()`);
|
|
71
|
+
if (existing.length === 0) await page.setInput(search, asset.query);
|
|
61
72
|
const testId = await findAsset(page, asset, deadline);
|
|
62
73
|
await page.click(dataTestId(testId));
|
|
63
74
|
while (Date.now() < deadline) {
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// Enters a swap/bridge amount and optionally waits for a matching usable quote or an unavailable route.
|
|
1
2
|
import { runAdapter, withExtensionPage, dataTestId } from '../platform/cdp.mjs';
|
|
2
3
|
import { resolveAmountOptions } from '../../shared/swap-bridge/transaction.mjs';
|
|
4
|
+
import { readQuoteInputIdentity } from './quote-state.mjs';
|
|
3
5
|
|
|
4
6
|
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
5
7
|
const options = resolveAmountOptions(input.node);
|
|
@@ -7,7 +9,6 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
7
9
|
await page.setInput(source, options.amount);
|
|
8
10
|
const deadline = Date.now() + options.timeoutMs;
|
|
9
11
|
let observed;
|
|
10
|
-
let lastRefreshAt = 0;
|
|
11
12
|
do {
|
|
12
13
|
observed = await page.evaluate(`(() => {
|
|
13
14
|
const value = (testId) => {
|
|
@@ -24,22 +25,24 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
24
25
|
noQuote: document.body.innerText.includes("This trade route isn't available right now"),
|
|
25
26
|
};
|
|
26
27
|
})()`);
|
|
28
|
+
if (options.waitForQuote) {
|
|
29
|
+
observed.quoteIdentity = await readQuoteInputIdentity(page);
|
|
30
|
+
if (observed.quoteIdentity.sourceAmount !== observed.sourceAmount || observed.quoteIdentity.destinationAmount !== observed.destinationAmount) observed.quoteIdentity.status = 'stale';
|
|
31
|
+
}
|
|
27
32
|
if (!options.waitForQuote || observed.noQuote) break;
|
|
28
|
-
if (
|
|
29
|
-
|
|
30
|
-
await page.click(dataTestId('bridge-cta-button'));
|
|
31
|
-
lastRefreshAt = Date.now();
|
|
33
|
+
if (observed.quoteIdentity.status === 'matching' && !observed.ctaEnabled && /^Insufficient funds$/iu.test(String(observed.cta))) {
|
|
34
|
+
throw new Error(`Swap quote matches the input but cannot be submitted: ${JSON.stringify(observed)}.`);
|
|
32
35
|
}
|
|
33
|
-
if (Number(observed.destinationAmount) > 0 && observed.ctaEnabled
|
|
36
|
+
if (observed.quoteIdentity?.status === 'matching' && Number(observed.destinationAmount) > 0 && observed.ctaEnabled
|
|
34
37
|
&& /^(?:Swap|Bridge)$/iu.test(String(observed.cta))) break;
|
|
35
38
|
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 250))');
|
|
36
39
|
} while (Date.now() < deadline);
|
|
37
40
|
if (observed.sourceAmount !== options.amount) {
|
|
38
41
|
throw new Error(`Swap source amount mismatch: expected ${options.amount}, observed ${observed.sourceAmount}.`);
|
|
39
42
|
}
|
|
40
|
-
if (options.waitForQuote && !observed.noQuote && !(Number(observed.destinationAmount) > 0
|
|
43
|
+
if (options.waitForQuote && !observed.noQuote && !(observed.quoteIdentity?.status === 'matching' && Number(observed.destinationAmount) > 0
|
|
41
44
|
&& observed.ctaEnabled && /^(?:Swap|Bridge)$/iu.test(String(observed.cta)))) {
|
|
42
|
-
throw new Error(`Swap quote did not resolve within ${options.timeoutMs}ms
|
|
45
|
+
throw new Error(`Swap quote did not resolve within ${options.timeoutMs}ms: ${JSON.stringify(observed)}.\nNext: mm-harness call metamask.swap_bridge.read_visible_state`);
|
|
43
46
|
}
|
|
44
47
|
return { action: input.action, ...observed, proofPath: 'visible-ui-amount' };
|
|
45
48
|
}));
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
// Uses the visible Max control and optionally waits for a matching usable quote or an unavailable route.
|
|
1
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
import { resolveAmountOptions } from '../../shared/swap-bridge/transaction.mjs';
|
|
4
|
+
import { requireVisibleObservation } from '../../shared/ui/locators.mjs';
|
|
5
|
+
import { readQuoteInputIdentity } from './quote-state.mjs';
|
|
3
6
|
|
|
4
7
|
async function pause(page) {
|
|
5
8
|
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 250))');
|
|
@@ -11,21 +14,11 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
11
14
|
wait_for_quote: input.node?.wait_for_quote,
|
|
12
15
|
timeout_ms: input.node?.timeout_ms,
|
|
13
16
|
});
|
|
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);
|
|
17
|
+
const visible = requireVisibleObservation(await page.observe(['ui.visible']));
|
|
18
|
+
const controls = visible.items.filter((item) =>
|
|
19
|
+
item.label === 'Max' && item.selector && item.enabled !== false);
|
|
20
|
+
if (controls.length !== 1) throw new Error('Swap requires exactly one visible, enabled Max control.');
|
|
21
|
+
await page.click(controls[0].selector);
|
|
29
22
|
const deadline = Date.now() + options.timeoutMs;
|
|
30
23
|
let observed = null;
|
|
31
24
|
while (Date.now() < deadline) {
|
|
@@ -45,14 +38,21 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
45
38
|
noQuote: document.body.innerText.includes("This trade route isn't available right now"),
|
|
46
39
|
};
|
|
47
40
|
})()`);
|
|
41
|
+
if (options.waitForQuote) {
|
|
42
|
+
observed.quoteIdentity = await readQuoteInputIdentity(page);
|
|
43
|
+
if (observed.quoteIdentity.sourceAmount !== observed.sourceAmount || observed.quoteIdentity.destinationAmount !== observed.destinationAmount) observed.quoteIdentity.status = 'stale';
|
|
44
|
+
}
|
|
45
|
+
if (observed.quoteIdentity?.status === 'matching' && !observed.loading && !observed.ctaEnabled && /^Insufficient funds$/iu.test(String(observed.cta))) {
|
|
46
|
+
throw new Error(`Swap Max quote matches the input but cannot be submitted: ${JSON.stringify(observed)}.`);
|
|
47
|
+
}
|
|
48
48
|
if (Number(observed.sourceAmount) > 0
|
|
49
|
-
&& (!options.waitForQuote || observed.noQuote || (!observed.loading && Number(observed.destinationAmount) > 0 && observed.ctaEnabled))) break;
|
|
49
|
+
&& (!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
50
|
await pause(page);
|
|
51
51
|
}
|
|
52
52
|
if (!(Number(observed?.sourceAmount) > 0)) throw new Error('Swap Max did not populate a positive source amount.');
|
|
53
53
|
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.`);
|
|
54
|
+
&& (observed.quoteIdentity?.status !== 'matching' || observed.loading || !(Number(observed.destinationAmount) > 0) || !observed.ctaEnabled || !/^(?:Swap|Bridge)$/iu.test(String(observed.cta)))) {
|
|
55
|
+
throw new Error(`Swap Max quote did not resolve within ${options.timeoutMs}ms: ${JSON.stringify(observed)}.`);
|
|
56
56
|
}
|
|
57
57
|
return { action: input.action, ...observed, proofPath: 'trusted-pointer-visible-ui' };
|
|
58
58
|
}));
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Saves custom swap/bridge slippage and waits for the displayed quote and submit control to refresh.
|
|
1
2
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
|
|
3
4
|
async function pause(page) {
|
|
@@ -74,10 +75,8 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
74
75
|
if (!observed?.includes(`${percent}%`)) {
|
|
75
76
|
throw new Error(`Swap quote did not display Slippage: ${percent}%.`);
|
|
76
77
|
}
|
|
77
|
-
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 500))');
|
|
78
78
|
let stableSince = null;
|
|
79
79
|
let quote = null;
|
|
80
|
-
let lastRefreshAt = 0;
|
|
81
80
|
while (Date.now() < deadline) {
|
|
82
81
|
quote = await page.evaluate(`(() => {
|
|
83
82
|
const destination = document.querySelector('[data-testid="to-amount"]');
|
|
@@ -90,13 +89,6 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
90
89
|
ctaEnabled: Boolean(cta && !cta.disabled),
|
|
91
90
|
};
|
|
92
91
|
})()`);
|
|
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
92
|
const ready = !quote.loading && Number(quote.destinationAmount) > 0 && quote.ctaEnabled
|
|
101
93
|
&& /^(?:Swap|Bridge)$/iu.test(quote.cta);
|
|
102
94
|
if (ready) {
|