From b5b420f95c62a31895b588304ed0b442c5fa5dd3 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 12:59:28 +0000 Subject: [PATCH 01/13] fix: appium tests --- .github/workflows/appium_Android.yml | 1 + lib/helper/Appium.js | 72 +++++++++++++++++++++++++++- test/helper/Appium_test.js | 1 + 3 files changed, 72 insertions(+), 2 deletions(-) diff --git a/.github/workflows/appium_Android.yml b/.github/workflows/appium_Android.yml index 58cd61fce..8a1a30000 100644 --- a/.github/workflows/appium_Android.yml +++ b/.github/workflows/appium_Android.yml @@ -4,6 +4,7 @@ on: push: branches: - 3.x + - 5228-fix-appium-tests env: CI: true diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index a092e1b31..02be636da 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -1523,7 +1523,39 @@ class Appium extends Webdriver { */ async dontSeeElement(locator) { if (this.isWeb) return super.dontSeeElement(locator) - return super.dontSeeElement(parseLocator.call(this, locator)) + + // For mobile native apps, handle isDisplayed error gracefully + const parsedLocator = parseLocator.call(this, locator) + const res = await this._locate(parsedLocator, false) + const { truth } = require('../assert/truth') + const Locator = require('../locator') + + if (!res || res.length === 0) { + return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(false) + } + + const selected = [] + for (const el of res) { + try { + const displayed = await el.isDisplayed() + if (displayed) selected.push(true) + } catch (err) { + // If isDisplayed fails due to execute/sync not being supported, + // fall back to checking if element exists (which we already verified) + if (err.message && err.message.includes('Method is not implemented')) { + // Element exists, assume it's displayed + selected.push(true) + } else { + throw err + } + } + } + + try { + return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(selected) + } catch (err) { + throw err + } } /** @@ -1656,7 +1688,43 @@ class Appium extends Webdriver { */ async seeElement(locator) { if (this.isWeb) return super.seeElement(locator) - return super.seeElement(parseLocator.call(this, locator)) + + // For mobile native apps, we need to handle isDisplayed differently + // because webdriverio's isDisplayed() tries to execute JavaScript which isn't supported + const parsedLocator = parseLocator.call(this, locator) + const res = await this._locate(parsedLocator, true) + const ElementNotFound = require('./errors/ElementNotFound') + const { truth } = require('../assert/truth') + const { dontSeeElementError } = require('./errors/ElementAssertion') + const Locator = require('../locator') + + if (!res || res.length === 0) { + throw new ElementNotFound(parsedLocator) + } + + // Use a safer approach for mobile: check if element exists and try isDisplayed with error handling + const selected = [] + for (const el of res) { + try { + const displayed = await el.isDisplayed() + if (displayed) selected.push(true) + } catch (err) { + // If isDisplayed fails due to execute/sync not being supported, + // fall back to checking if element exists (which we already verified) + if (err.message && err.message.includes('Method is not implemented')) { + // Element exists, assume it's displayed since we found it + selected.push(true) + } else { + throw err + } + } + } + + try { + return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').assert(selected) + } catch (e) { + dontSeeElementError(parsedLocator) + } } /** diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 5d7f47f6d..1a1b747ee 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -31,6 +31,7 @@ describe('Appium', function () { platformName: 'Android', platformVersion: '7.0', deviceName: 'Android GoogleAPI Emulator', + automationName: 'UiAutomator2', androidInstallTimeout: 90000, appWaitDuration: 300000, }, From 6d80f03323d3d02d8247d0f3366287a56dfbc504 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:13:33 +0000 Subject: [PATCH 02/13] fix: issues --- lib/helper/Appium.js | 135 ++++++++++++++++++++++++++++++------------- 1 file changed, 94 insertions(+), 41 deletions(-) diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index 02be636da..5a2a06b9e 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -391,6 +391,29 @@ class Appium extends Webdriver { return `${protocol}://${hostname}:${port}${normalizedPath}/session/${this.browser.sessionId}` } + /** + * Helper method to safely call isDisplayed() on mobile elements. + * Handles the case where webdriverio tries to use execute/sync which isn't supported in Appium. + * @private + */ + async _isDisplayedSafe(element) { + if (this.isWeb) { + // For web contexts, use the normal isDisplayed + return element.isDisplayed() + } + + try { + return await element.isDisplayed() + } catch (err) { + // If isDisplayed fails due to execute/sync not being supported in native mobile contexts, + // fall back to assuming the element is displayed (since we found it) + if (err.message && err.message.includes('Method is not implemented')) { + return true + } + throw err + } + } + /** * Execute code only on iOS * @@ -1523,34 +1546,23 @@ class Appium extends Webdriver { */ async dontSeeElement(locator) { if (this.isWeb) return super.dontSeeElement(locator) - - // For mobile native apps, handle isDisplayed error gracefully + + // For mobile native apps, use safe isDisplayed wrapper const parsedLocator = parseLocator.call(this, locator) const res = await this._locate(parsedLocator, false) const { truth } = require('../assert/truth') const Locator = require('../locator') - + if (!res || res.length === 0) { return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(false) } - + const selected = [] for (const el of res) { - try { - const displayed = await el.isDisplayed() - if (displayed) selected.push(true) - } catch (err) { - // If isDisplayed fails due to execute/sync not being supported, - // fall back to checking if element exists (which we already verified) - if (err.message && err.message.includes('Method is not implemented')) { - // Element exists, assume it's displayed - selected.push(true) - } else { - throw err - } - } + const displayed = await this._isDisplayedSafe(el) + if (displayed) selected.push(true) } - + try { return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(selected) } catch (err) { @@ -1609,7 +1621,18 @@ class Appium extends Webdriver { */ async grabNumberOfVisibleElements(locator) { if (this.isWeb) return super.grabNumberOfVisibleElements(locator) - return super.grabNumberOfVisibleElements(parseLocator.call(this, locator)) + + // For mobile native apps, use safe isDisplayed wrapper + const parsedLocator = parseLocator.call(this, locator) + const res = await this._locate(parsedLocator) + + const selected = [] + for (const el of res) { + const displayed = await this._isDisplayedSafe(el) + if (displayed) selected.push(true) + } + + return selected.length } /** @@ -1688,38 +1711,25 @@ class Appium extends Webdriver { */ async seeElement(locator) { if (this.isWeb) return super.seeElement(locator) - - // For mobile native apps, we need to handle isDisplayed differently - // because webdriverio's isDisplayed() tries to execute JavaScript which isn't supported + + // For mobile native apps, use safe isDisplayed wrapper const parsedLocator = parseLocator.call(this, locator) const res = await this._locate(parsedLocator, true) const ElementNotFound = require('./errors/ElementNotFound') const { truth } = require('../assert/truth') const { dontSeeElementError } = require('./errors/ElementAssertion') const Locator = require('../locator') - + if (!res || res.length === 0) { throw new ElementNotFound(parsedLocator) } - - // Use a safer approach for mobile: check if element exists and try isDisplayed with error handling + const selected = [] for (const el of res) { - try { - const displayed = await el.isDisplayed() - if (displayed) selected.push(true) - } catch (err) { - // If isDisplayed fails due to execute/sync not being supported, - // fall back to checking if element exists (which we already verified) - if (err.message && err.message.includes('Method is not implemented')) { - // Element exists, assume it's displayed since we found it - selected.push(true) - } else { - throw err - } - } + const displayed = await this._isDisplayedSafe(el) + if (displayed) selected.push(true) } - + try { return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').assert(selected) } catch (e) { @@ -1771,7 +1781,30 @@ class Appium extends Webdriver { */ async waitForVisible(locator, sec = null) { if (this.isWeb) return super.waitForVisible(locator, sec) - return super.waitForVisible(parseLocator.call(this, locator), sec) + + // For mobile native apps, use safe isDisplayed wrapper + const parsedLocator = parseLocator.call(this, locator) + const aSec = sec || this.options.waitForTimeoutInSeconds + const Locator = require('../locator') + + return this.browser.waitUntil( + async () => { + const res = await this._res(parsedLocator) + if (!res || res.length === 0) return false + + const selected = [] + for (const el of res) { + const displayed = await this._isDisplayedSafe(el) + if (displayed) selected.push(true) + } + + return selected.length > 0 + }, + { + timeout: aSec * 1000, + timeoutMsg: `element (${new Locator(parsedLocator)}) still not visible after ${aSec} sec`, + }, + ) } /** @@ -1780,7 +1813,27 @@ class Appium extends Webdriver { */ async waitForInvisible(locator, sec = null) { if (this.isWeb) return super.waitForInvisible(locator, sec) - return super.waitForInvisible(parseLocator.call(this, locator), sec) + + // For mobile native apps, use safe isDisplayed wrapper + const parsedLocator = parseLocator.call(this, locator) + const aSec = sec || this.options.waitForTimeoutInSeconds + const Locator = require('../locator') + + return this.browser.waitUntil( + async () => { + const res = await this._res(parsedLocator) + if (!res || res.length === 0) return true + + const selected = [] + for (const el of res) { + const displayed = await this._isDisplayedSafe(el) + if (displayed) selected.push(true) + } + + return selected.length === 0 + }, + { timeout: aSec * 1000, timeoutMsg: `element (${new Locator(parsedLocator)}) still visible after ${aSec} sec` }, + ) } /** From 54f4b58c7ce8b071b0396bde70f0a410ace4bd64 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:36:51 +0000 Subject: [PATCH 03/13] Fix additional Appium test issues: swipe parameters, context switch timing, and swipeTo timeout --- test/helper/Appium_test.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 1a1b747ee..be46b6716 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -190,6 +190,7 @@ describe('Appium', function () { await app.see('Prefered Car') assert.ok(app.isWeb) await app.switchToNative() + await app.browser.pause(1000) val = await app.grabContext() assert.equal(val, 'NATIVE_APP') return assert.ok(!app.isWeb) @@ -293,7 +294,7 @@ describe('Appium', function () { await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 120, 100) + await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 500, 500) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") assert.equal(type, 'FLICK') }) @@ -366,6 +367,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -377,6 +379,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -387,6 +390,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeLeft("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -397,6 +401,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeRight("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -558,7 +563,7 @@ describe('Appium', function () { await app.tap('~email of the customer') await app.appendField('//android.widget.EditText[@content-desc="email of the customer"]', '1') await app.hideDeviceKeyboard('pressKey', 'Done') - await app.swipeTo('//android.widget.Button', '//android.widget.ScrollView/android.widget.LinearLayout', 'up', 30, 100, 700) + await app.swipeTo('//android.widget.Button', '//android.widget.ScrollView/android.widget.LinearLayout', 'up', 60, 100, 700) await app.tap('//android.widget.Button') await app.see('1', '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]') const id = await app.grabNumberOfVisibleElements('//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', 'contentDescription') From 59e2548926ab98f43081db1ba430a55f482e6807 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:47:27 +0000 Subject: [PATCH 04/13] Fix swipeTo to use _isDisplayedSafe and increase wait timeouts for swipe tests --- lib/helper/Appium.js | 36 +++++++++++++++++------------------- test/helper/Appium_test.js | 4 ++-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index 5a2a06b9e..7284b4fdb 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -1319,28 +1319,26 @@ class Appium extends Webdriver { let currentSource return browser .waitUntil( - () => { + async () => { if (err) { return new Error(`Scroll to the end and element ${searchableLocator} was not found`) } - return browser - .$$(parseLocator.call(this, searchableLocator)) - .then(els => els.length && els[0].isDisplayed()) - .then(res => { - if (res) { - return true - } - return this[direction](scrollLocator, offset, speed) - .getSource() - .then(source => { - if (source === currentSource) { - err = true - } else { - currentSource = source - return false - } - }) - }) + const els = await browser.$$(parseLocator.call(this, searchableLocator)) + if (els.length) { + const displayed = await this._isDisplayedSafe(els[0]) + if (displayed) { + return true + } + } + + await this[direction](scrollLocator, offset, speed) + const source = await this.browser.getPageSource() + if (source === currentSource) { + err = true + } else { + currentSource = source + return false + } }, timeout * 1000, errorMsg, diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index be46b6716..ea59c694a 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -367,7 +367,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5) + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 10) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -379,7 +379,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5) + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 10) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') From 8da30a4b1a868204501f17b278efba58296873a0 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 14:56:06 +0000 Subject: [PATCH 05/13] Increase pause after swipe operations to allow UI to settle --- lib/helper/Appium.js | 2 +- test/helper/Appium_test.js | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index 7284b4fdb..79bed2536 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -1157,7 +1157,7 @@ class Appium extends Webdriver { ], }, ]) - await this.browser.pause(1000) + await this.browser.pause(2000) } /** diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index ea59c694a..2b5050c03 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -367,6 +367,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.browser.pause(1000) await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 10) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") @@ -379,6 +380,7 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.browser.pause(1000) await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 10) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") From 936a4c308aea3f2d06c13d945e6e4d75ad1a4611 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:04:22 +0000 Subject: [PATCH 06/13] Increase pause after switchToNative to prevent stale element errors --- test/helper/Appium_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 2b5050c03..78b6d23f8 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -190,7 +190,7 @@ describe('Appium', function () { await app.see('Prefered Car') assert.ok(app.isWeb) await app.switchToNative() - await app.browser.pause(1000) + await app.browser.pause(2000) val = await app.grabContext() assert.equal(val, 'NATIVE_APP') return assert.ok(!app.isWeb) From ffce7156a1f1db9f8a6e4f2b7a95279606674d91 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:11:17 +0000 Subject: [PATCH 07/13] Increase pause and timeout for performTouchAction swipe tests --- test/helper/Appium_test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 78b6d23f8..07f363476 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -367,8 +367,8 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.browser.pause(1000) - await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 10) + await app.browser.pause(2000) + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -380,8 +380,8 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.browser.pause(1000) - await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 10) + await app.browser.pause(2000) + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') From 93804c6e62e7d6d892fbd827f2f86322cfbf85ab Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:18:13 +0000 Subject: [PATCH 08/13] Reset isWeb flag to false in resetApp() to fix context switching --- lib/helper/Appium.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/helper/Appium.js b/lib/helper/Appium.js index 79bed2536..b62fdda49 100644 --- a/lib/helper/Appium.js +++ b/lib/helper/Appium.js @@ -642,6 +642,7 @@ class Appium extends Webdriver { */ async resetApp() { onlyForApps.call(this) + this.isWeb = false // Reset to native context after app reset return this.axios({ method: 'post', url: `${this._buildAppiumEndpoint()}/appium/app/reset`, From 6808bb37dfcb5c30c5e582aa46ba6136a4853b1c Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:24:27 +0000 Subject: [PATCH 09/13] Add pause after tap to allow webview screen to load and prevent stale elements --- test/helper/Appium_test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 07f363476..351376ff5 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -183,6 +183,7 @@ describe('Appium', function () { it('should switch to native and web contexts @quick', async () => { await app.resetApp() await app.tap('~buttonStartWebviewCD') + await app.browser.pause(1000) await app.see('WebView location') await app.switchToWeb() let val = await app.grabContext() From 6047fa9972dc7806981fd7d6915859e8322a842a Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:31:42 +0000 Subject: [PATCH 10/13] Fix swipe gesture parameters: increase distance to generate FLICK and use LinearLayout1 for scrolling --- test/helper/Appium_test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 351376ff5..8bead9cdf 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -295,7 +295,7 @@ describe('Appium', function () { await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 500, 500) + await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 800, 800) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") assert.equal(type, 'FLICK') }) @@ -367,7 +367,7 @@ describe('Appium', function () { it('should react on swipeUp action @second', async () => { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.swipeUp("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) await app.browser.pause(2000) await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") @@ -380,7 +380,7 @@ describe('Appium', function () { await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") + await app.swipeUp("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) await app.browser.pause(2000) await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") From c07fa0da9dd505ec80894cb1ecaa57c27cf4a9ee Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:42:43 +0000 Subject: [PATCH 11/13] Fix swipe tests: increase distance to 1200,1000 and correct swipeDown method call --- test/helper/Appium_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 8bead9cdf..bf141170d 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -295,7 +295,7 @@ describe('Appium', function () { await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 800, 800) + await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") assert.equal(type, 'FLICK') }) @@ -380,7 +380,7 @@ describe('Appium', function () { await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") - await app.swipeUp("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) + await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) await app.browser.pause(2000) await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") From 314b3749d5334d1e902c3029d2f6e75b0a290c6d Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:49:38 +0000 Subject: [PATCH 12/13] Increase pause to 3s and timeout to 20s for flaky performTouchAction swipe tests --- test/helper/Appium_test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index bf141170d..6a66150b8 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -368,8 +368,8 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) - await app.browser.pause(2000) - await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) + await app.browser.pause(3000) + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 20) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') @@ -381,8 +381,8 @@ describe('Appium', function () { await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) - await app.browser.pause(2000) - await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 15) + await app.browser.pause(3000) + await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 20) const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']") assert.equal(type, 'FLICK') From dde2db262c8b778ed9b6619004aa9fd7825a8219 Mon Sep 17 00:00:00 2001 From: kobenguyent <7845001+kobenguyent@users.noreply.github.com> Date: Wed, 1 Oct 2025 15:53:56 +0000 Subject: [PATCH 13/13] Add resetApp() to all performTouchAction tests for test isolation and stability --- test/helper/Appium_test.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/helper/Appium_test.js b/test/helper/Appium_test.js index 6a66150b8..6266e8127 100644 --- a/test/helper/Appium_test.js +++ b/test/helper/Appium_test.js @@ -365,6 +365,7 @@ describe('Appium', function () { describe('#performTouchAction', () => { it('should react on swipeUp action @second', async () => { + await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeUp("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000) @@ -390,6 +391,7 @@ describe('Appium', function () { }) it('should react on swipeLeft action', async () => { + await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeLeft("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") @@ -401,6 +403,7 @@ describe('Appium', function () { }) it('should react on swipeRight action', async () => { + await app.resetApp() await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']") await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']") await app.swipeRight("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")