I'm trying to configure an element using the 'link text' locateStrategy in a PageObject with Nightwatch - nightwatch.js

I am trying to use a pageObect that has an element that is located via the 'link text' locate strategy. I can recreate the failure on google.com with their 'Store' link.
nightwatch v0.9.21
//google page object
module.exports = {
url: function () {
return 'https://www.google.com';
},
elements: {
storeLinkText: {
selector: 'Store',
locateStrategy: 'link text'
},
storeXPath: {
selector: '//*[#id="hptl"]/a[2]',
locateStrategy: 'xpath'
},
storeDefaultSelector: '#hptl > a:nth-child(2)',
},
};
From the tests:
...
// Fails
return client.page.core.google().navigate().then( () => {
return client.page.core.google().waitForElementPresent('#storeLinkText', 1000);
});
// Passes
return client.page.core.google().navigate().then( () => {
return client.page.core.google().waitForElementPresent('#storeXPath', 1000);
});
//Passes
return client.page.core.google().navigate().then( () => {
return client.page.core.google().waitForElementPresent('#storeDefaultSelector', 1000);
});
The API says they support 'link text' and 'partial link text' as locateStrategy options. https://github.com/nightwatchjs/nightwatch/wiki/Page-Object-API

Related

Skip a after hook in a Cypress test

In my Cypress config I have a hook:
//hooks.ts
after(() => {
if (Cypress.currentTest.titlePath[0] === 'Skip after') {
return;
}
cy.wait(2500);
cy.el('btnLogoutUser').should('exist').click({ force: true });
cy.url().should('contain', 'login');
cy.task('getCompany').then((data: any) => {
cy.task('cleanCompany', data.company.company_id).then((response: any) => {
cy.log(`
=================================================
Removed company with id: ${response.substring(47)}
=================================================
`);
});
});
localStorage.clear();
});
But on some tests I want to skip this hook. That's why I added the if condition.
//test.cy.ts
describe('Skip after', () => {
it('Does something', () => {
cy.el('btnCompanySettings').click({ force: true });
});
});
This works, but I would much rather add a boolean value in the describe and pass that so I can check that value. Something like:
//test.cy.ts
describe({title: 'Name of test', skipAfter: true}), () => { ... }
But the describe only takes a string value.
You can add a custom property to the configuration object in your describe method (that is the one between the suite name and the callback function). Then, you can access it from Cypress.config().
describe("Suite", { skipAfter: false }, () => {
it("test", () => {
//your code
});
after(() => {
if (Cypress.config().skipAfter) {
return;
}
//your code
});
});

Cypress - Adding handelExpection code but still can't catch the app exceptions

I'm trying to test this site https://store.google.com/regionpicker and when I select a region (US) for example and get redirected to this url: https://store.google.com/us/?hl=en-US&regionRedirect=true I get an app error in the console, and cypress fails to continue
I added this code to handle exceptions before the test starts, but it doesn't catch it:
cy.on('uncaught:exception', () => {
return false
})
full code:
describe('Purchase a device from Google Store', () => {
const LOCATORS = {
devicesMenu: '[id="desktop-products"]',
headerContainer: '.header-container',
regionTitle: '.region-title',
dialog: '[role="dialog"]',
button: '[role="button"]',
}
before(() => {
cy.visit('/')
})
beforeEach(() => {
Cypress.on('uncaught:exception', () => {
return false
})
})
it('Purchase device', () => {
let region = 'United States'
let urlToVerify = 'us/?hl=en-US&regionRedirect=true'
cy.contains(LOCATORS.regionTitle, region)
.click().then(() => {
cy.get(LOCATORS.dialog).within(() => {
cy.contains('Continue')
.click({ force: true }).then(() => {
cy.url()
.should('include', urlToVerify)
})
})
})
})
})
You can test the URL in an event handler for the url:changed event.
The basic problem seems to be that it's not possible to take Cypress to https://store.google.com, the browser ends up with URL about:blank.
But by adding a listener to url:changed you can at least verify that the correct URL was attempted.
it('redirects after region select', () => {
const region = 'United States'
const urlToVerify = 'us/?hl=en-US&regionRedirect=true'
cy.on('url:changed', (url) => {
expect(url).to.include(urlToVerify) // use chai to assert
})
cy.contains(LOCATORS.regionTitle, region).click()
cy.get(LOCATORS.dialog).within(() => {
cy.contains('Continue').click({ force: true })
})
})

React - can't dispatch action in hook component

Using dispatch in useffect hook of functional component,
Below code shows error page like below;
Component:
import { GetParks } from "../../../redux/actions/survey_actions"
...
function BarcodeGenerator(props) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(props.GetParks());
}, []);
actions:
export const GetParks = (Id) => async (dispatch, getState) => {
try {
const response = await axiosHelper.get("api/survey/GetParks", {
params: {
Id,
},
});
debugger;
response = response.data;
if (response.status !== ResponseStatus.SUCCESS) {
dispatch({
type: GET_PARKS,
payload: [1, 4555, 34],
});
}
} catch (error) {
catchCallback(error);
}
};
const _getParks = (data) => ({
type: GET_PARKS,
payload: data,
});
how does dispatch the action to reducer properly
Action must be a plain object, as it is described in the error description. E.g. it is ok to use dispatch directly as is:
if (*statement*) {
dispatch({
action: DO_SMTH,
payload: true
})
}
or to make the action creator returning the equal object if you want to make clean re-usable code:
if (*statement*) {
dispatch(doSmth(true));
}
function doSmth(toggle) {
return ({
action: DO_SMTH,
payload: toggle
})
}

Moxios Requests State Not Cleared In Between Tests

My specs are behaving weirdly in that when I run the tests alone, they pass. However, when I run the test suite all together, the failure tests still continue to use the success axios mock instead of using the correct failing http axios mock. This results in my tests failing. Am I missing something for isolating the 2 mocks from each other in the different portions of code?
jobactions.js
export const loadUnassignedJobs = (job_type) => {
if (!['unscheduled', 'overdue'].includes(job_type)) {
throw 'Job Type must be "unscheduled" or "overdue".';
}
return (dispatch) => {
dispatch({type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED, job_type });
return axios.get(defaults.baseapi_uri + 'jobs/' + job_type)
.then(function (response) {
dispatch(updateUnassignedJobs(response.data.jobs));
// handle success
})
.catch(function (error) {
// handle error
dispatch({ type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE, error });
})
.then(function () {
// always executed
});
}
};
export const updateUnassignedJobs = (unassigned_jobs) => {
let unassigned_job_ids = [];
let jobs = {};
for (let job of unassigned_jobs) {
unassigned_job_ids.push(job.id);
jobs[job.id]=job;
}
return({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs,
unassigned_job_ids,
});
};
spec.js
import configureMockStore from "redux-mock-store";
import thunk from "redux-thunk";
import * as jobActions from "../../../app/javascript/actions/JobActions"
import { findAction } from '../support/redux_store'
import * as JobActionTypes from '../../../app/javascript/constants/JobActionTypes'
import fixtures_jobs_unscheduled_success from '../fixtures/jobs_unscheduled_success'
import moxios from "moxios";
export const mockStore = configureMockStore([thunk]);
let store;
describe ('loadUnassignedJobs', () => {
context('when bad parameters are passed', async () => {
it('will raise an error', () => {
const store = mockStore();
expect(() => {
store.dispatch(jobActions.loadUnassignedJobs('wrong_type'));
}).to.throw('Job Type must be "unscheduled" or "overdue".');
});
});
context('when unscheduled is passed', () => {
beforeEach(() => {
moxios.install();
console.log("before each called");
console.log(moxios.requests);
store = mockStore();
store.clearActions();
});
afterEach(() => {
console.log("after each called");
console.log(moxios.requests);
moxios.uninstall();
});
context('on success', () => {
beforeEach(() => {
moxios.wait(() => {
let request = moxios.requests.mostRecent();
request.respondWith({
status: 200,
response: fixtures_jobs_unscheduled_success
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_STARTED', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED)).to.be.eql({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_STARTED,
job_type: 'unscheduled'
});
});
});
it('dispatches updateUnassignedJobs()', () => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store,JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.eql(jobActions.updateUnassignedJobs(fixtures_jobs_unscheduled_success.jobs))
});
});
});
context('on error', () => {
beforeEach(() => {
//console.log("before each on error called");
//console.log(moxios.requests);
moxios.wait(() => {
console.log('after waiting for moxios..')
console.log(moxios.requests);
let request = moxios.requests.mostRecent();
request.respondWith({
status: 500,
response: { error: 'internal server error' }
});
});
})
it('dispatches LOAD_UNASSIGNED_JOBS_FAILURE', (done) => {
console.log(moxios.requests);
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
console.log(moxios.requests);
console.log(store.getActions());
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE)).to.include({
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE
});
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_FAILURE).error).to.include({
message: 'Request failed with status code 500'
});
done();
});
});
it('does not dispatch LOAD_UNASSIGNED_JOBS_SUCCESS', (done) => {
store.dispatch(jobActions.loadUnassignedJobs('unscheduled')).then(() => {
expect(findAction(store, JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS)).to.be.undefined;
done();
});
});
})
});
});
describe('updateUnassignedJobs', () => {
it('assigns jobs to hash and creates an unassigned_job_ids array', () => {
expect(jobActions.updateUnassignedJobs([ { id: 1, step_status: 'all_complete' }, { id: 2, step_status: 'not_started' } ])).to.be.eql(
{
type: JobActionTypes.LOAD_UNASSIGNED_JOBS_SUCCESS,
jobs: { 1: { id: 1, step_status: 'all_complete' }, 2: { id: 2, step_status: 'not_started' } },
unassigned_job_ids: [ 1,2 ]
}
)
});
});
Found the issue!
The it() blocks for the success case were not using the done callback causing the afterEach() moxios.uninstall() to be called prematurely and not resetting the requests after the call was complete. Fixing this, and now all the tests pass.

After closing the modal dialog refresh the base view

suggestion and code sample
I am new to Backbone marionette, I have a view ("JoinCommunityNonmemberWidgetview.js") which opens a modal dialog ("JoinCommunityDetailWidgetview.js").On closing of the dialog ( I want the view JoinCommunityNonmemberWidgetview.js") to be refreshed again by calling a specific function "submitsuccess" of the view JoinCommunityNonmemberWidgetview.js.
How can I achieve it.
The code for the modal is as below:
define(
[
"grads",
"views/base/forms/BaseFormLayout",
"models/MembershipRequestModel",
"require.text!templates/communitypagewidget/JoinCommunityWidgetDetailTemplate.htm",
],
function (grads, BaseFormLayout, MembershipRequestModel, JoinCommunityWidgetDetailTemplate) {
// Create custom bindings for edit form
var MemberDetailbindings = {
'[name="firstname"]': 'FirstName',
'[name="lastname"]': 'LastName',
'[name="organization"]': 'InstitutionName',
'[name="email"]': 'Email'
};
var Detailview = BaseFormLayout.extend({
formViewOptions: {
template: JoinCommunityWidgetDetailTemplate,
bindings: MemberDetailbindings,
labels: {
'InstitutionName': "Organization"
},
validation: {
'Email': function (value) {
var emailconf = this.attributes.conf;
if (value != emailconf) {
return 'Email message and Confirm email meassage should match';
}
}
}
},
editViewOptions: {
viewEvents: {
"after:render": function () {
var self = this;
var btn = this.$el.find('#buttonSubmit');
$j(btn).button();
}
}
},
showToolbar: false,
editMode: true,
events: {
"click [data-name='buttonSubmit']": "handleSubmitButton"
},
beforeInitialize: function (options) {
this.model = new MembershipRequestModel({ CommunityId: this.options.communityId, MembershipRequestStatusTypeId: 1, RequestDate: new Date() });
},
onRender: function () {
BaseFormLayout.prototype.onRender.call(this)
},
handleSubmitButton: function (event) {
this.hideErrors();
// this.model.set({ conf: 'conf' });
this.model.set({ conf: this.$el.find('#confirmemail-textbox').val() });
//this.form.currentView.save();
//console.log(this.form);
this.model.save({}, {
success: this.saveSuccess.bind(this),
error: this.saveError.bind(this),
wait: true
});
},
saveSuccess: function (model, response) {
var mesg = 'You have submitted a request to join this community.';
$j('<div>').html(mesg).dialog({
title: 'Success',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
grads.modal.close();
},
saveError: function (model, response) {
var msg = 'There was a problem. The request could not be processed.Please try again.';
$j('<div>').html(msg).dialog({
title: 'Error',
buttons: {
OK: function () {
$j(this).dialog('close');
}
}
});
}
});
return Detailview;
}
);
I would use Marionette's event framework.
Take a look at: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.commands.md
Specifically, you need to:
1) Create a marionette application :
App = new Marionette.Application();
2) Use the application to set up event handlers
//Should be somewhere you can perform the logic you are after
App.commands.setHandler('refresh');
3) Fire a 'command' and let marionette route the event
App.execute('refresh');

Resources