Creation of Shadow Root Test Object during runtime

I have been trying to create test object during runtime which has shadow root. I have yet to do so, but while doing so, I have made the following observations: -
below i have explained the 3 different cases and validated ElementPresent, ElementVisible and Click result against each one of them
case 1 -


ElementPresent - TRUE
ElementVisible - TRUE
Click - Clicked successfully

case 2 -


ElementPresent - TRUE
ElementVisible - FALSE
Click - Element not interactable

case 3 -

ElementPresent - TRUE
ElementVisible - FALSE
Click - Element not interactable

Que 1 - Help me understand the difference between these 3 and why the case 1 works but not case 2 and case 3.
Que 2 - How to create test object with shadow root during runtime
I have referred multiple old post regarding OR and I believe you guys can surely help me on this . @kazurayam @Russ_Thomas @grylion54

1 Like

Is your target web page public to the Internet? Is it possible for others to get access to it and see its HTML source on their machine in hand?

Unless the target URL is provided publicly accessible, others would never be able to check if the XPath expressions of your Test Objects are valid or not. Unless checking the validity of XPath expressions, nobody can say anything about your questions.

Only you can think about it. Nobody else can.

1 Like

No its not publicly accessible. I have tried different website this time. URL - LetCode with Koushik and tried set text operation on Enter your first name textbox,
Case 1- Set text pass
Case 2 - invalid element state
Case 3 - invalid element state

1 Like

Then, what is your question?

my main question - How to create shadow root test object during runtime similar to what we have for normal object ( object without iframe or shadow root)

Firstly, makeTO() can only accept css (or xpath) selectors as strings. So you can’t use it to do what you want to do.

You can access elements inside a WebComponent (shadow dom) using JavaScript. For you, this means rewriting how you approach your test case.

The following JavaScript will access the <input id="fname" ...> shadow element:

document.querySelector("#open-shadow").shadowRoot.querySelector("input#fname") 

You will need to use WebUI.executeJavaScript() to run JavaScript in your Groovy code.

By the following Groovy function, you can create a Test Object that can select an HTML element inside a Shadow DOM.

static TestObject makeTOforElementInsideShadowDOM(TestObject parentObject, String css) {
	TestObject to = new TestObject(parentObject.toString() + "|" + css)
	to.addProperty("css", ConditionType.EQUALS, css)
	to.setParentObject(parentObject)
	to.setParentObjectShadowRoot(true)
	return to
}

I have created a demo project on the GitHub:

Try running the “Test Cases/official/Shadow DOM Test using Test Objects constructed runtime” to see how it works.

The sample code is based on the Katalon’s offical documentation:


Thank you for sharing the URL, but I did not work on this because the page is difficult for me — it requires me to register a username/password etc. I prefered the site Books because it is dum simple, no setups required.

I checked the source code of Katalon Studio v8.3.0. I found the following code:

in com.kms.katalon.core.webui.common.WebUiCommonHelper

    private static List<WebElement> doFindElementsInsideShadowDom(TestObject testObject, int timeOut,
            WebDriver webDriver, final String cssLocator, final TestObject parentObject, WebElement shadowRootElement)
            throws WebElementNotFoundException {
        Object shadowRootElementSandbox = ((JavascriptExecutor) webDriver)
                .executeScript("return arguments[0].shadowRoot;", shadowRootElement);
        if (shadowRootElementSandbox == null) {
            throw new StepFailedException(MessageFormat
                    .format(CoreWebuiMessageConstants.MSG_FAILED_WEB_ELEMENT_X_IS_NOT_SHADOW_ROOT, parentObject));
        }
        
        String filteredCssSelector = StringUtils.defaultString(cssLocator).replace("'", "\\\'");
        List<WebElement> webElements = (List<WebElement>) ((JavascriptExecutor) webDriver).executeScript(
                "return arguments[0].querySelectorAll('" + filteredCssSelector + "');", shadowRootElementSandbox);
        if (webElements != null && webElements.size() > 0) {
            logger.logDebug(MessageFormat.format(StringConstants.KW_LOG_INFO_FINDING_WEB_ELEMENT_W_ID_SUCCESS,
                    webElements.size(), testObject.getObjectId(), cssLocator, timeOut));
        }
        return webElements;
    }

As the code tells, it uses WebUI.executeJavaScript() to get access to the elements inside Shadow DOM. This supports what @Russ_Thomas wrote is right.

Thanks @Russ_Thomas @kazurayam for your quick response. I have tried below code and worked for me.

        TestObject ParentObj = new TestObject('button_Shadow_Banner')
        ParentObj.setSelectorMethod(SelectorMethod.XPATH)
        ParentObj.setSelectorValue(SelectorMethod.XPATH, '//div[@id = \'gpc-banner-container\']')
        TestObject tObj = new TestObject('button_CloseBanner')
        tObj.setSelectorMethod(SelectorMethod.BASIC)
        tObj.addProperty('tag', ConditionType.EQUALS, 'button',true)
        tObj.addProperty('id', ConditionType.EQUALS, 'CloseBanner',true)
        tObj.setParentObject(ParentObj)
        tObj.setParentObjectShadowRoot(true)
        WebUI.click(tObj)

Please let me know if there is something wrong with the above code.

along with this I have one additional doubt. I am just curious to know the difference between below 2 code snippets. if you guys have any idea share your comments.

ParentObj.setSelectorValue(SelectorMethod.XPATH, '//div[@id = \'shadow-banner-container\']')

ParentObj.addProperty('xpath', ConditionType.EQUALS, '//div[@id = \'gpc-banner-container\']')

Neither of ‘tag’ and ‘id’ would work. You are supposed to use ‘css’.

Why?

See the source of com.kms.katalon.core.webui.common.WebUiCommonHelper#doFindElementsInsideShadowDom() method.

        ...
        String filteredCssSelector = StringUtils.defaultString(cssLocator).replace("'", "\\\'");
        List<WebElement> webElements = (List<WebElement>) ((JavascriptExecutor) webDriver).executeScript(
                "return arguments[0].querySelectorAll('" + filteredCssSelector + "');", shadowRootElementSandbox);
        ...
       

The javascript code arguments[0].querySelectorAll(xxxxx) demands xxxxx to be a “css” selector. No ‘id’, no ‘tag’, no ‘xpath’ will work.

Please read the source code to find the answer for yourself:

I would interprete your question as “What is the selectorCollection variable in a TestObject instance?, How does the selectorCollection variable function?”

To answer to it, I looked at Katalon’s GUI. In the Test Object definition window, I can find 2 labels “Selection Method” and “Selected Locator”.

If you read the source of TestObject class, you would find that a pair of “Selection Method” and “Selected Locator” makes an entry of Map<SelectionMethod, String> selectorCollection.

How the selectorCollection is used?

If you switch the “SelectionMethod” from “Attribute” to something else (for example, “XPath”) then the GUI shows the Selected Locator empty.

Here, I guess, the GUI is rendering the content of selectorColloection variable.

So, the selectorCollection is just a secondarry storage to help the GUI rendering this view.

Conclusion: To create a TestObject with acting locator, you need to call addProperty() method. The setSelectorValue method is not necessary.

By the way, sometimes you may want to view the internal state of a TestObject. Unfortunately, TestObject.toString() method does not provide good information.

The following custom keyword may help:

Quote from the above post:

WebUI.comment("#prettyPrint()\n" + tObj.prettyPrint())

This emits this output:

{
    "cachedWebElement": null,
    "objectId": "Object Repository/Page_CURA Healthcare Service/a_Make Appointment_BASIC",
    "parentObjectShadowRoot": false,
    "properties": [
        {
            "active": true,
            "value": "a",
            "condition": "EQUALS",
            "name": "tag"
        },
        {
            "active": true,
            "value": "btn-make-appointment",
            "condition": "EQUALS",
            "name": "id"
        },
        {
            "active": false,
            "value": "./profile.php#login",
            "condition": "EQUALS",
            "name": "href"
        },
        {
            "active": false,
            "value": "btn btn-dark btn-lg",
            "condition": "EQUALS",
            "name": "class"
        },
        {
            "active": false,
            "value": "Make Appointment",
            "condition": "EQUALS",
            "name": "text"
        },
        {
            "active": false,
            "value": "id(\"btn-make-appointment\")",
            "condition": "EQUALS",
            "name": "xpath"
        }
    ],
    "imagePath": null,
    "selectorMethod": "BASIC",
    "selectorCollection": {
        "BASIC": "//a[@id = 'btn-make-appointment']"
    },
    "useRelativeImagePath": false,
    "parentObject": null,
    "xpaths": [

    ],
    "activeProperties": [
        {
            "active": true,
            "value": "a",
            "condition": "EQUALS",
            "name": "tag"
        },
        {
            "active": true,
            "value": "btn-make-appointment",
            "condition": "EQUALS",
            "name": "id"
        }
    ],
    "activeXpaths": [

    ]
}