Using custom commands in sections - nightwatch.js

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;
}
}]
}

Related

Watch Value In Vue.js 3, Equivalent In Pinia?

I have a checkbox list of domain tlds, such as com, net, io, etc. I also have a search text input, where I can drill down the list of 500 or so domains to a smaller amount. For example, if I start to type co in to my search text input, I will get back results that match co, such as co, com, com.au, etc. I am using Laravel and Vue,js 3 to achieve this with a watcher. It works beautifully. How can an achieve the same within a Pinia store?
Here is my code currently:
watch: {
'filters.searchedTlds': function(after, before) {
this.fetchsearchedTlds();
}
},
This is inside my vue component.
Next is the code to fetch searched tlds:
fetchsearchedTlds() {
self = this;
axios.get('/fetch-checked-tlds', { params: { searchedTlds: self.filters.searchedTlds } })
.then(function (response) {
self.filters.tlds = response.data.tlds;
console.log(response.data.tlds);
})
.catch(function (error) {
console.log(error);
})
.then(function () {
// always executed
});
},
And finally, the code inside my Laravel controller:
public function fetchCheckedTlds(Request $request)
{
$data['tlds'] = Tld::where('tld', 'LIKE','%'.$request->input('searchedTlds').'%')->pluck('tld');
return response()->json($data);
}
I am converting my code to use a Pinia store and I am stuck on how to convert my vue component watcher to Pinia?
Many thanks in advance.
To watch a pinia status, you may watch a computed attribute based on pinia or use watch getter
Your pinia may look like the one below.
~/store/filters.js
export const useFilters = defineStore('filters', {
state: () => {
return {
_filters: {},
};
},
getters: {
filters: state => state._filters,
},
...
}
In where you want to watch
<script setup>
import { computed, watch } from 'vue';
import { useFilters } from '~/store/filters.js';
const filters = useFilters();
// watch a computed attributes instead
const searchedTlds = computed(() => {
return filters.filters?.searchedTlds || '';
});
watch(
searchedTlds,
(newValue, oldValue) {
fetchsearchedTlds();
}
);
// or use watch getter
watch(
() => {
return filters.filters?.searchedTlds || '';
},
(newValue, oldValue) {
fetchsearchedTlds();
}
);
</script>
The first parameter of watch() can be a single ref or a getter function, or an array of getter functions, for more details, please view the Watch Source Types.

How do I fix a "browser.elements is not a function" error in Nightwatch.js?

I'm trying to use page objects in Nightwatch js and am creating my own commands in that. For some reason now Nightwatch doesn't seem to recognise standard commands on browser and give me a type error on different commands. What am I doing wrong with my code?
I'm tried different things here already, for example adding 'this' or 'browser' in front of the command, which didn't help. My code has gone through many versions already I am not even sure anymore what all I've tried after Googling the error.
My pageObject:
const homePageCommands = {
deleteAllListItems: function (browser) {
browser
.elements('css selector', '#mytodos img', function (res) {
res.value.forEach(elementObject => {
browser.elementIdClick(elementObject.ELEMENT);
});
})
.api.pause(1000)
return this;
}
};
module.exports = {
url: "http://www.todolistme.net"
},
elements: {
myTodoList: {
selector: '#mytodos'
},
deleteItemButton: {
selector: 'img'
}
},
commands: [homePageCommands]
};
My test:
require('../nightwatch.conf.js');
module.exports = {
'Validate all todo list items can be removed' : function(browser) {
const homePage = browser.page.homePage();
homePage.navigate()
.deleteAllListItems(homePage)
// I have not continued the test yet because of the error
// Should assert that there are no list items left
}
};
Expected behaviour of the custom command is to iterate over the element and click on it.
Actual result:
TypeError: browser.elements is not a function
at Page.deleteAllListItems (/pageObjects/homePage.js:18:14)
at Object.Validate all todo list items can be removed (/specs/addToList.js:8:14)
at processTicksAndRejections (internal/process/next_tick.js:81:5)
And also:
Error while running .navigateTo() protocol action: invalid session id
Looks like you need to pass browser to the deleteAllListItems function instead of homePage on this line:
homePage.navigate()
.deleteAllListItems(homePage)

How to write test cases for titanium with alloy

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!

How to refresh content when using CrossroadJS and HasherJS with KnockoutJS

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.

How to set mandatory route parameters

I want to make a route with has a mandatory parameter. If not, it should fall into
$urlRouterProvider.otherwise("/home");
Current route:
function router($stateProvider) {
$stateProvider.state("settings", {
url: "^/settings/{id:int}",
views: {
main: {
controller: "SettingsController",
templateUrl: "settings.html"
}
}
});
}
Currently both the routes below are valid:
http://myapp/settings //Should be invalid route
http://myapp/settings/123
Any ideas?
Use a state change start listener to check if params were passed:
$rootScope.$on('$stateChangeStart',
function (event, toState, toParams, fromState, fromParams) {
if(toState.name==="settings")
{
event.preventDefault(); //stop state change
if (toParams.id===undefined)
$state.go("home");
else
$state.go(toState, toParams);
}
});
The following solution is valid for ui-router 1.0.0:
.config(($stateProvider, $transitionsProvider) => {
//Define state
$stateProvider.state('verifyEmail', {
parent: 'portal',
url: '/email/verify/:token/:optional',
component: 'verifyEmail',
params: {
token: {
type: 'string',
},
optional: {
value: null,
squash: true,
},
},
});
//Transition hooks
$transitionsProvider.onBefore({
to: 'verifyEmail',
}, transition => {
//Get params
const params = transition.params();
//Must have token param
if (!params.token) {
return transition.router.stateService.target('error', {
type: 'page-not-found',
});
}
});
})
The above will make the :token parameter mandatory and the :optional parameter optional. If you try to browse to the page without the token parameter it will fail the transition and redirect to your error page. If you omit the :optional parameter however, it will use the default value (null).
Remember to use squash: true on the trailing optional parameters, because otherwise you'll also get a 404 if you omit the trailing / in the URL.
Note: the hook is required, because if you browse to email/verify/ with a trailing slash, ui-router will think the token parameter is an empty string. So you need the additional handling in the transition hook to capture those cases.
In my app I had to make required parameters for a lot of routes. So I needed a reusable and DRY way to do it.
I define a constants area in my app to access global code. I use for other things as well.
I run this notFoundHandler at app config time. This is setting up a router state for handling errors. It is setting the otherwise route to this error route. You could define a different route for when a required parameter is missing, but for us this was defined as being the same as a 404 experience.
Now at app run time I also define a stateChangeErrorHandler which will look for a rejected route resolve with the 'required-param' string.
angular.module('app')
.constant('constants', constants)
.config(notFoundHandler)
.run(stateChangeErrorHandler);
// use for a route resolve when a param is required
function requiredParam(paramName) {
return ['$stateParams', '$q', function($stateParams, $q) {
// note this is just a truthy check. if you have a required param that could be 0 or false then additional logic would be necessary here
if (!$stateParams[paramName]) {
// $q.reject will trigger the $stateChangeError
return $q.reject('required-param');
}
}];
}
var constants = {
requiredParam: requiredParam,
// define other constants or globals here that are used by your app
};
// define an error state, and redirect to it if no other route matches
notFoundHandler.$inject = ['$stateProvider', '$urlRouterProvider'];
function notFoundHandler($stateProvider, $urlRouterProvider) {
$stateProvider
//abstract state so that we can hold all our ingredient stuff here
.state('404', {
url: '/page-not-found',
views: {
'': {
templateUrl: "/app/error/error.tpl.html",
}
},
resolve: {
$title: function () { return 'Page Not Found'; }
}
});
// redirect to 404 if no route found
$urlRouterProvider.otherwise('/page-not-found');
}
// if an error happens in changing state go to the 404 page
stateChangeErrorHandler.$inject = ['$rootScope', '$state'];
function stateChangeErrorHandler($rootScope, $state) {
$rootScope.$on('$stateChangeError', function(evt, toState, toParams, fromState, fromParams, error) {
if (error && error === 'required-param') {
// need location: 'replace' here or back button won't work on error page
$state.go('404', null, {
location: 'replace'
});
}
});
}
Now, elsewhere in the app, when I have a route defined, I can make it have a required parameter with this route resolve:
angular.module('app')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider', 'constants'];
function routeConfig($stateProvider, constants) {
$stateProvider.state('app.myobject.edit', {
url: "/:id/edit",
views: {
'': {
template: 'sometemplate.html',
controller: 'SomeController',
controllerAs: 'vm',
}
},
resolve: {
$title: function() { return 'Edit MyObject'; },
// this makes the id param required
requiredParam: constants.requiredParam('id')
}
});
}
I'd like to point out that there shouldn't be any problem with accessing the /settings path, since it doesn't correspond to any state, unless you've used inherited states (see below).
The actual issue should happen when accessing the /settings/ path, because it will assign the empty string ("") to the id parameter.
If you didn't use inherited states
Here's a solution in plunker for the following problem:
accessing the /state_name/ path, when there's a state with url /state_name/:id
Solution explanation
It works through the onBefore hook (UI router 1.x or above) of the Transition service, which prevents transitioning to states with missing required parameters.
In order to declare which parameters are required for a state, I use the data hash like this:
.state('settings', {
url: '/settings/:id',
data: {
requiredParams: ['id']
}
});
Then in app.run I add the onBefore hook:
transitionService.onBefore({}, function(transition) {
var toState = transition.to();
var params = transition.params();
var requiredParams = (toState.data||{}).requiredParams || [];
var $state = transition.router.stateService;
var missingParams = requiredParams.filter(function(paramName) {
return !params[paramName];
});
if (missingParams.length) {
/* returning a target state from a hook
issues a transition redirect to that state */
return $state.target("home", {alert: "Missing params: " + missingParams});
}
});
If you used inherited states
You could implement the same logic via inherited states:
function router($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings'
})
.state('settings.show", {
url: '/:id'
});
}
then you'd need to add the abstract property to the parent declaration, in order to make /settings path inaccessible.
Solution explanation
Here's what the documentation says about the abstract states:
An abstract state can never be directly activated. Use an abstract state to provide inherited properties (url, resolve, data, etc) to children states.
The solution:
function router($stateProvider) {
$stateProvider
.state('settings', {
url: '/settings',
abstract: true
})
.state('settings.show", {
url: '/:id'
});
}
Note: that this only solves the issue with /settings path and you still need to use the onBefore hook solution in order to also limit the access to /settings/.
it is not very well documented, but you can have required and optional parameters, and also parameters with default values.
Here is how you can set required params:
function router($stateProvider) {
$stateProvider.state("settings", {
url: "^/settings/{id:int}",
params: {
id: {}
},
views: {
main: {
controller: "SettingsController",
templateUrl: "settings.html"
}
}
});
}
I never used params with curly brackets, just with the semicolon, like this url: "^/settings/:id", but from what I read, those are equivalent.
For other types of parameters, please see the other half of my answer here: AngularJS UI Router - change url without reloading state
Please note that when I added that answer, I had to build ui-router from source, but I read that functionality has been added to the official release by now.

Resources