Skip to content

Commit 743d0a1

Browse files
authored
fix: appium tests (#5230)
1 parent 1075552 commit 743d0a1

File tree

3 files changed

+162
-29
lines changed

3 files changed

+162
-29
lines changed

.github/workflows/appium_Android.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ on:
44
push:
55
branches:
66
- 3.x
7+
- 5228-fix-appium-tests
78

89
env:
910
CI: true

lib/helper/Appium.js

Lines changed: 145 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,29 @@ class Appium extends Webdriver {
391391
return `${protocol}://${hostname}:${port}${normalizedPath}/session/${this.browser.sessionId}`
392392
}
393393

394+
/**
395+
* Helper method to safely call isDisplayed() on mobile elements.
396+
* Handles the case where webdriverio tries to use execute/sync which isn't supported in Appium.
397+
* @private
398+
*/
399+
async _isDisplayedSafe(element) {
400+
if (this.isWeb) {
401+
// For web contexts, use the normal isDisplayed
402+
return element.isDisplayed()
403+
}
404+
405+
try {
406+
return await element.isDisplayed()
407+
} catch (err) {
408+
// If isDisplayed fails due to execute/sync not being supported in native mobile contexts,
409+
// fall back to assuming the element is displayed (since we found it)
410+
if (err.message && err.message.includes('Method is not implemented')) {
411+
return true
412+
}
413+
throw err
414+
}
415+
}
416+
394417
/**
395418
* Execute code only on iOS
396419
*
@@ -619,6 +642,7 @@ class Appium extends Webdriver {
619642
*/
620643
async resetApp() {
621644
onlyForApps.call(this)
645+
this.isWeb = false // Reset to native context after app reset
622646
return this.axios({
623647
method: 'post',
624648
url: `${this._buildAppiumEndpoint()}/appium/app/reset`,
@@ -1134,7 +1158,7 @@ class Appium extends Webdriver {
11341158
],
11351159
},
11361160
])
1137-
await this.browser.pause(1000)
1161+
await this.browser.pause(2000)
11381162
}
11391163

11401164
/**
@@ -1296,28 +1320,26 @@ class Appium extends Webdriver {
12961320
let currentSource
12971321
return browser
12981322
.waitUntil(
1299-
() => {
1323+
async () => {
13001324
if (err) {
13011325
return new Error(`Scroll to the end and element ${searchableLocator} was not found`)
13021326
}
1303-
return browser
1304-
.$$(parseLocator.call(this, searchableLocator))
1305-
.then(els => els.length && els[0].isDisplayed())
1306-
.then(res => {
1307-
if (res) {
1308-
return true
1309-
}
1310-
return this[direction](scrollLocator, offset, speed)
1311-
.getSource()
1312-
.then(source => {
1313-
if (source === currentSource) {
1314-
err = true
1315-
} else {
1316-
currentSource = source
1317-
return false
1318-
}
1319-
})
1320-
})
1327+
const els = await browser.$$(parseLocator.call(this, searchableLocator))
1328+
if (els.length) {
1329+
const displayed = await this._isDisplayedSafe(els[0])
1330+
if (displayed) {
1331+
return true
1332+
}
1333+
}
1334+
1335+
await this[direction](scrollLocator, offset, speed)
1336+
const source = await this.browser.getPageSource()
1337+
if (source === currentSource) {
1338+
err = true
1339+
} else {
1340+
currentSource = source
1341+
return false
1342+
}
13211343
},
13221344
timeout * 1000,
13231345
errorMsg,
@@ -1523,7 +1545,28 @@ class Appium extends Webdriver {
15231545
*/
15241546
async dontSeeElement(locator) {
15251547
if (this.isWeb) return super.dontSeeElement(locator)
1526-
return super.dontSeeElement(parseLocator.call(this, locator))
1548+
1549+
// For mobile native apps, use safe isDisplayed wrapper
1550+
const parsedLocator = parseLocator.call(this, locator)
1551+
const res = await this._locate(parsedLocator, false)
1552+
const { truth } = require('../assert/truth')
1553+
const Locator = require('../locator')
1554+
1555+
if (!res || res.length === 0) {
1556+
return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(false)
1557+
}
1558+
1559+
const selected = []
1560+
for (const el of res) {
1561+
const displayed = await this._isDisplayedSafe(el)
1562+
if (displayed) selected.push(true)
1563+
}
1564+
1565+
try {
1566+
return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(selected)
1567+
} catch (err) {
1568+
throw err
1569+
}
15271570
}
15281571

15291572
/**
@@ -1577,7 +1620,18 @@ class Appium extends Webdriver {
15771620
*/
15781621
async grabNumberOfVisibleElements(locator) {
15791622
if (this.isWeb) return super.grabNumberOfVisibleElements(locator)
1580-
return super.grabNumberOfVisibleElements(parseLocator.call(this, locator))
1623+
1624+
// For mobile native apps, use safe isDisplayed wrapper
1625+
const parsedLocator = parseLocator.call(this, locator)
1626+
const res = await this._locate(parsedLocator)
1627+
1628+
const selected = []
1629+
for (const el of res) {
1630+
const displayed = await this._isDisplayedSafe(el)
1631+
if (displayed) selected.push(true)
1632+
}
1633+
1634+
return selected.length
15811635
}
15821636

15831637
/**
@@ -1656,7 +1710,30 @@ class Appium extends Webdriver {
16561710
*/
16571711
async seeElement(locator) {
16581712
if (this.isWeb) return super.seeElement(locator)
1659-
return super.seeElement(parseLocator.call(this, locator))
1713+
1714+
// For mobile native apps, use safe isDisplayed wrapper
1715+
const parsedLocator = parseLocator.call(this, locator)
1716+
const res = await this._locate(parsedLocator, true)
1717+
const ElementNotFound = require('./errors/ElementNotFound')
1718+
const { truth } = require('../assert/truth')
1719+
const { dontSeeElementError } = require('./errors/ElementAssertion')
1720+
const Locator = require('../locator')
1721+
1722+
if (!res || res.length === 0) {
1723+
throw new ElementNotFound(parsedLocator)
1724+
}
1725+
1726+
const selected = []
1727+
for (const el of res) {
1728+
const displayed = await this._isDisplayedSafe(el)
1729+
if (displayed) selected.push(true)
1730+
}
1731+
1732+
try {
1733+
return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').assert(selected)
1734+
} catch (e) {
1735+
dontSeeElementError(parsedLocator)
1736+
}
16601737
}
16611738

16621739
/**
@@ -1703,7 +1780,30 @@ class Appium extends Webdriver {
17031780
*/
17041781
async waitForVisible(locator, sec = null) {
17051782
if (this.isWeb) return super.waitForVisible(locator, sec)
1706-
return super.waitForVisible(parseLocator.call(this, locator), sec)
1783+
1784+
// For mobile native apps, use safe isDisplayed wrapper
1785+
const parsedLocator = parseLocator.call(this, locator)
1786+
const aSec = sec || this.options.waitForTimeoutInSeconds
1787+
const Locator = require('../locator')
1788+
1789+
return this.browser.waitUntil(
1790+
async () => {
1791+
const res = await this._res(parsedLocator)
1792+
if (!res || res.length === 0) return false
1793+
1794+
const selected = []
1795+
for (const el of res) {
1796+
const displayed = await this._isDisplayedSafe(el)
1797+
if (displayed) selected.push(true)
1798+
}
1799+
1800+
return selected.length > 0
1801+
},
1802+
{
1803+
timeout: aSec * 1000,
1804+
timeoutMsg: `element (${new Locator(parsedLocator)}) still not visible after ${aSec} sec`,
1805+
},
1806+
)
17071807
}
17081808

17091809
/**
@@ -1712,7 +1812,27 @@ class Appium extends Webdriver {
17121812
*/
17131813
async waitForInvisible(locator, sec = null) {
17141814
if (this.isWeb) return super.waitForInvisible(locator, sec)
1715-
return super.waitForInvisible(parseLocator.call(this, locator), sec)
1815+
1816+
// For mobile native apps, use safe isDisplayed wrapper
1817+
const parsedLocator = parseLocator.call(this, locator)
1818+
const aSec = sec || this.options.waitForTimeoutInSeconds
1819+
const Locator = require('../locator')
1820+
1821+
return this.browser.waitUntil(
1822+
async () => {
1823+
const res = await this._res(parsedLocator)
1824+
if (!res || res.length === 0) return true
1825+
1826+
const selected = []
1827+
for (const el of res) {
1828+
const displayed = await this._isDisplayedSafe(el)
1829+
if (displayed) selected.push(true)
1830+
}
1831+
1832+
return selected.length === 0
1833+
},
1834+
{ timeout: aSec * 1000, timeoutMsg: `element (${new Locator(parsedLocator)}) still visible after ${aSec} sec` },
1835+
)
17161836
}
17171837

17181838
/**

test/helper/Appium_test.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ describe('Appium', function () {
3131
platformName: 'Android',
3232
platformVersion: '7.0',
3333
deviceName: 'Android GoogleAPI Emulator',
34+
automationName: 'UiAutomator2',
3435
androidInstallTimeout: 90000,
3536
appWaitDuration: 300000,
3637
},
@@ -182,13 +183,15 @@ describe('Appium', function () {
182183
it('should switch to native and web contexts @quick', async () => {
183184
await app.resetApp()
184185
await app.tap('~buttonStartWebviewCD')
186+
await app.browser.pause(1000)
185187
await app.see('WebView location')
186188
await app.switchToWeb()
187189
let val = await app.grabContext()
188190
assert.equal(val, 'WEBVIEW_io.selendroid.testapp')
189191
await app.see('Prefered Car')
190192
assert.ok(app.isWeb)
191193
await app.switchToNative()
194+
await app.browser.pause(2000)
192195
val = await app.grabContext()
193196
assert.equal(val, 'NATIVE_APP')
194197
return assert.ok(!app.isWeb)
@@ -292,7 +295,7 @@ describe('Appium', function () {
292295
await app.resetApp()
293296
await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']")
294297
await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
295-
await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 120, 100)
298+
await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000)
296299
const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
297300
assert.equal(type, 'FLICK')
298301
})
@@ -362,9 +365,12 @@ describe('Appium', function () {
362365

363366
describe('#performTouchAction', () => {
364367
it('should react on swipeUp action @second', async () => {
368+
await app.resetApp()
365369
await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']")
366370
await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
367-
await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
371+
await app.swipeUp("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000)
372+
await app.browser.pause(3000)
373+
await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 20)
368374
const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
369375
const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']")
370376
assert.equal(type, 'FLICK')
@@ -375,27 +381,33 @@ describe('Appium', function () {
375381
await app.resetApp()
376382
await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']")
377383
await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
378-
await app.swipeUp("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
384+
await app.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000)
385+
await app.browser.pause(3000)
386+
await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 20)
379387
const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
380388
const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']")
381389
assert.equal(type, 'FLICK')
382390
expect(parseInt(vy.split(' ')[1], 10)).to.be.above(-300)
383391
})
384392

385393
it('should react on swipeLeft action', async () => {
394+
await app.resetApp()
386395
await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']")
387396
await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
388397
await app.swipeLeft("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
398+
await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5)
389399
const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
390400
const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']")
391401
assert.equal(type, 'FLICK')
392402
expect(vy.split(' ')[1]).to.be.below(730)
393403
})
394404

395405
it('should react on swipeRight action', async () => {
406+
await app.resetApp()
396407
await app.tap("//android.widget.Button[@resource-id = 'io.selendroid.testapp:id/touchTest']")
397408
await app.waitForText('Gesture Type', 10, "//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
398409
await app.swipeRight("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
410+
await app.waitForElement("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']", 5)
399411
const type = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/gesture_type_text_view']")
400412
const vy = await app.grabTextFrom("//android.widget.TextView[@resource-id = 'io.selendroid.testapp:id/text_view4']")
401413
assert.equal(type, 'FLICK')
@@ -557,7 +569,7 @@ describe('Appium', function () {
557569
await app.tap('~email of the customer')
558570
await app.appendField('//android.widget.EditText[@content-desc="email of the customer"]', '1')
559571
await app.hideDeviceKeyboard('pressKey', 'Done')
560-
await app.swipeTo('//android.widget.Button', '//android.widget.ScrollView/android.widget.LinearLayout', 'up', 30, 100, 700)
572+
await app.swipeTo('//android.widget.Button', '//android.widget.ScrollView/android.widget.LinearLayout', 'up', 60, 100, 700)
561573
await app.tap('//android.widget.Button')
562574
await app.see('1', '//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]')
563575
const id = await app.grabNumberOfVisibleElements('//android.widget.TextView[@resource-id="io.selendroid.testapp:id/label_email_data"]', 'contentDescription')

0 commit comments

Comments
 (0)