The application has list of items in a table and items have [id=item[0].setupCost, id=item[1].setupCost, id=item[2].setupCost] etc.
There's a functionality to add items also, so the index keeps on increasing.
I want to get input field regardless of using magic numbers. For eg (cy.get('[id=item[some_regex].setupCost]')
The regex that applies is \[id="item\[\d+\].setupCost\].
Enclose the regex in forward slashes, not quotes.
cy.get(/\[id="item\[\d+\].setupCost\]/)
But don't use it
This syntax is undocumented - it works (in Cypress v9.5.0) but it only returns one result.
So if you want to count your items, this will fail
cy.get(/\[id="item\[\d+\].setupCost\]/)
.its('length')
.should('eq', 3) // ❌
Partial attribute selector
If you want to use partial attribute selectors, this is strongest as it includes .setupCost
Ref Find the element with id that starts with "local-" and ends with "-remote"
cy.get('[id^="item"][id$=".setupCost"]') // use "starts-with" and "ends-with" selectors
This succeeds with the count test
cy.get('[id^="item"][id$=".setupCost"]')
.its('length')
.should('eq', 3) // ✅
In case you need another answer:
You can use a regex if you add a jQuery extension.
It works because Cypress uses jQuery internally, and you can modify selector behavior by extending jQuery.
it('finds multiple ids by regex', () => {
const $ = Cypress.$
$.extend(
$.expr[':'], {
idRegex: function(a, i, m) {
const regex = new RegExp(m[3], 'i');
return regex.test(a.getAttribute('id'));
}
}
)
const regex = 'item\\[\\d+\\].setupCost'
cy.get(`input:idRegex(${regex})`)
.its('length')
.should('eq', 3) // ✅
})
Put the jQuery extension in /cypress/support/index.js to use it globally.
Note in the regex we have to double-escaped because we pass in a string not a regex.
Instead of Regex you can use ^ with your selector. This denotes the start of the text. So you can write:
cy.get('[id^="item"]')
Now if you want to access any particular element you can use eq like this:
cy.get('[id^="item"]').eq(0) //gets id=item[0].setupCost
cy.get('[id^="item"]').eq(1) //gets id=item[1].setupCost
If you want to loop over each element you can use each()
cy.get('[id^="item"]').each(($ele) => {
cy.wrap($ele)
})
You could create a custom Cypress command that would insert the index for you. Below, I'm assuming the id you're referencing is the ID of the element and is a standard CSS selector
Cypress.Commands.add('getItemById', (index) => {
return cy.get(`#item\\[${index}\\].setupCost`)
});
cy.getItemById(0)...
Related
Is there a way to get an element by its content(a word it contains?)
For example, get all the elements with the letter "F," and put it in a array of elements
I highly recommand you to use jQuery for these kind of DOM elements searching.
Then you can use this:
var foos = $("div:contains('foo')" )
will make an array with all divs containing the word 'foo'.
One fairly easy way is to select the elements you're interested in and then use 'filter' to look at the innerText. You can make this case insensitive with toLowerCase
var result = $('div').filter( (i,e) => e.innerText.toLowerCase().indexOf("f")>-1);
console.log("Items with 'F':",result.length);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>Forest</div>
<div>Fortnight</div>
<div>Trees</div>
<div>Africa</div>
The simpler way is using :contains('F') as a selector - but that is always case sensitive (which may be fine for your case).
You can use :contains as a selector. For example, to filter all divs of a special class that also contains your text, you can use $("div.myclass:contains('searched text')")
I think you can "bruteforce" it by iterating all DOM items. e.g.:
let arrayDom = Array.from(document.getElementsByTagName("*"));
arrayDom.forEach(element => {
if (element.innerHTML.contains('F')){
// Do something
}
})
Is there a way to do a wildcard element name match using querySelector or querySelectorAll?
The XML document I'm trying to parse is basically a flat list of properties
I need to find elements that have certain strings in their names.
I see support for wildcards in attribute queries but not for the elements themselves.
Any solution except going back to using the apparently deprecated XPath (IE9 dropped it) is acceptable.
[id^='someId'] will match all ids starting with someId.
[id$='someId'] will match all ids ending with someId.
[id*='someId'] will match all ids containing someId.
If you're looking for the name attribute just substitute id with name.
If you're talking about the tag name of the element I don't believe there is a way using querySelector
I was messing/musing on one-liners involving querySelector() & ended up here, & have a possible answer to the OP question using tag names & querySelector(), with credits to #JaredMcAteer for answering MY question, aka have RegEx-like matches with querySelector() in vanilla Javascript
Hoping the following will be useful & fit the OP's needs or everyone else's:
// basically, of before:
var youtubeDiv = document.querySelector('iframe[src="http://www.youtube.com/embed/Jk5lTqQzoKA"]')
// after
var youtubeDiv = document.querySelector('iframe[src^="http://www.youtube.com"]');
// or even, for my needs
var youtubeDiv = document.querySelector('iframe[src*="youtube"]');
Then, we can, for example, get the src stuff, etc ...
console.log(youtubeDiv.src);
//> "http://www.youtube.com/embed/Jk5lTqQzoKA"
console.debug(youtubeDiv);
//> (...)
Set the tagName as an explicit attribute:
for(var i=0,els=document.querySelectorAll('*'); i<els.length;
els[i].setAttribute('tagName',els[i++].tagName) );
I needed this myself, for an XML Document, with Nested Tags ending in _Sequence. See JaredMcAteer answer for more details.
document.querySelectorAll('[tagName$="_Sequence"]')
I didn't say it would be pretty :)
PS: I would recommend to use tag_name over tagName, so you do not run into interferences when reading 'computer generated', implicit DOM attributes.
I just wrote this short script; seems to work.
/**
* Find all the elements with a tagName that matches.
* #param {RegExp} regEx regular expression to match against tagName
* #returns {Array} elements in the DOM that match
*/
function getAllTagMatches(regEx) {
return Array.prototype.slice.call(document.querySelectorAll('*')).filter(function (el) {
return el.tagName.match(regEx);
});
}
getAllTagMatches(/^di/i); // Returns an array of all elements that begin with "di", eg "div"
i'm looking for regex + not + multiClass selector, and this is what I got.
Hope this help someone looking for same thing!
// contain abc class
"div[class*='abc']"
// contain exact abc class
"div[class~='abc']"
// contain exact abc & def(case-insensitively)
"div[class~='abc'][class*='DeF'i]"
// contain exact abc but not def(case-insensitively)
"div[class~='abc']:not([class*='DeF'i])"
css selector doc: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
simple test: https://codepen.io/BIgiCrab/pen/BadjbZe
I liked many of the answers above, but I prefer my queries run only on classes/IDs so they don't have to iterate over every element. This is a combination of code from both #bigiCrab and #JaredMcAteer
// class exactly matches abc
const exactAbc = document.querySelectorAll("[class='abc']")
// class begins with abc
const startsAbc = document.querySelectorAll("[class^='abc']")
// class contains abc
const containsAbc = document.querySelectorAll("[class*='abc']")
// class contains white-space separated word exactly matching abc
const wordAbc = document.querySelectorAll("[class~='abc']")
// class ends with abc
const endsAbc = document.querySelectorAll("[class$='abc']")
Substitute "class" with "id" or "href" to get other matches. Read the article linked below for further examples.
Reference:
CSS attribute selectors on MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
There is a way by saying what is is not. Just make the not something it never will be. A good css selector reference:
https://www.w3schools.com/cssref/css_selectors.asp which shows the :not selector as follows:
:not(selector) :not(p) Selects every element that is not a <p> element
Here is an example: a div followed by something (anything but a z tag)
div > :not(z){
border:1px solid pink;
}
<body class="some-class xxx-term-i-want-to-extract some-other-class">
How do I extract "term-i-want-to-extract" from the body class, knowing that this always starts with "xxx-"?
Edit: The question is about getting "term-i-want-to-extract" and storing it inside a variable, for instance. Not about removing it from the body classes or returning the body classes without it. Thanks for your answers!
You can use the classList to get a list of all the classes the body tag has, and then use the $.map function to go over them and return only the relevant ones (after removed the xxx- string).
var classes = $.map($('body')[0].classList, function(cls, i) {
if (cls.indexOf('xxx-') === 0) {
return cls.replace('xxx-', '');
}
})
console.log(classes);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<body class="some-class xxx-term-i-want-to-extract some-other-class">
Here's a JSfiddle
// get the jQuery element
var $body = $('body');
// get the contents of the `class` attribute
// and use String#split to divide it into an
// array of individual class strings
var classes = $body.attr('class').split(' ');
// Array#find is used to find the first class
// that starts with the selected pattern
// and then the pattern is sliced off of the
// full class string
var xxx = classes.find(function (className) {
return className.startsWith('xxx-');
}).slice(4);
xxx === 'term-i-want-to-extract'; // true
Array#find and String#startsWith are part of the ES2015 specification and therefore may not be available on all platforms. You may then need to use polyfills in older browsers like IE:
startsWith polyfill
find polyfill
Is there a way to do a wildcard element name match using querySelector or querySelectorAll?
The XML document I'm trying to parse is basically a flat list of properties
I need to find elements that have certain strings in their names.
I see support for wildcards in attribute queries but not for the elements themselves.
Any solution except going back to using the apparently deprecated XPath (IE9 dropped it) is acceptable.
[id^='someId'] will match all ids starting with someId.
[id$='someId'] will match all ids ending with someId.
[id*='someId'] will match all ids containing someId.
If you're looking for the name attribute just substitute id with name.
If you're talking about the tag name of the element I don't believe there is a way using querySelector
I was messing/musing on one-liners involving querySelector() & ended up here, & have a possible answer to the OP question using tag names & querySelector(), with credits to #JaredMcAteer for answering MY question, aka have RegEx-like matches with querySelector() in vanilla Javascript
Hoping the following will be useful & fit the OP's needs or everyone else's:
// basically, of before:
var youtubeDiv = document.querySelector('iframe[src="http://www.youtube.com/embed/Jk5lTqQzoKA"]')
// after
var youtubeDiv = document.querySelector('iframe[src^="http://www.youtube.com"]');
// or even, for my needs
var youtubeDiv = document.querySelector('iframe[src*="youtube"]');
Then, we can, for example, get the src stuff, etc ...
console.log(youtubeDiv.src);
//> "http://www.youtube.com/embed/Jk5lTqQzoKA"
console.debug(youtubeDiv);
//> (...)
Set the tagName as an explicit attribute:
for(var i=0,els=document.querySelectorAll('*'); i<els.length;
els[i].setAttribute('tagName',els[i++].tagName) );
I needed this myself, for an XML Document, with Nested Tags ending in _Sequence. See JaredMcAteer answer for more details.
document.querySelectorAll('[tagName$="_Sequence"]')
I didn't say it would be pretty :)
PS: I would recommend to use tag_name over tagName, so you do not run into interferences when reading 'computer generated', implicit DOM attributes.
I just wrote this short script; seems to work.
/**
* Find all the elements with a tagName that matches.
* #param {RegExp} regEx regular expression to match against tagName
* #returns {Array} elements in the DOM that match
*/
function getAllTagMatches(regEx) {
return Array.prototype.slice.call(document.querySelectorAll('*')).filter(function (el) {
return el.tagName.match(regEx);
});
}
getAllTagMatches(/^di/i); // Returns an array of all elements that begin with "di", eg "div"
i'm looking for regex + not + multiClass selector, and this is what I got.
Hope this help someone looking for same thing!
// contain abc class
"div[class*='abc']"
// contain exact abc class
"div[class~='abc']"
// contain exact abc & def(case-insensitively)
"div[class~='abc'][class*='DeF'i]"
// contain exact abc but not def(case-insensitively)
"div[class~='abc']:not([class*='DeF'i])"
css selector doc: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
simple test: https://codepen.io/BIgiCrab/pen/BadjbZe
I liked many of the answers above, but I prefer my queries run only on classes/IDs so they don't have to iterate over every element. This is a combination of code from both #bigiCrab and #JaredMcAteer
// class exactly matches abc
const exactAbc = document.querySelectorAll("[class='abc']")
// class begins with abc
const startsAbc = document.querySelectorAll("[class^='abc']")
// class contains abc
const containsAbc = document.querySelectorAll("[class*='abc']")
// class contains white-space separated word exactly matching abc
const wordAbc = document.querySelectorAll("[class~='abc']")
// class ends with abc
const endsAbc = document.querySelectorAll("[class$='abc']")
Substitute "class" with "id" or "href" to get other matches. Read the article linked below for further examples.
Reference:
CSS attribute selectors on MDN: https://developer.mozilla.org/en-US/docs/Web/CSS/Attribute_selectors
There is a way by saying what is is not. Just make the not something it never will be. A good css selector reference:
https://www.w3schools.com/cssref/css_selectors.asp which shows the :not selector as follows:
:not(selector) :not(p) Selects every element that is not a <p> element
Here is an example: a div followed by something (anything but a z tag)
div > :not(z){
border:1px solid pink;
}
I have an html table where one of the columns contains <span>s with comma-delimited data. I want to come up with a jQuery selector that returns all of the rows (<tr>s are preferable, but the <span>s will work for now) where one of the comma-delimited tokens <span> in a span tag matches a supplied string.
I started with something like the following:
$('td.col_8 span:contains("duck")')
which will get me all spans in a particular column containing the word 'duck'. However, it could also match <span>fox, mallard-duck</span>. Since 'duck' is not a unique token in that span, I wouldn't want that included in the match.
Is there a way to narrow my result set so I'm only including results where there's an exact match to a particular token in a column-delimited list?
(I'm using jQuery 1.2.3)
This is where the beauty of jQuery expressions come in.
You can add your own custom selector. I'm going to call it tag in this case
jQuery.expr[":"].tag = function(elem, index, match, nodeList) {
var tags = $(elem).text().replace(/(?:\ +)?,(?:\ +)?/g, ",").split(",");
return tags.indexOf(match[3]) > -1;
}
This will check each previously matched element against this selector. Fist it flattens the , seperations (so something like "firstTag , onotherer, badly , spaced, tag" will work). jQuery already takes care of parsing the selector and passes it in as match. match[3] is what you're interested in. With the following usage match[3] will be 'duck'
$('td.col_8 span:tag("duck")')
the return line will return true of false based on the tag being in the list.
I think this should do it
$('td.col_8 span').filter(function() {
return $(this).text().match(/^duck$/);
});
Now you just need to define the regex that you need, or implement a string split and array search to return a boolean value to indicate if the <span> should be included.
Going off of Russ Cam's answer, here's what I came up with:
$('td.col_8 span').filter(function() {
return $.inArray('quiet',$(this).html().split(', ')) !== -1;
});
Of course, this assumes that the list will always be delimited with ,[space], but it should work for my immediate use case.