I am new of mobile app automation tested using Appium with TestNG.Am practicing to automate amazon app, App was launching successfully but when I try to click login option, it's getting:
"FAILED: login org.openqa.selenium.NoSuchElementException: An
element could not be located on the page using the given search
parameters. (WARNING: The server did not provide any stacktrace
information) Command duration or timeout: 0 milliseconds"
public void login() throws InterruptedException{
System.out.println("Login check");
Thread.sleep(3000);
// String sample = driver.findElementByXPath("//*[#class='android.widget.Button' and #index='5']").getText();
System.out.println("Next sleep");
// driver.findElement(By.xpath("//[#class='android.widget.Button' and #index='5']')]")).click();
// driver.findElement(By.id("in.amazon.mShop.android.shopping:id/sign_in_button")).click();
driver.findElement(By.xpath("//android.widget.Button[#index='5']")).click();
Thread.sleep(3000);
System.out.println("Pass");
}
Image:
"//android.widget.Button[#index='5']" is invalid xpath locator. If you will ever what to use index (but I strongly suggest not to do it), do it this way:
"(//android.widget.Button)[5]"
But in your case the best is to search by resource-id:
driver.findElement(By.id("sign_in_button")).click();
And if it still doesn't work you can print app screen xml structure just before you search for element:
System.out.println(driver.getPageSource())
Analyse the xml you get if it contains your button and what are the attributes of it.
Please try this code:
MobileElement el1 = (MobileElement) driver.findElementById("in.amazon.mShop.android.shopping:id/sign_in_button");
el1.click();
Related
The appium test is to perform certain combination of few input fields, and one field is password field.
If the username is already taken, a corresponding message will be shown below the password button saying "username already taken".
But if username is valid, but password is wrong, the message will be shown below as "username and password combo didn't work".
The error message in above cases is shown in a android.widget.TextView**
Two questions:
I recorded the test using Appium Studio. When I run the test, I want to wait for couple of second and grab the text of error message. How to do the wait part and grab the text? (More details below). THe challenge is there is no resource id for the textView, but xpath and other details are available.
Note: Since the error message is shown right below the pwd field, it has no resouceid, only xpath available.
This is the structure
<android.widget.LinearLayout resource-id="com.a.b/textinputlayout_lgin_username">
<android.widget.FrameLayout>
<android.widget.EditText resource-id="com.a.b/lgin_username_edit">
<!--above 3 lines are for username field, just added for more clarity -->
<android.widget.LinearLayout resource-id="com.a.b:id/textinputlayout_lgin_pwd">
<android.widget.FrameLayout>
<android.widget.EditText>
<android.widget.ImageButton resource-id="com.a.b/text_input_end_icon">
<android.widget.TextView> <!-- ** this is the text I'm trying to grab -->
<android.widget.TextView resource-id="com.a.b/tv_forgotpwd">
<android.widget.Button resource-id="com.a.b/btn_login">
Please help. Thanks.
I assume, you have no ability to update the app in order to add resource id for the element.
The best, you can do in this case, is to use xpath.
Find the first parent by resorce-id attribute, and then go down and search by tag. I see 2 text tag elements here in LinearLayout, so you might try to get the first text tag by index, or search for text tag without resouce id attribute.
"(//android.widget.LinearLayout[#resouce-id='com.a.b:id/textinputlayout_lgin_pwd']//android.widget.TextView)[1]"
or
"//android.widget.LinearLayout[#resouce-id='com.a.b:id/textinputlayout_lgin_pwd']//android.widget.TextView[not(#resouce-id)]"
Based on appium client you're using, just utilize webdriver wait + expected conditions and use xpath location strategy for the element.
PS: Here is the code
public class Test_9 {
private String reportDirectory = "reports";
private String reportFormat = "xml";
private String testName = "Untitled";
protected AndroidDriver<AndroidElement> driver = null;
DesiredCapabilities dc = new DesiredCapabilities();
#BeforeEach
public void setUp() throws MalformedURLException {
dc.setCapability("reportDirectory", reportDirectory);
dc.setCapability("reportFormat", reportFormat);
dc.setCapability("testName", testName);
dc.setCapability(MobileCapabilityType.UDID, "123456");
dc.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.a.b");
dc.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, ".view.base.SplashActivity");
driver = new AndroidDriver<>(new URL("http://localhost:4723/wd/hub"), dc);
driver.setLogLevel(Level.INFO);
}
#Test
public void testUntitled() {
driver.findElement(By.xpath("(//*[#contentDescription='Google Map']/*[#class='android.view.View'])[26]")).click();
driver.findElement(By.xpath("//*[#text='log in']")).click();
driver.findElement(By.xpath("//*[#id='lgin_edit']")).sendKeys("1234567789");
driver.findElement(By.xpath("//*[#class='android.widget.EditText' and (./preceding-sibling::* | ./following-sibling::*)[#id='text_input_end_icon']]")).sendKeys("qwer");
new WebDriverWait(driver, 120).until(ExpectedConditions.presenceOfElementLocated(By.xpath("//*[#class='android.widget.EditText' and (./preceding-sibling::* | ./following-sibling::*)[#id='text_input_end_icon']]")));
driver.hideKeyboard();
driver.findElement(By.xpath("//*[#class='android.widget.EditText' and (./preceding-sibling::* | ./following-sibling::*)[#id='text_input_end_icon']]")).click();
driver.findElement(By.xpath("//*[#text='Log In']")).click();
driver.findElement(By.xpath("//*[#text='Log In']")).click();
}
#AfterEach
public void tearDown() {
//driver.quit();
}
}
Ok, this is what resolved my issue.
After posting the questino I realized, the button wasn't enabled, that's because the previous input text field is not clicked or something.
All this time I was recording via Appium, but I was manually clicking the keyboard and buttons on mobile phone, not the mobile simulator of appium.
After 2.5 days, I used mouse pointer of my computer to click all the fields and all simulation on the simulator of appium and recorded and re-ran the tests and it worked.
I did try printing out the button state and it was always btn.isEnabled() was returning false.
I have an Android application in which I'm using Azure AD B2C to authenticate users. Users login and logout of the application as needed. I would like to give the user the option to delete their own account.
I understand that I need to use the Azure AD Graph API to delete the user. This is what I have so far:
According to this link, it looks like deleting a user from a personal account (which is what the B2C users are using) is not possible. Is that correct?
Here's my code snippet for the Graph API call. Feel free to ignore it if I'm off track and there is a better way to solve this.
I believe I need a separate access token than what my app currently has (as the graph API requires other API consent). So, I'm getting the access token as follows:
AcquireTokenParameters parameters = new AcquireTokenParameters.Builder()
.startAuthorizationFromActivity(getActivity())
.fromAuthority(B2CConfiguration.getAuthorityFromPolicyName(B2CConfiguration.Policies.get("SignUpSignIn")))
.withScopes(B2CConfiguration.getGraphAPIScopes())
.withPrompt(Prompt.CONSENT)
.withCallback(getGraphAPIAuthCallback())
.build();
taxApp.acquireToken(parameters);
In the getGraphAPIAuthCallback() method, I'm calling the Graph API using a separate thread (in the background):
boolean resp = new DeleteUser().execute(authenticationResult.getAccessToken()).get();
Finally, in my DeleterUser() AsyncTask, I'm doing the following:
#Override
protected Boolean doInBackground(String... aToken) {
final String asToken = aToken[0];
//this method will be running on background thread so don't update UI from here
//do your long running http tasks here,you dont want to pass argument and u can access the parent class' variable url over here
IAuthenticationProvider mAuthenticationProvider = new IAuthenticationProvider() {
#Override
public void authenticateRequest(final IHttpRequest request) {
request.addHeader("Authorization",
"Bearer " + asToken);
}
};
final IClientConfig mClientConfig = DefaultClientConfig
.createWithAuthenticationProvider(mAuthenticationProvider);
final IGraphServiceClient graphClient = new GraphServiceClient.Builder()
.fromConfig(mClientConfig)
.buildClient();
try {
graphClient.getMe().buildRequest().delete();
} catch (Exception e) {
Log.d(AccountSettingFragment.class.toString(), "Error deleting user. Error Details: " + e.getStackTrace());
}
return true;
}
Currently, my app fails when trying to get an access token with a null pointer exception:
com.microsoft.identity.client.exception.MsalClientException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
Any idea what I need to do to provide the user the option to users to delete their own account? Thank you!
Thanks for the help, #allen-wu. Due to his help, this azure feedback request and this azure doc, I was able to figure out how to get and delete users silently (without needing intervention).
As #allen-wu stated, you cannot have a user delete itself. So, I decided to have the mobile app call my server-side NodeJS API when the user clicks the 'Delete Account' button (as I do not want to store the client secret in the android app) and have the NodeJS API call the Azure AD endpoint to delete the user silently. The one caveat is that admin consent is needed the first time you try to auth. Also, I have only tested this for Graph API. I'm not a 100% sure if it works for other APIs as well.
Here are the steps:
Create your application in your AAD B2C tenant. Create a client secret and give it the following API permissions: Directory.ReadWrite.All ;
AuditLog.Read.All (I'm not a 100% sure if we need the AuditLog permission. I haven't tested without it yet).
In a browser, paste the following link:
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Login using an existing admin account and provide the consent to the app.
Once you've given admin consent, you do not have to repeat steps 1-3 again. Next, make the following call to get an access token:
POST https://login.microsoftonline.com/{B2c_tenant_name}.onmicrosoft.com/oauth2/v2.0/token
In the body, include your client_id, client_secret, grant_type (the value for which should be client_credentials) and scope (value should be 'https://graph.microsoft.com/.default')
Finally, you can call the Graph API to manage your users, including deleting them:
DELETE https://graph.microsoft.com/v1.0/users/{upn}
Don't forget to include the access token in the header. I noticed that in Postman, the graph api had a bug and returned an error if I include the word 'Bearer' at the start of the Authorization header. Try without it and it works. I haven't tried it in my NodeJS API yet, so, can't comment on it so far.
#allen-wu also suggested using the ROPC flow, which I have not tried yet, so, cannot compare the two approaches.
I hope this helps!
There is a line of code: graphClient.getUsers("").buildRequest().delete();
It seems that you didn't put the user object id in it.
However, we can ignore this problem because Microsoft Graph doesn't allow a user to delete itself.
Here is the error when I try to do it.
{
"error": {
"code": "Request_BadRequest",
"message": "The principal performing this request cannot delete itself.",
"innerError": {
"request-id": "8f44118f-0e49-431f-a0a0-80bdd954a7f0",
"date": "2020-06-04T06:41:14"
}
}
}
I am testing a website on Chrome (v65) installed on Android (7.1.1 API 25).
Chromedriver version 2.37.
Appium latest desktop version (v1.5.0, shows server version 1.7.2)
I need to upload an image from the device itself.
Although on the web-browser I am able to upload an image with sendKeys, its not working on the android emulator.
This is what the image selection page in the emulator looks like:
I used UiAutomator to get the resource id: "com.android.chrome:id/bitmap_view" and the class: "android.widget.ImageView"
I have used the following line(s) of code to try and look for the element:
(a)
driver.findElement(By.xpath("//android.widget.ImageView[contains(#resource-id,'com.android.chrome:id/bitmap_view')]")).click();
(b) When I use below, I get an empty list
List<WebElement> elements = driver.findElementsByClassName("android.widget.ImageView");
(c) I got this xpath from Appium Inspector Session
MobileElement el8 = (MobileElement) driver.findElementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.support.v7.widget.Af/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.RelativeLayout/android.widget.FrameLayout/android.support.v7.widget.RecyclerView/android.widget.FrameLayout[3]/android.widget.FrameLayout/android.widget.ImageView");
el8.click();
I don't know what I am missing.
[Update] []2
Did you change the context to the NATIVE_APP in your test?
Since you mentioned you test chrome, it means your test is using WEBVIEW context. But as soon as you open Upload files screen it is a NATIVE_APP view and you need to switch to it before start searching:
AndroidDriver<MobileElement> driver = new AndroidDriver<>(
new URL("http://127.0.0.1:4723/wd/hub"), capabilities);
driver.get(<your_web_site>);
// do what you need in browser, open Upload files screen
// switch to Native context to search inside it
Set<String> contexts = driver.getContextHandles();
for (String context : contexts) {
System.out.println(contexts);
if (context.equals("NATIVE_APP")) {
driver.context(context);
break;
}
}
List<MobileElement> images = driver.findElementsByClassName("android.widget.ImageView");
// click the last image in view if exist
images.stream()
.reduce((first, second) -> second)
.orElseThrow(NotFoundException::new)
.click();
I am new to Appium and have been trying to automate the Conversion Calculator app for Android. Am getting the error "org.openqa.selenium.NoSuchElementException: An element could not be located on the page using the given search parameters", when trying to find a EditText element. Using Appium ver 1.0.0 and Android 4.3
The following is my code:
List<WebElement> textViews = driver.findElements(By.className("android.widget.TextView"));
for (i=0; i<textViews.size(); i++) {
if(textViews.get(i).getText().toLowerCase().contains("memory")) {
textViews.get(i).click();
}
}
Thread.sleep(5000);
WebElement editText = driver.findElement(By.className("android.widget.EditText"));
editText.sendKeys("123");
Even findElement by ID is not working. Please let me know what I am doing wrong here or if I need to provide more details.
I would use
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS); instead of Thread.sleep(5000).
Try to use a newer version of Appium, I's been improved a lot. You can download the latest version of Appium and Appium clients here:http://appium.io/downloads.html
But be careful because in the newer version the findElement throws an Exception if there are more then one result of the search.
I would write this in a comment but I've not enough reputation :/
Possible Cause:
Multiple EditText in the current screen.
Please try with the following:
Solution1:
List<WebElement> editText = driver.findElements(By.className("android.widget.EditText"));
editText.get(0).sendKeys("123");
0 - Index of EditText
Solution2:
Use any other locating strategy like Xpath.
Maybe you could try waiting until the element is visible or enabled using a WebDriverWait object?
Avoid using sleep as much as possible, try using the WAIT command.
Sleep without waiting for the time that has been determined, even if the element is already on the screen.
In the case of the wait command, as soon as the element appears, the action will already be performed, this along the code will reduce the execution time considerably.
The issue for me was the app path I was using. If you are using a config file make sure to declare the application separately from the device.
If not, make sure the "app" capability has the right path. Here is the code in my config file for example:
devices_by_ids = {
"platformName": "Android",
"appium:DEVICE ADB ID": {
"android_version": "13",
"device_name": "google_Pixel_5a",
"DEVICE ADB ID": "DEVICE ADB ID",
"port":"4723",
"autoGrantPermissions": "true",
},
"appium:app": "YOUR APP PATH",
"appium:appWaitActivity": "*"
}
In Appium v2 use
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
I am working through the Facebook Wishlist app for Android. When I attempt to add my custom object to the Facebook graph the API returns the following error:
{
"error: {
"type": "OAuthExeption",
"message":"(#100)
Application does not own
230752723668296 action type"
}
}
I haven't been able to find any specific documentation on this particular error. Are there any Facebook developers out there who may be able to help me interpret this.
I should also mention that I already have a working example of this app running without errors (I am trying to duplicate it so I can tweak it with out fear of losing work). I have tried changing the name of the Action but this makes no difference. Below is the snippet of the Android code where it adds the item to the Facebook time line. This is however a small piece of the over all puzzle as there are three sides to this project (Facebook, My Server, and Android)
public void addToTimeline() {
dialog = ProgressDialog.show(Wishlist.this, "",getString(R.string.adding_to_timeline), true, true);
/*
* Create Product URL
*/
String productURL = HOST_SERVER_URL + HOST_PRODUCT_URI;
Bundle productParams = new Bundle();
productParams.putString("name", mProductName);
productParams.putString("image", mProductImageName);
productURL = productURL + "?" + Util.encodeUrl(productParams);
Bundle wishlistParams = new Bundle();
if(mPlacesAvailable) {
try {
wishlistParams.putString("place", mPlacesJSONArray.getJSONObject(mPlacesListSpinner.getSelectedItemPosition()).getString("id"));
} catch (JSONException e) {}
}
wishlistParams.putString("wishlist", WISHLIST_OBJECTS_URL[mWishlistSpinner.getSelectedItemPosition()]);
wishlistParams.putString("product", productURL);
wishlistParams.putString("image", mProductImageURL);
//TODO
//put the app's namespace and 'add_to' action here
Utility.mAsyncRunner.request("me/wishlisteight:adding_to", wishlistParams, "POST", new addToTimelineListener(), null);
}
"I should also mention that I already have a working example of this app running without errors (I am trying to duplicate it so I can tweak it with out fear of losing work)"
That action is specific to an app id. When you made the duplicate of your code, did you use the same app id or create a new one? If you created a new app id, you will need to register actions for the new app just like you did with the first app.
I encountererd this error (#100 Application does not own action type) when using the Graph API Explorer tool to post an action to my app's timeline. Here is a brief summary of the values I had entered when encountering this error and how I resolved the issue. Fyi, my app is called 'recipestogo'. The action I defined for my app is called 'cook', and the associated object is called 'recipe'.
Values I entered into Open Graph API:
Chose 'Post' from the drop-down
https://graph.facebook.com/me/recipestogo:cook?recipe=https://www.mywebsite.com/index.html
Error encountered:
100 Application does not own action type
Resolution:
At the top, right of the API Graph Explorer tool, there is a drop-down next to Application. It had been set by default to Graph API Explorer. Changing this from Graph API Explorer to RecipesToGo resolved the issue and I was able to successfully post to my app's timeline.