From 1f508a30d9939175dd113cf274624e46595a9138 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Wed, 16 Aug 2023 21:11:17 -0230 Subject: [PATCH] Add types of hidden properties to Sentry data (#20457) * Add types of hidden properties to Sentry data The masked wallet state object sent to Sentry has been updated to include the type of each property omitted from the mask. This lets us at least see the full state shape, making it easier to see when errors are caused by invalid state. Relates to #20449 * Remove inconsistent state snapshot properties The state snapshot tests have been updated to exclude properties that were shown to differ between runs. --- shared/modules/object.utils.js | 4 + test/e2e/tests/errors.spec.js | 60 +++++++--- ...rs-after-init-opt-in-background-state.json | 63 ++++++++-- .../errors-after-init-opt-in-ui-state.json | 110 +++++++++++++++++- 4 files changed, 211 insertions(+), 26 deletions(-) diff --git a/shared/modules/object.utils.js b/shared/modules/object.utils.js index 24119be56..ce7eb1da1 100644 --- a/shared/modules/object.utils.js +++ b/shared/modules/object.utils.js @@ -7,6 +7,8 @@ * should be included, and a sub-mask implies the property should be further * masked according to that sub-mask. * + * If a property is not found in the last, its type is included instead. + * * @param {object} object - The object to mask * @param {Object} mask - The mask to apply to the object */ @@ -16,6 +18,8 @@ export function maskObject(object, mask) { state[key] = object[key]; } else if (mask[key]) { state[key] = maskObject(object[key], mask[key]); + } else { + state[key] = typeof object[key]; } return state; }, {}); diff --git a/test/e2e/tests/errors.spec.js b/test/e2e/tests/errors.spec.js index 8784ef168..3098a5083 100644 --- a/test/e2e/tests/errors.spec.js +++ b/test/e2e/tests/errors.spec.js @@ -1,42 +1,72 @@ const { resolve } = require('path'); const { promises: fs } = require('fs'); const { strict: assert } = require('assert'); -const { get, has, set } = require('lodash'); +const { get, has, set, unset } = require('lodash'); const { Browser } = require('selenium-webdriver'); const { format } = require('prettier'); const { convertToHexValue, withFixtures } = require('../helpers'); const FixtureBuilder = require('../fixture-builder'); -const backgroundDateFields = ['CurrencyController.conversionDate']; -const uiDateFields = ['metamask.conversionDate']; +const maskedBackgroundFields = [ + 'CurrencyController.conversionDate', // This is a timestamp that changes each run +]; +const maskedUiFields = [ + 'metamask.conversionDate', // This is a timestamp that changes each run +]; + +const removedBackgroundFields = [ + // This property is timing-dependent + 'AccountTracker.currentBlockGasLimit', + // These properties are set to undefined, causing inconsistencies between Chrome and Firefox + 'AppStateController.currentPopupId', + 'AppStateController.timeoutMinutes', + 'TokenListController.tokensChainsCache', +]; + +const removedUiFields = [ + // This property is timing-dependent + 'metamask.currentBlockGasLimit', + // These properties are set to undefined, causing inconsistencies between Chrome and Firefox + 'metamask.currentPopupId', + 'metamask.timeoutMinutes', + 'metamask.tokensChainsCache', +]; /** - * Transform date properties to value types, to ensure that state is - * consistent between test runs. + * Transform background state to make it consistent between test runs. * * @param {unknown} data - The data to transform */ -function transformBackgroundDates(data) { - for (const field of backgroundDateFields) { +function transformBackgroundState(data) { + for (const field of maskedBackgroundFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } } + for (const field of removedBackgroundFields) { + if (has(data, field)) { + unset(data, field); + } + } return data; } /** - * Transform date properties to value types, to ensure that state is - * consistent between test runs. + * Transform UI state to make it consistent between test runs. * * @param {unknown} data - The data to transform */ -function transformUiDates(data) { - for (const field of uiDateFields) { +function transformUiState(data) { + for (const field of maskedUiFields) { if (has(data, field)) { set(data, field, typeof get(data, field)); } } + for (const field of removedUiFields) { + if (has(data, field)) { + unset(data, field); + } + } return data; } @@ -257,7 +287,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: transformBackgroundDates(appState), + data: transformBackgroundState(appState), snapshot: 'errors-before-init-opt-in-background-state', }); }, @@ -342,7 +372,7 @@ describe('Sentry errors', function () { const mockJsonBody = JSON.parse(mockTextBody[2]); const appState = mockJsonBody?.extra?.appState; await matchesSnapshot({ - data: transformUiDates(appState), + data: transformUiState(appState), snapshot: 'errors-before-init-opt-in-ui-state', }); }, @@ -509,7 +539,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformBackgroundDates(appState.store), + data: transformBackgroundState(appState.store), snapshot: 'errors-after-init-opt-in-background-state', }); }, @@ -603,7 +633,7 @@ describe('Sentry errors', function () { 'Invalid version state', ); await matchesSnapshot({ - data: transformUiDates(appState.store), + data: transformUiState(appState.store), snapshot: 'errors-after-init-opt-in-ui-state', }); }, diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json index f41fee885..2f29cc98c 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-background-state.json @@ -1,34 +1,81 @@ { - "AccountTracker": { "currentBlockGasLimit": "0x1c9c380" }, + "AccountTracker": { "accounts": "object" }, "AppStateController": { "connectedStatusPopoverHasBeenShown": true, - "defaultHomeActiveTabName": null + "defaultHomeActiveTabName": null, + "browserEnvironment": "object", + "popupGasPollTokens": "object", + "notificationGasPollTokens": "object", + "fullScreenGasPollTokens": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "outdatedBrowserWarningLastShown": "number", + "nftsDetectionNoticeDismissed": "boolean", + "showTestnetMessageInDropdown": "boolean", + "showBetaHeader": "boolean", + "showProductTour": "boolean", + "trezorModel": "object", + "nftsDropdownState": "object", + "termsOfUseLastAgreed": "number", + "qrHardware": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean", + "serviceWorkerLastActiveTime": "number" }, + "ApprovalController": "object", + "CachedBalancesController": "object", "CurrencyController": { "conversionDate": "number", "conversionRate": 1700, "nativeCurrency": "ETH", - "currentCurrency": "usd" + "currentCurrency": "usd", + "pendingCurrentCurrency": "object", + "pendingNativeCurrency": "object", + "usdConversionRate": "number" + }, + "DecryptMessageController": { + "unapprovedDecryptMsgs": "object", + "unapprovedDecryptMsgCount": 0 }, - "DecryptMessageController": { "unapprovedDecryptMsgCount": 0 }, "EncryptionPublicKeyController": { + "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0 }, + "EnsController": "object", "MetaMetricsController": { "participateInMetaMetrics": true, - "metaMetricsId": "fake-metrics-id" + "metaMetricsId": "fake-metrics-id", + "eventsBeforeMetricsOptIn": "object", + "traits": "object", + "fragments": "object", + "segmentApiCalls": "object", + "previousUserTraits": "object" }, "NetworkController": { + "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { + "chainId": "string", "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", "ticker": "ETH", - "type": "rpc" - } + "type": "rpc", + "id": "string" + }, + "networksMetadata": "object", + "networkConfigurations": "object" }, "SignatureController": { + "unapprovedMsgs": "object", + "unapprovedPersonalMsgs": "object", + "unapprovedTypedMessages": "object", "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, "unapprovedTypedMessagesCount": 0 - } + }, + "SwapsController": "object", + "TokenRatesController": "object", + "TokensController": "object", + "TxController": "object" } diff --git a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json index 4ecaa0417..129ae885a 100644 --- a/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -1,16 +1,28 @@ { + "DNS": "object", + "activeTab": "object", + "appState": "object", + "confirmTransaction": "object", "gas": { "customData": { "price": null, "limit": null } }, "history": { "mostRecentOverviewPage": "/" }, + "invalidCustomNetwork": "object", + "localeMessages": "object", "metamask": { "isInitialized": true, "isUnlocked": false, "isAccountMenuOpen": false, + "isNetworkMenuOpen": "boolean", + "identities": "object", + "unapprovedTxs": "object", + "networkConfigurations": "object", + "addressBook": "object", + "contractExchangeRates": "object", + "pendingTokens": "object", "customNonceValue": "", "useBlockie": false, "featureFlags": { "showIncomingTransactions": true }, "welcomeScreenSeen": false, "currentLocale": "en", - "currentBlockGasLimit": "", "preferences": { "hideZeroBalanceTokens": false, "showFiatInTestnets": false, @@ -19,31 +31,89 @@ }, "firstTimeFlowType": "import", "completedOnboarding": true, + "knownMethodData": "object", + "use4ByteResolution": "boolean", "participateInMetaMetrics": true, "nextNonce": null, "conversionRate": 1300, "nativeCurrency": "ETH", "connectedStatusPopoverHasBeenShown": true, "defaultHomeActiveTabName": null, + "browserEnvironment": "object", + "popupGasPollTokens": "object", + "notificationGasPollTokens": "object", + "fullScreenGasPollTokens": "object", + "recoveryPhraseReminderHasBeenShown": "boolean", + "recoveryPhraseReminderLastShown": "number", + "outdatedBrowserWarningLastShown": "number", + "nftsDetectionNoticeDismissed": "boolean", + "showTestnetMessageInDropdown": "boolean", + "showBetaHeader": "boolean", + "showProductTour": "boolean", + "trezorModel": "object", + "nftsDropdownState": "object", + "termsOfUseLastAgreed": "number", + "qrHardware": "object", + "usedNetworks": "object", + "snapsInstallPrivacyWarningShown": "boolean", + "serviceWorkerLastActiveTime": "number", "currentAppVersion": "10.34.4", "previousAppVersion": "", "previousMigrationVersion": 0, "currentMigrationVersion": 94, + "selectedNetworkClientId": "string", "networkId": "1337", "providerConfig": { + "chainId": "string", "nickname": "Localhost 8545", + "rpcPrefs": "object", + "rpcUrl": "string", "ticker": "ETH", - "type": "rpc" + "type": "rpc", + "id": "string" }, + "networksMetadata": "object", + "cachedBalances": "object", + "keyringTypes": "object", + "keyrings": "object", "useNonceField": false, "usePhishDetect": true, + "dismissSeedBackUpReminder": "boolean", + "disabledRpcMethodPreferences": "object", + "useMultiAccountBalanceChecker": "boolean", + "useTokenDetection": "boolean", + "useNftDetection": "boolean", + "useCurrencyRateCheck": "boolean", + "openSeaEnabled": "boolean", + "advancedGasFee": "object", + "lostIdentities": "object", "forgottenPassword": false, "ipfsGateway": "dweb.link", + "useAddressBarEnsResolution": "boolean", + "infuraBlocked": "boolean", + "ledgerTransportType": "string", + "snapRegistryList": "object", + "transactionSecurityCheckEnabled": "boolean", + "theme": "string", + "isLineaMainnetReleased": "boolean", + "selectedAddress": "string", "metaMetricsId": "fake-metrics-id", + "eventsBeforeMetricsOptIn": "object", + "traits": "object", + "fragments": "object", + "segmentApiCalls": "object", + "previousUserTraits": "object", "conversionDate": "number", "currentCurrency": "usd", + "pendingCurrentCurrency": "object", + "pendingNativeCurrency": "object", + "usdConversionRate": "number", "alertEnabledness": { "unconnectedAccount": true, "web3ShimUsage": true }, + "unconnectedAccountAlertShownOrigins": "object", + "web3ShimUsageOrigins": "object", "seedPhraseBackedUp": true, + "onboardingTabs": "object", + "incomingTransactions": "object", "incomingTxLastFetchedBlockByChainId": { "0x1": null, "0xe708": null, @@ -51,11 +121,45 @@ "0xaa36a7": null, "0xe704": null }, + "subjects": "object", + "permissionHistory": "object", + "permissionActivityLog": "object", + "subjectMetadata": "object", + "announcements": "object", + "gasFeeEstimates": "object", + "estimatedGasFeeTimeBounds": "object", + "gasEstimateType": "string", + "tokenList": "object", + "preventPollingOnNetworkRestart": "boolean", + "tokens": "object", + "ignoredTokens": "object", + "detectedTokens": "object", + "allTokens": "object", + "allIgnoredTokens": "object", + "allDetectedTokens": "object", + "smartTransactionsState": "object", + "allNftContracts": "object", + "allNfts": "object", + "ignoredNfts": "object", + "accounts": "object", + "currentNetworkTxList": "object", + "unapprovedDecryptMsgs": "object", "unapprovedDecryptMsgCount": 0, + "unapprovedEncryptionPublicKeyMsgs": "object", "unapprovedEncryptionPublicKeyMsgCount": 0, + "unapprovedMsgs": "object", + "unapprovedPersonalMsgs": "object", + "unapprovedTypedMessages": "object", "unapprovedMsgCount": 0, "unapprovedPersonalMsgCount": 0, - "unapprovedTypedMessagesCount": 0 + "unapprovedTypedMessagesCount": 0, + "swapsState": "object", + "ensResolutionsByAddress": "object", + "pendingApprovals": "object", + "pendingApprovalCount": "number", + "approvalFlows": "object" }, + "send": "object", + "swaps": "object", "unconnectedAccount": { "state": "CLOSED" } }