Cypress how to select option containing text - cypress

In Cypress, the select('option-text') command will only work if there is an exact match. How can I tell it to select an option that contains the text instead ?

Through aliasing you can make it work:
cy.get('select')
.find('option')
.contains('YOUR TEXT')
.as('selectOption')
.then( () => {
cy.get('select')
.select(`${this.selectOption.text()}`)
})

A slightly improved variant of Can Akgun user is as following (bonus adding as cypress command):
/**
* Select an option containing part of string in its text body
* {String} elementSelector
* {String} optionTextPart
*/
Cypress.Commands.add('selectOptionContaining', (elementSelector, optionTextPart) => {
cy.get(elementSelector)
.find('option')
.contains(optionTextPart)
.then($option => {
cy.get(elementSelector).select($option.text());
});
});
In this way we do not need global variables.

I haven't found anything like that in the api so far, nor in the github issues.
So I resorted to adding a custom command in my project:
// cypress/support/commands.ts
Cypress.Commands.add('selectContaining', {prevSubject: 'element'}, (subject, text, options) => {
return cy.wrap(subject).contains('option', text, options).then(
option => cy.get('select#report-selection').select(option.text().trim())
);
});
// cypress/support/commands_type.ts
declare namespace Cypress {
interface Chainable<Subject = any> {
requestApi(method: HttpMethod, url: string, body?: RequestBody): Chainable<Cypress.Response>;
selectContaining(text: string | string[], options?: Partial<SelectOptions>): Chainable<Subject>;
}
}

Related

Use Cypress Variable From Command

I have a utility command in cypress:
Cypress.Commands.add('generateDummyText', (length: number, delay?: number) => {
const wordLength = 5;
const lorem = new LoremIpsum({
wordsPerSentence: {
max: wordLength,
min: wordLength,
},
words: ['word1', 'word2', 'word3', 'word4', 'word5'],
});
const words = lorem.generateWords(Math.ceil(length / wordLength));
cy.wrap(words).as('dummyText');
});
it('test', () => {
// Generate dummy text
cy.generateDummyText(280);
// I want to type the "dummyText variable here"
cy.get('myinput').type()
});
How can this be done? I don't need to use a custom cypress command, but I'd prefer to use one. Looking forward to your reply!
You can access it just by using:
cy.type(this.dummyText)
Another option is just to use faker to generate the random words for you.
Add the faker module to you project, then you can just do:
const faker = require("faker");
.type(faker.random.words(5));
instead of using a custom command for it
You can get the value either by alias:
cy.generateDummyText(280);
cy.get("#dummyText").then(dummyText => {
cy.get('myinput').type(this.dummyText)
})
or directly by the command return value:
cy.generateDummyText(280).then(dummyText => {
cy.get('myinput').type(this.dummyText)
})

Cypress: The 'task' event has not been registered in the plugins file. You must register it before using cy.task()

I am writing the end-to-end tests using Cypress for my web application. In my tests, I am trying to create a task, https://docs.cypress.io/api/commands/task. But it is throwing an error. Here is what I did.
I declared a task in the plugins/index.js file as follow.
module.exports = (on) => {
on("task", {
setTestId(id) {
testId = id;
return null;
},
getTestId() {
return testId;
}
});
};
Then I use the task in the test as follow.
cy.task('setTestId', 7654321);
When I run the tests, I am getting the following error.
The 'task' event has not been registered in the plugins file. You must register it before using cy.task()
As you can see, I tried this solution as well, Cypress task fails and complains that task event has not been registered in the plugins file. It did not work either. What is wrong with my code and how can I fix it?
You're missing some formatting (":" and "=>" and such). Try this:
module.exports = (on) => {
on("task", {
setTestId: (id) => {
testId = id;
return null;
},
getTestId: () => {
return testId;
}
});
};
Set the testId as you did:
cy.task('setTestId', 7654321);
And when you want to retrieve the testId, use:
cy.task('getTestId').then((testId) => {
cy.log(testId)
});

Cypress - extract URL info

I have this URL :
https://www.acme.com/book/passengers?id=h1c7cafc-5457-4564-af9d-2599c6a37dde&hash=7EPbMqFFQu8T5R3AQr1GCw&gtmsearchtype=City+Break
and want to store these values :
id=h1c7cafc-5457-4564-af9d-2599c6a37dde
hash=7EPbMqFFQu8T5R3AQr1GCw
for use in a later test.
How do I extract these values from the URL? I am using Cypress. Thanks.
Please follow the following steps and that's all there is to it.
You can put this snippet into before() hooks of your spec file and you can access them wherever you want.
cy.location().then(fullUrl => {
let pathName = fullUrl.pathname
let arr = pathName.split('?');
let arrayValues = arr[1].split('&');
cy.log(arrayValues[0]);
cy.log(arrayValues[1]);
cy.log(arrayValues[2]);
})
In case anyone needs the correct answer, use the cy.location('search') to extract the search part of the location data.
Then for convenience, convert it to a javascript object with key/value pairs for each item.
Finally, store it in a Cypress alias to use later in the test.
cy.location('search')
.then(search=> {
const searchValues = search.split('?')[1].split('&')
// yields: [
// id=h1c7cafc-5457-4564-af9d-2599c6a37dde,
// hash=7EPbMqFFQu8T5R3AQr1GCw,
// gtmsearchtype=City+Break
// ]
const searchMap = searchValues.reduce((acc,item) => {
const [key,value] = item.split('=')
acc[key] = value.replace('+', ' ')
return acc
}, {})
// yields: {
// id: "h1c7cafc-5457-4564-af9d-2599c6a37dde",
// hash: "7EPbMqFFQu8T5R3AQr1GCw",
// gtmsearchtype: "City Break"
// }
cy.wrap(searchMap).as('searchMap')
})
Using #Srinu Kodi's answer I got it working changing ...then(fullUrl => ... to
...then((fullUrl) => ...

Says Jasmine spy is not being called, but I can see that its being called

I can't figure out why Jasmine is claiming that the function I'm spying on isn't being called, especially since it is logging in buildLinksObj when called through and not calling when I remove .and.callThrough() I feel like I've written similar code a bunch of times before without any problem. I'm using Jasmine 2.9
The error message I'm getting is:
1) addToLinks should call buildLinksObj if its given an object with children
it should add the personalized links to PageApp.meta.analytics.links
Expected spy buildLinksObj to have been called.
at UserContext.<anonymous> (http://localhost:9877webpack:///tests/specs/common/FetchPersonalContent.spec.js:854:0 <- tests/app-mcom.js:104553:48)
Here's the except of my code:
FetchPersonalContent.js
const buildLinksObj = (responseObj = {}, targetObj, PageApp) => {
console.log('it logs in buildLinksObj') // This is logging!
}
const addToLinks = (responseArr, personalizedLinks) => {
responseArr.forEach((media) => {
const type = media.type;
const typeObj = media[type];
buildLinksObj(typeObj, personalizedLinks, PageApp);
if (typeObj && typeObj.children) {
console.log('has children!')
console.log('typeObj.children is: ', typeObj.children);
typeObj.children.forEach((child) => {
console.log('has a child')
buildLinksObj(child, personalizedLinks, PageApp);
console.log('buildLinksObj was definitely called. what the heck?')
});
}
});
}
export {buildLinksObj, addToLinks, FetchPersonalContent as default,
};
FetchPersonalContent.spec.js
import * as FetchPersonalContent from '../../../src/FetchPersonalContent'; // my path is definitely correct
describe('it should add the personalized links to PageApp.meta.analytics.links', () => {
it('addToLinks should call buildLinksObj if its given an object with children ', () => {
spyOn(FetchPersonalContent, 'buildLinksObj').and.callThrough();
FetchPersonalContent.addToLinks([{
"personalId": 30718,
"type": "carousel",
"carousel": {}
}], {});
expect(FetchPersonalContent.buildLinksObj).toHaveBeenCalled();
});
});
I'd really appreciate any help!
I have a feeling FetchPersonalContent.buildLinksObj in the spec file is not pointing to the same instance as buildLinksObj in the FetchPersonalContent.js file.
Why is export {FetchPersonalContent as default} required? I am assuming you have shared the complete content of FetchPersonalContent.js in your question.
Possible solutions:
You can try removing FetchPersonalContent from the export statement.
Or
Instead of
export {buildLinksObj, addToLinks, FetchPersonalContent as default,
};
You can directly export the constants in FetchPersonalContent.js file.

BooleanField with FunctionField change number to Boolean

I have question and I'm sure it will help other developers.
I have field "is_active" which is Boolean in my API side but it return 0 or 1 and not TRUE or FALSE.
I want to use <FunctionField/> to wrap the <BooleanField/> but it didn't work. Someone can help please.
This is my code:
<FunctionField source="is_active" label="is_active" render={(record) => record.is_active ? true : false}>
<BooleanField/>
</FunctionField>
The column is still blank.
Thanks.
I think you misunderstood the FunctionField component. It renders the result of the render prop. What you are trying to achieve is:
<FunctionField source="is_active" label="is_active" render={(record,source) =>
<BooleanField record={{...record,is_active:!!record.is_active}} source={source}/>}/>
But this is not very nice. Better is to wrap your dataProvider/restClient and ensure the data is a boolean.
// In FixMyDataFeature.js
export default restClient => (type, resource, params) => restClient(type,resource,params).then(response=>
if(resource === 'Resource_with_numeric_is_active_field`){
return {
data: mutateIsActiveFieldToBoolean(response.data)
}
}
else{
return response;
}
);
And call it with Admin:
<Admin dataProvider={FixMyDataFeature(dataProvider)}... />
Here is my solution: (you can import it and use instead of BooleanField)
import React from 'react';
import { BooleanField } from "react-admin";
export const BooleanNumField = ({ record = {}, source}) => {
let theRecord = {...record};
theRecord[source + 'Num'] = !!parseInt(record[source]);
return <BooleanField record={theRecord} source={source + 'Num'} />
}
I had an issue where the in a DB table there was a field called disabled but in the Admin was a bit confusing setting disabled to false to actually enable something.
Based on 'Dennie de Lange' answer, I have created a Typescript generic BooleanOppositeField and BooleanOppositeInput. Putting here hoping may help someone:
import { BooleanField, BooleanInput, FunctionField } from 'react-admin';
interface IProps {
label: string;
source: string;
}
/**
* Usually called using:
* <BooleanOppositeField label="Enabled" source="disabled"/>
*/
export const BooleanOppositeField = (props: IProps) => {
return (
<FunctionField {...props} render={(record: any | undefined, source: string | undefined) =>
<BooleanField source="enabled" record={{ ...record, enabled: !(record![source!]) }} />}
/>
);
};
/**
* Usually called using:
* <BooleanOppositeInput label="Enabled" source="disabled" />
*/
export const BooleanOppositeInput = (props: IProps) => {
return (
<BooleanInput format={(v: boolean) => !v} parse={(v: boolean) => !v} {...props} />
)
}
And you can use it by:
<BooleanOppositeField label="Enabled" source="disabled"/>
or
<BooleanOppositeInput label="Enabled" source="disabled" />
Note: I liked more this solution, than the recommended by Dennie

Resources