SeleniumHQ / selenium-google-code-issue-archive

Archive, please see main selenium repo
https://github.com/seleniumhq/selenium
345 stars 195 forks source link

Click events are sometimes being delivered to the wrong element #5404

Closed lukeis closed 8 years ago

lukeis commented 8 years ago

Originally reported on Google Code with ID 5404

I run a test against an ADF web application, which test does open a menu and find and
click menu items. It clicks a menu item and after waiting for a page to load goes back,
and clicks another menu item, and so on. About 50% of tests fails, when the browser
gets redirected to the wrong page. That is, the click event by some black magic is
being sent to the wrong menu item. Menu items are located by xpath like //td[text()
= "Menuitem1"], and there are no two menu items with the same text. Moreover, when
I print found element's text before calling .click(), it is always correct. Nevertheless,
the destination sometimes is that of a different menu item.

What steps will reproduce the problem?
1.
2.
3.

What is the expected output? What do you see instead?

Selenium version: 2.31.0
OS: Windows 7 x86
Browser: Mozilla Firefox
Browser version: 18.0.2

Please provide any additional information below. A sample reduced test
case, or a public URL that demonstrates the problem will intrigue our merry
band of Open Source developers far more than nothing at all: they'll be far
more likely to look at your problem if you make it easy for them!

Reported by discordia.primula on 2013-03-27 02:15:48

lukeis commented 8 years ago
You appear to be missing some information in your report, as in where I can go to or
what page I can use to reproduce the same scenario.

Reported by arran.huxtable on 2013-03-27 10:13:57

lukeis commented 8 years ago
I don't have either, sorry. In the meantime, however, it looks like the cause of this
behaviour is the dropdown menu's animation. Between calling .click() and webdriver
actually clicks at the element's position, its position changes. Seems like that it
is an inherent defect of the webdriver (or is it possible to check if the intended
click location still belongs to the element to be clicked?)

I was able to work around this issue using the following predicate (need more testing
though):

private class ElementMotionlessPredicate implements Predicate<WebDriver> {
    private By by;
    private Point loc = new Point(-1, 1);

    ElementMotionlessPredicate(By by) {
        this.by = by;
    }

    @Override
    public boolean apply(WebDriver webDriver) {
        try {
            WebElement element = driver.findElement(by);
            if (element.isDisplayed()) {
                Point newLoc = element.getLocation();
                if (newLoc.equals(loc)) {
                    return true;
                }
                loc = newLoc;
            }
        } catch (NoSuchElementException e) {
            ;
        } catch (StaleElementReferenceException e) {
            ;
        }
        return false;
    }
};

Reported by discordia.primula on 2013-03-29 01:53:03

lukeis commented 8 years ago
Here are the HTML page and test class for it, exposing the behaviour described above.

1.html:

<html>
    <head>
        <style>
            #t {
                position: absolute;
                left: 200px;
                top: 10px;
            }
            td {
                text-decoration: underline;
                color: blue;
                cursor: pointer;
            }
        </style>
        <script language="javascript">
            var offset = 10;
            function refresh() {
                var t = document.getElementById('t');
                var top = parseInt(
                    document.defaultView
                            .getComputedStyle(t)
                            .top
                            .replace('px', '')
                    );
                top += offset;
                if (top > 300)
                    offset = -10;
                else if (top < 10)
                    offset = 10;
                t.style.top = top + 'px';
            }
            function onload() {
                for (var i = 1; i < 10; i++) {
                    var e = document.getElementById('t'+i);
                    e.addEventListener('click', (function (i) {
                        return function () {
                            document.title = i;
                        }
                    })(i), false);
                }
                setInterval(refresh, 100);
            }
        </script>
    </head>
    <body onload="onload()">
        <table id="t">
            <tr><td id="t1">Menuitem 1</td></tr>
            <tr><td id="t2">Menuitem 2</td></tr>
            <tr><td id="t3">Menuitem 3</td></tr>
            <tr><td id="t4">Menuitem 4</td></tr>
            <tr><td id="t5">Menuitem 5</td></tr>
            <tr><td id="t6">Menuitem 6</td></tr>
            <tr><td id="t7">Menuitem 7</td></tr>
            <tr><td id="t8">Menuitem 8</td></tr>
            <tr><td id="t9">Menuitem 9</td></tr>
        </table>
    </body>
</html>

Test1.java:

import com.google.common.base.Predicate;

import java.util.concurrent.TimeUnit;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.support.ui.WebDriverWait;

public class Test1 {
    WebDriver driver;

    public Test1() {
    }

    @Before
    public void setUp() throws Exception {
        driver = new FirefoxDriver();
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        driver.get("http://127.0.0.1:8000/1.html");
    }

    @After
    public void tearDown() throws Exception {
    }

    private void waitForTitle(final String title) {
        new WebDriverWait(driver, 5).until(new Predicate<WebDriver>() {
            @Override
            public boolean apply(WebDriver driver) {
                return driver.getTitle().startsWith(title);
            }
        });
    }

    @Test
    public void test1() {
        for (int i = 9; i >= 1; i--) {
            driver.findElement(By.id("t" + i)).click();
            System.out.println("clicked " + i);
            waitForTitle(Integer.toString(i));
        }
    }
}

Reported by discordia.primula on 2013-03-29 03:13:49

lukeis commented 8 years ago

Reported by barancev on 2013-03-29 20:00:08

lukeis commented 8 years ago
Yes, Selenium determines location and size of at element to be clicked, and emulates
click in the specified position on the page. If the element is actively moving the
click can miss it. It is expected behaviour.

Using a predicate like ElementMotionlessPredicate from the comment 2 is a correct way
to deal with such elements.

Reported by barancev on 2013-05-13 21:39:35

lukeis commented 8 years ago

Reported by luke.semerau on 2015-09-17 18:17:02