mozilla / geckodriver

WebDriver for Firefox
https://firefox-source-docs.mozilla.org/testing/geckodriver/
Mozilla Public License 2.0
7.2k stars 1.53k forks source link

Geckodriver not sending complete text to input type:text #1554

Open jain87varun opened 5 years ago

jain87varun commented 5 years ago

System

Testcase

Scenario: Adding shipping address for an order

Steps to reproduce

  1. Launch Bestbuy.com
  2. Search for "6320040" (SKU ID for a product) or any product which can be shipped such as HDMI cable
  3. Add this product to the cart
  4. Navigate to the cart page
  5. Select fulfillment as shipping/delivery
  6. Click on checkout
  7. Fulfillment page is displayed
  8. Enter the address in the form

The code sent text correctly to input element where the type attribute was not defined

Framework Information

Geb code executing for issue

static content = {
    addressForm(wait: true) { String classNameText = "checkout__container", Integer index = 0 -> $(".${classNameText}", index).find("form") }

    firstNameTextBox(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[aria-labelledby=\"firstName\"]").module(TextInput) }
    lastNameTextBox(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[aria-labelledby=\"lastName\"]").module(TextInput) }
    addressStreetTextBox(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[aria-labelledby=\"street\"]").find("input").module(TextInput) }
    addressStreet2TextBox(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[aria-labelledby=\"street2\"]").module(TextInput) }
    cityTextBox(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[aria-labelledby=\"city\"]").module(TextInput) }
    stateDropdown(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[name=\"state\"]").module(Select) }
    zipCodeTextBox(wait: true) { classNameText, index -> addressForm(classNameText, index).find("[aria-labelledby=\"zipcode\"]").module(TelInput) }
}
void setAddressByClass(String classNameText, String address1, String address2, String city, String state, String zip, Integer index) {
    sleep(200)
    firstNameTextBox(classNameText, index).text = "View"
    lastNameTextBox(classNameText, index).text = "Automation"
    addressStreetTextBox(classNameText, index).text = address1
    addressStreet2TextBox(classNameText, index).text = address2
    cityTextBox(classNameText, index).text = city
    stateDropdown(classNameText, index).selected = state
    zipCodeTextBox(classNameText, index).text = zip
    sleep(200)
}

Stacktrace

Trace-level log

Added the trace level log FirefoxTrace.txt

Screen Shot 2019-05-14 at 3 46 22 PM

The same code works as expected in Chrome and Safari

Screen Shot 2019-05-14 at 4 18 33 PM
whimboo commented 5 years ago

Do those textinput fields have some event handlers attached (like change, or input) which mess around with the entered text? Sadly this site is behind an user account, so I cannot use the checkout feature, and also cannot test myself.

jain87varun commented 5 years ago

Henrik, you should be able to access bestbuy.com/checkout/r/fulfillment by adding an item in the best buy cart and then checkout and then continue as a guest. The site uses react native so maybe there are event handlers. However, assuming that they have event handlers, then I should have seen the same issue in the email input textbox, but I had not seen issues with that. I can see that in address input type is the text whereas, for email, a type is not defined for the input field. Also, the test worked in chrome and safari so event handlers should have created a problem in those browsers too but it did not.

twalpole commented 5 years ago

The most likely issue here is that the address field sends an XHR request for every key press made in the address box. This isn't normally an issue because people don't type that fast, however when automating the key presses are made with effectively 0 delay between them. This means you get a request generated for every character in the address and them all being sent at effectively the same time. When these requests arrive at a server that can handle more than one request at the same time you end up with out of order request processing, so the request from the sixth character may end up getting processed after the request generated by the 10th character, and (depending on exactly what the JS does when it receives the response) can end up resetting the address input to a previous state. That makes it look like the entire address didn't get processed, when in fact it did, it's just that an earlier substring actually got processed by the page/app after the complete string.

There are two solutions to that

  1. add some type of key debounce of 50ms or so to the field to limit the number of requests made
  2. add delay between the keystrokes by using the actions API to send the keys.
jain87varun commented 5 years ago

Thomas, I tried the second approach but with that, only the last key appeared. The XHR request is called on filling street address but I can see that even the first and last name text box has issues.

twalpole commented 5 years ago

Without seeing your code it's tough to say, but If all you did was send each key one at a time with sleeps in between rather than using the actions api to send keys with tick delays then seeing only the last character would be expected. Unfortunately I don't use geb so I don't know what the correct syntax for using the actions api in your code is. Note: the fact the last character is showing up proves it's not geckodriver at fault here, it's the site not processing rapid fire keystrokes correctly.

whimboo commented 5 years ago

Please note that our implementation of Element Send Keys doesn't use the Action primitives yet. Work to make that happens is covered by https://bugzilla.mozilla.org/show_bug.cgi?id=1418995.

@jain87varun it wold be great if you could try the Actions API suggestion from above and let us know if delaying the typing helps for you. Thanks!

a-margaritis commented 5 years ago

Hello all,

Recently switched from chrome to firefox and my cases start to fail because the input provided to forms was wrong. Immediately noticed that the input provided was only the first character of whatever input I was trying to sent. After searching the web came across this discussion.

Few words for my test setup. I am testing an application that a user could change some daily limits. The input fields are set up as follows: image and the acceptable events are the following: image

I am using Spock (spock-core:1.1-groovy-2.3) with Geb (2.3.1). Testing against chrome 78, firefox 70 and safari 13. In chrome and safari the input provided to the geb navigators is being inserted correctly to input fields where in firefox as I said above only the first character is only inserted.

Before proceeding further I want to mention that Geb has three ways to pass a value to an input field. The first is via the $("#dailyInput").value('myInputValue'), the second is with the use of = sign and finally with the use of << (ex: $("#dailyInput") << "myValue"). From all of them only the << manages to pass the whole input as is in the input field where the rest only pass the first character. But the catch is that << appends this value to the one already existing within the input so can't be used unless before send a key combination to clear the input.

Moving forward with the tests performed with the solutions mentioned above. I have opened the page under discussion and opened the network console window as well were I checked whether any call (XHR or AJAX ) was being triggered to back end system but not luck there. Nothing was send unless the user clicked on the submit form button.

After that I tried to pass my input via the Actions API. That seems to work without a problem. (Again I was required to clean the input field otherwise actually it was appending my new input value to the old one) - (in other words the same behaviour with the <<)

To this point I would finalise my comment but I have to admit that I see a strange behaviour. Starting with debugger my test and adding a breaking point just before the first input. Then via the evaluation window (intelliJ) I am executing the expression to enter my string. As usual the first character only is being entered. Then switching to firefox and opening the console, switching to intelliJ again and executing the expression once more, then the whole length of the string is being inserted. That is reproducible.

Unfortunately can't provide a page to work, test with since it is a customer page. I could provide the official one if you require but then the access to it would be a problem.

whimboo commented 5 years ago

@a-margaritis, I wonder if a specific registered key event handler is causing this problem. As I can see there are some for keydown, keypress, and keyup. Do you have a chance to remove some of those for testing? Probably one of those is the problem here. If that is the case and we know what's happening in that handler it would help us to further investigate the problem.

a-margaritis commented 5 years ago

Sorry for late response but the troubleshooting took more than I could imagine. Initially I kept only one of them and seemed that the Keyup was the issue but then also keydown was creating the problem. After further investigation we noticed that finally seems that neither keyup or keydown or both are the issue along to keypress to my extend understanding. If for example I change the event to 'change' then the input is always entered.

Few things are taking place while I am issuing the $('#daily').value('12'). First of all, all content within the field is cleared and then element looses focus. The blur event is triggered which then checks whether the field is empty and since it is it adds some classes to input filed and to it's label following tag as per business requirements. Then I had add debugger comments to keydown and keyup events. Then the keydown event it triggered with the event to contain the following info: image

Since I have entered the value 12 then we are ok. It handles the first key press. The logic behind keydown and keypress is the following: image

On keydown check whether the entered value is not a char then if I proceed to next break point I move to on keyup event. Again the keyValue is correct. We call the validateRules which checks wether the field is empty, it contains numbers only and in case those two rules are met then it removes the error classes entered before. The code that does this is the following: inp.removeClass('form-error').parent().find('label.form-error').remove(); were inp is the input provided by the user.

So far so good, from this point onwards two paths exist.

Something that I noticed and don't know whether it helps you or not. Let's assume that we have tried to add a value to our input field and it failed as before so we know that a focus in focus out occurred while it deleted cleared the field and then the keydown keyup events followed. Then we open the console to allow next .value call to succeed and we send our next input...... and we would expect again a blur event to be triggered while deleting the previous value then an inline error to be presented and then keydown and keyup. BUT that is not the case. The blur event is not triggered but rather that the keydown is triggered immediately followed by keyup then keydown and then keyup. In other words we skipped blur event.

So a question might be here, what are the actual steps that value performs on driver? If target field contains data then delete them, then send the keys one by one?