NightmareJs + Jquery return empty JSON response - javascript

Im trying to deal with testing tools likewise nightmare, phantom etc. And seems to be stuck with some basic DOM manipulation. Im using jquery here for later use of $().parent() methods. Ive already tryed all posiible selector that come in collection with no use. It only returns some pieces of data. Where it actually fully exists on a page.
....
nightmare
.goto(link)
.inject('js', 'jquery-2.2.4.min.js')
.wait()
.evaluate( () => {
$('.sport--head:not(.folding--open)').click()
})
.wait(4000)
.evaluate( () => {
let events = [];
$('.view-wrapper .events--list > li').each( (i, elem) => {
let event = {
'name' : $(elem).text()
}
events.push(event);
});
let data = JSON.stringify(events, null, '\t');
return data;
})
.end()
....
It returns empty fields where they are actually not:
[{ "name": "" }, { "name": "" }, { "name": "" }, { "name": "contents" } ]
Why could this happen? Any ideas?

You do not know the pages loads completely within 4 seconds.
Either there are some Ajax data and while you are scraping it appears only at that moment. Or, the selector is actually wrong.
Instead of a specific number, use .wait to wait for a target selector,
.wait(4000)
.wait('.view-wrapper .events--list > li')
Also make sure the code actually returns the data by putting it on console.log of your actual browser.

Related

How .elements() works in nightwatch?

Considering I have multiple trs I want to pick one and edit value in it by clicking edit button bounded to it.
I'm doing something like this:
browser.elements('css selector', '#someId tr', elements => {
elements.value.forEach(val => {
console.log(val)
}
And I get something like this:
{
'abcd-1234-qwer-0987': 'some id'
},
{
'abcd-1234-qwer-0987': 'some other id'
}
What is exactly 'abcd-1234-qwer-0987', is this session id kind of stuff and does it change?
What is the best way to grab particular element? Because as I guess my approach is wrong: elements.value[1]['abcd-1234-qwer-0987']
When I run browser.elements and log the entries in the elements.value array, I get something like { ELEMENT: '5' }. So, to answer your questions:
What is exactly 'abcd-1234-qwer-0987', is this session id kind of stuff and does it change?
Probably some sort of session ID, but I'm not sure. Whatever it is, you don't need it for what you're doing.
What is the best way to grab particular element?
If I'm trying to select one out of several similar elements, my code ends up looking something like this:
browser.elements('css selector', '#someId', result => {
// index is defined somewhere else
browser.elementIdClick(result.value[index].ELEMENT, () => {
// whatever you want to do after you click the element.
});
});
If this doesn't work for you, can you share what OS and browser you are running on, as well as your nightwatch configuration file?
Its return the elements items are Web Element json objects. that a Web Element ID
https://nightwatchjs.org/api/commands/#elements
1.What is exactly 'abcd-1234-qwer-0987', is this session id kind of stuff and does it change?
yes it will change according to Session
2.what is the best way to grab particular element
refer the below
Using Nightwatch, how do you get the DOM element, and it's attributes from the ELEMENT entity?
browser.elements('css selector', '#someId tr', elements => {
console.log(elements);
elements.value.forEach(element =>{
let key = Object.keys(element)[0];
let ElementIDvalue = element[key];
console.log("key = ", key);
console.log("WebElementIDvalue = ", ElementIDvalue);
})
});
All of the documentation I've found around what you're trying to do says I should be able to use obj.ELEMENT but what they don't say is that it requires setting w3c to false in your nightwatch config.
Example with context:
test_settings: {
default: {
end_session_on_fail: false,
disable_error_log: false,
launch_url: 'http://localhost:3000',
desiredCapabilities : {
browserName : 'chrome',
'goog:chromeOptions' : {
// More info on Chromedriver: https://sites.google.com/a/chromium.org/chromedriver/
//
// w3c:false tells Chromedriver to run using the legacy JSONWire protocol (not required in Chrome 78)
w3c: true,
args: [
'--auto-open-devtools-for-tabs',
//'--no-sandbox',
. . .
Here is an example of how I use it:
.elements('css selector', `${targetNodeSelector} #nodeCard`, nodeContents => {
console.log(nodeContents.value)
nodeContents.value.forEach(nodeContent => {
console.log(nodeContent.ELEMENT)
// browser.elementIdText(nodeContent.ELEMENT, function (result) {
// console.log('\n' + result.value)
// })
})
})
This will display the correct keys/values:
[
{ ELEMENT: '0.944080972569816-108' },
{ ELEMENT: '0.944080972569816-109' },
{ ELEMENT: '0.944080972569816-110' },

Customxmlnode: is there a way to get a single node using office js?

I noticed that with VBO you can call the method getSingleNode to get a specific node object, is it possible to do something similar with office js?
Also, I have a radio button value in my Word metadata, I managed to access its customxmlnode object, then I used setTextsync method to change its value from true to false, but the new value I get for my radio button metadata is empty. Other text type metadata could be edited correctly though.
Could anyone give some suggestions?
function EditCTF() {
//$("#fields").css({ display: "" });
Word.run(function(context) {
context.document.properties.title = $("#Title").val();
Office.context.document.customXmlParts.getByNamespaceAsync(
"http://schemas.microsoft.com/office/2006/metadata/properties",
function(asyncResult) {
if (asyncResult.value.length > 0) {
var xmlPart = asyncResult.value[0];
xmlPart.getNodesAsync("*/*", function(nodeResults) {
console.log(nodeResults.value.length);
for (i = 0; i < nodeResults.value.length; i++) {
var node = nodeResults.value[i];
node.getTextAsync({ asyncContext: "StateNormal" }, function(result) {
console.log(result);
console.log(result.value);
});
console.log("NewValue");
if (node.baseName == "Address") {
node.setTextAsync(
$("#Address").val(),
{
asyncContext: "StateNormal"
},
function(newresult) {}
);
}
if (node.baseName == "MainContactPerson") {
node.setTextAsync(
$("#Main Contact Person").val(),
{
asyncContext: "StateNormal"
},
function(newresult) {}
);
}
if (node.baseName == "GDPR") {
node.setTextAsync(
"true",
{
asyncContext: "StateNormal"
},
function(newresult) {
console.log(newresult);
console.log(newresult.value);
}
);
}
}
});
}
}
);
return context.sync().then(function() {});
});
}
The correct XPATH format is not an intuitive format, and it does not help that the Microsoft API documentation provide dumb examples that use wildcard (/) xpaths, like so:
xmlPart.getNodesAsync('*', function (nodeResults)
OfficeJS (or perhaps, SharePoint) injects namespace aliases into the mix (ie /ns3:...), so you were on the right track, but to get the 'CGI_Address' SharePoint document property, you need this XPATH syntax:
var xpath = "/ns0:properties[1]/documentManagement[1]/ns3:CGI_Address[1]";
xmlPart.getNodesAsync(xpath, ...);
GOTCHA: When making significant schema changes to the SharePoint content types that the Word document is based on, it may suddenly change the namespace alias from "ns3" to "ns4" or indeed, from "ns4" back to "ns3" like what happened to me today - go figure?!?
And it seems that the OfficeJS API does not properly implement XPATH, as trying to wildcard the namespace alias (so it can accept /ns3 or /ns4 etc) with /*:CGI_Address1 doesn't work.
SO Reference (as to why it should work) - how to ignore namespaces with XPath
Please use the XPATH expression you send in the xmlPart.getNodesAsync("/", function(nodeResults) method. The first parameter is an XPath expression you can use to get the single node you need.

Replace a JSON document with another in MongoDB & Mongoose without knowing the properties of the JSON document beforehand

I've been at this problem for a few days now so I'm asking here now. I'm writing a poll app and the user can link a poll to their account if they're logged in. They can then edit the poll afterwards and how I've done it is copy the create a poll form but filled in the data already put in. The JSON document is made just fine, but the problem I am running into is attempting to replace the original document with the new one. I've made several solutions that don't work, but here's the closest one. In my Polls Mongoose Schema file, I have this router function:
var polls = db.collection('polls');
module.exports.replace = function(newPoll, callback){
polls.replaceOne(
{ $text: { $search: newPoll.question} }, {newPoll}
);
}
Unfortunately, this is the result:
{
"_id": 1,
"newPoll": {
[Contents of newPoll]
}
}
I want the contents of the newPoll to be "one step up" if that makes sense, but I couldn't figure that out. So I tried removing the old document's contents and looping through the JSON document and adding it to the old poll one by one, but the data wouldn't save.
for(var key in newPoll){
var value = newPoll[key];
polls.update(
{ $text: { $search: newPoll.question} }, {'$set' : { [key]: [value]}}, {strict: false}
);
}
I'm still new to this, so I'm sorry if the solution is obvious but I've tried and my Google-fu is yielding no results that have worked.
In this code:
var polls = db.collection('polls');
module.exports.replace = function(newPoll, callback){
polls.replaceOne(
{ $text: { $search: newPoll.question} }, {newPoll}
);
}
{newPoll} is equivalent to {newPoll: newPoll}. That is the reason for the structure of inserted document. Assuming newPoll is an object you should do it like this:
var polls = db.collection('polls');
module.exports.replace = function(newPoll, callback){
polls.replaceOne(
{ $text: { $search: newPoll.question} }, newPoll
);
}

With RESTful API, how I don't make error in the client side if specific response json level is undefined

here is a simple response json:
{
"blog": {
"title": "Blog"
"paragraphs": [{
"title": "paragraph1",
"content": "content1"
}, {
"title": "paragraph2",
"content": "content2"
}, {
"title": "paragraph3",
"content": "content3"
}
],
"selectList": ["food", "play", "mood"]
}
}
If something wrong with server, paragraphs property is undefined, how I avoid to make error in the client side? Even I still could get other datas successfully, like blog.title, blog.selectList...etc
if I don't want to use :
if (response.blog && response.blog.paragraphs && response.blog.paragraphs....) {
//do something
}
Because if there are many level in JSON object, it is not efficient way. Is anyone have smart and efficient way?
UPDATE
#MatthewHerbst said:just check the lowest property.
if I just check the lowest property(response.blog.paragraphs), that will make javascript error, crush my site. because the browser found response.blog is undefined. In the real case, if response.blog is undefined, I will get response.error property, then if(response.blog.paragraphs) will encounter error, because response object doesn't defined blog property in javascript. how I just check the lowest property?
You could use lodash for this. E.g.
let paragraphs = _.get(response, 'blog.paragraphs', null);
if (!paragraphs) {
// Couldn't get from response.
} else {
// Do something with response.blog.paragraphs.
}
You can do something like this-
var blogs = response.blogs;
if(blogs){
//do something
var paraData = blogs.paragraphs;
if (paraData){
// do some more fun
}
}
OR
You can do something like this-
var arrKeys = Object.keys(blogs),
newBlogArr = [] // to get the filtered result;
(function(){
for(i=0;i<arrKeys.length;i++){
// to get the filtered defined objects only
if(blogs[arrKeys[i]] !== null || blogs[arrKeys[i]] !== undefined){
//Now simply push that array in newBlogArr;
newBlogArr.push(blogs[arrKeys[i]]);
}
}
return newBlogArr;
})();

Meteor method reactivity doesn't work

I'm not experienced in Javascript but I've read a ton of articles about Meteor reactivity but still can't figure out why it is not working in my case.
When a new product is added, I want to be recalculated total cost and use it in the totalCost helper so it's almost real time visible in the browser.
Can someone please take a look at my code and try to figure out some logic error? Everything except the reactivity is working on my computer.
I have got this method in /models/Product.js :
Meteor.methods({
totalProductCost: function() {
var pipeline = [
{$match: {owner: Meteor.userId()}},
{$group: {_id: null, cost: {$sum: "$cost"}}}
];
var data = Products.aggregate(pipeline)["0"].cost;
return (data === undefined) ? 0 : data;
}
});
Then I've got layout.js in client folder:
if (Meteor.isClient) {
var handle = Meteor.subscribe("Products", Meteor.userId());
ProductManager = {
_productItems: null,
_dep: new Tracker.Dependency(),
getProducts: function () {
this._dep.depend();
return this._productItems;
},
setProducts: function (value) {
if (value !== this._productItems) {
this._productItems = value;
this._dep.changed();
}
},
getTotalCost: function () {
return ReactiveMethod.call('totalProductCost');
}
}
// TRACKER
Tracker.autorun(function () {
if (handle.ready()) {
ProductManager.setProducts(Products.find().fetch());
}
});
// HELPERS
Template.boxOverview.helpers({
"totalCost" : function () {
return ProductManager.getTotalCost();
},
});
}
It seems that you used a collection.aggregate in a method. If you need reactivity, you need to use a publication rather than a method (or you need to call the method each time you want to refresh). However, if you use your aggregation inside your publication (I assume you use a package for it) you will loose reactivity as well.
What I would advise you is to use a publication without aggregate function. You calculate your product cost by creating a new field and adding it to your cursor. Once, you do that, if you want to keep reactivity, it is necessary to use to use cursor.observeChanges() or just cursor.observe().
Have a look at this example:
var self = this;
// Modify the document we are sending to the client.
function filter(doc) {
var length = doc.item.length;
// White list the fields you want to publish.
var docToPublish = _.pick(doc, [
'someOtherField'
]);
// Add your custom fields.
docToPublish.itemLength = length;
return docToPublish;
}
var handle = myCollection.find({}, {fields: {item:1, someOtherField:1}})
// Use observe since it gives us the the old and new document when something is changing.
// If this becomes a performance issue then consider using observeChanges,
// but its usually a lot simpler to use observe in cases like this.
.observe({
added: function(doc) {
self.added("myCollection", doc._id, filter(doc));
},
changed: function(newDocument, oldDocument)
// When the item count is changing, send update to client.
if (newDocument.item.length !== oldDocument.item.length)
self.changed("myCollection", newDocument._id, filter(newDocument));
},
removed: function(doc) {
self.removed("myCollection", doc._id);
});
self.ready();
self.onStop(function () {
handle.stop();
});
This is taken from here.

Categories

Resources