Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/appium_Android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ on:
push:
branches:
- 3.x
- 5228-fix-appium-tests

env:
CI: true
Expand Down
170 changes: 145 additions & 25 deletions lib/helper/Appium.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down Expand Up @@ -619,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`,
Expand Down Expand Up @@ -1134,7 +1158,7 @@ class Appium extends Webdriver {
],
},
])
await this.browser.pause(1000)
await this.browser.pause(2000)
}

/**
Expand Down Expand Up @@ -1296,28 +1320,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,
Expand Down Expand Up @@ -1523,7 +1545,28 @@ 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, 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) {
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) {
throw err
}
}

/**
Expand Down Expand Up @@ -1577,7 +1620,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
}

/**
Expand Down Expand Up @@ -1656,7 +1710,30 @@ 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, 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)
}

const selected = []
for (const el of res) {
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) {
dontSeeElementError(parsedLocator)
}
}

/**
Expand Down Expand Up @@ -1703,7 +1780,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`,
},
)
}

/**
Expand All @@ -1712,7 +1812,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` },
)
}

/**
Expand Down
20 changes: 16 additions & 4 deletions test/helper/Appium_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ describe('Appium', function () {
platformName: 'Android',
platformVersion: '7.0',
deviceName: 'Android GoogleAPI Emulator',
automationName: 'UiAutomator2',
androidInstallTimeout: 90000,
appWaitDuration: 300000,
},
Expand Down Expand Up @@ -182,13 +183,15 @@ 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()
assert.equal(val, 'WEBVIEW_io.selendroid.testapp')
await app.see('Prefered Car')
assert.ok(app.isWeb)
await app.switchToNative()
await app.browser.pause(2000)
val = await app.grabContext()
assert.equal(val, 'NATIVE_APP')
return assert.ok(!app.isWeb)
Expand Down Expand Up @@ -292,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']", 120, 100)
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')
})
Expand Down Expand Up @@ -362,9 +365,12 @@ 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.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(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')
Expand All @@ -375,27 +381,33 @@ 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.swipeDown("//android.widget.LinearLayout[@resource-id = 'io.selendroid.testapp:id/LinearLayout1']", 1200, 1000)
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')
expect(parseInt(vy.split(' ')[1], 10)).to.be.above(-300)
})

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']")
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')
expect(vy.split(' ')[1]).to.be.below(730)
})

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']")
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')
Expand Down Expand Up @@ -557,7 +569,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')
Expand Down
Loading