1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Introduce action metrics for mv3 service worker restart (#18346)

* fix dapp interaction e2e test

* wip

* add sentry post request mock

* fix console errors

* fix scripting console error and remove e2e test unnecessary check

* clean up

* remove e2e test

* stop skipping  test

* fixing build mv3 job

* fixing unit tests

* fixing unit tests

* fixing unit tests

* update coverages

* revert skip mv3 e2e test

* remove IN_TEST on the npm script

* remove console.log

* revert aria label changes

* revert aria label changes

* revert permission changes

* revert permission changes

* implement sw restart delay tracking

* fix rebase
This commit is contained in:
Pedro Figueiredo 2023-03-31 14:22:33 +01:00 committed by GitHub
parent 18a21ca57e
commit fbd68d4a3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 81 additions and 10 deletions

View File

@ -556,6 +556,10 @@ export function setupController(initState, initLangCode, overrides) {
if (message.name === WORKER_KEEP_ALIVE_MESSAGE) { if (message.name === WORKER_KEEP_ALIVE_MESSAGE) {
// To test un-comment this line and wait for 1 minute. An error should be shown on MetaMask UI. // To test un-comment this line and wait for 1 minute. An error should be shown on MetaMask UI.
remotePort.postMessage({ name: ACK_KEEP_ALIVE_MESSAGE }); remotePort.postMessage({ name: ACK_KEEP_ALIVE_MESSAGE });
controller.appStateController.setServiceWorkerLastActiveTime(
Date.now(),
);
} }
}); });
} }

View File

@ -51,6 +51,7 @@ export default class AppStateController extends EventEmitter {
'0x5': true, '0x5': true,
'0x539': true, '0x539': true,
}, },
serviceWorkerLastActiveTime: 0,
}); });
this.timer = null; this.timer = null;
@ -362,4 +363,10 @@ export default class AppStateController extends EventEmitter {
getCurrentPopupId() { getCurrentPopupId() {
return this.store.getState().currentPopupId; return this.store.getState().currentPopupId;
} }
setServiceWorkerLastActiveTime(serviceWorkerLastActiveTime) {
this.store.updateState({
serviceWorkerLastActiveTime,
});
}
} }

View File

@ -121,6 +121,7 @@ import { isMain, isFlask } from '../../shared/constants/environment';
// eslint-disable-next-line import/order // eslint-disable-next-line import/order
import { DesktopController } from '@metamask/desktop/dist/controllers/desktop'; import { DesktopController } from '@metamask/desktop/dist/controllers/desktop';
///: END:ONLY_INCLUDE_IN ///: END:ONLY_INCLUDE_IN
import { ACTION_QUEUE_METRICS_E2E_TEST } from '../../shared/constants/test-flags';
import { import {
onMessageReceived, onMessageReceived,
checkForMultipleVersionsRunning, checkForMultipleVersionsRunning,
@ -695,6 +696,7 @@ export default class MetamaskController extends EventEmitter {
this.keyringController.memStore.subscribe((state) => this.keyringController.memStore.subscribe((state) =>
this._onKeyringControllerUpdate(state), this._onKeyringControllerUpdate(state),
); );
this.keyringController.on('unlock', () => this._onUnlock()); this.keyringController.on('unlock', () => this._onUnlock());
this.keyringController.on('lock', () => this._onLock()); this.keyringController.on('lock', () => this._onLock());
@ -1190,6 +1192,25 @@ export default class MetamaskController extends EventEmitter {
}, },
); );
if (isManifestV3 && globalThis.isFirstTimeProfileLoaded === false) {
const { serviceWorkerLastActiveTime } =
this.appStateController.store.getState();
const metametricsPayload = {
category: EVENT.SOURCE.SERVICE_WORKERS,
event: EVENT_NAMES.SERVICE_WORKER_RESTARTED,
properties: {
service_worker_restarted_time:
Date.now() - serviceWorkerLastActiveTime,
},
};
try {
this.metaMetricsController.trackEvent(metametricsPayload);
} catch (e) {
log.warn('Failed to track service worker restart metric:', e);
}
}
this.metamaskMiddleware = createMetamaskMiddleware({ this.metamaskMiddleware = createMetamaskMiddleware({
static: { static: {
eth_syncing: false, eth_syncing: false,
@ -2627,7 +2648,7 @@ export default class MetamaskController extends EventEmitter {
try { try {
// Automatic login via config password // Automatic login via config password
const password = process.env.CONF?.PASSWORD; const password = process.env.CONF?.PASSWORD;
if (password) { if (password && !process.env.IN_TEST) {
await this.submitPassword(password); await this.submitPassword(password);
} }
// Automatic login via storage encryption key // Automatic login via storage encryption key
@ -2960,6 +2981,13 @@ export default class MetamaskController extends EventEmitter {
* @returns {} keyState * @returns {} keyState
*/ */
async addNewAccount(accountCount) { async addNewAccount(accountCount) {
const isActionMetricsQueueE2ETest =
this.appStateController.store.getState()[ACTION_QUEUE_METRICS_E2E_TEST];
if (process.env.IN_TEST && isActionMetricsQueueE2ETest) {
await new Promise((resolve) => setTimeout(resolve, 5_000));
}
const [primaryKeyring] = this.keyringController.getKeyringsByType( const [primaryKeyring] = this.keyringController.getKeyringsByType(
KeyringType.hdKeyTree, KeyringType.hdKeyTree,
); );

View File

@ -21,6 +21,7 @@
"build: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 test", "build: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 test",
"build:test:flask": "yarn build test --build-type flask", "build:test:flask": "yarn build test --build-type flask",
"build: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 test", "build: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 test",
"build:test:dev: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 --apply-lavamoat=false",
"test": "yarn lint && yarn test:unit && yarn test:unit:jest", "test": "yarn lint && yarn test:unit && yarn test:unit:jest",
"dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080", "dapp": "node development/static-server.js node_modules/@metamask/test-dapp/dist --port 8080",
"dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'", "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && yarn dapp'",
@ -32,6 +33,7 @@
"test:unit:mocha": "node ./test/run-unit-tests.js --mocha", "test:unit:mocha": "node ./test/run-unit-tests.js --mocha",
"test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js", "test:e2e:chrome": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js",
"test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps", "test:e2e:chrome:snaps": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --snaps",
"test:e2e:chrome:mv3": "SELENIUM_BROWSER=chrome node test/e2e/run-all.js --mv3",
"test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js", "test:e2e:firefox": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js",
"test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps", "test:e2e:firefox:snaps": "SELENIUM_BROWSER=firefox node test/e2e/run-all.js --snaps",
"test:e2e:single": "node test/e2e/run-e2e-test.js", "test:e2e:single": "node test/e2e/run-e2e-test.js",

View File

@ -374,6 +374,7 @@ export const EVENT_NAMES = {
ONBOARDING_WALLET_IMPORT_ATTEMPTED: 'Wallet Import Attempted', ONBOARDING_WALLET_IMPORT_ATTEMPTED: 'Wallet Import Attempted',
ONBOARDING_WALLET_VIDEO_PLAY: 'SRP Intro Video Played', ONBOARDING_WALLET_VIDEO_PLAY: 'SRP Intro Video Played',
ONBOARDING_TWITTER_CLICK: 'External Link Clicked', ONBOARDING_TWITTER_CLICK: 'External Link Clicked',
SERVICE_WORKER_RESTARTED: 'Service Worker Restarted',
}; };
export const EVENT = { export const EVENT = {
@ -447,6 +448,7 @@ export const EVENT = {
DAPP: 'dapp', DAPP: 'dapp',
USER: 'user', USER: 'user',
}, },
SERVICE_WORKERS: 'service_workers',
}, },
LOCATION: { LOCATION: {
TOKEN_DETAILS: 'token_details', TOKEN_DETAILS: 'token_details',

View File

@ -0,0 +1 @@
export const ACTION_QUEUE_METRICS_E2E_TEST = 'action_queue_metrics_e2e_test';

View File

@ -4,6 +4,9 @@ const {
} = require('@metamask/snaps-utils'); } = require('@metamask/snaps-utils');
const { merge } = require('lodash'); const { merge } = require('lodash');
const { CHAIN_IDS } = require('../../shared/constants/network'); const { CHAIN_IDS } = require('../../shared/constants/network');
const {
ACTION_QUEUE_METRICS_E2E_TEST,
} = require('../../shared/constants/test-flags');
const { SMART_CONTRACTS } = require('./seeder/smart-contracts'); const { SMART_CONTRACTS } = require('./seeder/smart-contracts');
function defaultFixture() { function defaultFixture() {
@ -309,6 +312,7 @@ function onboardingFixture() {
[CHAIN_IDS.GOERLI]: true, [CHAIN_IDS.GOERLI]: true,
[CHAIN_IDS.LOCALHOST]: true, [CHAIN_IDS.LOCALHOST]: true,
}, },
[ACTION_QUEUE_METRICS_E2E_TEST]: false,
}, },
NetworkController: { NetworkController: {
networkId: '1337', networkId: '1337',

View File

@ -12,6 +12,7 @@ describe('MV3 - Dapp interactions', function () {
balance: convertToHexValue(25000000000000000000), balance: convertToHexValue(25000000000000000000),
}, },
], ],
concurrent: { port: 8546, chainId: 1338 },
}; };
it('should continue to support dapp interactions after service worker re-start', async function () { it('should continue to support dapp interactions after service worker re-start', async function () {
await withFixtures( await withFixtures(

View File

@ -428,10 +428,6 @@ class Driver {
const artifactDir = `./test-artifacts/${this.browser}/${title}`; const artifactDir = `./test-artifacts/${this.browser}/${title}`;
const filepathBase = `${artifactDir}/test-failure`; const filepathBase = `${artifactDir}/test-failure`;
await fs.mkdir(artifactDir, { recursive: true }); await fs.mkdir(artifactDir, { recursive: true });
const isPageError = await this.isElementPresent('.error-page__details');
if (isPageError) {
await this.clickElement('.error-page__details');
}
const screenshot = await this.driver.takeScreenshot(); const screenshot = await this.driver.takeScreenshot();
await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, { await fs.writeFile(`${filepathBase}-screenshot.png`, screenshot, {
encoding: 'base64', encoding: 'base64',
@ -482,20 +478,22 @@ class Driver {
// 4Byte // 4Byte
'Failed to load resource: the server responded with a status of 502 (Bad Gateway)', 'Failed to load resource: the server responded with a status of 502 (Bad Gateway)',
]; ];
const { errors } = this; const { errors } = this;
const cdpConnection = await this.driver.createCDPConnection('page'); const cdpConnection = await this.driver.createCDPConnection('page');
await this.driver.onLogEvent(cdpConnection, (event) => { await this.driver.onLogEvent(cdpConnection, (event) => {
if (event.type === 'error') { if (event.type === 'error') {
const eventDescription = event.args.filter( const eventDescriptions = event.args.filter(
(err) => err.description !== undefined, (err) => err.description !== undefined,
); );
const [{ description }] = eventDescription;
const [eventDescription] = eventDescriptions;
const ignore = ignoredErrorMessages.some((message) => const ignore = ignoredErrorMessages.some((message) =>
description.includes(message), eventDescription?.description.includes(message),
); );
if (!ignore) { if (!ignore) {
errors.push(description); errors.push(eventDescription?.description);
logBrowserError(failOnConsoleError, description); logBrowserError(failOnConsoleError, eventDescription?.description);
} }
} }
}); });

View File

@ -41,6 +41,7 @@ describe('ActionQueue', () => {
expect(background.backgroundFunction.called).toStrictEqual(false); expect(background.backgroundFunction.called).toStrictEqual(false);
}); });
}); });
describe('submitRequestToBackground', () => { describe('submitRequestToBackground', () => {
it('calls promisified background method if the stream is connected', async () => { it('calls promisified background method if the stream is connected', async () => {
const background = { const background = {
@ -74,6 +75,7 @@ describe('ActionQueue', () => {
readable: false, readable: false,
}, },
backgroundFunction3: sinon.stub().yields(), backgroundFunction3: sinon.stub().yields(),
trackMetaMetricsEvent: sinon.stub().yields(),
}; };
_setBackgroundConnection(background); _setBackgroundConnection(background);
const requestPromise = submitRequestToBackground('backgroundFunction3'); const requestPromise = submitRequestToBackground('backgroundFunction3');
@ -133,6 +135,7 @@ describe('ActionQueue', () => {
trace.secondStarted = Date.now(); trace.secondStarted = Date.now();
setTimeout(() => cb(null, 'second'), 10); setTimeout(() => cb(null, 'second'), 10);
}, },
trackMetaMetricsEvent: sinon.stub().yields(),
}; };
_setBackgroundConnection(background); _setBackgroundConnection(background);
const scheduled = Promise.all([ const scheduled = Promise.all([
@ -159,6 +162,7 @@ describe('ActionQueue', () => {
trace.secondStarted = Date.now(); trace.secondStarted = Date.now();
setTimeout(() => cb(null, 'second'), 10); setTimeout(() => cb(null, 'second'), 10);
}, },
trackMetaMetricsEvent: sinon.stub().yields(),
}; };
_setBackgroundConnection(background); _setBackgroundConnection(background);
const scheduled = Promise.all([ const scheduled = Promise.all([
@ -189,6 +193,7 @@ describe('ActionQueue', () => {
trace.secondStarted = Date.now(); trace.secondStarted = Date.now();
setTimeout(() => cb(null, 'second'), 10); setTimeout(() => cb(null, 'second'), 10);
}, },
trackMetaMetricsEvent: sinon.stub().yields(),
}; };
_setBackgroundConnection(background); _setBackgroundConnection(background);
const scheduled = Promise.all([ const scheduled = Promise.all([
@ -217,6 +222,7 @@ describe('ActionQueue', () => {
}, 5); }, 5);
}, },
second: sinon.stub().yields(), second: sinon.stub().yields(),
trackMetaMetricsEvent: sinon.stub().yields(),
}; };
_setBackgroundConnection(background); _setBackgroundConnection(background);
const scheduled = Promise.race([ const scheduled = Promise.race([
@ -251,6 +257,7 @@ describe('ActionQueue', () => {
setTimeout(() => cb(null, 'second'), 10); setTimeout(() => cb(null, 'second'), 10);
}, },
third: sinon.stub().yields(), third: sinon.stub().yields(),
trackMetaMetricsEvent: sinon.stub().yields(),
}; };
flowControl.triggerRaceCondition = () => { flowControl.triggerRaceCondition = () => {
flowControl.waitFor = submitRequestToBackground('third'); flowControl.waitFor = submitRequestToBackground('third');

View File

@ -1,5 +1,8 @@
import pify from 'pify'; import pify from 'pify';
import { EVENT, EVENT_NAMES } from '../../../shared/constants/metametrics';
import { isManifestV3 } from '../../../shared/modules/mv3.utils'; import { isManifestV3 } from '../../../shared/modules/mv3.utils';
import { trackMetaMetricsEvent } from '../actions';
// // A simplified pify maybe? // // A simplified pify maybe?
// function pify(apiObject) { // function pify(apiObject) {
// return Object.keys(apiObject).reduce((promisifiedAPI, key) => { // return Object.keys(apiObject).reduce((promisifiedAPI, key) => {
@ -187,6 +190,20 @@ async function processActionRetryQueue() {
} }
processingQueue = true; processingQueue = true;
try { try {
if (actionRetryQueue.length > 0) {
const metametricsPayload = {
category: EVENT.SOURCE.SERVICE_WORKERS,
event: EVENT_NAMES.SERVICE_WORKER_RESTARTED,
properties: {
service_worker_action_queue_methods: actionRetryQueue.map(
(action) => action.request.method,
),
},
};
trackMetaMetricsEvent(metametricsPayload);
}
while ( while (
background?.connectionStream.readable && background?.connectionStream.readable &&
actionRetryQueue.length > 0 actionRetryQueue.length > 0