From cb4ff0351bf1a8aa104187bf1351d2f8f8c9450c Mon Sep 17 00:00:00 2001 From: Curtis David Date: Thu, 23 Jan 2025 13:15:31 -0500 Subject: [PATCH] test: deprecate gestures in helpers.js file (#13059) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** The helpers.js file was a bit of a dumping ground for all test actions: Selecting elements, asserting elements, and using utility methods like delays, deep linking, launching the app, and interacting with elements. We did some work last year to separate the various types of logic. Locating elements - `Matchers.js` Interacting with elements - `Guestures.js` Asserting elements - `Assertions.js` They all live in the e2e/utils folder. This PR aims to remove and deprecate all test actions within the helper's file. Once this PR is merged, we can avoid engineers using anti-patterns within the e2e framework. The next step is to extract all logic from the testHelpers and place them in the appropriate utility class. ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** ### **Before** ### **After** ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --- e2e/helpers.js | 137 +++++++++++++++--- e2e/pages/Transactions/ActivitiesView.js | 4 +- e2e/pages/wallet/EditAccountNameView.js | 14 +- .../accounts/change-account-name.spec.js | 25 ++-- e2e/specs/confirmations/send-eth.spec.js | 10 +- .../wallet/edit-recipient-address.spec.js | 13 +- 6 files changed, 151 insertions(+), 52 deletions(-) diff --git a/e2e/helpers.js b/e2e/helpers.js index 5add2ff7db9..3f1672f2358 100644 --- a/e2e/helpers.js +++ b/e2e/helpers.js @@ -9,6 +9,9 @@ import Utilities from './utils/Utilities'; import { resolveConfig } from 'detox/internals'; export default class TestHelpers { + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async waitAndTap(elementId, timeout, index) { await waitFor(element(by.id(elementId))) .toBeVisible() @@ -18,7 +21,9 @@ export default class TestHelpers { .atIndex(index || 0) .tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async waitAndTapText(text, timeout) { await waitFor(element(by.text(text))) .toBeVisible() @@ -26,79 +31,114 @@ export default class TestHelpers { return element(by.text(text)).tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tap(elementId) { return element(by.id(elementId)).tap(); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tapByDescendentTestID(parentElement, ChildElement) { return element( by.id(parentElement).withDescendant(by.id(ChildElement)), ).tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tapByText(text, index) { return element(by.text(text)) .atIndex(index || 0) .tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static doubleTapByText(text, index) { return element(by.text(text)) .atIndex(index || 0) .multiTap(2); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tapAtPoint(elementId, point) { return element(by.id(elementId)).tap(point); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tapItemAtIndex(elementID, index) { return element(by.id(elementID)) .atIndex(index || 0) .tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tapItemAtIndexByLabel(elementID, index) { return element(by.label(elementID, index)) .atIndex(index || 0) .tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async typeText(elementId, text) { await TestHelpers.tap(elementId); return element(by.id(elementId)).typeText(text); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async typeNumbers(elementId, text, submitLabel) { await element(by.id(elementId)).replaceText(text.replace('\n', '')); return element(by.label(submitLabel)).atIndex(0).tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async typeTextAndHideKeyboard(elementId, text) { if (device.getPlatform() === 'android') { await TestHelpers.clearField(elementId); } await TestHelpers.typeText(elementId, text + '\n'); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async clearField(elementId) { return element(by.id(elementId)).replaceText(''); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async tapAndLongPress(elementId) { await TestHelpers.tap(elementId); return element(by.id(elementId)).longPress(2000); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async tapAndLongPressAtIndex(elementId, index) { return element(by.id(elementId)) .atIndex(index || 0) .longPress(2000); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async replaceTextInField(elementId, text) { return element(by.id(elementId)).replaceText(text); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static tapAlertWithButton(text, index) { if (device.getPlatform() === 'android') { @@ -109,6 +149,10 @@ export default class TestHelpers { return element(by.label(text)).atIndex(0).tap(); } + + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async waitAndTapByLabel(text, timeout, index) { await waitFor(element(by.label(text))) .toBeVisible() @@ -118,12 +162,16 @@ export default class TestHelpers { .atIndex(index || 0) .tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async tapWebviewElement(elementId) { // this method only words on android: https://wix.github.io/Detox/docs/api/webviews/ return web.element(by.web.id(elementId)).tap(); } - + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async swipe(elementId, direction, speed, percentage, xStart, yStart) { await element(by.id(elementId)).swipe( direction, @@ -133,18 +181,30 @@ export default class TestHelpers { yStart, ); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async swipeByLabel(elementId, direction, speed, percentage) { await element(by.label(elementId)).swipe(direction, speed, percentage); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async swipeByText(text, direction, speed, percentage) { await element(by.text(text)).atIndex(0).swipe(direction, speed, percentage); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async scrollTo(scrollViewId, edge) { await element(by.id(scrollViewId)).scrollTo(edge); } + /** + * @deprecated Use Guestures Class to accomplish this. + */ static async scrollUpTo(elementId, distance, direction) { await element(by.id(elementId)).scroll(distance, direction); } @@ -161,22 +221,34 @@ export default class TestHelpers { }); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfVisible(elementId) { return await waitFor(element(by.id(elementId))) .toBeVisible() .withTimeout(15000); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfNotVisible(elementId) { return await waitFor(element(by.id(elementId))) .not.toBeVisible() .withTimeout(10000); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfElementWithTextIsNotVisible(text) { return await expect(element(by.text(text)).atIndex(0)).not.toBeVisible(); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfElementNotToHaveText(elementId, text) { await waitFor(element(by.id(elementId))) .toBeVisible() @@ -185,6 +257,9 @@ export default class TestHelpers { return expect(element(by.id(elementId))).not.toHaveText(text); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfExists(elementId) { await waitFor(element(by.id(elementId))) .toBeVisible() @@ -192,6 +267,9 @@ export default class TestHelpers { return expect(element(by.id(elementId))).toExist(); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfHasText(elementId, text) { await waitFor(element(by.id(elementId))) .toBeVisible() @@ -200,25 +278,41 @@ export default class TestHelpers { return expect(element(by.id(elementId))).toHaveText(text); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfElementWithTextIsVisible(text, index) { return await waitFor(element(by.text(text)).atIndex(index || 0)) .toBeVisible() .withTimeout(10000); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfElementByTextIsVisible(text, timeout = 25000) { return await waitFor(element(by.text(text))) .toBeVisible() .withTimeout(timeout); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async checkIfElementHasString(elementID, text) { return expect(element(by.id(elementID))).toString(text); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static checkIfToggleIsOn(elementID) { return expect(element(by.id(elementID))).toHaveToggleValue(true); } + + /** + * @deprecated Use Assertion Class to accomplish this. + */ static checkIfToggleIsOff(elementID) { return expect(element(by.id(elementID))).toHaveToggleValue(false); } @@ -240,6 +334,9 @@ export default class TestHelpers { }); } // Detox has no waits for webview elements visibility. Here is the custom one. + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async waitForWebElementToBeVisibleById(elementId, timeout = 15000) { const start = Date.now(); while (Date.now() - start < timeout) { @@ -253,6 +350,9 @@ export default class TestHelpers { } throw new Error('Element with ' + elementId + ' not found'); } + /** + * @deprecated Use Assertion Class to accomplish this. + */ static async retry(maxAttempts, testLogic) { for (let attempt = 1; attempt <= maxAttempts; attempt++) { @@ -293,7 +393,9 @@ export default class TestHelpers { } static async launchAppForDebugBuild(platform, launchOptions) { - const deepLinkUrl = this.getDeepLinkUrl(this.getDevLauncherPackagerUrl(platform)); + const deepLinkUrl = this.getDeepLinkUrl( + this.getDevLauncherPackagerUrl(platform), + ); if (platform === 'ios') { await device.launchApp(launchOptions); @@ -304,15 +406,16 @@ export default class TestHelpers { return device.launchApp({ url: deepLinkUrl, - ...launchOptions + ...launchOptions, }); } static getDeepLinkUrl(url) { - return `expo-metamask://expo-development-client/?url=${encodeURIComponent(url)}`; + return `expo-metamask://expo-development-client/?url=${encodeURIComponent( + url, + )}`; } - static getDevLauncherPackagerUrl(platform) { return `http://localhost:8081/index.bundle?platform=${platform}&dev=true&minify=false&disableOnboarding=1`; } diff --git a/e2e/pages/Transactions/ActivitiesView.js b/e2e/pages/Transactions/ActivitiesView.js index 3e8a0dfc1e7..2aa41516046 100644 --- a/e2e/pages/Transactions/ActivitiesView.js +++ b/e2e/pages/Transactions/ActivitiesView.js @@ -66,7 +66,9 @@ class ActivitiesView { const element = this.swapActivityTitle(sourceToken, destinationToken); await Gestures.waitAndTap(element); } - + async tapConfirmedTransaction() { + await Gestures.waitAndTap(this.confirmedLabel); + } async swipeDown() { await Gestures.swipe(this.container, 'down', 'slow', 0.5); } diff --git a/e2e/pages/wallet/EditAccountNameView.js b/e2e/pages/wallet/EditAccountNameView.js index eb3b23db8fa..10cf9e98f96 100644 --- a/e2e/pages/wallet/EditAccountNameView.js +++ b/e2e/pages/wallet/EditAccountNameView.js @@ -4,16 +4,24 @@ import { EditAccountNameSelectorIDs } from '../../selectors/wallet/EditAccountNa class EditAccountNameView { get saveButton() { - return Matchers.getElementByID(EditAccountNameSelectorIDs.EDIT_ACCOUNT_NAME_SAVE); + return Matchers.getElementByID( + EditAccountNameSelectorIDs.EDIT_ACCOUNT_NAME_SAVE, + ); } - get accountNameInput() { - return Matchers.getElementByID(EditAccountNameSelectorIDs.ACCOUNT_NAME_INPUT); + return Matchers.getElementByID( + EditAccountNameSelectorIDs.ACCOUNT_NAME_INPUT, + ); } async tapSave() { await Gestures.waitAndTap(this.saveButton); } + + async updateAccountName(accountName) { + await Gestures.clearField(EditAccountNameView.accountNameInput); + await Gestures.typeTextAndHideKeyboard(this.accountNameInput, accountName); + } } export default new EditAccountNameView(); diff --git a/e2e/specs/accounts/change-account-name.spec.js b/e2e/specs/accounts/change-account-name.spec.js index 179ba7039d5..995f351dfd2 100644 --- a/e2e/specs/accounts/change-account-name.spec.js +++ b/e2e/specs/accounts/change-account-name.spec.js @@ -7,20 +7,18 @@ import { stopFixtureServer, defaultGanacheOptions, } from '../../fixtures/fixture-helper'; -import TestHelpers from '../../helpers'; import FixtureServer from '../../fixtures/fixture-server'; import { getFixturesServerPort } from '../../fixtures/utils'; import { Regression } from '../../tags.js'; import WalletView from '../../pages/wallet/WalletView'; import AccountActionsBottomSheet from '../../pages/wallet/AccountActionsBottomSheet'; import EditAccountNameView from '../../pages/wallet/EditAccountNameView'; -import { EditAccountNameSelectorIDs } from '../../selectors/wallet/EditAccountName.selectors'; -import Gestures from '../../utils/Gestures'; import Assertions from '../../utils/Assertions'; import TabBarComponent from '../../pages/wallet/TabBarComponent'; import SettingsView from '../../pages/Settings/SettingsView'; import LoginView from '../../pages/wallet/LoginView'; import AccountListBottomSheet from '../../pages/wallet/AccountListBottomSheet'; +import TestHelpers from '../../helpers'; const fixtureServer = new FixtureServer(); const NEW_ACCOUNT_NAME = 'Edited Name'; @@ -52,13 +50,11 @@ describe(Regression('Change Account Name'), () => { // Open account actions and edit account name await TabBarComponent.tapWallet(); await WalletView.tapIdenticon(); - await AccountListBottomSheet.tapEditAccountActionsAtIndex(MAIN_ACCOUNT_INDEX); - await AccountActionsBottomSheet.tapEditAccount(); - await Gestures.clearField(EditAccountNameView.accountNameInput); - await TestHelpers.typeTextAndHideKeyboard( - EditAccountNameSelectorIDs.ACCOUNT_NAME_INPUT, - NEW_ACCOUNT_NAME, + await AccountListBottomSheet.tapEditAccountActionsAtIndex( + MAIN_ACCOUNT_INDEX, ); + await AccountActionsBottomSheet.tapEditAccount(); + await EditAccountNameView.updateAccountName(NEW_ACCOUNT_NAME); await EditAccountNameView.tapSave(); // Verify updated name @@ -92,13 +88,12 @@ describe(Regression('Change Account Name'), () => { // Edit imported account name await WalletView.tapIdenticon(); - await AccountListBottomSheet.tapEditAccountActionsAtIndex(IMPORTED_ACCOUNT_INDEX); - await AccountActionsBottomSheet.tapEditAccount(); - await Gestures.clearField(EditAccountNameView.accountNameInput); - await TestHelpers.typeTextAndHideKeyboard( - EditAccountNameSelectorIDs.ACCOUNT_NAME_INPUT, - NEW_IMPORTED_ACCOUNT_NAME, + await AccountListBottomSheet.tapEditAccountActionsAtIndex( + IMPORTED_ACCOUNT_INDEX, ); + await AccountActionsBottomSheet.tapEditAccount(); + + await EditAccountNameView.updateAccountName(NEW_IMPORTED_ACCOUNT_NAME); await EditAccountNameView.tapSave(); // Verify updated name diff --git a/e2e/specs/confirmations/send-eth.spec.js b/e2e/specs/confirmations/send-eth.spec.js index c94eff9f852..745f77490c9 100644 --- a/e2e/specs/confirmations/send-eth.spec.js +++ b/e2e/specs/confirmations/send-eth.spec.js @@ -16,6 +16,7 @@ import { defaultGanacheOptions, } from '../../fixtures/fixture-helper'; import { SMART_CONTRACTS } from '../../../app/util/test/smart-contracts'; +import Assertions from '../../utils/Assertions'; describe(SmokeConfirmations('Send ETH'), () => { const TOKEN_NAME = enContent.unit.eth; @@ -49,9 +50,7 @@ describe(SmokeConfirmations('Send ETH'), () => { await TransactionConfirmationView.tapConfirmButton(); await TabBarComponent.tapActivity(); - await TestHelpers.checkIfElementByTextIsVisible( - `${AMOUNT} ${TOKEN_NAME}`, - ); + await Assertions.checkIfTextIsDisplayed(`${AMOUNT} ${TOKEN_NAME}`); }, ); }); @@ -83,10 +82,7 @@ describe(SmokeConfirmations('Send ETH'), () => { await TransactionConfirmationView.tapConfirmButton(); await TabBarComponent.tapActivity(); - - await TestHelpers.checkIfElementByTextIsVisible( - `${AMOUNT} ${TOKEN_NAME}`, - ); + await Assertions.checkIfTextIsDisplayed(`${AMOUNT} ${TOKEN_NAME}`); }, ); }); diff --git a/e2e/specs/wallet/edit-recipient-address.spec.js b/e2e/specs/wallet/edit-recipient-address.spec.js index 6693192e3e1..047c215e794 100644 --- a/e2e/specs/wallet/edit-recipient-address.spec.js +++ b/e2e/specs/wallet/edit-recipient-address.spec.js @@ -16,12 +16,9 @@ import { } from '../../fixtures/fixture-helper'; import FixtureServer from '../../fixtures/fixture-server'; import FixtureBuilder from '../../fixtures/fixture-builder'; -import Gestures from '../../utils/Gestures'; -import { - ActivitiesViewSelectorsText, - sentMessageTokenIDs, -} from '../../selectors/Transactions/ActivitiesView.selectors'; -import { contractConfiguration } from '../../../app/util/test/smart-contracts'; + +import ActivitiesView from '../../pages/Transactions/ActivitiesView'; + const INCORRECT_SEND_ADDRESS = '0xebe6CcB6B55e1d094d9c58980Bc10Fed69932cAb'; const CORRECT_SEND_ADDRESS = '0x37cc5ef6bfe753aeaf81f945efe88134b238face'; @@ -90,9 +87,7 @@ describe( // Transactions view to assert address remains consistent await TabBarComponent.tapActivity(); await TestHelpers.delay(3000); - await TestHelpers.tapByText( - ActivitiesViewSelectorsText.CONFIRM_TEXT, - ); + await ActivitiesView.tapConfirmedTransaction(); await Assertions.checkIfTextIsDisplayed(`${SHORTHAND_ADDRESS}`); } },