I am new to Cucumber, and trying to write some simple tests to get started. One thing I want to test is if an element is not on the page.
In my code I do:
var myBrowser = this.browser;
menu_data.hashes().forEach(function(menuItem, idx, items) {
myBrowser
.isExisting('#' + menuItem.anchor_id, function(err, isExisting) {
if (err) {
throw err;
} else {
isExisting.should.is.isfalse;
}
});
});
Everything I have tried testing isExisting has failed. I tried using assert.isfalse(isExisting), but I get an error saying assert is not there. In fact, when I try to use any methods, like should.assert.toFalse(isExisting) throws an error saying toFalse doesn't exist.
My bad. Have you tried should.be(false)
This is due to view port being too small.
Duplicate of this WebDriver element is returning false for isVisible/waitForForVisible
Related
This question seems to be more or less a duplicate of this one, but that one received no answers and is over 2 years old so I don't know what the protocol is (I tried to find out).
Anyway, I've written an ASP.NET MVC5 web app and it all works fine in debug. After publishing to the server with the Release configuration I started seeing the error "Uncaught ReferenceError: Cannot access 'n' before initialization".
After many hours of trawling through the code I think I've isolated the issue. I have this small function (it's a Knockout view model, but that's irrelevant):
eventIndexViewModel = function (params) {
let self = this;
// Map values from params object to properties of self
for (const [key, value] of Object.entries(params)) {
self['_' + key] = value;
}
self.eventsView = ko.observable(self._eventsView);
self.calendarToggleButtonClass = ko.pureComputed(function () {
return self.eventsView() === "calendar" ? "active" : "";
});
self.tableToggleButtonClass = ko.pureComputed(function () {
return self.eventsView() === "table" ? "active" : "";
});
};
After being minified and published to the server, if I view the source in the dev tools console it looks like this:
eventIndexViewModel = function(n) {
let t = this;
for (const [n,i] of Object.entries(n))
t["_" + n] = i;
t.eventsView = ko.observable(t._eventsView);
t.calendarToggleButtonClass = ko.pureComputed(function() {
return t.eventsView() === "calendar" ? "active" : ""
});
t.tableToggleButtonClass = ko.pureComputed(function() {
return t.eventsView() === "table" ? "active" : ""
})
}
It is overkill to map the properties for the params object in this way in this particular instance, but I have much larger view models with many more properties in the same project and I want to keep them code consistent, so go with it.
Unless I'm misunderstanding something, the minified version has renamed both the params variable and the key variable in the for statement to n, and I think that is what is causing my error. Certainly, that line with the for statement is where the error is thrown.
Am I understanding the cause of this problem correctly? If so, is it a bug in the minification process? And either way, how can I get around it?
I'm trying to get all files from firebase's storage through listAll.
By the way..
storageReference.listAll().addOnSuccessListener { listResult ->
val image_task : FileDownloadTask
for (fileRef in listResult.items) {
fileRef.downloadUrl.addOnSuccessListener { Uri ->
image_list.add(Uri.toString())
println("size1 : " + image_list.size)
}
}
println("size2 : " + image_list.size)
}//addOnSuccessListener
enter image description here
Why is the execution order like this?
How do I solve it??
When you add a listener or callback to something, the code inside the listener will not be called until sometime later. Everything else in the current function will happen first.
You are adding listeners for each item using your for loop. No code in the listeners is running yet. Then your "size2" println call is made after the for loop. At some later time, all your listeners will fire.
If you want asynchronous code like this to be written sequentially, then you need to use coroutines. That's a huge topic, but your code would look something like this (but probably a little more involved than this if you want to properly handle errors). I'm using lifecycleScope from an Android Activity or Fragment for this example. If you're not on Android, you need to use some other CoroutineScope.
The calls to await() are an alternative to adding success and failure listeners. await() suspends the coroutine and then returns a result or throws an exception on failure.
lifecycleScope.launch {
val results = try {
storageReference.listAll().await()
} catch (e: Exception) {
println("Failed to get list: ${e.message}")
return#launch
}
val uris = try {
results.map { it.downloadUrl.await().toString() }
} catch (e: Exception) {
println("Failed to get at least one URI: ${e.message}")
return#launch
}
image_list.addAll(uris)
}
There is nothing wrong with the execution order here.
fileRef.downloadUrl.addOnSuccessListener { Uri ->
the downloadUrl is an asynchronous action which means it doesn't wait for the action to actually complete in order to move along with the code.
You receive the result with the success listener (at least in this case)
If you want to deal with it in a sequential way, look at coroutines.
I am trying to build a test step in Cucumber that will login a test user with a given role. My approach is to try to see is the Sign Out option is there and click it, then to try to login. The problem is that the promise structure doesn't guarantee that the logout happens first, and since I can't assume I am always logged in first, I need a way to basically wait until that happens before moving on.
My test step looks like this:
this.Given(/^I am logged in as a\/an "([^"]*)"$/, function(roleName, callback) {
console.log('Starting login as ', roleName);
var myBrowser = this.browser;
this.browser
.setViewportSize({width: 1000, height: 600})
.url(url.resolve(process.env.HOST, '/'));
this.browser
.waitForVisible('#main_menu').then(function() {
console.log('#main_menu is visible' );
myBrowser.waitForVisible('#appSignOut').then(function() {
console.log('Logging out now');
myBrowser.click('#appSignOut');
});
myBrowser.waitForVisible('#appSignIn').then(function() {
console.log('Logging in as ', roleName);
myBrowser.click('#appSignIn');
myBrowser.waitForVisible('#username').then(function() {
myBrowser
.setValue('#username', 'test' + roleName)
.setValue('#password', 'test' + roleName)
.leftClick('#signin')
.call(callback);
});
});
});
console.log('Done logging in as ', roleName);
});
Is there a way to put a stop after the myBrowser.waitForVisible('#appSignOut')? Am I just missing the intended usage all together?
Update
I have tried a new approach, but still not working:
this.Given(/^I am logged in as a\/an "([^"]*)"$/, function(roleName, callback) {
var myBrowser = this.browser;
this.browser
.setViewportSize({width: 1000, height: 600})
.url(url.resolve(process.env.HOST, '/'));
this.browser
.waitForVisible('#main_menu')
.then(myBrowser.isVisible('#appSignOut').then(function(isVisible) {
if (isVisible) {
myBrowser.click('#appSignOut');
}
}))
.then(myBrowser.waitForVisible('#appSignIn').then(function() {
myBrowser
.click('#appSignIn')
.waitForVisible('#username')
.setValue('#username', 'test' + roleName)
.setValue('#password', 'test' + roleName)
.leftClick('#signin');
}))
.call(callback);;
});
The logic here was:
When the #main_menu is visible (page is loaded)
then - if #appSignOut is present, click on it
then - wait for #appSignIn to become visible, and then complete the login
The error I get is:
[chimp] Detected an unhandledRejection.
[chimp][hooks] Reason:
[chimp][hooks] RuntimeError
[chimp][hooks] no such element
But I don't think any of this is working at all. The popup browser goes too fast for me to see what's happening, and the cucumber.log doesn't really give me any good indication as to what it's doing either.
It's old question, hopefully you found your answer since, but I'll answer it might help others.
When using promises, you have to make sure you use them everywhere (in most cases, or you really want things to be run asynchronously).
In your code it would be:
this.Given(/^I am logged in as a\/an "([^"]*)"$/, function(roleName) {
return this.browser
.setViewportSize({width: 1000, height: 600})
.url(url.resolve(process.env.HOST, '/'))
.waitForVisible('#main_menu')
.isVisible('#appSignOut')
.then(function(isVisible) {
if (isVisible) {
return this.click('#appSignOut');
}
})
.waitForVisible('#appSignIn')
.click('#appSignIn')
.waitForVisible('#username')
.setValue('#username', 'test' + roleName)
.setValue('#password', 'test' + roleName)
.leftClick('#signin');
});
You can notice it is way more readable than before.
The key things I changed:
I chained everything together (webdriverio allows to do that)
When using .Then() this corresponds to this.browser so you can still chain
By returning the result of this.click() from the then() you can continue chaining
CucumberJS can handle promises, for that you need to remove the callback parameter and return a Promise. In this case just return the result of a webdriverio API call
Cheers
I was having problems using ChimpJS to automatically login to my Meteor app using the login form, I was getting "login forbidden", using http://webdriver.io/api/protocol/execute.html to execute Meteor.loginWithPassword() (be careful due to synchronous nature of ChimpJS's version of WebDriverIO don't use the .then()) as suggested by looshi worked for me: http://docs.meteor.com/#/full/meteor_loginwithpassword
On what seems to be random occasions, when a user on my website tries to answer a question on the quiz page, the following error occurs:
TypeError: Cannot read property 'invalidate' of undefined
at Tracker.Dependency.changed (tracker.js:388)
at ReactiveVar.set (reactive-var.js:82)
at null. (builtins.js:22)
at view.js:191
at Function.Template._withTemplateInstanceFunc (template.js:437)
at view.js:190
at Object.Blaze._withCurrentView (view.js:523)
at viewAutorun (view.js:189)
at Tracker.Computation._compute (tracker.js:294)
at Tracker.Computation._recompute (tracker.js:313)
I am unsure as to why this occurs, and I am not entirely sure when this error even means so if someone could point me in the right direction, that would be great!
Note: this error seems to only occur when using Chrome.
I'll hazard a guess, here's the culprit code in Tracker.Dependency
Tracker.Dependency.prototype.changed = function () {
var self = this;
for (var id in self._dependentsById)
// My Comment - we're expecting this to be a `Tracker.Computation` which has an `.invalidate()` method!
self._dependentsById[id].invalidate();
};
So, the problem is that something is modifying Object.prototype.
eg.
Object.prototype.test = function(){}
var emptyObj = {};
for (i in emptyObj)
console.log('has key=', i, 'isOwnProperty?=', emptyObj.hasOwnProperty(i))
will print out:
has key= test isOwnProperty?= false
A possible solution to try, would be adding this snipped to the client, and seeing if it fixes the problem (At least temporarily):
Tracker.Dependency.prototype.changed = function () {
var self = this;
for (var id in self._dependentsById){
if (self._dependentsById.hasOwnProperty(id)){
self._dependentsById[id].invalidate();
}
}
};
Tracker.Dependency.prototype.hasDependents = function () {
var self = this;
for(var id in self._dependentsById)
if (self._dependentsById.hasOwnProperty(id))
return true;
return false;
};
However - It's bad practice to add to Object.prototype, and lots of code will fail in unexpected ways. You'll need to track this down.
Hard to say without more info, but I'd take a look here, specifically the part under "Subscriptions Don't Block":
https://dweldon.silvrback.com/common-mistakes
As the author states, "In meteor, the majority of "Cannot read property of undefined"errors are caused by an incorrect assumption about the existence of subscribed data."
In other words, you may be trying to access the invalidate property before your subscriptions has returned anything.
I've been scratching my head as to why this code will work some of the time, but not all (or at least most of the time). I've found that it actually does run displaying the correct content in the browser some of the time, but strangely there will be days when I'll come back to the same code, run the server (as per normal) and upon loading the page will receive an error in the console: TypeError: 'undefined' is not an object (evaluating 'Session.get('x').html')
(When I receive that error there will be times where the next line in the console will read Error - referring to the err object, and other times when it will read Object - referring the data object!?).
I'm obviously missing something about Session variables in Meteor and must be misusing them? I'm hoping someone with experience can point me in the right direction.
Thanks, in advance for any help!
Here's my dummy code:
/client/del.html
<head>
<title>del</title>
</head>
<body>
{{> hello}}
</body>
<template name="hello">
Hello World!
<div class="helloButton">{{{greeting}}}</div>
</template>
My client-side javascript file is:
/client/del.js
Meteor.call('foo', 300, function(err, data) {
err ? console.log(err) : console.log(data);
Session.set('x', data);
});
Template.hello.events = {
'click div.helloButton' : function(evt) {
if ( Session.get('x').answer.toString() === evt.target.innerHTML ) {
console.log('yay!');
}
}
};
Template.hello.greeting = function() {
return Session.get('x').html;
};
And my server-side javascript is:
/server/svr.js
Meteor.methods({
doubled: function(num) {
return num * 2;
},
foo: function(lmt) {
var count = lmt,
result = {};
for ( var i = 0; i < lmt; i++ ) {
count++;
}
count = Meteor.call('doubled', count);
result.html = "<em>" + count + "</em>";
result.answer = count;
return result;
}
});
I think it's just that the session variable won't be set yet when the client first starts up. So Session.get('x') will return undefined until your method call (foo) returns, which almost certainly won't happen before the template first draws.
However after that it will be in the session, so things will probably behave right once you refresh.
The answer is to just check if it's undefined before trying to access the variable. For example:
Template.hello.greeting = function() {
if (Session.get('x')) return Session.get('x').html;
};
One of the seven principles of Meteor is:
Latency Compensation. On the client, use prefetching and model simulation to make it look like you have a zero-latency connection to the database.
Because there is latency, your client will first attempt to draw the lay-out according to the data it has at the moment your client connects. Then it will do the call and then it will update according to the call. Sometimes the call might be able to respond fast enough to be drawn at the same time.
As now there is a chance for the variable to not be set, it would throw an exception in that occasion and thus break down execution (as the functions in the call stack will not continue to run).
There are two possible solutions to this:
Check that the variable is set when using it.
return Session.get('x') ? Session.get('x').html : '';
Make sure the variable has an initial value by setting it at the top of the script.
Session.set('x', { html = '', answer = ''});
Another approach would be to add the templates once the call responds.
Meteor.call('foo', 300, function(err, data) {
Session.set('x', data);
$('#page').html(Meteor.ui.render(function() {
return Template.someName();
}));
});