With the promised web driver I would like to check if an element exists on the page, then login if it does, otherwise continue with the promise chain.
Is this not possible at the moment due to https://github.com/theintern/intern/issues/14?
In Intern 2, simply use the normal find command:
var remote = this.remote;
remote.get(url)
.findById('foo')
.then(function (element) {
// exists
}, function () {
// does not exist
});
In Intern 1, if you need to conditionally branch, you’ll need to stop and add new instructions based on the result of your check.
var remote = this.remote;
remote.get(url)
.elementByIdIfExists('foo')
.then(function (element) {
if (element) {
remote.clickElement()
.type('foo');
// ...etc.
}
});
This should work in Intern 1.1 only if you are adding new commands to the remote promise chain when there are no other already-existing commands pending. Intern 1.2 will contain improvements that eliminate this restriction. This is issue #14.
Related
I have a client-side form that can create a document upon submission. I want to see if one of the input fields already exists on a Document in the DB though. This would then alert the user and ask them if they want to continue creating the record.
Client-side event
Template.createDoc.events({
'click button[type=submit]'(e, template) {
//This particular example is checking to see if a Doc with its `name` property set to `value` already exists
const value = $('#name');
const fieldName = 'name';
const exists = Meteor.call('checkIfFieldExistsOnDoc', fieldName, value);
if (exists) {
if (confirm(`Doc with ${value} as its ${fieldName} already exists. Are you sure you want to continue creating Doc?`) {
//db.Docs.insert....
}
}
}
});
Server-side Meteor Method
'checkIfFieldExistsOnDoc'(field, val) {
if (this.isServer) {
this.unblock();
check(field, String);
check(val, String);
if (!this.userId) {
throw new Meteor.Error('not-authorized', 'You are not authorized.');
}
const findObj = {};
findObj[field] = val;
const fieldsObj = {};
fieldsObj[fieldsObj] = 1;
const doc = Docs.findOne(findObj, {fields: fieldsObj});
return doc;
}
},
My issue is that the client-side code always gets undefined back when calling the Server method. I now understand why, however, I'm not keen on wrapping all of my subsequent client-code into a callback yet.
So - any other ideas on how I can attempt to do this simple feature?
Also - I was thinking of having the client-side page's onCreated do a 1-time server call to get ALL names for all Docs, storing this in memory, and then doing the check upon form submission using this. Obviously, this is inefficient and not-scalable, although it would work
Meteor.call in the client side is always an async call. Then you need implement a callback.
See docs: https://docs.meteor.com/api/methods.html#Meteor-call
Meteor.call('checkIfFieldExistsOnDoc', fieldName, value, function(error, result) {
if (result) {
if (confirm(`Doc with ${value} as its ${fieldName} already exists. Are you sure you want to continue creating Doc?`) {
//db.Docs.insert....
}
}
});
On the client, you can wrap any Meteor.call with a Promise and then use it with async/await. There are some packages on Atmosphere that do this for you to.
I've used this package for years: https://atmospherejs.com/deanius/promise
On the client I often just use await Meteor.callPromise() which returns a response nicely.
Here are a couple of the best write-ups on the many options available to you:
https://blog.meteor.com/using-promises-on-the-client-in-meteor-fb4f1c155f84
https://forums.meteor.com/t/meteor-methods-return-values-via-promise-async/42060
https://dev.to/jankapunkt/async-meteor-method-calls-24f9
I was looking through the Wolkenkit API ... and it's not 100% clear how to know which commands require an aggregate ID and which ones do not.
From what I can tell, the client api offers something like this
app.accounting.invoice().issue({
amount: 1000
});
which is fine for creating a new invoice, but it shouldn't be possible to run the following that intends to update something that exists
app.accounting.invoice().update({
amount: 10
});
I assume this check should go into the command function, but how do I write it?
const commands = {
update (invoice, command, mark) {
const canInvoiceBeUpdated = // ...
if (!canInvoiceBeUpdated) {
return mark.asRejected('...');
}
// ... update invoice
mark.asDone();
}
};
What goes into canInvoiceBeUpdated check?
Answered 2018-06-08 by #goloroden in the wolkenkit slack
I'll try to explain it to you: If you want a new aggregate, you omit the ID. So, e.g., to stick with the chat example, when you want to send a new message you do:
app.communication.message().send({ /* ... */ });
If, instead, you want to edit an existing message, e.g. to like it, then you have to provide the ID of the message:
const messageId = '...';
app.communication.message(messageId).like({ /* ... */ });
Within every command you will probably want to check that it only works on a new aggregate (which we call a constructor command) or only on an existing aggregate. The easiest way to check this is to use the aggregate's exists function, which returns true for new aggregates, and false otherwise. So, inside of a command, you could do something like this:
const commands = {
send (message, command, mark) {
if (!message.exists()) {
return mark.asRejected('Aggregate does not exist yet.');
}
// ...
mark.asDone();
}
};
If you don't want to do this manually every time, you could also use a middleware for this, such as https://github.com/thenativeweb/wolkenkit-command-tools … then the previous example comes down to:
const { only } = require('wolkenkit-command-tools');
// ...
const commands = {
send: [
only.ifExists(),
(message, command, mark) {
// ...
mark.asDone();
}
]
};
Please note that the current version of this middleware module is 3.0, but that until wolkenkit 2.0 will be released, you will have to use version 2.0 of wolkenkit-command-tools.
I started working with Galen and I had this test that was working perfectly:
this.HomePage = $page('Welcome Page', {
aboutButton: 'nav.navbar .about'
});
this.AboutPage = $page('About page', {
modalContent: 'div.modal-content',
supportLink: '.support-link',
dismissButton: '.dismiss'
});
var homePage = new HomePage(driver);
homePage.waitForIt();
homePage.aboutButton.click();
var aboutPage = new AboutPage(driver);
aboutPage.waitForIt();
I understand that the waitForIt method waits for all the attributes defined by page so the framework knows when to execute the next statement.
Now, I want to run this as a grunt task and I've been using grunt-galenframework, and I configured it correctly, everything is working, but I can't make the previous test pass, the task code is as follows:
load('../gl.js');
forAll(config.getDevices(), function (device) {
test('simple test on ' + device.deviceName, function () {
gl.openPage(device, config.getProjectPage(), "Welcome Page", {
aboutButton: 'nav.navbar .about'
});
elements.aboutButton.click();
// MISSING WAIT FOR ABOUT_PAGE
gl.runSpecFile(device, './test/responsive/galen/about.gspec');
});
});
As you can see, I get into the Welcome Page and then I need to click a button, wait for a dialog to appear, and then check the about.gspec specs (they verify elements inside the dialog).
So how can I add a wait for new elements to appear on the same URL? it feels like grunt-galenframework only offers wait when entering a new url, with the openPage method.
you could try
elements.newElement.waitToBeShown()
The methods from here should be available.
PS: I'm the author of the grunt plugin for Galen and also involved in the development of the Galen Framework itself
Preparation
Hi i am using CasperJS in combination with grunt-casper (github.com/iamchrismiller/grunt-casper) for running automated functional and regression tests in our GUI Development process for verification.
We use it like this, casper runner in gruntfile.js:
casper: {
componentTests: {
options: {
args: ['--ssl-protocol=any', '--ignore-ssl-errors=true', '--web-security=no'],
test: true,
includes: ['tests/testutils/testutils.js']
},
files: {
'tests/testruns/logfiles/<%= grunt.template.today("yyyy-mm-dd-hhMMss") %>/componenttests/concat-testresults.xml': [
'tests/functionaltests/componenttests/componentTestController.js']
}
},
so as it can be seen here we just normally run casper tests with SSL params and calling only ONE Controllerclass here instead of listing the single tests (this is one of the roots of my problem). grunt-casper delivers the object which is in charge for testing and inside every single Controllerclass the tests are included and concatenated....
...now the componentTestController.js looks like the following:
var config = require('../../../testconfiguration');
var urls = config.test_styleguide_components.urls;
var viewportSizes = config.test_styleguide_components.viewportSizes;
var testfiles = config.test_styleguide_components.testfiles;
var tempCaptureFolder = 'tests/testruns/temprun/';
var testutils = new testutils();
var x = require('casper').selectXPath;
casper.test.begin('COMPONENT TEST CONTROLLER', function(test) {
casper.start();
/* Run tests for all given URLs */
casper.each(urls, function(self, url, i) {
casper.thenOpen(url, function() {
/* Test different viewport resolutions for every URL */
casper.each(viewportSizes, function(self, actViewport, j) {
/* Reset the viewport */
casper.then(function() {
casper.viewport(actViewport[0], actViewport[1]);
});
/* Run the respective tests */
casper.then(function() {
/* Single tests for every resolution and link */
casper.each(testfiles, function(self, actTest, k) {
casper.then(function() {
require('.'+actTest);
});
});
});
});
});
});
casper.run(function() {
test.done();
});
});
Here you can see that we running a 3 level loop for testing
ALL URLs given in a JSON config file which are contained in an ARRAY of String ["url1.com","url2.com"....."urln.com"]
ALL VIEWPORT SIZES so that every URL is tested in our desired Viewport resolutions to test the correct Responsibility behaviour of the components
ALL TESTFILES, all testfiles only include a TEST STUB what means, no start, begin or something else, its all in a large Testsourrounding.
MAYBE this is already mocky and can be done in a bette way, so if this is the case i would glad if someone has proposals here, but don't forget that grunt-casper is involved as runner.
Question
So far, so good, the tool in general works fine and the construction we built works as we desired. But the problem is, because all testfiles are ran in a large single context, one failing component fails the whole suite.
In normal cases this is a behaviour i would support, but in our circumstances i do not see any proper solution than log the error / fail the single testcomponent and run on.
Example:
I run a test, which is setUp like described above and in this part:
/* Single tests for every resolution and link */
casper.each(testfiles, function(self, actTest, k) {
casper.then(function() {
require('.'+actTest);
});
});
we include 2 testfiles looking like the following:
Included testfile1.js
casper.then(function () {
casper.waitForSelector(x("//a[normalize-space(text())='Atoms']"),
function success() {
casper.test.assertExists(x("//a[normalize-space(text())='Atoms']"));
casper.click(x("//a[normalize-space(text())='Atoms']"));
},
function fail() {
casper.test.assertExists(x("//a[normalize-space(text())='Atoms']"));
});
});
Included testfile2.js
casper.then(function () {
casper.waitForSelector(x("//a[normalize-space(text())='Buttons']"),
function success() {
casper.test.assertExists(x("//a[normalize-space(text())='Buttons']"));
casper.click(x("//a[normalize-space(text())='Buttons']"));
},
function fail() {
testutils.createErrorScreenshot('#menu > li.active > ul > li:nth-child(7)', tempCaptureFolder, casper, 'BUTTONGROUPS#2-buttons-menu-does-not-exist.png');
casper.test.assertExists(x("//a[normalize-space(text())='Buttons']"));
});
});
So if the assert in testfile1.js fails, everthing failes. So how can i move on to testfile2.js, even if the first fails? Is this possible to configure? Can it be encapsulated somehow?
FYI, this did not work:
https://groups.google.com/d/msg/casperjs/3jlBIx96Tb8/RRPA9X8v6w4J
Almost similar problems
My problem is almost the same like this here:
https://stackoverflow.com/a/27755205/4353553
And this guy here has almost another approach i tried but got his problems too because multiple testsuites ran in a loop occuring problems:
groups.google.com/forum/#!topic/casperjs/VrtkdGQl3FA
MUCH THANKS IN ADVICE
Hopefully I understood what you ware asking - is there a way to suppress the failed assertion's exception throwing behavior?
The Tester's assert method actually allows for overriding the default behavior of throwing an exception on a failed assertion:
var message = "This test will always fail, but never throw an exception and fail the whole suite.";
test.assert(false, message, { doThrow: false });
Not all assert helpers have this option though and the only other solution I can think of is catching the exception:
var message = "This test will always fail, but never throw an exception and fail the whole suite.";
try {
test.assertEquals(true, false, message);
} catch (e) { /* Ignore thrown exception. */ }
Both of these approaches are far from ideal since the requirement in our cases is to not throw for all assertions.
Another short term solution that requires overriding the Tester instance's core assert method is (but is still quite hacky):
// Override the default assert method. Hopefully the test
// casper property doesn't change between running suites.
casper.test.assert =
casper.test.assertTrue = (function () {
// Save original assert.
var __assert = casper.test.assert;
return function (subject, message, context) {
casper.log('Custom assert called!', 'debug');
try {
return __assert.apply(casper.test, arguments);
}
catch (error) {
casper.test.processAssertionResult(error.result);
return false;
}
};
})();
That said, I'm currently looking for a non-intrusive solution to this "issue" (not being able to set a default value for doThrow) as well. The above is relative to Casper JS 1.1-beta3 which I'm currently using.
I follow a tutorial with Meteor I try to create a collection, both for client and server. Here is my code:
var lists = new Meteor.Collection("Lists");
if (Meteor.isClient) {
}
if (Meteor.isServer) {
Meteor.startup(function () {
// code to run on server at startup
});
}
As tutorial I have read, when run on server, if I open chrome console and type lists I will receive Meteor.Collection. But when I tried on my machine, I received error:
Reference error. lists is not define
Have I done something wrong? Please tell me.
Thanks :)
Also you can put all your collections inside the /lib/collection.js route (for better practices).
So with that we ensure that meteor loads first the collections, and they will be available on both client/server.
you should remove Autopublish/insecure package, to avoid meteor sends all the collections when load and to control who can or not insert/remove/update on the collections.
meteor remove autopublish
meteor remove insecure.
So a simple collection will look like this.
//lib/collection.js
Example = new Mongo.Collection("Example") //we create collection global
if(Meteor.isClient) {
Meteor.subscribe('Example') //we subscribe both after meteor loads client and server folders
}
now on /server/collections.js
Meteor.publish('Example', function(){
return Example.find(); //here you can control whatever you want to send to the client, you can change the return to just return Example.find({}, {fields: {stuff: 1}});
});
// Here we control the security of the collections.
Example.allow({
insert: function(userId, doc) {
if(Meteor.userId()){
return true; //if the user is connected he can insert
} else{
return false// not connected no insert
}
},
update: function(userId, doc, fields, modifier) { //other validation },
remove: function(userId, doc) { //other validation },
});
Just to try to explain a little more deep the Collection here on meteor, hope it help you GL
I think you have autopulish/autosubscribe turned off. Try
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
For your naming, I'd also recommend you reverse the way you're capitalizing your collections. So instead it would be
var Lists = new Meteor.Collection("lists");
And finally, look at https://github.com/matteodem/meteor-boilerplate for your directory structure so you don't have to do the if meteor.is stuff anymore.
Edit
Full code should look like:
var Lists = new Meteor.Collection("lists");
if (Meteor.isClient) {
Meteor.subscribe('lists');
}
if (Meteor.isServer){
Meteor.publish('lists',function(){
return Lists.find();
});
}
All of your script source files are wrapped in a function closure as part of the build process. In order for your collection to be visible outside of that file (or in your case - attached to the window object) you will need to declare it as a global variable:
Lists = new Meteor.Collection('lists');
Note the lack of var. As #thatgibbyguy pointed out, the accepted pattern is to capitalize collection variables, and camelcase collection names.