1

After years of testing one global DOM for end-to-end testing, I'm finding it very difficult, if not impossible, to test web components that use slots. Before I explain the problem, I want to say that I cannot change the generated markup to improve things as they are.

<wc-1 attributes-etc="">
  <wc-2 attributes-etc="">
    <slot>
      <wc-3 attributes-etc="">
        <slot>
       ...eventually get to an input...
         <input type="text" name="firstName" />

There are a buttload of nested web components from some kind of form builder, and there are also plenty of slots used. The web components have attributes but the slots never do, so I use the web component name for querying.

document.querSelector('wc-1')
  .shadowRoot.querySelector('wc-2')
  .shadowRoot.querySelector('slot')

// Yields <slot>...</slot>

All fine to this point and Cypress has a .shadow() command I used, but I'm testing with just devtools here to see all the properties the slot has.

document.querSelector('wc-1')
  .shadowRoot.querySelector('wc-2')
  .shadowRoot.querySelector('slot')
  .shadowRoot

// Yields "null".
// I don't know how to get to the .lightDOM? of wc-2?

Any property I try ends up being null or having 0 elements in the returned value. Using other front-end tools and the global DOM, I can always cy.get('div[data-testid="the-nested-element-i-want"]').type('important words') in one command.

So my main question is: How do people test these things once web components start piling up? Or don't do this and just test the web components in isolation/unit tests since it's so hard to query the nested shadow DOMs?

The main goal is to eventually get to a form input to cy.get('input[name"firstName"]').type('John'). Can someone give me the chained docuement.querySelector() command to get to <wc-3> in my example?

Alex Finnarn
  • 342
  • 1
  • 11

2 Answers2

2

The answer involves assignedNodes(): https://developer.mozilla.org/en-US/docs/Web/API/HTMLSlotElement/assignedNodes

The assignedNodes() property of the HTMLSlotElement interface returns a sequence of the nodes assigned to this slot...

It made no difference for me to use that vs. assignedElements(). So, all you have to do is use that method once you've queried down to the slot you need. For my example, the answer is:

const wc-3 = document.querySelector('wc-1').shadowRoot
  .querySelector('wc-2').shadowRoot
  .querySelector('slot').assignedNodes()
  .map((el) => el.shadowRoot)[0]

And then you can keep going down the chain...I know I only have one un-named slot, so that's why I grab it from the returned .map().

Props to this Q&A for pointing me on the right direction: Web components: How to work with children?

Dharman
  • 30,962
  • 25
  • 85
  • 135
Alex Finnarn
  • 342
  • 1
  • 11
-1

There will be no DOM content in your <slot>, as there is no DOM content moved to slots.

lightDOM content is reflected in slots, but remains invisible! in lightDOM.
(that is why you also style slotted content in lightDOM)

From the docs:

, . ' ; .

So to test if something is "in" a slot

  • you need to check for slot=? attributes on lightDOM elements
  • and double check if that <slot name=? > actually exists in shadowDOM

Or vice versa

Or hook into the slotchange Event, but that is not Testing

pseudo code:

for the vice-versa approach; can contain errors.. its pseudo code..

function processDOMnode( node ){
  if (node.shadowRoot){

    // query shadowDOM
    let slotnames = [...node.shadowRoot.querySelectorAll("slot")].map(s=>s.name);

    // query lightDOM
    slotnames.forEach( name =>{
      let content = node.querySelectorAll(`[slot="${name}"]`);
      console.log( "slot:" , name , "content:" , content );
    });

    // maybe do something with slotnames in lightDOM that do NOT exist in shadowDOM

    // dive deeper
    this.shadowRooot.children.forEach(shadownode => processDOMnode(shadownode));
  }
}

Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • Thanks for the explanation of "lightDOM", a term I've not heard before. So are you saying there is no way for me to query for `` and its shadow root/DOM in my example? I want to essentially "pierce through" the elements and keep going down the trees, but I'm stuck at the slot level. – Alex Finnarn Feb 12 '21 at 18:18
  • see the link to slotted content; you don't 'pierce' into a slot, the content is in the elements lightDOM. Takes some time to get used to, it is a new concept (and thus all developers who are afraid to properly learn new concepts despise shadowDOM) – Danny '365CSI' Engelman Feb 12 '21 at 18:44
  • I looked at the link, but I'm still not getting how to answer my question...Before web components, you could do what I need to do simply with the DOM and keep all the logic you need elsewhere *cough* VirtualDOM *cough*...but looks like it not simple to answer the question starting from the document root? I need to eventually "type" into a text input once I get through 2 billion more shadows and slots. – Alex Finnarn Feb 12 '21 at 19:29
  • VirtualDOM is something completely different This is about real DOM, and now we have lightDOM and shadowDOM. As I said; a completely new concept. And if you don't grasp that concept you don't know what to do. Take time to learn what lightDOM is; only then can you solve your problem. And once you see the light **[pun intended]** you will notice *querying* both light- and shadow- DOM is exactly what you always did. The important part is: **slotted content remains IN lightDOM** .. or do like most developers: *This is way too complex; it must be bad; I don't want to use it; Web Components are bad* – Danny '365CSI' Engelman Feb 12 '21 at 19:59
  • And yes, you have to write some code (I estimate maybe 15 LOC) to "dive" into shadowRoots. If you know what recursion is and how to "dive" or "walk" the DOM you are halfway done. – Danny '365CSI' Engelman Feb 12 '21 at 20:02
  • Is there a simple working example somewhere? –  Feb 12 '21 at 20:52
  • Cheers, nice bit of information to get into. –  Feb 13 '21 at 10:34