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:
parent
129ba1290e
commit
d634ff89df
@ -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 {
|
||||
|
@ -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'),
|
||||
];
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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 }) => {
|
||||
|
@ -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);
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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';
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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>
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -93,6 +93,6 @@ SearchableItemList.propTypes = {
|
||||
hideItemIf: PropTypes.func,
|
||||
listContainerClassName: PropTypes.string,
|
||||
shouldSearchForImports: PropTypes.bool,
|
||||
searchQuery: PropTypes.func,
|
||||
searchQuery: PropTypes.string,
|
||||
setSearchQuery: PropTypes.func,
|
||||
};
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user