13

我将 Selenium WebDriver 与 java.awt.Robot 结合使用,以更好地模拟用户与我们的 Web 应用程序的交互。是的,我知道这可能是不必要的,但我服务的客户需要它。

目前一切运作良好,但是我有一个小问题,我似乎无法找到一种让网络元素在屏幕上的位置的好方法。标题栏、菜单栏、导航栏等都将内容向下推送到物理屏幕上(Robot 从中获取坐标),但对 Selenium 报告元素的位置没有影响。

当我调用:element.getLocation();在 Selenium WebElement 上时,它总是给我它相对于 HTML 内容呈现窗格的位置,而不是浏览器窗口本身。

一个更好的例子是:driver.findElement(By.tagName("body")).getLocation();总是返回 0,0,不管窗口在屏幕上的实际位置。

现在我正在通过在最大化窗口后添加垂直和水平偏移来破解它,但这些在不同浏览器之间并不相同(例如,IE 的顶部装饰比 Firefox 占用更多的空间),并且可能因每个用户而异如果他们添加了书签工具栏、搜索栏等。

是的,我知道我可以在全屏模式下运行,但如果可能的话,我宁愿不这样做。

有没有办法使用 WebDriver 以可靠的方式获取元素在屏幕上的物理位置?

4

4 回答 4

8

我相信没有办法获得页面上元素的真实屏幕位置。

我也认为全屏模式是你最好的选择。

也就是说,我编写了一个RobotCalibration可以检测当前浏览器实际偏移量的类。它会打开一个特制页面并使用Robot该类单击它。该算法从浏览器的中心开始,然后使用二等分来找到浏览器视口的左上角。

在 IE8 和 FF18 上测试。适用于最大化和窗口浏览器。已知问题:如果您启用了顶部书签工具栏,它可能会单击某些书签并因此重定向。它可以很容易地处理,但如果你需要的话,我把它留给你:)。

测试页面:

<!DOCTYPE html>
<html lang="en" onclick="document.getElementById('counter').value++">
<head>
    <meta charset="utf-8" />
    <title>Calibration Test</title>
</head>
<body>
    <img height="1" width="1" style="position: absolute; left: 0; top: 0;"
        onclick="document.getElementById('done').value = 'yep'" />
    <input type="text" id="counter" value="0" />
    <input type="text" id="done" value="nope" />
</body>
</html>

RobotCalibration班级。它有点长,所以我建议你将它复制粘贴到你最喜欢的 IDE 中并在那里进行探索:

import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.nio.file.Paths;

import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.ie.InternetExplorerDriver;

public class RobotCalibration {

    public static Point calibrate(WebDriver driver) {
        return new RobotCalibration(driver).calibrate();
    }

    /** Time for which to wait for the page response. */
    private static final long TIMEOUT = 1000;

    private final WebDriver driver;
    private final Robot r;

    private final Point browserCenter;
    private int leftX;
    private int rightX;
    private int midX;
    private int topY;
    private int bottomY;
    private int midY;

    private RobotCalibration(WebDriver driver) {
        this.driver = driver;
        try {
            driver.manage().window().getSize();
        } catch (UnsupportedOperationException headlessBrowserException) {
            throw new IllegalArgumentException("Calibrating a headless browser makes no sense.", headlessBrowserException);
        }

        try {
            this.r = new Robot();
        } catch (AWTException headlessEnvironmentException) {
            throw new IllegalStateException("Robot won't work on headless environments.", headlessEnvironmentException);
        }

        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        org.openqa.selenium.Dimension browserSize = driver.manage().window().getSize();
        org.openqa.selenium.Point browserPos = driver.manage().window().getPosition();

        // a maximized browser returns negative position
        // a maximized browser returns size larger than actual screen size
        // you can't click outside the screen
        leftX = Math.max(0, browserPos.x);
        rightX = Math.min(leftX + browserSize.width, screenSize.width - 1);
        midX = (leftX + rightX) /2;

        topY = Math.max(0, browserPos.y);
        bottomY = Math.min(topY + browserSize.height, screenSize.height - 1);
        midY = (topY + bottomY) /2;

        browserCenter = new Point(midX, midY);
    }

    private Point calibrate() {
        driver.get(Paths.get("files/RobotCalibration.html").toUri().toString());

        // find left border
        while (leftX < rightX) {
            click(midX, midY);
            if (clickWasSuccessful()) {
                rightX = midX;
            } else {
                leftX = midX + 1;
                // close any menu we could have opened
                click(browserCenter.x, browserCenter.y);
            }
            midX = (leftX + rightX) /2;
        }

        // find top border
        while (topY < bottomY) {
            click(midX, midY);
            if (clickWasSuccessful()) {
                bottomY = midY;
            } else {
                topY = midY + 1;
                // close any menu we could have opened
                click(browserCenter.x, browserCenter.y);
            }
            midY = (topY + bottomY) /2;
        }

        if (!isCalibrated()) {
            throw new IllegalStateException("Couldn't calibrate the Robot.");
        }
        return new Point(midX, midY);
    }

    /** clicks on the specified location */
    private void click(int x, int y) {
        r.mouseMove(x, y);
        r.mousePress(InputEvent.BUTTON1_DOWN_MASK);
        r.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);

        // for some reason, my IE8 can't properly register clicks that are close
        // to each other faster than click every half a second
        if (driver instanceof InternetExplorerDriver) {
            sleep(500);
        }
    }

    private static void sleep(int millis) {
        try {
            Thread.sleep(millis);
        } catch (InterruptedException ignored) {
            // nothing to do
        }
    }

    private int counter = 0;
    /** @return whether the click on a page was successful */
    private boolean clickWasSuccessful() {
        counter++;

        long targetTime = System.currentTimeMillis() + TIMEOUT;
        while (System.currentTimeMillis() < targetTime) {
            int pageCounter = Integer.parseInt(driver.findElement(By.id("counter")).getAttribute("value"));
            if (counter == pageCounter) {
                return true;
            }
        }
        return false;
    }

    /** @return whether the top left corner has already been clicked at */
    private boolean isCalibrated() {
        long targetTime = System.currentTimeMillis() + TIMEOUT;
        while (System.currentTimeMillis() < targetTime) {
            if (driver.findElement(By.id("done")).getAttribute("value").equals("yep")) {
                return true;
            }
        }
        return false;
    }

}

示例用法:

WebDriver driver = new InternetExplorerDriver();
Point p = RobotCalibration.calibrate(driver);
System.out.println("Left offset: " + p.x + ", top offset: " + p.y);
driver.quit();

如果有不清楚的地方,请随时提出任何问题。

于 2013-02-03T21:29:13.840 回答
6

看起来 selenium 现在有一种方法可以获取元素相对于浏览器窗口的位置。在我的一个测试中,我试图验证页面是否锚定到某个位置,并认为最好的方法是获取相对于窗口的元素位置。这可以使用getCoordinates().inViewPort()方法来完成。

下面是一些获取 WebElement 相对位置的示例代码:

import org.openqa.selenium.By;
import org.openqa.selenium.Point;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.internal.Locatable;

public class RelativeElementLocation {

private static Point locationPoint;
private static Locatable elementLocation;
WebDriver driver = new FirefoxDriver();
WebElement element;

public Point location() throws Throwable {
    driver.get("Insert your URL here");
    element = driver.findElement(By.id("Insert id/css/xpath/ here"));
    elementLocation = (Locatable) element;
    locationPoint = elementLocation.getCoordinates().inViewPort();
    System.out.println(locationPoint);

    return locationPoint; 
    }
}

inViewPort()方法的唯一警告是,如果您尝试获取当前不在页面视图中的元素的相对元素位置,该方法将滚动页面以使该元素在视图中,然后为您提供相对位置。意思是说,如果您尝试获取位于浏览器页面视图上方的元素的相对位置,它不会给您一个负的 y 坐标,而是向上滚动到该元素并使 y 坐标为零。

希望这会有所帮助,我是 Selenium 和 Java 的新手,所以如果我的代码中有任何错误,请原谅我。

于 2015-08-18T17:23:12.023 回答
4

有一种比Slanec 答案更简单的方法来获得相对于绝对偏移量。

首先,您需要一个在窗口上附加了 javascript 的单击侦听器的空白页面来获取坐标并将它们保存到全局变量中:

window.coordinates = [];
window.attachEvent('click', function() {
     window.coordinates = [event.pageX, event.pageY];
});

然后单击并通过以下方式获取相对单击位置JavascriptExecutor

// browser center
Dimension size = driver.manage().window().getSize();
Point point = driver.manage().window().getPosition()
    .translate(size.width / 2, size.height / 2);

// Click through robot
click(point.x, point.y);
Thread.sleep(500);

// read coordinates
List<Object> coordinates = (List<Object>) ((JavascriptExecutor) driver)
    .executeScript("return window.coordinates;");
if (coordinates.size() != 2)
     throw new IllegalStateException("Javascript did not receive the click");

// point contains screen coordinates of upper left page point
point.translate(-((Integer)coordinates.get(0)),
                -((Integer)coordinates.get(1)));
于 2015-02-18T13:53:44.000 回答
0

当前的 Selenium 没有实现获取 Web 元素绝对位置的 API。但是SAFS已经实现了一个版本来获取 web 元素的绝对位置。

思路如下:

  1. 获取元素对于框架的位置。因为一个浏览器网页中可能有很多框架。此外,该框架可以植入另一个框架中。因此,最好使用带有父框架的类来递归搜索到最外层的框架位置。

`

class FrameElement {
    FrameElement parent;
    Point location;

    FrameElement(FrameElement parent, WebElement frame) throws ... {
        if(parent != null) {
            this.parent = parent;
            location = frame.getLocation().moveBy(parent.getLocation().x, parent.getLocation().y);
        } else {
            location = frame.getLocation();
        }
    }

    public Point getLocation() {return location;} 
}

Point p = webelement.getLocation();
  1. 添加框架的位置,相对于浏览器客户区(部分不包括工具栏、菜单等)。

  2. 添加浏览器客户区,相对于浏览器窗口。

  3. 添加浏览器窗口的位置,相对于屏幕。

于 2015-07-09T09:07:20.387 回答