Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
65 changes: 63 additions & 2 deletions lib/helper/Appium.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const { truth } = require('../assert/truth')
const recorder = require('../recorder')
const Locator = require('../locator')
const ConnectionRefused = require('./errors/ConnectionRefused')
const { dontSeeElementError, seeElementError } = require('./errors/ElementAssertion')
const { getElementId } = require('../utils')

const mobileRoot = '//*'
const webRoot = 'body'
Expand Down Expand Up @@ -1523,7 +1525,36 @@ class Appium extends Webdriver {
*/
async dontSeeElement(locator) {
if (this.isWeb) return super.dontSeeElement(locator)
return super.dontSeeElement(parseLocator.call(this, locator))

// For mobile apps, use native display check instead of JavaScript execution
const parsedLocator = parseLocator.call(this, locator)
const res = await this._locate(parsedLocator, false)

if (!res || res.length === 0) {
return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(false)
}

// Use native isDisplayed() method without JavaScript execution for mobile
const selected = []
for (let i = 0; i < res.length; i++) {
try {
// Get element ID using utility function
const elementId = getElementId(res[i])

// Use the native WebDriver isDisplayed method directly
const isDisplayed = await this.browser.isElementDisplayed(elementId)
selected.push(isDisplayed)
} catch (err) {
// If native method fails, element is not displayed
selected.push(false)
}
}

try {
return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').negate(selected)
} catch (e) {
seeElementError(parsedLocator)
}
}

/**
Expand Down Expand Up @@ -1656,7 +1687,37 @@ class Appium extends Webdriver {
*/
async seeElement(locator) {
if (this.isWeb) return super.seeElement(locator)
return super.seeElement(parseLocator.call(this, locator))

// For mobile apps, use native display check instead of JavaScript execution
const parsedLocator = parseLocator.call(this, locator)
const res = await this._locate(parsedLocator, true)

// Check if elements exist
if (!res || res.length === 0) {
throw new AssertionFailedError(`Element ${new Locator(parsedLocator)} was not found`)
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AssertionFailedError is not imported but is being used. Add the import statement at the top of the file.

Copilot uses AI. Check for mistakes.
}

// Use native isDisplayed() method without JavaScript execution for mobile
const selected = []
for (let i = 0; i < res.length; i++) {
try {
// Get element ID using utility function
const elementId = getElementId(res[i])

// Use the native WebDriver isDisplayed method directly
const isDisplayed = await this.browser.isElementDisplayed(elementId)
selected.push(isDisplayed)
} catch (err) {
// If native method fails, element is not displayed
selected.push(false)
}
}

try {
return truth(`elements of ${new Locator(parsedLocator)}`, 'to be seen').assert(selected)
} catch (e) {
dontSeeElementError(parsedLocator)
}
}

/**
Expand Down
22 changes: 4 additions & 18 deletions lib/helper/WebDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const { focusElement } = require('./scripts/focusElement')
const { blurElement } = require('./scripts/blurElement')
const { dontSeeElementError, seeElementError, seeElementInDOMError, dontSeeElementInDOMError } = require('./errors/ElementAssertion')
const { dontSeeTraffic, seeTraffic, grabRecordedNetworkTraffics, stopRecordingTraffic, flushNetworkTraffics } = require('./network/actions')
const { getElementId } = require('../utils')
const WebElement = require('../element/WebElement')

const SHADOW = 'shadow'
Expand Down Expand Up @@ -998,7 +999,7 @@ class WebDriver extends Helper {
* {{ react }}
*/
async click(locator, context = null) {
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
const locateFn = prepareLocateFn.call(this, context)

const res = await findClickable.call(this, locator, locateFn)
Expand Down Expand Up @@ -1217,7 +1218,7 @@ class WebDriver extends Helper {
* {{> checkOption }}
*/
async checkOption(field, context = null) {
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
const locateFn = prepareLocateFn.call(this, context)

const res = await findCheckable.call(this, field, locateFn)
Expand All @@ -1237,7 +1238,7 @@ class WebDriver extends Helper {
* {{> uncheckOption }}
*/
async uncheckOption(field, context = null) {
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
const locateFn = prepareLocateFn.call(this, context)

const res = await findCheckable.call(this, field, locateFn)
Expand Down Expand Up @@ -2985,21 +2986,6 @@ function usingFirstElement(els) {
return els[0]
}

function getElementId(el) {
// W3C WebDriver web element identifier
// https://w3c.github.io/webdriver/#dfn-web-element-identifier
if (el['element-6066-11e4-a52e-4f735466cecf']) {
return el['element-6066-11e4-a52e-4f735466cecf']
}
// (deprecated) JsonWireProtocol identifier
// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#webelement-json-object
if (el.ELEMENT) {
return el.ELEMENT
}

return null
}

// List of known key values to unicode code points
// https://www.w3.org/TR/webdriver/#keyboard-actions
const keyUnicodeMap = {
Expand Down
26 changes: 26 additions & 0 deletions lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,28 @@ const { convertColorToRGBA, isColorProperty } = require('./colorUtils')
const Fuse = require('fuse.js')
const { spawnSync } = require('child_process')

// WebDriver constants
// W3C WebDriver web element identifier
// https://w3c.github.io/webdriver/#dfn-web-element-identifier
const W3C_ELEMENT_ID = 'element-6066-11e4-a52e-4f735466cecf'

/**
* Get element ID from WebDriver element response
* Supports both W3C and legacy JsonWireProtocol formats
*/
function getElementId(el) {
// W3C WebDriver web element identifier
if (el[W3C_ELEMENT_ID]) {
return el[W3C_ELEMENT_ID]
}
// (deprecated) JsonWireProtocol identifier
// https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol#webelement-json-object
if (el.ELEMENT) {
return el.ELEMENT
}
return el
Copy link

Copilot AI Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function should return null when neither W3C_ELEMENT_ID nor ELEMENT properties exist, not the element itself. This could cause issues when the caller expects a string ID but receives an object.

Suggested change
return el
return null

Copilot uses AI. Check for mistakes.
}

function deepMerge(target, source) {
const merge = require('lodash.merge')
return merge(target, source)
Expand Down Expand Up @@ -659,3 +681,7 @@ module.exports.markdownToAnsi = function (markdown) {
})
)
}

// WebDriver constants and utilities
module.exports.W3C_ELEMENT_ID = W3C_ELEMENT_ID
module.exports.getElementId = getElementId
1 change: 1 addition & 0 deletions test/helper/AppiumWeb_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ describe('Appium Web', function () {
platformName: 'Android',
platformVersion: '6.0',
deviceName: 'Android Emulator',
automationName: 'UiAutomator2',
},
host: 'ondemand.saucelabs.com',
port: 80,
Expand Down
1 change: 1 addition & 0 deletions test/helper/Appium_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe('Appium', function () {
deviceName: 'Android GoogleAPI Emulator',
androidInstallTimeout: 90000,
appWaitDuration: 300000,
automationName: 'UiAutomator2',
},
restart: true,
protocol: 'http',
Expand Down
4 changes: 2 additions & 2 deletions test/helper/WebDriver.noSeleniumServer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const WebDriver = require('../../lib/helper/WebDriver')
const AssertionFailedError = require('../../lib/assert/error')
const Secret = require('../../lib/secret')
global.codeceptjs = require('../../lib')
const { W3C_ELEMENT_ID } = require('../../lib/utils')

const siteUrl = TestHelper.siteUrl()
let wd
Expand Down Expand Up @@ -41,7 +42,7 @@ describe('WebDriver - No Selenium server started', function () {
},
},
customLocatorStrategies: {
customSelector: selector => ({ 'element-6066-11e4-a52e-4f735466cecf': `${selector}-foobar` }),
customSelector: selector => ({ [W3C_ELEMENT_ID]: `${selector}-foobar` }),
},
})
})
Expand Down Expand Up @@ -382,7 +383,6 @@ describe('WebDriver - No Selenium server started', function () {
})
})


describe('#seeTitleEquals', () => {
it('should check that title is equal to provided one', async () => {
await wd.amOnPage('/')
Expand Down
3 changes: 2 additions & 1 deletion test/helper/WebDriver_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const AssertionFailedError = require('../../lib/assert/error')
const webApiTests = require('./webapi')
const Secret = require('../../lib/secret')
global.codeceptjs = require('../../lib')
const { W3C_ELEMENT_ID } = require('../../lib/utils')

const siteUrl = TestHelper.siteUrl()
let wd
Expand Down Expand Up @@ -44,7 +45,7 @@ describe('WebDriver', function () {
},
},
customLocatorStrategies: {
customSelector: selector => ({ 'element-6066-11e4-a52e-4f735466cecf': `${selector}-foobar` }),
customSelector: selector => ({ [W3C_ELEMENT_ID]: `${selector}-foobar` }),
},
})
})
Expand Down
Loading