SeleniumHQ / selenium

A browser automation framework and ecosystem.
https://selenium.dev
Apache License 2.0
30.72k stars 8.19k forks source link

Webelements inside the vue.js's fragment are not visible to Selenium webdriver #9668

Closed spbrantan closed 3 weeks ago

spbrantan commented 3 years ago

🐛 Bug Report

Elements with attribute 'fragment' are not visible. Impossible to interact with child elements.

Root Cause vue.js uses a concept called Fragments to create more than one root node in a Vue component. This is done for Accessibility support which is necessary to allow assistive technology like screen readers to interpret web pages and applications, More about this in -> https://blog.logrocket.com/fragments-in-vue-js/

So, basically they are creating HTML tag that would not read as a node by the DOM and called it fragments.

Hence the webelements inside such fragments are not visible for Webdriver. This is more or less like a iframe inside html page where we do switchToIFrame and interact with it. Only that for Fragments there are no ways to make it visible to the webdriver.

PFA Screenshot of DOM snapshot containing the Fragment.

image

To Reproduce

https://jsfiddle.net/brantansp/bwdkx1tg/3/#&togetherjs=eM6k1p6Iaz

Detailed steps to reproduce the behavior:

System.out.println(webDriver.findElement(By.xpath("((//div[@class='nested-list']/div)[1]//div)[4]")).getText()); //returns null

Expected behavior

System.out.println(webDriver.findElement(By.xpath("((//div[@class='nested-list']/div)[1]//div)[4]")).getText()); //should return value

Test script or set of commands reproducing this issue

WebDriverManager.chromedriver().setup(); WebDriver webDriver = new ChromeDriver(); System.out.println(webDriver.findElement(By.xpath("((//div[@class='nested-list']/div)[1]//div)[4]")).getText());

Environment

OS: Windows 10 x64 Browser: Chrome Browser version: 89 Browser Driver version: ChromeDriver 89 Language Bindings version: Java 3.141.59

sethidden commented 3 years ago

The article you posted is using vue-fragment, but I'm getting the same issue using the vue-frag library.

Reading vue-frag's documentation I saw the below excerpt:

How does this work?

Vue associates vNodes with specific DOM references so once a component has mounted, the DOM nodes can be moved around and Vue will still be able to mutate them by reference. The Frag directive simply replaces the root element of a component in the DOM with it's children upon DOM insertion, and monkey-patches native properties like parentNode on the children to make Vue think they're still using the component root element.

Perhaps the monkey-patching behaves in a non-native way that makes Selenium interpret it wrong?

diemol commented 3 years ago

Can you please provide a complete code snippet so we can have a look? Without any 3rd party dependencies.

spbrantan commented 3 years ago
public static void main(String[] args) throws IOException {
        WebDriverManager.chromedriver().setup();
        WebDriver driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(20, TimeUnit.SECONDS);
        try {
            driver.navigate().to("https://oel-auto.pandodev.in");
            Thread.sleep(5000);
            driver.findElement(By.xpath("//label[text()='User name:']/preceding-sibling::input")).sendKeys("testuser@testuser.in");;
            driver.findElement(By.xpath("//label[text()='Password:']/preceding-sibling::input")).sendKeys("test@1234");;
            Thread.sleep(1000);
            driver.findElement(By.xpath("//button[contains(text(),'Log in')]")).click();;
            driver.findElement(By.xpath("//main[@class='container-fluid main-content']"));
            driver.navigate().to("https://oel-auto.pandodev.in/exim-master-indent-list");
            Thread.sleep(3000);
            driver.findElement(By.xpath("//span[@class='row-expand']")).click();
            Thread.sleep(2000);
            System.out.println(driver.findElement(By.xpath("(//div[contains(@Class,'grid-list')]//div)[4]/p")).getText()); //Blue-highlighted PFA. Returns text "FOB" as expected.
            System.out.println(driver.findElement(By.xpath("((//div[@class='nested-list']/div)[1]//div[contains(@class,'vendor')]//span)[last()]")).getText()); //Red-highlighted PFA. Returns text "HELLMAN LOGISTI..." but expected is "t320210730071752".
        }  catch (Exception e) {
            e.printStackTrace();
        } finally {
            driver.close();
            driver.quit();
        }
    }

In the above code, first sysout gives me expected output which is "FOB". But the second sysout gives me "HELLMAN LOGISTI..." but expected output is "t320210730071752".

If you check the xpath in DOM then it highlights the "t320210727141945" but since it is in vue fragment block selenium could not read that and instead returns the wrong text.

screenshot

spbrantan commented 3 years ago

Blue Highlighted Element is accessible to webdriver

Red-Highlighted Element is not accessible to webdriver since that is generated by vue Fragment.

@diemol

screenshot2

titusfortner commented 2 years ago

As discussed in #10157 Something in isDisplayed.js atom is returning false and I'm not sure where to start with it.

twalpole commented 2 years ago

Using the example from https://github.com/SeleniumHQ/selenium/issues/10157 the reason for this is the Vue fragment addons screwing with the parentNode property on the inserted template element. In that example the

<main fragment=​"13484d9094b" class=​"App---main---2ArFP">​…​</main>​ 

element has had parentNode modified to return an element which isn't actually in the render tree

<div fragment=​"13484d9094b">​</div>​

That alone wouldn't be so bad but the element returned as the parent doesn't have childNodes set

$0.parentNode.childNodes.length
0

So it's a 0 height, 0 width element with no children to overflow and therefore calculates as non-visible (positiveSize function returns false). Capybara is not affected by this because it had overridden the isDisplayed atom with it's own version which used parentElement to get the parent - which Vue hasn't monkeyed with

twalpole commented 2 years ago

This issue may only affect Vue 2 projects using vue-fragment - vue-frag appears to set childNodes on the container so the atom should correctly calculate visibility and Vue 3 appears to use a different method for implementing this functionality

titusfortner commented 2 years ago

So, the problem is with how Vue is messing with the DOM, and presumably users can update their code to avoid this issue.

That said, this is probably a good opportunity to discuss slimming down the atom and we can address the issue on the Selenium side as well.

jasonfreec commented 2 years ago

Add $(element).css({"overflow": "visible", "position": "static"}) will resolve this issue. isDisplayed.js use this style to check whether element exists.

GgStormer commented 2 years ago

Any updates on this issue? Should we expect this to be fixed sometime?

titusfortner commented 2 years ago

Technically this is a bug in vue-fragment which removes ChildNodes from the container. vue-frag and Vue 3 both do this correctly.

If Selenium changed the atom to use parentElement instead of parentNode it could also solve it, but that may or may not be the right fix. @AutomatedTester was looking into this, but I should have had this discussion on the other Issue, because that's where the reproducible use case is — https://github.com/SeleniumHQ/selenium/issues/10157

AutomatedTester commented 2 years ago

I am trying to reduce this down as there isn't reduced html show casing this... it might take some time unless someone with vue experience can give me a single page html with everything. Until then I will be trying to reduce down the html in #10157

GgStormer commented 2 years ago

Still no updates here?

titusfortner commented 2 years ago

@GgStormer please change/update your library or raise an issue with vue-fragment. This is not a defect in Selenium.

Additionally, the Selenium atom is not an easy thing to work with in its current state and it has some bigger limitations than this issue. Really it needs an overhaul and maybe a move away from Closure to TypeScript. This particular issue might be addressed as part of that move, or it might not, because moving from parentElement to parentNode might not be the right choice for it.

diemol commented 2 years ago

@GgStormer did you raise an issue with vue-fragment? Could you please link it here?

conor-f commented 1 year ago

Facing this too. I understand that this is not an issue within Selenium, but is there any hacky workaround that could be used here when changing vue-fragment isn't an option? The workaround @jasonfreec mentioned doesn't seem to work currently, maybe something has changed in the past year.

NikhilVerma commented 1 year ago

I forked the XPath libary and replaced the native implementation https://github.com/NikhilVerma/xpath-next this handles Vue 3 issues by ignoring the vue fragments and adds supports for shadow DOM elements

I also logged an issue here - https://github.com/vuejs/core/issues/8444

whimboo commented 1 year ago

Maybe this is related to #13132 (get-text atom)?

whimboo commented 11 months ago

One more question. Do these vue.js components use a closed Shadow DOM? If yes, I noticed https://github.com/SeleniumHQ/selenium/issues/13132 by the end of last week.

github-actions[bot] commented 10 months ago

This issue is looking for contributors.

Please comment below or reach out to us through our IRC/Slack/Matrix channels if you are interested.

github-actions[bot] commented 1 month ago

This issue is stale because it has been open 280 days with no activity. Remove stale label or comment or this will be closed in 14 days.

github-actions[bot] commented 3 weeks ago

This issue was closed because it has been stalled for 14 days with no activity.