@deeeed/metamask-harness 0.48.0 → 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 +6 -0
- package/dist/commands/help.js +17 -5
- package/dist/funding-execution-context.js +19 -4
- package/dist/metamask-action-validation.js +3 -2
- package/dist/mm-harness-cli.js +2 -1
- package/docs/RECIPES.md +2 -2
- package/library/actions/core/perps/_controller.mjs +14 -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 +28 -4
- 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 +1 -0
- package/library/actions/extension/assets/import_custom_token.mjs +2 -0
- package/library/actions/extension/assets/open_details.mjs +1 -0
- package/library/actions/extension/assets/prepare_send.mjs +1 -0
- package/library/actions/extension/assets/read_details.mjs +1 -0
- package/library/actions/extension/assets/read_visible_state.mjs +1 -0
- package/library/actions/extension/assets/review_send.mjs +1 -0
- package/library/actions/extension/assets/select_network_scope.mjs +1 -0
- 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 +2 -0
- package/library/actions/extension/assets/verify_sorting.mjs +1 -0
- 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 +2 -0
- package/library/actions/extension/networks/custom_network.mjs +2 -0
- package/library/actions/extension/networks/read_visible_state.mjs +1 -0
- package/library/actions/extension/networks/remove_custom.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +2 -0
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +2 -0
- package/library/actions/extension/performance/measure_detached_dom.mjs +2 -0
- package/library/actions/extension/performance/measure_navigation_memory.mjs +2 -0
- 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 +2 -0
- 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 +159 -10
- package/library/actions/extension/perps/compare_provider_market.mjs +2 -0
- package/library/actions/extension/perps/edit_margin.mjs +2 -0
- 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 +1 -0
- package/library/actions/extension/perps/open_position_action.mjs +1 -0
- package/library/actions/extension/perps/perps.mjs +401 -25
- 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 +2 -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 +2 -0
- package/library/actions/extension/perps/read_visible_state.mjs +2 -0
- package/library/actions/extension/perps/search_markets.mjs +1 -0
- package/library/actions/extension/perps/select_activity_filter.mjs +1 -0
- package/library/actions/extension/perps/select_market_filter.mjs +1 -0
- package/library/actions/extension/perps/set_market_favorite.mjs +1 -0
- package/library/actions/extension/perps/state.mjs +1 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +2 -0
- package/library/actions/extension/perps/visible-mutation-identity.mjs +2 -0
- package/library/actions/extension/platform/cdp.mjs +29 -2
- package/library/actions/extension/settings/set_basic_functionality.mjs +1 -0
- package/library/actions/extension/swap_bridge/quote-state.mjs +2 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +1 -0
- package/library/actions/extension/swap_bridge/select_assets.mjs +1 -0
- package/library/actions/extension/swap_bridge/set_amount.mjs +1 -0
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +1 -0
- package/library/actions/extension/swap_bridge/set_slippage.mjs +1 -0
- package/library/actions/extension/ui/locators.mjs +1 -0
- package/library/actions/extension/ui/navigate.mjs +2 -0
- 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 +2 -0
- 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 +2 -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 +1 -0
- package/library/actions/mobile/assets/set_token_visibility.mjs +2 -0
- 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 +1 -0
- package/library/actions/mobile/networks/read_visible_state.mjs +1 -0
- package/library/actions/mobile/networks/remove_custom.mjs +1 -0
- 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 +6 -2
- 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 +2 -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 +2 -0
- 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 +2 -0
- package/library/actions/mobile/platform/observe-ui.mjs +2 -0
- 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 +2 -0
- 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 +2 -0
- 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 +20 -0
- package/library/manifests/extension.action-manifest.json +2 -2
- package/library/manifests/mobile.action-manifest.json +3 -3
- package/library/recipes/mobile/app/{lifecycle.android-smoke.recipe.json → lifecycle-smoke.recipe.json} +12 -11
- package/package.json +1 -1
- package/site/assets/help-recipes.json +188 -35
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Read bounded, normalized Perps snapshots through background APIs, optionally sampling market changes.
|
|
2
|
+
// History checks can require a matching visible activity card identity.
|
|
1
3
|
import { pathToFileURL } from 'node:url';
|
|
2
4
|
|
|
3
5
|
import { normalizeMarketSymbol, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Capture Perps DOM controls, order-entry values, and layout within configured item and visibility bounds.
|
|
2
|
+
// Normalize the observation and check requested visible-state expectations.
|
|
1
3
|
import { pathToFileURL } from 'node:url';
|
|
2
4
|
|
|
3
5
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Submit live take-profit and stop-loss changes through visible controls with mutation receipts.
|
|
2
|
+
// Check the funded position identity and observe the exact requested prices.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
assertTrustedExternalMutationTarget,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Parse and match visible Perps position and order identities using USD base-unit tolerances.
|
|
2
|
+
// Also wait for a stable, loaded Perps home view, optionally requiring zero positions and orders.
|
|
1
3
|
const INTEGER = /^(0|[1-9][0-9]{0,29})$/u;
|
|
2
4
|
|
|
3
5
|
export function parseUsdBaseUnits(value) {
|
|
@@ -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,
|
|
@@ -736,6 +739,24 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
736
739
|
});
|
|
737
740
|
}
|
|
738
741
|
|
|
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 };
|
|
758
|
+
}
|
|
759
|
+
|
|
739
760
|
async setInput(selector, value) {
|
|
740
761
|
const input = String(value ?? '');
|
|
741
762
|
const target = await this.evaluate(`(() => {
|
|
@@ -747,8 +768,14 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
747
768
|
if (typeof element.select === 'function') element.select();
|
|
748
769
|
return { tagName: element.tagName, previousLength: String(element.value ?? '').length };
|
|
749
770
|
})()`);
|
|
750
|
-
await this.session.call('Input.dispatchKeyEvent', {
|
|
751
|
-
|
|
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
|
+
});
|
|
752
779
|
for (const character of input) {
|
|
753
780
|
const upper = character.toUpperCase();
|
|
754
781
|
const isLetter = /^[A-Z]$/u.test(upper);
|
|
@@ -1,3 +1,5 @@
|
|
|
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.
|
|
1
3
|
export async function readQuoteInputIdentity(page) {
|
|
2
4
|
return page.evaluate(`(() => {
|
|
3
5
|
const state = globalThis.stateHooks?.store?.getState?.();
|
|
@@ -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';
|
|
@@ -1,3 +1,4 @@
|
|
|
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';
|
|
3
4
|
import { readQuoteInputIdentity } from './quote-state.mjs';
|
|
@@ -1,3 +1,4 @@
|
|
|
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';
|
|
3
4
|
import { requireVisibleObservation } from '../../shared/ui/locators.mjs';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Unlocks an existing wallet with the supplied or fixture password when a password input is present.
|
|
2
|
+
// Rejects onboarding profiles and waits for the password input to disappear after submission.
|
|
1
3
|
import { readFile } from 'node:fs/promises';
|
|
2
4
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
3
5
|
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Imports a wallet through onboarding or verifies an existing profile against the expected account.
|
|
2
|
+
// May unlock the wallet, finish onboarding, and select the expected account through visible UI.
|
|
1
3
|
import { resolveWalletImportCredentials, validateWalletImportOptions, walletImportMethod } from '../../shared/wallet/import-source.mjs';
|
|
2
4
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
3
5
|
import { pasteSecretRecoveryPhrase, setSecretInput } from './secret-input.mjs';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Locks the wallet through visible menu controls and waits for the unlock page.
|
|
2
|
+
// Opens Home first when the account menu is unavailable.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
|
|
3
5
|
const MENU = dataTestId('account-options-menu-button');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Clears the wallet through the visible forgot-password reset flow unless it is already fresh.
|
|
2
|
+
// Checks that onboarding is incomplete, the route is onboarding, and no account is selected.
|
|
1
3
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import { readWalletStateExpression } from './state.mjs';
|
|
3
5
|
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Enters wallet secrets through CDP input events and checks their retained length.
|
|
2
|
+
// Recovery-phrase paste reloads the page to install a temporary clipboard bridge, then removes it.
|
|
1
3
|
export async function setSecretInput(page, selector, value) {
|
|
2
4
|
const found = await page.evaluate(`(() => {
|
|
3
5
|
const element = document.querySelector(${JSON.stringify(selector)});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Selects an account by address, ID, or name through the background request hook.
|
|
2
|
+
// Falls back to visible account search when that hook is unavailable and checks the selected address.
|
|
1
3
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
listWalletAccountsExpression,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Builds page expressions for reading wallet/account state and changing the selected account through state hooks.
|
|
2
|
+
// Account selection sends a background request and polls until the selected ID matches.
|
|
1
3
|
const walletStatePreamble = `
|
|
2
4
|
const readWalletState = async () => {
|
|
3
5
|
const hooks = globalThis.stateHooks || {};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Dismisses onboarding completion and navigates the account list to select and check an expected EVM account.
|
|
2
|
+
// Validates address search with an alternate-address control and returns a hash of the matched identity.
|
|
1
3
|
import { createHash } from 'node:crypto';
|
|
2
4
|
|
|
3
5
|
const ACCOUNT_SEARCH = 'input[data-testid="multichain-account-list-search"], [data-testid="multichain-account-list-search"] input';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Switch token sorting to check alphabetical and declining fiat order in visible rows.
|
|
2
|
+
// Restore the original sort preference and report restoration failures.
|
|
1
3
|
import { runAdapter } from '../platform/bridge.mjs';
|
|
2
4
|
import {
|
|
3
5
|
closeNativeSession,
|
|
@@ -31,7 +33,8 @@ function assetRows(snapshot) {
|
|
|
31
33
|
|
|
32
34
|
function isAlphabetical(rows) {
|
|
33
35
|
const names = rows.map((row) => row.name);
|
|
34
|
-
|
|
36
|
+
// MetaMask's SES lockdown compares names by UTF-16 code units.
|
|
37
|
+
const expected = [...names].sort();
|
|
35
38
|
return names.length >= 2 && names.every((name, index) => name === expected[index]);
|
|
36
39
|
}
|
|
37
40
|
|
|
@@ -40,18 +43,78 @@ function isDeclining(rows) {
|
|
|
40
43
|
return values.length >= 2 && values.every((value, index) => index === 0 || values[index - 1] >= value);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
async function
|
|
46
|
+
async function openSortMenu(session, timeoutMs) {
|
|
47
|
+
if (sessionNode(await session.snapshot(), 'sort-alphabetical')) return;
|
|
44
48
|
await session.execute('ui.press', { test_id: 'token-sort-button', timeout_ms: timeoutMs, settle: false });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function selectedSort(session, timeoutMs) {
|
|
52
|
+
await openSortMenu(session, timeoutMs);
|
|
53
|
+
const deadline = Date.now() + timeoutMs;
|
|
54
|
+
do {
|
|
55
|
+
const snapshot = await session.snapshot();
|
|
56
|
+
const byIndex = new Map(snapshot.nodes.map((node) => [node.index, node]));
|
|
57
|
+
const selected = ['sort-alphabetical', 'sort-declining-balance'].filter((option) => {
|
|
58
|
+
const row = sessionNode(snapshot, option);
|
|
59
|
+
if (!row) return false;
|
|
60
|
+
// ListItemSelect renders a checkbox child only for the selected option.
|
|
61
|
+
return snapshot.nodes.some((node) => {
|
|
62
|
+
if (node.visibleToUser === false || !/checkbox$/iu.test(String(node.type))) return false;
|
|
63
|
+
let parent = byIndex.get(node.parentIndex);
|
|
64
|
+
for (let depth = 0; depth < 40 && parent; depth += 1) {
|
|
65
|
+
if (parent.index === row.index) return true;
|
|
66
|
+
parent = byIndex.get(parent.parentIndex);
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
if (selected.length === 1) return selected[0];
|
|
72
|
+
if (Date.now() < deadline) await new Promise((resolve) => setTimeout(resolve, 200));
|
|
73
|
+
} while (Date.now() < deadline);
|
|
74
|
+
throw new Error('Asset sort preference is unavailable or ambiguous.\nNext: mm-harness call ui.screenshot --adapter mobile');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async function choose(session, option, timeoutMs) {
|
|
78
|
+
await openSortMenu(session, timeoutMs);
|
|
45
79
|
await session.execute('ui.press', { test_id: option, timeout_ms: timeoutMs, settle: false });
|
|
46
80
|
const deadline = Date.now() + timeoutMs;
|
|
47
81
|
let rows = [];
|
|
48
82
|
while (Date.now() <= deadline) {
|
|
49
83
|
const snapshot = await session.snapshot();
|
|
50
84
|
rows = assetRows(snapshot);
|
|
51
|
-
|
|
85
|
+
const ordered = option === 'sort-alphabetical' ? isAlphabetical(rows) : isDeclining(rows);
|
|
86
|
+
if (ordered && !sessionNode(snapshot, option)) return rows;
|
|
52
87
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
53
88
|
}
|
|
54
|
-
throw new Error(`Asset sort ${option} did not
|
|
89
|
+
throw new Error(`Asset sort ${option} did not produce the expected visible row order: ${JSON.stringify(rows)}.`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function visibleTokenList(session, deadline) {
|
|
93
|
+
while (Date.now() <= deadline) {
|
|
94
|
+
const snapshot = await session.snapshot();
|
|
95
|
+
if (!sessionNode(snapshot, 'sort-alphabetical')
|
|
96
|
+
&& sessionNode(snapshot, 'token-sort-button') && assetRows(snapshot).length >= 2) return;
|
|
97
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
98
|
+
}
|
|
99
|
+
throw new Error('Asset token list was not visible after restoring the sort preference.\nNext: mm-harness call ui.screenshot --adapter mobile');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function restoreSort(session, option, timeoutMs) {
|
|
103
|
+
const deadline = Date.now() + timeoutMs;
|
|
104
|
+
const remaining = () => {
|
|
105
|
+
const ms = deadline - Date.now();
|
|
106
|
+
if (ms <= 0) throw new Error('Asset sort restoration timed out.\nNext: mm-harness call ui.screenshot --adapter mobile');
|
|
107
|
+
return ms;
|
|
108
|
+
};
|
|
109
|
+
if (await selectedSort(session, remaining()) !== option) {
|
|
110
|
+
await session.execute('ui.press', { test_id: option, timeout_ms: remaining(), settle: false });
|
|
111
|
+
await visibleTokenList(session, deadline);
|
|
112
|
+
if (await selectedSort(session, remaining()) !== option) {
|
|
113
|
+
throw new Error('Asset sort preference was not restored.\nNext: mm-harness call ui.screenshot --adapter mobile');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
await session.execute('ui.press', { test_id: option, timeout_ms: remaining(), settle: false });
|
|
117
|
+
await visibleTokenList(session, deadline);
|
|
55
118
|
}
|
|
56
119
|
|
|
57
120
|
async function revealTokenList(session, timeoutMs) {
|
|
@@ -76,9 +139,13 @@ function sessionNode(snapshot, identifier) {
|
|
|
76
139
|
runAdapter(async (input) => {
|
|
77
140
|
const timeoutMs = Number(input.node?.timeout_ms ?? 30_000);
|
|
78
141
|
const session = createNativeSession(input, 'Assets', `sort-${process.pid}`);
|
|
142
|
+
const errors = [];
|
|
143
|
+
let initialSort;
|
|
144
|
+
let result;
|
|
79
145
|
try {
|
|
80
146
|
await session.open();
|
|
81
147
|
await revealTokenList(session, timeoutMs);
|
|
148
|
+
initialSort = await selectedSort(session, timeoutMs);
|
|
82
149
|
const alphabetical = await choose(session, 'sort-alphabetical', timeoutMs);
|
|
83
150
|
const declining = await choose(session, 'sort-declining-balance', timeoutMs);
|
|
84
151
|
const failures = [];
|
|
@@ -91,14 +158,30 @@ runAdapter(async (input) => {
|
|
|
91
158
|
if (failures.length > 0) {
|
|
92
159
|
throw new Error(`Asset token sorting failed: ${failures.join('; ')}.`);
|
|
93
160
|
}
|
|
94
|
-
|
|
161
|
+
result = {
|
|
95
162
|
action: input.action,
|
|
96
163
|
alphabetical: alphabetical.map((row) => row.name),
|
|
97
164
|
decliningFiat: declining.map((row) => row.fiat),
|
|
98
|
-
|
|
165
|
+
initialSort: initialSort.slice('sort-'.length),
|
|
166
|
+
restoredSort: initialSort.slice('sort-'.length),
|
|
99
167
|
proofPath: 'native-accessibility-token-sorting',
|
|
100
168
|
};
|
|
101
|
-
}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
errors.push(error);
|
|
171
|
+
}
|
|
172
|
+
if (initialSort) {
|
|
173
|
+
try {
|
|
174
|
+
await restoreSort(session, initialSort, timeoutMs);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
errors.push(error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
try {
|
|
102
180
|
await closeNativeSession(session);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
errors.push(error);
|
|
103
183
|
}
|
|
184
|
+
if (errors.length === 1) throw errors[0];
|
|
185
|
+
if (errors.length > 1) throw new AggregateError(errors, `Asset sorting failed: ${errors.map((error) => error.message).join('; ')}`);
|
|
186
|
+
return result;
|
|
104
187
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Capture a bounded section of Perps console logs and write performance measurements and validation artifacts.
|
|
2
|
+
// Persist the start offset and file identity to reject replaced or rewritten logs when the capture ends.
|
|
1
3
|
import { createHash } from "node:crypto";
|
|
2
4
|
import { mkdir, open, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
3
5
|
import path from "node:path";
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Implement Mobile Perps navigation, trading mutations, assertions, and setup or cleanup through the bridge.
|
|
2
|
+
// Strategy getters used during cleanup can settle tracked collateral.
|
|
1
3
|
import { pathToFileURL } from 'node:url';
|
|
2
4
|
import {
|
|
3
5
|
MOBILE_BRIDGE_RESULT_ERROR_CODES,
|
|
@@ -1771,7 +1773,7 @@ async function applyTutorialState(input, config) {
|
|
|
1771
1773
|
|
|
1772
1774
|
export async function startState(input) {
|
|
1773
1775
|
if (input.node?.account !== undefined && (typeof input.node.account !== 'string' || !input.node.account.trim())) {
|
|
1774
|
-
throw new Error('Perps start state requires a non-empty
|
|
1776
|
+
throw new Error('Perps start state requires a non-empty account name or address when account is supplied.\nNext: mm-harness actions metamask.perps.start_state --adapter mobile --json');
|
|
1775
1777
|
}
|
|
1776
1778
|
const params = paramsForState(input);
|
|
1777
1779
|
const config = applyTradingStatePolicy(
|
|
@@ -1831,7 +1833,9 @@ export async function startState(input) {
|
|
|
1831
1833
|
node: {
|
|
1832
1834
|
...accountInput.node,
|
|
1833
1835
|
action: 'metamask.wallet.select_account',
|
|
1834
|
-
|
|
1836
|
+
...(/^0x[0-9a-f]{40}$/iu.test(config.account.trim())
|
|
1837
|
+
? { address: config.account.trim() }
|
|
1838
|
+
: { name: config.account.trim() }),
|
|
1835
1839
|
},
|
|
1836
1840
|
})
|
|
1837
1841
|
: { skipped: true };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Poll native screen and accessibility observations until Perps state satisfies the requested checks.
|
|
2
|
+
// Return normalized state or report the last assertion or observation failure at the deadline.
|
|
1
3
|
import { observeNativeUi } from '../platform/observe-ui.mjs';
|
|
2
4
|
import {
|
|
3
5
|
normalizeVisiblePerpsState,
|