Slots as a Container Element

Slot in a Web Component

A slot is a placeholder inside a web component that you can fill with your own markup, which lets you create separate DOM trees and present them together. See MDN: slots documentation.

Let's look at an example of how to use slots in a page object. Here's a modal with account fields. We're using the Salesforce application as an example, but this could be any application that has an account object with certain fields.

Inspected slot

As you can see from the screenshot, the <records-record-layout-item> component has an element <slot name="inputField"></slot> that can contain other components like input, lookup, or picklist depending on the account field type.

For the field that we selected for inspection, the content of the slot is a <records-record-layout-lookup slot="inputField">...</records-record-layout-lookup> element that has other nested HTML elements.

Container element for a slot

Now let's add two elements to our page objects that represent the account field: one basic "inputFieldSlot" element that points to the slot itself and one container element with a name of "inputFieldContainer":

{
    "elements": [
        {
            "type": "container",
            "name": "inputFieldContainer",
            "public": true,
            "selector": {
                "css": "[slot='inputField']"
            }
        }
    ],
    "shadow" : {
        "elements": [
            {
                "name": "inputFieldSlot",
                "public": true,
                "selector": {
                    "css": "slot[name='inputField']"
                }
            }
        ]
    }
}

The first thing to notice is that the content of the slot isn't inside the shadow root, but the slot itself is. If a web developer puts something inside a <slot>, it becomes the fallback or “default” content. The browser shows it if there’s no corresponding filler in light DOM. Even though our component has a shadow root, the container element isn't crossing the shadow boundary and isn't inside the "shadow" element. The slot element is hidden inside the shadow root though.

Selector for a slot content versus the slot's selector

It's important to remember that when we create an element that points to a slot itself ("inputFieldSlot"), its internal elements can only be found using assignedNodes(), which isn't supported in UI Automation tools. All we can do with such an element is to call basic actions like checking its presence or getting its attributes and text.

In our example, we want to point to another component inside the slot and then use the methods of the component's page object. So the selector of our container element should point to the content of the slot, not to the slot itself.

For our example, here's how we would write a UI test, where field is an instance of our page object from the JSON snippet above:

// correct
RecordLayoutLookup lookup = field.getInputFieldContainer(RecordLayoutLookup.class);
lookup.doSomething();

// incorrect. might work to get element properties, but not recommended
BasicElement fieldSlot = field.getInputFieldSlot();
assertEquals(fieldSlot.getText(), "*AccountName");
// correct
const lookup = await field.getInputFieldContainer(RecordLayoutLookup);
await lookup.doSomething();

// incorrect. might work to get element properties, but not recommended
const fieldSlot = await field.getInputFieldSlot();
expect(await fieldSlot.getText()).toBe('*AccountName');

Summary