From eeb676bac0029b247c8e0d3cb2aeb07737a8a8b4 Mon Sep 17 00:00:00 2001 From: Nick Gambino <35090461+gambinish@users.noreply.github.com> Date: Thu, 4 Dec 2025 00:09:56 -0800 Subject: [PATCH 1/3] fix: Disappearing candles on Perps chart cp-7.61.0 (#23632) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** There was a caching conflict in the `CandleStreamChannel` between the `usePerpsMarketStats` and `usePerpsLiveCandles` hooks on the asset details page. `usePerpsMarketStats` always subscribes to 1-hour candles, regardless of what interval the chart is displaying. This is because it calculates 24-hour high/low and volume, which requires 24 one-hour candles. However, this would cause a conflict in the cache key that would also cause the chart to render only 24 candles, rather than the full 500 in data set. This was only apparent for the `1h` duration: When viewing 15m chart: Stats subscribes to BTC-1h (24 candles) Chart subscribes to BTC-15m (500 candles) Different cache keys, no conflict When viewing 4h chart: Stats subscribes to BTC-1h (24 candles) Chart subscribes to BTC-4h (500 candles) Different cache keys, no conflict When viewing 1h chart: Stats subscribes to BTC-1h (24 candles) ← first Chart subscribes to BTC-1h (500 candles) ← same key! Same cache key, conflict The 1h interval was the only one where both components competed for the same `coin-interval` cache entry. Causing the chart to not render the full data set. The solution I opted for was to simply always fetch 500 candles. The component that needs less data can just use what it needs from the larger dataset. This way, everyone gets enough data regardless of subscription order. The performance to fetch this amount of data seems negligible. ## **Changelog** CHANGELOG entry: fix 1h candle period on perps chart not displaying full dataset ## **Related issues** Fixes: https://consensyssoftware.atlassian.net/browse/TAT-2197 ## **Manual testing steps** ```gherkin Feature: Perps chart candle display Scenario: user views 1h candles on market chart Given user is on the market details view When user selects the 1h candle period Then the chart displays several weeks of historical candles ``` ## **Screenshots/Recordings** https://github.com/user-attachments/assets/9c5e9bae-85ad-43a7-85b9-81f7dc33ed55 ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I’ve included tests if applicable - [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [x] 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. --- > [!NOTE] > Force `CandleStreamChannel` to always fetch max candles (`YEAR_TO_DATE`) and ignore requested duration; update tests to validate shared connections and caching. > > - **Perps Candle Streaming**: > - Always use `TimeDuration.YEAR_TO_DATE` when subscribing in `CandleStreamChannel` to fetch max candles (500), ignoring requested `duration`. > - Simplify API: remove `duration` from internal `connect(...)` and from subscriber handling; cache key remains `coin-interval`. > - Maintain shared WebSocket per `coin+interval`; disconnect when no subscribers remain; unchanged throttling/cache behaviors. > - **Tests** (`app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts`): > - Add specs ensuring `YEAR_TO_DATE` is always used, connections are shared across different requested durations, and second subscribers receive cached data immediately. > - Existing tests continue to validate caching, throttling, pause/resume, disconnect, and historical fetch/merge behavior. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 3856ceff7d8d9a5cb2f921fd72bbfb8a2ecfce8e. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- .../channels/CandleStreamChannel.test.ts | 76 +++++++++++++++++++ .../providers/channels/CandleStreamChannel.ts | 24 +++--- 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts b/app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts index fc132c8e672..af81e1cd2c1 100644 --- a/app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts +++ b/app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts @@ -981,4 +981,80 @@ describe('CandleStreamChannel', () => { expect(subscriber).not.toHaveBeenCalled(); }); }); + + describe('Always Uses Maximum Duration', () => { + it('always uses YEAR_TO_DATE duration when subscribing regardless of requested duration', () => { + mockSubscribeToCandles.mockImplementation(({ duration }) => { + // Verify YEAR_TO_DATE is always used + expect(duration).toBe(TimeDuration.YEAR_TO_DATE); + return jest.fn(); + }); + + // Subscribe with ONE_DAY - should still use YEAR_TO_DATE internally + channel.subscribe({ + coin: 'BTC', + interval: CandlePeriod.ONE_HOUR, + duration: TimeDuration.ONE_DAY, + callback: jest.fn(), + }); + + expect(mockSubscribeToCandles).toHaveBeenCalledTimes(1); + }); + + it('shares WebSocket connection when subscribers use different durations for same coin+interval', () => { + mockSubscribeToCandles.mockReturnValue(jest.fn()); + + // First subscriber with ONE_DAY + channel.subscribe({ + coin: 'BTC', + interval: CandlePeriod.ONE_HOUR, + duration: TimeDuration.ONE_DAY, + callback: jest.fn(), + }); + + // Second subscriber with YEAR_TO_DATE (different duration, same coin+interval) + channel.subscribe({ + coin: 'BTC', + interval: CandlePeriod.ONE_HOUR, + duration: TimeDuration.YEAR_TO_DATE, + callback: jest.fn(), + }); + + // WebSocket subscription created only once (connection is shared) + expect(mockSubscribeToCandles).toHaveBeenCalledTimes(1); + }); + + it('second subscriber receives cached data immediately', () => { + let capturedCallback: ((data: CandleData) => void) | undefined; + const firstCallback = jest.fn(); + const secondCallback = jest.fn(); + + mockSubscribeToCandles.mockImplementation(({ callback }) => { + capturedCallback = callback; + return jest.fn(); + }); + + // First subscriber + channel.subscribe({ + coin: 'BTC', + interval: CandlePeriod.ONE_HOUR, + duration: TimeDuration.YEAR_TO_DATE, + callback: firstCallback, + }); + + // Simulate initial data load + capturedCallback?.(mockCandleData); + + // Second subscriber + channel.subscribe({ + coin: 'BTC', + interval: CandlePeriod.ONE_HOUR, + duration: TimeDuration.ONE_DAY, + callback: secondCallback, + }); + + // Second callback receives cached data immediately + expect(secondCallback).toHaveBeenCalledWith(mockCandleData); + }); + }); }); diff --git a/app/components/UI/Perps/providers/channels/CandleStreamChannel.ts b/app/components/UI/Perps/providers/channels/CandleStreamChannel.ts index 9c70076fc06..8d1b818a18d 100644 --- a/app/components/UI/Perps/providers/channels/CandleStreamChannel.ts +++ b/app/components/UI/Perps/providers/channels/CandleStreamChannel.ts @@ -113,7 +113,9 @@ export class CandleStreamChannel extends StreamChannel { } /** - * Subscribe to candle updates for a specific coin and interval + * Subscribe to candle updates for a specific coin and interval. + * Note: The duration parameter is accepted for API compatibility but ignored - + * we always fetch maximum candles (YEAR_TO_DATE = 500) to avoid complex caching issues. */ public subscribe(params: { coin: string; @@ -123,13 +125,13 @@ export class CandleStreamChannel extends StreamChannel { throttleMs?: number; onError?: (error: Error) => void; }): () => void { - const { coin, interval, duration, callback, throttleMs, onError } = params; + const { coin, interval, callback, throttleMs, onError } = params; const cacheKey = this.getCacheKey(coin, interval); const id = Math.random().toString(36); const subscription: StreamSubscription = { id, - cacheKey, // Store cacheKey to filter subscribers by their subscription + cacheKey, callback, throttleMs, hasReceivedFirstUpdate: false, @@ -145,7 +147,7 @@ export class CandleStreamChannel extends StreamChannel { } // Ensure WebSocket connected for this coin+interval - this.connect(coin, interval, duration, cacheKey); + this.connect(coin, interval, cacheKey); // Return unsubscribe function return () => { @@ -165,9 +167,7 @@ export class CandleStreamChannel extends StreamChannel { if (remainingForThisKey === 0) { DevLogger.log( 'CandleStreamChannel: Disconnecting WebSocket (no subscribers)', - { - cacheKey, - }, + { cacheKey }, ); this.disconnect(cacheKey); } @@ -175,12 +175,13 @@ export class CandleStreamChannel extends StreamChannel { } /** - * Connect to WebSocket for specific coin and interval + * Connect to WebSocket for specific coin and interval. + * Always uses YEAR_TO_DATE duration to fetch maximum candles (500) on initial load. + * This ensures all subscribers get the full dataset regardless of their individual duration needs. */ private connect( coin: string, interval: CandlePeriod, - duration: TimeDuration, cacheKey: string, ): void { // Skip if already connected for this coin+interval @@ -198,17 +199,18 @@ export class CandleStreamChannel extends StreamChannel { // Only reconnect if subscribers still exist and no connection established if (hasActiveSubscribers && !this.wsSubscriptions.has(cacheKey)) { - this.connect(coin, interval, duration, cacheKey); + this.connect(coin, interval, cacheKey); } }, PERPS_CONSTANTS.RECONNECTION_CLEANUP_DELAY_MS); return; } // Subscribe to candle updates via controller + // Always use YEAR_TO_DATE to fetch maximum candles (500) regardless of subscriber's duration const unsubscribe = Engine.context.PerpsController.subscribeToCandles({ coin, interval, - duration, + duration: TimeDuration.YEAR_TO_DATE, // Always fetch max candles callback: (candleData: CandleData) => { // Update cache this.cache.set(cacheKey, candleData); From fa5f8bcc1ed2b823a931043ad388025386d74495 Mon Sep 17 00:00:00 2001 From: javiergarciavera <76975121+javiergarciavera@users.noreply.github.com> Date: Thu, 4 Dec 2025 09:59:55 +0100 Subject: [PATCH 2/3] test: Fix for performance tests (#22606) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** this is a PR that fixes: performance build issues test changes according to the new UI test flakiness fixes appwright patch to improve some DOM issues we had finding locators due to nested components ## **Changelog** CHANGELOG entry: ## **Related issues** Fixes: ## **Manual testing steps** ```gherkin Feature: my feature name Scenario: user [verb for user action] Given [describe expected initial app state] When user [verb for user action] Then [describe expected outcome] ``` ## **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. --- > [!NOTE] > Patches Appwright (locators, BrowserStack settings) and refactors performance tests/screens with new selectors, utilities, and modal handling to reduce flakiness and match the updated UI. > > - **Appwright (patched via Yarn)**: > - Lower retries/workers in default config; adjust element lookup to skip text matching; minor logging. > - BrowserStack: add/update capabilities (networkProfile, selfHeal, appProfiling), env‑driven buildName, Appium settings, optional video download via `DISABLE_VIDEO_DOWNLOAD`; apply `updateSettings` on session start. > - Wire patch in `package.json`/`yarn.lock`. > - **Performance tests (Appwright)**: > - Update login/onboarding/send/swap/asset view flows to new UI; tweak timers; change tokens (e.g., `USDC`, `LINK`). > - Add `dissmissPredictionsModal` and use across scenarios; skip perps onboarding test. > - Simplify fixture cleanup; always attach metrics; remove orphan timer recovery/fallback. > - **Utilities**: > - New `appwright/utils/Utils.splitAmountIntoDigits`; refactor amount entry in `AmountScreen`, `SwapScreen`, `BridgeScreen`, `PerpsDepositScreen`. > - **Screen objects**: > - Numerous selector/tap fixes (IDs/text/XPath), iOS exact ID matching, improved visibility timeouts, scroll-into-view, and modal buttons (e.g., Rewards, Login, Networks, Wallet actions). > - **Config and tooling**: > - Appwright project configs unchanged functionally (comment cleanup); extend `.depcheckrc.yml` ignores for Appwright/Appium; minor test config/timeouts. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit e34ae38cec8f1df22021c24cf74b8102f523ecc6. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --------- Co-authored-by: metamaskbot Co-authored-by: sethkfman <10342624+sethkfman@users.noreply.github.com> --- .depcheckrc.yml | 1 + .../appwright-npm-0.1.45-f282bc1c1b.patch | 245 ++++++++---------- .../patches/appwright-patch-bd36f2d657.patch | 26 ++ appwright/appwright.config.ts | 12 +- appwright/fixtures/performance-test.js | 71 ----- .../performance/login/asset-view.spec.js | 11 +- .../login/cross-chain-swap-flow.spec.js | 4 +- .../performance/login/eth-swap-flow.spec.js | 2 - .../login/import-multiple-srps.spec.js | 13 +- .../performance/login/send-flows.spec.js | 7 +- .../onboarding/import-wallet.spec.js | 10 +- .../imported-wallet-account-creation.spec.js | 13 +- .../cold-start-after-wallet-import.spec.js | 21 +- .../new-wallet-account-creation.spec.js | 23 +- .../onboarding/perps-onboarding.spec.js | 14 +- appwright/utils/Flows.js | 43 +-- appwright/utils/TestConstants.js | 1 + appwright/utils/Utils.js | 9 + e2e/framework/AppwrightSelectors.ts | 3 +- package.json | 2 +- wdio/screen-objects/AccountListComponent.js | 10 +- wdio/screen-objects/AmountScreen.js | 11 +- wdio/screen-objects/BridgeScreen.js | 102 ++------ wdio/screen-objects/LoginScreen.js | 18 +- wdio/screen-objects/Modals/RewardsGTMModal.js | 7 +- wdio/screen-objects/Modals/TabBarModal.js | 16 +- .../Modals/WalletActionModal.js | 22 +- wdio/screen-objects/NetworksScreen.js | 40 +-- .../Onboarding/CreateNewWalletScreen.js | 5 +- .../Onboarding/CreatePasswordScreen.js | 5 +- .../Onboarding/ImportFromSeedScreen.js | 30 ++- .../Onboarding/MetaMetricsScreen.js | 2 +- .../Onboarding/OnboardingScreen.js | 4 +- .../Onboarding/OnboardingSheet.js | 2 +- wdio/screen-objects/PerpsDepositScreen.js | 13 +- wdio/screen-objects/PerpsTabView.js | 2 +- wdio/screen-objects/PerpsTutorialScreen.js | 11 +- wdio/screen-objects/PredictDetailsScreen.js | 9 +- wdio/screen-objects/SendScreen.js | 3 +- wdio/screen-objects/SwapScreen.js | 12 +- wdio/screen-objects/WalletMainScreen.js | 14 +- yarn.lock | 8 +- 42 files changed, 370 insertions(+), 507 deletions(-) create mode 100644 .yarn/patches/appwright-patch-bd36f2d657.patch create mode 100644 appwright/utils/Utils.js diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 6084d59a6b3..8bc400aa92a 100644 --- a/.depcheckrc.yml +++ b/.depcheckrc.yml @@ -1,4 +1,5 @@ # List things here that *are - 'used, that depcheck is wrong about' + ignores: - '@metamask/oss-attribution-generator' - '@metamask/test-dapp-multichain' diff --git a/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch b/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch index f49973b123a..7bc12aad2a9 100644 --- a/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch +++ b/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch @@ -13,11 +13,107 @@ index a51913b3f9380fd5740b1d52cd1d3472e29233c3..6479c9b92ed17319f654e9323f689792 reporter: [["list"], ["html", { open: "always" }]], use: { // TODO: Use this for actions +diff --git a/dist/device/index.js b/dist/device/index.js +index fda54b682837d06eb579596087826c862c35f33e..b61aceca45b8b3293dee14552c26f002b38a4e36 100644 +--- a/dist/device/index.js ++++ b/dist/device/index.js +@@ -237,7 +237,7 @@ let Device = (() => { + findStrategy: isAndroid + ? "-android uiautomator" + : "-ios predicate string", +- textToMatch: text, ++ textToMatch: null, + }); + } + /** +diff --git a/dist/locator/index.js b/dist/locator/index.js +index 03f00574615d647d1887440ee7ae10798934c250..40089ce61ad598625afeb42d1149da8ce007dc26 100644 +--- a/dist/locator/index.js ++++ b/dist/locator/index.js +@@ -244,51 +244,33 @@ let Locator = (() => { + ]); + } + } +- /** +- * Retrieves the element reference based on the `selector`. +- * +- * @returns +- */ ++ + async getElement() { +- /** +- * Determine whether `path` is a regex or string, and find elements accordingly. +- * +- * If `path` is a regex: +- * - Iterate through all the elements on the page +- * - Extract text content of each element +- * - Return the first matching element +- * +- * If `path` is a string: +- * - Use `findStrategy` (either XPath, Android UIAutomator, or iOS predicate string) to find elements +- * - Apply regex to clean extra characters from the matched element’s text +- * - Return the first element that matches +- */ +- let elements = await this.webDriverClient.findElements(this.findStrategy, this.selector); +- // If there is only one element, return it +- if (elements.length === 1) { +- return elements[0]; +- } +- // If there are multiple elements, we reverse the order since the probability +- // of finding the element is higher at higher depth ++ const elements = await this.webDriverClient.findElements(this.findStrategy, this.selector); + const reversedElements = elements.reverse(); ++ console.log('Reversed elements length', reversedElements.length); ++ // If no text matching is needed, just return the first (deepest) element ++ if (!this.textToMatch) { ++ console.log('No text to match, returning first element'); ++ return reversedElements[0] || null; ++ } ++ ++ // Only iterate and fetch text if we need to match text + for (const element of reversedElements) { + let elementText = await this.webDriverClient.getElementText(element["element-6066-11e4-a52e-4f735466cecf"]); +- if (this.textToMatch) { +- if (this.textToMatch instanceof RegExp && +- this.textToMatch.test(elementText)) { +- return element; +- } +- if (typeof this.textToMatch === "string" && +- elementText.includes(this.textToMatch)) { +- return element; +- } ++ console.log('Element text', elementText); ++ console.log('Text to match', this.textToMatch); ++ ++ if (this.textToMatch instanceof RegExp && ++ this.textToMatch.test(elementText)) { ++ return element; + } +- else { +- // This is returned for cases where xpath is findStrategy and we want +- // to return the last element found in the list ++ if (typeof this.textToMatch === "string" && ++ elementText.includes(this.textToMatch)) { + return element; + } + } ++ + return null; + } + }; diff --git a/dist/providers/browserstack/index.js b/dist/providers/browserstack/index.js -index 638c5df686413ffe050ef7e7329b9e9446e98002..61ecd1508400f59302d6139a805e7a912e9a1036 100644 +index 638c5df686413ffe050ef7e7329b9e9446e98002..34e211940e22cc195002e6d88a98d7b8488d88d0 100644 --- a/dist/providers/browserstack/index.js +++ b/dist/providers/browserstack/index.js -@@ -120,6 +120,12 @@ class BrowserStackDeviceProvider { +@@ -104,6 +104,7 @@ class BrowserStackDeviceProvider { + async createDriver(config) { + const WebDriver = (await import("webdriver")).default; + const webDriverClient = await WebDriver.newSession(config); ++ + this.sessionId = webDriverClient.sessionId; + const bundleId = await this.getAppBundleIdFromSession(); + const testOptions = { +@@ -120,6 +121,12 @@ class BrowserStackDeviceProvider { return this.sessionDetails?.app_details.app_name ?? ""; } static async downloadVideo(sessionId, outputDir, fileName) { @@ -30,7 +126,7 @@ index 638c5df686413ffe050ef7e7329b9e9446e98002..61ecd1508400f59302d6139a805e7a91 const sessionData = await getSessionDetails(sessionId); const sessionDetails = sessionData?.automation_session; const videoURL = sessionDetails?.video_url; -@@ -241,7 +247,10 @@ class BrowserStackDeviceProvider { +@@ -241,7 +248,10 @@ class BrowserStackDeviceProvider { capabilities: { "bstack:options": { debug: true, @@ -41,7 +137,7 @@ index 638c5df686413ffe050ef7e7329b9e9446e98002..61ecd1508400f59302d6139a805e7a91 networkLogs: true, appiumVersion: "2.6.0", enableCameraImageInjection: this.project.use.device?.enableCameraImageInjection, -@@ -250,10 +259,10 @@ class BrowserStackDeviceProvider { +@@ -250,10 +260,10 @@ class BrowserStackDeviceProvider { osVersion: this.project.use.device.osVersion, platformName: platformName, deviceOrientation: this.project.use.device?.orientation, @@ -54,144 +150,21 @@ index 638c5df686413ffe050ef7e7329b9e9446e98002..61ecd1508400f59302d6139a805e7a91 : process.env.USER, }, "appium:autoGrantPermissions": true, -@@ -261,6 +270,11 @@ class BrowserStackDeviceProvider { +@@ -261,6 +271,17 @@ class BrowserStackDeviceProvider { "appium:autoAcceptAlerts": true, "appium:fullReset": true, "appium:settings[snapshotMaxDepth]": 62, + "appium:settings[autoGrantPermissions]": true, -+ "appium:settings[waitForIdleTimeout]": 10000, ++ "appium:settings[waitForIdleTimeout]": 2000, + "appium:settings[actionAcknowledgmentTimeout]": 3000, + "appium:settings[ignoreUnimportantViews]": true, -+ "appium:settings[waitForSelectorTimeout]": 10000, ++ "appium:settings[waitForSelectorTimeout]": 1000, ++ "appium:bstackPageSource": { ++ "enable": true, ++ "samplesX": 15, ++ "samplesY": 15, ++ "maxDepth": 75 ++ } }, }; } -diff --git a/dist/reporter.js b/dist/reporter.js -index 516da403677d49af5fbb18edaf9f308dad401e55..ae1895be0513e2aa3ada45ad93a3f3c489e42911 100644 ---- a/dist/reporter.js -+++ b/dist/reporter.js -@@ -6,8 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); - const providers_1 = require("./providers"); - const fs_1 = __importDefault(require("fs")); - const path_1 = __importDefault(require("path")); --const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg")); --const ffmpeg_1 = __importDefault(require("@ffmpeg-installer/ffmpeg")); -+// const fluent_ffmpeg_1 = __importDefault(require("fluent-ffmpeg")); -+// const ffmpeg_1 = __importDefault(require("@ffmpeg-installer/ffmpeg")); - const logger_1 = require("./logger"); - const utils_1 = require("./utils"); - const workerInfo_1 = require("./fixture/workerInfo"); -@@ -183,25 +183,32 @@ function trimVideo({ originalVideoPath, startSecs, durationSecs, outputPath, }) - fs_1.default.copyFileSync(originalVideoPath, copyFullPath); - return new Promise((resolve, reject) => { - let stdErrs = ""; -- (0, fluent_ffmpeg_1.default)(copyFullPath) -- .setFfmpegPath(ffmpeg_1.default.path) -- .setStartTime(startSecs) -- .setDuration(durationSecs) -- .output(fullOutputPath) -- .on("end", () => { -- logger_1.logger.log(`Trimmed video saved at: ${fullOutputPath}`); -- fs_1.default.unlinkSync(copyFullPath); -- resolve(fullOutputPath); -- }) -- .on("stderr", (stderrLine) => { -- stdErrs += stderrLine + "\n"; -- }) -- .on("error", (err) => { -- logger_1.logger.error("ffmpeg error:", err); -- logger_1.logger.error("ffmpeg stderr:", stdErrs); -- reject(err); -- }) -- .run(); -+ // Video trimming disabled - ffmpeg removed -+ // (0, fluent_ffmpeg_1.default)(copyFullPath) -+ // .setFfmpegPath(ffmpeg_1.default.path) -+ // .setStartTime(startSecs) -+ // .setDuration(durationSecs) -+ // .output(fullOutputPath) -+ // .on("end", () => { -+ // logger_1.logger.log(`Trimmed video saved at: ${fullOutputPath}`); -+ // fs_1.default.unlinkSync(copyFullPath); -+ // resolve(fullOutputPath); -+ // }) -+ // .on("stderr", (stderrLine) => { -+ // stdErrs += stderrLine + "\n"; -+ // }) -+ // .on("error", (err) => { -+ // logger_1.logger.error("ffmpeg error:", err); -+ // logger_1.logger.error("ffmpeg stderr:", stdErrs); -+ // reject(err); -+ // }) -+ // .run(); -+ -+ // Just copy the original video without trimming -+ fs_1.default.copyFileSync(copyFullPath, fullOutputPath); -+ fs_1.default.unlinkSync(copyFullPath); -+ logger_1.logger.log(`Video copied without trimming: ${fullOutputPath}`); -+ resolve(fullOutputPath); - }); - } - async function getWorkerStartTime(idx) { -diff --git a/dist/vision/index.js b/dist/vision/index.js -index bf485308fe5ddde81b396bf87dbb365cc37efa0e..8e5f191e6b210c5839257bd6a1879b1f9ce45228 100644 ---- a/dist/vision/index.js -+++ b/dist/vision/index.js -@@ -38,8 +38,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) { - }; - Object.defineProperty(exports, "__esModule", { value: true }); - exports.VisionProvider = void 0; --const vision_1 = require("@empiricalrun/llm/vision"); --const point_1 = require("@empiricalrun/llm/vision/point"); -+// const vision_1 = require("@empiricalrun/llm/vision"); -+// const point_1 = require("@empiricalrun/llm/vision/point"); - const fs_1 = __importDefault(require("fs")); - const test_1 = __importDefault(require("@playwright/test")); - const utils_1 = require("../utils"); -@@ -64,42 +64,12 @@ let VisionProvider = (() => { - this.webDriverClient = webDriverClient; - } - async query(prompt, options) { -- test_1.default.skip(!process.env.OPENAI_API_KEY, "LLM vision based extract text is not enabled. Set the OPENAI_API_KEY environment variable to enable it"); -- let base64Screenshot = options?.screenshot; -- if (!base64Screenshot) { -- base64Screenshot = await this.webDriverClient.takeScreenshot(); -- } -- return await (0, vision_1.query)(base64Screenshot, prompt, options); -+ test_1.default.skip(true, "LLM vision based extract text is disabled - @empiricalrun/llm removed"); -+ throw new Error("LLM vision functionality has been disabled"); - } - async tap(prompt, options) { -- test_1.default.skip(!process.env.EMPIRICAL_API_KEY, "LLM vision based tap is not enabled. Set the EMPIRICAL_API_KEY environment variable to enable it"); -- const base64Image = await this.webDriverClient.takeScreenshot(); -- const coordinates = await (0, point_1.getCoordinatesFor)(prompt, base64Image, options); -- if (coordinates.annotatedImage) { -- const random = Math.floor(1000 + Math.random() * 9000); -- const file = test_1.default.info().outputPath(`${random}.png`); -- await fs_1.default.promises.writeFile(file, Buffer.from(coordinates.annotatedImage, "base64")); -- await test_1.default.info().attach(`${random}`, { path: file }); -- } -- const driverSize = await this.webDriverClient.getWindowRect(); -- const { container: imageSize, x, y } = coordinates; -- const scaleFactorWidth = imageSize.width / driverSize.width; -- const scaleFactorHeight = imageSize.height / driverSize.height; -- if (scaleFactorWidth !== scaleFactorHeight) { -- logger_1.logger.warn(`Scale factors are different: ${scaleFactorWidth} vs ${scaleFactorHeight}`); -- } -- const tapTargetX = x / scaleFactorWidth; -- // This uses the width scale factor because getWindowRect on LambdaTest returns a smaller -- // height value than the screenshot height, which causes disproportionate scaling -- // for width and height. -- // For example, Pixel 8 screenshot is 1080 (w) x 2400 (h), but LambdaTest returns -- // 1080 (w) x 2142 (h) for getWindowRect. -- const tapTargetY = y / scaleFactorWidth; -- await this.device.tap({ -- x: tapTargetX, -- y: tapTargetY, -- }); -- return { x: tapTargetX, y: tapTargetY }; -+ test_1.default.skip(true, "LLM vision based tap is disabled - @empiricalrun/llm removed"); -+ throw new Error("LLM vision functionality has been disabled"); - } - }; - })(); diff --git a/.yarn/patches/appwright-patch-bd36f2d657.patch b/.yarn/patches/appwright-patch-bd36f2d657.patch new file mode 100644 index 00000000000..024574d7338 --- /dev/null +++ b/.yarn/patches/appwright-patch-bd36f2d657.patch @@ -0,0 +1,26 @@ +diff --git a/dist/providers/browserstack/index.js b/dist/providers/browserstack/index.js +index 61ecd1508400f59302d6139a805e7a912e9a1036..c7772bec15413478dd0d446f7c9f805e16b00851 100644 +--- a/dist/providers/browserstack/index.js ++++ b/dist/providers/browserstack/index.js +@@ -104,6 +104,21 @@ class BrowserStackDeviceProvider { + async createDriver(config) { + const WebDriver = (await import("webdriver")).default; + const webDriverClient = await WebDriver.newSession(config); ++ await webDriverClient.updateSettings({ ++ waitForIdleTimeout: 100, ++ waitForSelectorTimeout: 0, ++ shouldWaitForQuiescence: false, ++ snapshotMaxDepth: 62, ++ snapshotTimeout: 50000, ++ "appium:settings[snapshotMaxDepth]": 62, ++ "appium:settings[customSnapshotTimeout]": 50000, ++ "appium:bstackPageSource": { ++ "enable": true, ++ "samplesX": 15, ++ "samplesY": 15, ++ "maxDepth": 100 ++ } ++ }); + this.sessionId = webDriverClient.sessionId; + const bundleId = await this.getAppBundleIdFromSession(); + const testOptions = { diff --git a/appwright/appwright.config.ts b/appwright/appwright.config.ts index b13ab79a7ac..f426c3b3104 100644 --- a/appwright/appwright.config.ts +++ b/appwright/appwright.config.ts @@ -29,7 +29,7 @@ export default defineConfig({ osVersion: '14', // this can be changed to your emulator version }, buildPath: 'PATH-TO-BUILD', // Path to your .apk file - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + expectTimeout: 30 * 1000, }, }, { @@ -42,7 +42,7 @@ export default defineConfig({ osVersion: '16.0', // this can be changed to your simulator version }, buildPath: 'PATH-TO-BUILD', // Path to your .app file - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + expectTimeout: 30 * 1000, }, }, { @@ -56,7 +56,7 @@ export default defineConfig({ osVersion: process.env.BROWSERSTACK_OS_VERSION || '13.0', // this can changed }, buildPath: process.env.BROWSERSTACK_ANDROID_APP_URL, // Path to Browserstack url - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + expectTimeout: 30 * 1000, }, }, { @@ -70,7 +70,7 @@ export default defineConfig({ osVersion: process.env.BROWSERSTACK_OS_VERSION || '16.0', }, buildPath: process.env.BROWSERSTACK_IOS_APP_URL, - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + expectTimeout: 30 * 1000, }, }, { @@ -84,7 +84,7 @@ export default defineConfig({ osVersion: process.env.BROWSERSTACK_OS_VERSION || '13.0', }, buildPath: process.env.BROWSERSTACK_ANDROID_CLEAN_APP_URL, - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + expectTimeout: 30 * 1000, }, }, { @@ -98,7 +98,7 @@ export default defineConfig({ osVersion: process.env.BROWSERSTACK_OS_VERSION || '16.0', }, buildPath: process.env.BROWSERSTACK_IOS_CLEAN_APP_URL, - expectTimeout: 30 * 1000, //90 seconds increased since login the app takes longer + expectTimeout: 30 * 1000, }, }, ], diff --git a/appwright/fixtures/performance-test.js b/appwright/fixtures/performance-test.js index 7650dca0709..dbfbecc9c0a 100644 --- a/appwright/fixtures/performance-test.js +++ b/appwright/fixtures/performance-test.js @@ -21,51 +21,6 @@ export const test = base.extend({ console.log('⚠️ No timers found in performance tracker'); } - // Enhanced timer recovery: capture any timers that weren't added to the tracker - try { - const Timers = await import('../utils/Timers.js').then((m) => m.default); - const allGlobalTimers = Timers.getAllTimers(); - - // Check for timers that exist globally but weren't added to the tracker - for (const globalTimer of allGlobalTimers) { - const existsInTracker = performanceTracker.timers.some( - (t) => t.id === globalTimer.id, - ); - - if (!existsInTracker) { - console.log(`🔄 Recovering orphaned timer: "${globalTimer.id}"`); - - try { - const recoveredTimer = new TimerHelper(globalTimer.id); - - if (globalTimer.start !== null && globalTimer.duration === null) { - recoveredTimer.stop(); - } - - performanceTracker.addTimer(recoveredTimer); - } catch (error) { - console.log(`⚠️ Failed to recover timer ${globalTimer.id}`); - } - } - } - } catch (importError) { - console.log(`⚠️ Timer recovery failed: ${importError.message}`); - } - - // Stop any running timers in the tracker - for (const timer of performanceTracker.timers) { - try { - const isRunning = timer.isRunning ? timer.isRunning() : false; - const isCompleted = timer.isCompleted ? timer.isCompleted() : false; - - if (isRunning && !isCompleted) { - timer.stop(); - } - } catch (error) { - console.log(`⚠️ Error checking timer ${timer.id}`); - } - } - // Always try to attach performance metrics, even if test failed try { const metrics = await performanceTracker.attachToTest(testInfo); @@ -76,32 +31,6 @@ export const test = base.extend({ ); } catch (error) { console.error('❌ Failed to attach performance metrics:', error.message); - - // Create fallback metrics for failed tests - try { - const fallbackMetrics = { - testFailed: true, - failureReason: testInfo?.status || 'unknown', - testDuration: testInfo?.duration || 0, - message: 'Performance metrics could not be properly attached', - timersFound: performanceTracker.timers.length, - device: testInfo?.project?.use?.device || { - name: 'Unknown', - osVersion: 'Unknown', - }, - }; - - await testInfo.attach( - `performance-metrics-fallback-${testInfo.title}`, - { - body: JSON.stringify(fallbackMetrics), - contentType: 'application/json', - }, - ); - console.log(`✅ Fallback metrics attached`); - } catch (fallbackError) { - console.error('❌ Fallback metrics attachment failed'); - } } console.log('🔍 Looking for session ID...'); diff --git a/appwright/tests/performance/login/asset-view.spec.js b/appwright/tests/performance/login/asset-view.spec.js index 379c31e5c5b..0b2fe6a2edd 100644 --- a/appwright/tests/performance/login/asset-view.spec.js +++ b/appwright/tests/performance/login/asset-view.spec.js @@ -54,17 +54,8 @@ test('Asset View, SRP 1 + SRP 2 + SRP 3', async ({ TokenOverviewScreen.device = device; CommonScreen.device = device; WalletActionModal.device = device; - await login(device); - // await importSRPFlow(device, process.env.TEST_SRP_2); - // await importSRPFlow(device, process.env.TEST_SRP_3); - - await WalletMainScreen.isMainWalletViewVisible(); - - //await WalletMainScreen.tapNetworkNavBar(); - //await NetworksScreen.selectNetwork('Ethereum'); - const assetViewScreen = new TimerHelper( 'Time since the user clicks on the asset view button until the user sees the token overview screen', ); @@ -72,7 +63,7 @@ test('Asset View, SRP 1 + SRP 2 + SRP 3', async ({ await WalletMainScreen.tapNetworkNavBar(); await NetworksScreen.selectNetwork('Ethereum'); - await WalletMainScreen.tapOnToken('Link'); + await WalletMainScreen.tapOnToken('USDC'); assetViewScreen.start(); await TokenOverviewScreen.isTokenOverviewVisible(); await TokenOverviewScreen.isTodaysChangeVisible(); diff --git a/appwright/tests/performance/login/cross-chain-swap-flow.spec.js b/appwright/tests/performance/login/cross-chain-swap-flow.spec.js index 82b48fed136..6469d49061b 100644 --- a/appwright/tests/performance/login/cross-chain-swap-flow.spec.js +++ b/appwright/tests/performance/login/cross-chain-swap-flow.spec.js @@ -32,16 +32,14 @@ test('Cross-chain swap flow - ETH to SOL - 50+ accounts, SRP 1 + SRP 2 + SRP 3', NetworkEducationModal.device = device; NetworksScreen.device = device; BridgeScreen.device = device; - await login(device); - // await onboardingFlowImportSRP(device, process.env.TEST_SRP_2, 120000); const timer1 = new TimerHelper( 'Time since the user clicks on the "Swap" button until the swap page is loaded', ); - timer1.start(); await WalletMainScreen.tapSwapButton(); + timer1.start(); await BridgeScreen.isVisible(); timer1.stop(); diff --git a/appwright/tests/performance/login/eth-swap-flow.spec.js b/appwright/tests/performance/login/eth-swap-flow.spec.js index e228e152533..77a4f29fcf5 100644 --- a/appwright/tests/performance/login/eth-swap-flow.spec.js +++ b/appwright/tests/performance/login/eth-swap-flow.spec.js @@ -28,9 +28,7 @@ test('Swap flow - ETH to LINK, SRP 1 + SRP 2 + SRP 3', async ({ AccountListComponent.device = device; AddAccountModal.device = device; BridgeScreen.device = device; - await login(device); - // await importSRPFlow(device, process.env.TEST_SRP_2); const swapLoadTimer = new TimerHelper( 'Time since the user clicks on the "Swap" button until the swap page is loaded', diff --git a/appwright/tests/performance/login/import-multiple-srps.spec.js b/appwright/tests/performance/login/import-multiple-srps.spec.js index b4c28c65e36..ca0b751d3ff 100644 --- a/appwright/tests/performance/login/import-multiple-srps.spec.js +++ b/appwright/tests/performance/login/import-multiple-srps.spec.js @@ -8,7 +8,12 @@ import AddAccountModal from '../../../../wdio/screen-objects/Modals/AddAccountMo import WalletActionModal from '../../../../wdio/screen-objects/Modals/WalletActionModal.js'; import SwapScreen from '../../../../wdio/screen-objects/SwapScreen.js'; import TabBarModal from '../../../../wdio/screen-objects/Modals/TabBarModal.js'; -import { importSRPFlow, login } from '../../../utils/Flows.js'; +import { + dismissMultichainAccountsIntroModal, + dissmissPredictionsModal, + importSRPFlow, + login, +} from '../../../utils/Flows.js'; /* Scenario 4: Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3 */ test('Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3', async ({ @@ -22,12 +27,10 @@ test('Import SRP with +50 accounts, SRP 1, SRP 2, SRP 3', async ({ WalletActionModal.device = device; SwapScreen.device = device; TabBarModal.device = device; - + test.setTimeout(1800000); await login(device); - const timers = await importSRPFlow(device, process.env.TEST_SRP_2); - - await WalletMainScreen.tapIdenticon(); + const timers = await importSRPFlow(device, process.env.TEST_SRP_2, false); timers.forEach((timer) => performanceTracker.addTimer(timer)); await performanceTracker.attachToTest(testInfo); }); diff --git a/appwright/tests/performance/login/send-flows.spec.js b/appwright/tests/performance/login/send-flows.spec.js index a9f1dd8ee1d..4e8e5072de0 100644 --- a/appwright/tests/performance/login/send-flows.spec.js +++ b/appwright/tests/performance/login/send-flows.spec.js @@ -13,7 +13,7 @@ import NetworksScreen from '../../../../wdio/screen-objects/NetworksScreen.js'; import LoginScreen from '../../../../wdio/screen-objects/LoginScreen.js'; import { TEST_AMOUNTS } from '../../../utils/TestConstants.js'; -import { login } from '../../../utils/Flows.js'; +import { dissmissPredictionsModal, login } from '../../../utils/Flows.js'; import TokenOverviewScreen from '../../../../wdio/screen-objects/TokenOverviewScreen.js'; const ethAddress = '0xbea21b0b30ddd5e04f426ffb0c4c79157fc4047d'; @@ -23,7 +23,6 @@ test('Send flow - Ethereum, SRP 1 + SRP 2 + SRP 3', async ({ device, performanceTracker, }, testInfo) => { - test.setTimeout(1800000); // TODO: Investigate why this is taking so long on Android WalletAccountModal.device = device; WalletMainScreen.device = device; AccountListComponent.device = device; @@ -36,7 +35,7 @@ test('Send flow - Ethereum, SRP 1 + SRP 2 + SRP 3', async ({ NetworksScreen.device = device; TokenOverviewScreen.device = device; await login(device); - // await onboardingFlowImportSRP(device, process.env.TEST_SRP_1, 120000); + await dissmissPredictionsModal(device); const timer1 = new TimerHelper( 'Time since the user clicks on the send button, until the user is in the send screen', ); @@ -94,7 +93,6 @@ test('Send flow - Solana, SRP 1 + SRP 2 + SRP 3', async ({ NetworksScreen.device = device; TokenOverviewScreen.device = device; await login(device); - // await onboardingFlowImportSRP(device, process.env.TEST_SRP_1, 120000); const timer1 = new TimerHelper( 'Time since the user clicks on the send button, until the user is in the send screen', ); @@ -112,7 +110,6 @@ test('Send flow - Solana, SRP 1 + SRP 2 + SRP 3', async ({ await SendScreen.assetsListIsDisplayed(); timer1.stop(); await SendScreen.typeTokenName('Solana\n'); - console.log('Solana typed, so waiting 5 seconds'); await SendScreen.clickOnFirstTokenBadge(); timer2.start(); diff --git a/appwright/tests/performance/onboarding/import-wallet.spec.js b/appwright/tests/performance/onboarding/import-wallet.spec.js index 7070711d459..67a54612981 100644 --- a/appwright/tests/performance/onboarding/import-wallet.spec.js +++ b/appwright/tests/performance/onboarding/import-wallet.spec.js @@ -14,7 +14,7 @@ import CreatePasswordScreen from '../../../../wdio/screen-objects/Onboarding/Cre import ImportFromSeedScreen from '../../../../wdio/screen-objects/Onboarding/ImportFromSeedScreen.js'; import { getPasswordForScenario } from '../../../utils/TestConstants.js'; -import { dissmissAllModals } from '../../../utils/Flows.js'; +import { dissmissPredictionsModal } from '../../../utils/Flows.js'; /* Scenario 4: Imported wallet with +50 accounts */ test.setTimeout(150000000); @@ -34,7 +34,6 @@ test('Onboarding Import SRP with +50 accounts, SRP 3', async ({ WalletMainScreen.device = device; ImportFromSeedScreen.device = device; CreatePasswordScreen.device = device; - const timer3 = new TimerHelper( 'Time since the user clicks on "Create new wallet" button until "Social sign up" is visible', ); @@ -97,9 +96,12 @@ test('Onboarding Import SRP with +50 accounts, SRP 3', async ({ timer8.start(); await OnboardingSucessScreen.tapDone(); timer8.stop(); - await dissmissAllModals(device); + + await dissmissPredictionsModal(device); timer9.start(); - await WalletMainScreen.tapOnToken('ETH'); + + await WalletMainScreen.isTokenVisible('ETH'); + await WalletMainScreen.isTokenVisible('SOL'); timer9.stop(); performanceTracker.addTimer(timer3); diff --git a/appwright/tests/performance/onboarding/imported-wallet-account-creation.spec.js b/appwright/tests/performance/onboarding/imported-wallet-account-creation.spec.js index f0d783ada49..67cb94c5d43 100644 --- a/appwright/tests/performance/onboarding/imported-wallet-account-creation.spec.js +++ b/appwright/tests/performance/onboarding/imported-wallet-account-creation.spec.js @@ -27,7 +27,6 @@ test.skip('Account creation with 50+ accounts, SRP 1 + SRP 2 + SRP 3', async ({ AccountListComponent.device = device; AddAccountModal.device = device; AddNewHdAccountComponent.device = device; - await onboardingFlowImportSRP(device, process.env.TEST_SRP_2); // await importSRPFlow(device, process.env.TEST_SRP_2); @@ -36,28 +35,22 @@ test.skip('Account creation with 50+ accounts, SRP 1 + SRP 2 + SRP 3', async ({ const screen1Timer = new TimerHelper( 'Time since the user clicks on "Account list" button until the account list is visible', ); - const screen2Timer = new TimerHelper( - 'Time since the user clicks on "Create account" button until the account is in the account list', - ); + const screen3Timer = new TimerHelper( 'Time since the user clicks on new account created until the Token list is visible', ); - screen1Timer.start(); await WalletMainScreen.tapIdenticon(); + screen1Timer.start(); await AccountListComponent.isComponentDisplayed(); screen1Timer.stop(); - screen2Timer.start(); await AccountListComponent.tapCreateAccountButton(); - screen2Timer.stop(); - //await AccountListComponent.tapOnAccountByName('Account 4'); - screen3Timer.start(); await WalletMainScreen.isTokenVisible('ETH'); + await WalletMainScreen.isTokenVisible('SOL'); screen3Timer.stop(); performanceTracker.addTimer(screen1Timer); - performanceTracker.addTimer(screen2Timer); performanceTracker.addTimer(screen3Timer); await performanceTracker.attachToTest(testInfo); diff --git a/appwright/tests/performance/onboarding/launch-times/cold-start-after-wallet-import.spec.js b/appwright/tests/performance/onboarding/launch-times/cold-start-after-wallet-import.spec.js index 2d44ac8fec5..6017beefd4c 100644 --- a/appwright/tests/performance/onboarding/launch-times/cold-start-after-wallet-import.spec.js +++ b/appwright/tests/performance/onboarding/launch-times/cold-start-after-wallet-import.spec.js @@ -47,26 +47,27 @@ test('Cold Start after importing a wallet', async ({ AmountScreen.device = device; MultichainAccountEducationModal.device = device; LoginScreen.device = device; - - await onboardingFlowImportSRP(device, process.env.TEST_SRP_2, 120000); + WalletActionModal.device = device; + await onboardingFlowImportSRP(device, process.env.TEST_SRP_3); // await importSRPFlow(device, process.env.TEST_SRP_2); // await importSRPFlow(device, process.env.TEST_SRP_3); await AppwrightGestures.terminateApp(device); await AppwrightGestures.activateApp(device); await LoginScreen.waitForScreenToDisplay(); - await login(device, { scenarioType: 'onboarding', skipIntro: true }); // Skip intro screens on second login + await login(device, { + scenarioType: 'onboarding', + skipIntro: true, + dismissModals: false, + }); // Skip intro screens on second login const timer1 = new TimerHelper( 'Time since the user clicks on unlock button, until the app unlocks', ); - const timer2 = new TimerHelper( - 'Time since the user closes the multichain account education modal, until the wallet main screen appears', - ); - timer2.start(); - await WalletMainScreen.isMainWalletViewVisible(); - timer2.stop(); + timer1.start(); + await WalletActionModal.isSendButtonVisible(); + timer1.stop(); - performanceTracker.addTimer(timer2); + performanceTracker.addTimer(timer1); await performanceTracker.attachToTest(testInfo); }); diff --git a/appwright/tests/performance/onboarding/new-wallet-account-creation.spec.js b/appwright/tests/performance/onboarding/new-wallet-account-creation.spec.js index 1a316d4f05d..89f52ec4183 100644 --- a/appwright/tests/performance/onboarding/new-wallet-account-creation.spec.js +++ b/appwright/tests/performance/onboarding/new-wallet-account-creation.spec.js @@ -13,10 +13,8 @@ import SkipAccountSecurityModal from '../../../../wdio/screen-objects/Modals/Ski import WalletMainScreen from '../../../../wdio/screen-objects/WalletMainScreen.js'; import { getPasswordForScenario } from '../../../utils/TestConstants.js'; import AccountListComponent from '../../../../wdio/screen-objects/AccountListComponent.js'; -import { - dissmissAllModals, - tapPerpsBottomSheetGotItButton, -} from '../../../utils/Flows.js'; +import { dissmissPredictionsModal } from '../../../utils/Flows.js'; +import CreatePasswordScreen from '../../../../wdio/screen-objects/Onboarding/CreatePasswordScreen.js'; /* Scenario 2: Account creation after fresh install */ @@ -35,7 +33,7 @@ test('Account creation after fresh install', async ({ SkipAccountSecurityModal.device = device; WalletMainScreen.device = device; AccountListComponent.device = device; - + CreatePasswordScreen.device = device; await OnboardingScreen.tapCreateNewWalletButton(); await OnboardingSheet.isVisible(); @@ -49,7 +47,10 @@ test('Account creation after fresh install', async ({ getPasswordForScenario('onboarding'), ); - await CreateNewWalletScreen.tapSubmitButton(); + await CreatePasswordScreen.tapIUnderstandCheckBox(); + + await CreatePasswordScreen.tapCreatePasswordButton(); + await CreateNewWalletScreen.tapRemindMeLater(); await MetaMetricsScreen.isScreenTitleVisible(); @@ -59,7 +60,7 @@ test('Account creation after fresh install', async ({ await OnboardingSucessScreen.tapDone(); - await dissmissAllModals(device); + await dissmissPredictionsModal(device); await WalletMainScreen.isMainWalletViewVisible(); @@ -74,8 +75,9 @@ test('Account creation after fresh install', async ({ const screen3Timer = new TimerHelper( 'Time since the user clicks on new account created until the Token list is visible', ); - screen1Timer.start(); + await WalletMainScreen.tapIdenticon(); + screen1Timer.start(); await AccountListComponent.isComponentDisplayed(); screen1Timer.stop(); @@ -84,10 +86,9 @@ test('Account creation after fresh install', async ({ await AccountListComponent.isAccountDisplayed('Account 2'); screen2Timer.stop(); await AccountListComponent.tapOnAccountByName('Account 2'); - screen3Timer.start(); - await WalletMainScreen.isMainWalletViewVisible(); - // await WalletMainScreen.isTokenVisible('SOL'); // TODO: skipped since locator is no longer reachable + await WalletMainScreen.isTokenVisible('ETH'); + await WalletMainScreen.isTokenVisible('SOL'); screen3Timer.stop(); performanceTracker.addTimer(screen1Timer); performanceTracker.addTimer(screen2Timer); diff --git a/appwright/tests/performance/onboarding/perps-onboarding.spec.js b/appwright/tests/performance/onboarding/perps-onboarding.spec.js index 94a9bdbd518..0ce0b3ce6bb 100644 --- a/appwright/tests/performance/onboarding/perps-onboarding.spec.js +++ b/appwright/tests/performance/onboarding/perps-onboarding.spec.js @@ -32,13 +32,16 @@ async function screensSetup(device) { } /* Scenario 5: Perps onboarding + add funds 10 USD ARB.USDC */ -test('Perps onboarding + add funds 10 USD ARB.USDC', async ({ +// TODO: Fix this test: https://consensyssoftware.atlassian.net/browse/MMQA-1190 +test.skip('Perps onboarding + add funds 10 USD ARB.USDC', async ({ device, performanceTracker, }, testInfo) => { + test.setTimeout(10 * 60 * 1000); // 10 minutes await screensSetup(device); - await onboardingFlowImportSRP(device, process.env.TEST_SRP_3, 120000); + await onboardingFlowImportSRP(device, process.env.TEST_SRP_3); + await WalletMainScreen.isTokenVisible('ETH'); await TabBarModal.tapTradeButton(); // Open Perps tab @@ -50,9 +53,8 @@ test('Perps onboarding + add funds 10 USD ARB.USDC', async ({ await PerpsTutorialScreen.expectFirstScreenVisible(); }, ); - // Open Tutorial flow - await PerpsTutorialScreen.flowTapContinueTutorial(5); + await PerpsTutorialScreen.flowTapContinueTutorial(6); // Open Add Funds flow await TimerHelper.withTimer( @@ -63,14 +65,13 @@ test('Perps onboarding + add funds 10 USD ARB.USDC', async ({ await PerpsDepositScreen.isAmountInputVisible(); }, ); - // Select pay token await TimerHelper.withTimer( performanceTracker, 'Select pay token - 1 click USDC.arb', async () => { await PerpsDepositScreen.tapPayWith(); - await PerpsDepositScreen.selectPayTokenByText('0xa4b1', 'USDC'); + await PerpsDepositScreen.selectPayTokenByText('USDC'); }, ); @@ -80,7 +81,6 @@ test('Perps onboarding + add funds 10 USD ARB.USDC', async ({ 'Fill amount - 10 USD', async () => { await PerpsDepositScreen.fillUsdAmount('10'); - await PerpsDepositScreen.tapContinue(); }, ); diff --git a/appwright/utils/Flows.js b/appwright/utils/Flows.js index e05580b5a94..a20dbadba9a 100644 --- a/appwright/utils/Flows.js +++ b/appwright/utils/Flows.js @@ -15,6 +15,8 @@ import LoginScreen from '../../wdio/screen-objects/LoginScreen.js'; import MultichainAccountEducationModal from '../../wdio/screen-objects/Modals/MultichainAccountEducationModal.js'; import PerpsGTMModal from '../../wdio/screen-objects/Modals/PerpsGTMModal.js'; import RewardsGTMModal from '../../wdio/screen-objects/Modals/RewardsGTMModal.js'; +import AppwrightGestures from '../../e2e/framework/AppwrightGestures.js'; +import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors.js'; export async function onboardingFlowImportSRP(device, srp) { WelcomeScreen.device = device; @@ -55,18 +57,27 @@ export async function onboardingFlowImportSRP(device, srp) { await OnboardingSucessScreen.isVisible(); await OnboardingSucessScreen.tapDone(); - await dissmissAllModals(device); - + //await dismissRewardsBottomSheetModal(device); + await dissmissPredictionsModal(device); await WalletMainScreen.isMainWalletViewVisible(); } export async function dissmissAllModals(device) { await dismissMultichainAccountsIntroModal(device); - await tapPerpsBottomSheetGotItButton(device); - await dismissRewardsBottomSheetModal(device); + await dissmissPredictionsModal(device); +} + +export async function dissmissPredictionsModal(device) { + const notNowPredictionsModalButton = await AppwrightSelectors.getElementByID( + device, + 'predict-gtm-not-now-button', + ); + if (await notNowPredictionsModalButton.isVisible({ timeout: 5000 })) { + await AppwrightGestures.tap(notNowPredictionsModalButton); + } } -export async function importSRPFlow(device, srp) { +export async function importSRPFlow(device, srp, dismissModals = true) { WalletMainScreen.device = device; AccountListComponent.device = device; AddAccountModal.device = device; @@ -85,9 +96,8 @@ export async function importSRPFlow(device, srp) { 'Time since the user clicks on "Continue" button on SRP screen until Wallet main screen is visible', ); - timer.start(); - await WalletMainScreen.tapIdenticon(); + timer.start(); await AccountListComponent.isComponentDisplayed(); timer.stop(); @@ -104,7 +114,7 @@ export async function importSRPFlow(device, srp) { await ImportFromSeedScreen.tapImportScreenTitleToDismissKeyboard(false); await ImportFromSeedScreen.tapContinueButton(false); - await dissmissAllModals(device); + dismissModals ? await dissmissAllModals(device) : null; timer4.start(); await WalletMainScreen.isMainWalletViewVisible(); timer4.stop(); @@ -115,16 +125,18 @@ export async function importSRPFlow(device, srp) { export async function login(device, options = {}) { LoginScreen.device = device; - const { scenarioType = 'login' } = options; + const { scenarioType = 'login', dismissModals = true } = options; const password = getPasswordForScenario(scenarioType); - // Type password and unlock await LoginScreen.typePassword(password); await LoginScreen.tapUnlockButton(); // Wait for app to settle after unlock - await dissmissAllModals(device); + if (dismissModals) { + await dismissMultichainAccountsIntroModal(device); + await dissmissPredictionsModal(device); + } } export async function tapPerpsBottomSheetGotItButton(device) { PerpsGTMModal.device = device; @@ -133,15 +145,11 @@ export async function tapPerpsBottomSheetGotItButton(device) { await PerpsGTMModal.tapNotNowButton(); console.log('Perps onboarding dismissed'); } - if (await container.isVisible({ timeout: 5000 })) { - await PerpsGTMModal.tapNotNowButton(); - console.log('Perps onboarding dismissed'); - } } export async function dismissRewardsBottomSheetModal(device) { RewardsGTMModal.device = device; - const container = await RewardsGTMModal.container; + const container = await RewardsGTMModal.notNowButton; if (await container.isVisible({ timeout: 5000 })) { await RewardsGTMModal.tapNotNowButton(); } @@ -156,7 +164,4 @@ export async function dismissMultichainAccountsIntroModal( if (await closeButton.isVisible({ timeout })) { await MultichainAccountEducationModal.tapGotItButton(); } - if (await closeButton.isVisible({ timeout })) { - await MultichainAccountEducationModal.tapGotItButton(); - } } diff --git a/appwright/utils/TestConstants.js b/appwright/utils/TestConstants.js index beb74e9eb31..93939030e4c 100644 --- a/appwright/utils/TestConstants.js +++ b/appwright/utils/TestConstants.js @@ -40,6 +40,7 @@ export function getPasswordForScenario(scenarioType) { case 'login': return TEST_PASSWORDS.LOGIN; case 'onboarding': + return TEST_PASSWORDS.ONBOARDING; case 'import': return TEST_PASSWORDS.ONBOARDING; default: diff --git a/appwright/utils/Utils.js b/appwright/utils/Utils.js new file mode 100644 index 00000000000..c6cb07bb077 --- /dev/null +++ b/appwright/utils/Utils.js @@ -0,0 +1,9 @@ +export const splitAmountIntoDigits = (amount) => + // Convert to string and split into array of digits + amount + .toString() + .split('') + .map((char) => + // Return only numeric digits, filter out decimal points, commas, etc. + /\d/.test(char) ? parseInt(char, 10) : char, + ); diff --git a/e2e/framework/AppwrightSelectors.ts b/e2e/framework/AppwrightSelectors.ts index 005d9bff6c9..e98ee7c6b46 100644 --- a/e2e/framework/AppwrightSelectors.ts +++ b/e2e/framework/AppwrightSelectors.ts @@ -4,8 +4,9 @@ export default class AppwrightSelectors { static async getElementByID( testDevice: Device, id: string, + exact: boolean = false, ): Promise { - return await testDevice.getById(id, { exact: true }); + return await testDevice.getById(id, { exact }); } static async getElementByXpath( diff --git a/package.json b/package.json index 4f7e3e7a96c..50d3ec87139 100644 --- a/package.json +++ b/package.json @@ -561,7 +561,7 @@ "appium-adb": "^9.11.4", "appium-uiautomator2-driver": "4.2.7", "appium-xcuitest-driver": "5.16.1", - "appwright": "^0.1.45", + "appwright": "patch:appwright@npm%3A0.1.45#~/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch", "assert": "^1.5.0", "babel-jest": "^29.7.0", "babel-loader": "^9.1.3", diff --git a/wdio/screen-objects/AccountListComponent.js b/wdio/screen-objects/AccountListComponent.js index 904e32f1ffb..ff7dc48ee50 100644 --- a/wdio/screen-objects/AccountListComponent.js +++ b/wdio/screen-objects/AccountListComponent.js @@ -30,7 +30,7 @@ class AccountListComponent { if (!this._device) { return Selectors.getXpathElementByResourceId(AccountListBottomSheetSelectorsIDs.ACCOUNT_LIST_ADD_BUTTON_ID); } else { - return AppwrightSelectors.getElementByCatchAll(this._device, 'Add account'); + return AppwrightSelectors.getElementByID(this._device, AccountListBottomSheetSelectorsIDs.CREATE_ACCOUNT); } } @@ -42,13 +42,13 @@ class AccountListComponent { if (!this._device) { await Gestures.waitAndTap(this.addAccountButton); } else { - await AppwrightGestures.tap(this.addAccountButton); // Use static tapElement method with retry logic + await AppwrightGestures.scrollIntoView(this.device, this.addAccountButton, {scrollParams: {direction: 'down'}}); + await AppwrightGestures.tap(this.addAccountButton); } } async tapOnAddWalletButton() { - const element = await this.addWalletButton; - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.addWalletButton); // Use static tap method with retry logic } async isComponentDisplayed() { @@ -71,7 +71,7 @@ class AccountListComponent { } async tapOnAccountByName(name) { - let account = await AppwrightSelectors.getElementByText(this.device, name); + const account = AppwrightSelectors.getElementByText(this.device, name); await AppwrightGestures.scrollIntoView(this.device, account); // Use inherited method with retry logic await AppwrightGestures.tap(account); // Tap after scrolling into view } diff --git a/wdio/screen-objects/AmountScreen.js b/wdio/screen-objects/AmountScreen.js index 417c957c78f..bbfe3f24b62 100644 --- a/wdio/screen-objects/AmountScreen.js +++ b/wdio/screen-objects/AmountScreen.js @@ -9,6 +9,7 @@ import { NEXT_BUTTON, TRANSACTION_AMOUNT_INPUT, } from './testIDs/Screens/AmountScreen.testIds'; +import { splitAmountIntoDigits } from 'appwright/utils/Utils'; class AmountScreen { @@ -48,14 +49,6 @@ class AmountScreen { return AppwrightSelectors.getElementByCatchAll(this._device, 'Continue'); } } - // Helper method to split amount into digits - splitAmountIntoDigits(amount) { - // Convert to string and split into array of digits - return amount.toString().split('').map(char => { - // Return only numeric digits, filter out decimal points, commas, etc. - return /\d/.test(char) ? parseInt(char, 10) : char; - }); - } async tapNumberKey(digit) { console.log(`tapNumberKey called with digit: "${digit}"`); @@ -92,7 +85,7 @@ class AmountScreen { } else { console.log('Direct input failed, falling back to digit tapping'); // Fallback to digit tapping if direct input fails - const digits = this.splitAmountIntoDigits(text); + const digits = splitAmountIntoDigits(text); for (const digit of digits) { console.log('Tapping digit:', digit); await this.tapNumberKey(digit); diff --git a/wdio/screen-objects/BridgeScreen.js b/wdio/screen-objects/BridgeScreen.js index 3381dc86b12..c29f50ece01 100644 --- a/wdio/screen-objects/BridgeScreen.js +++ b/wdio/screen-objects/BridgeScreen.js @@ -6,6 +6,7 @@ import { PerpsWithdrawViewSelectorsIDs } from '../../e2e/selectors/Perps/Perps.s import { QuoteViewSelectorText } from '../../e2e/selectors/swaps/QuoteView.selectors'; import Selectors from '../helpers/Selectors.js'; import { LoginViewSelectors } from '../../e2e/selectors/wallet/LoginView.selectors'; +import { splitAmountIntoDigits } from 'appwright/utils/Utils.js'; class BridgeScreen { @@ -56,9 +57,6 @@ class BridgeScreen { } async isQuoteDisplayed() { - - const element = await this.quoteDisplayed; // bridge swap view shows on - await appwrightExpect(element).toBeVisible({ timeout: 30000 }); const mmFee = await AppwrightSelectors.getElementByCatchAll(this._device, "Includes 0.875% MM fee"); await appwrightExpect(mmFee).toBeVisible({ timeout: 30000 }); @@ -67,7 +65,7 @@ class BridgeScreen { async enterSourceTokenAmount(amount) { // Split amount into digits - const digits = this.splitAmountIntoDigits(amount); + const digits = splitAmountIntoDigits(amount); console.log('Amount digits:', digits); for (const digit of digits) { if (AppwrightSelectors.isAndroid(this._device)) { @@ -91,75 +89,24 @@ class BridgeScreen { } async selectNetworkAndTokenTo(network, token) { - const destinationToken = this.destinationTokenArea; - await AppwrightGestures.tap(destinationToken); - await AppwrightGestures.tap(this.getNetworkButton(network)); - const tokenField = AppwrightSelectors.getElementByText(this._device, 'Enter token name or paste address'); - await AppwrightGestures.typeText(tokenField, token); - let tokenNetworkId; - if (network == 'Ethereum'){ - tokenNetworkId = `0x1`; - } - else if (network == 'Polygon'){ - tokenNetworkId = `0x89`; - } - else if (network == 'Solana'){ - tokenNetworkId = `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`; - } - let tokenButton; - if (AppwrightSelectors.isAndroid(this._device)){ - tokenButton = await AppwrightSelectors.getElementByXpath(this._device, `//*[@resource-id="asset-${tokenNetworkId}-${token}"]`); - } - else { - // Try multiple iOS element selection strategies - console.log(`Looking for iOS token with ID: asset-${tokenNetworkId}-${token}`); - tokenButton = await AppwrightSelectors.getElementByID(this._device, `asset-${tokenNetworkId}-${token}`); - - } - await appwrightExpect(tokenButton).toBeVisible({ timeout: 10000 }); - console.log('Token button found and visible'); - - console.log('About to hide keyboard...'); - await AppwrightGestures.hideKeyboard(this._device); - console.log('Keyboard hidden successfully'); - - console.log('About to tap token button...'); - - // Try multiple tap strategies for iOS - if (AppwrightSelectors.isAndroid(this._device)) { - await AppwrightGestures.tap(tokenButton); - } else { - // iOS-specific tap strategy - console.log('Using iOS-specific tap strategy...'); - try { - await AppwrightGestures.tap(tokenButton); - console.log('iOS click() succeeded'); - } catch (error) { - console.log('iOS click() failed, trying tap()...'); - await AppwrightGestures.tap(tokenButton); - console.log('iOS tap() succeeded'); - } - } - console.log('Token button tapped successfully'); - - // Wait for the amount input field to appear after tapping token - await new Promise(resolve => setTimeout(resolve, 2000)); - console.log('Waited 2 seconds after token button tap'); - - // Check if number input field is available - try { - const testNumberButton = AppwrightSelectors.isIOS(this._device) ? await AppwrightSelectors.getElementByXpath(this._device, `//XCUIElementTypeButton[@name="1"]`) : await AppwrightSelectors.getElementByXpath(this._device, `//android.widget.Button[@content-desc='1']`); - await appwrightExpect(testNumberButton).toBeVisible({ timeout: 5000 }); - console.log('Number input field is visible - token tap worked'); - } catch (error) { - console.log('Number input field not visible - token tap may not have worked, trying alternative tap method...'); - - // Try alternative tap methods for iOS - await AppwrightGestures.tap(tokenButton); // Try click instead of tap - await new Promise(resolve => setTimeout(resolve, 1000)); - - console.log('Tried alternative tap methods'); - } + const destinationToken = await this.destinationTokenArea; + await AppwrightGestures.tap(destinationToken); + const filterNetworkButton = await AppwrightSelectors.getElementByCatchAll(this._device, 'See all'); + await AppwrightGestures.tap(filterNetworkButton); + const networkButton = await this.getNetworkButton(network); + await AppwrightGestures.tap(networkButton); + let tokenNetworkId; + if (network == 'Ethereum'){ + tokenNetworkId = `0x1`; + } + else if (network == 'Polygon'){ + tokenNetworkId = `0x89`; + } + else if (network == 'Solana'){ + tokenNetworkId = `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`; + } + const tokenButton = await AppwrightSelectors.getElementByID(this._device, `asset-${tokenNetworkId}-${token}`); + await AppwrightGestures.tap(tokenButton); } async tapGetQuotes(network){ @@ -170,15 +117,6 @@ class BridgeScreen { } } - // Helper method to split amount into digits - splitAmountIntoDigits(amount) { - // Convert to string and split into array of digits - return amount.toString().split('').map(char => { - // Return only numeric digits, filter out decimal points, commas, etc. - return /\d/.test(char) ? parseInt(char, 10) : char; - }); - } - async enterDestinationTokenAmount(amount) { const element = this.destTokenInput; await AppwrightGestures.typeText(element, amount); diff --git a/wdio/screen-objects/LoginScreen.js b/wdio/screen-objects/LoginScreen.js index 5e13ebbda48..a3048aed6df 100644 --- a/wdio/screen-objects/LoginScreen.js +++ b/wdio/screen-objects/LoginScreen.js @@ -58,7 +58,7 @@ class LoginScreen { LoginViewSelectors.PASSWORD_INPUT, ); } else { - return AppwrightSelectors.getElementByID(this._device, "textfield"); + return AppwrightSelectors.getElementByID(this._device, "textfield", true); } } } @@ -76,7 +76,7 @@ class LoginScreen { if (!this._device) { return Selectors.getXpathElementByResourceId(LoginViewSelectors.TITLE_ID); } else { - return AppwrightSelectors.getElementByID(this._device, LoginViewSelectors.TITLE_ID); + return AppwrightSelectors.getElementByID(this._device,'log-in-button'); } } @@ -115,19 +115,19 @@ class LoginScreen { if (!this._device) { await Gestures.waitAndTap(this.resetWalletButton); } else { - await AppwrightGestures.tap(this.resetWalletButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.resetWalletButton); } } async typePassword(password) { - await this.isLoginScreenVisible(); + //await this.isLoginScreenVisible(); if (!this._device) { await Gestures.typeText(this.passwordInput, password); } else { - const screenTitle = await this.title const element = await this.getPasswordInputElement; await AppwrightGestures.typeText(element, password); - await screenTitle.tap() + await AppwrightGestures.hideKeyboard(this._device); + } } @@ -136,7 +136,7 @@ class LoginScreen { const element = await this.unlockButton; await element.click(); } else { - await AppwrightGestures.tap(this.unlockButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.unlockButton); } } @@ -144,7 +144,7 @@ class LoginScreen { if (!this._device) { await Gestures.waitAndTap(this.title); } else { - await AppwrightGestures.tap(this.title); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.title); } } @@ -152,7 +152,7 @@ class LoginScreen { if (!this._device) { await Gestures.waitAndTap(this.rememberMeToggle); } else { - await AppwrightGestures.tap(this.rememberMeToggle); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.rememberMeToggle); } } } diff --git a/wdio/screen-objects/Modals/RewardsGTMModal.js b/wdio/screen-objects/Modals/RewardsGTMModal.js index ecfb1f5fdc8..9ddb408ef9d 100644 --- a/wdio/screen-objects/Modals/RewardsGTMModal.js +++ b/wdio/screen-objects/Modals/RewardsGTMModal.js @@ -16,9 +16,9 @@ class RewardsGTMModal { } get notNowButton() { - return AppwrightSelectors.getElementByCatchAll( + return AppwrightSelectors.getElementByID( this._device, - 'Not now', + 'rewards-view-skip-button', ); } @@ -32,8 +32,7 @@ class RewardsGTMModal { } async tapNotNowButton() { - const notNowButton = await this.notNowButton; - await AppwrightGestures.tap(notNowButton); + await AppwrightGestures.tap(this.notNowButton); } } diff --git a/wdio/screen-objects/Modals/TabBarModal.js b/wdio/screen-objects/Modals/TabBarModal.js index bab6fd03b11..90111a8228a 100644 --- a/wdio/screen-objects/Modals/TabBarModal.js +++ b/wdio/screen-objects/Modals/TabBarModal.js @@ -83,7 +83,7 @@ class TabBarModal { await appwrightExpect(walletIcon).toBeVisible(); // Use static tap method with retry logic - await AppwrightGestures.tap(walletIcon); + await AppwrightGestures.tap(this.walletButton); } } @@ -91,8 +91,7 @@ class TabBarModal { if (!this._device) { await Gestures.waitAndTap(this.browserButton); } else { - const browserIcon = await this.browserButton; - await AppwrightGestures.tap(browserIcon); // Use static tap method with retry logic + await AppwrightGestures.tap(this.browserButton); // Use static tap method with retry logic } } @@ -105,7 +104,7 @@ class TabBarModal { } else { const actionButton = await this.actionButton; await appwrightExpect(actionButton).toBeVisible(); - await AppwrightGestures.tap(actionButton); // Use static tap method with retry logic + await AppwrightGestures.tap(this.actionButton); // Use static tap method with retry logic } } @@ -113,8 +112,7 @@ class TabBarModal { if (!this._device) { await Gestures.waitAndTap(this.tradeButton); } else { - const tradeButton = await this.tradeButton; - await AppwrightGestures.tap(tradeButton); + await AppwrightGestures.tap(this.tradeButton); } } @@ -123,8 +121,7 @@ class TabBarModal { await driver.pause(10000); await Gestures.waitAndTap(this.settingsButton); } else { - const settingsButton = await this.settingsButton; - await AppwrightGestures.tap(settingsButton); // Use static tap method with retry logic + await AppwrightGestures.tap(this.settingsButton); // Use static tap method with retry logic } } @@ -132,8 +129,7 @@ class TabBarModal { if (!this._device) { await Gestures.waitAndTap(this.activityButton); } else { - const activityButton = await this.activityButton; - await AppwrightGestures.tap(activityButton); // Use static tap method with retry logic + await AppwrightGestures.tap(this.activityButton); // Use static tap method with retry logic } } } diff --git a/wdio/screen-objects/Modals/WalletActionModal.js b/wdio/screen-objects/Modals/WalletActionModal.js index 4c538e63456..ca966bde241 100644 --- a/wdio/screen-objects/Modals/WalletActionModal.js +++ b/wdio/screen-objects/Modals/WalletActionModal.js @@ -3,6 +3,7 @@ import Gestures from '../../helpers/Gestures'; import AppwrightSelectors from '../../../e2e/framework/AppwrightSelectors'; import AppwrightGestures from '../../../e2e/framework/AppwrightGestures'; import { WalletActionsBottomSheetSelectorsIDs } from '../../../e2e/selectors/wallet/WalletActionsBottomSheet.selectors'; +import { expect as appwrightExpect } from 'appwright'; class WalletActionModal { @@ -59,11 +60,20 @@ class WalletActionModal { } } + + async isSendButtonVisible() { + if (!this._device) { + await expect(this.sendButton).toBeDisplayed(); + } else { + const element = await this.sendButton; + await appwrightExpect(element).toBeVisible({ timeout: 30000 }); + } + } async tapSendButton() { if (!this._device) { await Gestures.waitAndTap(this.sendButton); } else { - await AppwrightGestures.tap(this.sendButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.sendButton); } } @@ -75,7 +85,7 @@ class WalletActionModal { if (!this._device) { await Gestures.waitAndTap(this.swapButton); } else { - await AppwrightGestures.tap(this.swapButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.swapButton); } } @@ -83,7 +93,7 @@ class WalletActionModal { if (!this._device) { await Gestures.waitAndTap(this.bridgeButton); } else { - await AppwrightGestures.tap(this.bridgeButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.bridgeButton); } } @@ -91,8 +101,7 @@ class WalletActionModal { if (!this._device) { await Gestures.waitAndTap(this.perpsButton); } else { - const element = await this.perpsButton; - await AppwrightGestures.tap(element); + await AppwrightGestures.tap(this.perpsButton); } } @@ -100,8 +109,7 @@ class WalletActionModal { if (!this._device) { await Gestures.waitAndTap(this.predictButton); } else { - const element = await this.predictButton; - await AppwrightGestures.tap(element); + await AppwrightGestures.tap(this.predictButton); } } } diff --git a/wdio/screen-objects/NetworksScreen.js b/wdio/screen-objects/NetworksScreen.js index 3dae2fe623f..3cf56e11e9e 100644 --- a/wdio/screen-objects/NetworksScreen.js +++ b/wdio/screen-objects/NetworksScreen.js @@ -191,29 +191,29 @@ class NetworksScreen { } async tapAddNetworkButton() { - const element = await this.addNetworkButton; if (!this._device) { + const element = await this.addNetworkButton; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.addNetworkButton); // Use static tap method with retry logic } } async tapPopularNetworksTab() { - const element = await this.getPopularNetworksTab; if (!this._device) { + const element = await this.getPopularNetworksTab; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.getPopularNetworksTab); // Use static tap method with retry logic } } async tapCustomNetworksTab() { - const element = await this.getCustomNetworks; if (!this._device) { + const element = await this.getCustomNetworks; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.getCustomNetworks); // Use static tap method with retry logic } } @@ -308,11 +308,11 @@ class NetworksScreen { } async tapCustomAddButton() { - const element = await this.addCustomNetworkButton; if (!this._device) { + const element = await this.addCustomNetworkButton; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.addCustomNetworkButton); // Use static tap method with retry logic } } @@ -325,20 +325,20 @@ class NetworksScreen { } async tapDeleteNetworkButton() { - const element = await this.removeNetworkButton; if (!this._device) { + const element = await this.removeNetworkButton; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.removeNetworkButton); // Use static tap method with retry logic } } async tapSaveNetworkButton() { - const element = await this.saveNetworkButton; if (!this._device) { + const element = await this.saveNetworkButton; await Gestures.tap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.saveNetworkButton); // Use static tap method with retry logic } } @@ -426,38 +426,38 @@ class NetworksScreen { } async tapBackButtonInNewScreen() { - const element = await this.networkScreenBackButton; if (!this._device) { + const element = await this.networkScreenBackButton; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.networkScreenBackButton); // Use static tap method with retry logic } } async tapBackButtonInSettingsScreen() { - const element = await this.settingsPageAndroidBackButton; if (!this._device) { + const element = await this.settingsPageAndroidBackButton; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.settingsPageAndroidBackButton); // Use static tap method with retry logic } } async tapCloseNetworkScreen() { - const element = await this.closeNetworkScreen; if (!this._device) { + const element = await this.closeNetworkScreen; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.closeNetworkScreen); // Use static tap method with retry logic } } async tapBackButton() { - const element = await this.networkScreenBackButton; if (!this._device) { + const element = await this.networkScreenBackButton; await Gestures.waitAndTap(element); } else { - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.networkScreenBackButton); // Use static tap method with retry logic } } } diff --git a/wdio/screen-objects/Onboarding/CreateNewWalletScreen.js b/wdio/screen-objects/Onboarding/CreateNewWalletScreen.js index cd277584a50..557fc05a7e5 100644 --- a/wdio/screen-objects/Onboarding/CreateNewWalletScreen.js +++ b/wdio/screen-objects/Onboarding/CreateNewWalletScreen.js @@ -11,6 +11,7 @@ import Gestures from "../../helpers/Gestures"; import Selectors from "../../helpers/Selectors"; import AppwrightSelectors from '../../../e2e/framework/AppwrightSelectors'; import { expect } from "appwright"; +import { ChoosePasswordSelectorsIDs } from "../../../e2e/selectors/Onboarding/ChoosePassword.selectors"; class CreateNewWalletScreen { get device() { @@ -98,7 +99,7 @@ class CreateNewWalletScreen { } else { return AppwrightSelectors.getElementByID( this._device, - I_UNDERSTAND_BUTTON_ID, + ChoosePasswordSelectorsIDs.I_UNDERSTAND_CHECKBOX_ID, ); } } @@ -131,8 +132,6 @@ class CreateNewWalletScreen { } else { const field = await this.newWalletPasswordConfirm; await field.fill(secondPassword); - const checkbox = await this.termsAndConditionCheckBox; - await checkbox.tap(); } } diff --git a/wdio/screen-objects/Onboarding/CreatePasswordScreen.js b/wdio/screen-objects/Onboarding/CreatePasswordScreen.js index 369ecffea4a..7eefeb08b65 100644 --- a/wdio/screen-objects/Onboarding/CreatePasswordScreen.js +++ b/wdio/screen-objects/Onboarding/CreatePasswordScreen.js @@ -96,7 +96,8 @@ class CreatePasswordScreen { if (!this._device) { await Gestures.waitAndTap(this.iUnderstandCheckbox); } else { - await AppwrightGestures.tap(this.iUnderstandCheckbox); // Use static tapElement method with retry logic + await AppwrightGestures.hideKeyboard(this._device); + await AppwrightGestures.tap(this.iUnderstandCheckbox); } } @@ -104,7 +105,7 @@ class CreatePasswordScreen { if (!this._device) { await Gestures.waitAndTap(this.submitButton); } else { - await AppwrightGestures.tap(this.submitButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.submitButton); } } diff --git a/wdio/screen-objects/Onboarding/ImportFromSeedScreen.js b/wdio/screen-objects/Onboarding/ImportFromSeedScreen.js index 02b2f9d0910..b6c229afc8c 100644 --- a/wdio/screen-objects/Onboarding/ImportFromSeedScreen.js +++ b/wdio/screen-objects/Onboarding/ImportFromSeedScreen.js @@ -59,7 +59,11 @@ class ImportFromSeedScreen { } } else { - return `srp-input-word-${String(srpIndex)}`; + if (AppwrightSelectors.isAndroid(this._device)) { + return `seed-phrase-input_${String(srpIndex)}`; + } else { + return `//*[@label="${srpIndex+1}."]`; + } } } @@ -72,7 +76,7 @@ class ImportFromSeedScreen { const element = await this.screenTitle; await appwrightExpect(element).toBeVisible({ timeout: 10000 }); } else { - const element = await AppwrightSelectors.getElementByText(this.device, 'Import Secret Recovery Phrase'); + const element = await AppwrightSelectors.getElementByText(this.device, 'Import a wallet'); await appwrightExpect(element).toBeVisible({ timeout: 10000 }); } } @@ -108,10 +112,12 @@ class ImportFromSeedScreen { const lastInput = AppwrightSelectors.isAndroid(this._device) ? await AppwrightSelectors.getElementByID(this.device, wordElement) : await AppwrightSelectors.getElementByXpath(this.device, wordElement); await AppwrightGestures.typeText(lastInput, lastWord); } else { - for (let i = 1; i <= phraseArray.length; i++) { + const firstWordImput = AppwrightSelectors.isAndroid(this._device) ? await AppwrightSelectors.getElementByID(this.device, 'seed-phrase-input') : await AppwrightSelectors.getElementByID(this.device, 'textfield'); + await AppwrightGestures.typeText(firstWordImput, `${phraseArray[0]} `); + for (let i = 1; i < phraseArray.length; i++) { const wordElement = await this.inputOfIndex(i, false); - const input = await AppwrightSelectors.getElementByID(this.device, wordElement); - await AppwrightGestures.typeText(input, `${phraseArray[i-1]} `); + const input = AppwrightSelectors.isAndroid(this._device) ? await AppwrightSelectors.getElementByID(this.device, wordElement) : await AppwrightSelectors.getElementByXpath(this.device, wordElement); + await AppwrightGestures.typeText(input, `${phraseArray[i]} `); await AppwrightGestures.tap(input); } } @@ -123,9 +129,8 @@ class ImportFromSeedScreen { if (!this._device) { await Gestures.waitAndTap(this.continueButton); } else { - const element = await this.continueButton; await AppwrightGestures.hideKeyboard(this.device); - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.continueButton); // Use static tap method with retry logic } } else { if (!this._device) { @@ -148,15 +153,18 @@ class ImportFromSeedScreen { if (!this._device) { await Gestures.waitAndTap(this.screenTitle); } else { - const element = await this.screenTitle; - await AppwrightGestures.tap(element); // Use static tap method with retry logic + await AppwrightGestures.tap(this.screenTitle); // Use static tap method with retry logic } } else { if (!this._device) { await Gestures.waitAndTap(this.screenTitle); } else { - const element = await AppwrightSelectors.getElementByText(this.device, 'Import Secret Recovery Phrase'); - await AppwrightGestures.tap(element); // Use static tap method with retry logic + if (AppwrightSelectors.isIOS(this._device)) { + const element = await AppwrightSelectors.getElementByText(this.device, 'Import a wallet'); + await AppwrightGestures.tap(element); + } else { + await AppwrightGestures.hideKeyboard(this.device); + } } } } diff --git a/wdio/screen-objects/Onboarding/MetaMetricsScreen.js b/wdio/screen-objects/Onboarding/MetaMetricsScreen.js index 20dae1fb8ce..a17b17329d6 100644 --- a/wdio/screen-objects/Onboarding/MetaMetricsScreen.js +++ b/wdio/screen-objects/Onboarding/MetaMetricsScreen.js @@ -75,7 +75,7 @@ class MetaMetricsScreen { await element.waitForEnabled(); await Gestures.waitAndTap(this.iAgreeButton); } else { - await AppwrightGestures.tap(this.iAgreeButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.iAgreeButton); } } diff --git a/wdio/screen-objects/Onboarding/OnboardingScreen.js b/wdio/screen-objects/Onboarding/OnboardingScreen.js index 12b213ade66..35444f53a87 100644 --- a/wdio/screen-objects/Onboarding/OnboardingScreen.js +++ b/wdio/screen-objects/Onboarding/OnboardingScreen.js @@ -39,7 +39,7 @@ class OnBoardingScreen { if (!this._device) { await Gestures.waitAndTap(this.existingWalletButton); } else { - await AppwrightGestures.tap(this.existingWalletButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.existingWalletButton); } } @@ -47,7 +47,7 @@ class OnBoardingScreen { if (!this._device) { await Gestures.waitAndTap(this.createNewWalletButton); } else { - await AppwrightGestures.tap(this.createNewWalletButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.createNewWalletButton); } } diff --git a/wdio/screen-objects/Onboarding/OnboardingSheet.js b/wdio/screen-objects/Onboarding/OnboardingSheet.js index 071632b0ef7..a4498ee941b 100644 --- a/wdio/screen-objects/Onboarding/OnboardingSheet.js +++ b/wdio/screen-objects/Onboarding/OnboardingSheet.js @@ -52,7 +52,7 @@ class OnboardingSheet { if (!this.device) { await Gestures.waitAndTap(this.importSeedButton); } else { - await AppwrightGestures.tap(this.importSeedButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.importSeedButton); } } diff --git a/wdio/screen-objects/PerpsDepositScreen.js b/wdio/screen-objects/PerpsDepositScreen.js index eacb5e375c0..cca9c75cd89 100644 --- a/wdio/screen-objects/PerpsDepositScreen.js +++ b/wdio/screen-objects/PerpsDepositScreen.js @@ -1,5 +1,6 @@ import AppwrightSelectors from '../../e2e/framework/AppwrightSelectors'; import AppwrightGestures from '../../e2e/framework/AppwrightGestures'; +import AmountScreen from './AmountScreen'; class PerpsDepositScreen { @@ -21,7 +22,7 @@ class PerpsDepositScreen { } get amountInput() { - return AppwrightSelectors.getElementByID(this._device, 'edit-amount-input'); + return AppwrightSelectors.getElementByID(this._device, 'custom-amount-input'); } get payWithButton() { @@ -36,13 +37,15 @@ class PerpsDepositScreen { await input.isVisible({ timeout: 15000 }); } - async selectPayTokenByText(networkId, token) { - const networkButton = await AppwrightSelectors.getElementByID(this._device, `asset-${networkId}-${token}`); - await AppwrightGestures.tap(networkButton); // Use static tap method with retry logic + async selectPayTokenByText(token) { + const tokenButton = await AppwrightSelectors.getElementByCatchAll(this._device, token); + await AppwrightGestures.tap(tokenButton); // Use static tap method with retry logic } async fillUsdAmount(amount) { - await AppwrightGestures.typeText(this.amountInput, String(amount)); + AmountScreen.device = this._device; + await AmountScreen.enterAmount(amount); + await AmountScreen.tapOnNextButton(); } async tapPayWith() { diff --git a/wdio/screen-objects/PerpsTabView.js b/wdio/screen-objects/PerpsTabView.js index 21443c1cb96..27e5547167e 100644 --- a/wdio/screen-objects/PerpsTabView.js +++ b/wdio/screen-objects/PerpsTabView.js @@ -35,7 +35,7 @@ class PerpsTabView { async tapOnboardingButton() { const button = await this.onboardingButton; await button.isVisible({ timeout: 5000 }); - await AppwrightGestures.tap(button); // Use static tap method with retry logic + await AppwrightGestures.tap(this.onboardingButton); // Use static tap method with retry logic } } diff --git a/wdio/screen-objects/PerpsTutorialScreen.js b/wdio/screen-objects/PerpsTutorialScreen.js index 6b6112d0713..6e3d32448cc 100644 --- a/wdio/screen-objects/PerpsTutorialScreen.js +++ b/wdio/screen-objects/PerpsTutorialScreen.js @@ -23,7 +23,7 @@ class PerpsTutorialScreen { // Legacy alias for backward compatibility get addFundsButton() { - return this.continueButton; + return AppwrightSelectors.getElementByCatchAll(this._device, 'Add funds'); } get skipButtonTutorial() { @@ -31,16 +31,16 @@ class PerpsTutorialScreen { } async tapContinue() { - await AppwrightGestures.tap(this.continueButton); // Use static tap method with retry logic + await AppwrightGestures.tap(this.continueButton); } // Legacy alias for backward compatibility async tapAddFunds() { - await this.tapContinue(); + await AppwrightGestures.tap(this.addFundsButton); } async tapSkip() { - await AppwrightGestures.tap(this.skipButton); // Use static tap method with retry logic + await AppwrightGestures.tap(this.skipButton); } async expectFirstScreenVisible() { @@ -49,9 +49,8 @@ class PerpsTutorialScreen { } async flowTapContinueTutorial(times = 1) { - const btn = await this.continueButton; for (let i = 0; i < times; i++) { - await AppwrightGestures.tap(btn); // Use static tap method with retry logic + await AppwrightGestures.tap(this.continueButton); } } } diff --git a/wdio/screen-objects/PredictDetailsScreen.js b/wdio/screen-objects/PredictDetailsScreen.js index 7eed899e070..fe317b44e80 100644 --- a/wdio/screen-objects/PredictDetailsScreen.js +++ b/wdio/screen-objects/PredictDetailsScreen.js @@ -89,8 +89,7 @@ class PredictDetailsScreen { const backButton = await this.backButton; await Gestures.waitAndTap(backButton); } else { - const backButton = await this.backButton; - await AppwrightGestures.tap(backButton); + await AppwrightGestures.tap(this.backButton); } } @@ -108,8 +107,7 @@ class PredictDetailsScreen { const positionsTab = await this.positionsTab; await Gestures.waitAndTap(positionsTab); } else { - const positionsTab = await this.positionsTab; - await AppwrightGestures.tap(positionsTab); + await AppwrightGestures.tap(this.positionsTab); } } @@ -118,8 +116,7 @@ class PredictDetailsScreen { const outcomesTab = await this.outcomesTab; await Gestures.waitAndTap(outcomesTab); } else { - const outcomesTab = await this.outcomesTab; - await AppwrightGestures.tap(outcomesTab); + await AppwrightGestures.tap(this.outcomesTab); } } diff --git a/wdio/screen-objects/SendScreen.js b/wdio/screen-objects/SendScreen.js index 2856da16c1c..58d2e1faedc 100644 --- a/wdio/screen-objects/SendScreen.js +++ b/wdio/screen-objects/SendScreen.js @@ -232,7 +232,8 @@ class SendScreen { await Gestures.tapTextByXpath(tokenName); } else { if (AppwrightSelectors.isAndroid(this._device)) { - const networkButton = await AppwrightSelectors.getEl(this._device, `asset-${networkName}`); + const networkButton = await AppwrightSelectors.getElementByID(this._device, `asset-${networkName}`); + await AppwrightGestures.tap(networkButton); const tokenButton = await AppwrightSelectors.getElementByID(this._device, `asset-${tokenSymbol}`); await AppwrightGestures.tap(tokenButton); } else { diff --git a/wdio/screen-objects/SwapScreen.js b/wdio/screen-objects/SwapScreen.js index 34258adc04e..caed54c3f2f 100644 --- a/wdio/screen-objects/SwapScreen.js +++ b/wdio/screen-objects/SwapScreen.js @@ -5,6 +5,7 @@ import { expect as appwrightExpect } from 'appwright'; import { PerpsWithdrawViewSelectorsIDs } from '../../e2e/selectors/Perps/Perps.selectors'; import { QuoteViewSelectorIDs,QuoteViewSelectorText } from '../../e2e/selectors/swaps/QuoteView.selectors'; import { SwapsViewSelectorsIDs } from '../../e2e/selectors/swaps/SwapsView.selectors'; +import { splitAmountIntoDigits } from 'appwright/utils/Utils'; class SwapScreen { @@ -57,7 +58,7 @@ class SwapScreen { async enterSourceTokenAmount(amount) { // Split amount into digits - const digits = this.splitAmountIntoDigits(amount); + const digits = splitAmountIntoDigits(amount); console.log('Amount digits:', digits); digits.forEach(async digit => { if (AppwrightSelectors.isAndroid(this._device)) { @@ -114,15 +115,6 @@ class SwapScreen { } } - // Helper method to split amount into digits - splitAmountIntoDigits(amount) { - // Convert to string and split into array of digits - return amount.toString().split('').map(char => { - // Return only numeric digits, filter out decimal points, commas, etc. - return /\d/.test(char) ? parseInt(char, 10) : char; - }); - } - async enterDestinationTokenAmount(amount) { await AppwrightGestures.typeText(this.destTokenInput, amount); } diff --git a/wdio/screen-objects/WalletMainScreen.js b/wdio/screen-objects/WalletMainScreen.js index 0fec6530257..858025dba0f 100644 --- a/wdio/screen-objects/WalletMainScreen.js +++ b/wdio/screen-objects/WalletMainScreen.js @@ -197,9 +197,9 @@ class WalletMainScreen { } else { if (AppwrightSelectors.isAndroid(this._device)) { let tokenName = await AppwrightSelectors.getElementByID(this._device, `asset-${token}`); // for some reason by Id does not work sometimeselse { - await tokenName.tap(); + await AppwrightGestures.tap(tokenName); } else { // if ios, click on any token that is visible - const anyToken = await AppwrightSelectors.getElementByXpath(this._device, `//*[@name="token-list"]//XCUIElementTypeOther[1]`); + const anyToken = await AppwrightSelectors.getElementByID(this._device, `asset-${token}`); await AppwrightGestures.tap(anyToken); await new Promise(resolve => setTimeout(resolve, 2000)); } @@ -223,14 +223,14 @@ class WalletMainScreen { if (!this._device) { await Gestures.waitAndTap(this.accountIcon); } else { - await AppwrightGestures.tap(this.accountIcon); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.accountIcon); } } async tapSwapButton() { if (!this._device) { await Gestures.waitAndTap(this.swapButton); } else { - await AppwrightGestures.tap(this.swapButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.swapButton); } } @@ -239,7 +239,7 @@ class WalletMainScreen { if (!this._device) { await Gestures.waitAndTap(await this.networkInNavBar); } else { - await AppwrightGestures.tap(this.networkInNavBar); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.networkInNavBar); } } @@ -280,7 +280,7 @@ class WalletMainScreen { await this.walletButton.waitForDisplayed(); } else { const element = await this.walletButton; - await appwrightExpect(element).toBeVisible({ timeout: 10000 }); + await appwrightExpect(element).toBeVisible({ timeout: 30000 }); } } @@ -307,7 +307,7 @@ class WalletMainScreen { if (!this._device) { await Gestures.waitAndTap(this.accountActionsButton); } else { - await AppwrightGestures.tap(this.accountActionsButton); // Use static tapElement method with retry logic + await AppwrightGestures.tap(this.accountActionsButton); } } diff --git a/yarn.lock b/yarn.lock index 83d636b0ae2..686a415f467 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22041,9 +22041,9 @@ __metadata: languageName: node linkType: hard -"appwright@patch:appwright@npm%3A0.1.45#./.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch::locator=metamask%40workspace%3A.": +"appwright@patch:appwright@npm%3A0.1.45#~/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch": version: 0.1.45 - resolution: "appwright@patch:appwright@npm%3A0.1.45#./.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch::version=0.1.45&hash=7499a8&locator=metamask%40workspace%3A." + resolution: "appwright@patch:appwright@npm%3A0.1.45#~/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch::version=0.1.45&hash=988851" dependencies: "@empiricalrun/llm": "npm:^0.9.25" "@ffmpeg-installer/ffmpeg": "npm:^1.1.0" @@ -22059,7 +22059,7 @@ __metadata: webdriver: "npm:^8.36.1" bin: appwright: dist/bin/index.js - checksum: 10/e2567f4e57df88b1a3de695ff0132d9fdffed057e50aacf53356374917c0d5d1de48f4c936d7d1bbf38f2ad406432055d09f1fa518892acb066792d812462682 + checksum: 10/498cb6f4f3422b605c67adf24717910839b5be7e831b0a23ced3b357a9d183d0726d09b9ab7869fdf99d798dacb7b8aff0c3bed0a4527787384d59166f22b1f6 languageName: node linkType: hard @@ -36164,7 +36164,7 @@ __metadata: appium-adb: "npm:^9.11.4" appium-uiautomator2-driver: "npm:4.2.7" appium-xcuitest-driver: "npm:5.16.1" - appwright: "npm:^0.1.45" + appwright: "patch:appwright@npm%3A0.1.45#~/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch" assert: "npm:^1.5.0" asyncstorage-down: "npm:4.2.0" axios: "npm:^1.8.2" From 905838fab60bac2af3424bc372bcfbc07093a775 Mon Sep 17 00:00:00 2001 From: Jorge Carrasco Date: Thu, 4 Dec 2025 11:49:05 +0100 Subject: [PATCH 3/3] feat: enable require-pr-numbers flag for changelog generation (#23661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## **Description** This PR updates the changelog workflows to use the new `@metamask/auto-changelog@5.3.0` which always filters out direct commits without PR numbers. This ensures all changelog entries represent reviewed and approved changes. **Changes:** - Update `@metamask/auto-changelog` from `^5.1.0` to `^5.3.0` - Update `update-release-changelog` workflow to use `github-tools@v1.1.2` The `--requirePrNumbers` flag is now always applied by default in github-tools, so no additional configuration is needed. This aligns `metamask-mobile` with `metamask-extension` for consistent changelog generation across platforms. ## **Changelog** CHANGELOG entry: null ## **Related issues** - Related to: [MetaMask/metamask-extension#38520](https://github.com/MetaMask/metamask-extension/pull/38520) - ✅ [MetaMask/auto-changelog#253](https://github.com/MetaMask/auto-changelog/pull/253) - Merged (v5.3.0) - ✅ [MetaMask/github-tools#181](https://github.com/MetaMask/github-tools/pull/181) - Merged and released as v1.1.2 ## **Manual testing steps** ```gherkin Feature: Changelog generation with PR number filtering Scenario: Only PR commits appear in changelog Given a release branch exists When a commit with an associated PR is merged Then the changelog should include that commit When a direct commit without PR is pushed Then the changelog should NOT include that commit ``` Tested on [consensys-test/metamask-extension-test](https://github.com/consensys-test/metamask-extension-test) with `release/1100.0.0`: - [Generated changelog](https://github.com/consensys-test/metamask-extension-test/blob/release/1100.0.0-Changelog/CHANGELOG.md) ## **Screenshots/Recordings** ### **Before** Direct commits without PR numbers would appear in the changelog. ### **After** Only commits with PR numbers (representing reviewed changes) appear in the changelog. ## **Pre-merge author checklist** - [x] 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). - [x] I've completed the PR template to the best of my ability - [x] I've included tests if applicable - [x] 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. --- .github/workflows/update-release-changelog.yml | 4 ++-- package.json | 2 +- yarn.lock | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/update-release-changelog.yml b/.github/workflows/update-release-changelog.yml index 1e74b277064..8e323839ac3 100644 --- a/.github/workflows/update-release-changelog.yml +++ b/.github/workflows/update-release-changelog.yml @@ -48,11 +48,11 @@ jobs: pull-requests: write steps: - name: Update Release Changelog - uses: MetaMask/github-tools/.github/actions/update-release-changelog@v1.1.0 + uses: MetaMask/github-tools/.github/actions/update-release-changelog@v1.1.2 with: release-branch: ${{ github.ref_name }} repository-url: ${{ github.server_url }}/${{ github.repository }} platform: mobile previous-version-ref: 'null' - github-tools-version: v1.1.0 + github-tools-version: v1.1.2 github-token: ${{ secrets.PR_TOKEN }} diff --git a/package.json b/package.json index 50d3ec87139..47a647c7b24 100644 --- a/package.json +++ b/package.json @@ -497,7 +497,7 @@ "@lavamoat/allow-scripts": "^3.0.4", "@lavamoat/git-safe-dependencies": "^0.2.1", "@metamask/abi-utils": "^3.0.0", - "@metamask/auto-changelog": "^5.1.0", + "@metamask/auto-changelog": "^5.3.0", "@metamask/browser-passworder": "^5.0.0", "@metamask/build-utils": "^3.0.0", "@metamask/eslint-config-typescript": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index 686a415f467..df4cae3b197 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7587,9 +7587,9 @@ __metadata: languageName: node linkType: hard -"@metamask/auto-changelog@npm:^5.1.0": - version: 5.1.0 - resolution: "@metamask/auto-changelog@npm:5.1.0" +"@metamask/auto-changelog@npm:^5.3.0": + version: 5.3.0 + resolution: "@metamask/auto-changelog@npm:5.3.0" dependencies: "@octokit/rest": "npm:^20.0.0" diff: "npm:^5.0.0" @@ -7600,7 +7600,7 @@ __metadata: prettier: ">=3.0.0" bin: auto-changelog: dist/cli.mjs - checksum: 10/ee9f651c313a2377a21a304383f56f1872b4ce6411a0b61cf0b70b23bff9d3204332cd5a052cdfcaf363ce15424a7a1fdbde99a6b4352f2472fbe6b6d27c5791 + checksum: 10/5381c2b1efbade000bafbbee7b1becbee1787b9f24849352d16ddd3b14f511f865b3478250301f3d22f98fe0208690f62f166a476e64d38ed58361d816a673b6 languageName: node linkType: hard @@ -35962,7 +35962,7 @@ __metadata: "@metamask/app-metadata-controller": "npm:^2.0.0" "@metamask/approval-controller": "npm:^8.0.0" "@metamask/assets-controllers": "patch:@metamask/assets-controllers@npm%3A92.0.0#~/.yarn/patches/@metamask-assets-controllers-npm-92.0.0-ea998cb0bd.patch" - "@metamask/auto-changelog": "npm:^5.1.0" + "@metamask/auto-changelog": "npm:^5.3.0" "@metamask/base-controller": "npm:^9.0.0" "@metamask/bitcoin-wallet-snap": "npm:^1.8.0" "@metamask/bridge-controller": "npm:^61.0.0"