Using CSS selectors to get first child from a nested element - html

I am trying to understand CSS selectors better and am fiddling around with Google/Gmail. When you go to Google's home page and enter "gmail", it will automatically present you with search results for that term. I want to write a CSS selector that will find the first one (that is, the link to Gmail, since it should always be the first result). The HTML for these results looks like:
<div class="srg">
<div class="g">
<h3 class="r">
Gmail - Google
...
Based on what I could gather from the W3schools CSS docs, it seems like I want the first <a> child of a class named r, or:
h3.r a:first-child
However, the tool I'm using doesn't recognize this as the first link. So I ask: is this a correct selector for the Gmail (first) link, or did I go awry somewhere?

Well, the anchor element you're referring to is the only child of the h3.r parent.
So :first-child, :last-child and :only-child would all apply.
A simple h3.r > a (child selector) or h3.r a (descendant selector) should suffice, assuming it's unique in the document.
Your selector – h3.r a:first-child – should, technically speaking, work as well.
Based on the image above, an attribute selector may also work:
h3.r a[data-href="https://mail.google.com/"]
More information: https://www.w3.org/TR/css3-selectors/#selectors

Within Geb, you can also use
`$("h3.r").find("a")[0]
to select the first child.

Using :first-of-type is very similar to :nth-child, but there is a critical difference: it is less specific.
In the example above, if we had used p:nth-child(1), nothing would happen because the paragraph is not the first child of its parent (the <article>). This reveals the power of :first-of-type: it targets a particular type of element in a particular arrangement with relation to similar siblings, not all siblings.
Reference: https://css-tricks.com/almanac/selectors/f/first-of-type/

Related

Is it possible to select elements that do not have a child of a certain type?

I'm trying to select <a> elements that are not the parents of <img> elements. (Note: if it's relevant some of the anchors I want to select are childless.) I tried this:
a > :not(img) {}
and this:
a:not(> img) {}
but neither of them seem to work. How would I accomplish this in CSS?
There is a spec, currently in draft, for a :has() pseudo-class. No browser supports it yet. If the spec is someday approved and implemented, you'd be able to do this:
a:not(:has(img)) {
// Styles
}
The MDN page says that :has would never work in stylesheets, only in JavaScript; but in saying that, it links to a section of the spec about a "dynamic selector profile" that apparently no longer exists.
I think the browser vendors typically have a problem with implementing CSS features that require knowledge of the DOM that only exists after the selected element is created, so I don't know if we should get our hopes up for this. Someone who follows the mailing lists or is generally smarter than me might offer a better prognosis.
Unfortunately, no. You'd need to use jQuery.
You could do some kind of workaround using CSS:
Assign a class to links that do not have child elements that are images and use that class to style the links as normal (e.g. a.class{color: red})
Assign a class to links that do have an image child element, and use a:not(.class){} to change their color
Reason: There is no parent selector in CSS. See:
Is there a CSS parent selector?, CSS Parent/Ancestor Selector

Can't use children with :not pseudo selector

I'm having trouble using some relatively simple CSS selectors using :not. Namely, the following selector is giving me an error:
a:not(.ebook_document *)
I am trying to get all <a> elements that are not children of the element with class ebook_document. This also fails:
a:not(.ebook_document > *)
As well as this:
a:not(.ebook_document, *)
Putting the selectors on their own, not in a :not section works fine. What have I done wrong?
:not only takes a simple selector. (For now, CSS 4 expands that to a selector list.)
Plus, https://developer.mozilla.org/en/docs/Web/CSS/:not -
This selector only applies to one element; you cannot use it to exclude all ancestors. For instance, body :not(table) a will still apply to links inside of a table, since will match with the :not() part of the selector."
What you want is not possible using :not.
You can only go about it the other way around - format all "normal" links, and then apply different formatting for the links inside the target element(s) using .ebook_document a { ... }
So that means rather than not applying styles to those links in the first place, you might need to overwrite the styles you don't like for those links again.
(Or use initial/all to actually reset styles, but browser support for that is still lacking AFAIK.)
Hmm shouldn't it just be a:not(.ebook_document)? I didn't test it but it seems that that should reference all the a tags that don't have a .ebook_document tag.

How can I find a certain element that comes right after another element with Capybara?

I am trying to use learn Capybara for a scraping task I have. I have heretofore only used it for testing. There are a million things I want to learn, but at the very basic part of it I want to know how to find a certain element that is a sibling and comes after another element that I am able to find?
Take a page like this:
<body>
<h3>Name1</h3>
<table>
...
</table>
<h3>Name2</h3>
<table>
...
</table>
<h3>Name3</h3>
<table>
...
</table>
</body>
I want to return the <table> element that comes after the <h3> element having text Name2.
I know how to loop through elements with all, and I know how to use first instead of find, but I don't know how to "Find the first element X following specific element Y".
CSS
In CSS you could use a sibling selector. These allow you to select sibling elements; or those at the same nesting level and with the same parent element. There are two types of sibling selectors:
'+' the adjacent sibling selector
'~' the general sibling selector (adjacent or non-adjacent siblings)
It's usually ideal to avoid matching by text whenever possible. (This makes your specs easier to write and also means that textual changes are less likely to break your specs.) In that ideal world your 'h3' elements might have IDs on them and we could just:
find('h3#name2+table')
However, in your example they don't have IDs so let's connect a couple of queries to scope to what we want.
find('h3', text: 'Name2').find('+table')
First we found the correct 'h3' element (using text matching) and then with that query as a basis we request the sibling 'table' element.
You may also note that if you used the general sibling selector '~' you would get an ambiguous element error; Capybara found all the 'table' elements rather than just the adjacent one.
XPath
Sometimes XPath is actually easier to use if you're really forced to do textual element selection. So you could instead:
find(:xpath, "//h3[contains(text(),'Name2')]/following-sibling::table")
More difficult to read but does the same thing. First find an 'h3' with text 'Name2' and then select it's sibling 'table' element.
Capybara now has sibling and ancestor finders (~>=2.15.0)
[Updated 2018/09]
As #trueunlessfalse commented out, the original answer using sibling finder will return amgibuous match error if there are multiple matches. So, please consider to use xpath in that case..
Following code using xpath will return what OP wanted
find('h3', text: 'Name2').first(:xpath, './following-sibling::table')
Following code will return what OP wanted => ambiguous match error
find('h3', text: 'Name2').sibling('table')
You can check the detail here.
https://www.rubydoc.info/github/jnicklas/capybara/Capybara/Node/Finders:sibling
Using
find('h3#name2+table')
didnt work for me. I needed to add :css then it worked
find(:css, 'h3#name2+table')

CSS first-child and last-child don't work when used on the same element

I just noticed that CSS :first-child and :last-child don't work when used on the same element. Only one is used. I'm wondering why is that and if there is any workaround to that in CSS? It seems like CSS bug to me - first element can be also last element.
I couldn't find anything in Google on that subject. Each tutorial assumes that there will be at least 2 elements.
I'm looking for something like: "first child is also last child" selector. Does it exist?
I'm wondering why is that and if there is any workaround to that in CSS? It seems like CSS bug to me - first element can be also last element.
It's not a bug. It's how the cascade works: if an element is both the first child and the last child, then if you have :first-child and :last-child in separate rules and they both match the same element, then anything in the later declared or more specific rule will override the other.
You can find an example of this behavior here, with the border-radius shorthand property, and workarounds that include using the component properties instead of the shorthand, as well as specifying a separate rule altogether using one of the following selectors...
I'm looking for something like: "first child is also last child" selector. Does it exist?
Literally, you would chain both pseudo-classes like this, which works fine:
:first-child:last-child
However there exists a special pseudo-class that acts the same way:
:only-child
The main difference (in fact, the only difference) between these two selectors is specificity: the first one is more specific as it contains one additional pseudo-class. There is no difference even in browser support, as :last-child and :only-child are both supported by the exact same versions of each browser.
I realise I'm massively late to this, but you can also use is CSS's :first-of-type and :last-of-type. For example:
<blockquote>
<p>Some text you want to quote</p>
</blockquote>
This CSS will add quotes to the paragraph:
blockquote{
quotes: "“" "”" "‘" "’";
}
blockquote p:first-of-type:before{
content:open-quote;
}
blockquote p:last-of-type:after{
content:close-quote;
}

:first-letter CSS - restricting it to first letter of an article in a template

I'm having trouble targetting just the first letter in this example here: http://jsfiddle.net/gB94x/
Obviously the "read more" isn't supposed to have the drop cap too.
The HTML can't be changed, it's part of a fixed template.
Thanks!
.od_article>p references all p tags that are immediate children of .od_article and therein lies your problem. You only want the first p tag. Change .od_article>p:first-letter to .od_article>p:first-of-type:first-letter as in the updated fiddle below.
http://jsfiddle.net/gB94x/1/
UPDATE:
I don't believe IE7 supports the :first-of-type unfortunately. I'm not sure if it supports :first-child either but for reference, :first-child matches if the element it's applied to is the first child of its parent.
I can't think of a workaround off the top of my head but looking at the HTML the p you want to change the first letter for has the class CaptionPic1. If the first paragraph will always have this class, just use that, so .CaptionPic1:first-letter.
Your selector:
.od_article>p:first-letter
Is trying to apply styles to the first letter of every p child whose parent has the class od_article.
You can either use this to get the first p element:
.od_article>p:first-of-type:first-letter
Or, if you need to support older browsers like IE7 and IE8, which don't understand CSS3 pseudo-classes such as :first-of-type, and you know that the first child is always an h1 and the first p always follows that h1 in your HTML structure, you can use :first-child with a sibling selector like this:
.od_article>h1:first-child+p:first-letter
jsFiddle preview