Using CSS Selector to locate a child element within a found element

3,516 views
Skip to first unread message

Mat Walker

unread,
Jan 28, 2015, 12:07:26 AM1/28/15
to seleniu...@googlegroups.com
Mildly driving me up the wall this :-)

I recent decided to bite the bullet and go CSS Selectors rather than XPath.  I'm currently building a framework that enables tests to execute across a wide range of browsers & devices and found some inconsistencies with XPath  - especially with IE9.  After reading tonnes of arguments about CSS and XPath I decided to go CSS as browsers generically know CSS whereas XPath is added in and has issues as a result.


I'm happy with CSS not handling multi-level predicates (IE. //div[class='aa' && ./span[class='bb']]  - Select div element with class attribute of aa and who has a child span element with attribute class of bb) ) without a bit of programming (which I've done OK) but this current issue is baffling me.

I have an IWebElement that I identified earlier and i now want to find a child element  in the found element.  IE;

<a>
    <b>
        <c>
        </c>
        <d>
           <c>
           </c>
        </d>
    </b>
</a>

X = Driver.FindElement(By.CssSelector("b");  // Great - X is now the 'b' element.
Y = X.FindElements(By.CssSelector("c"));  // I want this to match on just the 'c' element that is a direct child of 'b', *not* the descendant that is in 'd'.

I want Y to be a single element - the top 'c' element.  However, it matches on both 'c' elements.  So, is there a way to say 'Find me the child elements (NOT descendants) that match'.  This is a Selenium issue rather than CSS as CSS Selectors are usually applied to the whole DOM but Selenium has the ability to apply the Selector only to elements that are in the element the Find is being performed from.

In XPath I would say:-

X = Driver.FindElement(By.XPath("//b"); // Great - X is now the 'b' element.
Y = X.FindElements(By.XPath("./c"); // Also great - Y is a collection of 1 element, the child 'c'.  So, how to do this in CssS!?\

This just HAS to be possible!? :-)

Mat







Andreas Tolfsen

unread,
Jan 28, 2015, 12:34:29 AM1/28/15
to seleniu...@googlegroups.com
On 28 Jan 2015, at 07:07, Mat Walker <mathieu...@technologyonecorp.com> wrote:
> I have an IWebElement that I identified earlier and i now want to find a child element in the found element. IE;
>
> <a>
> <b>
> <c>
> </c>
> <d>
> <c>
> </c>
> </d>
> </b>
> </a>
>
> X = Driver.FindElement(By.CssSelector("b"); // Great - X is now the 'b' element.
> Y = X.FindElements(By.CssSelector("c")); // I want this to match on just the 'c' element that is a direct child of 'b', *not* the descendant that is in 'd’.

I believe you’re looking for the selector b > c, which is the
direct descendant selector (>).

This is all described in the CSS Selectors Level 4 spec:
http://dev.w3.org/csswg/selectors-4/#child-combinators

When you call findElements on a web element I don’t think
you can limit it to only the element’s direct descendants.

Mat Walker

unread,
Jan 28, 2015, 12:54:22 AM1/28/15
to seleniu...@googlegroups.com
I am. I'm looking for the direct descendt - IE. child.  But i want it to be the child of the 'X' element.  I can do the Selector from the DOM root (as you say 'b>c'), but I want it to be from the 'X' element.

Something like this:-  Y = X.FindElements(By.CssSelector(">c")) // This gets thrown out.... :-(

I just tried ":root>c" as that would make sense; in the context of the found 'X' element, it IS the root of the pseudo DOM.....

Mat Walker

unread,
Jan 28, 2015, 12:57:08 AM1/28/15
to seleniu...@googlegroups.com
On paper, the following should work.

X = Driver.FindElement(By.CssSelector("b");  // Great - X is now the 'b' element. 
Y = X.FindElements(By.CssSelector(":root>c"));

The second CSS Selector should match the single 'c' element as, in the context of 'X' 'b' is the root element....

Can anyone see what I'm getting at here? :-)

Mat

David

unread,
Jan 28, 2015, 1:47:00 AM1/28/15
to seleniu...@googlegroups.com
Do you know what "d" is or will the "c" under "d" vary in path or that "d" will vary? If "d" is a static pattern you can figure out, perhaps you can write a CSS selector that means "give me the c's that are not a child of d", which should get you what you want from DOM subset X (or "b").

On a related note, is XPath that troublesome for you? An alternative could be using a mix of CSS and XPath. Find X/b with CSS, then find Y /c by XPath since XPath has better DOM structure querying support. e.g.:

X = Driver.FindElement(By.CssSelector("b");  // Great - X is now the 'b' element.
Y = X.FindElements(By.XPath("./c"); // Also great - Y is a collection of 1 element, the child 'c'.

And last question, given your particular problem scenario, aren't you better off finding Y/c by leveraging b directly and not store b as X to then find as a child?

Meaning just directly do  Y = Driver.FindElements(By.CssSelector("b > c");

just my two cents

David

unread,
Jan 28, 2015, 2:09:17 AM1/28/15
to seleniu...@googlegroups.com
By the way, are you sure what we can do with Selenium finding elements within elements can't be done to the DOM in a browser? See these examples:



those examples start with document.getElementById(), but you could start it differently like this as well: document.querySelector("#myDIV").querySelectorAll(".example")

I'd say the Selenium issue you see, you may see it with the above too when tested in browser developer/javascript console or Firebug.

Mat Walker

unread,
Jan 28, 2015, 4:39:42 PM1/28/15
to seleniu...@googlegroups.com
Thanks David

Bit of a background to this....:-)

The UI endpoints of the AUT's have zero DfT (Design for Test) built in and getting the teams (well, managers :-)) to get the required test hooks and handles in - and maintain them - would be akin to beating my head against a proverbial brick wall.  In addition, the test team while being great testers are very non-technical.  The AUT's are highly cross-platform and the UI's render on IE8/9/10/11, Chrome, iPhone's, iPad's and Android (Chrome again) devices.  So that is canvas I've got for building the framework - they have done the usual record-playback route and currently realising it doesn't deliver.


The framework is based on use of an Action/Keyword hybrid system using Selenium to drive the UI endpoint. The AUT's all use in-house devved UI controls (grids, dropdowns, textboxes et al....) which can be highly nested etc. So, the framework is based on function oriented Object Maps that contain the control page related find-logic.  The Controls are controlled by libraries with their own internal mapping to their elements other Controls etc. This enables the framework to run against any of the targeted UI endpoints as any 'custom' control actions (IE. Appium left/right swipe or scroll is a bit iffy at the moment so I've used some Javascript to workaround that issue; when fixed I can just take the 'hack' out of the library and tests wont be affected).  Therefore the Find-Logic is very parent-child oriented; there is find logic to the top-level control, then the child control is located and so on.  While is sounds complex, it is actually very simple and produces pretty rock solid and robust tests that the non-techy testers can write/maintain (Databases, Filedrops and SMTP are the other test endpoints).

I had been using XPath as the find logic as it worked cross-browser/device and, even v1.0, able to handle any of the find-logic needed.  However, I had an issue with some find-logic not working in IE9 and during digging found that XPath under IE is a bit of a Selenium weak-point (3rd party old XPath interpreter etc...).  I've been hearing the loud noises from the CSS Selector advocates and after reading the arguments for/against CSSS decided to move to CSSS.  I'd sorted out CSSS's descending predicate issue (where you can select an element based on something to do with its children/descendants) with a recursive filter-loop but this one looks like it could be a job-stopper that forces me back to XPath.

The example is a very simplified view of the issue.  In wordy speak, I want to be able to select a child element under an already identified element.  In XPath, something like this...

X = driver.FindElement(By.XPath("//div[attirb='xyz']");
Y = X.FindElement(By.XPath("/div[attrib='abc']");

Where the actual find logic is irrelevant, In the second line I really just want the FindElement to apply the find logic from the 'X' element - so in that case the 'DOM root element is the 'X' element and the find-logic is being applied from that point.  In CSS Selector world I would expect to be able to say (neither works);

X = driver.FindElement(By.CssSelector("div[attrib=xyz]");
Y = X.FindElements(By.CssSelector(":root>div[attrib=abc]");
or
Y = X.FindElements(By.CssSelector(">div[attrib=abc]");

In the first instance, the ':root' is the 'X' element, so FindElements returns only the child div's that have an attrib atribute containing 'abc'.
In the second instance, there is nothing before the '>' and so it is implied that we are looking for a child div under the current element.

I spent most of last night trying various ways but have come to the conclusion that it really is not possible with CSS Selectors to do this.  There are workarounds like remembering the find-logic for each node down the tree or mixing XPath and CSS but they are not really robust and add quite a bit of complexity (Breaking the 'KISS' law - Keep It Simple Stupid :-)).

The thing that mildly confuses me is that the javascript 'querySelectorAll()' method does look - reading the w3c - to be exactly what is needed; "querySelectorAll() method returns a collection of an element's child elements that match a specified CSS selector(s)." So what does the Selenium driver use for <IWebElement>.FindElements(ByCssSelector) as it returns the descendants that match the selector NOT just the child elements'?  If I have a selector "div[attrib=xyz" it returns all the element's descendants that match, not just the child elements...  I've not actually tried it yet to see if 'child' really does means the direct children of the element or really means 'descendants'....

Mat Walker

unread,
Jan 28, 2015, 4:44:54 PM1/28/15
to seleniu...@googlegroups.com
This explains my exact issue very well - much better than I have!

David

unread,
Jan 29, 2015, 4:06:47 PM1/29/15
to seleniu...@googlegroups.com
Interesting situation you have there. I too use the parent-child and even preceding/following sibling navigation schemes to locate elements off a base element when they are grouped together for our site's UI too. I found it simpler that way than to use separate similar locators for each element. But in doing so, I often had to go with XPath.

Sadly, as much as the CSS selector advocates advocate CSS, the CSS3 spec is limiting compared to XPath on what you can do. Here are some features I wish CSS has that XPath has:

* element/node text contains matching, official support not pseudo support
* navigating up parent/ancestor DOM tree
* navigating to preceding siblings, CSS seems to only allow following siblings
* CSS equivalent to this type of XPath: (//xpathValueThatMatchesMultipleElements)[n] - where I want the nth match of the result in parenthesis
* better element index selection, nth-child() and nth-of-type() don't work as well as XPath, see previous bullet plus the general //xpathNode[n] syntax

You might just have to stick with XPath, or have a mix of it with CSS, if you really like to keep CSS usage.

Mat Walker

unread,
Jun 29, 2015, 11:24:42 PM6/29/15
to seleniu...@googlegroups.com
(Sorry for delay replying, changed employer...)

Yes, sticking with 100% XPath.  Thought about (and indeed played with) using a mix but that kinda defeats the purpose of going to CSS :-)

I 100% agree with you on the feature 'wish list'.  However, we (testers) have to remember that CSS is primarily for styling rather than test and so if there is no requirement for a feature from a styling perspective it wont happen.

 
Reply all
Reply to author
Forward
0 new messages