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

Enable and stabilise e2e tests for Swaps, add debouncing (#16326)

This commit is contained in:
Daniel 2022-11-03 14:54:49 +01:00 committed by GitHub
parent 129ba1290e
commit d634ff89df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 112 additions and 91 deletions

View File

@ -189,19 +189,10 @@ async function setupMocking(server, testSpecificMock) {
}; };
}); });
// It disables loading of token icons, e.g. this URL: https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x0000000000000000000000000000000000000000.png
await server await server
.forGet( .forGet(
'https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x0d8775f648430679a709e98d2b0cb6250d2887ef.png', /^https:\/\/static\.metaswap\.codefi\.network\/api\/v1\/tokenIcons\/1337\/.*\.png/u,
)
.thenCallback(() => {
return {
statusCode: 200,
};
});
await server
.forGet(
'https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x2efa2cb29c2341d8e5ba7d3262c9e9d6f1bf3711.png',
) )
.thenCallback(() => { .thenCallback(() => {
return { return {

View File

@ -51,8 +51,7 @@ async function main() {
if (!snaps) { if (!snaps) {
testPaths = [ testPaths = [
...testPaths, ...testPaths,
// TODO: Enable the next line once the Swaps E2E tests are stable. ...(await getTestPathsForTestDir(path.join(__dirname, 'swaps'))),
// ...(await getTestPathsForTestDir(path.join(__dirname, 'swaps'))),
path.join(__dirname, 'metamask-ui.spec.js'), path.join(__dirname, 'metamask-ui.spec.js'),
]; ];
} }

View File

@ -1,3 +1,5 @@
const FixtureBuilder = require('../fixture-builder');
const ganacheOptions = { const ganacheOptions = {
accounts: [ accounts: [
{ {
@ -8,6 +10,11 @@ const ganacheOptions = {
], ],
}; };
const withFixturesOptions = {
fixtures: new FixtureBuilder().build(),
ganacheOptions,
};
const loadSwaps = async (driver) => { const loadSwaps = async (driver) => {
await driver.navigate(); await driver.navigate();
await driver.fill('#password', 'correct horse battery staple'); await driver.fill('#password', 'correct horse battery staple');
@ -28,19 +35,37 @@ const buildQuote = async (driver, options) => {
await driver.wait(async () => { await driver.wait(async () => {
const tokens = await driver.findElements('.searchable-item-list__item'); const tokens = await driver.findElements('.searchable-item-list__item');
return tokens.length > 1; return tokens.length > 1;
}, 10000); });
await driver.clickElement('.searchable-item-list__labels'); await driver.clickElement('.searchable-item-list__labels');
await driver.clickElement('.dropdown-input-pair__to'); await driver.clickElement('.dropdown-input-pair__to');
await driver.clickElement('[placeholder="Search name or paste address"]'); await driver.clickElement('input[data-testid="search-list-items"]');
await driver.fill( await driver.fill(
'[placeholder="Search name or paste address"]', 'input[data-testid="search-list-items"]',
options.swapTo, options.swapTo || options.swapToContractAddress,
); );
if (options.swapTo) {
await driver.wait(async () => {
const tokenNames = await driver.findElements(
'.searchable-item-list__primary-label',
);
if (tokenNames.length === 0) {
return false;
}
const tokenName = await tokenNames[0].getText();
return tokenName === options.swapTo;
});
}
if (options.swapToContractAddress) {
await driver.waitForSelector({
css: '.searchable-item-list__item button.btn-primary',
text: 'Import',
});
}
await driver.clickElement('.searchable-item-list__primary-label'); await driver.clickElement('.searchable-item-list__primary-label');
}; };
module.exports = { module.exports = {
ganacheOptions, withFixturesOptions,
loadSwaps, loadSwaps,
buildQuote, buildQuote,
}; };

View File

@ -1,16 +1,13 @@
const { strict: assert } = require('assert'); const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers'); const { withFixtures } = require('../helpers');
const FixtureBuilder = require('../fixture-builder'); const { withFixturesOptions, loadSwaps, buildQuote } = require('./shared');
const { ganacheOptions, loadSwaps, buildQuote } = require('./shared');
describe('Swap Eth for another Token', function () { describe('Swap Eth for another Token', function () {
it('Completes a Swap between Eth and Matic', async function () { it('Completes a Swap between Eth and Matic', async function () {
await withFixtures( await withFixtures(
{ {
fixtures: new FixtureBuilder().build(), ...withFixturesOptions,
ganacheOptions,
failOnConsoleError: false,
title: this.test.title, title: this.test.title,
}, },
async ({ driver }) => { async ({ driver }) => {

View File

@ -1,16 +1,14 @@
const { strict: assert } = require('assert'); const { strict: assert } = require('assert');
const { withFixtures } = require('../helpers'); const { withFixtures } = require('../helpers');
const FixtureBuilder = require('../fixture-builder');
const { ganacheOptions, loadSwaps, buildQuote } = require('./shared'); const { withFixturesOptions, loadSwaps, buildQuote } = require('./shared');
describe('Swaps - notifications', function () { describe('Swaps - notifications', function () {
it('tests notifications for verified token on 1 source and price difference', async function () { it('tests notifications for verified token on 1 source and price difference', async function () {
await withFixtures( await withFixtures(
{ {
fixtures: new FixtureBuilder().build(), ...withFixturesOptions,
ganacheOptions,
failOnConsoleError: false,
title: this.test.title, title: this.test.title,
}, },
async ({ driver }) => { async ({ driver }) => {
@ -46,19 +44,52 @@ describe('Swaps - notifications', function () {
); );
}); });
it('tests a notification for not enough balance', async function () {
await withFixtures(
{
...withFixturesOptions,
title: this.test.title,
},
async ({ driver }) => {
await loadSwaps(driver);
await buildQuote(driver, {
amount: 50,
swapTo: 'USDC',
});
const reviewSwapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await reviewSwapButton.getText(), 'Review swap');
assert.equal(await reviewSwapButton.isEnabled(), true);
await reviewSwapButton.click();
await driver.waitForSelector({
css: '[class*="box--align-items-center"]',
text: 'Estimated gas fee',
});
await driver.waitForSelector({
css: '[class*="actionable-message__message"]',
text: 'You need 43.4467 more TESTETH to complete this swap',
});
const swapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await swapButton.getText(), 'Swap');
assert.equal(await swapButton.isEnabled(), false);
},
);
});
it('tests notifications for verified token on 0 sources and high slippage', async function () { it('tests notifications for verified token on 0 sources and high slippage', async function () {
await withFixtures( await withFixtures(
{ {
fixtures: new FixtureBuilder().build(), ...withFixturesOptions,
ganacheOptions,
failOnConsoleError: false,
title: this.test.title, title: this.test.title,
}, },
async ({ driver }) => { async ({ driver }) => {
await loadSwaps(driver); await loadSwaps(driver);
await buildQuote(driver, { await buildQuote(driver, {
amount: 2, amount: 2,
swapTo: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf', swapToContractAddress: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf',
}); });
await driver.waitForSelector({ await driver.waitForSelector({
css: '.popover-header__title', css: '.popover-header__title',
@ -96,41 +127,4 @@ describe('Swaps - notifications', function () {
}, },
); );
}); });
it('tests a notification for not enough balance', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
title: this.test.title,
},
async ({ driver }) => {
await loadSwaps(driver);
await buildQuote(driver, {
amount: 50,
swapTo: 'USDC',
});
const reviewSwapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await reviewSwapButton.getText(), 'Review swap');
assert.equal(await reviewSwapButton.isEnabled(), true);
await reviewSwapButton.click();
await driver.waitForSelector({
css: '[class*="box--align-items-center"]',
text: 'Estimated gas fee',
});
await driver.waitForSelector({
css: '[class*="actionable-message__message"]',
text: 'You need 43.4467 more TESTETH to complete this swap',
});
const swapButton = await driver.findElement(
'[data-testid="page-container-footer-next"]',
);
assert.equal(await swapButton.getText(), 'Swap');
assert.equal(await swapButton.isEnabled(), false);
},
);
});
}); });

View File

@ -1,4 +1,4 @@
export { screen, fireEvent } from '@testing-library/react'; export { screen, fireEvent, waitFor } from '@testing-library/react';
export { createSwapsMockStore } from './mock-store'; export { createSwapsMockStore } from './mock-store';
export { renderWithProvider } from './rendering'; export { renderWithProvider } from './rendering';
export { setBackgroundConnection } from './background'; export { setBackgroundConnection } from './background';

View File

@ -192,5 +192,5 @@ FeeCard.propTypes = {
onQuotesClick: PropTypes.func.isRequired, onQuotesClick: PropTypes.func.isRequired,
numberOfQuotes: PropTypes.number.isRequired, numberOfQuotes: PropTypes.number.isRequired,
chainId: PropTypes.string.isRequired, chainId: PropTypes.string.isRequired,
isBestQuote: PropTypes.bool.isRequired, isBestQuote: PropTypes.bool,
}; };

View File

@ -67,7 +67,7 @@ export default function ImportToken({
fontWeight={FONT_WEIGHT.BOLD} fontWeight={FONT_WEIGHT.BOLD}
boxProps={{ marginTop: 2, marginBottom: 3 }} boxProps={{ marginTop: 2, marginBottom: 3 }}
> >
{tokenForImport.name} {tokenForImport.name || ''}
</Typography> </Typography>
<Typography variant={TYPOGRAPHY.H6}>{t('contract')}:</Typography> <Typography variant={TYPOGRAPHY.H6}>{t('contract')}:</Typography>
<Typography <Typography
@ -75,7 +75,7 @@ export default function ImportToken({
variant={TYPOGRAPHY.H7} variant={TYPOGRAPHY.H7}
boxProps={{ marginBottom: 6 }} boxProps={{ marginBottom: 6 }}
> >
{tokenForImport.address} {tokenForImport.address || ''}
</Typography> </Typography>
</Box> </Box>
</Popover> </Popover>

View File

@ -17,6 +17,8 @@ const renderAdornment = () => (
</InputAdornment> </InputAdornment>
); );
let timeoutIdForSearch;
export default function ListItemSearch({ export default function ListItemSearch({
onSearch, onSearch,
error, error,
@ -37,7 +39,6 @@ export default function ListItemSearch({
* @param {string} contractAddress * @param {string} contractAddress
*/ */
const handleSearchTokenForImport = async (contractAddress) => { const handleSearchTokenForImport = async (contractAddress) => {
setSearchQuery(contractAddress);
try { try {
const token = await fetchToken(contractAddress, chainId); const token = await fetchToken(contractAddress, chainId);
if (token) { if (token) {
@ -60,22 +61,32 @@ export default function ListItemSearch({
}; };
const handleSearch = async (newSearchQuery) => { const handleSearch = async (newSearchQuery) => {
const trimmedNewSearchQuery = newSearchQuery.trim();
const validHexAddress = isValidHexAddress(trimmedNewSearchQuery);
const fuseSearchResult = fuseRef.current.search(newSearchQuery);
const results =
defaultToAll && newSearchQuery === '' ? listToSearch : fuseSearchResult;
if (shouldSearchForImports && results.length === 0 && validHexAddress) {
await handleSearchTokenForImport(trimmedNewSearchQuery);
return;
}
setSearchQuery(newSearchQuery); setSearchQuery(newSearchQuery);
onSearch({ if (timeoutIdForSearch) {
searchQuery: newSearchQuery, clearTimeout(timeoutIdForSearch);
results, }
}); timeoutIdForSearch = setTimeout(async () => {
timeoutIdForSearch = null;
const trimmedNewSearchQuery = newSearchQuery.trim();
const validHexAddress = isValidHexAddress(trimmedNewSearchQuery);
const fuseSearchResult = fuseRef.current.search(newSearchQuery);
const results =
defaultToAll && newSearchQuery === '' ? listToSearch : fuseSearchResult;
if (shouldSearchForImports && results.length === 0 && validHexAddress) {
await handleSearchTokenForImport(trimmedNewSearchQuery);
return;
}
onSearch({
searchQuery: newSearchQuery,
results,
});
}, 350);
}; };
useEffect(() => {
return () => clearTimeout(timeoutIdForSearch);
}, []);
useEffect(() => { useEffect(() => {
if (!fuseRef.current) { if (!fuseRef.current) {
fuseRef.current = new Fuse(listToSearch, { fuseRef.current = new Fuse(listToSearch, {
@ -128,6 +139,6 @@ ListItemSearch.propTypes = {
searchPlaceholderText: PropTypes.string, searchPlaceholderText: PropTypes.string,
defaultToAll: PropTypes.bool, defaultToAll: PropTypes.bool,
shouldSearchForImports: PropTypes.bool, shouldSearchForImports: PropTypes.bool,
searchQuery: PropTypes.func, searchQuery: PropTypes.string,
setSearchQuery: PropTypes.func, setSearchQuery: PropTypes.func,
}; };

View File

@ -74,6 +74,7 @@ describe('ListItemSearch', () => {
}); });
it('changes the search query', () => { it('changes the search query', () => {
jest.useFakeTimers();
const store = configureMockStore(middleware)(createSwapsMockStore()); const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps(); const props = createProps();
const { getByTestId } = renderWithProvider( const { getByTestId } = renderWithProvider(
@ -82,6 +83,7 @@ describe('ListItemSearch', () => {
); );
const input = getByTestId('search-list-items'); const input = getByTestId('search-list-items');
fireEvent.change(input, { target: { value: 'USD' } }); fireEvent.change(input, { target: { value: 'USD' } });
jest.runAllTimers();
expect(props.setSearchQuery).toHaveBeenCalledWith('USD'); expect(props.setSearchQuery).toHaveBeenCalledWith('USD');
expect(props.onSearch).toHaveBeenCalledWith({ expect(props.onSearch).toHaveBeenCalledWith({
searchQuery: 'USD', searchQuery: 'USD',
@ -90,6 +92,7 @@ describe('ListItemSearch', () => {
}); });
it('imports a token', async () => { it('imports a token', async () => {
jest.useFakeTimers();
const store = configureMockStore(middleware)(createSwapsMockStore()); const store = configureMockStore(middleware)(createSwapsMockStore());
const props = createProps({ shouldSearchForImports: true }); const props = createProps({ shouldSearchForImports: true });
const { getByTestId } = renderWithProvider( const { getByTestId } = renderWithProvider(
@ -98,6 +101,7 @@ describe('ListItemSearch', () => {
); );
const input = getByTestId('search-list-items'); const input = getByTestId('search-list-items');
await fireEvent.change(input, { target: { value: token.address } }); await fireEvent.change(input, { target: { value: token.address } });
await jest.runAllTimers();
expect(props.setSearchQuery).toHaveBeenCalledWith(token.address); expect(props.setSearchQuery).toHaveBeenCalledWith(token.address);
expect(props.onSearch).toHaveBeenCalledWith({ expect(props.onSearch).toHaveBeenCalledWith({
searchQuery: token.address, searchQuery: token.address,

View File

@ -93,6 +93,6 @@ SearchableItemList.propTypes = {
hideItemIf: PropTypes.func, hideItemIf: PropTypes.func,
listContainerClassName: PropTypes.string, listContainerClassName: PropTypes.string,
shouldSearchForImports: PropTypes.bool, shouldSearchForImports: PropTypes.bool,
searchQuery: PropTypes.func, searchQuery: PropTypes.string,
setSearchQuery: PropTypes.func, setSearchQuery: PropTypes.func,
}; };

View File

@ -741,7 +741,7 @@ export default function ViewQuote() {
const priceSlippageUnknownFiatValue = const priceSlippageUnknownFiatValue =
!priceSlippageFromSource || !priceSlippageFromSource ||
!priceSlippageFromDestination || !priceSlippageFromDestination ||
usedQuote?.priceSlippage?.calculationError; Boolean(usedQuote?.priceSlippage?.calculationError);
let priceDifferencePercentage = 0; let priceDifferencePercentage = 0;
if (usedQuote?.priceSlippage?.ratio) { if (usedQuote?.priceSlippage?.ratio) {
@ -1038,7 +1038,7 @@ export default function ViewQuote() {
} }
hideCancel hideCancel
disabled={isSwapButtonDisabled} disabled={isSwapButtonDisabled}
className={isShowingWarning && 'view-quote__thin-swaps-footer'} className={isShowingWarning ? 'view-quote__thin-swaps-footer' : ''}
showTopBorder showTopBorder
/> />
</div> </div>