I'm trying to use nightwatch.js to 1. login to my app, and 2. test a page there. I've setup my login steps as a page, which works well, but after they execute, the steps to navigate to my page and test it are never executed. If I try to execute the page first, it can be navigated to, but I need the login to test it. Is it just not possible to go from page to page in nightwatch?
Login:
const loginCommands = {
login() {
return this
.waitForElementVisible('#emailInput')
.setValue('#emailInput', config.email)
.setValue('#passInput', config.password)
.waitForElementVisible('#loginButton')
.click('#loginButton');
},
};
module.exports = {
url: loginUrl,
commands: [loginCommands],
elements: {
emailInput: {
selector: 'input[name=email]',
},
passInput: {
selector: 'input[name=password]',
},
loginButton: {
selector: 'button[type=submit]',
},
},
};
Page:
const pageCommands = {
navigateToTab() {
return this
.waitForElementVisible('#tabOne')
.click('#tabOne')
},
};
module.exports = {
url: pageUrl,
commands: [pageCommands],
elements: {
tabOne: {
selector: 'a[id="tab-one]',
}
},
};
Test:
'test page': function (browser) {
const loginPage = browser.page.login();
loginPage
.navigate()
.login();
const callPage = browser.page.callPage();
callPage
.navigate()
.navigateToTab();
browser.end();
}
I had the same struggle, but now i think i've found the solution.
This one will change a bit the way of writing your tests but will do the job, please take a look at the next repository:
Nightwatch POM done right :)
Related
I am trying to create a custom controller to update the user profile.
I created the routing file and the corresponding controller.
Routing file: server/src/api/profile/routes/profile.js
module.exports = {
routes: [
{
method: 'GET',
path: '/profile',
handler: 'profile.getProfile',
},
{
method: 'PUT',
path: '/profile',
handler: 'profile.updateProfile',
},
]
}
Controller: src/api/profile/controllers/profile.js
async updateProfile(ctx) {
try {
const { id } = ctx.state?.user;
const user = strapi.query('admin::user').update({
where: { id },
data: {
username: "testUsername"
}
})
ctx.body = "User updated"
} catch(error) {
ctx.badRequest("Something went wrong", { error })
}
},
The above code returns "User updated", but the username does not update. I am executing the PUT call with a correct Bearer authorisation token and the user permissions for that user are set to enable "updateProfile".
Oddly enough, the same code, when changed to update a different API item, works perfectly fine:
async updateArticle(ctx) {
try {
const { id } = ctx.state?.user;
const article = strapi.query('api::article.article').update({
where: { author: id },
data: {
title: "New title"
}
})
ctx.body = article
} catch(error) {
ctx.badRequest("Something went wrong", { error })
}
},
I am also confused by different syntaxes appearing in the official Strapi documentation, for example some docs mention:
strapi.query('admin::user').update({ id }, data)
But in other places in the documentation its:
strapi.plugins['users-permissions'].services.user.update({ id });
And then elsewhere:
strapi.query('user', 'users-permissions').update(params, values);
Another question is: do I need to sanitise the input / output in any way? If yes, how? Importing sanitizeEntity from "Strapi-utils" doesn't work, but it's mentioned in several places on the internet.
Additionally, I cannot find a list of all ctx properties. Where can I read what is the difference between ctx.body and ctx.send?
The lack of good documentation is really hindering my development. Any help with this will be greatly appreciated.
I'm writing a Vue app that uses vue-apollo to interact with graphql. I'm wondering if it's possible to stub the graphql requests. I thought this should work:
it('should access a story', function() {
cy.server();
cy.route('http://localhost:3002/graphql', {
data: {
Story: { id: 2, title: 'story title', content: 'story content' }
}
});
cy.visit('/stories/2');
});
Unfortunately, I get an error from graphql complaining that id is an Int instead of an ObjectId. Am I missing something?
The problem was that stubbing fetch requests isn't yet implemented in Cypress (which is what Vue Apollo is using). I ended up following these instructions:
Install github/fetch
Add this to cypress/support/index.js:
.
Cypress.on('window:before:load', win => {
win.fetch = null;
win.Blob = null;
});
Now it works!
I got it working with this package here:
npm i #iam4x/cypress-graphql-mock
Add this line to 'support/commands.js'
import "#iam4x/cypress-graphql-mock";
go to your graphiql playground and download your schema
add task command to 'plugins/index.js' (REMEMBER TO CHANGE PATH TO SCHEMA FILE YOU DOWNLOADED EARLIER)
module.exports = (on, config) => {
on("task", {
getSchema() {
return fs.readFileSync(
path.resolve(__dirname, "../../../schema.graphql"),
"utf8"
);
}
});
};
write your tests with loaded schema
beforeEach(() => {
cy.server();
cy.task("getSchema").then(schema => {
cy.mockGraphql({
schema
});
});
});`
describe("Login Form", () => {
it("should redirect after login", () => {
cy.mockGraphqlOps({
operations: {
Login: {
login: {
jwt: "some-token",
user: {
id: "5d5a8e1e635a8b6694dd7cb0"
}
}
}
}
});
cy.visit("/login");
cy.getTestEl("email-input").type("Max Mustermann");
cy.getTestEl("password-input").type("passwort");
cy.getTestEl("submit").click();
cy.getTestEl("toolbar-title").should("exist");
});
})
Visit the original repo for further explanation as i find it less confusing. The package you have installed is just a working fork of this one:
https://github.com/tgriesser/cypress-graphql-mock
I am stuck while writing test cases for alloy framework as i am not getting how to use controllers and alloy files in mocha testing framework. I searched on google and few links suggested below code to moke a controller but it throws error that "TypeError: alloy.createController is not a function".
var alloy = require('../../alloy');
it('Verify row controller', function() {
console.log(JSON.stringify(alloy))
var controller = alloy.createController('login', {
name : "uniqueName",
});
// if(controller.passwordTest.value !== "uniqueName"){
// throw new ("Verify row controller FAILED");
// }
});
Currently, I can show you a (slightly modified) example of our codebase.
First of all, our controller tests are pure javascript tests. All calls to the Ti api are executed against a mock. We solely focus on the controller under test and all dependencies are mocked.
We rely on jasmine and jasmine-npm for that.
install jasmine-npm; see https://github.com/jasmine/jasmine-npm
create 'spec' folder in the root of your project (folder with tiapp.xml)
place all your test files inside this folder
the filenames of test files must end with _spec.js
run the jasmine command from within the root folder of your project
describe('authenticate controller test', function() {
var USER_NAME = "John Doe";
var fooControllerMock = {
getView: function(){}
};
var fooViewMock = {
open: function(){}
}
Ti = {
// create here a mock for all Ti* functions and properties you invoke in your controller
}
Alloy = {
CFG: {
timeout: 100
},
Globals: {
loading: {
hide: function(){}
},
networkClient: {
hasAutoLogin: function(){}
},
notifications: {
showError: function(){}
}
},
createController: function(){}
};
var controllerUnderTest; // class under test
$ = {
btnAuthenticate: {
addEventListener: function(){}
},
authenticate: {
addEventListener: function(){},
close: function(){}
},
username: {
addEventListener: function(){},
getValue: function(){}
},
password: {
addEventListener: function(){}
},
windowContainer: {
addEventListener: function(){}
}
};
L = function(s){
return s;
};
beforeEach(function () {
controllerUnderTest = require('../app/controllers/auth');
});
it('should create foo controller when authentication was succesful', function(){
spyOn(Alloy.Globals.loading, 'hide');
spyOn(Alloy, 'createController').and.returnValue(fooControllerMock);
spyOn(fooControllerMock, 'getView').and.returnValue(fooViewMock);
spyOn($.username, 'getValue').and.returnValue(USER_NAME);
spyOn($.auth, 'close');
controllerUnderTest.test._onAuthSuccess();
expect(Alloy.Globals.loading.hide).toHaveBeenCalled();
expect(Alloy.createController).toHaveBeenCalledWith('foo');
expect(fooControllerMock.getView).toHaveBeenCalled();
expect($.auth.close).toHaveBeenCalled();
});
it('should show error message when a login error has occured', function(){
spyOn(Alloy.Globals.loading, 'hide');
spyOn(Alloy.Globals.notifications, 'showError');
controllerUnderTest.test._onAuthLoginError();
expect(Alloy.Globals.loading.hide).toHaveBeenCalled();
expect(Alloy.Globals.notifications.showError).toHaveBeenCalledWith('msg.auth.failure');
});
});
Be sure to write a mock implementation(just empty) for all Ti* stuff you call from within your controller. I know this is quite cumbersome but a solution is on it's way.
Note that we already created an npm package which has a generated mock for this (based on api.jsca), together with some code with which you can mock all required dependencies, together with a set of testing best practices. However we will validate this code internally before we opensource it. I hope we can write our blog post and expose our accompanying github repo within a few weeks. Just keep an eye on tiSlack.
Controller code:
function _onAuthSuccess() {
Alloy.Globals.loading.hide();
Alloy.createController('foo').getView().open();
$.authenticate.close();
}
function _onAuthLoginError() {
Alloy.Globals.loading.hide();
Alloy.Globals.notifications.showError(L('msg.auth.failure'));
}
function _onAuthTokenValidationFailure() {
Alloy.Globals.loading.hide();
}
function _authenticate() {
var username = $.username.value;
var password = $.password.value;
if(Alloy.Globals.validationEmail.isValidEmailAddress(username)){
Alloy.Globals.loading.show(L('authenticate.msg.logging.in'), false);
} else {
Alloy.Globals.notifications.showError(L('app.error.invalid.email'));
}
}
function _onNetworkAbsent() {
Alloy.Globals.loading.hide();
Alloy.Globals.notifications.showError(L('global.no.network.connection.available'));
}
function _hideKeyboard() {
$.username.blur();
$.password.blur();
}
function _focusPassword() {
$.username.blur();
$.password.focus();
}
function _init() {
Ti.App.addEventListener('auth:success', _onAuthSuccess);
Ti.App.addEventListener('auth:loginFailed', _onAuthLoginError);
Ti.App.addEventListener('app:parseError', _onAppParseError);
Ti.App.addEventListener('network:none', _onNetworkAbsent);
$.btnAuthenticate.addEventListener('click', ..);
$.authenticate.addEventListener('close', _cleanup);
$.username.addEventListener('return', _focusPassword);
$.password.addEventListener('return', _authenticate);
$.windowContainer.addEventListener('touchstart', _hideKeyboard);
}
_init();
function _cleanup() {
Ti.API.info('Closing and destroying the auth controller');
...
$.windowContainer.removeEventListener('touchstart', _hideKeyboard);
$.destroy();
$.off();
}
module.exports = {
test: {
_onAuthSuccess: _onAuthSuccess,
_onAuthLoginError: _onAuthLoginError
}
}
and the corresponding view:
<Alloy>
<Window>
<View id="windowContainer">
<TextField id="username" />
<TextField id="password" >
<Button id="btnAuthenticate" />
</View>
</Window>
</Alloy>
Titanium internally uses Ti-Mocha to write unit-tests for all of it's components. It's a modified version of Mocha and uses test-suites, test-cases, chaining and much more to test suitable code-coverage. Give it a try!
Having a simple custom command like this (file pressTab.js):
exports.command = function() {
this.keys(this.Keys.TAB);
return this;
};
I am defining a section in a page and try to call this command from the section:
module.exports = {
url: "...",
commands: [{
testCommandInSection: function(){
this.section.testSection.callPressTab();
return this;
}
}],
sections: {
testSection: {
selector: ".mySectionCssSelector",
commands: [{
callPressTab: function() {
this.pressTab();
return this;
}
}]
}
}
}
If I now use
myPage.testCommandInSection();
an error is thrown before starting the nightwatch queue:
Error while running testCommandInSection command: Cannot read property 'toString' of undefined
But this error does not show up, if I add a dummy parameter to the pressTab call:
callPressTab: function() {
this.pressTab("dummy");
return this;
}
and this doesn't happen, if I call this.pressTab() directly from the page, but not from the section. Why is that?
Problem with "this" object :
In custom commands, "this" usually is browser
In pageobject, it depends .
*In your case, your firstthis.section.testSection.callPressTab(); is your page object, and your second one this.pressTab(); is your section object.
If you want to call custom commands with Browser object, you should try "this.api.YourCustomCommand"
testSection: {
selector: ".mySectionCssSelector",
commands: [{
callPressTab: function() {
this.api.pressTab();
return this;
}
}]
}
I was following Lazy Blogger for getting started with routing in knockoutJS using crossroads and hasher and it worked correctly.
Now I needed to refresh the content using ajax for Home and Settings page every time they are clicked. So I googled but could not find some useful resources. Only these two links
Stack Overflow Here I could not understand where to place the ignoreState property and tried these. But could not make it work.
define(["jquery", "knockout", "crossroads", "hasher"], function ($, ko, crossroads, hasher) {
return new Router({
routes:
[
{ url: '', params: { page: 'product' } },
{ url: 'log', params: { page: 'log' } }
]
});
function Router(config) {
var currentRoute = this.currentRoute = ko.observable({});
ko.utils.arrayForEach(config.routes, function (route) {
crossroads.addRoute(route.url, function (requestParams) {
currentRoute(ko.utils.extend(requestParams, route.params));
});
});
activateCrossroads();
}
function activateCrossroads() {
function parseHash(newHash, oldHash) {
//crossroads.ignoreState = true; First try
crossroads.parse(newHash);
}
crossroads.normalizeFn = crossroads.NORM_AS_OBJECT;
hasher.initialized.add(parseHash);
hasher.changed.add(parseHash);
hasher.init();
$('a').on('click', function (e) {
crossroads.ignoreState = true; //Second try
});
}
});
Crossroads Official Page Here too I could not find where this property need to be set.
If you know then please point me to some url where I can get more details about this.