I need to send some key-presses to a web app in an integration test that uses Capybara and WebKit. Using Selenium (WebDriver and Firefox) I can achieve it like this:
find("#element_id").native.send_keys :tab
but WebKit's native element node doesn't have a send_keys method. Actually native in WebKit returned a string containing a number. Is there another way to send keystrokes to WebKit? Maybe even some workaround using JavaScript/jQuery?
I've been trying to implement Marc's answer without any success, but I found some help from a similar question: capybara: fill in form field value with terminating enter key. And apparently there was a pull request from capybara that seems to address this issue.
What worked for me was:
before { fill_in "some_field_id", with: "\t" }
My example erases the text in the field and then presses Tab. To fill in a field with 'foobar', replace "\t" with "foobar\t". You can also use "\n" for the Enter key.
For your example, you could use:
find("#element_id").set("\t")
This worked for me with Poltergeist, to trigger the asterisk key:
find("body").native.send_key("*")
I had no luck with the other solutions; not even Syn.
This was to trigger an angular-hotkeys event.
You can do it like that:
keypress_script = "var e = $.Event('keydown', { keyCode: #{keycode} }); $('body').trigger(e);"
page.driver.browser.execute_script(keypress_script)
Now since Capybara-webkit 1.9.0 you can send key presses like enter and others using send_keys:
find("textarea#comment").send_keys(:enter)
Source: https://github.com/thoughtbot/capybara-webkit/issues/191#issuecomment-228758761
Capybara API Docs: http://www.rubydoc.info/github/jnicklas/capybara/Capybara%2FNode%2FElement%3Asend_keys
I ended up doing the following:
Capybara.current_driver = Capybara.javascript_driver
keypress_script = "$('input#my_field').val('some string').keydown();"
page.driver.browser.execute_script(keypress_script)
I discovered in Chrome, testing my JavaScript, that actually creating an $.Event with keyCode or charCode and then triggering that on my input field didn't put the characters in the input. I was testing autocompletion which required a few characters be in the input field, and it would start the autocompletion on keydown. So I set the input value manually with val, then trigger keydown to cause the autocompletion script to start.
For simple cases, triggering a keypress event in JS will work:
def press(code)
page.execute_script("$('#my-input').trigger($.Event('keypress', {keyCode: #{code}}))")
end
For a more general and robust answer, use this great library that goes through the trouble of triggering the right events (i.e. keydown, then keypress and finally keyup).
def type(string)
page.execute_script("Syn.click({}, 'my-input').wait().type(#{string.to_json})")
end
A more complex example can be found here
Here is my solution, which works with capybara 2.1.0:
fill_in('token-input-machine_tag_list', :with => 'new tag name')
page.evaluate_script("var e = $.Event('keydown', { keyCode: 13 }); $('#token-input-machine_tag_list').trigger(e);") # Press enter
Please, note, that in new capybara you have to use page.evaluate_script.
For Capybara Webkit, this is the solution I used:
def press_enter(input)
script = "var e = jQuery.Event('keypress');"
script += "e.which = 13;"
script += "$('#{input}').trigger(e);"
page.execute_script(script);
end
Then I use it cleanly in my test like:
press_enter("textarea#comment")
Related
I'm scraping [this page][1] to look for details of schools that are contained within the CSS selectors .box .column which is contained within a div .schools which is loaded dynamically and takes some time to appear.
I've done this with the watir gem and had no problems. Here's the code as reference.
browser = Watir::Browser.new
browser.goto('https://educationdestinationmalaysia.com/schools/pre-university')
js_doc = browser.element(css: '.schools').wait_until(&:present?)
schools_list = Nokogiri::HTML(js_doc.inner_html)
school_cards = schools_list.css('.box .columns .column:nth-child(2)')
I'm now trying to achieve the same with the kimurai gem but I'm not really familiar with Capybara.
What I've Tried
Changing the default max wait time
def parse(response, url:, data: {})
Capybara.default_max_wait_time = 20
puts browser.has_css?('div.schools')
end
using_wait_time
browser.using_wait_time(20) do
puts browser.has_css?('.schools')
end
Passing in a wait argument to has_css?
browser.has_css?('.schools', wait: 20)
Thanks for reading!
[1]: https://educationdestinationmalaysia.com/schools/pre-university
Your Watir code
js_doc = browser.element(css: '.schools').wait_until(&:present?)
returns the element, but in your Capybara code you're calling predicate methods (has_css?, has_xpath?, has_selector?, etc) that just return true or false. Those predicate methods will only wait if Capybara.predicates_wait is true. Is there a specific reason you're using the predicates though? Instead you can just find the element you're interested in, which will wait up to Capybara.default_max_wait_time or you can specify a custom wait option. The "equivalent" to your Watir example of
js_doc = browser.element(css: '.schools').wait_until(&:present?)
schools_list = Nokogiri::HTML(js_doc.inner_html)
school_cards = schools_list.css('.box .columns .column:nth-child(2)'
assuming you had Capybara.default_max_wait_time set to a number high enough for your app and testing setup
school_cards = browser.find('.schools').all('.box .columns .column:nth-child(2)')
If you do need to extend the wait for one of the finds you could do
school_cards = browser.find('.schools', wait: 10).all('.box .columns .column:nth-child(2)')
to wait up to 10 seconds for the .schools element to appear. This could also just be collapsed into
school_cards = browser.all('.schools .box .columns .column:nth-child(2)')
which will also wait (up to Capybara.default_max_wait_time) for at least one matching element to exist before returning it although depending on your exact HTML
school_cards = browser.all('.schools .column:nth-child(2)')
may be just as good and less fragile
Note: you do have to be using a Kimurai engine that supports JS - https://github.com/vifreefly/kimuraframework#available-engines - otherwise you won't be able to interact with dynamic websites
I want to run my perticular scenario or feature file more than one time.
Let's say if user enter 5 then i want my url to be hit 5 times.
is it possible in karate? Any help would be appreciated
Yes, read the docs: https://github.com/intuit/karate#loops
But also see example below using dynamic scenario outlines:
EDIT: using a Background will not work in Karate 1.3.0 onwards, please refer to this example: https://stackoverflow.com/a/75155712/143475
Background:
* def fun = function(i){ return { name: 'User ' + (i + 1) } }
* def data = karate.repeat(5, fun)
Scenario Outline:
* url 'http://httpbin.org/anything'
* request __row
* method post
Examples:
| data |
So run this, see how it works and study how it works as well.
Note that data driven features is an alternate approach where you can call a second feature file in a loop. So for example after using karate.repeat() 5 times like in the above Background, you use data as the argument to a second feature file that hits your url.
I'm switching from the Poltergeist to Cuprite driver for Capybara.
I have Javascript code which sets the message for a confirmation modal which I want to check in my feature specs.
The javascript is confirm("....").
With Poltergiest I could do page.driver.browser.modal_message.
This is unsupported by Cuprite, it there another way?
Capybaras accept_confirm (which cuprite supports) returns the string from the system modal:
text = accept_confirm do
# ... the actions that triggers the modal to appear
end
Or you can pass a string to accept_confirm to have it verify the string:
accept_confirm('the text to check') do
# ... the actions that triggers the modal to appear
end
Looking at the ferrum driver which Cuprite uses under the hood I can see it is possible register a hook for a dialog appearing.
message = nil
page.driver.browser.on(:dialog) do |dialog|
message = dialog.message
end
accept_confirm do
click_on progress_tab.name
expect(message).to eq text('...')
end
It isn't pretty.
Just new in Prestashop (1.6.0.6), I've a problem with my product page in admin. All translatable-field are to display:none (I inspect the code with chrome).
So when I want to create a new product I can't because the name field is required.
I thought that it was simple to find the .js whose do that but it isn't.
If somebody could help me, I would be happy.
Thank you for your help
Hi,
I make some searches and see that the function hideOtherLanguage(id) hide and show translatable-field element.
function hideOtherLanguage(id)
{
console.log(id_language);
$('.translatable-field').hide();
$('.lang-' + id).show();
var id_old_language = id_language;
id_language = id;
if (id_old_language != id)
changeEmployeeLanguage();
updateCurrentText();
}
When I set the Id to 1 (default language), it works. It seems that when I load the page, the function is called twice and the last calling, the id value is undefined. So the show() function will not work.
If somebody could help me. Thank you.
In my console, I see only one error
undefined is not a function.
under index.php / Line 1002
...
$("#product_form").validate({
...
But I find the form.tpl template and set this lines in comment but nothing change.
EDIT: According to comment on this link http://forge.prestashop.com/browse/PSCFV-2928 this can possibly be caused by corrupted installation file(s) - so when on clean install - try to re-download and reinstall...
...otherwise:
I got into a similar problem - in module admin page, when creating configuration form using PrestaShop's HelperForm. I will provide most probable cases and their possible solutions.
The solution for HelperForm was tested on PS 1.6.0.14
Generally there are 2 cases when this will happen.
First, you have to check what html you recieve.
=> Display source code - NOT in developer tools/firebug/etc...!
=> I really mean the pure recieved (JavaScript untouched) html.
Check if your translatable-fields have already the inline style "display: none":
Case 1 - fields already have inline style(s) for "display: none"
This means the template/html was already prepared this way - most probably in some TPL file I saw codes similar to these:
<div class="translatable-field lang-{$language.id_lang}"
{if $language.id_lang != $id_lang_default}style="display:none"{/if}>
Or particularly in HelperForm template:
<div class="translatable-field lang-{$language.id_lang}"
{if $language.id_lang != $defaultFormLanguage}style="display:none"{/if}>
Case 1 is the most easy to solve, you just have to find, where to set this default language.
Solutions
HelperForm
Look where you've (or someone else) prepared the HelperForm object - something like:
$formHelper = new HelperForm();
...
Somewhere there will be something like $formHelper->default_form_language = ...;
My wrong first solution was to get default form language from context - which might not be set:
$this->context->controller->default_form_language; //THIS IS WRONG!
The correct way is to get the default language from configuration - something like:
$default_lang = new Language((int)Configuration::get('PS_LANG_DEFAULT'));
$formHelper->default_form_language = $default_lang->id;
...this particularly solved my problem...
Other form-creations
If there is something else than HelperForm used for form creations, the problem is still very similar.
You have to find where in files(probably tpls) is a condition for printing display:none for your case - then find where is the check-against-variable set and set it correctly yourself.
Case 2 - fields don't have inline style(s) for "display: none"
This means it is done after loading HTML by JavaScript. There are two options:
There is a call for hideOtherLanguage(), but there is wrongly set input language - that means no language will be displayed and all hidden.Solution for this one can be often solved by solving Case 1 (see above). In addition there can be programming error in not setting the after-used language id variable at all... then you would have to set it yourself (assign in JavaScript).
Some script calls some sort of .hide() on .translatable-field - you will have to search for it the hard way and remove/comment it out.
PS: Of course you can set the language to whatever you want, it is just common to set it to default language, because it is the most easier and the most clear way how to set it.
I have a web page that opens a div when you click a button. This div allows you to drag a file from your desktop onto its area; the file then gets uploaded to the server. I'm working with the Ruby implementation of Selenium.
By using the JavaScript debugger in Firefox, I can see that an event called "drop" is being passed to some JavaScript code "handleFileDrop(event)". I presume that if I were to create a mock event and fire it somehow that I could trigger this code.
If found an interesting article that seemed to point me in a promising direction, but I'm still short of figuring it all out. I am able to pass JavaScript to the page using Selenium's get_eval method. Calling methods using this.browserbot is getting me the elements I need.
So:
How do I build the file object that
needs to be part of the mock drop
event?
How do I fire the drop event
such that it gets picked up as if I
had dropped a file in the div?
I post an RSpec test that simulate files drag and drop using Selenium webdriver.
It use jQuery to make and trigger a fake 'drop' event.
This code simulate drag and drop of a single file. For sake of simplicity I've stripped code that allow multiple files dropping. Tell me if you need it.
describe "when user drop files", :js => true do
before do
page.execute_script("seleniumUpload = window.$('<input/>').attr({id: 'seleniumUpload', type:'file'}).appendTo('body');")
attach_file('seleniumUpload', Rails.root + 'spec/support/pdffile/pdfTest.pdf')
# Trigger the drop event
page.execute_script("e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : seleniumUpload.get(0).files } }; $('#fileDropArea').trigger(e);")
end
it "should ..." do
should have_content '...'
end
P.S.: remember to replace #fileDropArea with ID of your drop area.
P.P.S: don't use evaluate_script in place of execute_script, otherwise selenium get stuck evaluating complex jQuery objects!
UPDATE:
I've write a method you can reuse and do the stuff written above.
def drop_files files, drop_area_id
js_script = "fileList = Array();"
files.count.times do |i|
# Generate a fake input selector
page.execute_script("if ($('#seleniumUpload#{i}').length == 0) { seleniumUpload#{i} = window.$('<input/>').attr({id: 'seleniumUpload#{i}', type:'file'}).appendTo('body'); }")
# Attach file to the fake input selector through Capybara
attach_file("seleniumUpload#{i}", files[i])
# Build up the fake js event
js_script = "#{js_script} fileList.push(seleniumUpload#{i}.get(0).files[0]);"
end
# Trigger the fake drop event
page.execute_script("#{js_script} e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : fileList } }; $('##{drop_area_id}').trigger(e);")
end
Usage:
describe "when user drop files", :js => true do
before do
files = [ Rails.root + 'spec/support/pdffile/pdfTest1.pdf',
Rails.root + 'spec/support/pdffile/pdfTest2.pdf',
Rails.root + 'spec/support/pdffile/pdfTest3.pdf' ]
drop_files files, 'fileDropArea'
end
it "should ..." do
should have_content '...'
end
end
As #Shmoopy asked for it, here's a C# translation of the code provided by #micred
private void DropImage(string dropBoxId, string filePath)
{
var javascriptDriver = this.Driver as IJavaScriptExecutor;
var inputId = dropBoxId + "FileUpload";
// append input to HTML to add file path
javascriptDriver.ExecuteScript(inputId + " = window.$('<input id=\"" + inputId + "\"/>').attr({type:'file'}).appendTo('body');");
this.Driver.FindElement(By.Id(inputId)).SendKeys(filePath);
// fire mock event pointing to inserted file path
javascriptDriver.ExecuteScript("e = $.Event('drop'); e.originalEvent = {dataTransfer : { files : " + inputId + ".get(0).files } }; $('#" + dropBoxId + "').trigger(e);");
}
You can use Blueduck Sda (http://sda.blueducktesting.com)
Is an OSS that has implemented ALL selenium functions (It works with selenium RC) but it allows you to automate Windows actions. So you can test web, and interact with the OS.
So you can make your test, and then, just tell the mouse to click on the element and drop it where you want!
Nice testing!
Note: you should also add
e.originalEvent.dataTransfer.types = [ 'Files' ];