Skip to content

Commit

Permalink
chore: fix virtualized lists tests [LW-12151]
Browse files Browse the repository at this point in the history
Introduces generic and configurable utils to scan through virtualized
lists and retrieve items as specific index.
  • Loading branch information
DominikGuzei committed Feb 12, 2025
1 parent 2321381 commit 2e0363f
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 61 deletions.
4 changes: 3 additions & 1 deletion packages/e2e-tests/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ module.exports = exports = {
'new-cap': ['error', { capIsNew: false }],
'no-magic-numbers': ['off'],
'wdio/no-pause': ['off'],
'@typescript-eslint/no-explicit-any': ['off']
'@typescript-eslint/no-explicit-any': ['off'],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error'
},
'env': {
'node': true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,37 +1,64 @@
/* global WebdriverIO */
import TokenSelectionPage from '../../elements/newTransaction/tokenSelectionPage';
import { TokenSearchResult } from '../../elements/newTransaction/tokenSearchResult';
import { expect } from 'chai';
import { t } from '../../utils/translationService';
import { getVirtualizedListElementAtIndex, scanVirtualizedList } from '../../utils/virtualizedListUtils';

class TransactionAssetSelectionAssert {
async assertAssetIsPresentInTokenList(assetName: string, shouldBeDisplayed: boolean) {
await new TokenSearchResult(assetName).container.waitForDisplayed({ reverse: !shouldBeDisplayed });
}

async assertAssetsAreSelected(shouldBeSelected: boolean, amount: number, assetType: string) {
for (let i = 1; i <= amount; i++) {
await this.assertSpecificAssetSelected(shouldBeSelected, assetType, i);
}
async assertAssetsAreSelected(shouldBeSelected: boolean, expectedAmount: number, assetType: 'Tokens' | 'NFTs') {
await (assetType === 'Tokens'
? this.assertTokensSelected(shouldBeSelected, expectedAmount)
: this.assertNFTsSelected(shouldBeSelected, expectedAmount));
}

async assertSpecificAssetSelected(shouldBeSelected: boolean, assetType: string, index: number) {
if (assetType === 'Tokens') {
await TokenSelectionPage.tokenItem(index).grayedOutTokenIcon.waitForDisplayed({
reverse: !shouldBeSelected
});
await TokenSelectionPage.tokenItem(index).checkmarkInSelectedToken.waitForDisplayed({
reverse: !shouldBeSelected
});
} else {
await TokenSelectionPage.grayedOutNFT(index).waitForDisplayed({
reverse: !shouldBeSelected
});
await TokenSelectionPage.checkmarkInSelectedNFT(index).waitForDisplayed({
reverse: !shouldBeSelected
});
async assertTokensSelected(shouldBeSelected: boolean, expectedAmount: number) {
for (let i = 1; i <= expectedAmount; i++) {
await this.assertTokenSelectedAtIndex(shouldBeSelected, i);
}
}

async assertTokenSelectedAtIndex(shouldBeSelected: boolean, index: number) {
await TokenSelectionPage.tokenItem(index).grayedOutTokenIcon.waitForDisplayed({
reverse: !shouldBeSelected
});
await TokenSelectionPage.tokenItem(index).checkmarkInSelectedToken.waitForDisplayed({
reverse: !shouldBeSelected
});
}

assertNFTsSelected(shouldBeSelected: boolean, expectedAmount: number) {
return scanVirtualizedList(
expectedAmount,
() => TokenSelectionPage.nftContainers,
(nft) => TokenSelectionPage.getNftName(nft),
(nextNFT) => this.assertNFTSelected(shouldBeSelected, nextNFT)
);
}

async assertNFTSelected(shouldBeSelected: boolean, nft: WebdriverIO.Element) {
await TokenSelectionPage.grayedOutNFT(nft).waitForDisplayed({
reverse: !shouldBeSelected
});
await TokenSelectionPage.checkmarkInSelectedNFT(nft).waitForDisplayed({
reverse: !shouldBeSelected
});
}

async assertNFTSelectedAtIndex(shouldBeSelected: boolean, index: number) {
const nft = await getVirtualizedListElementAtIndex(
index,
() => TokenSelectionPage.nftContainers,
(n) => TokenSelectionPage.getNftName(n)
);
if (!nft) return Promise.reject(new Error(`NFT at index ${index} not found`));
return await this.assertNFTSelected(shouldBeSelected, nft);
}

async assertSelectedAssetsCounter(shouldBeDisplayed: boolean, amount: number) {
await TokenSelectionPage.assetsCounter.waitForDisplayed({ reverse: !shouldBeDisplayed });
if (shouldBeDisplayed) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { TokenSearchResult } from './tokenSearchResult';
import { browser } from '@wdio/globals';
import { scrollDownWithOffset } from '../../utils/scrollUtils';
import { ChainablePromiseElement } from 'webdriverio';
import { getVirtualizedListElementAtIndex, scanVirtualizedList } from '../../utils/virtualizedListUtils';

class TokenSelectionPage extends CommonDrawerElements {
private TOKENS_BUTTON = '//input[@data-testid="asset-selector-button-tokens"]';
Expand Down Expand Up @@ -67,22 +68,31 @@ class TokenSelectionPage extends CommonDrawerElements {
return this.assetSelectorContainer.$$(this.NFT_ITEM_NAME);
}

getNftAtIndex = (index: number) =>
getVirtualizedListElementAtIndex(
index,
() => this.nftContainers,
(nft) => this.getNftName(nft)
);

getNftContainer = async (name: string) =>
(await this.nftContainers.find(
async (item) => (await item.$(this.NFT_ITEM_NAME).getText()) === name
)) as WebdriverIO.Element;

getNftName = async (name: string) => {
getNftNameElement = async (name: string) => {
const nftContainer = await this.getNftContainer(name);
return nftContainer.$(this.NFT_ITEM_NAME);
};

grayedOutNFT(index: number) {
return this.nftContainers[index].$(this.NFT_ITEM_OVERLAY);
getNftName = async (nft: WebdriverIO.Element) => nft.$(this.NFT_ITEM_NAME).getText();

grayedOutNFT(nft: WebdriverIO.Element): ChainablePromiseElement<WebdriverIO.Element> {
return nft.$(this.NFT_ITEM_OVERLAY);
}

checkmarkInSelectedNFT(index: number) {
return this.nftContainers[index].$(this.NFT_ITEM_SELECTED_CHECKMARK);
checkmarkInSelectedNFT(nft: WebdriverIO.Element): ChainablePromiseElement<WebdriverIO.Element> {
return nft.$(this.NFT_ITEM_SELECTED_CHECKMARK);
}

get assetsCounter(): ChainablePromiseElement<WebdriverIO.Element> {
Expand Down Expand Up @@ -119,7 +129,7 @@ class TokenSelectionPage extends CommonDrawerElements {

clickNftItemInAssetSelector = async (nftName: string) => {
await this.waitForNft(nftName);
const nftNameElement = await this.getNftName(nftName);
const nftNameElement = await this.getNftNameElement(nftName);
await nftNameElement.waitForClickable();
await nftNameElement.click();
};
Expand All @@ -144,26 +154,39 @@ class TokenSelectionPage extends CommonDrawerElements {
}
};

deselectToken = async (assetType: string, index: number) => {
assetType === 'Tokens' ? await this.tokenItem(Number(index)).container.click() : await this.nftNames[index].click();
deselectAsset = (assetType: string, index: number) =>
assetType === 'Tokens' ? this.tokenItem(Number(index)).container.click() : this.deselectNFTAtIndex(index);

deselectNFTAtIndex = async (index: number): Promise<void> => {
const nft = await this.getNftAtIndex(index);
if (!nft) return Promise.reject(new Error(`No NFT at index ${index} found`));
await nft.waitForClickable();
return nft.click();
};

saveSelectedTokens = async (assetType: string, bundle: number) => {
const amountOfAssets = Number(await this.assetsCounter.getText());
testContext.save(`amountOfAssetsInBundle${String(bundle)}`, amountOfAssets);

for (let i = 1; i <= amountOfAssets; i++) {
if (assetType === 'Tokens') {
if (assetType === 'Tokens') {
for (let i = 1; i <= amountOfAssets; i++) {
const tokenName = String(await this.tokenItem(i).name.getText()).slice(0, 6);
const asset =
tokenName === 'asset1'
? String(await this.tokenItem(i).name.getText()).slice(0, 10)
: String(await this.tokenItem(i).ticker.getText()).slice(0, 10);
testContext.save(`bundle${String(bundle)}asset${String(i)}`, asset);
} else {
const asset = String(await this.nftNames[i].getText()).slice(0, 10);
testContext.save(`bundle${String(bundle)}asset${String(i)}`, asset);
}
} else {
await scanVirtualizedList(
amountOfAssets,
() => this.nftContainers,
(nft) => this.getNftName(nft),
async (_, index, nftName) => {
const asset = String(nftName).slice(0, 10);
testContext.save(`bundle${String(bundle)}asset${String(index)}`, asset);
}
);
}
};

Expand Down Expand Up @@ -217,30 +240,17 @@ class TokenSelectionPage extends CommonDrawerElements {
}

async selectNFTs(numberOfNFTs: number) {
let selectedCount = 0;

while (selectedCount < numberOfNFTs) {
const nfts = await this.nftContainers;

for (const nft of nfts) {
await scanVirtualizedList(
numberOfNFTs,
() => this.nftContainers,
(nft) => this.getNftName(nft),
async (nft) => {
const isSelected = await nft.$(this.NFT_ITEM_SELECTED_CHECKMARK).isExisting();
if (isSelected) {
continue;
}

if (isSelected) return;
await nft.waitForClickable();
await nft.click();
selectedCount++;

if (selectedCount >= numberOfNFTs) {
return;
}
}

if (selectedCount < numberOfNFTs) {
await scrollDownWithOffset(nfts);
}
}
);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Feature: Send - Multiple selection for Extended Browser View
Background:
Given Wallet is synced

@LW-5043 @Pending
@LW-5043
@issue=LW-12151
Scenario Outline: Extended view - Send - Multiple tokens selection - <assetsType> - happy path
And I click "Send" button on page header
Expand Down Expand Up @@ -66,7 +66,7 @@ Feature: Send - Multiple selection for Extended Browser View
| Tokens |
| NFTs |

@LW-5046 @Pending
@LW-5046
@issue=LW-12151
Scenario Outline: Extended view - Send - Multiple tokens selection - <assetsType> - clear and cancel
And I click "Send" button on page header
Expand All @@ -93,7 +93,7 @@ Feature: Send - Multiple selection for Extended Browser View
| Tokens |
| NFTs |

@LW-5267 @Pending
@LW-5267
@issue=LW-12151
Scenario Outline: Extended view - Send - Multiple tokens selection - <assetsType> - Maximum amount to select is 30
And I click "Send" button on page header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Feature: Send - Multiple selection for Popup View
Background:
Given Wallet is synced

@LW-5044 @Pending
@LW-5044
@issue=LW-12151
Scenario Outline: Popup view - Send - Multiple tokens selection - <assetsType> - happy path
And I click "Send" button on Tokens page in popup mode
Expand All @@ -27,7 +27,7 @@ Feature: Send - Multiple selection for Popup View
| Tokens |
| NFTs |

@LW-5045 @Pending
@LW-5045
@issue=LW-12151
Scenario Outline: Extended view - Send - Multiple tokens selection - <assetsType> - clear and cancel
And I click "Send" button on Tokens page in popup mode
Expand All @@ -54,7 +54,7 @@ Feature: Send - Multiple selection for Popup View
| Tokens |
| NFTs |

@LW-5268 @Pending
@LW-5268
@issue=LW-12151
Scenario Outline: Extended view - Send - Multiple tokens selection - <assetsType> - Maximum amount to select is 30
And I click "Send" button on Tokens page in popup mode
Expand Down
6 changes: 4 additions & 2 deletions packages/e2e-tests/src/steps/sendTransactionSimpleSteps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ When(/^I select amount: (\d*) of asset type: (Tokens|NFTs)$/, async (amount: num
});

When(/^I deselect (Tokens|NFTs) (\d*)$/, async (assetType: 'Tokens' | 'NFTs', index: number) => {
await TokenSelectionPage.deselectToken(assetType, index);
await TokenSelectionPage.deselectAsset(assetType, index);
});

When(/^I save selected (Tokens|NFTs) in bundle (\d*)$/, async (assetType: 'Tokens' | 'NFTs', bundle: number) => {
Expand Down Expand Up @@ -533,7 +533,9 @@ Then(
/^(Tokens|NFTs) (\d*) (is|is not) selected$/,
async (assetType: 'Tokens' | 'NFTs', index: number, selected: string) => {
const shouldBeSelected: boolean = selected === 'are';
await TransactionAssetSelectionAssert.assertSpecificAssetSelected(shouldBeSelected, assetType, index);
assetType === 'Tokens'
? await TransactionAssetSelectionAssert.assertTokenSelectedAtIndex(shouldBeSelected, index)
: await TransactionAssetSelectionAssert.assertNFTSelectedAtIndex(shouldBeSelected, index);
}
);

Expand Down
63 changes: 63 additions & 0 deletions packages/e2e-tests/src/utils/virtualizedListUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* global WebdriverIO */

import { scrollDownWithOffset, scrollToTheTop } from './scrollUtils';

/**
* Scroll-scan through the active virtualized list.
*
* @param numberOfItems specify how many items should be scanned from the start of the virtualized list
* @param getVisibleElements function that should always return the currently visible elements in the list
* @param getElementId function that should return a unique identifier for every element
* @param handleNextElement iterator callback that will be invoked for every element to perform side-effects
*/
export const scanVirtualizedList = async (
numberOfItems: number,
getVisibleElements: () => Promise<WebdriverIO.ElementArray>,
getElementId: (element: WebdriverIO.Element) => Promise<string>,
handleNextElement: (element: WebdriverIO.Element, index: number, elementId: string) => Promise<void>
): Promise<void> => {
// scroll to the top of the visible virtualized list
await scrollToTheTop();
// ID references to already visited elements
const iteratedElementIds: string[] = [];

while (iteratedElementIds.length < numberOfItems) {
// get all currently visible elements in the virtualized list
const visibleElements = await getVisibleElements();
// filter-out any already iterated elements
const newVisibleElements = await visibleElements.filter(
async (el) => !iteratedElementIds.includes(await getElementId(el))
);
// iterate over the new visible elements
for (const element of newVisibleElements) {
const elementId = await getElementId(element);
// notify external iterator about next element
await handleNextElement(element, iteratedElementIds.length + 1, elementId);
// save the iterated element to avoid duplications
iteratedElementIds.push(elementId);
// stop immediately when target index was reached
if (iteratedElementIds.length >= numberOfItems) break;
}
// scroll further down and continue loop
await scrollDownWithOffset(visibleElements);
}
};

/**
* Scroll-scan through the active virtualized list and return only the item at given index
*
* @param index specify which item should be returned
* @param getVisibleElements function that should always return the currently visible elements in the list
* @param getElementId function that should return a unique identifier for every element
*/
export const getVirtualizedListElementAtIndex = async (
index: number,
getVisibleElements: () => Promise<WebdriverIO.ElementArray>,
getElementId: (element: WebdriverIO.Element) => Promise<string>
): Promise<WebdriverIO.Element | undefined> => {
let lastElement: WebdriverIO.Element | undefined;
await scanVirtualizedList(index, getVisibleElements, getElementId, async (nextElement) => {
lastElement = nextElement;
});
return lastElement;
};

0 comments on commit 2e0363f

Please sign in to comment.