Cypress get text value from an element - string

I am trying to get a text from an element with Cypress in the first test from the first domain and then type it in the second test in another domain, here is a code
I have to grab code from h4.
I implemented next part of code:
get studentCouponValue() {
return cy.get('h4').then(($span) => {
const couponValue = $span.text();
cy.log(couponValue);
})
}
in logs, I see the correct coupon's value, but when I am trying to type it into the field I get an error
The chain approach doesn't fit my expectation, cause i am going to use it in different tests.

Try this:
get studentCouponValue() {
return cy.get('h4').then(($span) => {
const couponValue = $span.innerText;
cy.log(couponValue);
})
}

i resolved
initStudentCouponValue() {
const self = this;
return cy.get('main > .container-fluid').find('h4').then((span) => {
self.couponValue = span.text();
cy.log('First log '+ self.couponValue);
return new Cypress.Promise((resolve) => {
return resolve(self.couponValue);
});
});
}
getStudentCouponValue() {
return this.couponValue;
}
in the test where we want to use value
let couponValue;
admin.initStudentCouponValue().then(() => {
couponValue = admin.getStudentCouponValue()
});
and later we can use
coupoValue
for inputs

Related

Calculate sum of values in Cypress

I am using BDD/Cucumber with Cypress. I want to calculate the sum of some rows of table.
This is my step definition:
And("I add up all the total on hand",()=>{
const sumOnHand = itemListPage.totalOnHandAmsterdam()+itemListPage.totalOnHandDelft()
cy.log(sumOnHand)
})
And this is my js page:
totalOnHandAmsterdam() {
cy.get(':nth-child(2) > .dx-grandtotal > span').invoke('text').then(text =>{
const ttOnHandAmst = text
return ttOnHandAmst;
})
}
totalOnHandDelft() {
cy.get(':nth-child(11) > .dx-grandtotal > span').invoke('text').then(text =>{
const ttOnHandDelft = text
return ttOnHandDelft;
})
}
But this is the output of the calculation:
Any ideas on how can I sum up this value is appreciated.
You can't use the results of totalOnHandAmsterdam() and totalOnHandDelft() directly in a summation because
they don't return anything (the return inside .then(text => does not return the value from the function).
Cypress commands don't return values, they add the values to the command queue
You can do it like this
totalOnHandAmsterdam() {
return cy.get(':nth-child(2) > .dx-grandtotal > span')
.invoke('text').then(parseInt)
}
totalOnHandDelft() {
return cy.get(':nth-child(11) > .dx-grandtotal > span')
.invoke('text').then(parseInt)
}
And("I add up all the total on hand", () => {
itemListPage.totalOnHandAmsterdam().then(ams => // get value on command queue
itemListPage.totalOnHandDelft().then(delft => // get other value
const sumOnHand = ams + delft;
cy.log(sumOnHand)
})
})
})
The key to accessing command return values is using .then() after the command.
It's annoying but necessary because Cypress ensures that the web page has received data from the server before evaluating the element text.
Since the test runs faster than web page fetches data, it can easily evaluate the text before the page is fully populated.
You have to convert your texts to numbers and then add it. You can simply add + in front of the number to convert them into Integers. Also I have added trim() in case your strings have any unwanted spaces.
And('I add up all the total on hand', () => {
const sumOnHand =
+itemListPage.totalOnHandAmsterdam().trim() + +itemListPage.totalOnHandDelft().trim()
cy.log(sumOnHand)
})
You could set the function results as aliases.
Since the code is asynchronous, access it within cy.then().
totalOnHandAmsterdam() {
cy.get(':nth-child(2) > .dx-grandtotal > span')
.invoke('text')
.then(parseInt)
.as('amsterdamTotal') // alias will set this.amsterdamTotal
}
totalOnHandDelft() {
return cy.get(':nth-child(11) > .dx-grandtotal > span')
.invoke('text')
.then(parseInt)
.as('defltTotal') // alias will set this.delftTotal
}
And("I add up all the total on hand", function() { // use function() to access "this"
po.totalOnHandAmsterdam()
po.totalOnHandDelft()
cy.then(() => {
const sumOnHand = this.amsterdamTotal + this.defltTotal;
cy.log(sumOnHand)
})
})

Pagination testing via cypress JS

I need to test that when I select some model car, as a result, I have only that model in all pages. So basically I do pagination testing. But I do something wrong that it does not moves to another page although selectors are correct. Please tell me what I am doing wrong.
findItem("AUDI")
});
async function findItem(value) {
async function findInPage(index) {
let found = false;
cy.get("li.page-item:not(.page-pre):not(.page-next)").as("pages");
await cy.get("#pages")
.its("length")
.then(async (len) => {
if (index >= len) {
return false;
} else {
await cy.get("#pages")
.eq(index)
.click();
await cy.get("table tr > td:nth-child(5) p")
.each(async ($itemNameEl, index) => {
const itemText = $itemNameEl.text().toUpperCase();
cy.log('item ', itemText);
if (itemText.includes(value)) {
found = true;
await cy.wrap($itemNameEl).eq(index);
//cy.get('.chakra-text.css-0').should('include', value)
cy.get('.css-1b4k5p > .chakra-text.css-0')
.each(($el) => {
expect($el.text().toUpperCase()).to.include(value)
})
return false;
}
})
.then(() => {
if (!found) {
findInPage(++index);
}
});
}
});
}
findInPage(0);
}
A simple example without aliases, async functions, etc, just using recursion and a single NEXT PAGE button (that I see you have in your screen shot) would look like this (tested and working on bootstrap pagination examples):
it('Some test', () => {
cy.visit('/')
const findInPage = () => {
cy.get('li:has(a.page-link:has(span:contains(»)))').then((el) => {
// do your test
if (el.hasClass('disabled')) {
// on last page, break out
return
}
cy.wrap(el).click()
findInPage()
})
}
findInPage()
});
How this works: Look for li element which represents a single pagination next-page button, in bootstrap case it has child a tag which has child span tag which contains an icon ». Once you reach last page, the button get's disabled by adding .disabled class to li tag which is checked for on every page. Using this it doesn't matter if you have 3 or 33 pages and if some numbers are hidden with ...
Reference: https://glebbahmutov.com/blog/cypress-recurse/

Update prop for dynamically inserted element

New to react... Really banging my head against it with this one... I'm trying to figure out how to get a dynamically inserted component to update when the props are changed. I've assigned it to a parent state object but it doesn't seem to re-render. I've read that this is what's supposed to happen.
I was using ReactDOM.unmountComponentAtNode to re-render the specific elements I needed to, but it kept yelling at me with red text.
I need to hide "chat.message" unless the user has the authority to see it (server just sends empty string), but I still need to render the fact that it exists, and reveal it should the user get authentication. I'm using a css transition to reveal it, but I really need a good way to update the chat.message prop easily.
renderChats(uuid){
let userState = this.state.userStates.find(user => {
return user.uuid === uuid;
});
const children = userState.chats.map((chat) => {
let ChatReactElement = this.getChatMarkup(chat.cuid, chat.message, chat.status);
return ChatReactElement;
});
ReactDOM.render(children, document.getElementById(`chats-${this.state.guid}-${uuid}`));
}
getChatMarkup() just returns JSX and inserts Props... I feel like state should be getting passed along here. Even when I use a for-loop and insert the state explicitly, it doesn't seem to re-render on changes.
getChatMarkup(cuid, message, status){
return(
<BasicChatComponent
key={cuid}
cuid={cuid}
message={message}
status={status}
/>
);
}
I attempted to insert some code line this:
renderChats(uuid){
let userState = this.state.userStates.find(user => {
return user.uuid === uuid;
});
const children = userState.chats.map((chat) => {
let ChatReactElement = this.getChatMarkup(chat.cuid, chat.message, chat.status);
if(chat.status.hidden)
this.setState({ hiddenChatRE: [ ...this.state.hiddenChatRE, ChatReactElement ] }); // <== save elements
return ChatReactElement;
});
ReactDOM.render(children, document.getElementById(`chats-${this.state.guid}-${uuid}`));
}
and later in my code:
this.state.hiddenChatRE.every(ReactElement => {
if(ReactElement.key == basicChats[chatIndex].cuid){
ReactElement.props = {
... //completely invalid code
}
}
});
The only response I see here is my ReactDOM.unmountComponentAtNode(); approach...
Can anyone point me in the right direction here?
Although perhaps I should be kicking myself, I read up on how React deals with keys on their components. So there's actually a fairly trivial answer here if anyone comes looking... Just call your render function again after you update the state.
In my case, something like:
this.setState(state =>({
...state,
userStates : state.userStates.map((userstate) => {
if(userstate.uuid == basicChats[chatIndex].uuid) return {
...userstate,
chats: userstate.chats.map((chat) => {
if(chat.cuid == basicChats[chatIndex].cuid){
//
return {
cuid: basicChats[chatIndex].cuid,
message: basicChats[chatIndex].message,
status: basicChats[chatIndex].status
}
}
else return chat;
})
}
else return userstate;
})
}));
and then, elsewhere in my example:
this.state.userStates.map((userstate) => {
this.renderChats(userstate.uuid);
});
Other than the fact that I'd recommend using indexed arrays for this example to cut complexity, this is the solution, and works. This is because even though it feels like you'd end up with duplicates (that was my intuition), the uid on the BasicChatComponent itself makes all the difference, letting react know to only re-render those specific elements.

Tiptap how to create a paragraph (p) on Shift-Enter, instead of a br?

Using TipTap, I'm trying to avoid adding a <br />, but create a <p></p> instead, with the focus inside that <p>|</p> when the user hit shift-Enter but I can't make it work.
Here's what I did so far:
new (class extends Extension {
keys () {
return {
'Shift-Enter' (state, dispatch, view) {
const { schema, tr } = view.state
const paragraph = schema.nodes.paragraph
console.log(tr.storedMarks)
const transaction = tr.deleteSelection().replaceSelectionWith(paragraph.create(), true).scrollIntoView()
view.dispatch(transaction)
return true
}
}
}
})()
How can I do this?
I don't know if this is still relevant but as I was looking for the same thing, I found two ways to make this work.
NOTE:
I'm using tiptap v2, if that's not a problem, then:
I overrode the HardBreak extension, since it's the one that use the Shift-Enter keybinding. It looks something like;
const CustomHardBreak = HardBreak.extend({
addKeyboardShortcuts() {
return {
"Mod-Enter": () => this.editor.commands.setHardBreak(),
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And used it like so;
editor = new Editor({
extensions: [
customNewline,
CustomHardBreak,
]
});
Use the default editor command createParagraphNear. E.g this.editor.commands.createParagraphNear()
I tried creating a custom extension from your code and ended up with something similar to the command above, i.e;
export const customNewline = Extension.create({
name: "newline",
priority: 1000, // Optional
addCommands() {
return {
addNewline:
() =>
({ state, dispatch }) => {
const { schema, tr } = state;
const paragraph = schema.nodes.paragraph;
const transaction = tr
.deleteSelection()
.replaceSelectionWith(paragraph.create(), true)
.scrollIntoView();
if (dispatch) dispatch(transaction);
return true;
},
};
},
addKeyboardShortcuts() {
return {
"Shift-Enter": () => this.editor.commands.addNewline(),
};
},
});
And added this as an extension in my editor instance.
PS:
They both work, almost exactly the same, I haven't found a difference yet. But there's somewhat of a 'catch' if you would call it that; Both these methods don't work on empty lines/nodes, a character has to be added before the cursor for it to work, any character, even a space.
In TipTap 2.0 I am able to use this custom extension:
const ShiftEnterCreateExtension = Extension.create({
addKeyboardShortcuts() {
return {
"Shift-Enter": ({ editor }) => {
editor.commands.enter();
return true;
},
};
},
});
To make shift + enter behave like enter.
In my case I actually wanted enter to do something different. So I use prosemirror events to set a ref flag on whether shift was pressed. Than I check that flag under the "Enter" keyboard event -- which could be triggered normally or through the shift + enter extension.

How to test a function that accepts a JSX.Element?

I have a type:
type button = JSX.Element | null;
and a function:
const getFirstButton = (buttonArray: button[], first: boolean) => {
if (first) {
return buttonArray[1];
}
return buttonArray.find(b => b !== null);
};
here is my test
test('getFirstButton', () => {
const buttons = // what goes here?
expect(getFirstButton(buttons, false)).toContain('button_1');
});
I need help with the second line on the test. How do i handle this?
Is this even possible?
Note: my test file is test.ts and I don't want to change it .tsx
JSX.Element is an object created by JSX syntax, i.e. React.createElement.
It can be:
const buttons = [null, null, <p/>, <div/>];
expect(getFirstButton(buttons, false)).toBe(buttons[2]);
Notice that JavaScript arrays are zero-based, so buttonArray[1] is possibly a mistake that will be detected when covering if (first) condition.

Resources