Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appium cannot find element by ID, but can by XPATH #509

Closed
ws-cdavis opened this issue Feb 28, 2020 · 31 comments
Closed

Appium cannot find element by ID, but can by XPATH #509

ws-cdavis opened this issue Feb 28, 2020 · 31 comments

Comments

@ws-cdavis
Copy link

ws-cdavis commented Feb 28, 2020

The problem

I've been working on dual Appium/Selenium test suite for a web page and mobile app. When using Appium, it is unable to find the element by ID. If I change it to use XPATH, it works as expected, then fails to find the next element by ID. This may not sound like a problem, but other elements in the app are not easily distinguished by XPATH values.

This test suite worked previously with manual app install and the Appium Service running on Mac. I've since tried to automate the process to install the application and run Appium Service programmatically. I am wondering if the way I've set this up is causing it not to detect elements by ID.

I know for certain the ID is correct. It works for the Selenium + web page combination, and I've tried digging into the app with Appium Studio. It displays the ID correctly in the app, and copy pasting the ID into my code doesn't change anything.

Environment

  • Appium version (or git revision) that exhibits the issue: All versions I've tried experience this (currently using 1.14.2)
  • Desktop OS/version used to run Appium: MacOS Catalina
  • Node.js version (unless using Appium.app|exe): 10.15.0
  • Npm or Yarn package manager: NPM
  • Mobile platform/version under test: Android 8.1
  • Real device or emulator/simulator: Real Device
  • Appium CLI or Appium.app|exe: Appium CLI

Link to Appium logs

Unfortunately, it doesn't look like the AppiumService in Python creates logs. If it does, I'll attach them when I find them or somebody tells me where they are

Code To Reproduce Issue [ Good To Have ]

Code below (redesign_tags and redesign_locators contain some sensitive info, but they work as intended in the Selenium flow):

class Driver:
    desired_capabilities = {}

    def __init__(self):
        # AppiumService was not called in previous version
        redesign_tags.appium_service = AppiumService()
        redesign_tags.appium_service.start()

        self.desired_capabilities[redesign_tags.platform_name_label] = redesign_tags.platform_device_name_android_label
        self.desired_capabilities[redesign_tags.no_reset_label] = redesign_tags.no_reset_value
        self.desired_capabilities[redesign_tags.platform_device_name_label] = redesign_tags.platform_device_name_galaxy_8
        self.desired_capabilities["app"] = "/Path/To/android-app.apk" # this line did not exist in the old version
        self.desired_capabilities[redesign_tags.app_package_label] = redesign_tags.app_package_value_dev
        self.desired_capabilities[redesign_tags.app_activity_label] = redesign_tags.app_activity_value
        self.instance = webdriver.Remote(redesign_tags.link_to_wd_hub, self.desired_capabilities)
        time.sleep(10)  # wait for application install
        print("Installed application")

def setupDevice():
    if redesign_tags.platform == "Android":
        print("Installing application...")
        driver = Driver()
    elif redesign_tags.platform == "Chrome":
        options = webdriver.ChromeOptions()
        options.add_argument('--start-maximized')
        options.add_argument('--unlimited-storage')
        driver = webdriver.Chrome(options=options)
        driver.get("localhost:8100")
        redesign_tags.webdriver = driver
        driver.set_window_position(0, 0)
        driver.maximize_window()
    else:
        print("Invalid platform specified")
        return False

    print("Registering device...")
    pairing = PairingPage(driver)
    # this element is not found by Appium
    pairing_credentials = pairing.get_element_by_id(redesign_locators.pairing_credentials_button, 25)
@ki4070ma
Copy link
Collaborator

@ws-aweaver
Which info will you use for find_element_by_id ?
(e.g. accessibility id from below image)
Could you check your app with Appium Desktop?

SS_2020-02-29 17 27 14

@ki4070ma
Copy link
Collaborator

@ws-aweaver

You can see appium log on terminal by adding stdout=None as below.
Could you take and attach appium log?

service = AppiumService()
service.start(stdout=None)

@ws-cdavis
Copy link
Author

ws-cdavis commented Mar 2, 2020

@ki4070ma Thank you so much for this! I've attached the full logs below, but I saw a few possible candidates for the issue:

[ADB] Using 'apkanalyzer' from '/Users/andrewweaver/Library/Android/sdk/tools/bin/apkanalyzer'
[debug] [ADB] Starting '/Users/andrewweaver/Library/Android/sdk/tools/bin/apkanalyzer' with args ["manifest","print","/Users/andrewweaver/.nvm/versions/node/v10.15.0/lib/node_modules/appium/node_modules/io.appium.settings/apks/settings_apk-debug.apk"]
[ADB] Cannot extract apk info using apkanalyzer. Falling back to aapt. Original error: Exception in thread "main" java.lang.NoClassDefFoundError: javax/xml/bind/annotation/XmlSchema
[ADB] 	at com.android.repository.api.SchemaModule$SchemaModuleVersion.<init>(SchemaModule.java:156)
[ADB] 	at com.android.repository.api.SchemaModule.<init>(SchemaModule.java:75)
[ADB] 	at com.android.sdklib.repository.AndroidSdkHandler.<clinit>(AndroidSdkHandler.java:81)
[ADB] 	at com.android.tools.apk.analyzer.ApkAnalyzerCli.getAaptInvokerFromSdk(ApkAnalyzerCli.java:277)
[ADB] 	at com.android.tools.apk.analyzer.ApkAnalyzerCli.main(ApkAnalyzerCli.java:129)
[ADB] Caused by: java.lang.ClassNotFoundException: javax.xml.bind.annotation.XmlSchema
[ADB] 	at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:581)
[ADB] 	at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
[ADB] 	at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
[ADB] 	... 5 more
[ADB] 
[ADB] Using 'aapt' from '/Users/andrewweaver/Library/Android/sdk/build-tools/29.0.2/aapt'

Maybe it is unable to analyze the apk to get all of the ids?

ADB] The application at 'PATH/TO/APP/android-app.apk' is already cached to '/data/local/tmp/appium_cache/06e635178a30e612dd2256a4d5401acf1b4eee8f.apk'
[debug] [ADB] Running '/Users/andrewweaver/Library/Android/sdk/platform-tools/adb -P 5037 -s 5200615242e31623 shell pm install /data/local/tmp/appium_cache/06e635178a30e612dd2256a4d5401acf1b4eee8f.apk'

It may be taking cached data from an old version of the application that did not have the IDs (doubtful, as I've verified the ID exists in Appium Studio)

Full logs:

Moved to https://gist.github.com/ki4070ma/7740f16c776c8b2e5540911c385c7559

@ws-cdavis
Copy link
Author

@ws-aweaver
Which info will you use for find_element_by_id ?
(e.g. accessibility id from below image)
Could you check your app with Appium Desktop?

SS_2020-02-29 17 27 14

I am using the ID value, not the accessibility id. I've verified the ID is correct using Appium Studio, which has similar functionality to the Appium Desktop you showed.

@ki4070ma ki4070ma removed the NeedsInfo label Mar 7, 2020
@ki4070ma
Copy link
Collaborator

ki4070ma commented Mar 8, 2020

@KazuCocoa @mykola-mokhnach

Is below result expected(as design)?
It seems that android:id/text1 works, but io.appium.android.apis:id/text1 doesn't work.

Notes

    print(len(driver.find_elements_by_android_uiautomator('new UiSelector().resourceId("text1")')))
    print(len(driver.find_elements_by_android_uiautomator('new UiSelector().resourceId("id/text1")')))
    print(len(driver.find_elements_by_android_uiautomator('new UiSelector().packageName("android").resourceId("id/text1")')))
    print(len(driver.find_elements_by_android_uiautomator('new UiSelector().packageName("android").resourceId("text1")')))
    print(len(driver.find_elements_by_android_uiautomator('new UiSelector().packageName("io.appium.android.apis").resourceId("id/text1")')))
    print(len(driver.find_elements_by_android_uiautomator('new UiSelector().packageName("io.appium.android.apis").resourceId("text1")')))

Results

$python SampleAndroidTest_apidemo.py
0
12
0

Sample codes

#!/usr/bin/python
# -*- coding: utf-8 -*-

from appium import webdriver

caps = {
    'platformName': "Android",
    'deviceName': "Android Emulator",
    'appPackage': "io.appium.android.apis",
    'appActivity': ".ApiDemos",
    'automationName': "uiautomator2",
}

if __name__ == '__main__':
    driver = webdriver.Remote('http://localhost:4723/wd/hub', caps)

    # Found elements: 0
    els = driver.find_elements_by_id("id/text1")
    print(len(els))

    # Found elements: 12
    els = driver.find_elements_by_id("android:id/text1")
    print(len(els))

    # Found elements: 0
    els = driver.find_elements_by_id("io.appium.android.apis:id/text1")
    print(len(els))

    driver.quit()

@KazuCocoa
Copy link
Member

KazuCocoa commented Mar 8, 2020

Yes.

It depends on the app under test. You can find elements by io.appium.android.apis:id/text1 if the app under test had their customised resource id. Android's general component could have android: prefixed resource id if nothing developers specified.

The behaviour is not Appium specific.

@ki4070ma
Copy link
Collaborator

ki4070ma commented Mar 8, 2020

@ws-aweaver
At first, you will use resource-id, right?
If so, check if the id which you will use is same to the resource-id in appium desktop.

As #509 (comment), it might be necessary to update used id.

@ws-cdavis
Copy link
Author

I am trying to replicate this behavior in my code. I am using this function to wait for the element to appear:

def wait_for_element_by_id(self, id, time):
        try:
            WebDriverWait(self.driver, time).until(EC.visibility_of_element_located((MobileBy.ID, id)))
        except NoSuchElementException:
            print("Element not found: " + id)
            return False
        except TimeoutException:
            print("Timeout occurred: " + id)
            return False
        return True

My understanding is MobileBy.ID will not work. I do not see MobileBy.Resource_ID. I've also tried experimenting with just getting the element without a wait using this:

    def is_element_present_by_id(self, id):
        try:
            element = self.driver.find_element_by_id("android:id/"+id)
            if element is None:
                return False
        except NoSuchElementException:
            return False
        except TimeoutException:
            return False
        return True

I also tried element = self.driver.find_element_by_id("io.appium.android.apis:id/"+id) but this is not working either. The original code used self.driver.find_element(MobileBy.ID, id). I checked in Appium Studio. The ID and Resource ID are the same value.

I also tried downloading Appium Desktop just to be sure. It looks like the application I already have and was previously using to manually set up the Appium service. However, Mac is being difficult and says the app can't be opened with no explanation why. The version of Appium Desktop I already have doesn't have any element inspection services that I know of.

@ws-cdavis
Copy link
Author

ws-cdavis commented Mar 11, 2020

Update: I tried yesterday to demonstrate to somebody that XPATH works. It is no longer working. I also confirmed that I am in the correct context ("NATIVE_APP"). I am unsure what this means, as I have not changed the code in the application or the automation suite.

EDIT: XPATH is working correctly with UIAutomator2. It breaks on UIAutomator1. ID does not work with either. I also tried a cheap workaround by using XPATH to find an element with the ID //*[@resource-id='pairing-go-to-pair-by-login-button'] and this worked.

It looks like I need to find a method that looks by resource ID. I tried all of the methods below and they do not find the element:
io.appium.android.apis:id/pairing-go-to-pair-by-login-button
android:id/pairing-go-to-pair-by-login-button
package_name:id/pairing-go-to-pair-by-login-button

I have the app set the ID programatically, but not the resource ID. The resource ID is the same as the regular ID, so for now I am able to progress with the ugly workaround. I would like to do this properly, though.

@ws-cdavis
Copy link
Author

@ki4070ma How do I get resource ID properly in Appium Python Client? I didn't find a method for it. I used the ugly workaround in my above comment for now, and it looks like there is a serious performance issue. My test is taking around 15 minutes to execute. The old suite of 12 tests took around 20 minutes. I'd really like to get the execution time much lower.

@Timwintle1979
Copy link

@ws-cdavis just wondering if you ever found a resolution for this? I'm afraid I'm not proficient in python like you clearly are so I'm utilising the appiumlibrary that's part of robot framework, however, I too have a problem that if I express as id=X it fails to find the element. If I express that same element as //*[@resource-id="X"] it works flawlessly.

@Amulyakr
Copy link

Amulyakr commented Jun 2, 2021

I am trying to replicate this behavior in my code. I am using this function to wait for the element to appear:

def wait_for_element_by_id(self, id, time):
        try:
            WebDriverWait(self.driver, time).until(EC.visibility_of_element_located((MobileBy.ID, id)))
        except NoSuchElementException:
            print("Element not found: " + id)
            return False
        except TimeoutException:
            print("Timeout occurred: " + id)
            return False
        return True

My understanding is MobileBy.ID will not work. I do not see MobileBy.Resource_ID. I've also tried experimenting with just getting the element without a wait using this:

    def is_element_present_by_id(self, id):
        try:
            element = self.driver.find_element_by_id("android:id/"+id)
            if element is None:
                return False
        except NoSuchElementException:
            return False
        except TimeoutException:
            return False
        return True

I also tried element = self.driver.find_element_by_id("io.appium.android.apis:id/"+id) but this is not working either. The original code used self.driver.find_element(MobileBy.ID, id). I checked in Appium Studio. The ID and Resource ID are the same value.

I also tried downloading Appium Desktop just to be sure. It looks like the application I already have and was previously using to manually set up the Appium service. However, Mac is being difficult and says the app can't be opened with no explanation why. The version of Appium Desktop I already have doesn't have any element inspection services that I know of.

Hey Did you find the way to find elements by id?

@KazuCocoa
Copy link
Member

Basically id locator is for resource-id. https://github.com/appium/appium-uiautomator2-driver/blob/93be98ec9583ca498a2abad08e38f3220c0a5323/README.md#element-location (uia1 as well)
UiAutomator's By.res is used as the backend.

@ammaramja
Copy link

Try specifying "chromeOptions": {"w3c": False} in your dc.

@mzserroukh
Copy link

I am facing a similar issue too.
This does not work:by.ID("text")
This works: By.XPATH("//*[@resource-id='text']")

Does anyone know why? potential fix ID directly?

@KazuCocoa
Copy link
Member

It is via findObject by Android directly https://github.com/appium/appium-uiautomator2-server/blob/cef81e2a9755e103ff5a6fb488938a14ff8a7016/app/src/main/java/io/appium/uiautomator2/handler/FindElement.java#L82 or XPath info cached by Appium/UIA2 server from AccessibilityNodeInfo by Android (since Android does not support XPath natively).

The difference might cause it.

@mykola-mokhnach
Copy link
Contributor

^ This could also be an issue with accessibility identifiers naming (app identifier prefix is missing). Then appium/appium#15138 (comment) could be used as a workaround.

@HugoGresse
Copy link

I'm having the same issue maybe:

  • XPath does work
  • some findElements with id does work (maintly the top level one, top the deeper one)
  • but all the useful findElements with ID return no results

I've attached two screenshots, the second one is to show that selecting with id: "org.plantnet:id/action_bar_root" does work. but the first one with id: "org.plantnet:id/bottomtabexplorer" or simply "bottomtabexplorer" does not work.

Here is my capabilities:

{
  "platformName": "Android",
  "appium:automationName": "UiAutomator2",
  "appium:appPackage": "org.plantnet",
  "appium:appActivity": "org.plantnet.MainActivity",
  "appium:disableIdLocatorAutocompletion": true
}

Capture d’écran 2022-06-27 à 16 28 05

Capture d’écran 2022-06-27 à 16 31 29

@HugoGresse
Copy link

it's crazy but the only thing that worked for me was to use

`xpath://*[@resource-id='${selector}']`

I had updated Appium globally, tried to change the capabilities to be more accurate and everything but this solve the issue right away.
Before I even had strange behavior where I needed to click somewhere else for the next click to work.

@mtaylor-evidation
Copy link

Yes I cannot get the accessibility ID's to work either and like @HugoGresse I can only get it to find the elements by the xpath in appium with webdriver i/o latest versions. This is a react native app that I'm working with:

DC's are

capabilities: {
platformName: "android",
automationName: "Appium",
platformVersion: "12",
deviceName: "emulator-5554",
app: "path-to-my-app",
automationName: "UiAutomator2",
autoGrantPermissions: 'true',
}

I'm using an android nexus 6p with android 12

I have tried everything suggested.

This is blocking for me as one of the elements is off the screen and the scrolltoView is not implemented yet and I can't find elements that are not visible with xpaths. Any suggestions?

@jlipps
Copy link
Member

jlipps commented Aug 25, 2022

@mtaylor-evidation if you're dealing with android you have a different problem--elements that are not on screen do not exist (android doesn't have any concept of an element being available/existing if it's not actually on screen). so you'll never be able to find it. the best practice for the uiautomator2 driver is to do what a user would do--perform a scroll and then check if the element is on screen, and repeat.

@mtaylor-evidation
Copy link

mtaylor-evidation commented Aug 25, 2022 via email

@jlipps
Copy link
Member

jlipps commented Aug 25, 2022

I don't know what "scroll into view" is. sounds like maybe it's a webdriverio thing. it's not an appium command. it's unclear to me how webdriverio implements it under the hood. it'd be better to do the correct appium thing, which is to use the actions api to construct a finger movement that will perform a scroll.

@HugoGresse
Copy link

I've made something like this to scroll the view down. It's not perfect but it works ~ok


/**
 * Scroll down by simulating a swipe down gesture.
 *
 * @param driver
 * @param scrollAmount does not work currently...
 * @param scrollDuration a lower scroll duration add more fling speed.
 */
export const scrollDown = async (driver, scrollAmount =1, scrollDuration = 300) => {
    const startPercentage = 90
    const endPercentage = 10
    const anchorPercentage = 50

    const { width, height } = await driver.getWindowSize()
    const density = (await driver.getDisplayDensity()) / 100
    const anchor = (width * anchorPercentage) / 100
    const startPoint = (height * startPercentage) / 100
    const endPoint = (height * endPercentage) / 100

    for (let i = 0; i < scrollAmount; i++) {
        await driver.performActions([
            {
                type: 'pointer',
                id: 'finger1',
                parameters: { pointerType: 'touch' },
                actions: [
                    { type: 'pointerMove', duration: 0, x: anchor, y: startPoint },
                    { type: 'pointerDown', button: 0 },
                    { type: 'pause', duration: 100 },
                    { type: 'pointerMove', duration: scrollDuration, origin: 'pointer', x: 0, y: -endPoint * density },
                    { type: 'pointerUp', button: 0 },
                    { type: 'pause', duration: scrollDuration },
                ],
            },
        ])
    }
}

@mykola-mokhnach
Copy link
Contributor

@HugoGresse Check appium/appium#15138

@mtaylor-evidation
Copy link

So amazed by this community. Although the solution proposed won't work for me (JavaScript is what I'm using) but I've really gleaning off of all of your knowledge. I appreciate your responses today I'll be heads down trying to figure it out for JavaScript maybe even consider branching out and using native webdriver

@Nidhi3420009
Copy link

Nidhi3420009 commented Nov 22, 2022

I am also facing issue to find the locator, I have mentioned the code below

package Mobile_Testing;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import org.openqa.selenium.By;
import org.openqa.selenium.remote.DesiredCapabilities;

import io.appium.java_client.android.AndroidDriver;
import io.appium.java_client.remote.MobileCapabilityType;

public class First_TestCase {

public static void main(String[] args) throws MalformedURLException, InterruptedException {
	File app = new File("D:\\Technogiq Work", "standard-bank.apk");
	DesiredCapabilities C=new DesiredCapabilities();
	C.setCapability(MobileCapabilityType.AUTOMATION_NAME , "uiautomator2");
	C.setCapability(MobileCapabilityType.PLATFORM_NAME,"Android");
	C.setCapability(MobileCapabilityType.DEVICE_NAME,"Android");
	C.setCapability(MobileCapabilityType.PLATFORM_VERSION,"7.0");
	
	C.setCapability("appPackage", "com.bank.standard");
	C.setCapability("appActivity", "com.bank.standard.MainActivity");
	
	URL url=new URL("http://0.0.0.0:4723/wd/hub");
	
	AndroidDriver driver=new AndroidDriver(url,C);
	
	Thread.sleep(2000);
	driver.findElement(By.xpath("//*[@resource-id, '${com.bank.standard:id/urs_nm}']")).sendKeys("1234");
	
driver.quit();

}}

I have also tried
By.xpath("//[@resource-id, 'com.bank.standard:id/urs_nm']"
By.xpath("//android.widget.EditText[@resource-id, 'com.bank.standard:id/urs_nm']")
By.xpath("//*[contains(@resource-id, 'com.bank.standard:id/urs_nm')]")
By.xpath("//com.bank.standard[@id='com.bank.standard:id/urs_nm']")
By.id("com.bank.standard:id/urs_nm")

Screenshot 2022-11-22 192303

@Nidhi3420009
Copy link

Please help me

@Nidhi3420009
Copy link

Nidhi3420009 commented Nov 22, 2022

Error on Selenium

Screenshot 2022-11-22 192505

@mzserroukh
Copy link

try replacing your Xpath to look like this: "//*[@resource-id='com.bank.standard:id/urs_nm']"

@Nidhi3420009
Copy link

Hi thanks for the reply

I have tried but not working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests