diff --git a/.github/workflows/build-android-e2e.yml b/.github/workflows/build-android-e2e.yml index b27338838e0..4effd0be4de 100644 --- a/.github/workflows/build-android-e2e.yml +++ b/.github/workflows/build-android-e2e.yml @@ -122,6 +122,7 @@ jobs: - name: Cache Gradle dependencies uses: cirruslabs/cache@v4 + if: ${{ steps.apk-cache-restore.outputs.cache-hit != 'true' }} env: GRADLE_CACHE_VERSION: 1 with: @@ -134,6 +135,7 @@ jobs: key: gradle-${{ env.GRADLE_CACHE_VERSION }}-${{ runner.os }}-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} - name: Build Android E2E APKs + if: ${{ steps.apk-cache-restore.outputs.cache-hit != 'true' }} run: | echo "🏗 Building Android E2E APKs..." export NODE_OPTIONS="--max-old-space-size=8192" diff --git a/app/components/UI/Earn/Views/EarnInputView/__snapshots__/EarnInputView.test.tsx.snap b/app/components/UI/Earn/Views/EarnInputView/__snapshots__/EarnInputView.test.tsx.snap index e089ca754ab..bb0f8faefc0 100644 --- a/app/components/UI/Earn/Views/EarnInputView/__snapshots__/EarnInputView.test.tsx.snap +++ b/app/components/UI/Earn/Views/EarnInputView/__snapshots__/EarnInputView.test.tsx.snap @@ -586,10 +586,8 @@ exports[`EarnInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -623,10 +621,8 @@ exports[`EarnInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -660,10 +656,8 @@ exports[`EarnInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -697,10 +691,8 @@ exports[`EarnInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -2080,10 +2072,8 @@ exports[`EarnInputView when values are entered in the keypad updates ETH and fia { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -2117,10 +2107,8 @@ exports[`EarnInputView when values are entered in the keypad updates ETH and fia { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -2154,10 +2142,8 @@ exports[`EarnInputView when values are entered in the keypad updates ETH and fia { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -2191,10 +2177,8 @@ exports[`EarnInputView when values are entered in the keypad updates ETH and fia { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, diff --git a/app/components/UI/Earn/Views/EarnWithdrawInputView/__snapshots__/EarnWithdrawInputView.test.tsx.snap b/app/components/UI/Earn/Views/EarnWithdrawInputView/__snapshots__/EarnWithdrawInputView.test.tsx.snap index 82bcd49c306..6e23522abbc 100644 --- a/app/components/UI/Earn/Views/EarnWithdrawInputView/__snapshots__/EarnWithdrawInputView.test.tsx.snap +++ b/app/components/UI/Earn/Views/EarnWithdrawInputView/__snapshots__/EarnWithdrawInputView.test.tsx.snap @@ -577,10 +577,8 @@ exports[`EarnWithdrawInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -614,10 +612,8 @@ exports[`EarnWithdrawInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -651,10 +647,8 @@ exports[`EarnWithdrawInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, @@ -688,10 +682,8 @@ exports[`EarnWithdrawInputView render matches snapshot 1`] = ` { "alignItems": "center", "alignSelf": "stretch", - "backgroundColor": "#ffffff", - "borderColor": "#b7bbc8", + "backgroundColor": "#3c4d9d0f", "borderRadius": 20, - "borderWidth": 1, "flex": 1, "flexDirection": "row", "height": 40, diff --git a/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.test.ts b/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.test.ts index 18d405db45b..c63459b6aa1 100644 --- a/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.test.ts +++ b/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.test.ts @@ -429,5 +429,102 @@ describe('useCryptoCurrencies', () => { cryptosWithMissing[0], ); }); + + it('selects native token when intent has slip44 wildcard assetId', () => { + const intent = { assetId: 'eip155:1/slip44:.' }; + mockUseDepositSDK.mockReturnValue( + createMockSDKReturn({ + selectedRegion: MOCK_US_REGION, + selectedCryptoCurrency: null, + setSelectedCryptoCurrency: mockSetSelectedCryptoCurrency, + intent, + setIntent: mockSetIntent, + }), + ); + + renderHook(() => useCryptoCurrencies()); + + expect(mockSetSelectedCryptoCurrency).toHaveBeenCalledWith( + MOCK_ETH_TOKEN, + ); + }); + + it('selects native token when intent has slip44 wildcard with different chainId', () => { + const mockPolygonNativeToken = { + assetId: 'eip155:137/slip44:966', + chainId: 'eip155:137', + name: 'Polygon', + symbol: 'POL', + decimals: 18, + iconUrl: 'https://example.com/pol.png', + }; + const cryptosWithPolygon = [ + ...MOCK_CRYPTOCURRENCIES, + mockPolygonNativeToken, + ]; + mockUseDepositSdkMethod.mockReturnValue([ + { data: cryptosWithPolygon, error: null, isFetching: false }, + mockRetryFetchCryptoCurrencies, + ]); + mockUseSelector.mockReturnValue({ + ...mockNetworkConfigurations, + 'eip155:137': { name: 'Polygon', chainId: '0x89' }, + }); + + const intent = { assetId: 'eip155:137/slip44:.' }; + mockUseDepositSDK.mockReturnValue( + createMockSDKReturn({ + selectedRegion: MOCK_US_REGION, + selectedCryptoCurrency: null, + setSelectedCryptoCurrency: mockSetSelectedCryptoCurrency, + intent, + setIntent: mockSetIntent, + }), + ); + + renderHook(() => useCryptoCurrencies()); + + expect(mockSetSelectedCryptoCurrency).toHaveBeenCalledWith( + mockPolygonNativeToken, + ); + }); + + it('falls back to first token when slip44 wildcard does not match any native token', () => { + const intent = { assetId: 'eip155:999/slip44:.' }; + mockUseDepositSDK.mockReturnValue( + createMockSDKReturn({ + selectedRegion: MOCK_US_REGION, + selectedCryptoCurrency: null, + setSelectedCryptoCurrency: mockSetSelectedCryptoCurrency, + intent, + setIntent: mockSetIntent, + }), + ); + + renderHook(() => useCryptoCurrencies()); + + expect(mockSetSelectedCryptoCurrency).toHaveBeenCalledWith( + MOCK_CRYPTOCURRENCIES[0], + ); + }); + + it('prefers direct match over slip44 wildcard matching', () => { + const intent = { assetId: MOCK_USDC_TOKEN.assetId }; + mockUseDepositSDK.mockReturnValue( + createMockSDKReturn({ + selectedRegion: MOCK_US_REGION, + selectedCryptoCurrency: null, + setSelectedCryptoCurrency: mockSetSelectedCryptoCurrency, + intent, + setIntent: mockSetIntent, + }), + ); + + renderHook(() => useCryptoCurrencies()); + + expect(mockSetSelectedCryptoCurrency).toHaveBeenCalledWith( + MOCK_USDC_TOKEN, + ); + }); }); }); diff --git a/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.ts b/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.ts index c9dea844a76..da1dc7fd442 100644 --- a/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.ts +++ b/app/components/UI/Ramp/Deposit/hooks/useCryptoCurrencies.ts @@ -11,6 +11,7 @@ import { isCaipChainId } from '@metamask/utils'; import { toEvmCaipChainId } from '@metamask/multichain-network-controller'; import { toHex } from '@metamask/controller-utils'; import { toLowerCaseEquals } from '../../../../../util/general'; +import { parseCAIP19AssetId } from '../../Aggregator/utils/parseCaip19AssetId'; export interface UseCryptoCurrenciesResult { cryptoCurrencies: DepositCryptoCurrency[] | null; @@ -56,10 +57,26 @@ export function useCryptoCurrencies(): UseCryptoCurrenciesResult { useEffect(() => { if (cryptoCurrencies && cryptoCurrencies.length > 0) { if (intent?.assetId) { - const intentCrypto = cryptoCurrencies.find((token) => + let intentCrypto = cryptoCurrencies.find((token) => toLowerCaseEquals(token.assetId, intent.assetId), ); + // Handle slip44 wildcard matching any native asset + if (!intentCrypto) { + const intentParsedCaip19 = parseCAIP19AssetId(intent.assetId); + if (intentParsedCaip19?.assetNamespace === 'slip44') { + intentCrypto = cryptoCurrencies.find((token) => { + const tokenParsed = parseCAIP19AssetId(token.assetId); + return ( + tokenParsed && + tokenParsed.namespace === intentParsedCaip19.namespace && + tokenParsed.chainId === intentParsedCaip19.chainId && + tokenParsed.assetNamespace === 'slip44' + ); + }); + } + } + setIntent((prevIntent) => prevIntent ? { ...prevIntent, assetId: undefined } : undefined, ); diff --git a/app/components/UI/Stake/components/QuickAmounts.tsx b/app/components/UI/Stake/components/QuickAmounts.tsx index 58714fd0f4c..b6b005190ad 100644 --- a/app/components/UI/Stake/components/QuickAmounts.tsx +++ b/app/components/UI/Stake/components/QuickAmounts.tsx @@ -28,9 +28,7 @@ const createStyles = (colors: Colors) => }, amount: { flex: 1, - borderWidth: 1, - borderColor: colors.border.default, - backgroundColor: colors.background.default, + backgroundColor: colors.background.muted, flexDirection: 'row', justifyContent: 'center', paddingHorizontal: 16, diff --git a/app/components/UI/Stake/components/UpsellBanner/UpsellBannerHeader/UpsellBannerHeader.styles.tsx b/app/components/UI/Stake/components/UpsellBanner/UpsellBannerHeader/UpsellBannerHeader.styles.tsx index 4b7befc72a2..bbced69d5d7 100644 --- a/app/components/UI/Stake/components/UpsellBanner/UpsellBannerHeader/UpsellBannerHeader.styles.tsx +++ b/app/components/UI/Stake/components/UpsellBanner/UpsellBannerHeader/UpsellBannerHeader.styles.tsx @@ -7,7 +7,7 @@ const styleSheet = (params: { theme: Theme }) => { return StyleSheet.create({ container: { - backgroundColor: colors.background.alternative, + backgroundColor: colors.background.section, borderRadius: 8, gap: 8, paddingVertical: 24, diff --git a/wdio/helpers/Gestures.js b/wdio/helpers/Gestures.js new file mode 100644 index 00000000000..8a9eb87a39c --- /dev/null +++ b/wdio/helpers/Gestures.js @@ -0,0 +1,408 @@ +import Selectors from './Selectors'; + +/** + * To make a Gesture methods more robust for multiple devices and also + * multiple screen sizes the advice is to work with percentages instead of + * actual coordinates. The percentages will calculate the position on the + * screen based on the SCREEN_SIZE which will be determined once if needed + * multiple times. + */ +let SCREEN_SIZE; + +/** + * The values in the below object are percentages of the screen + */ +const SWIPE_DIRECTION = { + down: { + start: { + x: 50, + y: 15, + }, + end: { + x: 50, + y: 85, + }, + }, + left: { + start: { + x: 95, + y: 50, + }, + end: { + x: 5, + y: 50, + }, + }, + right: { + start: { + x: 5, + y: 50, + }, + end: { + x: 95, + y: 50, + }, + }, + up: { + start: { + x: 50, + y: 85, + }, + end: { + x: 50, + y: 15, + }, + }, +}; + +const Actions = { + PRESS: 'press', + LONGPRESS: 'longPress', + TAP: 'tap', + MOVETO: 'moveTo', + WAIT: 'wait', + RELEASE: 'release', +}; + +class Gestures { + static async waitAndTap(element) { + const elem = await element; + await elem.waitForDisplayed({ timeout: 25000 }); + await elem.click(); + } + + static async tap(element, tapType = 'TAP') { + const elem = await element; + switch (tapType) { + case 'TAP': + await elem.click(); + break; + case 'LONGPRESS': + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerDown', duration: 0 }, + { type: 'pause', duration: 1000 }, + { type: 'pointerUp', duration: 0 } + ] + }]); + break; + case 'RELEASE': + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerUp', duration: 0 } + ] + }]); + break; + case 'WAIT': + await driver.pause(1000); + break; + case 'MOVETO': + const location = await elem.getLocation(); + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerMove', duration: 0, x: location.x, y: location.y } + ] + }]); + break; + default: + throw new Error('Tap type not found'); + } + } + + static async tapTextByXpath(text, tapType = 'TAP') { + const elem = await Selectors.getXpathElementByText(text); + await elem.waitForDisplayed(); + switch (tapType) { + case 'TAP': + await elem.click(); + break; + case 'LONGPRESS': + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerDown', duration: 0 }, + { type: 'pause', duration: 1000 }, + { type: 'pointerUp', duration: 0 } + ] + }]); + break; + case 'RELEASE': + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerUp', duration: 0 } + ] + }]); + break; + default: + throw new Error('Tap type not found'); + } + } + + static async tapByTextContaining(text, tapType = 'TAP') { + const elem = await Selectors.getXpathElementByTextContains(text); + await elem.waitForDisplayed(); + switch (tapType) { + case 'TAP': + await elem.click(); + break; + case 'LONGPRESS': + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerDown', duration: 0 }, + { type: 'pause', duration: 1000 }, + { type: 'pointerUp', duration: 0 } + ] + }]); + break; + case 'RELEASE': + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerUp', duration: 0 } + ] + }]); + break; + default: + throw new Error('Tap type not found'); + } + } + + static async tapByCoordinatesPercentage( + xAxisPercent, + yAxisPercentage, + tapCount = 1, + ) { + const { width, height } = await driver.getWindowSize(); + const widthPoint = (width * xAxisPercent) / 100; + const heightPoint = (height * yAxisPercentage) / 100; + await driver.touchPerform([ + { + action: 'tap', + options: { + x: widthPoint, + y: heightPoint, + count: tapCount, + }, + }, + ]); + } + + static async longPress(element, waitTime) { + const elem = await element; + await elem.waitForDisplayed(); + await driver.performActions([{ + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + { type: 'pointerDown', duration: 0 }, + { type: 'pause', duration: waitTime }, + { type: 'pointerUp', duration: 0 } + ] + }]); + } + + static async typeText(element, text) { + const elem = await element; + await elem.waitForDisplayed(); + await elem.click(); + await elem.clearValue(); + await elem.setValue(text, +'\n'); + } + + static async setValueWithoutTap(element, text) { + //Some instances typeText above does not work because of tap + const elem = await element; + await elem.waitForDisplayed(); + await elem.clearValue(); + await elem.setValue(text, +'\n'); + } + + /** + * Check if an element is visible and if not wipe up a portion of the screen to + * check if it visible after x amount of scrolls + */ + static async checkIfDisplayedWithSwipeUp(element, maxScrolls, amount = 0) { + // If the element is not displayed and we haven't scrolled the max amount of scrolls + // then scroll and execute the method again + const elem = await element; + if (!(await elem.isDisplayed()) && amount <= maxScrolls) { + await this.swipeUp(0.85); + await this.checkIfDisplayedWithSwipeUp(element, maxScrolls, amount + 1); + } else if (amount > maxScrolls) { + // If the element is still not visible after the max amount of scroll let it fail + throw new Error( + `The element '${element}' could not be found or is not visible.`, + ); + } // The element was found, proceed with the next action + } + /** + * Swipe down based on a percentage + */ + + static async swipeDown(percentage = 1) { + await this.swipeOnPercentage( + this.calculateXY(SWIPE_DIRECTION.down.start, percentage), + this.calculateXY(SWIPE_DIRECTION.down.end, percentage), + ); + } + /** + * Swipe Up based on a percentage + */ + + static async swipeUp(percentage = 1) { + await this.swipeOnPercentage( + this.calculateXY(SWIPE_DIRECTION.up.start, percentage), + this.calculateXY(SWIPE_DIRECTION.up.end, percentage), + ); + } + /** + * Swipe left based on a percentage + */ + + static async swipeLeft(percentageX = 1, percentageY = 1) { + await this.swipeOnPercentage( + this.calculateXY(SWIPE_DIRECTION.left.start, percentageX), + this.calculateXY(SWIPE_DIRECTION.left.end, percentageY), + ); + } + /** + * Swipe right based on a percentage + */ + + static async swipeRight(percentage = 1) { + await this.swipeOnPercentage( + this.calculateXY(SWIPE_DIRECTION.right.start, percentage), + this.calculateXY(SWIPE_DIRECTION.right.end, percentage), + ); + } + /** + * Swipe from coordinates (from) to the new coordinates (to). The given coordinates are + * percentages of the screen. + */ + + static async swipeOnPercentage(from, to) { + // Get the screen size and store it so it can be re-used. + // This will save a lot of webdriver calls if this methods is used multiple times. + SCREEN_SIZE = SCREEN_SIZE || (await driver.getWindowSize()); // Get the start position on the screen for the swipe + + const pressOptions = this.getDeviceScreenCoordinates(SCREEN_SIZE, from); // Get the move to position on the screen for the swipe + + const moveToScreenCoordinates = this.getDeviceScreenCoordinates( + SCREEN_SIZE, + to, + ); + await this.swipe(pressOptions, moveToScreenCoordinates); + } + /** + * Swipe from coordinates (from) to the new coordinates (to). The given coordinates are in pixels. + */ + + static async improvedSwipe() { + // TODO + const startPercentage = 98; + const endPercentage = 0; + const anchorPercentage = 50; + + const { width, height } = await driver.getWindowSize(); + const anchor = (height * anchorPercentage) / 100; + const startPoint = (width * startPercentage) / 100; + const endPoint = (width * endPercentage) / 100; + await driver.touchPerform([ + { + action: 'press', + options: { + x: startPoint, + y: anchor, + }, + }, + { + action: 'wait', + options: { + ms: 100, + }, + }, + { + action: 'moveTo', + options: { + x: endPoint, + y: anchor, + }, + }, + { + action: 'release', + options: {}, + }, + ]); + } + + static async swipe(from, to) { + // TODO + await driver.performActions([ + { + // a. Create the event + type: 'pointer', + id: 'finger1', + parameters: { pointerType: 'touch' }, + actions: [ + // b. Move finger into start position + { type: 'pointerMove', duration: 0, x: from.x, y: from.y }, + // c. Finger comes down into contact with screen + { type: 'pointerDown', button: 0 }, + // d. Pause for a little bit + { type: 'pause', duration: 100 }, + // e. Finger moves to end position + // We move our finger from the center of the element to the + // starting position of the element. + // Play with the duration to make the swipe go slower / faster + { type: 'pointerMove', duration: 1000, x: to.x, y: to.y }, + // f. Finger gets up, off the screen + { type: 'pointerUp', button: 0 }, + ], + }, + ]); + // Add a pause, just to make sure the swipe is done + await driver.pause(1000); + } + /** + * Get the screen coordinates based on a device his screen size + */ + + static getDeviceScreenCoordinates(screenSize, coordinates) { + return { + x: Math.round(screenSize.width * (coordinates.x / 100)), + y: Math.round(screenSize.height * (coordinates.y / 100)), + }; + } + /** + * Calculate the x y coordinates based on a percentage + */ + + static calculateXY({ x, y }, percentage) { + return { + x: x * percentage, + y: y * percentage, + }; + } +} + +export default Gestures; diff --git a/wdio/helpers/Selectors.js b/wdio/helpers/Selectors.js new file mode 100644 index 00000000000..5dc793d19e3 --- /dev/null +++ b/wdio/helpers/Selectors.js @@ -0,0 +1,64 @@ +class Selectors { + + static async getElementByPlatform(id, isNested = false) { + if (!isNested) { + return $(`~${id}`); + } + + const platform = await driver.getPlatform(); + if (platform === 'Android') { + return $(`~${id}`); + } else if (platform === 'iOS') { + /** + * Use class chains for iOS + * Ref.: https://webdriver.io/docs/selectors#ios-uiautomation + * Too many levels of nesting cause test ids not to be rendered + * Ref.: https://github.com/appium/appium/issues/14825 + */ + return $(`-ios class chain:${id}`); + } + } + + static async getXpathByContentDesc(id) { + return driver.$$(`//*[@content-desc='${id}']`); + } + + static async getXpathElementByText(text) { + const platform = await driver.getPlatform(); + if (platform === 'iOS') { + return await $(`//*[@name='${text}']`); + } + + if (platform === 'Android') { + return await $(`//*[@text='${text}']`); + } + } + + static async getXpathElementByTextContains(text) { + const platform = await driver.getPlatform(); + if (platform === 'iOS') { + return await $(`//*[contains(@name, '${text}')]`); + } + + if (platform === 'Android') { + return await $(`//*[contains(@text, '${text}')]`); + } + } + + static async getXpathElementByResourceId(id) { + const platform = await driver.getPlatform(); + if (platform === 'iOS') { + return await $(`~${id}`); + } + + if (platform === 'Android') { + return await $(`//*[@resource-id='${id}']`); + } + } + + static async getElementByCss(css) { + return await $(css); + } +} + +export default Selectors;