diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e9e2c295a7e5..8f911a8a7b2c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -2439,6 +2439,7 @@ export default class MetamaskController extends EventEmitter { allowedEvents: [], }), disabled: !this.preferencesController.state.useExternalServices, + getMetaMetricsId: () => this.metaMetricsController.getMetaMetricsId(), clientConfigApiService: new ClientConfigApiService({ fetch: globalThis.fetch.bind(globalThis), config: { diff --git a/lavamoat/browserify/beta/policy.json b/lavamoat/browserify/beta/policy.json index 14bbfb44df6d..caa9bc784288 100644 --- a/lavamoat/browserify/beta/policy.json +++ b/lavamoat/browserify/beta/policy.json @@ -1645,7 +1645,8 @@ "@metamask/remote-feature-flag-controller": { "packages": { "@metamask/base-controller": true, - "cockatiel": true + "cockatiel": true, + "uuid": true } }, "@metamask/rpc-errors": { diff --git a/lavamoat/browserify/flask/policy.json b/lavamoat/browserify/flask/policy.json index 14bbfb44df6d..caa9bc784288 100644 --- a/lavamoat/browserify/flask/policy.json +++ b/lavamoat/browserify/flask/policy.json @@ -1645,7 +1645,8 @@ "@metamask/remote-feature-flag-controller": { "packages": { "@metamask/base-controller": true, - "cockatiel": true + "cockatiel": true, + "uuid": true } }, "@metamask/rpc-errors": { diff --git a/lavamoat/browserify/main/policy.json b/lavamoat/browserify/main/policy.json index 14bbfb44df6d..caa9bc784288 100644 --- a/lavamoat/browserify/main/policy.json +++ b/lavamoat/browserify/main/policy.json @@ -1645,7 +1645,8 @@ "@metamask/remote-feature-flag-controller": { "packages": { "@metamask/base-controller": true, - "cockatiel": true + "cockatiel": true, + "uuid": true } }, "@metamask/rpc-errors": { diff --git a/lavamoat/browserify/mmi/policy.json b/lavamoat/browserify/mmi/policy.json index c37c59addda1..4f25a7024bd8 100644 --- a/lavamoat/browserify/mmi/policy.json +++ b/lavamoat/browserify/mmi/policy.json @@ -1737,7 +1737,8 @@ "@metamask/remote-feature-flag-controller": { "packages": { "@metamask/base-controller": true, - "cockatiel": true + "cockatiel": true, + "uuid": true } }, "@metamask/rpc-errors": { diff --git a/package.json b/package.json index 93b0a0425483..d07771a17f43 100644 --- a/package.json +++ b/package.json @@ -338,7 +338,7 @@ "@metamask/providers": "^18.2.0", "@metamask/queued-request-controller": "^7.0.1", "@metamask/rate-limit-controller": "^6.0.0", - "@metamask/remote-feature-flag-controller": "^1.1.0", + "@metamask/remote-feature-flag-controller": "^1.3.0", "@metamask/rpc-errors": "^7.0.0", "@metamask/safe-event-emitter": "^3.1.1", "@metamask/scure-bip39": "^2.0.3", diff --git a/test/e2e/constants.ts b/test/e2e/constants.ts index 6af1056d7232..2826200dc293 100644 --- a/test/e2e/constants.ts +++ b/test/e2e/constants.ts @@ -74,8 +74,8 @@ export const DEFAULT_SOLANA_ACCOUNT = /* Default (mocked) SOLANA balance used by the Solana RPC provider */ export const DEFAULT_SOLANA_BALANCE = 1; // SOL -/* Title of the mocked E2E test empty HTML page */ -export const EMPTY_E2E_TEST_PAGE_TITLE = 'E2E Test Page'; +/* Title of Portfolio page */ +export const PORTFOLIO_PAGE_TITLE = 'MetaMask Portfolio'; /* Account types */ export enum ACCOUNT_TYPE { @@ -83,3 +83,7 @@ export enum ACCOUNT_TYPE { Bitcoin, Solana, } + +/* Meta metricsId generated by generateMetaMetricsId */ +export const MOCK_META_METRICS_ID = + '0x86bacb9b2bf9a7e8d2b147eadb95ac9aaa26842327cd24afc8bd4b3c1d136420'; diff --git a/test/e2e/mock-e2e.js b/test/e2e/mock-e2e.js index c7d8b02d4c7d..72809ea62e48 100644 --- a/test/e2e/mock-e2e.js +++ b/test/e2e/mock-e2e.js @@ -753,7 +753,29 @@ async function setupMocking( return { ok: true, statusCode: 200, - json: [{ feature1: true }, { feature2: false }], + json: [ + { feature1: true }, + { feature2: false }, + { + feature3: [ + { + value: 'valueA', + name: 'groupA', + scope: { type: 'threshold', value: 0.3 }, + }, + { + value: 'valueB', + name: 'groupB', + scope: { type: 'threshold', value: 0.5 }, + }, + { + scope: { type: 'threshold', value: 1 }, + value: 'valueC', + name: 'groupC', + }, + ], + }, + ], }; }); diff --git a/test/e2e/tests/metrics/delete-metametrics-data.spec.ts b/test/e2e/tests/metrics/delete-metametrics-data.spec.ts index 308ff8508d0a..621e42b29c85 100644 --- a/test/e2e/tests/metrics/delete-metametrics-data.spec.ts +++ b/test/e2e/tests/metrics/delete-metametrics-data.spec.ts @@ -17,11 +17,7 @@ const selectors = { globalMenuSettingsButton: '[data-testid="global-menu-settings"]', securityAndPrivacySettings: { text: 'Security & privacy', tag: 'div' }, experimentalSettings: { text: 'Experimental', tag: 'div' }, - deletMetaMetricsSettings: '[data-testid="delete-metametrics-data-button"]', - deleteMetaMetricsDataButton: { - text: 'Delete MetaMetrics data', - tag: 'button', - }, + deleteMetaMetricsDataButton: '[data-testid="delete-metametrics-data-button"]', clearButton: { text: 'Clear', tag: 'button' }, backButton: '[data-testid="settings-back-button"]', }; @@ -111,7 +107,6 @@ describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) { await driver.clickElement(selectors.globalMenuSettingsButton); await driver.clickElement(selectors.securityAndPrivacySettings); - await driver.findElement(selectors.deletMetaMetricsSettings); await driver.clickElement(selectors.deleteMetaMetricsDataButton); // there is a race condition, where we need to wait before clicking clear button otherwise an error is thrown in the background @@ -157,65 +152,37 @@ describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) { }, ); }); + it('while user has opted out for metrics tracking', async function () { await withFixtures( { fixtures: new FixtureBuilder() .withMetaMetricsController({ metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: false, }) .build(), defaultGanacheOptions, title: this.test?.fullTitle(), testSpecificMock: mockSegment, }, - async ({ - driver, - mockedEndpoint: mockedEndpoints, - }: TestSuiteArguments) => { + async ({ driver }: TestSuiteArguments) => { await unlockWallet(driver); await driver.clickElement(selectors.accountOptionsMenuButton); await driver.clickElement(selectors.globalMenuSettingsButton); await driver.clickElement(selectors.securityAndPrivacySettings); - await driver.findElement(selectors.deletMetaMetricsSettings); - await driver.clickElement(selectors.deleteMetaMetricsDataButton); - - // there is a race condition, where we need to wait before clicking clear button otherwise an error is thrown in the background - // we cannot wait for a UI conditon, so we a delay to mitigate this until another solution is found - await driver.delay(3000); - await driver.clickElementAndWaitToDisappear(selectors.clearButton); - const deleteMetaMetricsDataButton = await driver.findElement( selectors.deleteMetaMetricsDataButton, ); await ( deleteMetaMetricsDataButton as WebElementWithWaitForElementState ).waitForElementState('disabled'); - - const events = await getEventPayloads( - driver, - mockedEndpoints as MockedEndpoint[], - ); - assert.equal(events.length, 2); - - await driver.clickElementAndWaitToDisappear( - '.mm-box button[aria-label="Close"]', - ); - await driver.clickElement(selectors.accountOptionsMenuButton); - await driver.clickElement(selectors.globalMenuSettingsButton); - await driver.clickElement(selectors.securityAndPrivacySettings); - - const deleteMetaMetricsDataButtonRefreshed = await driver.findElement( - selectors.deleteMetaMetricsDataButton, - ); - await ( - deleteMetaMetricsDataButtonRefreshed as WebElementWithWaitForElementState - ).waitForElementState('disabled'); }, ); }); + it('when the user has never opted in for metrics', async function () { await withFixtures( { @@ -230,7 +197,6 @@ describe('Delete MetaMetrics Data @no-mmi', function (this: Suite) { await driver.clickElement(selectors.accountOptionsMenuButton); await driver.clickElement(selectors.globalMenuSettingsButton); await driver.clickElement(selectors.securityAndPrivacySettings); - await driver.findElement(selectors.deletMetaMetricsSettings); const deleteMetaMetricsDataButton = await driver.findElement( selectors.deleteMetaMetricsDataButton, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json index 0480d536973d..460aa14595bf 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-background-state.json @@ -258,7 +258,7 @@ }, "QueuedRequestController": { "queuedRequestCount": 0 }, "RemoteFeatureFlagController": { - "remoteFeatureFlags": { "feature1": true, "feature2": false }, + "remoteFeatureFlags": {}, "cacheTimestamp": "number" }, "SelectedNetworkController": { "domains": "object" }, diff --git a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json index ea5ac7a32034..19000925eebe 100644 --- a/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json +++ b/test/e2e/tests/metrics/state-snapshots/errors-after-init-opt-in-ui-state.json @@ -6,65 +6,65 @@ "isAccountMenuOpen": false, "isNetworkMenuOpen": false, "nextNonce": null, + "pendingTokens": "object", + "welcomeScreenSeen": false, "confirmationExchangeRates": {}, - "ledgerTransportStatus": "string", - "ledgerWebHidConnectedStatus": "string", - "loadingMessage": "undefined", + "shouldClose": "boolean", "menuOpen": "boolean", "modal": "object", "alertOpen": "boolean", "alertMessage": null, + "qrCodeData": null, "networkDropdownOpen": "boolean", "importNftsModal": "object", + "showPermittedNetworkToastOpen": "boolean", + "showIpfsModalOpen": "boolean", + "showBasicFunctionalityModal": "boolean", + "externalServicesOnboardingToggleState": "boolean", "keyringRemovalSnapModal": "object", + "showKeyringRemovalSnapModal": "boolean", "importTokensModalOpen": "boolean", "deprecatedNetworkModalOpen": "boolean", "accountDetail": "object", "isLoading": "boolean", "isNftStillFetchingIndication": "boolean", + "showNftDetectionEnablementToast": "boolean", + "loadingMessage": "undefined", + "warning": "string", "buyView": "object", "defaultHdPaths": "object", "networksTabSelectedRpcUrl": "string", "requestAccountTabs": "object", + "openMetaMaskTabs": "object", "currentWindowTab": "object", + "showWhatsNewPopup": "boolean", + "showTermsOfUsePopup": "boolean", + "singleExceptions": "object", "gasLoadingAnimationIsShowing": "boolean", - "externalServicesOnboardingToggleState": "boolean", + "smartTransactionsError": null, + "smartTransactionsErrorMessageDismissed": "boolean", + "ledgerWebHidConnectedStatus": "string", + "ledgerTransportStatus": "string", "newNftAddedMessage": "string", "removeNftMessage": "string", "newNetworkAddedName": "string", "editedNetwork": "undefined", "newNetworkAddedConfigurationId": "string", + "selectedNetworkConfigurationId": "string", + "sendInputCurrencySwitched": "boolean", "newTokensImported": "string", "newTokensImportedError": "string", "onboardedInThisUISession": "boolean", "customTokenAmount": "string", - "accountDetailsAddress": "string", - "isAddingNewNetwork": "boolean", - "isMultiRpcOnboarding": "boolean", - "errorInSettings": null, - "openMetaMaskTabs": "object", - "pendingTokens": "object", - "qrCodeData": null, - "selectedNetworkConfigurationId": "string", "scrollToBottom": "boolean", - "sendInputCurrencySwitched": "boolean", - "shouldClose": "boolean", - "showBasicFunctionalityModal": "boolean", - "showDataDeletionErrorModal": "boolean", + "txId": null, + "accountDetailsAddress": "string", "showDeleteMetaMetricsDataModal": "boolean", - "showIpfsModalOpen": "boolean", - "showKeyringRemovalSnapModal": "boolean", - "showNftDetectionEnablementToast": "boolean", - "showPermittedNetworkToastOpen": "boolean", - "showTermsOfUsePopup": "boolean", - "showWhatsNewPopup": "boolean", - "singleExceptions": "object", - "smartTransactionsError": null, - "smartTransactionsErrorMessageDismissed": "boolean", + "showDataDeletionErrorModal": "boolean", "snapsInstallPrivacyWarningShown": "boolean", - "txId": null, - "warning": "string", - "welcomeScreenSeen": false + "isAddingNewNetwork": "boolean", + "isMultiRpcOnboarding": "boolean", + "errorInSettings": null }, "bridge": "object", "confirmAlerts": "object", @@ -289,7 +289,7 @@ "isCheckingAccountsPresence": "boolean", "queuedRequestCount": 0, "fcmToken": "string", - "remoteFeatureFlags": { "feature1": true, "feature2": false }, + "remoteFeatureFlags": {}, "cacheTimestamp": "number", "accounts": "object", "accountsByChainId": "object", diff --git a/test/e2e/tests/portfolio/portfolio-site.spec.ts b/test/e2e/tests/portfolio/portfolio-site.spec.ts index f630de50f1da..d7952b5f1baa 100644 --- a/test/e2e/tests/portfolio/portfolio-site.spec.ts +++ b/test/e2e/tests/portfolio/portfolio-site.spec.ts @@ -1,6 +1,6 @@ import { MockttpServer } from 'mockttp'; import { withFixtures } from '../../helpers'; -import { EMPTY_E2E_TEST_PAGE_TITLE } from '../../constants'; +import { PORTFOLIO_PAGE_TITLE, MOCK_META_METRICS_ID } from '../../constants'; import FixtureBuilder from '../../fixture-builder'; import { emptyHtmlPage } from '../../mock-e2e'; import HomePage from '../../page-objects/pages/home/homepage'; @@ -26,20 +26,23 @@ describe('Portfolio site', function () { await withFixtures( { dapp: true, - fixtures: new FixtureBuilder().build(), + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: MOCK_META_METRICS_ID, + participateInMetaMetrics: true, + }) + .build(), title: this.test?.fullTitle(), testSpecificMock: mockPortfolioSite, }, async ({ driver }) => { await loginWithBalanceValidation(driver); await new HomePage(driver).openPortfolioPage(); - - // Click Portfolio site - await driver.switchToWindowWithTitle(EMPTY_E2E_TEST_PAGE_TITLE); + await driver.switchToWindowWithTitle(PORTFOLIO_PAGE_TITLE); // Verify site await driver.waitForUrl({ - url: 'https://portfolio.metamask.io/?metamaskEntry=ext_portfolio_button&metametricsId=null&metricsEnabled=false&marketingEnabled=false', + url: `https://portfolio.metamask.io/?metamaskEntry=ext_portfolio_button&metametricsId=${MOCK_META_METRICS_ID}&metricsEnabled=true&marketingEnabled=false`, }); }, ); diff --git a/test/e2e/tests/remote-feature-flag/mock-data.ts b/test/e2e/tests/remote-feature-flag/mock-data.ts index 5d05dc9367f9..f9da6d052f19 100644 --- a/test/e2e/tests/remote-feature-flag/mock-data.ts +++ b/test/e2e/tests/remote-feature-flag/mock-data.ts @@ -1,4 +1,8 @@ export const MOCK_REMOTE_FEATURE_FLAGS_RESPONSE = { feature1: true, feature2: false, + feature3: { + name: 'groupC', + value: 'valueC', + }, }; diff --git a/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts b/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts index d8d6878e37c4..42ebe7dc762c 100644 --- a/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts +++ b/test/e2e/tests/remote-feature-flag/remote-feature-flag.spec.ts @@ -4,13 +4,19 @@ import { getCleanAppState, withFixtures } from '../../helpers'; import FixtureBuilder from '../../fixture-builder'; import { TestSuiteArguments } from '../confirmations/transactions/shared'; import { loginWithBalanceValidation } from '../../page-objects/flows/login.flow'; +import { MOCK_META_METRICS_ID } from '../../constants'; import { MOCK_REMOTE_FEATURE_FLAGS_RESPONSE } from './mock-data'; describe('Remote feature flag', function (this: Suite) { - it('should be fetched when basic functionality toggle is on', async function () { + it('should be fetched with threshold value when basic functionality toggle is on', async function () { await withFixtures( { - fixtures: new FixtureBuilder().build(), + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: MOCK_META_METRICS_ID, + participateInMetaMetrics: true, + }) + .build(), title: this.test?.fullTitle(), }, async ({ driver }: TestSuiteArguments) => { diff --git a/ui/pages/settings/info-tab/info-tab.component.js b/ui/pages/settings/info-tab/info-tab.component.js index 6c6efac729bb..6da93878c7ef 100644 --- a/ui/pages/settings/info-tab/info-tab.component.js +++ b/ui/pages/settings/info-tab/info-tab.component.js @@ -57,10 +57,12 @@ export default class InfoTab extends PureComponent { componentDidMount() { const { t } = this.context; handleSettingsRefs(t, t('about'), this.settingsRefs); - if (this.props.remoteFeatureFlags.testBooleanFlag) { + if (this.props.remoteFeatureFlags.testFlagForThreshold) { // eslint-disable-next-line no-console console.log( - `Fetch remote feature flag success, eg: testBooleanFlag has value ${this.props.remoteFeatureFlags.testBooleanFlag}`, + `Fetch remote feature flag success, eg: testFlagForThreshold has value ${JSON.stringify( + this.props.remoteFeatureFlags.testFlagForThreshold, + )}`, ); } } diff --git a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap index 491ed613b1e5..8043e29b0eec 100644 --- a/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap +++ b/ui/pages/settings/security-tab/__snapshots__/security-tab.test.js.snap @@ -1563,7 +1563,6 @@ exports[`Security Tab should match snapshot 1`] = `