I'm using the library provided here successfully in several tests, but am now stuck trying to use asserters as per their docs as a passed function in the waitForElementByLinkText API method.
Here is where I've defined the Asserter:
function Asserter(_assert){
this.assert = _assert;
}
/**
* asserters.isVisible
*
* #asserter
*/
var isDisplayed = new Asserter(
function(el,cb) {
el.isDisplayed(function(err, displayed) {
if(err) { return cb(err); }
cb(null, displayed);
});
}
);
module.exports = {
Asserter: Asserter,
isDisplayed: isDisplayed
};
Then in my chained script, I am attempting to use it as follows, but the console.log executes before the element is visible:
.get('http://mydomain.com/mypage')
.elementByLinkText('Reset', function(err, el){
browser.next('clickElement', el, noop);
})
.waitForElementByLinkText('This is the link text', isDisplayed, 10000, 100, function(err){
console.log('The page has updated!');
})
I believe my code is using a deprecated version of the chaining syntax which is needed to support legacy code coming out of SeBuilder, but makes it hard to follow the samples which all use the new method.
You don't need to redefine commonly used asserters, please refer to the new example here:
https://github.com/admc/wd/blob/master/examples/deprecated/wait-for-simple.js
If you need more help, please provide some html/js sample, otherwise it's hard to figure out what you are actually trying to achieve.
Related
Background: Been trying for the last 2 day to resolve this myself by looking at various examples from both this website and others and I'm still not getting it. Whenever I try adding callbacks or async/await I'm getting no where. I know this is where my problem is but I can't resolve it myself.
I'm not from a programming background :( Im sure its a quick fix for the average programmer, I am well below that level.
When I console.log(final) within the 'ready' block it works as it should, when I escape that block the output is 'undefined' if console.log(final) -or- Get req/server info, if I use console.log(ready)
const request = require('request');
const ready =
// I know 'request' is deprecated, but given my struggle with async/await (+ callbacks) in general, when I tried switching to axios I found it more confusing.
request({url: 'https://www.website.com', json: true}, function(err, res, returnedData) {
if (err) {
throw err;
}
var filter = returnedData.result.map(entry => entry.instrument_name);
var str = filter.toString();
var addToStr = str.split(",").map(function(a) { return `"trades.` + a + `.raw", `; }).join("");
var neater = addToStr.substr(0, addToStr.length-2);
var final = "[" + neater + "]";
// * * * Below works here but not outside this block* * *
// console.log(final);
});
// console.log(final);
// returns 'final is not defined'
console.log(ready);
// returns server info of GET req endpoint. This is as it is returning before actually returning the data. Not done as async.
module.exports = ready;
Below is an short example of the JSON that is returned by website.com. The actual call has 200+ 'result' objects.
What Im ultimately trying to achieve is
1) return all values of "instrument_name"
2) perform some manipulations (adding 'trades.' to the beginning of each value and '.raw' to the end of each value.
3) place these manipulations into an array.
["trades.BTC-26JUN20-8000-C.raw","trades.BTC-25SEP20-8000-C.raw"]
4) export/send this array to another file.
5) The array will be used as part of another request used in a websocket connection. The array cannot be hardcoded into this new request as the values of the array change daily.
{
"jsonrpc": "2.0",
"result": [
{
"kind": "option",
"is_active": true,
"instrument_name": "26JUN20-8000-C",
"expiration_timestamp": 1593158400000,
"creation_timestamp": 1575305837000,
"contract_size": 1,
},
{
"kind": "option",
"is_active": true,
"instrument_name": "25SEP20-8000-C",
"expiration_timestamp": 1601020800000,
"creation_timestamp": 1569484801000,
"contract_size": 1,
}
],
"usIn": 1591185090022084,
"usOut": 1591185090025382,
"usDiff": 3298,
"testnet": true
}
Looking your code we find two problems related to final and ready variables. The first one is that you're trying to console.log(final) out of its scope.
The second problem is that request doesn't immediately return the result of your API request. The reason is pretty simple, you're doing an asynchronous operation, and the result will only be returned by your callback. Your ready variable is just the reference to your request object.
I'm not sure about what is the context of your code and why you want to module.exports ready variable, but I suppose you want to export the result. If that's the case, I suggest you to return an async function which returns the response data instead of your request variable. This way you can control how to handle your response outside the module.
You can use the integrated fetch api instead of the deprecated request. I changed your code so that your component exports an asynchronous function called fetchData, which you can import somewhere and execute. It will return the result, updated with your logic:
module.exports = {
fetchData: async function fetchData() {
try {
const returnedData = await fetch({
url: "https://www.website.com/",
json: true
});
var ready = returnedData.result.map(entry => entry.instrument_name);
var str = filter.toString();
var addToStr = str
.split(",")
.map(function(a) {
return `"trades.` + a + `.raw", `;
})
.join("");
var neater = addToStr.substr(0, addToStr.length - 2);
return "[" + neater + "]";
} catch (error) {
console.error(error);
}
}
}
I hope this helps, otherwise please share more of your code. Much depends on where you want to display the fetched data. Also, how you take care of the loading and error states.
EDIT:
I can't get responses from this website, because you need an account as well as credentials for the api. Judging your code and your questions:
1) return all values of "instrument_name"
Your map function works:
var filter = returnedData.result.map(entry => entry.instrument_name);
2)perform some manipulations (adding 'trades.' to the beginning of each value and '.raw' to the end of each value.
3) place these manipulations into an array. ["trades.BTC-26JUN20-8000-C.raw","trades.BTC-25SEP20-8000-C.raw"]
This can be done using this function
const manipulatedData = filter.map(val => `trades.${val}.raw`);
You can now use manipulatedData in your next request. Being able to export this variable, depends on the component you use it in. To be honest, it sounds easier to me not to split this logic into two separate components - regarding the websocket -.
I have this code in Javascript, using Sails and MongoDB, but the execution of the code is not as I am expecting. The first part of the code is executed at first but the addition to the "foundwaiters" array is executed only at the end (after the logged out case) which is a problem because the array is not updated when the HTML page shows up.
How can I fix this ? Thank you.
Tutorial.find(...)
.populate('videos')
.exec(function (err, foundTutorials){
_.each(foundTutorials, function(tutorial){
_.each(tutorial.videos, function(video){
User.find().populate('isWaiting').exec(function (err, foundusers){
_.each(foundusers, function(user){
_.each(user.isWaiting, function(myvideo){
if (myvideo.id === video.id ) {
foundwaiters.push(user);
}
});
});
});
});
});
// The logged out case
if (!req.session.userId) {
return res.view('profile', {
// This is for the navigation bar
me: null,
// This is for profile body
username: foundUser.username,
gravatarURL: foundUser.gravatarURL,
frontEnd: {
numOfTutorials: foundTutorials.length,
numOfFollowers: foundUser.followers.length,
numOfFollowing: foundUser.following.length,
/// ###########
numOfWaiters:foundwaiters.length
// ############
},
// This is for the list of tutorials
tutorials: foundTutorials,
// #######
waiters:foundwaiters
});
}
You could wrap your code with promises. Off-topic, but if this is for Sails, maybe you could drop underscore and use ES2015 or later. You might be able to rewrite your find() to dump of the code, BTW.
Heroku recently posted a list of some good tips for postgres. I was most intreged by the Track the Source of Your Queries section. I was curious if this was something that's possible to use with Sequelize. I know that sequelize has hooks, but wasn't sure if hooks could be used to make actual query string adjustments.
I'm curious if it's possible to use a hook or another Sequelize method to append a comment to Sequelize query (without using .raw) to keep track of where the query was called from.
(Appending and prepending to queries would also be helpful for implementing row-level security, specifically set role / reset role)
Edit: Would it be possible to use sequelize.fn() for this?
If you want to just insert a "tag" into the SQL query you could use Sequelize.literal() to pass a literal string to the query generator. Adding this to options.attributes.include will add it, however it will also need an alias so you would have to pass some kind of value as well.
Model.findById(id, {
attributes: {
include: [
[Sequelize.literal('/* your comment */ 1'), 'an_alias'],
],
},
});
This would produce SQL along the lines of
SELECT `model`.`id`, /* your comment */ 1 as `an_alias`
FROM `model` as `model`
WHERE `model`.`id` = ???
I played around with automating this a bit and it probably goes beyond the scope of this answer, but you could modify the Sequelize.Model.prototype before you create a connection using new Sequelize() to tweak the handling of the methods. You would need to do this for all the methods you want to "tag".
// alias findById() so we can call it once we fiddle with the input
Sequelize.Model.prototype.findById_untagged = Sequelize.Model.prototype.findById;
// override the findbyId() method so we can intercept the options.
Sequelize.Model.prototype.findById = function findById(id, options) {
// get the caller somehow (I was having trouble accessing the call stack properly)
const caller = ???;
// you need to make sure it's defined and you aren't overriding settings, etc
options.attributes.include.push([Sequelize.literal('/* your comment */ 1'), 'an_alias']);
// pass it off to the aliased method to continue as normal
return this.findById_untagged(id, options);
}
// create the connection
const connection = new Sequelize(...);
Note: it may not be possible to do this automagically as Sequelize has use strict so the arguments.caller and arguments.callee properties are not accessible.
2nd Note: if you don't care about modifying the Sequelize.Model prototypes you can also abstract your calls to the Sequelize methods and tweak the options there.
function Wrapper(model) {
return {
findById(id, options) {
// do your stuff
return model.findById(id, options);
},
};
}
Wrapper(Model).findById(id, options);
3rd Note: You can also submit a pull request to add this functionality to Sequelize under a new option value, like options.comment, which is added at the end of the query.
This overrides the sequelize.query() method that's internally used by Sequelize for all queries to add a comment showing the location of the query in the code. It also adds the stack trace to errors thrown.
const excludeLineTexts = ['node_modules', 'internal/process', ' anonymous ', 'runMicrotasks', 'Promise.'];
// overwrite the query() method that Sequelize uses internally for all queries so the error shows where in the code the query is from
sequelize.query = function () {
let stack;
const getStack = () => {
if (!stack) {
const o = {};
Error.captureStackTrace(o, sequelize.query);
stack = o.stack;
}
return stack;
};
const lines = getStack().split(/\n/g).slice(1);
const line = lines.find((l) => !excludeLineTexts.some((t) => l.includes(t)));
if (line) {
const methodAndPath = line.replace(/(\s+at (async )?|[^a-z0-9.:/\\\-_ ]|:\d+\)?$)/gi, '');
if (methodAndPath) {
const comment = `/* ${methodAndPath} */`;
if (arguments[0]?.query) {
arguments[0].query = `${comment} ${arguments[0].query}`;
} else {
arguments[0] = `${comment} ${arguments[0]}`;
}
}
}
return Sequelize.prototype.query.apply(this, arguments).catch((err) => {
err.fullStack = getStack();
throw err;
});
};
I'm trying to use the Steam Community (steamcommunity) npm package along with meteorhacks:npm Meteor package to retreive a user's inventory. My code is as follows:
lib/methods.js:
Meteor.methods({
getSteamInventory: function(steamId) {
// Check arguments for validity
check(steamId, String);
// Require Steam Community module
var SteamCommunity = Meteor.npmRequire('steamcommunity');
var community = new SteamCommunity();
// Get the inventory (730 = CSGO App ID, 2 = Valve Inventory Context)
var inventory = Async.runSync(function(done) {
community.getUserInventory(steamId, 730, 2, true, function(error, inventory, currency) {
done(error, inventory);
});
});
if (inventory.error) {
throw new Meteor.Error('steam-error', inventory.error);
} else {
return inventory.results;
}
}
});
client/views/inventory.js:
Template.Trade.helpers({
inventory: function() {
if (Meteor.user() && !Meteor.loggingIn()) {
var inventory;
Meteor.call('getSteamInventory', Meteor.user().services.steam.id, function(error, result) {
if (!error) {
inventory = result;
}
});
return inventory;
}
}
});
When trying to access the results of the call, nothing is displayed on the client or through the console.
I can add console.log(inventory) inside the callback of the community.getUserInventory function and receive the results on the server.
Relevant docs:
https://github.com/meteorhacks/npm
https://github.com/DoctorMcKay/node-steamcommunity/wiki/CSteamUser#getinventoryappid-contextid-tradableonly-callback
You have to use a reactive data source inside your inventory helper. Otherwise, Meteor doesn't know when to rerun it. You could create a ReactiveVar in the template:
Template.Trade.onCreated(function() {
this.inventory = new ReactiveVar;
});
In the helper, you establish a reactive dependency by getting its value:
Template.Trade.helpers({
inventory() {
return Template.instance().inventory.get();
}
});
Setting the value happens in the Meteor.call callback. You shouldn't call the method inside the helper, by the way. See David Weldon's blog post on common mistakes for details (section Overworked Helpers).
Meteor.call('getSteamInventory', …, function(error, result) {
if (! error) {
// Set the `template` variable in the closure of this handler function.
template.inventory.set(result);
}
});
I think the issue here is you're calling an async function inside your getSteamInventory Meteor method, and thus it will always try to return the result before you actually have the result from the community.getUserInventory call. Luckily, Meteor has WrapAsync for this case, so your method then simply becomes:
Meteor.methods({
getSteamInventory: function(steamId) {
// Check arguments for validity
check(steamId, String);
var community = new SteamCommunity();
var loadInventorySync = Meteor.wrapAsync(community.getUserInventory, community);
//pass in variables to getUserInventory
return loadInventorySync(steamId,730,2, false);
}
});
Note: I moved the SteamCommunity = Npm.require('SteamCommunity') to a global var, so that I wouldn't have to declare it every method call.
You can then just call this method on the client as you have already done in the way chris has outlined.
I'm working with express-hbs and Async Helpers but I need send options to the helper but async helpers doesn't support this feature ( or I don't know how ).
As you can see ( code below ) I'm trying to load a ADS helper/component but I need send extra information/attrs to render based on orientation a dimensions.
JavaScript
hbs.registerAsyncHelper( 'ads', function(filename, cb, options) {
// options: is undefined
// LOAD ADS FROM REMOVE SERVER.
cb( new hbs.SafeString( ' == ADS == ' ) );
});
HTML
{{{ads'page-x', 'vertical', '256x56' }}
Anybody can help me with this situation?
Thank you!
The short answer, you can currently (v0.7.10) only supply one parameter in express-hbs.
The only workaround I can think of is to use as JSON string as param and then use JSON.parse() to get them out again in your helper function. This works only for static params.
e.g.:
hbs.registerAsyncHelper( 'ads', function(arg, cb) {
var options = JSON.parse(arg);
console.log(options);
// LOAD ADS FROM REMOVE SERVER.
cb( new hbs.SafeString( ' == ADS == ' ) );
});
{{{ads "[\"page-x\",\"vertical\",\"256x56\"]" }}}
Reason for problem with multiple args in express-hbs following:
tl;dr
The express-hbs registerAsyncHelper code:
ExpressHbs.prototype.registerAsyncHelper = function(name, fn) {
this.handlebars.registerHelper(name, function(context) {
return async.resolve(fn.bind(this), context);
});
};
only registers one parameter (context) with handlebars.registerHelper, but handlebars will call the registered helper with each supplied parameter as extra parameter. So the registered function should look like this:
ExpressHbs.prototype.registerAsyncHelper = function(name, fn) {
this.handlebars.registerHelper(name, function(contextArgs) {
return async.resolve(fn.bind(this), Array.prototype.slice.call(contextArgs));
});
};
but that also requires a change of the resolve function. So you have to wait for a fix or fix it yourself, I guess.
Also as mentioned in the comment below your question the correct syntax for multiple arguments in handlebars is to not use a comma. So your template should like this (when handlebars-hbs is fixed):
{{{ads 'page-x' 'vertical' '256x56'}}}