mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 18:00:18 +01:00
a6ef7bb244
* Reorganize Sentry error e2e tests The tests have been reorganized into different describe blocks. Each describe block is for either before or after initialization, and either with or without opting into metrics. This was done to simplify later test additions. The conditions for each test are now in the describe block, letting us test additional things in each of these conditions. The conditions were flattened to a single level to avoid excessive indentation. * Add error e2e test for background and UI errors The Sentry e2e tests before initialization only tested background errors, and the after initialization tests only tested UI errors. Now both types of errors are tested in both scenarios. * Add error e2e tests for Sentry error state E2E tests have been added to test the state object sent along with each Sentry error. At the moment this object is empty in some circumstances, but this will change in later PRs. * Rename throw test error function * Only setup debug/test state hooks in dev/test builds The state hooks used for debugging and testing are now only included in dev or test builds. The function name was updated and given a JSDoc description to explain this more clearly as well. * Add state snapshot assertions State snapshot assertions have been added to the e2e error tests. These snapshots will be very useful in reviewing a few PRs that will follow this one. We might decide to remove these snapshots after this set of Sentry refactors, as they might be more work to maintain than they're worth. But they will be useful at least in the short-term. The login step has been removed from a few tests because it introduced indeterminacy (the login process continued asynchronously after the login, and sometimes was not finished when the error was triggered). * Ensure login page has rendered during setup This fixes an intermittent failure on Firefox * Format snapshots with prettier before writing them * Use defined set of date fields rather than infering from name * Remove waits for error screen The error screen only appears after a long timeout, and it doesn't affect the next test steps at all.
273 lines
7.8 KiB
JavaScript
273 lines
7.8 KiB
JavaScript
import copyToClipboard from 'copy-to-clipboard';
|
|
import log from 'loglevel';
|
|
import { clone } from 'lodash';
|
|
import React from 'react';
|
|
import { render } from 'react-dom';
|
|
import browser from 'webextension-polyfill';
|
|
|
|
import { getEnvironmentType } from '../app/scripts/lib/util';
|
|
import { AlertTypes } from '../shared/constants/alerts';
|
|
import { maskObject } from '../shared/modules/object.utils';
|
|
import { SENTRY_STATE } from '../app/scripts/lib/setupSentry';
|
|
import { ENVIRONMENT_TYPE_POPUP } from '../shared/constants/app';
|
|
import switchDirection from '../shared/lib/switch-direction';
|
|
import { setupLocale } from '../shared/lib/error-utils';
|
|
import * as actions from './store/actions';
|
|
import configureStore from './store/store';
|
|
import {
|
|
getPermittedAccountsForCurrentTab,
|
|
getSelectedAddress,
|
|
} from './selectors';
|
|
import { ALERT_STATE } from './ducks/alerts';
|
|
import {
|
|
getUnconnectedAccountAlertEnabledness,
|
|
getUnconnectedAccountAlertShown,
|
|
} from './ducks/metamask/metamask';
|
|
import Root from './pages';
|
|
import txHelper from './helpers/utils/tx-helper';
|
|
import { _setBackgroundConnection } from './store/action-queue';
|
|
|
|
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn');
|
|
|
|
let reduxStore;
|
|
|
|
/**
|
|
* Method to update backgroundConnection object use by UI
|
|
*
|
|
* @param backgroundConnection - connection object to background
|
|
*/
|
|
export const updateBackgroundConnection = (backgroundConnection) => {
|
|
_setBackgroundConnection(backgroundConnection);
|
|
backgroundConnection.onNotification((data) => {
|
|
if (data.method === 'sendUpdate') {
|
|
reduxStore.dispatch(actions.updateMetamaskState(data.params[0]));
|
|
} else {
|
|
throw new Error(
|
|
`Internal JSON-RPC Notification Not Handled:\n\n ${JSON.stringify(
|
|
data,
|
|
)}`,
|
|
);
|
|
}
|
|
});
|
|
};
|
|
|
|
export default function launchMetamaskUi(opts, cb) {
|
|
const { backgroundConnection } = opts;
|
|
///: BEGIN:ONLY_INCLUDE_IN(desktop)
|
|
let desktopEnabled = false;
|
|
|
|
backgroundConnection.getDesktopEnabled(function (err, result) {
|
|
if (err) {
|
|
return;
|
|
}
|
|
|
|
desktopEnabled = result;
|
|
});
|
|
///: END:ONLY_INCLUDE_IN
|
|
|
|
// check if we are unlocked first
|
|
backgroundConnection.getState(function (err, metamaskState) {
|
|
if (err) {
|
|
cb(
|
|
err,
|
|
{
|
|
...metamaskState,
|
|
///: BEGIN:ONLY_INCLUDE_IN(desktop)
|
|
desktopEnabled,
|
|
///: END:ONLY_INCLUDE_IN
|
|
},
|
|
backgroundConnection,
|
|
);
|
|
return;
|
|
}
|
|
startApp(metamaskState, backgroundConnection, opts).then((store) => {
|
|
setupStateHooks(store);
|
|
cb(
|
|
null,
|
|
store,
|
|
///: BEGIN:ONLY_INCLUDE_IN(desktop)
|
|
backgroundConnection,
|
|
///: END:ONLY_INCLUDE_IN
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
async function startApp(metamaskState, backgroundConnection, opts) {
|
|
// parse opts
|
|
if (!metamaskState.featureFlags) {
|
|
metamaskState.featureFlags = {};
|
|
}
|
|
|
|
const { currentLocaleMessages, enLocaleMessages } = await setupLocale(
|
|
metamaskState.currentLocale,
|
|
);
|
|
|
|
if (metamaskState.textDirection === 'rtl') {
|
|
await switchDirection('rtl');
|
|
}
|
|
|
|
const draftInitialState = {
|
|
activeTab: opts.activeTab,
|
|
|
|
// metamaskState represents the cross-tab state
|
|
metamask: metamaskState,
|
|
|
|
// appState represents the current tab's popup state
|
|
appState: {},
|
|
|
|
localeMessages: {
|
|
currentLocale: metamaskState.currentLocale,
|
|
current: currentLocaleMessages,
|
|
en: enLocaleMessages,
|
|
},
|
|
};
|
|
|
|
updateBackgroundConnection(backgroundConnection);
|
|
|
|
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
|
|
const { origin } = draftInitialState.activeTab;
|
|
const permittedAccountsForCurrentTab =
|
|
getPermittedAccountsForCurrentTab(draftInitialState);
|
|
const selectedAddress = getSelectedAddress(draftInitialState);
|
|
const unconnectedAccountAlertShownOrigins =
|
|
getUnconnectedAccountAlertShown(draftInitialState);
|
|
const unconnectedAccountAlertIsEnabled =
|
|
getUnconnectedAccountAlertEnabledness(draftInitialState);
|
|
|
|
if (
|
|
origin &&
|
|
unconnectedAccountAlertIsEnabled &&
|
|
!unconnectedAccountAlertShownOrigins[origin] &&
|
|
permittedAccountsForCurrentTab.length > 0 &&
|
|
!permittedAccountsForCurrentTab.includes(selectedAddress)
|
|
) {
|
|
draftInitialState[AlertTypes.unconnectedAccount] = {
|
|
state: ALERT_STATE.OPEN,
|
|
};
|
|
actions.setUnconnectedAccountAlertShown(origin);
|
|
}
|
|
}
|
|
|
|
const store = configureStore(draftInitialState);
|
|
reduxStore = store;
|
|
|
|
// if unconfirmed txs, start on txConf page
|
|
const unapprovedTxsAll = txHelper(
|
|
metamaskState.unapprovedTxs,
|
|
metamaskState.unapprovedMsgs,
|
|
metamaskState.unapprovedPersonalMsgs,
|
|
metamaskState.unapprovedDecryptMsgs,
|
|
metamaskState.unapprovedEncryptionPublicKeyMsgs,
|
|
metamaskState.unapprovedTypedMessages,
|
|
metamaskState.networkId,
|
|
metamaskState.providerConfig.chainId,
|
|
);
|
|
const numberOfUnapprovedTx = unapprovedTxsAll.length;
|
|
if (numberOfUnapprovedTx > 0) {
|
|
store.dispatch(
|
|
actions.showConfTxPage({
|
|
id: unapprovedTxsAll[0].id,
|
|
}),
|
|
);
|
|
}
|
|
|
|
// global metamask api - used by tooling
|
|
global.metamask = {
|
|
updateCurrentLocale: (code) => {
|
|
store.dispatch(actions.updateCurrentLocale(code));
|
|
},
|
|
setProviderType: (type) => {
|
|
store.dispatch(actions.setProviderType(type));
|
|
},
|
|
setFeatureFlag: (key, value) => {
|
|
store.dispatch(actions.setFeatureFlag(key, value));
|
|
},
|
|
};
|
|
|
|
// start app
|
|
render(<Root store={store} />, opts.container);
|
|
|
|
return store;
|
|
}
|
|
|
|
/**
|
|
* Setup functions on `window.stateHooks`. Some of these support
|
|
* application features, and some are just for debugging or testing.
|
|
*
|
|
* @param {object} store - The Redux store.
|
|
*/
|
|
function setupStateHooks(store) {
|
|
if (process.env.METAMASK_DEBUG || process.env.IN_TEST) {
|
|
/**
|
|
* The following stateHook is a method intended to throw an error, used in
|
|
* our E2E test to ensure that errors are attempted to be sent to sentry.
|
|
*
|
|
* @param {string} [msg] - The error message to throw, defaults to 'Test Error'
|
|
*/
|
|
window.stateHooks.throwTestError = async function (msg = 'Test Error') {
|
|
const error = new Error(msg);
|
|
error.name = 'TestError';
|
|
throw error;
|
|
};
|
|
/**
|
|
* The following stateHook is a method intended to throw an error in the
|
|
* background, used in our E2E test to ensure that errors are attempted to be
|
|
* sent to sentry.
|
|
*
|
|
* @param {string} [msg] - The error message to throw, defaults to 'Test Error'
|
|
*/
|
|
window.stateHooks.throwTestBackgroundError = async function (
|
|
msg = 'Test Error',
|
|
) {
|
|
store.dispatch(actions.throwTestBackgroundError(msg));
|
|
};
|
|
}
|
|
|
|
window.stateHooks.getCleanAppState = async function () {
|
|
const state = clone(store.getState());
|
|
state.version = global.platform.getVersion();
|
|
state.browser = window.navigator.userAgent;
|
|
state.completeTxList = await actions.getTransactions({
|
|
filterToCurrentNetwork: false,
|
|
});
|
|
return state;
|
|
};
|
|
window.stateHooks.getSentryState = function () {
|
|
const fullState = store.getState();
|
|
const debugState = maskObject(fullState, SENTRY_STATE);
|
|
return {
|
|
browser: window.navigator.userAgent,
|
|
store: debugState,
|
|
version: global.platform.getVersion(),
|
|
};
|
|
};
|
|
}
|
|
|
|
window.logStateString = async function (cb) {
|
|
const state = await window.stateHooks.getCleanAppState();
|
|
browser.runtime
|
|
.getPlatformInfo()
|
|
.then((platform) => {
|
|
state.platform = platform;
|
|
const stateString = JSON.stringify(state, null, 2);
|
|
cb(null, stateString);
|
|
})
|
|
.catch((err) => {
|
|
cb(err);
|
|
});
|
|
};
|
|
|
|
window.logState = function (toClipboard) {
|
|
return window.logStateString((err, result) => {
|
|
if (err) {
|
|
console.error(err.message);
|
|
} else if (toClipboard) {
|
|
copyToClipboard(result);
|
|
console.log('State log copied');
|
|
} else {
|
|
console.log(result);
|
|
}
|
|
});
|
|
};
|