diff --git a/packages/e2e-tests/src/assert/tokensPageAssert.ts b/packages/e2e-tests/src/assert/tokensPageAssert.ts index c03d4466b..d1dcf7108 100644 --- a/packages/e2e-tests/src/assert/tokensPageAssert.ts +++ b/packages/e2e-tests/src/assert/tokensPageAssert.ts @@ -15,6 +15,8 @@ type ExpectedTokenDetails = { }; class TokensPageAssert { + ADA_PRICE_CHECK_INTERVAL = 65_000; + assertSeeTitle = async () => { await TokensPage.title.waitForDisplayed({ timeout: 10_000 }); }; @@ -252,6 +254,32 @@ class TokensPageAssert { expect(tickerDisplayed).to.equal(expectedTicker); } + + async seePriceFetchExpiredErrorMessage(shouldBeVisible: boolean) { + await TokensPage.priceFetchErrorDescription.waitForDisplayed({ + reverse: !shouldBeVisible, + timeout: this.ADA_PRICE_CHECK_INTERVAL * 3 + }); + if (shouldBeVisible) { + const expiredErrorMessageToMatch = (await t('general.warnings.priceDataExpired')).split(':')[0]; + expect(await TokensPage.priceFetchErrorDescription.getText()) + .to.include(expiredErrorMessageToMatch) + .to.include(new Date().getFullYear()) + .to.include(new Date().getDate()) + .to.include(new Date().getMinutes()); + } + } + + async seePriceFetchFailedErrorMessage(shouldBeVisible: boolean) { + await TokensPage.priceFetchErrorDescription.waitForDisplayed({ + reverse: !shouldBeVisible, + timeout: this.ADA_PRICE_CHECK_INTERVAL * 3 + }); + if (shouldBeVisible) + expect(await TokensPage.priceFetchErrorDescription.getText()).to.equal( + await t('general.warnings.cannotFetchPrice') + ); + } } export default new TokensPageAssert(); diff --git a/packages/e2e-tests/src/elements/tokensPage.ts b/packages/e2e-tests/src/elements/tokensPage.ts index f992e9347..4282d8759 100644 --- a/packages/e2e-tests/src/elements/tokensPage.ts +++ b/packages/e2e-tests/src/elements/tokensPage.ts @@ -2,6 +2,7 @@ import SectionTitle from './sectionTitle'; import { ChainablePromiseElement } from 'webdriverio'; import { ChainablePromiseArray } from 'webdriverio/build/types'; +import TokensPageAssert from '../assert/tokensPageAssert'; class TokensPage { private BALANCE_LABEL = '[data-testid="portfolio-balance-label"]'; @@ -21,6 +22,7 @@ class TokensPage { private OPENED_EYE_ICON = '[data-testid="opened-eye-icon"]'; private VIEW_ALL_BUTTON = '[data-testid="view-all-button"]'; private TOKEN_ROW_SKELETON = '.ant-skeleton'; + private PRICE_FETCH_ERROR_DESCRIPTION = '[data-testid="banner-description"]'; get sendButtonPopupMode(): ChainablePromiseElement { return $(this.SEND_BUTTON_POPUP_MODE); @@ -103,6 +105,10 @@ class TokensPage { return $(this.TOKEN_ROW_SKELETON); } + get priceFetchErrorDescription(): ChainablePromiseElement { + return $(this.PRICE_FETCH_ERROR_DESCRIPTION); + } + async getRows(): Promise { return $$(this.TOKENS_TABLE_ROW); } @@ -143,6 +149,10 @@ class TokensPage { async getTokensCounterAsNumber(): Promise { return SectionTitle.getCounterAsNumber(); } + + async waitForPricesToBeFetched() { + await this.totalBalanceValue.waitForDisplayed({ timeout: TokensPageAssert.ADA_PRICE_CHECK_INTERVAL }); + } } export default new TokensPage(); diff --git a/packages/e2e-tests/src/features/SendTransactionBundlesExtended.feature b/packages/e2e-tests/src/features/SendTransactionBundlesExtended.feature index 105fff29e..b6f787e3f 100644 --- a/packages/e2e-tests/src/features/SendTransactionBundlesExtended.feature +++ b/packages/e2e-tests/src/features/SendTransactionBundlesExtended.feature @@ -265,7 +265,7 @@ Feature: Send - Extended Browser View (Advanced Tx) @LW-3578 Scenario: Extended-view - Transaction error screen displayed for multiple bundles on transaction submit error - Given I enable network interception to fail request: "*/tx-submit/submit" with error 400 + Given I enable network interception to finish request: "*/tx-submit/submit" with error 400 And I click "Send" button on page header And I set 2 bundles with the same assets And I click "Review transaction" button on "Send" page diff --git a/packages/e2e-tests/src/features/SendTransactionSimpleExtended.part3.feature b/packages/e2e-tests/src/features/SendTransactionSimpleExtended.part3.feature index f3d9ffdf7..efd300400 100644 --- a/packages/e2e-tests/src/features/SendTransactionSimpleExtended.part3.feature +++ b/packages/e2e-tests/src/features/SendTransactionSimpleExtended.part3.feature @@ -125,7 +125,7 @@ Feature: LW-484: Send & Receive - Extended Browser View (Simple Tx) @LW-2374 @Testnet Scenario: Extended-view - Transaction error screen displayed on transaction submit error - Given I enable network interception to fail request: "*/tx-submit/submit" with error 400 + Given I enable network interception to finish request: "*/tx-submit/submit" with error 400 And I click "Send" button on page header And I’ve entered accepted values for all fields of simple Tx And I click "Review transaction" button on "Send" page diff --git a/packages/e2e-tests/src/features/SendTransactionSimplePopup.part3.feature b/packages/e2e-tests/src/features/SendTransactionSimplePopup.part3.feature index 88910b51c..3d905a2b2 100644 --- a/packages/e2e-tests/src/features/SendTransactionSimplePopup.part3.feature +++ b/packages/e2e-tests/src/features/SendTransactionSimplePopup.part3.feature @@ -103,7 +103,7 @@ Feature: LW-484: Send & Receive - Popup View (Simple Tx) @LW-2408 @Testnet Scenario: Popup-view - Transaction error screen displayed on transaction submit error - Given I enable network interception to fail request: "*/tx-submit/submit" with error 400 + Given I enable network interception to finish request: "*/tx-submit/submit" with error 400 And I click "Send" button on Tokens page in popup mode And I’ve entered accepted values for all fields of simple Tx And I click "Review transaction" button on "Send" page diff --git a/packages/e2e-tests/src/features/TokensPageExtended.feature b/packages/e2e-tests/src/features/TokensPageExtended.feature index c59877f53..2cfceeb12 100644 --- a/packages/e2e-tests/src/features/TokensPageExtended.feature +++ b/packages/e2e-tests/src/features/TokensPageExtended.feature @@ -108,7 +108,7 @@ Feature: LW: Tokens tab - extended view And I see total wallet balance in USD And balance and FIAT balance for each token are visible - @Testnet @Mainnet @LW-7121 @LW-7123 + @LW-7121 @LW-7123 @Testnet @Mainnet Scenario Outline: Extended View - Hide my balance - keep state after the page When I click closed eye icon on Tokens page Then opened eye icon is displayed on Tokens page @@ -124,8 +124,7 @@ Feature: LW: Tokens tab - extended view # LW-7706 # | reopening | I reopen the page | - - @Testnet @Mainnet @LW-7125 + @LW-7125 @Testnet @Mainnet Scenario: Extended view - Hide my balance - keep state after switching to popup view When I click closed eye icon on Tokens page Then opened eye icon is displayed on Tokens page @@ -135,3 +134,46 @@ Feature: LW: Tokens tab - extended view Then opened eye icon is displayed on Tokens page And total wallet balance is masked with asterisks And balance and FIAT balance for each token are masked with asterisks + + @LW-6889 @Testnet @Mainnet + Scenario: Extended view - Token pricing - Price fetch expired error is displayed when coingecko request fails + Given ADA fiat price has been fetched + When I enable network interception to fail request: "https://coingecko.*" + And I shift back last fiat price fetch time in local storage by 500 seconds + Then "Price data expired" error is displayed + When I disable network interception + Then ADA fiat price has been fetched + And "Price data expired" error is not displayed + + @LW-10283 @Testnet @Mainnet @Pending + @issue=LW-10296 + Scenario: Extended view - Token pricing - Price fetch expired error is displayed when coingecko request returns 500 + Given ADA fiat price has been fetched + When I enable network interception to finish request: "https://coingecko.*" with error 500 + And I shift back last fiat price fetch time in local storage by 500 seconds + Then "Price data expired" error is displayed + When I disable network interception + Then ADA fiat price has been fetched + And "Price data expired" error is not displayed + + @LW-6890 @Testnet @Mainnet @Pending + @issue=LW-10296 + Scenario: Extended view - Token pricing - Fiat price unable to fetch error is displayed on failed request + Given ADA fiat price has been fetched + When I enable network interception to fail request: "https://coingecko.*" + And I delete fiat price timestamp from background storage + Then "Unable to fetch fiat values" error is displayed + When I disable network interception + Then ADA fiat price has been fetched + Then "Unable to fetch fiat values" error is not displayed + + @LW-6681 @Testnet @Mainnet @Pending + @issue=LW-10296 + Scenario: Extended view - Token pricing - Fiat price unable to fetch error is displayed when coingecko request returns 500 + Given ADA fiat price has been fetched + When I enable network interception to finish request: "https://coingecko.*" with error 500 + And I delete fiat price timestamp from background storage + Then "Unable to fetch fiat values" error is displayed + And I disable network interception + Then ADA fiat price has been fetched + Then "Unable to fetch fiat values" error is not displayed diff --git a/packages/e2e-tests/src/features/TokensPagePopup.feature b/packages/e2e-tests/src/features/TokensPagePopup.feature index fc0214081..329f6b514 100644 --- a/packages/e2e-tests/src/features/TokensPagePopup.feature +++ b/packages/e2e-tests/src/features/TokensPagePopup.feature @@ -78,7 +78,7 @@ Feature: LW: Tokens tab - popup view And I see total wallet balance in USD And balance and FIAT balance for each token are visible - @Testnet @Mainnet @LW-7122 @LW-7124 + @LW-7122 @LW-7124 @Testnet @Mainnet Scenario Outline: Popup View - Hide my balance - keep state after the page When I click closed eye icon on Tokens page Then opened eye icon is displayed on Tokens page @@ -94,7 +94,7 @@ Feature: LW: Tokens tab - popup view # LW-7706 # | reopening | I reopen the page | - @Testnet @Mainnet @LW-7126 + @LW-7126 @Testnet @Mainnet Scenario: Popup View - Hide my balance - keep state after switching to extended view When I click closed eye icon on Tokens page Then opened eye icon is displayed on Tokens page @@ -104,3 +104,46 @@ Feature: LW: Tokens tab - popup view Then opened eye icon is displayed on Tokens page And total wallet balance is masked with asterisks And balance and FIAT balance for each token are masked with asterisks + + @LW-6684 @Testnet @Mainnet + Scenario: Popup view - Token pricing - Price fetch expired error is displayed when coingecko request fails + Given ADA fiat price has been fetched + When I enable network interception to fail request: "https://coingecko.*" + And I shift back last fiat price fetch time in local storage by 500 seconds + Then "Price data expired" error is displayed + When I disable network interception + Then ADA fiat price has been fetched + And "Price data expired" error is not displayed + + @LW-10307 @Testnet @Mainnet @Pending + @issue=LW-10296 + Scenario: Popup view - Token pricing - Price fetch expired error is displayed when coingecko request returns 500 + Given ADA fiat price has been fetched + When I enable network interception to finish request: "https://coingecko.*" with error 500 + And I shift back last fiat price fetch time in local storage by 500 seconds + Then "Price data expired" error is displayed + When I disable network interception + Then ADA fiat price has been fetched + And "Price data expired" error is not displayed + + @LW-6682 @Testnet @Mainnet @Pending + @issue=LW-10296 + Scenario: Popup view - Token pricing - Fiat price unable to fetch error is displayed on failed request + Given ADA fiat price has been fetched + When I enable network interception to fail request: "https://coingecko.*" + And I delete fiat price timestamp from background storage + Then "Unable to fetch fiat values" error is displayed + When I disable network interception + Then ADA fiat price has been fetched + Then "Unable to fetch fiat values" error is not displayed + + @LW-6683 @Testnet @Mainnet @Pending + @issue=LW-10296 + Scenario: Popup view - Token pricing - Fiat price unable to fetch error is displayed when coingecko request returns 500 + Given ADA fiat price has been fetched + When I enable network interception to finish request: "https://coingecko.*" with error 500 + And I delete fiat price timestamp from background storage + Then "Unable to fetch fiat values" error is displayed + And I disable network interception + Then ADA fiat price has been fetched + Then "Unable to fetch fiat values" error is not displayed diff --git a/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature b/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature index 8b3c3bb02..8500b56a4 100644 --- a/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature +++ b/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature @@ -53,7 +53,7 @@ Feature: Staking Page - Switching pools - Extended Browser View - E2E @LW-4558 @Testnet Scenario: Extended View - Staking - Staking error screen displayed on transaction submit error - Given I enable network interception to fail request: "*/tx-submit/submit" with error 400 + Given I enable network interception to finish request: "*/tx-submit/submit" with error 400 When I navigate to Staking extended page Then I see currently staking stake pool in extended mode and choose new pool as "OtherStakePool" When I input "OtherStakePool" to the search bar diff --git a/packages/e2e-tests/src/steps/commonSteps.ts b/packages/e2e-tests/src/steps/commonSteps.ts index c800155d6..993f50c14 100755 --- a/packages/e2e-tests/src/steps/commonSteps.ts +++ b/packages/e2e-tests/src/steps/commonSteps.ts @@ -12,7 +12,11 @@ import localStorageManager from '../utils/localStorageManager'; import networkManager from '../utils/networkManager'; import { Logger } from '../support/logger'; import clipboard from 'clipboardy'; -import { cleanBrowserStorage } from '../utils/browserStorage'; +import { + shiftBackFiatPriceFetchedTimeInBrowserStorage, + cleanBrowserStorage, + deleteFiatPriceTimestampFromBackgroundStorage +} from '../utils/browserStorage'; import BackgroundStorageAssert from '../assert/backgroundStorageAssert'; import topNavigationAssert from '../assert/topNavigationAssert'; import testContext from '../utils/testContext'; @@ -201,13 +205,6 @@ When(/^I am in the offline network mode: (true|false)$/, async (offline: 'true' await networkManager.changeNetworkCapabilitiesOfBrowser(offline === 'true'); }); -When( - /^I enable network interception to fail request: "([^"]*)" with error (\d*)$/, - async (urlPattern: string, errorCode: number) => { - await networkManager.failResponse(urlPattern, errorCode); - } -); - When(/^I click outside the drawer$/, async () => { await new CommonDrawerElements().areaOutsideDrawer.click(); }); @@ -364,3 +361,26 @@ When(/^I scroll (down|up) (\d*) pixels$/, async (direction: 'down' | 'up', pixel Given(/^I confirm multi-address discovery modal$/, async () => { await settingsExtendedPageObject.multiAddressModalConfirm(); }); + +When(/^I enable network interception to fail request: "([^"]*)"$/, async (urlPattern: string) => { + await networkManager.failRequest(urlPattern); +}); + +When( + /^I enable network interception to finish request: "([^"]*)" with error (\d*)$/, + async (urlPattern: string, errorCode: number) => { + await networkManager.finishWithResponseCode(urlPattern, errorCode); + } +); + +Given(/^I shift back last fiat price fetch time in local storage by (\d+) seconds$/, async (seconds: number) => { + await shiftBackFiatPriceFetchedTimeInBrowserStorage(seconds); +}); + +Then(/^I disable network interception$/, async () => { + await networkManager.closeOpenedCdpSessions(); +}); + +Given(/^I delete fiat price timestamp from background storage$/, async () => { + await deleteFiatPriceTimestampFromBackgroundStorage(); +}); diff --git a/packages/e2e-tests/src/steps/tokensPageSteps.ts b/packages/e2e-tests/src/steps/tokensPageSteps.ts index a6ec0b68d..a23ffc713 100644 --- a/packages/e2e-tests/src/steps/tokensPageSteps.ts +++ b/packages/e2e-tests/src/steps/tokensPageSteps.ts @@ -1,4 +1,4 @@ -import { When, Then } from '@cucumber/cucumber'; +import { Then, When } from '@cucumber/cucumber'; import tokensPageAssert from '../assert/tokensPageAssert'; import tokensPageObject from '../pageobject/tokensPageObject'; import tokenDetailsAssert from '../assert/tokenDetailsAssert'; @@ -9,6 +9,7 @@ import { switchToLastWindow } from '../utils/window'; import extensionUtils from '../utils/utils'; import TokensPage from '../elements/tokensPage'; import type { NetworkType } from '../types/network'; +import { Given } from '@wdio/cucumber-framework'; When(/^I see Tokens counter with total number of tokens displayed$/, async () => { await tokensPageAssert.assertSeeTitleWithCounter(); @@ -217,3 +218,16 @@ Then(/^I see total wallet balance in ADA is "([^"]*)"$/, async (balanceInAda: nu Then(/^I see tMin token with the ADA balance of "([^"]*)"$/, async (balanceInAda: number) => { await tokensPageAssert.assertTMinBalance(balanceInAda); }); + +Then( + /^"(Price data expired|Unable to fetch fiat values)" error (is|is not) displayed$/, + async (errorType: 'Price data expired' | 'Unable to fetch fiat values', shouldBeDisplayed: 'is' | 'is not') => { + errorType === 'Price data expired' + ? await tokensPageAssert.seePriceFetchExpiredErrorMessage(shouldBeDisplayed === 'is') + : await tokensPageAssert.seePriceFetchFailedErrorMessage(shouldBeDisplayed === 'is'); + } +); + +Given(/^ADA fiat price has been fetched$/, async () => { + await TokensPage.waitForPricesToBeFetched(); +}); diff --git a/packages/e2e-tests/src/utils/browserStorage.ts b/packages/e2e-tests/src/utils/browserStorage.ts index 78e01cced..429ce781b 100644 --- a/packages/e2e-tests/src/utils/browserStorage.ts +++ b/packages/e2e-tests/src/utils/browserStorage.ts @@ -71,3 +71,29 @@ export const clearBackgroundStorageKey: any = async (): Promise => { Logger.warn(`Clearing background storage key failed: ${error}`); } }; + +export const shiftBackFiatPriceFetchedTimeInBrowserStorage = async (seconds: number): Promise => { + const backgroundStorage = await getBackgroundStorage(); + backgroundStorage.fiatPrices.timestamp -= seconds * 1000; + try { + await browser.execute( + `await chrome.storage.local.set({ BACKGROUND_STORAGE: ${JSON.stringify(backgroundStorage)}})`, + [] + ); + } catch (error) { + throw new Error(`Setting browser storage failed: ${error}`); + } +}; + +export const deleteFiatPriceTimestampFromBackgroundStorage = async (): Promise => { + const backgroundStorage = await getBackgroundStorage(); + delete backgroundStorage.fiatPrices.timestamp; + try { + await browser.execute( + `await chrome.storage.local.set({ BACKGROUND_STORAGE: ${JSON.stringify(backgroundStorage)}})`, + [] + ); + } catch (error) { + throw new Error(`Setting browser storage failed: ${error}`); + } +}; diff --git a/packages/e2e-tests/src/utils/networkManager.ts b/packages/e2e-tests/src/utils/networkManager.ts index 18c96a2f3..42d2869be 100644 --- a/packages/e2e-tests/src/utils/networkManager.ts +++ b/packages/e2e-tests/src/utils/networkManager.ts @@ -62,14 +62,12 @@ export class NetworkManager { await browser.pause(2000); }; - failResponse = async (urlPattern: string, responseCode: number): Promise => { + finishWithResponseCode = async (urlPattern: string, responseCode: number): Promise => { await browser.call(async () => { const puppeteer = await browser.getPuppeteer(); const targets = puppeteer .targets() - .filter( - (target) => target.type() === 'page' || target.type() === 'service_worker' || target.type() === 'other' - ); + .filter((target) => ['page', 'service_worker', 'other'].includes(target.type())); targets.map(async (target) => { const client: CDPSession = (await target.createCDPSession()) as unknown as CDPSession; NetworkManager.cdpSessions.push(client); @@ -88,6 +86,29 @@ export class NetworkManager { }); }; + failRequest = async (urlPattern: string): Promise => { + await browser.call(async () => { + const puppeteer = await browser.getPuppeteer(); + const targets = puppeteer + .targets() + .filter((target) => ['page', 'service_worker', 'other'].includes(target.type())); + targets.map(async (target) => { + const client: CDPSession = (await target.createCDPSession()) as unknown as CDPSession; + NetworkManager.cdpSessions.push(client); + await client.send('Fetch.enable', { + patterns: [{ urlPattern }] + }); + client.on('Fetch.requestPaused', async ({ requestId, request }) => { + Logger.log(`found request: ${request.url}, failing request`); + await client.send('Fetch.failRequest', { + requestId, + errorReason: 'Failed' + }); + }); + }); + }); + }; + logFailedRequests = async (): Promise => { await browser.call(async () => { const puppeteer = await browser.getPuppeteer();