vishnukarthikl / calabash-android-java

Write calabash android tests in Java.
http://vishnukarthikl.github.io/calabash-android-java/
MIT License
6 stars 9 forks source link

Touch fails due to “No elements found” but element was found #3

Closed mikechabot closed 10 years ago

mikechabot commented 10 years ago

Although I'm able to query for a specific DOM element, and list details (id, class, query, X-Y location), when I touch the element, a stack trace is thrown.

Is this expected?

HTML

   <div style="width: 100px">
        <a data-role="button" ng-click="submit()" id="loginButton">Log In</a>
    </div>

Java

public void user_logs_in_with_valid_credentials(String username) throws Throwable {
    UIElement login = app.query("webView css:'#loginButton'").first();
    System.out.println("Element_Id=" + login.getId());
    System.out.println("Element_Class=" + login.getElementClass());
    System.out.println("Element_Query=" + login.getQuery());
    System.out.println("Element_Text=" + login.getText());
    System.out.println("Element_Enabled=" + login.isEnabled());
    Rect rect = login.getRect();
    System.out.println("Center_X=" + rect.getCenter_x());
    System.out.println("Center_Y=" + rect.getCenter_y());
    login.touch();   //FAILURE HAPPENS HERE
}

Console output

Element_Id=loginButton
Element_Class=ui-btn ui-btn-up-c ui-shadow ui-btn-corner-all
Element_Query=webView css:'#loginButton' index:0
Element_Text=
Element_Enabled=false
Center_X=91.98979
Center_Y=308.5714
com.thoughtworks.calabash.android.CalabashException: Failed to touch on: webView css:'#loginButton' index:0. (RuntimeError) No elements found. Query: webView css:'#loginButton' index:0
        at com.thoughtworks.calabash.android.CalabashWrapper.touch(CalabashWrapper.java:293)
        at com.thoughtworks.calabash.android.UIElement.touch(UIElement.java:196)
        at com.mycompany.calabash.LoginStepDefs.user_logs_in_with_valid_credentials(LoginStepDefs.java:41)
        at ?.When user "xxx@xxx" logs in with valid credentials(C:/_Workspace/calabash-android-java/calabash-android-java/src/test/resources/features/login.feature:13)
mikechabot commented 10 years ago

It's worth noting that I'm able to touch the element using the same query when in console mode, and the device responds accordingly:

irb(main):023:0> touch("webView css:'#loginButton'")
{
    "bonusInformation" => [],
             "message" => "",
             "success" => true
}
vishnukarthikl commented 10 years ago

Currently we haven't added webview support and are working on it. It is because calabash-android does not handle webview queries with indices. But calabash-android-java references multiple elements by appending index:index-no to the query.

in the stack trace you can see that webView css:'#loginButton' index:0 was touched but calabash-android couldn't find that element since it should have been webView css:'#loginButton'

if your webview query returns multiple elements this will fail with the above mentioned error. Try changing your query(may be by id) to return a single button.

mikechabot commented 10 years ago

Thanks, I noted the appending of the "index:0" String as well and suspected it was problematic, thanks for confirming. And I've validated that the same issue occurs when you select the UI element by index id (i.e. logins.get(0)).

I'll clone the source to see if there's any type of workaround I can implement, but from your comments it sounds as though I should switch back to Ruby and wait for full support. Thanks for checking into this.

Java

@When("^user \"(.*?)\" logs in with valid credentials$")
public void user_logs_in_with_valid_credentials(String username) throws Throwable {
    UIElements logins = app.query("webView css:'#loginButton'");
    System.out.println("Element_Size="+logins.size());
    UIElement login = logins.get(0);
    System.out.println("Element_Id=" + login.getId());
    System.out.println("Element_Class=" + login.getElementClass());
    System.out.println("Element_Query=" + login.getQuery());
    System.out.println("Element_Text=" + login.getText());
    System.out.println("Element_Enabled=" + login.isEnabled());
    Rect rect = login.getRect();
    System.out.println("Center_X=" + rect.getCenter_x());
    System.out.println("Center_Y=" + rect.getCenter_y());
    login.touch();
}

Console

Element_Size=1
Element_Id=loginButton
Element_Class=ui-btn ui-btn-up-c ui-shadow ui-btn-corner-all
Element_Query=webView css:'#loginButton' index:0
Element_Text=
Element_Enabled=false
Center_X=97.95918
Center_Y=353.20407
com.thoughtworks.calabash.android.CalabashException: Failed to touch on: webView css:'#loginButton' index:0. (RuntimeError) No elements found. Query: webView css:'#loginButton' index:0
    at com.thoughtworks.calabash.android.CalabashWrapper.touch(CalabashWrapper.java:293)
    at com.thoughtworks.calabash.android.UIElement.touch(UIElement.java:196)
    at com.healthwyse.calabash.LoginStepDefs.user_logs_in_with_valid_credentials(LoginStepDefs.java:48)
    at ?.When user "demo@healthwyse.com" logs in with valid credentials(C:/_Workspace/calabash-android-java/calabash-android-java/src/test/resources/features/login.feature:13)
vishnukarthikl commented 10 years ago

If the ruby api is able to handle webviews with indices it would be very easy at our end to consume it. But since they don't have any notion of reference to UIElement, we can only provide a hacky API something like app.calabashQuery(String query) which can take the whole query with action

eg:

app.calabashQuery("touch(webView css:'#loginButton')")

so basically delegate the call to ruby client. Feel free to browse through the code and ideas are always welcome :)

mikechabot commented 10 years ago

Works like a charm - for a quick proof-of-concept hack, I quickly snipped the "index:N" String from the query variable, which is then passed to the Ruby script. I'll continue to make these one-off hacks for the time being, but will reevaluate if I need to abstract the change a little higher up. At any rate, I very much appreciate the feedback, and great library!

CalabashWrapper.java

    public void touch(String query) throws CalabashException {
        try {
            info("Touching - %s", query);
            query = query.substring(0, query.indexOf("index:"));
            container.put(QUERY_STRING, query);
            container.runScriptlet(String.format("touch(%s)", QUERY_STRING));
            pause();
        } catch (Exception e) {
            error("Failed to touch on: %s", e, query);
            throw new CalabashException(String.format("Failed to touch on: %s. %s", query, e.getMessage()));
        }
    }
Saieph commented 10 years ago

What kind of hack is available for setting text on a webview, as opposed to a touch action?

I have tried something like androidApplication.query("set_text(webview css:'input[name=\"host\"]', '192.168.2.110')");

to no avail...

mikechabot commented 10 years ago

Check out https://github.com/mikechabot/calabash-android-java/blob/master/src/com/thoughtworks/calabash/android/Keyboard.java - I sent this as a pull request to Vishnu, but I just haven't found the time to write proper tests for it.

Saieph commented 10 years ago

I'm very new to this, how exactly would I use that?

vishnukarthikl commented 10 years ago

Hi Mike,

I just tried out the Keyboard class, seems like it doesn't work if you have spaces in the string.

Currently calabash android java is based on calabash android 0.4.21. There are some breaking changes made in 0.5.1 which is the latest stable release. This release adds new functionality like handling keypress. So we shall migrate our api to accommodate those changes and shall release calabash android java (1.4.0) by next week.

Thanks Vishnu, @prateekbaheti

mikechabot commented 10 years ago

Hi Vishnu - good catch, I was only entering URLs with the Keyboard class, so I hadn't tested (or even tried for that matter) enter spaces. And that's great news about 0.5.1, glad they're addressing the root issue!

vishnukarthikl commented 10 years ago

Hi Mike, I have merged your pull request, but had to remove the keyboard class since enter text is now done through calabash directly. I have added a queryByCss which returns you a list of webelements and you can do actions like touch, enter_text and get property on them. Feel free to try out the new changes. I haven't drafted a new release though. Once everything seems fine, I'll made a 1.4.0 release.

Saieph commented 10 years ago

I just grabbed the 1.4.0 build and tried the following code:

    WebElements hostEditText = driver.queryByCss("input[name=\"host\"]");
    hostEditText.setText(validHost);

And I'm getting a java null pointer. In the calabash console I use this query:

query("webview css:'input[name=\"host\"]'")

And I'm able to see the element I'm looking for, and set the text when I need to do that. Am I doing something horrendously wrong in translating the query and action to my step definition?

Saieph commented 10 years ago

OK, I think I figured my stuff out. Do any of you know of any community based resources for calabash-android-java? Trying to figure some of this stuff out on my own is pretty tough. Thanks!

vishnukarthikl commented 10 years ago

@Gashzilla I don't think there are many people who are using this. So there isn't much community stuff happening. But the source code is completely open, so you can check it out. There is java doc also present https://vishnukarthikl.github.io/calabash-android-java. You can also checkout the AllActionsIT.java which has some tests that we run, so you can get a basic idea of how each method is called. If you have any doubts feel free to reach out to me. And also if you like this library spread the word and help the community grow :smiley:

BTW what was the problem that you had with the queryByCss()? I just now tried it with an input with a name attribute and it seemed to work.

Saieph commented 10 years ago

I will DEFINITELY spread the word! Thank you for the resources. I was just looking at AllActionsIT.java and had a question:

Suppose I have two lines in my feature - And I press the login button Then for my first time logging in I should see "mandatory_all"

That wind up as two steps...one to press the button, and the other that needs to validate that a specific message was received upon tapping the button. I may have missed something, but is there a specific way that I can wait until the presence of the webview element that corresponds to the message, PRIOR to attempting to validate that the message matches the expected value?

So, tap, wait for message, check to see if message matches expected?

I've tried a couple of things and I'm failing miserably...Thanks!

vishnukarthikl commented 10 years ago

You could do something like this

        WebElements button = application.queryByCss("button");
        button.touch();
        application.waitFor(new ICondition() {
            @Override
            public boolean test() throws CalabashException {
                return application.queryByCss("div").size() != 0;
            }
        }, 5);

        WebElements div = application.queryByCss("div");
        String result = div.getText();
        assertEquals("button was pressed", result);

so basically you will have to write a waitFor that gets executed till timeout or the condition satisfies. In your case, you will have to wait for some div where you display the result of pressing the button. Once the div gets loaded up. You can get the text and do assertions on it. Hope this helps.

Saieph commented 10 years ago

So, if I have something that can only be located and distinguished from another like class, like:

query("webview css:'.x-button-label' textContent:'Log In'")

...how does this translate to a queryByCss in calabash-android-java?

vishnukarthikl commented 10 years ago

queryByCss takes only the css property, it doesn't filter the properties. I thought calabash doesn't filter properties for webview elements. Do you think adding a filter option to queryByCss makes sense. Something like

queryByCss(".x-button-label","textContent:'Log In'")
Saieph commented 10 years ago

I would think it makes sense. Again, the particular app I'm testing is Sencha backed, so the elements are dynamic. I can actually, using the console, target the webview elements by x-button-label and then a text content label, so of course identification and interaction is pretty easy. My work around solution right now is to have the elements tagged by development with either a specific name or cls attribute and then I can use the compound css locator to identify and interact.

Thanks!!!

From the desk of (R)yan L. Bedino

On Sep 1, 2014, at 12:44 AM, vishnu karthik L notifications@github.com wrote:

queryByCss takes only the css property, it doesn't filter the properties. I thought calabash doesn't filter properties for webview elements. Do you think adding a filter option to queryByCss makes sense. Something like

queryByCss(".x-button-label","textContent:'Log In'") — Reply to this email directly or view it on GitHub.

vishnukarthikl commented 10 years ago

I think it would be better to have a method which takes the exact query that you provide in the irb. So I removed the queryByCss and added queryWebElements

     /**
     * returns a list of WebElements corresponding to the calabash query
     * eg: application.queryWebElements("webview css:'div' textContent:'login successful'");
     *
     * @param query calabash query
     * @return WebElements corresponding to the query
     * @throws CalabashException
     */
    public WebElements queryWebElements(String query) throws CalabashException {
        RubyArray array = calabashWrapper.query(query);
        return new WebElements(array, query, calabashWrapper);
    }

so you will just have to

application.queryWebElements("webview css:'.x-button-label' textContent:'Log In'")

this also helps to migrate uses who already use calabash ruby.

Saieph commented 10 years ago

Yeah, good point, that is a lot more elegant, and does make for some consistency with the irb, which is also what I use to analyze the UI elements in a webview.

Thanks so much for your direct action!

vishnukarthikl commented 10 years ago

Is there something else that you think are good to have, so that I can try to add it before making a new release with the?

Saieph commented 10 years ago

Honestly, I just started refactoring all of my Junit tests to the cucumber framework, using calabash-android-java for automating our hybrid application, so from what I can tell SO FAR, this api is going to work out great, given the ability to identify and interact using a class and name for a webview, so you have a glowing review AS-IS from me right now. I will continue looking thru the source code, and experimenting, and keep in contact with you for sure!!! And I’ll be preparing a blog soon to go over my adventures with re-architecting our automation…included in that will be my recommendation to use calabash-android-java and a link to this material in it!

Saieph commented 10 years ago

Let me ask, is there a way to avoid the "app not started" error I'm getting when running my features?

When I open the application                                      # StepDefinitionsNexus10.openApp()
  com.thoughtworks.calabash.android.CalabashException: Error starting the app:(RuntimeError) App did not start
    at com.thoughtworks.calabash.android.CalabashWrapper.start(CalabashWrapper.java:149)
    at com.thoughtworks.calabash.android.AndroidRunner.start(AndroidRunner.java:91)
    at MTCAPKCuke.features.StepDefinitionsNexus10.openApp(StepDefinitionsNexus10.java:79)
    at ?.When I open the application(C:/Users/rbedino/workspace/MTCAPKCuke/src/MTCAPKCuke/features/Test001FirstLogin.feature:4)
  Caused by: org.jruby.embed.EvalFailedException: (RuntimeError) App did not start
    at org.jruby.embed.internal.EmbedEvalUnitImpl.run(EmbedEvalUnitImpl.java:133)
    at org.jruby.embed.ScriptingContainer.runUnit(ScriptingContainer.java:1264)
    at org.jruby.embed.ScriptingContainer.runScriptlet(ScriptingContainer.java:1257)
    at com.thoughtworks.calabash.android.CalabashWrapper.start(CalabashWrapper.java:145)
    at com.thoughtworks.calabash.android.AndroidRunner.start(AndroidRunner.java:91)
    at MTCAPKCuke.features.StepDefinitionsNexus10.openApp(StepDefinitionsNexus10.java:79)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at cucumber.runtime.Utils$1.call(Utils.java:34)
    at cucumber.runtime.Timeout.timeout(Timeout.java:13)
    at cucumber.runtime.Utils.invoke(Utils.java:30)
    at cucumber.runtime.java.JavaStepDefinition.execute(JavaStepDefinition.java:35)
    at cucumber.runtime.StepDefinitionMatch.runStep(StepDefinitionMatch.java:37)
    at cucumber.runtime.Runtime.runStep(Runtime.java:298)
    at cucumber.runtime.model.StepContainer.runStep(StepContainer.java:44)
    at cucumber.runtime.model.StepContainer.runSteps(StepContainer.java:39)
    at cucumber.runtime.model.CucumberScenario.run(CucumberScenario.java:48)
    at cucumber.runtime.model.CucumberFeature.run(CucumberFeature.java:154)
    at cucumber.runtime.Runtime.run(Runtime.java:120)
    at cucumber.runtime.Runtime.run(Runtime.java:108)
    at cucumber.api.cli.Main.run(Main.java:36)
    at cucumber.api.cli.Main.main(Main.java:18)
  Caused by: org.jruby.exceptions.RaiseException: (RuntimeError) App did not start
    at RUBY.start_test_server_in_background(C:/Users/rbedino/AppData/Local/Temp/calabash-android-gems-1.4.0/gems/calabash-android-0.5.2/lib/calabash-android/operations.rb:609)
    at RUBY.perform(C:/Users/rbedino/AppData/Local/Temp/calabash-android-gems-1.4.0/gems/retriable-1.3.3.1/lib/retriable/retriable.rb:31)
    at RUBY.retriable(C:/Users/rbedino/AppData/Local/Temp/calabash-android-gems-1.4.0/gems/retriable-1.3.3.1/lib/retriable/retriable.rb:53)
    at RUBY.start_test_server_in_background(C:/Users/rbedino/AppData/Local/Temp/calabash-android-gems-1.4.0/gems/calabash-android-0.5.2/lib/calabash-android/operations.rb:608)
    at RUBY.start_test_server_in_background(C:/Users/rbedino/AppData/Local/Temp/calabash-android-gems-1.4.0/gems/calabash-android-0.5.2/lib/calabash-android/operations.rb:125)
    at RUBY.(root)(<script>:1)

And I wait for the application to become available               # StepDefinitionsNexus10.i_wait_for_the_application_to_become_available()
And I press the login button                                     # StepDefinitionsNexus10.tapLogin()
Then for my first time logging in I should see "mandatory_all"   # StepDefinitionsNexus10.validateLoginPage(String)
vishnukarthikl commented 10 years ago

Some diagnostic steps

It would be really great if you could blog about the experience you had with calabash-android-java :smile: and we can move this conversation to email, since few others are spammed by our conversations. (vishnucarbon at gmail dot com)