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 ...
});
});
Related
I'm trying to get a handle of a step to (hopefully) hide it/show it from the stage.
Here's what I have so far:
var steps = Xrm.Page.data.process.getActiveStage().getSteps();
I feel like I'm close, I can see in the console that they are there. But steps.getByName() returns null on all of them, that I know are there.
Any ideas ?
Thanks.
Apparently, this is not done through manipulating the step objects, but rather a simple way through the Xrm.Page client side reference
As such:
Xrm.Page.getControl('header_process_fieldName').setVisible(false);
Reference: http://www.inogic.com/blog/2014/07/how-to-apply-script-on-header-fields-and-bpf-fields/
Well, the problem itself is kind of hard to describe briefly, so here's a live example to demonstrate. It seems like I'm misunderstanding something about how Rx.js works, otherwise the functionality here comes from a bug.
What I tried to do was a simple reactive rendering setup, where what you see on the screen, and what events happen are both described in terms of Observables. The problem is that, for some indiscernible reason, the events are dropped entirely when the code is written one way, yet work fine with code that should theoretically be equivalent.
So, let's start with the first case in the example code above:
var dom = makeBox('one');
var clicks = Rx.Observable.fromEvent(dom, 'click');
If you create a DOM fragment, then you can simply use fromEvent to get an Observable for whatever event it emits. So far, so good. You can click this box and see a bunch of lines written to the log.
Now, the next step would be to make the DOM reactive, to express how it changes over time.
var domStream = Rx.Observable.return(makeBox('two'));
var clicks = domStream.flatMapLatest(function(dom) {
return Rx.Observable.fromEvent(dom, 'click');
});
That would make it an Observable, using return here to produce the simplest, constant case. The events you're interested in would be the ones emitted by the latest version of the dom, and that's exactly what the flatMapLatest operator does. This variant still works.
Ultimately, the goal would be to generate the current DOM state based on some application state. That is, map it from one Observable to another. Let's go with the simplest version for now, have a single constant value as the state, and then map it to the same fixed output we used previously:
var updates = Rx.Observable.return(1);
var domStream = updates.map(function (update) {
return makeBox('three');
});
var clicks = domStream.flatMapLatest(function(dom) {
return Rx.Observable.fromEvent(dom, 'click');
});
This should not be any different from the previous version. However, this outputs no events, no matter what you do.
What exactly is going on here? Did I misunderstand some fundamental concept of Rx, or what? I've run into some issues with hot vs cold Observables, but that seems unrelated in this minimal case. So, I'm kind of out of ideas. Can anyone enlighten me?
Sorry to tell you but it is a Hot vs Cold issue.
It is a subtle issue, but the difference between
Rx.Observable.return(makeBox('two'))
and
Rx.Observable.return(1).map(function() {return makeBox('three'); })
Is that the first returns a constant every time you subscribe to it, that is,
a box that you created initially. The second returns a new box every time the Observable is subscribed to, this causes a problem since you actually subscribe to the domStream variable twice, you are creating two instances of Box three, one which has event handlers but isn't shown and one that does not and is shown.
The fix is that you either need to use approach 2 or you need to convert the third into a hot stream either by using:
domStream.replay(1).refCount()
Or by using
domStream.publish()
then after all subscriptions are completed:
domStream.connect()
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());
I am attempting to use the autoNumeric jQuery plug-in which helps with the conversion of various currencies in jQuery.
The plug-in itself works when I use it in a jsFiddle example.
$(function () {
$('.money').autoNumeric('init', {
aSign: '$',
vMin: '-999999999.99',
nBracket: '(,)'
});
});
However, as soon as I integrate it into a big, legacy project, I start receiving the above error on line 194. I know why I'm getting the error - a string is not being passed into the negativeBracket function (negativeBracket(s, nBracket, oEvent) is the signature). Instead, it seems to be a jQuery object - e.fn.init1. I'm confused on how this might be happening. I realize the community may not be able to give a direct answer, but I would love (and will accept as an answer) being pointed in the right direction as nothing has jumped out at me so far.
Update
So, have some additional info that may be of help. It still has me stumped how it's happening (unfortunately, the answers below didn't help to provide any additional insight). When I link in autoNumeric, I key it off of any text field with the class money. It does work as I am typing in the box. I can see see formatting. However, when I tab into a new box, the box I just finished typing in clears itself completely after hitting line 152 in autoNumeric with the same exact error.
#Carlos487 was correct in his answer when he said I have an object that is not a string. I instead have an object that, I believe, is a function. Here's what I'm seeing in Chrome debugger tools:
e.fn.init[1]
> 0: input#price.money required
> context: input#price.money required
length: 1
selector: ""
> __proto__: Object[0]
The "arrowed" items can be further expanded out. I don't know if this provides any more clues, but it's at least something a bit different.
The errors like
no method XXXXX in Object
are produced because you are trying to call obj.XXXX() and obj is not of the desired type, in your particular case a string.
Have you tried in another browser because older or IE can be a little troublesome. I would recomend using chrome developer tools with your legacy app to see if anything else is conflicting or producing the error
I will bet money that you are using a second library which is interfering with jQuery. It has probably overridden $ with its own function.
Try using jQuery instead of $:
jQuery(function () {
jQuery('.money').autoNumeric('init', {
aSign: '$',
vMin: '-999999999.99',
nBracket: '(,)'
});
});
It turns out that the issue was a myriad of issue compounding into the error I saw. A couple things that was happening:
The validator plug-in was wrapping the jQuery object in its own structure (hence the charAt issue).
Once I fixed that, I also learned that some homegrown code was also wiping and rewriting data into the field to provide formatting (which is what autoNumeric is also doing), so autoNumeric would bomb out because it would get a null value and attempt to format it.
There was some other random craziness that also needed cleaned up. So...issue resolved! Still more to work on, but at least this hurdle is past. Thanks all for your help.
I am trying to bind a property of an object to a property that's bound in an ArrayController. I want all of this to occur after the object has already been created and added to the ArrayController.
Here is a fiddle with a simplified example of what I'm trying to achieve.
I am wondering if I'm having problems with scope - I've already tried to bind to the global path (i.e. 'App.objectTwoController.objectOne.param3') to set the binding to. I've also tried to bind directly to the objectOneController (which is not what I want to do, but tried it just to see if it worked) and that still didn't work.
Any ideas on what I'm doing incorrectly? Thanks in advance for taking the time to look at this post.
So in the example below (I simplified it a little bit, but same principles apply)... The method below ends up looking for "objectOne" on "objectTwo" instead of on the "objectTwoController".
var objectTwoController: Em.Object.create({
objectOneBinding: 'App.objectOne',
objectTwoBinding: 'App.objectTwo',
_onSomething: function() {
var objectTwo = this.get('objectTwo');
objectTwo.bind('param2', Em.Binding.from('objectOne.param3'));
}.observes('something')
});
The problem is that you can't bind between two none relative objects. If you look in the "connect" method in ember you will see that it only takes one reference object (this) in which to observe both paths (this is true for 9.8.1 from your example and the ember-pre-1.0 release).
You have few options (that I can think of at least).
First: You can tell the objects about each other and in turn the relative paths will start working. This will actually give "objectTwo" an object to reference when binding paths.
....
objectTwo.set('objectOne', this.get('objectOne');
....
Second: You could add your own observer/computed property that will just keep the two in sync (but it is a little more verbose). You might be able to pull off something really slick but it maybe difficult. Even go so far as writing your own binding (like Transforms) to allow you to bind two non-related objects as long as you have paths to both.
_param3: function(){
this.setPath('objectTwo.param2', this.getPath('objectOne.param3');
}.observes('objectOne.param3')
You can make these dynamically and not need to pre-define them...
Third: Simply make them global paths; "App.objectOneController.content.param3" should work as your binding "_from" path (but not sure how much this helps you in your real application, because with larger applications I personally don't like everything global).
EDIT: When setting the full paths. Make sure you wait until end of the current cycle before fetching the value because bindings don't always update until everything is flushed. Meaning, your alert message needs to be wrapped in Ember.run.next or you will not see the change.