Cypress - Element not found - automated-tests

How can I overcome a situation when the element is not found. I am automating a form where intentionally I am giving a duplicate name so an error message will be shown "Limit Name already exist" based on that I have written
add.limitName().type('Movie Limits') // Giving duplicate name "Movie Limits"
cy.get('.has-error > .col-sm-6 > [data-bv-validator="remote"]').then((wizard) => {
if(wizard.text().includes('Limit Name already exist.')) // Duplicate Limit Name Check
{
add.limitName().clear()
add.limitName().type('Movie TR Limits') // Giving another Name
}
})
This works perfectly fine if its a duplicate value but if its not a duplicate value then this element wont be found and an error is thrown and the test fails. How can i write in such a way if its not duplicate it carries on and if it is the above code comes into action ?

It seems perfectly legit for the test to fail when the error is missing, but to answer your question
Split off the last selector and check it with jQuery (which does not cause a fail) instead of including it in the Cypress cy.get().
cy.get('.has-error > .col-sm-6')
.then($col => {
// test the last element with jquery by checking it's length
if ($col.find('[data-bv-validator="remote"]').length === 0) {
...

Assuming that when there is no error the element .has-error itself won't exist in the dom. So you can do something like this:
cy.get('body').then(($body) => {
if ($body.find('.has-error').length > 0) {
//Element found
cy.get('.has-error').should('include.text', 'Limit Name already exist.')
add.limitName().clear()
add.limitName().type('Movie TR Limits')
} else {
//Error element not found.Write further code.
}
})

You should use another selector other than .has-error if that class may or may not exist.
cy.get('my-selector').then($el => {
if ($el.hasClass('has-error')) {
cy.get('.has-error > .col-sm-6 > [data-bv-validator="remote"]')
.then(wizard => {
...
})
} else {
...
}
})

Assuming the element with .has-class is not the <input> you type into, use .parent() to check if the class is present.
add.limitName().type('Movie Limits')
cy.get('[data-bv-validator="remote"]')
.parent() // up to ".col-sm-6"
.parent() // up to element which (maybe) has ".has-error"
.then($el =>
if ($el.hasClass('has-error')) {
cy.get('.has-error > .col-sm-6 > [data-bv-validator="remote"]')
.then((wizard) => { ...
});
}
})

You can check the error class after the .type() command
add.limitName().type('Movie Limits')
.then($el => {
if ($el.hasClass('has-error')) {
cy.get('.has-error > .col-sm-6 > [data-bv-validator="remote"]')
.then((wizard) => { ...
});
}
});

Related

How can I get a custom css variable from any element (cypress)

I want to create some tests checking the styles of elements. We use these custom CSS vars. Is there any way to get these in cypress instead of checking for e.g. RGB(0,0,0)?
Thx in advance!
If you use cy.should() alongside have.css, you can specify which CSS property to check, and the value.
Using a simple example from your image, it would look something like this:
cy.get('foo')
.should('have.css', 'min-width', '211px');
If there are more complex checks going on, you can always run the .should() as a callback.
cy.get('foo').should(($el) => {
const minHeight = +($el.css('min-height').split('px')[0]);
expect(minHeight).to.eql(40);
});
I found myself checking a lot of CSS values on elements, and opted to have a custom command that allowed me to pass in an expected object and check for all of those values.
Cypress.Commands.add('validateCss', { prevSubject: 'element' }, (subject, expected: { [key: string]: any }) => {
Object.entries(expected).forEach(([key, value]) => {
cy.wrap(subject).should('have.css', key, value);
});
});
const expected = { 'min-width': '211px', 'min-height': '40px' };
cy.get('foo').validateCss(expected);
Interacting with browser element or Dynamic CSS can be achieved in may ways ,
most use-full is cy.get() with the help of .should()
you can find here ( However i know you already checked this :) )
https://docs.cypress.io/api/commands/get#Get-vs-Find
for Example
cy.get('#comparison')
.get('div')
// finds the div.test-title outside the #comparison
// and the div.feature inside
.should('have.class', 'test-title')
.and('have.class', 'feature')
It is possible to evaluate a css variable fairly simply using getComputedStyle()
Cypress.Commands.add('cssVar', (cssVarName) => {
return cy.document().then(doc => {
return window.getComputedStyle(doc.body).getPropertyValue(cssVarName).trim()
})
})
cy.cssVar('--mycolor')
.should('eq', 'yellow')
where, for example
<html>
<head>
<style>
body {
--mycolor: yellow;
}
p {
background-color: var(--mycolor);
}
</style>
</head>
But asserting that <p> has --mycolor requires a dummy element to evaluate yellow to rgb(255, 255, 0).
Cypress.Commands.add('hasCssVar', {prevSubject:true}, (subject, styleName, cssVarName) => {
cy.document().then(doc => {
const dummy = doc.createElement('span')
dummy.style.setProperty(styleName, `var(${cssVarName})`)
doc.body.appendChild(dummy)
const evaluatedStyle = window.getComputedStyle(dummy).getPropertyValue(styleName).trim()
dummy.remove()
cy.wrap(subject)
.then($el => window.getComputedStyle($el[0]).getPropertyValue(styleName).trim())
.should('eq', evaluatedStyle)
})
})
it('compares element property to CSS variable', () => {
cy.cssVar('--mycolor').should('eq', 'yellow')
cy.get('p').hasCssVar('background-color', '--mycolor') // passes
cy.get('button').click() // change the css var color
cy.cssVar('--mycolor').should('eq', 'red')
cy.get('p').hasCssVar('background-color', '--mycolor') // passes
})
The complication is not really because of the CSS var, but because we are dealing with color names that are automatically translated by the browser CSS engine.
Full test page
<html>
<head>
<style>
body {
--mycolor: yellow;
}
p {
background-color: var(--mycolor);
}
</style>
</head>
<body>
<p>Some text, change the background from yellow to red.</p>
<button onclick="changeColor()">Change color</button>
<script>
function changeColor() {
document.body.style.setProperty('--mycolor', 'red')
};
</script>
</body>
</html>
Test log

Clicking button using inner text

I am using puppeteer to click a button that does not have a generic id. I have attached a screenshot of the DOM with an arrow showing the button element. I have tried the following code to no avail:
await page.evaluate(() => {
let btns1 = [...document.querySelector("typeaheadDropdownWrapped-0").querySelectorAll("button")];
btns1.forEach(function (btn) {
if (btn.innerText == "#jubaitca")
btn.click();
});
});
#jubaitca is a known text that can be used to identify the button. Can anyone help?
DOM
I don't see any #jubaitca inside the HTML DOM screenshot. But if it's inside this selector, then maybe this code will work.
Your selector should be like this:
await page.evaluate(() => {
document.querySelectorAll('[role="progressbar"] ~ div > [role="button"]').forEach(elem => {
if (elem.innerText.search('jubaitca') > -1) {
elem.click()
}
})
})

How to change the id of clicked image

I want to change the id of selected image on click. Currently, clicking one image will affect all the images with [id]="available". How can I change for specific image. Here is my html
<ion-row>
<ion-avatar *ngFor="let seat of row1.seats; let i=index;" id="item-
{{i}}">
<ion-label>{{seat.label}}</ion-label>
<img *ngIf="seat.booked===true" src="assets/st.png" id="booked">
<img *ngIf="seat.booked===false" src="assets/st.png"
[id]="available" (click)="selectedSeat(seat.id)">
</ion-avatar>
</ion-row>
Here is ts.
available = "available";
selectedSeat(id) {
this.available = "selected";
}
css:
#available {
background-color: cornsilk;
}
#selected {
background-color: #614056;
}
You can use setAttribute() of Renderer2 and template reference like following:
In template, add #myImg to img tag:
<img *ngIf="seat.booked===false" src="assets/st.png #myImg (click)="selectedSeat(myImg, seat.id)">
In component (.ts) file, inject Renderer2 and use it in selectedSeat method:
constructor(private renderer: Renderer2){}
selectedSeat(seletctedSeat: ElementRef, id) {
this.renderer.setAttribute(seletctedSeat.nativeElement, 'id', 'selected');
}
!BUT!
I think assigning id attribute is not the right way to do it. ID should be unique by nature, so you are assignin same ID to multiple elements. This sounds wrong. I think you should change CSS class if you only want to change styling but not applying any logic (like doing something with selected seat) with following:
In .ts file:
selectedSeat(seletctedSeat: ElementRef, id) {
this.renderer.addClass(seletctedSeat.nativeElement, 'selected');
}
In CSS:
.selected {
background-color: #614056;
}
UPDATE
You can create an array and push selected ID's to it. Then check if the array includes the seat.id:
In template:
<img *ngIf="seat.booked===false" src="assets/st.png"
[ngClass]="selectedSeatList.includes(seat.id) ? 'selected' : 'available'" (click)="toggleSelection(seat.id)">
In component (.ts) file:
selectedSeatList: Array<number> = [];
toggleSelection = (seatID) => {
if(this.selectedSeatList.includes(seatID)){
this.selectedSeatList = this.selectedSeatList.filter(id => id !== seatID);
}else{
this.selectedSeatList.push(seatID);
}
}
It looks like you want to change the background-color for an ngFor-created element with an index, when that index is selected through the selectedSeat() function.
If this is accurate, I would say to ignore the element's id entirely and use class instead.
You could start by using your selectedSeat() function to accept the template element's index as well as seat.id, then change your available variable to something like currentlySelectedSeat to the currently selected ngFor element's index:
currentlySelectedSeat = 0; // <-- default to first index (if you wish)
selectedSeat(seatId, index) {
if (index) { // <-- basic check
this.currentlySelectedSeat = index; // <-- assign id
}
// do whatever else with seatId
}
If you slightly alter your css to define classes instead of ids like this:
.available {
background-color: cornsilk;
}
.selected {
background-color: #614056;
}
..you could then use a ternary in an ngClass and structure your template like this:
<ion-row>
<ion-avatar *ngFor="let seat of row1.seats; index as i;"> // <-- id not necessarily needed. index still assigned to i, just written differently
<ion-label>{{seat.label}}</ion-label>
<img *ngIf="seat.booked===true" src="assets/st.png" class="booked"> // <-- unless there will only ever be one of these images, use class instead of id
<img *ngIf="seat.booked===false" src="assets/st.png"
[ngClass]="currentlySelectedSeat === i ? 'selected' : 'available'" // <-- *this line clarified below
(click)="selectedSeat(seat.id, i)"> // <-- expand function to accept template index as well.
</ion-avatar>
</ion-row>
*Clarified ngClass template line: this is the same as writing:
if (available === i) { // <-- if the variable named available's value is the same as the index which was passed through selectedSeat() on click
return 'selected'; // <-- give ngClass a string which corresponds to .selected
} else {
return 'available'; // <-- give ngClass a string which corresponds to .available

Want to create XML element on the fly

I want to create an element on the fly, I'm trying below query but its giving me this error: SQL16031N XQuery language feature using syntax "element {$first} { "Crockett" }, element last {"Johnson" } } })" is not supported
Could you please help me out.
XQUERY
let $first := concat('first','')
return (element book {
attribute isbn {"isbn-0060229357" },
element title { "Harold and the Purple Crayon"},
element author {
element {$first} { "Crockett" },
element last {"Johnson" }
}
})
Try this:
let $first := xs:QName('first')
return (element book {
attribute isbn {"isbn-0060229357" },
element title { "Harold and the Purple Crayon"},
element author {
element {$first} { "Crockett" },
element last {"Johnson" }
}
})
It seems that DB2 XQuery doesn't support computed element constructors with a dynamically computed name. If the number of possible names is small and known in advance, you can get around that limitation by listing all the possibilities. As DB2 doesn't seem to support switch either, we'll have to do it with an if/else cascade:
XQUERY
let $first := 'firstA'
return (element book {
attribute isbn {"isbn-0060229357" },
element title { "Harold and the Purple Crayon"},
element author {
if($first eq 'firstA')
then element firstA { "Crockett" }
else if($first eq 'firstB')
then element firstB { "Crockett" }
else if($first eq 'firstC')
then element firstC { "Crockett" }
else (),
element last {"Johnson" }
}
})

How to change the FireBug representation of a css class with a FireBug extension?

When messing around in the FireBug css panel, you change the their representation of the original css file. Like:
.myCssClass { width: 100px; }
However, if you add a jQuery line to this,
$(".myCssClass").css("width", "200px");
you end (of course) up with changing the style tag for this element and you see that your original width:100px has a strikethough in the FireBug representation.
So my question is, do you know a way to change the "original" width:100px instead of changing the style tag. I guess you have to through a FireBug extension to access that property, and that is not a problem for me. But I don't know where to start :)
Edit: Have to point out that I am need to change the property by code! Either from a FireBug extension or somehow reload the corresponding css so that FireBug think it is the orginal value.
Here is an old JS function that usually worked well for me (Before Stylish and Greasemonkey).
Note that plain JS has security restrictions from accessing some stylesheets. A FF add-on can get around that, but then you need to also beware of corrupting browser-chrome styles.
function replaceStyleRuleByName (sStyleName, sNewRule)
{
var iNumStyleSheets = document.styleSheets.length;
var bDebug = 0;
if (bDebug) console.log ('There are ' + iNumStyleSheets + ' style sheets.');
for (iStyleS_Idx=0; iStyleS_Idx < iNumStyleSheets; iStyleS_Idx++)
{
var iNumRules = 0;
var zStyleSheet = document.styleSheets[iStyleS_Idx];
if (zStyleSheet)
{
/*---WARNING!
This next line can throw an uncaught exception!
Error: uncaught exception:
[Exception... "Access to restricted URI denied" code: "1012"
nsresult: "0x805303f4 (NS_ERROR_DOM_BAD_URI)"
location: ... ...]
*/
//--- try/catch for cross domain access issue.
try
{
var zRules = zStyleSheet.cssRules;
if (zRules)
{
iNumRules = zRules.length;
}
}
catch (e)
{// Just swallow the error for now.
}
}
if (bDebug) console.log ("Style sheet " + iStyleS_Idx + " has " + iNumRules + " ACCESSIBLE rules and src: " + zStyleSheet.href);
//for (var iRuleIdx=iNumRules-1; iRuleIdx >= 0; --iRuleIdx)
for (var iRuleIdx=0; iRuleIdx < iNumRules; ++iRuleIdx)
{
if (zRules[iRuleIdx].selectorText == sStyleName)
{
zStyleSheet.deleteRule (iRuleIdx);
if (bDebug) console.log (sNewRule);
if (sNewRule != null)
{
zStyleSheet.insertRule (sStyleName + sNewRule, iRuleIdx);
}
//return; //-- Sometimes changing just the first rule is not enough.
}
}
//--- Optional: Punt and add the rule, cold, to any accessible style sheet.
if (iNumRules > 0)
{
if (sNewRule != null)
{
try
{
zStyleSheet.insertRule (sStyleName + sNewRule, iRuleIdx);
}
catch(e)
{// Just swallow the error for now.
}
}
}
}
return;
}
Sample Usage:
replaceStyleRuleByName ('body', '{line-height: 1.5;}' );
replaceStyleRuleByName ('#adBox', '{display: none;}' );
replaceStyleRuleByName ('.BadStyle', null );
Just right click on the property in question and then edit [stylename]
Look for the "Computed" tab, it displays the actual values used of the properties of an element. The "Style" tab only displays the "stylesheet values" that affects a particular element, which may or may not be actually used by Firefox due to CSS' cascading rule and other layouting considerations.

Resources