1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-22 09:23:21 +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
.forGet(
'https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x0d8775f648430679a709e98d2b0cb6250d2887ef.png',
)
.thenCallback(() => {
return {
statusCode: 200,
};
});
await server
.forGet(
'https://static.metaswap.codefi.network/api/v1/tokenIcons/1337/0x2efa2cb29c2341d8e5ba7d3262c9e9d6f1bf3711.png',
/^https:\/\/static\.metaswap\.codefi\.network\/api\/v1\/tokenIcons\/1337\/.*\.png/u,
)
.thenCallback(() => {
return {

View File

@ -51,8 +51,7 @@ async function main() {
if (!snaps) {
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'),
];
}

View File

@ -1,3 +1,5 @@
const FixtureBuilder = require('../fixture-builder');
const ganacheOptions = {
accounts: [
{
@ -8,6 +10,11 @@ const ganacheOptions = {
],
};
const withFixturesOptions = {
fixtures: new FixtureBuilder().build(),
ganacheOptions,
};
const loadSwaps = async (driver) => {
await driver.navigate();
await driver.fill('#password', 'correct horse battery staple');
@ -28,19 +35,37 @@ const buildQuote = async (driver, options) => {
await driver.wait(async () => {
const tokens = await driver.findElements('.searchable-item-list__item');
return tokens.length > 1;
}, 10000);
});
await driver.clickElement('.searchable-item-list__labels');
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(
'[placeholder="Search name or paste address"]',
options.swapTo,
'input[data-testid="search-list-items"]',
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');
};
module.exports = {
ganacheOptions,
withFixturesOptions,
loadSwaps,
buildQuote,
};

View File

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

View File

@ -1,16 +1,14 @@
const { strict: assert } = require('assert');
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 () {
it('tests notifications for verified token on 1 source and price difference', async function () {
await withFixtures(
{
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
...withFixturesOptions,
title: this.test.title,
},
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 () {
await withFixtures(
{
fixtures: new FixtureBuilder().build(),
ganacheOptions,
failOnConsoleError: false,
...withFixturesOptions,
title: this.test.title,
},
async ({ driver }) => {
await loadSwaps(driver);
await buildQuote(driver, {
amount: 2,
swapTo: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf',
swapToContractAddress: '0x72c9Fb7ED19D3ce51cea5C56B3e023cd918baaDf',
});
await driver.waitForSelector({
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 { renderWithProvider } from './rendering';
export { setBackgroundConnection } from './background';

View File

@ -192,5 +192,5 @@ FeeCard.propTypes = {
onQuotesClick: PropTypes.func.isRequired,
numberOfQuotes: PropTypes.number.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}
boxProps={{ marginTop: 2, marginBottom: 3 }}
>
{tokenForImport.name}
{tokenForImport.name || ''}
</Typography>
<Typography variant={TYPOGRAPHY.H6}>{t('contract')}:</Typography>
<Typography
@ -75,7 +75,7 @@ export default function ImportToken({
variant={TYPOGRAPHY.H7}
boxProps={{ marginBottom: 6 }}
>
{tokenForImport.address}
{tokenForImport.address || ''}
</Typography>
</Box>
</Popover>

View File

@ -17,6 +17,8 @@ const renderAdornment = () => (
</InputAdornment>
);
let timeoutIdForSearch;
export default function ListItemSearch({
onSearch,
error,
@ -37,7 +39,6 @@ export default function ListItemSearch({
* @param {string} contractAddress
*/
const handleSearchTokenForImport = async (contractAddress) => {
setSearchQuery(contractAddress);
try {
const token = await fetchToken(contractAddress, chainId);
if (token) {
@ -60,22 +61,32 @@ export default function ListItemSearch({
};
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);
onSearch({
searchQuery: newSearchQuery,
results,
});
if (timeoutIdForSearch) {
clearTimeout(timeoutIdForSearch);
}
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(() => {
if (!fuseRef.current) {
fuseRef.current = new Fuse(listToSearch, {
@ -128,6 +139,6 @@ ListItemSearch.propTypes = {
searchPlaceholderText: PropTypes.string,
defaultToAll: PropTypes.bool,
shouldSearchForImports: PropTypes.bool,
searchQuery: PropTypes.func,
searchQuery: PropTypes.string,
setSearchQuery: PropTypes.func,
};

View File

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

View File

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

View File

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