I need to interact with an online application written by a third-party developer, part of which is written in React. To interact with the site I'm driving Firefox via Selenium and Python. I can use traditional CSS selectors to interact with most of the site, but the parts written in React have random autogenerated class names that change every time the devs rebuild their app. There are no IDs found on any DOM elements except for the root node.
How can I get a unique CSS Selector for a DOM element, given a specific React Component in the virtual DOM?
For instance, by using React Develper Tools I see this tree:
Context.Provider
├─ Context.Provider
│ └─ gs
├─ Context.Provider
│ └─ styled.div
│ └─ styled.button
└─Iv
How can I get a unique CSS Selector for the DOM element that represents the Context.Provider > Context.Provider > styled.div > styled.button
React Component?
I have seen the React Element Selector Query NodeJS library, but I need something that can run in Python, or can be interpreted from within Python and Selenium.
Here is example code from the app I'm targeting:
<div id="root">
<div >
<div >
<button >Foo</button>
<button >Bar</button>
</div>
<div >
<div >
<div >
... Lots more ...
</div>
</div>
</div>
</div>
</div>
Other than the button element text, that is actual production HTML. Note that this is not deliberate obfuscation, rather this is not uncommon for React apps.
CodePudding user response:
You can check for id, if ids are given to each tag then can be accessed very easily.
for instance
<div id="renderingID">
This is some div rendering some content
<div>
You can access the div by #renderingID
this will give you the required properties.
CodePudding user response:
Usually, there is a pattern when a class name is transformed, like underscore. So you can split using that and check for given classname. For example, we are looking for .content
under .body
. So element will have className as body_content_<hash>
. So, we can split using and check if it has content
Note: This approach is based on string manipulation. So if the classname has same delimiter, it will fail.
document
.getElementById('btnSearch')
.addEventListener('click', () => {
const searchClass = document.getElementById('txtSearchId').value
const elements = searchElements(searchClass)
highlightElement(searchClass, elements)
})
function searchElements(className) {
return document.querySelectorAll(`[class*="${className}"]`)
}
function highlightElement(className, elements) {
document
.querySelectorAll('.border')
.forEach((element) => {
element.classList.remove('border')
})
Array.from(elements).filter((element) => {
const classes = element.className.split(/[ _]/);
const matched = classes.includes(className);
if (matched) {
element.classList.add('border')
}
return matched
})
}
.border {
border: 1px dashed gray;
}
div {
margin: 5px;
}
<div>
<input type="text" id="txtSearchId" />
<button id="btnSearch">Search and Highlight </button>
</div>
<div class='body_1nu21'>
<div class='body_content_2nasiy'>
<div class='body_card_21dfl'>test</div>
<div class='body_card-test_2345a'>test</div>
<div class='body_card-blue_9hysh8'>test</div>
<div class='body_card_7hdsg'>test</div>
</div>
</div>
CodePudding user response:
You probably have to go by structure / innerText in this case. For example:
#root > div > div > button => Foo
#root > div > div > button button => Bar