From bfbe10ba287d645a07dbddeaaf4719b286d75f90 Mon Sep 17 00:00:00 2001 From: Pedro Figueiredo Date: Thu, 4 May 2023 10:38:09 +0400 Subject: [PATCH] e2e test for action metrics (#18347) * e2e test for action metrics * add tests to implementing sw restart delay tracking * wip * wip * wip * wip * remove unneeded code * remove scuttling changes contemplated in #18989 --- app/scripts/metamask-controller.js | 2 +- package.json | 3 +- test/e2e/mv3/service-worker-restart.spec.js | 172 ++++++++++++++++++ test/e2e/tests/metamask-responsive-ui.spec.js | 6 +- test/e2e/webdriver/chrome.js | 4 +- test/e2e/webdriver/index.js | 4 +- 6 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 test/e2e/mv3/service-worker-restart.spec.js diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ffaa9bfe3..601091454 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1275,7 +1275,7 @@ export default class MetamaskController extends EventEmitter { }, ); - if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === false) { + if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === undefined) { const { serviceWorkerLastActiveTime } = this.appStateController.store.getState(); const metametricsPayload = { diff --git a/package.json b/package.json index e39491528..0e9356694 100644 --- a/package.json +++ b/package.json @@ -8,13 +8,14 @@ }, "scripts": { "start": "yarn build:dev dev --apply-lavamoat=false --snow=false", - "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "start:mv3": "ENABLE_MV3=true yarn build:dev dev --apply-lavamoat=false", "start:flask": "yarn start --build-type flask", + "start:lavamoat": "yarn build:dev dev --apply-lavamoat=true", "dist": "yarn build dist", "build": "yarn lavamoat:build", "build:dev": "node development/build/index.js", "start:test": "SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev", + "start:test:mv3": "ENABLE_MV3=true SEGMENT_HOST='https://api.segment.io' SEGMENT_WRITE_KEY='FAKE' SENTRY_DSN_DEV=https://fake@sentry.io/0000000 PORTFOLIO_URL=http://127.0.0.1:8080 yarn build:dev testDev", "benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/benchmark.js", "mv3:stats:chrome": "SELENIUM_BROWSER=chrome ENABLE_MV3=true ts-node test/e2e/mv3-perf-stats/index.js", "user-actions-benchmark:chrome": "SELENIUM_BROWSER=chrome ts-node test/e2e/user-actions-benchmark.js", diff --git a/test/e2e/mv3/service-worker-restart.spec.js b/test/e2e/mv3/service-worker-restart.spec.js new file mode 100644 index 000000000..82dab98c3 --- /dev/null +++ b/test/e2e/mv3/service-worker-restart.spec.js @@ -0,0 +1,172 @@ +const { strict: assert } = require('assert'); +const { convertToHexValue, withFixtures } = require('../helpers'); +const FixtureBuilder = require('../fixture-builder'); +const { + ACTION_QUEUE_METRICS_E2E_TEST, +} = require('../../../shared/constants/test-flags'); +const { + MetaMetricsEventName, + MetaMetricsEventCategory, +} = require('../../../shared/constants/metametrics'); + +const PRIVATE_KEY = + '0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC'; + +const generateETHBalance = (eth) => convertToHexValue(eth * 10 ** 18); +const defaultGanacheOptions = { + accounts: [{ secretKey: PRIVATE_KEY, balance: generateETHBalance(25) }], +}; + +const numberOfSegmentRequests = 3; + +async function mockSegment(mockServer) { + return await mockServer + .forPost('https://api.segment.io/v1/batch') + .withJsonBodyIncluding({ + batch: [ + { + event: MetaMetricsEventName.ServiceWorkerRestarted, + }, + ], + }) + .times(numberOfSegmentRequests) + .thenCallback(() => { + return { + statusCode: 200, + }; + }); +} + +describe('MV3 - Service worker restart', function () { + let windowHandles; + + it('should continue to add new a account when service worker can not restart immediately', async function () { + const driverOptions = { openDevToolsForTabs: true }; + await withFixtures( + { + dapp: true, + fixtures: new FixtureBuilder() + .withMetaMetricsController({ + metaMetricsId: 'fake-metrics-id', + participateInMetaMetrics: true, + }) + .withAppStateController({ + [ACTION_QUEUE_METRICS_E2E_TEST]: true, + }) + .build(), + ganacheOptions: defaultGanacheOptions, + title: this.test.title, + // because of segment + failOnConsoleError: false, + testSpecificMock: mockSegment, + driverOptions, + }, + async ({ driver, mockedEndpoint }) => { + await driver.navigate(); + + // unlock wallet + await driver.fill('#password', 'correct horse battery staple'); + await driver.press('#password', driver.Key.ENTER); + + // open the account menu + await driver.clickElement('[data-testid="account-menu-icon"]'); + await driver.clickElement({ text: 'Create account', tag: 'div' }); + + await driver.fill('.new-account-create-form__input', 'Test Account'); + + await driver.clickElement({ text: 'Create', tag: 'button' }); + + await driver.openNewPage('chrome://inspect/#service-workers/'); + await driver.clickElement({ + text: 'Service workers', + tag: 'button', + }); + + await driver.clickElement({ + text: 'terminate', + tag: 'span', + }); + + windowHandles = await driver.getAllWindowHandles(); + const extension = windowHandles[0]; + await driver.switchToWindow(extension); + + // balance renders + await driver.waitForSelector( + { + css: '[class="eth-overview__primary-container"]', + // balance is 0 because we switched to an empty account + text: '0 ETH', + }, + { timeout: 50_000 }, + ); + + await driver.findElement({ text: '0x097...7950', tag: 'div' }); + + // assert that the segment request has been sent through inspecting the mock + await driver.wait(async () => { + const isPending = await mockedEndpoint.isPending(); + return isPending === false; + }, 10_000); + const mockedRequests = await mockedEndpoint.getSeenRequests(); + + assert.equal(mockedRequests.length, numberOfSegmentRequests); + + await assertSWRestartTimeEvent(mockedRequests[0]); + await assertSWRestartTimeEvent(mockedRequests[1]); + await assertSWProcessActionQueueEvent( + mockedRequests[2], + 'addNewAccount', + ); + }, + ); + }); +}); + +async function assertSWRestartTimeEvent(request) { + assert.equal(request.url, 'https://api.segment.io/v1/batch'); + + assert.equal(request.body.json.batch.length, 1); + + const [firstResult] = request.body.json.batch; + + assert.equal(firstResult.event, MetaMetricsEventName.ServiceWorkerRestarted); + + assert.equal( + typeof firstResult.properties.service_worker_restarted_time, + 'number', + ); + + assert.equal(firstResult.properties.service_worker_restarted_time > 0, true); + assert.equal( + firstResult.properties.category, + MetaMetricsEventCategory.ServiceWorkers, + ); + assert.equal(firstResult.properties.chain_id, convertToHexValue(1337)); + assert.equal(firstResult.properties.environment_type, 'background'); + assert.equal(firstResult.properties.locale, 'en'); +} + +async function assertSWProcessActionQueueEvent(request, method) { + assert.equal(request.url, 'https://api.segment.io/v1/batch'); + + assert.equal(request.body.json.batch.length, 1); + + const [firstResult] = request.body.json.batch; + + assert.equal(firstResult.event, MetaMetricsEventName.ServiceWorkerRestarted); + + assert.equal( + firstResult.properties.service_worker_action_queue_methods.indexOf( + method, + ) !== '-1', + true, + ); + assert.equal( + firstResult.properties.category, + MetaMetricsEventCategory.ServiceWorkers, + ); + assert.equal(firstResult.properties.chain_id, convertToHexValue(1337)); + assert.equal(firstResult.properties.environment_type, 'background'); + assert.equal(firstResult.properties.locale, 'en'); +} diff --git a/test/e2e/tests/metamask-responsive-ui.spec.js b/test/e2e/tests/metamask-responsive-ui.spec.js index e21668a10..66b1a9cfc 100644 --- a/test/e2e/tests/metamask-responsive-ui.spec.js +++ b/test/e2e/tests/metamask-responsive-ui.spec.js @@ -4,7 +4,7 @@ const FixtureBuilder = require('../fixture-builder'); describe('MetaMask Responsive UI', function () { it('Creating a new wallet', async function () { - const driverOptions = { responsive: true }; + const driverOptions = { openDevToolsForTabs: true }; await withFixtures( { @@ -75,7 +75,7 @@ describe('MetaMask Responsive UI', function () { }); it('Importing existing wallet from lock page', async function () { - const driverOptions = { responsive: true }; + const driverOptions = { openDevToolsForTabs: true }; const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'; @@ -116,7 +116,7 @@ describe('MetaMask Responsive UI', function () { }); it('Send Transaction from responsive window', async function () { - const driverOptions = { responsive: true }; + const driverOptions = { openDevToolsForTabs: true }; const ganacheOptions = { accounts: [ { diff --git a/test/e2e/webdriver/chrome.js b/test/e2e/webdriver/chrome.js index 09069906c..1b32bd675 100644 --- a/test/e2e/webdriver/chrome.js +++ b/test/e2e/webdriver/chrome.js @@ -13,9 +13,9 @@ const HTTPS_PROXY_HOST = '127.0.0.1:8000'; * A wrapper around a {@code WebDriver} instance exposing Chrome-specific functionality */ class ChromeDriver { - static async build({ responsive, port }) { + static async build({ openDevToolsForTabs, port }) { const args = [`load-extension=dist/chrome`]; - if (responsive) { + if (openDevToolsForTabs) { args.push('--auto-open-devtools-for-tabs'); } diff --git a/test/e2e/webdriver/index.js b/test/e2e/webdriver/index.js index ebec9a002..aeb6bc964 100644 --- a/test/e2e/webdriver/index.js +++ b/test/e2e/webdriver/index.js @@ -3,14 +3,14 @@ const Driver = require('./driver'); const ChromeDriver = require('./chrome'); const FirefoxDriver = require('./firefox'); -async function buildWebDriver({ responsive, port, timeOut } = {}) { +async function buildWebDriver({ openDevToolsForTabs, port, timeOut } = {}) { const browser = process.env.SELENIUM_BROWSER; const { driver: seleniumDriver, extensionId, extensionUrl, - } = await buildBrowserWebDriver(browser, { responsive, port }); + } = await buildBrowserWebDriver(browser, { openDevToolsForTabs, port }); const driver = new Driver(seleniumDriver, browser, extensionUrl, timeOut); return {