mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 01:47:00 +01:00
feat: implement swap event metric e2e test (#20129)
* implement swap event metric e2e test * fix lint error * clean up initial balance helpers * fix eslint warnings * Fix `token_to_amount` format to address firefox bug and refactor tests
This commit is contained in:
parent
280fd5f7ef
commit
537f1c7aee
@ -246,6 +246,7 @@ module.exports = {
|
||||
'shared/**/*.test.js',
|
||||
'ui/**/*.test.js',
|
||||
'ui/__mocks__/*.js',
|
||||
'test/e2e/helpers.test.js',
|
||||
],
|
||||
extends: ['@metamask/eslint-config-mocha'],
|
||||
rules: {
|
||||
@ -271,10 +272,12 @@ module.exports = {
|
||||
'app/scripts/migrations/*.test.js',
|
||||
'app/scripts/platforms/*.test.js',
|
||||
'development/**/*.test.js',
|
||||
'development/**/*.test.ts',
|
||||
'shared/**/*.test.js',
|
||||
'shared/**/*.test.ts',
|
||||
'test/helpers/*.js',
|
||||
'test/jest/*.js',
|
||||
'test/e2e/helpers.test.js',
|
||||
'ui/**/*.test.js',
|
||||
'ui/__mocks__/*.js',
|
||||
'shared/lib/error-utils.test.js',
|
||||
|
@ -9,6 +9,8 @@ module.exports = {
|
||||
'./app/scripts/controllers/permissions/**/*.test.js',
|
||||
'./app/scripts/controllers/mmi-controller.test.js',
|
||||
'./app/scripts/constants/error-utils.test.js',
|
||||
'./development/fitness-functions/**/*.test.ts',
|
||||
'./test/e2e/helpers.test.js',
|
||||
],
|
||||
recursive: true,
|
||||
require: ['test/env.js', 'test/setup.js'],
|
||||
|
@ -40,7 +40,10 @@ import {
|
||||
hexWEIToDecGWEI,
|
||||
} from '../../../../shared/modules/conversion.utils';
|
||||
import { isSwapsDefaultTokenAddress } from '../../../../shared/modules/swaps.utils';
|
||||
import { MetaMetricsEventCategory } from '../../../../shared/constants/metametrics';
|
||||
import {
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../../shared/constants/metametrics';
|
||||
import {
|
||||
CHAIN_ID_TO_GAS_LIMIT_BUFFER_MAP,
|
||||
NETWORK_TYPES,
|
||||
@ -2128,7 +2131,7 @@ export default class TransactionController extends EventEmitter {
|
||||
);
|
||||
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Swap Completed',
|
||||
event: MetaMetricsEventName.SwapCompleted,
|
||||
category: MetaMetricsEventCategory.Swaps,
|
||||
sensitiveProperties: {
|
||||
...txMeta.swapMetaData,
|
||||
@ -2139,6 +2142,12 @@ export default class TransactionController extends EventEmitter {
|
||||
trade_gas_cost_in_eth: transactionsCost.tradeGasCostInEth,
|
||||
trade_and_approval_gas_cost_in_eth:
|
||||
transactionsCost.tradeAndApprovalGasCostInEth,
|
||||
// Firefox and Chrome have different implementations of the APIs
|
||||
// that we rely on for communication accross the app. On Chrome big
|
||||
// numbers are converted into number strings, on firefox they remain
|
||||
// Big Number objects. As such, we convert them here for both
|
||||
// browsers.
|
||||
token_to_amount: txMeta.swapMetaData.token_to_amount.toString(10),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ module.exports = {
|
||||
'<rootDir>/shared/**/*.(js|ts|tsx)',
|
||||
'<rootDir>/ui/**/*.(js|ts|tsx)',
|
||||
'<rootDir>/development/fitness-functions/**/*.test.(js|ts|tsx)',
|
||||
'<rootDir>/test/e2e/helpers.test.js',
|
||||
],
|
||||
coverageDirectory: './coverage',
|
||||
coveragePathIgnorePatterns: ['.stories.*', '.snap'],
|
||||
@ -50,6 +51,7 @@ module.exports = {
|
||||
'<rootDir>/shared/**/*.test.(js|ts)',
|
||||
'<rootDir>/ui/**/*.test.(js|ts|tsx)',
|
||||
'<rootDir>/development/fitness-functions/**/*.test.(js|ts|tsx)',
|
||||
'<rootDir>/test/e2e/helpers.test.js',
|
||||
],
|
||||
testTimeout: 5500,
|
||||
// We have to specify the environment we are running in, which is jsdom. The
|
||||
|
@ -612,6 +612,18 @@ export enum MetaMetricsEventName {
|
||||
ActivityScreenOpened = 'Activity Screen Opened',
|
||||
WhatsNewViewed = `What's New Viewed`,
|
||||
WhatsNewClicked = `What's New Link Clicked`,
|
||||
PrepareSwapPageLoaded = 'Prepare Swap Page Loaded',
|
||||
QuotesRequested = 'Quotes Requested',
|
||||
QuotesReceived = 'Quotes Received',
|
||||
BestQuoteReviewed = 'Best Quote Reviewed',
|
||||
AllAvailableQuotesOpened = 'All Available Quotes Opened',
|
||||
SwapStarted = 'Swap Started',
|
||||
TransactionAdded = 'Transaction Added',
|
||||
TransactionSubmitted = 'Transaction Submitted',
|
||||
TransactionApproved = 'Transaction Approved',
|
||||
SwapCompleted = 'Swap Completed',
|
||||
TransactionFinalized = 'Transaction Finalized',
|
||||
ExitedSwaps = 'Exited Swaps',
|
||||
}
|
||||
|
||||
export enum MetaMetricsEventAccountType {
|
||||
|
@ -589,9 +589,10 @@ function mockPhishingDetection(mockServer) {
|
||||
const PRIVATE_KEY =
|
||||
'0x7C9529A67102755B7E6102D6D950AC5D5863C98713805CEC576B945B15B71EAC';
|
||||
|
||||
const generateETHBalance = (eth) => convertToHexValue(eth * 10 ** 18);
|
||||
const convertETHToHexGwei = (eth) => convertToHexValue(eth * 10 ** 18);
|
||||
|
||||
const defaultGanacheOptions = {
|
||||
accounts: [{ secretKey: PRIVATE_KEY, balance: generateETHBalance(25) }],
|
||||
accounts: [{ secretKey: PRIVATE_KEY, balance: convertETHToHexGwei(25) }],
|
||||
};
|
||||
|
||||
const SERVICE_WORKER_URL = 'chrome://inspect/#service-workers';
|
||||
@ -654,7 +655,7 @@ const DEFAULT_GANACHE_OPTIONS = {
|
||||
accounts: [
|
||||
{
|
||||
secretKey: DEFAULT_PRIVATE_KEY,
|
||||
balance: generateETHBalance(25),
|
||||
balance: convertETHToHexGwei(25),
|
||||
},
|
||||
],
|
||||
};
|
||||
@ -687,6 +688,10 @@ const logInWithBalanceValidation = async (driver, ganacheServer) => {
|
||||
await assertAccountBalanceForDOM(driver, ganacheServer);
|
||||
};
|
||||
|
||||
async function sleepSeconds(sec) {
|
||||
return new Promise((resolve) => setTimeout(resolve, sec * 1000));
|
||||
}
|
||||
|
||||
function roundToXDecimalPlaces(number, decimalPlaces) {
|
||||
return Math.round(number * 10 ** decimalPlaces) / 10 ** decimalPlaces;
|
||||
}
|
||||
@ -699,8 +704,15 @@ function generateRandNumBetween(x, y) {
|
||||
return randomNumber;
|
||||
}
|
||||
|
||||
async function sleepSeconds(sec) {
|
||||
return new Promise((resolve) => setTimeout(resolve, sec * 1000));
|
||||
function genRandInitBal(minETHBal = 10, maxETHBal = 100, decimalPlaces = 4) {
|
||||
const initialBalance = roundToXDecimalPlaces(
|
||||
generateRandNumBetween(minETHBal, maxETHBal),
|
||||
decimalPlaces,
|
||||
);
|
||||
|
||||
const initialBalanceInHex = convertETHToHexGwei(initialBalance);
|
||||
|
||||
return { initialBalance, initialBalanceInHex };
|
||||
}
|
||||
|
||||
async function terminateServiceWorker(driver) {
|
||||
@ -762,12 +774,44 @@ async function getEventPayloads(driver, mockedEndpoints, hasRequest = true) {
|
||||
}
|
||||
|
||||
return isPending === !hasRequest;
|
||||
}, 10000);
|
||||
}, driver.timeout);
|
||||
const mockedRequests = [];
|
||||
for (const mockedEndpoint of mockedEndpoints) {
|
||||
mockedRequests.push(...(await mockedEndpoint.getSeenRequests()));
|
||||
}
|
||||
return mockedRequests.map((req) => req.body.json.batch).flat();
|
||||
|
||||
return mockedRequests.map((req) => req.body.json?.batch).flat();
|
||||
}
|
||||
|
||||
// Asserts that each request passes all assertions in one group of assertions, and the order does not matter.
|
||||
function assertInAnyOrder(requests, assertions) {
|
||||
// Clone the array to avoid mutating the original
|
||||
const assertionsClone = [...assertions];
|
||||
|
||||
return (
|
||||
requests.every((request) => {
|
||||
for (let a = 0; a < assertionsClone.length; a++) {
|
||||
const assertionArray = assertionsClone[a];
|
||||
|
||||
const passed = assertionArray.reduce(
|
||||
(acc, currAssertionFn) => currAssertionFn(request) && acc,
|
||||
true,
|
||||
);
|
||||
|
||||
if (passed) {
|
||||
// Remove the used assertion array
|
||||
assertionsClone.splice(a, 1);
|
||||
// Exit the loop early since we found a matching assertion
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// No matching assertion found for this request
|
||||
return false;
|
||||
}) &&
|
||||
// Ensure all assertions were used
|
||||
assertionsClone.length === 0
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@ -810,7 +854,7 @@ module.exports = {
|
||||
WALLET_PASSWORD,
|
||||
WINDOW_TITLES,
|
||||
DEFAULT_GANACHE_OPTIONS,
|
||||
generateETHBalance,
|
||||
convertETHToHexGwei,
|
||||
roundToXDecimalPlaces,
|
||||
generateRandNumBetween,
|
||||
sleepSeconds,
|
||||
@ -823,4 +867,6 @@ module.exports = {
|
||||
onboardingRevealAndConfirmSRP,
|
||||
onboardingCompleteWalletCreation,
|
||||
onboardingPinExtension,
|
||||
assertInAnyOrder,
|
||||
genRandInitBal,
|
||||
};
|
||||
|
53
test/e2e/helpers.test.js
Normal file
53
test/e2e/helpers.test.js
Normal file
@ -0,0 +1,53 @@
|
||||
import { assertInAnyOrder } from './helpers';
|
||||
|
||||
describe('assertInAnyOrder()', () => {
|
||||
it('returns true when all requests pass unique assertions', () => {
|
||||
const requests = ['req1', 'req2', 'req3'];
|
||||
const assertions = [
|
||||
[(req) => req === 'req1'],
|
||||
[(req) => req === 'req2'],
|
||||
[(req) => req === 'req3'],
|
||||
];
|
||||
expect(assertInAnyOrder(requests, assertions)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when all requests pass unique assertions independently of the order', () => {
|
||||
const requests = ['req1', 'req2', 'req3'];
|
||||
const assertions = [
|
||||
[(req) => req === 'req3'],
|
||||
[(req) => req === 'req2'],
|
||||
[(req) => req === 'req1'],
|
||||
];
|
||||
expect(assertInAnyOrder(requests, assertions)).toBe(true);
|
||||
});
|
||||
|
||||
it('returns false when a request cannot pass any assertions', () => {
|
||||
const requests = ['req1', 'req2', 'unknown'];
|
||||
const assertions = [
|
||||
[(req) => req === 'req1'],
|
||||
[(req) => req === 'req2'],
|
||||
[(req) => req === 'req3'],
|
||||
];
|
||||
expect(assertInAnyOrder(requests, assertions)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when there are unused assertions', () => {
|
||||
const requests = ['req1', 'req2'];
|
||||
const assertions = [
|
||||
[(req) => req === 'req1'],
|
||||
[(req) => req === 'req2'],
|
||||
[(req) => req === 'req3'],
|
||||
];
|
||||
expect(assertInAnyOrder(requests, assertions)).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when there are unused requests', () => {
|
||||
const requests = ['req1', 'req2', 'req3', 'req4'];
|
||||
const assertions = [
|
||||
[(req) => req === 'req1'],
|
||||
[(req) => req === 'req2'],
|
||||
[(req) => req === 'req3'],
|
||||
];
|
||||
expect(assertInAnyOrder(requests, assertions)).toBe(false);
|
||||
});
|
||||
});
|
524
test/e2e/metrics/mock-data.js
Normal file
524
test/e2e/metrics/mock-data.js
Normal file
File diff suppressed because one or more lines are too long
572
test/e2e/metrics/swaps.spec.js
Normal file
572
test/e2e/metrics/swaps.spec.js
Normal file
@ -0,0 +1,572 @@
|
||||
const { strict: assert } = require('assert');
|
||||
const { toHex } = require('@metamask/controller-utils');
|
||||
const FixtureBuilder = require('../fixture-builder');
|
||||
const {
|
||||
withFixtures,
|
||||
generateGanacheOptions,
|
||||
DEFAULT_GANACHE_OPTIONS,
|
||||
unlockWallet,
|
||||
getEventPayloads,
|
||||
assertInAnyOrder,
|
||||
genRandInitBal,
|
||||
} = require('../helpers');
|
||||
const {
|
||||
buildQuote,
|
||||
reviewQuote,
|
||||
waitForTransactionToComplete,
|
||||
checkActivityTransaction,
|
||||
changeExchangeRate,
|
||||
} = require('../swaps/shared');
|
||||
const {
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} = require('../../../shared/constants/metametrics');
|
||||
const {
|
||||
TOKENS_API_MOCK_RESULT,
|
||||
TOP_ASSETS_API_MOCK_RESULT,
|
||||
AGGREGATOR_METADATA_API_MOCK_RESULT,
|
||||
GAS_PRICE_API_MOCK_RESULT,
|
||||
FEATURE_FLAGS_API_MOCK_RESULT,
|
||||
NETWORKS_API_MOCK_RESULT,
|
||||
TRADES_API_MOCK_RESULT,
|
||||
NETWORKS_2_API_MOCK_RESULT,
|
||||
} = require('./mock-data');
|
||||
|
||||
const numberOfSegmentRequests = 19;
|
||||
|
||||
async function mockSegmentAndMetaswapRequests(mockServer) {
|
||||
return [
|
||||
await mockServer
|
||||
.forPost('https://api.segment.io/v1/batch')
|
||||
.withJsonBodyIncluding({
|
||||
batch: [{ properties: { category: MetaMetricsEventCategory.Swaps } }],
|
||||
})
|
||||
.times()
|
||||
.thenCallback(() => ({ statusCode: 200 })),
|
||||
await mockServer
|
||||
.forGet('https://swap.metaswap.codefi.network/networks/1/tokens')
|
||||
.thenCallback(() => ({ statusCode: 200, json: TOKENS_API_MOCK_RESULT })),
|
||||
await mockServer
|
||||
.forGet('https://swap.metaswap.codefi.network/networks/1/topAssets')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: TOP_ASSETS_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet(
|
||||
'https://swap.metaswap.codefi.network/networks/1/aggregatorMetadata',
|
||||
)
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: AGGREGATOR_METADATA_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet('https://gas-api.metaswap.codefi.network/networks/1/gasPrices')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: GAS_PRICE_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet('https://swap.metaswap.codefi.network/featureFlags')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: FEATURE_FLAGS_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet('https://tx-insights.metaswap.codefi.network/networks')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: NETWORKS_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet('https://swap.metaswap.codefi.network/networks/1/trades')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: TRADES_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet('https://swap.metaswap.codefi.network/networks/1')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: NETWORKS_2_API_MOCK_RESULT,
|
||||
})),
|
||||
await mockServer
|
||||
.forGet('https://token-api.metaswap.codefi.network/token/1337')
|
||||
.thenCallback(() => ({
|
||||
statusCode: 200,
|
||||
json: {},
|
||||
})),
|
||||
];
|
||||
}
|
||||
|
||||
describe('Swap Eth for another Token', function () {
|
||||
it('Completes a Swap between ETH and DAI after changing initial rate', async function () {
|
||||
const { initialBalanceInHex } = genRandInitBal();
|
||||
|
||||
await withFixtures(
|
||||
{
|
||||
fixtures: new FixtureBuilder()
|
||||
.withMetaMetricsController({
|
||||
metaMetricsId: 'fake-metrics-id',
|
||||
participateInMetaMetrics: true,
|
||||
})
|
||||
.build(),
|
||||
ganacheOptions: generateGanacheOptions({
|
||||
accounts: [
|
||||
{
|
||||
secretKey: DEFAULT_GANACHE_OPTIONS.accounts[0].secretKey,
|
||||
balance: initialBalanceInHex,
|
||||
},
|
||||
],
|
||||
}),
|
||||
title: this.test.title,
|
||||
testSpecificMock: mockSegmentAndMetaswapRequests,
|
||||
},
|
||||
async ({ driver, mockedEndpoint: mockedEndpoints }) => {
|
||||
await driver.navigate();
|
||||
|
||||
await unlockWallet(driver);
|
||||
|
||||
await getQuoteAndSwapTokens(driver);
|
||||
|
||||
const metricsReqs = await assertReqsNumAndFilterMetrics(
|
||||
driver,
|
||||
mockedEndpoints,
|
||||
);
|
||||
|
||||
await assertNavSwapButtonClickedEvent(metricsReqs);
|
||||
|
||||
await assertPrepareSwapPageLoadedEvents(metricsReqs);
|
||||
|
||||
await assertQuotesRequestedEvents(metricsReqs);
|
||||
|
||||
await assertQuotesReceivedAndBestQuoteReviewedEvents(metricsReqs);
|
||||
|
||||
await assertAllAvailableQuotesOpenedEvents(metricsReqs);
|
||||
|
||||
await assertSwapStartedEvents(metricsReqs);
|
||||
|
||||
await assertSwapCompletedEvents(metricsReqs);
|
||||
|
||||
await assertExitedSwapsEvents(metricsReqs);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
async function getQuoteAndSwapTokens(driver) {
|
||||
await buildQuote(driver, {
|
||||
amount: 2,
|
||||
swapTo: 'DAI',
|
||||
});
|
||||
await reviewQuote(driver, {
|
||||
amount: 2,
|
||||
swapFrom: 'TESTETH',
|
||||
swapTo: 'DAI',
|
||||
});
|
||||
await changeExchangeRate(driver);
|
||||
await reviewQuote(driver, {
|
||||
amount: 2,
|
||||
swapFrom: 'TESTETH',
|
||||
swapTo: 'DAI',
|
||||
skipCounter: true,
|
||||
});
|
||||
await driver.clickElement({ text: 'Swap', tag: 'button' });
|
||||
await waitForTransactionToComplete(driver, { tokenName: 'DAI' });
|
||||
await checkActivityTransaction(driver, {
|
||||
index: 0,
|
||||
amount: '2',
|
||||
swapFrom: 'TESTETH',
|
||||
swapTo: 'DAI',
|
||||
});
|
||||
}
|
||||
|
||||
async function assertReqsNumAndFilterMetrics(driver, mockedEndpoints) {
|
||||
const events = await getEventPayloads(driver, mockedEndpoints);
|
||||
|
||||
const numberOfMetaswapRequests = 9;
|
||||
assert.equal(
|
||||
events.length,
|
||||
numberOfSegmentRequests + numberOfMetaswapRequests,
|
||||
);
|
||||
|
||||
const reqs = events.slice(0, numberOfSegmentRequests);
|
||||
|
||||
return reqs;
|
||||
}
|
||||
|
||||
async function assertNavSwapButtonClickedEvent(reqs) {
|
||||
assert.equal(reqs[0].event, MetaMetricsEventName.NavSwapButtonClicked);
|
||||
assert.deepStrictEqual(reqs[0].properties, {
|
||||
category: MetaMetricsEventCategory.Swaps,
|
||||
chain_id: toHex(1337),
|
||||
environment_type: 'fullscreen',
|
||||
locale: 'en',
|
||||
location: 'Main View',
|
||||
text: 'Swap',
|
||||
token_symbol: 'ETH',
|
||||
});
|
||||
}
|
||||
|
||||
async function assertPrepareSwapPageLoadedEvents(reqs) {
|
||||
const assertionsReq1 = [
|
||||
(req) => req.event === MetaMetricsEventName.PrepareSwapPageLoaded,
|
||||
(req) => Object.keys(req.properties).length === 7,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
];
|
||||
|
||||
const assertionsReq2 = [
|
||||
(req) => req.event === MetaMetricsEventName.PrepareSwapPageLoaded,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[1], reqs[2]], [assertionsReq1, assertionsReq2]),
|
||||
'assertPrepareSwapPageLoadedEvents(): reqs[1] and reqs[2] did not match what was expected',
|
||||
);
|
||||
}
|
||||
|
||||
async function assertQuotesRequestedEvents(reqs) {
|
||||
const assertionsReq3 = [
|
||||
(req) => req.event === MetaMetricsEventName.QuotesRequested,
|
||||
(req) => Object.keys(req.properties).length === 14,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.anonymizedData === true,
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.request_type === 'Order',
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.token_from === 'TESTETH',
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
];
|
||||
|
||||
const assertionsReq4 = [
|
||||
(req) => req.event === MetaMetricsEventName.QuotesRequested,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[3], reqs[4]], [assertionsReq3, assertionsReq4]),
|
||||
'assertQuotesRequestedEvents(): reqs[3] and reqs[4] did not match what was expected',
|
||||
);
|
||||
}
|
||||
|
||||
async function assertQuotesReceivedAndBestQuoteReviewedEvents(reqs) {
|
||||
const assertionsReq5 = [
|
||||
(req) => req.event === MetaMetricsEventName.QuotesReceived,
|
||||
(req) => Object.keys(req.properties).length === 18,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.anonymizedData === true,
|
||||
(req) => typeof req.properties?.available_quotes === 'number',
|
||||
(req) => typeof req.properties?.best_quote_source === 'string',
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.request_type === 'Order',
|
||||
(req) => typeof req.properties?.response_time === 'number',
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.token_from === 'TESTETH',
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
(req) => typeof req.properties?.token_to_amount === 'string',
|
||||
];
|
||||
|
||||
const assertionsReq6 = [
|
||||
(req) => req.event === MetaMetricsEventName.QuotesReceived,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
const assertionsReq7 = [
|
||||
(req) => req.event === MetaMetricsEventName.BestQuoteReviewed,
|
||||
(req) => Object.keys(req.properties).length === 17,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => typeof req.properties?.available_quotes === 'number',
|
||||
(req) => typeof req.properties?.best_quote_source === 'string',
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.request_type === false,
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.token_from === 'TESTETH',
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
(req) => typeof req.properties?.token_to_amount === 'string',
|
||||
];
|
||||
|
||||
const assertionsReq8 = [
|
||||
(req) => req.event === MetaMetricsEventName.BestQuoteReviewed,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
// When running this test on Chrome in particular, reqs[5], reqs[6], reqs[7]
|
||||
// and reqs[8] sometimes switch order so we bundled them together for the
|
||||
// assertion
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder(
|
||||
[reqs[5], reqs[6], reqs[7], reqs[8]],
|
||||
[assertionsReq5, assertionsReq6, assertionsReq7, assertionsReq8],
|
||||
),
|
||||
'assertQuotesReceivedAndBestQuoteReviewedEvents(): reqs[5], reqs[6], reqs[7] and reqs[8] did not match what was expected',
|
||||
);
|
||||
}
|
||||
|
||||
async function assertAllAvailableQuotesOpenedEvents(reqs) {
|
||||
const assertionsReq9 = [
|
||||
(req) => req.event === MetaMetricsEventName.AllAvailableQuotesOpened,
|
||||
(req) => Object.keys(req.properties).length === 18,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => typeof req.properties?.available_quotes === 'number',
|
||||
(req) => typeof req.properties?.best_quote_source === 'string',
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.request_type === false,
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.token_from === 'TESTETH',
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
(req) => req.properties?.other_quote_selected === false,
|
||||
(req) => req.properties?.other_quote_selected_source === null,
|
||||
(req) => typeof req.properties?.token_to_amount === 'string',
|
||||
];
|
||||
|
||||
const assertionsReq10 = [
|
||||
(req) => req.event === MetaMetricsEventName.AllAvailableQuotesOpened,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[9], reqs[10]], [assertionsReq9, assertionsReq10]),
|
||||
'assertAllAvailableQuotesOpenedEvents(): reqs[9] and reqs[10] did not match what was expected',
|
||||
);
|
||||
}
|
||||
|
||||
async function assertSwapStartedEvents(reqs) {
|
||||
const assertionsReq11 = [
|
||||
(req) => req.event === MetaMetricsEventName.SwapStarted,
|
||||
(req) => Object.keys(req.properties).length === 24,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.token_from === 'TESTETH',
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => typeof req.properties?.token_to_amount === 'string',
|
||||
(req) => typeof req.properties?.best_quote_source === 'string',
|
||||
(req) => typeof req.properties?.other_quote_selected === 'boolean',
|
||||
(req) => typeof req.properties?.gas_fees === 'string',
|
||||
(req) => typeof req.properties?.estimated_gas === 'string',
|
||||
(req) => typeof req.properties?.suggested_gas_price === 'string',
|
||||
(req) => typeof req.properties?.reg_tx_fee_in_usd === 'number',
|
||||
(req) => typeof req.properties?.reg_tx_fee_in_eth === 'number',
|
||||
(req) => typeof req.properties?.reg_tx_max_fee_in_usd === 'number',
|
||||
(req) => typeof req.properties?.reg_tx_max_fee_in_eth === 'number',
|
||||
(req) => typeof req.properties?.other_quote_selected_source === 'string',
|
||||
];
|
||||
|
||||
const assertionsReq12 = [
|
||||
(req) => req.event === MetaMetricsEventName.SwapStarted,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[11], reqs[12]], [assertionsReq11, assertionsReq12]),
|
||||
'assertSwapStartedEvents(): reqs[11] and reqs[12] did not match what was expected',
|
||||
);
|
||||
}
|
||||
|
||||
async function assertSwapCompletedEvents(reqs) {
|
||||
const assertionsReq13 = [
|
||||
(req) => req.event === MetaMetricsEventName.SwapCompleted,
|
||||
(req) => Object.keys(req.properties).length === 30,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'background',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.token_from === 'TESTETH',
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.token_to === 'DAI',
|
||||
(req) => typeof req.properties?.token_to_amount === 'string',
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.best_quote_source === 'airswapV4',
|
||||
(req) => typeof req.properties?.other_quote_selected === 'boolean',
|
||||
(req) => typeof req.properties?.other_quote_selected_source === 'string',
|
||||
(req) => typeof req.properties?.gas_fees === 'string',
|
||||
(req) => typeof req.properties?.estimated_gas === 'string',
|
||||
(req) => req.properties?.suggested_gas_price === '30',
|
||||
(req) => req.properties?.used_gas_price === '30',
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
(req) => typeof req.properties?.reg_tx_fee_in_usd === 'number',
|
||||
(req) => typeof req.properties?.reg_tx_fee_in_eth === 'number',
|
||||
(req) => typeof req.properties?.reg_tx_max_fee_in_usd === 'number',
|
||||
(req) => typeof req.properties?.reg_tx_max_fee_in_eth === 'number',
|
||||
(req) => req.properties?.token_to_amount_received === '',
|
||||
(req) => req.properties?.quote_vs_executionRatio === null,
|
||||
(req) => req.properties?.estimated_vs_used_gasRatio === '100%',
|
||||
(req) => req.properties?.approval_gas_cost_in_eth === 0,
|
||||
(req) => typeof req.properties?.trade_gas_cost_in_eth === 'number',
|
||||
(req) =>
|
||||
typeof req.properties?.trade_and_approval_gas_cost_in_eth === 'number',
|
||||
];
|
||||
|
||||
const assertionsReq14 = [
|
||||
(req) => req.event === MetaMetricsEventName.SwapCompleted,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'background',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[13], reqs[14]], [assertionsReq13, assertionsReq14]),
|
||||
'assertSwapCompletedEvents(): reqs[13] and reqs[14] did not match what was expected',
|
||||
);
|
||||
}
|
||||
|
||||
async function assertExitedSwapsEvents(reqs) {
|
||||
const assertionsReq15 = [
|
||||
(req) => req.event === MetaMetricsEventName.ExitedSwaps,
|
||||
(req) => Object.keys(req.properties).length === 12,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.token_from_amount === '2',
|
||||
(req) => req.properties?.request_type === false,
|
||||
(req) => req.properties?.slippage === 2,
|
||||
(req) => req.properties?.custom_slippage === false,
|
||||
(req) => req.properties?.current_screen === 'awaiting-swap',
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
];
|
||||
|
||||
const assertionsReq16 = [
|
||||
(req) => req.event === MetaMetricsEventName.ExitedSwaps,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[15], reqs[16]], [assertionsReq15, assertionsReq16]),
|
||||
'assertExitedSwapsEvents(): reqs[15] and reqs[16] did not match what was expected',
|
||||
);
|
||||
|
||||
const assertionsReq17 = [
|
||||
(req) => req.event === MetaMetricsEventName.ExitedSwaps,
|
||||
(req) => Object.keys(req.properties).length === 9,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
|
||||
(req) => req.properties?.custom_slippage === true,
|
||||
(req) => req.properties?.current_screen === 'awaiting-swap',
|
||||
(req) => req.properties?.is_hardware_wallet === false,
|
||||
(req) => req.properties?.stx_enabled === false,
|
||||
(req) => req.properties?.current_stx_enabled === false,
|
||||
];
|
||||
|
||||
const assertionsReq18 = [
|
||||
(req) => req.event === MetaMetricsEventName.ExitedSwaps,
|
||||
(req) => Object.keys(req.properties).length === 4,
|
||||
|
||||
(req) => req.properties?.category === MetaMetricsEventCategory.Swaps,
|
||||
(req) => req.properties?.chain_id === toHex(1337),
|
||||
(req) => req.properties?.environment_type === 'fullscreen',
|
||||
(req) => req.properties?.locale === 'en',
|
||||
];
|
||||
|
||||
assert.ok(
|
||||
assertInAnyOrder([reqs[17], reqs[18]], [assertionsReq17, assertionsReq18]),
|
||||
'assertExitedSwapsEvents(): reqs[17] and reqs[18] did not match what was expected',
|
||||
);
|
||||
}
|
@ -6,22 +6,19 @@ const {
|
||||
WALLET_PASSWORD,
|
||||
WINDOW_TITLES,
|
||||
DEFAULT_GANACHE_OPTIONS,
|
||||
generateETHBalance,
|
||||
roundToXDecimalPlaces,
|
||||
generateRandNumBetween,
|
||||
sleepSeconds,
|
||||
terminateServiceWorker,
|
||||
unlockWallet,
|
||||
largeDelayMs,
|
||||
genRandInitBal,
|
||||
roundToXDecimalPlaces,
|
||||
} = require('../helpers');
|
||||
const FixtureBuilder = require('../fixture-builder');
|
||||
|
||||
describe('MV3 - Restart service worker multiple times', function () {
|
||||
it('Simple simple send flow within full screen view should still be usable', async function () {
|
||||
const initialBalance = roundToXDecimalPlaces(
|
||||
generateRandNumBetween(10, 100),
|
||||
4,
|
||||
);
|
||||
const { initialBalance, initialBalanceInHex } = genRandInitBal();
|
||||
|
||||
await withFixtures(
|
||||
{
|
||||
@ -30,7 +27,7 @@ describe('MV3 - Restart service worker multiple times', function () {
|
||||
accounts: [
|
||||
{
|
||||
secretKey: DEFAULT_GANACHE_OPTIONS.accounts[0].secretKey,
|
||||
balance: generateETHBalance(initialBalance),
|
||||
balance: initialBalanceInHex,
|
||||
},
|
||||
],
|
||||
}),
|
||||
|
@ -63,7 +63,10 @@ import {
|
||||
checkNetworkAndAccountSupports1559,
|
||||
} from '../../selectors';
|
||||
|
||||
import { MetaMetricsEventCategory } from '../../../shared/constants/metametrics';
|
||||
import {
|
||||
MetaMetricsEventCategory,
|
||||
MetaMetricsEventName,
|
||||
} from '../../../shared/constants/metametrics';
|
||||
import {
|
||||
ERROR_FETCHING_QUOTES,
|
||||
QUOTES_NOT_AVAILABLE_ERROR,
|
||||
@ -787,6 +790,18 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
} else {
|
||||
const newSelectedQuote = fetchedQuotes[selectedAggId];
|
||||
|
||||
const tokenToAmountBN = calcTokenAmount(
|
||||
newSelectedQuote.destinationAmount,
|
||||
newSelectedQuote.decimals || 18,
|
||||
);
|
||||
|
||||
// Firefox and Chrome have different implementations of the APIs
|
||||
// that we rely on for communication accross the app. On Chrome big
|
||||
// numbers are converted into number strings, on firefox they remain
|
||||
// Big Number objects. As such, we convert them here for both
|
||||
// browsers.
|
||||
const tokenToAmountToString = tokenToAmountBN.toString(10);
|
||||
|
||||
trackEvent({
|
||||
event: 'Quotes Received',
|
||||
category: MetaMetricsEventCategory.Swaps,
|
||||
@ -794,10 +809,7 @@ export const fetchQuotesAndSetQuoteState = (
|
||||
token_from: fromTokenSymbol,
|
||||
token_from_amount: String(inputValue),
|
||||
token_to: toTokenSymbol,
|
||||
token_to_amount: calcTokenAmount(
|
||||
newSelectedQuote.destinationAmount,
|
||||
newSelectedQuote.decimals || 18,
|
||||
),
|
||||
token_to_amount: tokenToAmountToString,
|
||||
request_type: balanceError ? 'Quote' : 'Order',
|
||||
slippage: maxSlippage,
|
||||
custom_slippage: maxSlippage !== Slippage.default,
|
||||
@ -1177,7 +1189,7 @@ export const signAndSendTransactions = (
|
||||
}
|
||||
|
||||
trackEvent({
|
||||
event: 'Swap Started',
|
||||
event: MetaMetricsEventName.SwapStarted,
|
||||
category: MetaMetricsEventCategory.Swaps,
|
||||
sensitiveProperties: swapMetaData,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user