skip to Main Content

I just found something very weird. Its about this css selector

div:nth-child(3) a

On X I have a container div selected in my DevTools

enter image description here

Now, with the css selector div:nth-child(3) a I want to find the link holding the 4h text

I copied the HTML from X to produce a demo here. You can see there that it works as expected, I see the correct href value!

Although this sounds super easy, which it is. Simplified the HTML looks like this:

    <div>
        <div><a href="/elonmusk">@elonmusk<a/></div>
        <div>·</div>
        <div>
           <a href="/status/234324234234234">3h</div>
        </div>
    </div>

However, when I try to reproduce this on X.com it doesn’t work

To reproduce, first select the container element
enter image description here

Now if I perform that query I get
enter image description here

It selects the first div. It can be fix when I query it as follows

$0.querySelector('div:nth-child(3)').querySelector('a')

Why does it work in my demo and not on X?

2

Answers


  1. That’s because your selector doesn’t ask for the <a> to be a direct descendant and on X, your <a> element is a descendant of another ancestor <div> that’s also the third child of its parent.

    The same situation can be reproduced with the following tree:

    const parent = document.querySelector(".parent");
    const target = parent.querySelector("div:nth-child(3) a");
    
    console.log(target);
    div:nth-child(3) { color: red }
    div:not(:nth-child(3)) { color: black }
    <section>
      <div>a</div>
      <div>b</div>
      <div class="grand-parent"><!-- this also matches div::nth-child(3) -->
        c
        <div>c1</div>
        <div>c2</div>
        <div class="parent">c3
          <div>c3a <a>another a</a></div>
          <div>c3b</div>
          <div>c3d <a>target</a></div>
        </div>
      </div>
    </section>

    You probably wanted "div:nth-child(3) > a", which would match your element in X, but the best might be to use the :scope pseudo-selector which will set the root of the selector to the element on which you called the query from:

    const parent = document.querySelector(".parent");
    const target = parent.querySelector(":scope div:nth-child(3) a");
    
    console.log(target);
    <section>
      <div>a</div>
      <div>b</div>
      <div class="grand-parent"><!-- this also matches div::nth-child(3) -->
        c
        <div>c1</div>
        <div>c2</div>
        <div class="parent">c3
          <div>c3a <a>another a</a></div>
          <div>c3b</div>
          <div>c3d <a>target</a></div>
        </div>
      </div>
    </section>
    Login or Signup to reply.
  2. The HTML subtree you looked at is

    <div> $0
      <div>
        <a> $1
          <div>
            <span>...</span>
          </div>
        </a>
      </div>
      <div>
        <span>·</span>
      </div>
      <div> $2
        <a> $3
          <time>...</time>
        </a>
      </div>
    </div>
    

    Now
    $0.querySelectorAll("div:nth-child(3) a") selects all a elements that

    • are descendants of $0 and
    • have an ancestor div that is the third among its siblings. This ancestor can be outside of the subtree you are looking at!

    The result of this query contains both $3 (with $2 as the ancestor) and $1 (with an ancestor outside of this subtree).

    And $0.querySelector("div:nth-child(3) a") returns only the first of these, that is, $1.

    What you want is correctly expressed as $0.querySelector("div:nth-child(3)").querySelector("a").

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search