Capybara: page.execute_script() not working - selenium

I am trying to simulate drag and drop using jquery.simulate library in rspec feature.The execute_script lines in spec are:
page.execute_script("$('#slide_1').draggable();")
page.evaluate_script("$('#slide_1').simulate('drag', {dragTarget: '#library_swap', interpolation: {stepWidth: 10, stepDelay: 300 }});")
page.evaluate_script("$('#slide_1').simulate('drop');")
If I run the lines inside the execute script over chrome console,its working fine(drag-drop simulation works),but not working with execute_script

Since you're not getting any errors, the JS code you're passing to execute_script is actually being executed. Since you're not seeing the behavior you expect the most likely explanation is that you're executing the JS before the element is actually on the page, which would then just silently do nothing. The one thing that confuses me about the code is why you're calling draggable on the #slide_1 element since I would assume that had already been called in your app. Anyway - add an expectation before your execute_script calls to make sure the element is actually on the page
expect(page).to have_css('#slide_1')
execute_script("$('#slide_1')...
Also note, there shouldn't be any reason you need to use three different execute_script calls for this, you could just combine them all into one. In recent versions of Capybara you can also DRY up the commands by not specifying the selector again and instead passing the element to execute_script
slider = page.find(:css, '#slide_1')
execute_script("$(arguments[0]). ... ", slider)

Related

Robot Framework+Selenium: how to avoid "stale element" error (flaky test)?

I'm using Robot Framework and Selenium to test a website that has a language selector. I need to be able to select a language and then verify the page has actually changed to that language.
Since after choosing a new language the value in the lang attribute of the <html> tag changes, I decided I would use that to validate the language has been successfully changed. But I'm getting flaky results, as my test sometimes passes and sometimes doesn't.
This is the definition of the keyword I'm using:
CHANGE LANGUAGE
[Arguments] ${lang}
Select From List By Value ${LANGUAGE SWITCH} ?hl=${lang}
Wait Until Page Contains Element css=html
${doc lang} Get Element Attribute css=html#lang
Should Be True '${doc lang}'=='${lang}' timeout=15s
Since I have to execute this keyword quite a few times (one per each available language), I keep getting pretty often the dreaded "stale element" error: | FAIL | stale element reference: element is not attached to the page document.
I read through this article and a few other questions in here and understand this could happen if an element reference is updated after obtaining it. However, I'm not sure how exactly I should modify my keyword to avoid getting this error.
Using the information that everyone has so kindly provided, I may have found a potential fix (not sure if it's robust enough not to throw the exception anymore, but after a few test runs they have all passed): I added a "Wait Until Keyword Succeeds" and moved the language validation to a new keyword:
VALIDATE PAGE LANGUAGE
[Arguments] ${lang}
${doc lang} Get Element Attribute css=html#lang
Should Be True '${doc lang}'=='${lang}'
CHANGE LANGUAGE
[Arguments] ${lang}
Select From List By Value ${LANGUAGE SWITCH} ?hl=${lang}
Wait For Condition return document.readyState=="complete"
Wait Until Keyword Succeeds 5 5s VALIDATE PAGE LANGUAGE ${lang}
Then I call this "CHANGE LANGUAGE" keyword as many times as languages I need to test.
I added this as an answer instead of a comment so I could show the code in a more readable way.
In order to wait for a page to be ready to test after a user action (clicking a link or button, for example), the algorithm I've found that seems to be almost bulletproof is this:
get a reference to the html element
perform the action that will cause the page to change (eg: click a link or button)
wait for the html element to go stale - this signals that the refresh has started
wait for document.readyState to be "complete"
Step 4 may not be necessary, but it doesn't hurt.
This has worked extremely well for my team. This can still fail since you might have some async javascript that runs after document.readyState has been set, but there's simply no generic solution to that.
If your pages have a bunch of async javascript, you'll have to come up with your own scheme to know when the page is finally ready to be tested. For example, the last job to complete could set a flag, or you could wait until there are no pending async jobs, etc.
I'm not sure if you can do the above with robot keywords since it relies on the selenium staleness_of condition. It's pretty easy to implement in python, though.
The inspiration for this solution came from this blog post: How to get Selenium to wait for page load after a click
If you use my page object library for robot, this is built-in as a context manager.
As highlighted, a Stale Element error typically means that between the element retrieval and the corresponding action the element changed. Often this is due to a page refresh.
For this reason it is important to invest in a robust waiting approach. Not guessing that your page/application has completed loading, but instead knowing it has completed. This will not only prevent Stale Element errors but also speed up your tests as you're not unnecessarily waiting.
As the Get Element Attribute ${DOCUMENT}#lang is causing the stale element error and the Select From List By Value ${LANGUAGE SWITCH} ?hl=${lang} is causing the page refresh, then that leaves the Wait Until Page Contains Element html as your waiting approach.
As the <html> tag is always present and the first to be loaded in the DOM, this is not the best tag to wait for. I'd advise something unique for the loaded page or the last element of that page. Though I have to stress that this still constitutes to guessing the page has loaded.
It is better to invest in a robust waiting approach. Especially if your application is using a framework like Angular, React or jQuery then you have several Javascript markers to help you with this. For some frameworks there are even custom Robot Framework Libraries that support their specific markers.
Should your application not use a framework, then talk to your developers and have them develop this for you. Simplest would be a visible spinner, but a Javascript function that returns True will work just as well.
I have created a custom keyword when ever i want to click an element or perform and action i would call this custom keyword . This custom keyword uses the built-in keyword 'Wait Until Keyword Succeeds' which runs the specified keyword and retries if it fails. through this built-in keyword the number of retry's and the time to wait before trying to run the keyword again after the previous run has failed can be configured.
This custom keyword would call another custom keyword where three SeleniumLibrary keywords would be called. The first one would Waits until element locator is enabled keyword, which would wait until the element is not disabled nor read-only. Once the element is enabled then focus would be taken to the element where we are going to perform action. Finally perform the action. All these precautions has prevented me from Stale Element error.
HighLevelKeyword_Identify And Click Element
[Arguments] ${locator}
Wait Until Keyword Succeeds ${RETRY_ATTEMPTS} ${RETRY_AFTER} Identify And Click Element ${locator}
Identify And Click Element
[Arguments] ${locator}
Wait Until Element Is Enabled ${locator}
Set Focus To Element ${locator}
Click Element ${locator}

Codeception ElementNotVisibleException error, unable to select option, or click

I am unable to interact with an element using browser tests. It says the element is not interact-able, or not visible. This doesn't happen in Acceptance
Sometimes this solution doesn't work because the element is unavailable for some other cryptic reason.
We just had a situation where we couldn't use a <select> element to pick one of the options.
Further more, there was behaviour that was being triggered by the "change" event when the option was selected.
We were able to solve it like this.
$js = "jQuery('#chosen-option-quantity-2').val('2').trigger('change');";
$I->executeJS($js);
so the first command selects the option, and the second triggers the change event.
I hope that helps some one, even if it is me in the future.
The problem that is happening here is that the html element is being hidden by something, probably css somewhere. Because it is hidden (display:none), WebDriver can't see it, and therefore can't interact with it.
In order to fix this problem, you need to use JS to un-hide the element.
use this $I->executeJS('jQuery("#your-css-selector").show()');
This doesn't happen in Acceptance tests because PHP Browser looks at the Page Source, and so can see everything, while WebDriver see's what a user see's on the browser.
You may use PhpBrowser
It works only with HTML then how PhantomJs emulate the real browser
But, with PhpBrowser you can't see what see your browser (only HTML such I said)
Another way, try executeJs with PhantomJs as it said before

Getting unexpected results using selenium waitForNotVisible

I have some selenium based tests a feature where an item is deselected on the page, which causes that element to be removed from the page. Because it is ajax based I do a click for the deselect action, and then wait for the element to no longer be on the page before moving on. the basic flow is
click(TargetElement)
if(isElementPresent(targetElement)){
waitForNotVisible(targetElement)
}
...
This seems to work 100% of the time when run against a local selenium server instance, but when run against the selenium grid I have set up, it always times out on waitforNotVisible (in both cases, the conditional is always met).
Originally when this was failing, I didn't have the conditional and I thought that would clear it up, but it didn't. Maybe my expectations for waitForNotVisible are not correct, but I wonder why this would be working locally and not against the grid. All of my other tests seem to work fine via both methods.
And yes, I am using selenium 1; at the moment moving to selenium2/Webdriver is not an option in the short term, so please don't suggest using webdriver as a solution. At the moment I'm most interested in understanding why this would fail as-is.

autocomplete method refuses to fire in RSpec test

I am on Day 4 of trying to get an autocomplete field to fire in an RSpec test. Works super in the browser, it is just incredibly resistant to running in my request specs.
UPDATE: It looks like my RSpec/Capy scripts are running against the dev db instead of the test db. I'm using Pow, so I don't know what to set default_url_options[:host] or Capybara.app_host and Capybara.server_port to. I have a feeling if I fix this, it may work.
The stack is:
Rails 3.2.16
Capybara
RSpec
Poltergeist/PhantomJS
Pow
Zeus
Factory Girl
Click links, click buttons, fill_in fields all work great. But when it comes time to get this autocomplete to work, it absolutely refuses to work.
I am using this method:
def fill_autocomplete(field, options = {})
fill_in field, with: options[:with]
page.execute_script %Q{ $("##{field}").trigger('focus') }
page.execute_script %Q{ $("##{field}").trigger('keydown') }
selector = %Q{ul.ui-autocomplete li.ui-menu-item a:contains("#{options[:select]}")}
Capybara::Screenshot.screenshot_and_open_image
page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
page.execute_script %Q{ $("#{selector}").trigger('mouseenter').click() }
end
which I found here. The screenshot line is my own. But the line above:
page.should have_selector('ul.ui-autocomplete li.ui-menu-item a')
returns false. It works like a charm in the browser. I just can't for the life of me figure out why it won't work. I have tried everything I know how. How can I debug this?
The screenshot just shows the page I am expecting, with the field filled in appropriately. I even tested this with a "hello" alert that I inserted into the autocomplete call. Works flawlessly in the browser, but no result at all in the test.
In short, it looks like the following two lines are having no effect:
page.execute_script %Q{ $("##{field}").trigger('focus') }
page.execute_script %Q{ $("##{field}").trigger('keydown') }
I had a similar problem and even though Capybara's documentation says that have_selector will wait for the AJAX call to complete, it did not work for me.
The following worked in my case:
def fill_in_autocomplete(field_label, field_class, options = {})
field_id = "##{page.evaluate_script("$('#{field_class}').attr('id')")}"
selector = "ul.ui-autocomplete li.ui-menu-item a"
fill_in(field_label, with: options[:with])
page.execute_script("$('#{field_id}').trigger('focus')")
page.execute_script("$('#{field_id}').trigger('keydown')")
sleep(2) # Hack! not sure why the line below isn't working...
#expect(page).to have_selector(selector, text: options[:with])
page.execute_script("$('#{selector}').click()")
end
You can call the method above like this:
fill_in_autocomplete('Some label', '.js-some-field', with: 'Some value'
I didn't want to pass the field ID and opted for a class instead, which is why the first line in the helper gets the ID based on the element's class.
This kind of asynchronous call is really tricky to troubleshoot. As Xaid suggested, sometimes just putting in an arbitrary sleep suffices to get the job done.
I've found in my own coding that what typically happening is that though Capybara tries to intelligently wait for the AJAX to do its magic, something is causing Capybara to believe the conditions are met for it to continue when you're not actually ready for it to do so.
Digging into the code for Capybara, you can find that the delays around asynchronous behavior are accomplished with the synchronize wrapper function that's part of the Node::Base class. (See here, if you're curious how it actually works: https://github.com/jnicklas/capybara/blob/master/lib/capybara/node/base.rb#L73)
Essentially, it's doing a series of wait commands for you (0.05 second each time) and trapping any "I can't find that element" exceptions until the timeout (defaulted at 2 seconds) has passed. If for any reason, the element you're waiting on DOES exist before you expect it to, it will stop waiting and move on.
Dumping out the HTML of the page as it exists in the headless browser can be a great troubleshooting trick. Try inserting a puts page.body into your code before your screenshot code. That should output the HTML for the page. Does the code include the ul.ui-autocomplete li.ui-menu-item a where you expect it to be? How about after that delay Xiad suggested. Does it exist then?
For really sticky problems, I'll add the pry gem to my project and insert a binding.pry into the code. That lets me play with the browser from within the test to see what it's seeing. Granted, with timing issues, I typically can't act fast enough to see what it's seeing, but even that's a clue.
Hopefully, these troubleshooting tips are helpful.
I had this problem and no proposed solution could solve it. My tests always failed when trying to find the ul.ui-autocomplete element. I finally noticed, that jQuery autocomplete appends the ul to the end of the html page and NOT to the input field in question. In my spec, I follow the practice of targeting my forms explicitly by within my_form do and doing all the fill_instuff inside this block:
within my_form do
fill_autocomplete …
end
Of course this could never find the ul attached OUTSIDE this form element. My solution was simple: Use jQuery autocomplete's attribute appendTo: '#id_of_input_field' when initializing autocomplete. Now it can find my uland everything works fine.

PhantomJS: injecting a script before any other scripts run

Using PhantomJS, I'd like to inject some JS as if there was an extra <script> tag before any other <script> tags. This is because the scripts on the page use some functions that PhantomJS does not have, namely Function.prototype.bind and window.webkitRequestAnimationFrame. I have a JS file with custom implementations of the two and I'd like PhantomJS to use them when running the scripts on the page.
The difficulty is that if I do page.injectJs before page.open, the script is injected into an empty page and is not carried over to the page being opened.
Alternatively, if I do page.injectJs after page.open, it's too late as the JavaScript errors (undefined functions) have already occurred.
I've found a way that appears to work, but is obviously a hack:
page.onResourceReceived = function() {
page.injectJs('phantom-hacks.js')
};
This injects it many times (twice for each resource, apparently), but that's okay because my script is idempotent. However, I'd like to know the proper way to do this: inject it only once and before any scripts on the page are run.
Thanks :)
I don't think there's a "proper" way to inject such script other than hooking to events.
I've spent half a year working massively with PhantomJs and found no way to inject before all the errors start happening but after the page finished loading.
I would try to go through onInitialized, onLoadStarted, onLoadFinished. Inside the hooks I would call to page.evaluate() which would just modify DOM to have this extra whatever place you like.
I think one of them (the hooks) should give you the right timing you want.
Cheers