I'm writing tests with Angular Scenario test runner. Within a traditional form, I can enter text into an input, but I need to press enter to execute the query and there is no button to click on. Surely there is some easy way to do this, but I do not know what it is.
input('query').enter('foo bar');
// ... now what?
I tried to simulate a keypress with JQuery, but as this answer indicates JQuery is not loaded in the e2e scenarios scope. So I followed his advice (as well as that of this answer) to simulate the keypress:
element('#search_input').query(function(el, done){
var press = document.createEvent('keypress');
press.which = 13;
press.trigger(evt);
done();
});
But to this Angular replies:
NotSupportedError: DOM Exception 9
Error: The implementation did not support the requested type of object or operation.
Update
I realized that a very easy workaround is to include a hidden submit input in my form:
<input id="search-submit" type="submit" style="display:none;">
Then in the scenario: element('#search-submit').click(); does what is needed.
For a purer solution which doesn't involve modifying the HTML for the sake of testing, #florian-f's answer (as well as this one) provides access to jQuery within the DSL via:
var $ = $window.$;
which can be used there or passed to the callback. However, even with this access when triggering a press of enter I was not able to submit my form in the following manner:
$(selector).trigger($.Event('keypress', { which: 13 }));
This must be another issue all together. But I did find jQuery's submit function to do the trick:
$(#the_form).submit();
You can access to the app (runner in an iframe) instance of jQuery :
angular.scenario.dsl('appElement', function() {
return function(selector, fn) {
return this.addFutureAction('element ' + selector, function($window, $document, done) {
fn.call(this, $window.angular.element(selector));
done();
});
};
});
Then you can call the trigger method of jQuery in your test :
appElement('yourSelector', function(elm) {
elm.trigger('enter');//or keypress
});
There is also another possibility to trigger a key event. While your first approach
element('#search_input').query(function(el, done){
var press = document.createEvent('keypress');
press.which = 13;
press.trigger(evt);
done();
});
will be blocked by angular, this one
element(<selector>).query(function($el, done) {
var event = new CustomEvent('keyup');
event.keyCode = 13;
$el.val(2);
$el.get(0).dispatchEvent(event);
done();
});
will pass and trigger a keyup event on the element specified by the selector (keyCode = 13 = Enter Key). See https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent for further information.
Related
I am writing e2e tests in NightWatch v2.1.3 using page objects.
There are list of items, and an item can be selected either by click or by hotKey of its index.
Example: second element can be selected by click or shift+2.
Following code i have written, taking reference from the docs, but it is not working
browser.perform(function () {
const actions = this.actions({async: true});
console.log('performing hotKeys');
actions
.click('#option1')
.keyDown(Keys.SHIFT)
.keyDown(Keys.NUMPAD2)
.keyUp(Keys.NUMPAD2)
.keyUp(Keys.SHIFT);
});
Console is happening but the click and keyUp, keyDown is not working, when kept inside the .perform method.
What need to be fixed here?
return keyword is important. (silly mistake here)
Use 'a', 'b', '1', '2' for normal keys (single quotes are important, even for numbers)
click is not working, inside actions api. Better use the api click instead of userActions click. No idea why this click is added under new user-actions. The documentations does not have enough examples, one need to find out through hit and trial method.
Example:
For SHIFT + 1
browser
.pause(3000)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(Keys.SHIFT)
.keyDown('1')
.keyUp('1')
.keyUp(Keys.SHIFT);
})
.pause(2000);
For Shift + 10
(This depends on the way feature is developed, if app need both 1 and 0 to be pressed or not)
browser
.pause(3000)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown(Keys.SHIFT)
.keyDown('1')
.keyUp('1')
.keyDown('0')
.keyUp('0')
.keyUp(Keys.SHIFT);
})
.pause(2000);
// keyUp('10') wont work, it is not a valid key in your keyboard
For simple 'a'
(This depends on the way feature is developed, if app need both 1 and 0 to be pressed or not)
browser
.pause(3000)
.perform(function () {
const actions = this.actions({ async: true });
return actions
.keyDown('a')
.keyUp('a')
})
.pause(2000);
I'm trying to verify that an account was created successfully, but after clicking the submit button, I need to wait until the next page has loaded and verify that the user ended up at the correct URL.
I'm using pollUntil to check the URL client side, but that results in Detected a page unload event; script execution does not work across page loads. in Safari at least. I can add a sleep, but I was wondering if there is a better way.
Questions:
How can you poll on something like this.remote.getCurrentUrl()? Basically I want to do something like this.remote.waitForCurrentUrlToEqual(...), but I'm also curious how to poll on anything from Selenium commands vs using pollUntil which executes code in the remote browser.
I'm checking to see if the user ended up at a protected URL after logging in here. Is there a better way to check this besides polling?
Best practices: do I need to make an assertion with Chai or is it even possible when I'm polling and waiting for stuff as my test? For example, in this case, I'm just trying to poll to make sure we ended up at the right URL within 30 seconds and I don't have an explicit assertion. I'm just assuming the test will fail, but it won't say why. If the best practice is to make an assertion here, how would I do it here or any time I'm using wait?
Here's an example of my code:
'create new account': function() {
return this.remote
// Hidden: populate all account details
.findByClassName('nextButton')
.click()
.end()
.then(pollUntil('return location.pathname === "/protected-page" ? true : null', [], 30000));
}
The pollUntil helper works by running an asynchronous script in the browser to check a condition, so it's not going to work across page loads (because the script disappears when a page loads). One way to poll the current remote URL would be to write a poller that would run as part of your functional test, something like (untested):
function pollUrl(remote, targetUrl, timeout) {
return function () {
var dfd = new Deferred();
var endTime = Number(new Date()) + timeout;
(function poll() {
remote.getCurrentUrl().then(function (url) {
if (url === targetUrl) {
dfd.resolve();
}
else if (Number(new Date()) < endTime) {
setTimeout(poll, 500);
}
else {
var error = new Error('timed out; final url is ' + url);
dfd.reject(error);
}
});
})();
return dfd.promise;
}
}
You could call it as:
.then(pollUrl(this.remote, '/protected-page', 30000))
When you're using something like pollUntil, there's no need (or place) to make an assertion. However, with your own polling function you could have it reject its promise with an informative error.
Let's say we have a simple Backbone View, like this:
class MyView extends Backbone.View
events:
'click .save': 'onSave'
onSave: (event) ->
event.preventDefault()
# do something interesting
I want to test that event.preventDefault() gets called when I click on my element with the .save class.
I could test the implementation of my callback function, pretty much like this (Mocha + Sinon.js):
it 'prevents default submission', ->
myView.onSave()
myView.args[0][0].preventDefault.called.should.be.true
I don't think it's working but this is only to get the idea; writing the proper code, this works. My problem here is that this way I'm testing the implementation and not the functionality.
So, my question really is: how can I verify , supposing to trigger a click event on my .save element?
it 'prevents default submission', ->
myView.$('.save').click()
# assertion here ??
Thanks as always :)
Try adding a listener on the view's $el, then triggering click on .save, then verify the event hasn't bubbled up to the view's element.
var view = new MyView();
var called = false;
function callback() { called = true; }
view.render();
// Attach a listener on the view's element
view.$el.on('click', callback);
// Test
view.$('.save').trigger('click');
// Verify
expect(called).toBeFalsy();
So you want to test that preventDefault is called when a click event is generated, correct?
Couldn't you do something like (in JavaScript. I'll leave the CoffeeScript as an exercise ;)):
var preventDefaultSpy;
before(function() {
preventDefaultSpy = sinon.spy(Event.prototype, 'preventDefault');
});
after(function() {
preventDefaultSpy.restore();
});
it('should call "preventDefault"', function() {
myView.$('.save').click();
expect(preventDefaultSpy.callCount).to.equal(1);
});
You might want to call preventDefaultSpy.reset() just before creating the click event so the call count is not affected by other things going on.
I haven't tested it, but I believe it would work.
edit: in other words, since my answer is not that different from a part of your question: I think your first approach is ok. By spying on Event.prototype you don't call myView so it's acting more as a black box, which might alleviate some of your concerns.
This jQuery selector matches a Rails 3 HTML form for a new model: $('form[id^="new_"]')
I'd like to have a simple focus function run each time a matching form loads. Sometimes the forms are loaded via a simple GET but also via Ajax. In the latter case, the content returned can be either HTML or escaped JS.
I was hoping jQuery would be able to match all cases via the selector, .on(), and the "load" event, but I can't seem to make that work for ANY case. Code:
$(document).ready(function() {
$('form[id^="new_"]').on("load", function(){
console.log("Matched!")
});
})
Any ideas?
Thanks Justice. I'm afraid I wasn't able to get your code to work. I'm using the following callback with the new custom event defined outside it as shown and I don't think the $('form') is triggering the event.
$('.shows-children').bind('ajax:success', function(evnt, data, status, xhr){
var boxSelector = '#' + $(this).data("shows");
$(boxSelector).html(xhr.responseText);
$('form').trigger('customevent');
});
$(document).on('customevent','form[id^="new_"]', function(){
console.log('Matched!')
});
(I'm surprised it seems more involved than expected to have jQuery act on HTML returned in an Ajax response.)
$(document).on("change","form[id^=\"new_\"]" function(){
console.log("Matched!")
});
For delegation, you want to delegate the original selector to a parent, as the event will bubble up.
However, load does NOT bubble up. In this case, change may suffice, but it'll trigger and attempt to see if the delegate is valid every time the document changes.
I would then suggest that you create a custom event after AJAX loads for the form.
Example:
$(document).on("customevent","form[id^="new_"]" function(){
console.log("Matched!")
$.ajax(url, function(response){
//success
$(document).append(response);
$('form').trigger('customevent');
});
});
HTH
when my ajaxupload script finishes it adds a read-only input w/ the value of the image's URL.
it is a long script, but i think this is the relevant part that fires on successful completion:
var location = '<div id="'+ID+'_location" class="img_location">' + '<input name="'+ID+'" class="location regular-text" type="text" size="50" readonly="readonly" value="'+response+'" />';
$(container).append(location).show(); //create readonly input
$(container) is defined just as the parent div of the upload button. that part seems to work... the image is uploaded, it is saved properly, and the input w/ the image's location is added to to the DOM. but i've discovered a bug that if I click my SAVE button (which triggers my ajax save function) then this new input is NOT captured.
here is my save function:
$('form#childoptions').live('submit', function(e) {
e.preventDefault();
var values = $(this).serialize();
alert(values);
var data = {
action: 'save_function',
type : 'save',
_nonce: '<?php echo $nonce; ?>',
formdata: values
};
$.post(ajaxurl, data, function(response) {
//alert(response);
if(response == 1) {
show_message(1);
t = setTimeout('fade_message()', 2000);
} else {
show_message(99);
t = setTimeout('fade_message()', 2000);
}
});
//return false;
});
only the new input is not captured. the rest works properly. there is also no problem if i refresh in between as I presume the input is part of the DOM. which is why i thought to use .live. i thought i had solved the issue twice- 1. i wasn't using a "name" on the dynamic input and 2. i wasn't using .live on the form. but now i am doing both and not getting anywhere.
all help is much appreciated. let me know if there is more information I can provide.
It appears that your using live on the whole form, not on inputs. So the live event binding would try to pickup new forms with id childoptions. This won't work. You'd be better off using bind() instead. Have you tried:
$('form#childoptions').bind('submit', function(e) {…}
I'm curious if this will fix your issue.