cypress: How can manage the application flow, if the element xpath is not present - cypress

I have the following scenario:
if the element is present, i have to do one activity and if not present will do another activity.
cy.xpath("//div[text()= 'button').its('length').then(res=> {
if (res > 0) {
return 1;
}
else {
cy.log ("Element is not present")
}
}
)} '''
if element is present = Code is working fine,
if the element xpath is not present = it try to search the element xpath (//div[text()= 'button') and throwing the error as 'Timed out retrying: Expected to find element: undefined, but never found it.'
if element is not present, Is there any way, i can handle the code ,

When using xpath you can (sort of) make it conditional by wrapping the xpath selector with count().
cy.xpath("count(//div[text()= 'button'])") // ok with async content
.then(count => {
if (count) {
//return 1; // not useful, returns a "chainer"
// ...but you can perform the required test here, e.g
cy.xpath("//div[text()= 'button']").click()
} else {
cy.log('not found')
}
})
The shorter syntax using built-in jQuery might be
const exists = !!Cypress.$("div:contains('button')").length
if (exists) {
cy.xpath("//div[text()= 'button']").click()
} else {
cy.log('not found')
}
Note that this is a partial match to 'button', where-as the xpath syntax is an exact match.
Also note - using Cypress.$ by-passes retry-ability, so it should not be used where the text is asynchronous.
From docs
This is a great way to synchronously query for elements when debugging from Developer Tools.
The implication is that it's more for debugging after the page has loaded.
The best practice is to try to construct the test and the app's data in such a way that you know that the button is present.

You can do something like this. With Cypress.$, you can validate the presence of the element with the help of the length attribute and then do further actions.
cy.get('body').then($body => {
const ele = $body.find('selector');
if (Cypress.$(ele).length == 0) {
//Do Something element is not present
}
else if (Cypress.$(ele).length > 0) {
//Do Something when element is present
}
})

Related

Cypress - How to use if statement with contains

so I have to use cy.contains to find the element I want, but all I can find online is how to use if() with cy.find or cy.get if there a way to do this with contains?
Example code:
if(cy.contains('div.name', 'Test 1').length > 0) {
//Type in name
cy.get('input.newName').click().type('Test 1');
cy.wait(2000);
//Click Add Name
cy.get('div.createNewName> a').click();
cy.wait(2000);
}
What I am trying to do there is:
if(Name doesnt exist){
Create it
}
I'm not sure if I have explained myself too well, if any more clarifications are needed feel free to ask
You can also do like this:
cy.get('body').then(($body) => {
if ($body.find('div.name:contains("Test 1")').length > 0) {
//Element Found
} else {
//Element not found
}
})
The general pattern for this would be as follows:
const element = Cypress.$('div.name:contains(Test 1)')
if (element.length > 0) {
...
Make sure the DOM is stable when you run this code, there is no retry built in as there is with cy.contains()
If the code inside if() is creating the name, then maybe the logic would be
const element = Cypress.$('div.name:contains(Test 1)')
if (element.length === 0) {
// not found so create it
...
You can also do it like this
cy.get('div.name').then($div => {
const found = $div.find(':contains("Test 1")')
if (found.length === 0) {
// create...
}
})

Cypress if else conditional test

I am trying to include the below condition into my test but I can't seem to get it to work and receive the below error, any ideas why?
Essentially, I want to test that a input is / is not empty:
cy.get(`[class^='input-module_field']`).eq(0).then(($input) => {
if ($input.should('have.value', '')) {
cy.get(`[class^='input-module_field']`).eq(0).should('be.visible').type(foo)
cy.get(`[class^='input-module_field']`).eq(1).should('be.visible').type(bar)
cy.get(`[class^='input-module_field']`).eq(2).should('be.visible').type(foo-bar)
cy.get(`[class^='input-module_field']`).eq(3).should('be.visible').type(foo-bar-foo)
} else {
cy.get(`[class^='input-module_field']`).eq(1).should('be.visible').type(bar)
cy.get(`[class^='input-module_field']`).eq(2).should('be.visible').type(foo-bar)
cy.get(`[class^='input-module_field']`).eq(3).should('be.visible').type(foo-bar-foo)
}
})
The error i get:
$input.should is not a function
When yielded from a .then(), the $input variable is just a JQuery element, and can't use Cypress commands. In this case, even using a Cypress command, such as .should() wouldn't work, because that does not yield a boolean value for the if/else.
Instead, we'll want to use JQuery's .val() function.
...
if ($input.val()) { // if $input.val() returns an empty string, this evaluates to false
// code to run if the $input element has a value
} else {
// code to run if the $input element does not have a value.
}
...
Note: I reversed the order of what you had, with the $input.val() === '' being the else, instead of the if.
The check can be on the value itself
cy.get(`[class^='input-module_field']`).eq(0)
.then(($input) => {
const field0Val = $input.val() || 'foo' // if empty use "foo"
cy.get(`[class^='input-module_field']`).eq(0).type(field0Val)
cy.get(`[class^='input-module_field']`).eq(1).type('bar')
cy.get(`[class^='input-module_field']`).eq(2).type('foo-bar')
cy.get(`[class^='input-module_field']`).eq(3).type('foo-bar-foo')
})

How to override 'data-testid' in the 'findByTestId function from Cypress Testing Library

Most of my existing codebase uses a 'id' only in few places 'data-testId' attribute present.
tried this code
import { configure } from '#testing-library/cypress';
configure({ testIdAttribute: ['data-testId','id'] });
But, still its not working.
Is there any way to use 'id' value in any of the testing-library functions.
My HTML code is something like:
<div class="some random class name" id="userprofile-open" role="button">SB</div>
I want click that element with this code:
cy.findByTestId("userprofile-open", { timeout: 120000 }).click();
I don't think you can configure testing-library with an array of ids, ref API configuration,
import { configure } from '#testing-library/cypress'
configure({ testIdAttribute: 'id' })
But even this fails. Instead you have to use the Cypress command to change the attribute name (only one name is allowed).
cy.configureCypressTestingLibrary({ testIdAttribute: 'id' })
To use either/or attribute name you can change the attribute name on the fly, wrapping it in a custom command (based on Custom Queries)
Cypress.Commands.add('findByTestIdOrId', (idToFind) => {
let result;
const { queryHelpers } = require('#testing-library/dom');
let queryAllByTestId = queryHelpers.queryAllByAttribute.bind(null, 'data-testId');
result = queryAllByTestId(Cypress.$('body')[0], idToFind)
if (result.length) return result;
queryAllByTestId = queryHelpers.queryAllByAttribute.bind(null, 'id');
result = queryAllByTestId(Cypress.$('body')[0], idToFind);
if (result.length) return result;
throw `Unable to find an element by: [data-test-id="${idToFind}"] or [id="${idToFind}"]`
})
cy.findByTestIdOrId('my-id')
.should('have.attr', 'id', 'my-id')
// passes and logs "expected <div#my-id> to have attribute id with the value my-id"
Note this custom command works only for synchronous DOM.
If you need to have Cypress retry and search for either/or attribute, don't use testing-library in the custom command.
Instead use Cypress .should() to enable retry
Cypress.Commands.add('findByTestIdOrId', (selector, idToFind) => {
cy.get(selector)
.should('satisfy', $els => {
const attrs = [...$els].reduce((acc, el) => {
const id = el.id || el.getAttribute('data-test-id') // either/or attribute
if (id) {
acc.push(id)
}
return acc
}, [])
return attrs.some(attr => attr === idToFind); // retries when false
})
.first(); // may be more than one
})
cy.findByTestIdOrId('div', 'my-id')
.should('have.attr', 'id', 'my-id')
// passes and logs "expected <div#my-id> to have attribute id with the value my-id"
The usual cypress way - which has an inherent check on the element visibility and existence as well as included retries for a period of time is using cy.get()
If you want to select element using property like data-id you need this sintax: cy.get('[propertyName="propertyValue"]')
If you want select an element by CSS selector you just pass CSS selector like this:
cy.get('#id')

check store for object before calling api

You know how they say you don't need state management until you know you need it. Well turns out my project needs it. So I need some help wit best practice as I am adding ngxs to an existing angular project.
I have an action called getServiceDetail and my statemodel has a list of objects called DriverListsStopInfoViewModel. each of these objects have a unique ID. The html template of the consuming component uses a selector for the property currentStopDetail, which is a state property that gets set in my action.
GOAL:
in my action I want to check the list of objects in my store to see if an object with the same id exists and return that object, and if it does not exist, call and api to get it.
EXAMPLE:
The following code works, but I would like to hear if this is the right way to do it. do I even need to return the object from the action function if its found, or can I just use patch state to assign it to the currentStopDetail
export interface SignServiceStateModel {
searchResults: ServiceSearchModel[];
driverStopsDetails: DriverListsStopInfoViewModel[];
driverStopsList: DriverListsStopsViewModel[];
driverStopsMarkers: DriverStopsMarkerViewModel[];
currentStopDetail: DriverListsStopInfoViewModel;
}
const SIGNSERVICE_STATE_TOKEN = new StateToken<SignServiceStateModel>(
'signservice'
);
#State<SignServiceStateModel>({
name: SIGNSERVICE_STATE_TOKEN,
defaults: {
searchResults: [],
driverStopsDetails: [],
driverStopsList: [],
driverStopsMarkers: [],
currentStopDetail: null
},
})
#Injectable()
export class SignServiceState {
constructor(private driverListsService: DriverListsService) {}
#Action(DriverList.GetServiceDetail)
getServiceDetail(
ctx: StateContext<SignServiceStateModel>,
action: DriverList.GetServiceDetail
) {
if (action.serviceId === undefined || action.serviceId <= 0) {
return;
}
// check if record already in list and return
const currentState = ctx.getState();
const existingStopDetail = currentState.driverStopsDetails.find(s => s.SignServiceId === action.serviceId);
if (existingStopDetail !== undefined) {
const currentStopDetail = existingStopDetail;
ctx.patchState({ currentStopDetail });
return currentStopDetail;
}
// else get new record, add it to list and return
return this.driverListsService.getDriverListsInfo(action.serviceId).pipe(
tap((currentStopDetail) => {
ctx.patchState({ currentStopDetail });
ctx.setState(
patch({
driverStopsDetails: append([currentStopDetail])
})
);
})
);
}
#Selector()
static currentStopDetail(state: SignServiceStateModel) {
return state.currentStopDetail;
}
}
I only included the relevant code from my state class
QUESTION:
is this the best way to check the store for an item and call api if it does not exist?
Thanks in advance
Short answer is yes, what you have done here is a typical way of handling this scenario (in my experience). There's a couple of improvements you could make:
do I even need to return the object from the action function if its found, or can I just use patch state to assign it to the currentStopDetail
No, you don't return anything from these action handlers, other than possibly an Observable that NGXS will handle (so in your case if there is no matching item found, you return the Observable that fetchs it from the API and patches the state).
Also when you do make the API call, you should only need a single update to the state:
return this.driverListsService.getDriverListsInfo(action.serviceId).pipe(
tap((result) => {
ctx.setState(
patch({
currentStopDetails: result
driverStopsDetails: append([result]),
})
);
})
);

Nightwatch.js. Get node children, iterate through them

My task is get children of some element, iterate through them, and using nightwatch assert make some tests. Here is example of code, what I need:
browser.url("http://someurl.com")
.getAttribute("#parent", "children", function (children) {
var assert = browser.assert;
var fisrtChild = children[0];
var secondChild = children[1];
assert.equal(fisrtChild.innerHTML, "Hello");
assert.equal(secondChild.innerHTML, "World");
})
So, is Nightwatch can do something like this?
P.S. I tried to use 'elements' command, but it returns something like this:
{ state: 'success',
sessionId: '63af98a9-d395-4e44-9529-e24a7ad7ff87',
hCode: 1223583237,
value: [],
class: 'org.openqa.selenium.remote.Response',
status: 0 }
Firstly "children" is NOT a valid attribute for HTML elements.
Secondly the callback function will simply pass the result of said command should you give it an argument. So the object displayed is in actual fact the result of the getAttribute command.
you could probably use the browser.execute command to allow you to do what you are trying to do
Essentially do something like the following: -
var firstChild;
var secondChild;
browser.execute(function() {
var childNodes = [];
childNodes.push(document.getElementById('parentID').childNodes);
firstChild = childNodes[0].innerHTML;
secondChild = childNodes[1].innerHTML;
}, [])
You will then be able to perform assertions on the values of firstChild and secondChild.
NOTE: Have not tested this and you may need to play around with it a little but hopefully you get the general idea
you can try below code. It will fetch all the buttons and then click on all sequentially.
browser
.url("http://someurl.com")
.elements('css selector', "button", function (links) {
for (var i = 0; i < links.value.length; i++) {
browser.elementIdClick(links.value[i].ELEMENT);
}
})

Resources