diff --git a/.depcheckrc.yml b/.depcheckrc.yml index 6084d59a6b30..8bc400aa92a1 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/.github/workflows/update-release-changelog.yml b/.github/workflows/update-release-changelog.yml index 1e74b2770646..8e323839ac3b 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/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch b/.yarn/patches/appwright-npm-0.1.45-f282bc1c1b.patch index f49973b123ae..7bc12aad2a92 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 000000000000..024574d73386 --- /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/app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts b/app/components/UI/Perps/providers/channels/CandleStreamChannel.test.ts index fc132c8e6722..af81e1cd2c15 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 9c70076fc068..8d1b818a18db 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); diff --git a/appwright/appwright.config.ts b/appwright/appwright.config.ts index b13ab79a7acf..f426c3b31040 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 7650dca07094..dbfbecc9c0a2 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 379c31e5c5b8..0b2fe6a2eddf 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 82b48fed1366..6469d49061bf 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 e228e1525337..77a4f29fcf5e 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 b4c28c65e36e..ca0b751d3ff7 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 a9f1dd8ee1de..4e8e5072de0c 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 7070711d4593..67a54612981c 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 f0d783ada499..67cb94c5d438 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 2d44ac8fec58..6017beefd4ce 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 1a316d4f05d3..89f52ec41830 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 94a9bdbd5181..0ce0b3ce6bb7 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 e05580b5a949..a20dbadba9a1 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 beb74e9eb315..93939030e4c6 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 000000000000..c6cb07bb077d --- /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 005d9bff6c91..e98ee7c6b46a 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 4f7e3e7a96c9..47a647c7b24d 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", @@ -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 904e32f1ffbe..ff7dc48ee50e 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 417c957c78f2..bbfe3f24b626 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 3381dc86b12d..c29f50ece017 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 5e13ebbda486..a3048aed6df8 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 ecfb1f5fdc8d..9ddb408ef9df 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 bab6fd03b11d..90111a8228ac 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 4c538e634567..ca966bde241d 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 3dae2fe623f6..3cf56e11e9ec 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 cd277584a501..557fc05a7e5b 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 369ecffea4aa..7eefeb08b655 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 02b2f9d09100..b6c229afc8cf 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 20dae1fb8ce4..a17b17329d6d 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 12b213ade66e..35444f53a878 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 071632b0ef7d..a4498ee941b4 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 eacb5e375c0d..cca9c75cd897 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 21443c1cb96b..27e5547167e7 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 6b6112d07136..6e3d32448cc7 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 7eed899e070a..fe317b44e801 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 2856da16c1c6..58d2e1faedc7 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 34258adc04ee..caed54c3f2f0 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 0fec6530257d..858025dba0fe 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 83d636b0ae26..df4cae3b1974 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 @@ -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 @@ -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" @@ -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"