Asserting an element is focused - javascript

According to the How do I assert an element is focused? thread, you can check if an element is focused by switching to an activeElement() and assert this is the same element you've expected to have the focus:
expect(page.element.getAttribute('id')).toEqual(browser.driver.switchTo().activeElement().getAttribute('id'));
In my case, the currently focused element does not have an id attribute.
What should I do instead of checking an id?
Bonus question: Also, as you can see from my tries to solve it, it looks like I cannot expect/assert an element (or web element) as a complete object. Why?
I've tried:
expect(page.element).toEqual(browser.driver.switchTo().activeElement());
But is is failing with an error I cannot even understand - there is a huge traceback (it is about 10 minutes to scroll in the console), but no user-friendly error inside.
I've also tried to use getWebElement():
expect(page.element.getWebElement()).toEqual(browser.driver.switchTo().activeElement());
But this resulted into the following error:
Error: expect called with WebElement argument, expected a Promise. Did
you mean to use .getText()?
Using the latest protractor development version.

In my answer I'm going to assume activeElem and pageElem are both protractor element finders, and are pointing to the same web element.
First to answer your question about why
expect(activeElem).toEqual(pageElem);
Gets into an infinite loop, it's because protractor patched jasmine's expect to resolve the promise before asserting, so that things like expect(activeElem.getText()).toEqual('text'); works without having to do
activeElem.getText().then(function(text) {
expect(text).toEqual('text');
})
You could say, why not just resolve the promise once? But then there are nested promises.
So now you might be thinking this is an issue, but it really isn't because you would never compare two elementFinders in a real use case. Jasmine's toEqual does a reference check, and not a deep compare, so expect(activeElem).toEqual(pageElem), is just the same as a simple reference comparison: (activeElem === pageElem).toToTruthy(), and there's really no point doing that. (Note element(by.css('html')) === element(by.css('html')) is false because it's not the same reference.)
So, to answer the real question for this thread: how to see if two elementFinders have the same underlying webelements:
expect(activeElem.getId()).toEqual(pageElem.getId());

It's weird that it's expecting a promise only and could not handle a webdriver element... I had the same HUGE stacktrace as you.
Anyway, would you accept this kind of solution: send a "dumb" promise with a nice comment to justify why you had to do that. It's more a workaround than a semantic solution I admit.
expect(page.element.getInnerHtml())
.toEqual(browser.driver.switchTo().activeElement().getInnerHtml());
It's working for me ;)
EDIT: Bonus answer
The reason you can't call expect with a WebElement comes from the Webdriver Control Flow Principle (I'm sure you already know about) and this line in jasminewd, the adapter for jasmine to Webdriver developped and used by Protractor ;)

For the future readers, I've created a custom matcher to assert if an element is active/focused:
beforeEach(function() {
jasmine.addMatchers({
toBeActive: function() {
return {
compare: function(elm) {
return {
pass: elm.getId().then(function (activeElementID) {
return browser.driver.switchTo().activeElement().getId().then(function (currentElementID) {
return jasmine.matchersUtil.equals(currentElementID, activeElementID);
});
})
};
}
};
}
});
});
Usage:
expect(element(by.css("#myid")).toBeActive();

What about using CSS selectors and :focus
expect(element.all(by.css('#myid:focus')).count()).toBe(1);

I solved it by using protractor's:
ElementFinder.prototype.equals
const activeElement = await browser.driver.switchTo().activeElement();
const potentialyFocusedElement = await component.$('your-locator');
const isFocused = await potentialyFocusedElement.equals(activeElement);
I am not sure how exactly equals work, if it does a deep compare, or it somehow compares instances. But it works.
Note that you can not call activeElement.equals(...) since it is not an ElementFinder, it is a WebElement.

You need to come up with a way to tell Protractor/webdriver how to find your element on the page. Protractor uses JavaScript to find elements, so any of the tools available for inspecting the DOM are available. Protractor and webdriver wrap these APIs in the various by flavors:
http://angular.github.io/protractor/#/api?view=ProtractorBy
In addition to the basic flavors, Protractor adds Angular-aware flavors (so you can search by Angular binding, etc).
If your element does not have any distinguishing characteristics, then it might be worth adding something to make it easier to test. Or, (though this is often frowned upon because its so fragile), you can use an xpath. See https://github.com/angular/protractor/blob/master/docs/locators.md

You can simply check the text (or anything else) of the element:
var currentElement = browser.switchTo().activeElement();
expect(currentElement.getText()).toEqual(page.element.getText());

Related

getting and using an attribute in protractor tests

this question is probably pretty basic, but I don't have the right feel for angularjs and protractor yet, as I'm new to using this. This might therefore be an XY problem kind of question. If that is the case, I'd be happy to know about this, and get a hint what to look for instead.
Actual Question
Say I want to test a slider, and wish to set the the slider-knob to the middle of that slider, to then compare the value the slider has to an expected value.
The slider might have a width that I do not know beforehand, so I'm wondering what the correct way is to get and use such an unknown value in a protractor test.
Do I wrap the it(..) statement into a promise that gets the desired value (for example via getAttribute()? or is there are better way to go on about this?
I've solved it by just using promise chaining to work with the values. I don't like that I have to do this for every spec where I need the values, but it seems nice enough otherwise.
Example:
it('should test some stuff', () => {
page.someElement.getAttribute('someAttribute').then(someAttribute => {
// ... use someAttribute here ...
});
});

How to develop a custom protractor locator?

I have an angular-application where I put custom html-attributes ("data-testid='saveButton'") for identification on e.g. buttons, inputs etc.
Protractor has no built-in locator for this: the xpath-way does not work as this will always search from the document root (to overcome this limitation I would have to do quite a lot of string-magic to build the xpath-string, so I discarded this variant).
So I wanted to write my own locator, and consulted the official Protractor Documentation.
The documentation leaves me with an important question:
What methods are available on the parent-element-object? In their example they use using.querySelectorAll('button'); but how do they know they can use this?
I searched for 'querySelectorAll' in the protractor-documentation to see if it is maybe a method of ElementFinder or WebElement, but I didn't find it.
Also I found no way to debug the locator (only stop the protractor-test by browser.pause()), so I can't look at runtime to see what I can do with the given parent-element.
So does anybody know how I can get information about what methods the parent-element has?
The parent-element, which is essentially the context of the selector is a DOM element that gets handled by the browser - it can use whatever DOM function there is, in this case Element.querySelectorAll. As the addLocator's example says about the function you pass to it:
This function will be serialized as a string and will execute in the browser.
Also, keep in mind that there is a way to provide context to the xpath selectors thus avoiding the search from root:
var context = element(by.css('.some-parent-element-class'));
var divDirectDescendants = context.element(by.xpath('div'));
var divAllDescendants = context.element(by.xpath('.//div'));
With CSS these would be:
var divDirectDescendants = element(by.css('.some-parent-element-class > div'));
var divAllDescendants = element(by.css('.some-parent-element-class div'));
Also, there's a way to achieve what you want with a CSS selector (which is what querySelectorAll is, after all):
var everyElementWithMyAttr = element.all(by.css('[data-testid="saveButton"]'));

How can I spy on element?

Let's say I have this directive:
.directive 'panelcreator', ->
(scope, element)->
element.bind 'mouseenter', ->
element.addClass 'alert alert-danger'
To keep this a unit spec, I want to stub out element.addClass, right? So how do I go about doing this?
My attempt:
describe 'panelcreator', ->
describe 'something', ->
beforeEach ->
spyOn(element, 'addClass')
element.trigger('mouseenter')
it 'shoud create a panel', ->
expect(element.addClass).toHaveBeenCalledWith 'alert alert-danger'
Gives me:
Error: Expected a spy, but got Function.
How do I go about analysing a variable such as element in future so I can find out the method chain I need to pass SpyOn?
If using jQuery, addClass will be on the jQuery object:
spyOn($.fn, 'addClass');
If not, it will be on jqLite (angular.element) object:
spyOn(angular.element, 'addClass');
You don't need to stub addClass to "keep it a unit spec", there's nothing wrong with using a real Element.
Stubbing a method is good when testing use of some discrete interface with a single point of access. DOM elements, however, are complex data structures with (usually) more than one way to perform a certain action. If you were to test that your function adds an element to an Array, you wouldn't want to stub .push() either (as there are many other ways to add an item). So, when testing DOM interaction, you can just add DOM elements to the list of "utility" stuff you're allowed to use (instead of stubbing).
When testing whether a class is added, just check its presence.
Note that this makes your bounty question invalid. To make it absolutely clear in general terms:
Yes, you could inspect the source code of jqLite (possibly by stepping through addClass call) and find out that you need to observe the real Element, not its angular.element() wrapper. Thus, you could try to stub Element.classList.add().
However, the whole previous paragraph is irrelevant. Stubbing is only appropriate when you need to test use of an entity without the need to provide an implementation and/or rely on it. Both Element and its jqLite wrapper have guaranteed forward-compatible stable functionality and their implementation is implicitly provided, since your code runs in a web browser and uses AngularJS. This makes stubbing either of them counter-productive.
Have you tried
spyOn(element.prototype, 'addClass') ?
and then
expect(element.prototype.addClass).toHaveBeenCalledWith 'alert alert-danger'

Why is "this" more effective than a saved selector?

I was doing this test case to see how much using the this selector speeds up a process. While doing it, I decided to try out pre-saved element variables as well, assuming they would be even faster. Using an element variable saved before the test appears to be the slowest, quite to my confusion. I though only having to "find" the element once would immensely speed up the process. Why is this not the case?
Here are my tests from fastest to slowest, in case anyone can't load it:
1
$("#bar").click(function(){
$(this).width($(this).width()+100);
});
$("#bar").trigger( "click" );
2
$("#bar").click(function(){
$("#bar").width($("#bar").width()+100);
});
$("#bar").trigger( "click" );
3
var bar = $("#bar");
bar.click(function(){
bar.width(bar.width()+100);
});
bar.trigger( "click" );
4
par.click(function(){
par.width(par.width()+100);
});
par.trigger( "click" );
I'd have assumed the order would go 4, 3, 1, 2 in order of which one has to use the selector to "find" the variable more often.
UPDATE: I have a theory, though I'd like someone to verify this if possible. I'm guessing that on click, it has to reference the variable, instead of just the element, which slows it down.
Fixed test case: http://jsperf.com/this-vs-thatjames/10
TL;DR: Number of click handlers executed in each test grows because the element is not reset between tests.
The biggest problem with testing for micro-optimizations is that you have to be very very careful with what you're testing. There are many cases where the testing code interferes with what you're testing. Here is an example from Vyacheslav Egorov of a test that "proves" multiplication is almost instantaneous in JavaScript because the testing loop is removed entirely by the JavaScript compiler:
// I am using Benchmark.js API as if I would run it in the d8.
Benchmark.prototype.setup = function() {
function multiply(x,y) {
return x*y;
}
};
var suite = new Benchmark.Suite;
suite.add('multiply', function() {
var a = Math.round(Math.random()*100),
b = Math.round(Math.random()*100);
for(var i = 0; i < 10000; i++) {
multiply(a,b);
}
})
Since you're already aware there is something counter-intuitive going on, you should pay extra care.
First of all, you're not testing selectors there. Your testing code is doing: zero or more selectors, depending on the test, a function creation (which in some cases is a closure, others it is not), assignment as the click handler and triggering of the jQuery event system.
Also, the element you're testing on is changing between tests. It's obvious that the width in one test is more than the width in the test before. That isn't the biggest problem though. The problem is that the element in one test has X click handlers associated. The element in the next test has X+1 click handlers.
So when you trigger the click handlers for the last test, you also trigger the click handlers associated in all the tests before, making it much slower than tests made earlier.
I fixed the jsPerf, but keep in mind that it still doesn't test just the selector performance. Still, the most important factor that skewes the results is eliminated.
Note: There are some slides and a video about doing good performance testing with jsPerf, focused on common pitfalls that you should avoid. Main ideas:
don't define functions in the tests, do it in the setup/preparation phase
keep the test code as simple as possible
compare things that do the same thing or be upfront about it
test what you intend to test, not the setup code
isolate the tests, reset the state after/before each test
no randomness. mock it if you need it
be aware of browser optimizations (dead code removal, etc)
You don't really test the performance between the different techniques.
If you look at the output of the console for this modified test:
http://jsperf.com/this-vs-thatjames/8
You will see how many event listeners are attached to the #bar object.
And you will see that they are not removed at the beginning for each test.
So the following tests will always become slower as the previous ones because the trigger function has to call all the previous callbacks.
Some of this increase in slowness is because the object reference is already found in memory, so the compiler doesn't have to go looking in memory for the variable
$("#bar").click(function(){
$(this).width($(this).width()+100); // Only has to check the function call
}); // each time, not search the whole memory
as opposed to
var bar = $("#bar");
...
bar.click(function(){
bar.width(bar.width()+100); // Has to search the memory to find it
}); // each time it is used
As zerkms said, dereferencing (having to look up the memory reference as I describe above) has some but little effect on the performance
Thus the main source of slowness in difference for the tests you have performed is the fact that the DOM is not reset between each function call. In actuality, a saved selector performs just about as fast as this
Looks like the performance results you're getting has nothing to do with the code. If you look at these edited tests, you can see that having the same code in two of the tests (first and last) yield totally different results.
I don't know, but if I had to guess I would say it is due to concurrency and multithreading.
When you do $(...) you call the jQuery constructor and create a new object that gets stored in the memory. However, when you reference to an existing variable you do not create a new object (duh).
Although I have no source to quote I believe that every javascript event gets called in its own thread so events don't interfere with eachother. By this logic the compiler would have to get a lock on the variable in order to use it, which might take time.
Once again, I am not sure. Very interesting test btw!

document.evaluate won't work from content script

var allTags = document.evaluate("//*[contains(#src,'"+imgSrc+"')]", document, null, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
This is the code that gives errors, it gives:
Uncaught Error: TYPE_ERR: DOM XPath Exception 52
Could someone tell me what the problem is?
I don't have a precise answer, but I can guess and give a workaround.
First the work around: change UNORDERED_NODE_SNAPSHOT_TYPE to a type that don't create a snapshot(unless you need it that way) and returns multiple nodes like UNORDERED_NODE_ITERATOR_TYPE(or ANY_TYPE).
And my guess: After reading the spec it say for this function 'TYPE_ERR: Raised if the result cannot be converted to return the specified type.'. It may be the case it can't allocate the resources to create a snapshot or something like this(the workaround assumes that).
Edit:
The real problem is most likely not the call to document.evaluate is that in your code you do allTags.iterateNext and this call expects allTags to be a *_NODE_ITERATOR_TYPE and not a *_NODE_SNAPSHOT_TYPE, using allTags.snapshotItem don't cause an error to be thrown. I wrote a sample at jsfiddle, it changes the borders after 2 seconds using the call to evaluate in your question and iterate over the elements in the proper way.

Categories

Resources