I have a function that waits for a selector under the scope of an element. The ElementHandle I'm passing in doesn't seem to be getting updated as the DOM updates- I log the outerHTML of this element each time my waitForFunction call fails, and changes aren't there.
async function waitForSubElSelector(page, el, subElSelector) {
// offset/rects checks for visible check (from https://stackoverflow.com/a/33456469)
await page.waitForFunction((selector, scopeEl) => {
const subEl = scopeEl.querySelector(selector)
let found = !!(subEl && (subEl.offsetWidth || subEl.offsetHeight || subEl.getClientRects().length));
if (!found) {
console.log(`waitForSubElSelector failed EL: ${scopeEl.outerHTML} SELECTOR: "${selector}", ${!subEl ? 'not found' : subEl.offsetWidth + ', ' + subEl.offsetHeight + ', ' + subEl.getClientRects().length}`)
}
return found;
}, {}, subElSelector, el
).catch(e => {
console.log(`waitForSubElSelector failed w/ selector '${subElSelector}'`)
throw e;
})
return await el.$(subElSelector);
}
I also verified this in the browser Console - I'm able to find the element and then the selector from there.
One use case is waiting for a Next button to be available, e.g.:
await waitForSubElSelector(this.browserPage, pageEl, '#nextButton:not([disabled])')
How can I get this working?
One idea I had— maybe my web framework replaces part of the DOM so the ElementHandle I have isn't the current one; is there some 'in-DOM' check in Puppeteer?
By design I'm using one div above form to show errors. For this, case I also need some prefix, before each error message, typically it's text from corresponding label tag.
I'd implemented this as extension, just as the new function, which should be executed manually... but in this case I've lost default ui behaviuor.
So, the question is it possible to implement such behaviour as part of parsley (by extending parsley)?
You can customize where parsley puts the error with the errorsContainer. Could be a function that creates the container within your summary area.
That errors container could also have some sort of other information that you populate.
There is a need for a better error message system, see among others this issue.
I'd implemented desired behavior by extending two internal functions. Not sure it was good idea, but it does exactly what I needed in some particular cases:
(function ($) {
function getFieldLabel(parsleyField, suffix) {
var result = '',
labels = $('label[for=' + parsleyField.$element.prop('id') + ']');
suffix = suffix || '';
if (labels.length) {
result = labels[0].innerText + suffix;
}
return result;
}
window.ParsleyExtend = window.ParsleyExtend || {};
window.ParsleyExtend = $.extend(window.ParsleyExtend, {
// NB! this method replaces parsley's default `_addError`
_addError: function _addError(name, _ref5) {
var message = _ref5.message;
var assert = _ref5.assert;
this._insertErrorWrapper();
this._ui.$errorsWrapper.addClass('filled').append($(this.options.errorTemplate).addClass('parsley-' + name).html(
getFieldLabel(this, ' - ') + (message || this._getErrorMessage(assert))
));
},
// NB! this method replaces parsley's default `_updateError`
_updateError: function _updateError(name, _ref6) {
var message = _ref6.message;
var assert = _ref6.assert;
this._ui.$errorsWrapper.addClass('filled').find('.parsley-' + name).html(
getFieldLabel(this, ' - ') + (message || this._getErrorMessage(assert))
);
}
});
})(jQuery);
So I found this little bit of code online that works pretty well for waiting for a unique identifier to be loaded on the page before you interact with it..
this.waitForElementToBePresent = function(element) {
browser.wait(function() {
return element.isPresent();
}, 60000);
};
I am new to JS and Protractor.. I was wondering how this code could be changed to wait for the presence of an element where there are multiples. I know you use $$ for the identifier when there are multiples, but how can I change this method to recognize that so I would then do something like..
utility.waitForElementsToBePresent(myElement).get(0);
Look at Alecxe's answer on this question. I've been using it for awhile and it works perfectly. Here's my slightly modified version:
// wait for X number of elements
presenceOfAll = function (elem, num, timeout) {
var time = timeout || 5000;
console.log('Waiting for elements ' + elem.locator() + ' to have a count of ' + num);
return browser.wait(function () {
return elem.count().then(function (count) {
return count >= num;
});
}, time, 'Failed waiting for ' + elem.locator() + ' to have ' + num + ' total items');
};
Rather than making a new function I would probably just last element in a group then wait for it.
var els = elements.all(by.css("#id"));
waitForElementToBePresent(els.last());
As something to remember ther is "isPresent" and "isDisplayed", present mean that an element exists on the page, visible or not. If you want to wait for it to actually show on the page, first wait for it to be present then wait for it to be displayed.
http://www.protractortest.org/#/api?view=ElementArrayFinder.prototype.last
So,I am trying to use the twitch API:
https://codepen.io/sterg/pen/yJmzrN
If you check my codepen page you'll see that each time I refresh the page the status order changes and I can't figure out why is this happening.
Here is my javascript:
$(document).ready(function(){
var ur="";
var tw=["freecodecamp","nightblue3","imaqtpie","bunnyfufuu","mushisgosu","tsm_dyrus","esl_sc2"];
var j=0;
for(var i=0;i<tw.length;i++){
ur="https://api.twitch.tv/kraken/streams/"+tw[i];
$.getJSON(ur,function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> "+tw[j]+"<p>"+""+"</p></li>");
if(json.stream==null){
$(".stat").append("<li>"+"Offline"+"</li>");
}
else{
$(".stat").append("<li>"+json.stream.game+"</li>");
}
j++;
})
}
});
$.getJSON() works asynchronously. The JSON won't be returned until the results come back. The API can return in different orders than the requests were made, so you have to handle this.
One way to do this is use the promise API, along with $.when() to bundle up all requests as one big promise, which will succeed or fail as one whole block. This also ensures that the response data is returned to your code in the expected order.
Try this:
var channelIds = ['freecodecamp', 'nightblue3', 'imaqtpie', 'bunnyfufuu', 'mushisgosu', 'tsm_dyrus', 'esl_sc2'];
$(function () {
$.when.apply(
$,
$.map(channelIds, function (channelId) {
return $.getJSON(
'https://api.twitch.tv/kraken/streams/' + encodeURIComponent(channelId)
).then(function (res) {
return {
channelId: channelId,
stream: res.stream
}
});
})
).then(function () {
console.log(arguments);
var $playersBody = $('table.players tbody');
$.each(arguments, function (index, data) {
$playersBody.append(
$('<tr>').append([
$('<td>'),
$('<td>').append(
$('<a>')
.text(data.channelId)
.attr('href', 'https://www.twitch.tv/' + encodeURIComponent(data.channelId))
),
$('<td>').text(data.stream ? data.stream.game : 'Offline')
])
)
})
})
});
https://codepen.io/anon/pen/KrOxwo
Here, I'm using $.when.apply() to use $.when with an array, rather than list of parameters. Next, I'm using $.map() to convert the array of channel IDs into an array of promises for each ID. After that, I have a simple helper function with handles the normal response (res), pulls out the relevant stream data, while attaching the channelId for use later on. (Without this, we would have to go back to the original array to get the ID. You can do this, but in my opinion, that isn't the best practice. I'd much prefer to keep the data with the response so that later refactoring is less likely to break something. This is a matter of preference.)
Next, I have a .then() handler which takes all of the data and loops through them. This data is returned as arguments to the function, so I simply use $.each() to iterate over each argument rather than having to name them out.
I made some changes in how I'm handling the HTML as well. You'll note that I'm using $.text() and $.attr() to set the dynamic values. This ensures that your HTML is valid (as you're not really using HTML for the dynamic bit at all). Otherwise, someone might have the username of <script src="somethingEvil.js"></script> and it'd run on your page. This avoids that problem entirely.
It looks like you're appending the "Display Name" in the same order every time you refresh, by using the j counter variable.
However, you're appending the "Status" as each request returns. Since these HTTP requests are asynchronous, the order in which they are appended to the document will vary each time you reload the page.
If you want the statuses to remain in the same order (matching the order of the Display Names), you'll need to store the response data from each API call as they return, and order it yourself before appending it to the body.
At first, I changed the last else condition (the one that prints out the streamed game) as $(".stat").append("<li>"+jtw[j]+": "+json.stream.game+"</li>"); - it was identical in meaning to what you tried to achieve, yet produced the same error.
There's a discrepancy in the list you've created and the data you receive. They are not directly associated.
It is a preferred way to use $(".stat").append("<li>"+json.stream._links.self+": "+json.stream.game+"</li>");, you may even get the name of the user with regex or substr in the worst case.
As long as you don't run separate loops for uploading the columns "DisplayName" and "Status", you might even be able to separate them, in case you do not desire to write them into the same line, as my example does.
Whatever way you're choosing, in the end, the problem is that the "Status" column's order of uploading is not identical to the one you're doing in "Status Name".
This code will not preserve the order, but will preserve which array entry is being processed
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
$(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
if (json.stream == null) {
$(".stat").append("<li>" + "Offline" + "</li>");
} else {
$(".stat").append("<li>" + json.stream.game + "</li>");
}
})
}(i));
}
});
This code will preserve the order fully - the layout needs tweaking though
$(document).ready(function() {
var ur = "";
var tw = ["freecodecamp", "nightblue3", "imaqtpie", "bunnyfufuu", "mushisgosu", "tsm_dyrus", "esl_sc2"];
for (var i = 0; i < tw.length; i++) {
ur = "https://api.twitch.tv/kraken/streams/" + tw[i];
(function(j) {
var name = $(".name").append("<li> " + tw[j] + "<p>" + "" + "</p></li>");
var stat = $(".stat").append("<li></li>")[0].lastElementChild;
console.log(stat);
$.getJSON(ur, function(json) {
$(".tst").append(JSON.stringify(json));
if (json.stream == null) {
$(stat).text("Offline");
} else {
$(stat).text(json.stream.game);
}
}).then(function(e) {
console.log(e);
}, function(e) {
console.error(e);
});
}(i));
}
});
I have the following code I am using to populate a JSON in one of my controllers:
$rootScope.commands.forEach(function(element, index, array){
$rootScope.voiceEvents[element] = function() {
$scope.$broadcast(element, element + ' from VoiceControlCtrl');
}
});
alert(JSON.stringify($rootScope.voiceEvents));
The problem with the above is that it breaks the app periodically since ,from time to time, $rootScope.commands is undefined, depending on (I assume) how fast some other controllers or views are loaded.
I have been attempting to implement the following fix:
$window.onload = function(commands){
//alert($rootScope.commands);
$rootScope.commands.forEach(function(element, index, array){
$rootScope.voiceEvents[element] = function() {
$scope.$broadcast(element, element + ' from VoiceControlCtrl');
}
});
alert(JSON.stringify($rootScope.voiceEvents));
};
The second version works, except for the fact that $rootScope.voiceEvents comes out empty. Why is that? What can I do to fix it?