问题:因为电脑缩放问题--在selenium中定位元素后使用location截取验证码图片有些电脑上经常失败。。

博客指出在selenium中定位元素后用location获取坐标值出现偏差,导致无法截取指定验证码图片。原因是windows系统下电脑显示缩放比例非100%,而location按100%显示获取坐标。解决办法是将电脑显示缩放设置调回100%,如win10系统可按特定操作设置,可能需注销重启。

解决方案:

一、出现问题

  在selenium中定位元素后使用loction获取坐标值出现偏差,导致未能截取到指定验证码图片

二、原因分析

  之所以会出现这个坐标偏差是因为windows系统下电脑设置的显示缩放比例造成的,location获取的坐标是按显示100%时得到的坐标,但我们的电脑缩放比例并不是100,因此就出现了偏差。

三、解决办法

  把电脑的显示缩放设置调回100%即可准确定位

以win10的系统为例,到桌面点击右键->显示设置->高级缩放设置->自定义缩放(100%),之后可能需要注销重启电脑(大部分不需要),然后就可以了。
在这里插入图片描述

  

<think>我们遇到的问题是在使用Appium的`getScreenshotAs`方法对单个元素(背景图)截图时,出现了`UnsupportedCommandException`异常。 根据错误信息,可能是Appium版本或驱动兼容性问题。因为单个元素截图功能在Appium中并不是所有驱动都完全支持(尤其是UIAutomator2在早期版本可能不支持)。 解决方案: 1. 替代方案:改为截取整个屏幕,然后根据元素的位置和大小裁剪出背景图。 2. 或者,检查Appium版本和驱动,确保支持元素级别的截图。 步骤: 方法一:使用全屏截图再裁剪 a. 获取整个屏幕的截图并保存为文件。 b. 获取背景图元素的位置和大小。 c. 使用OpenCV读取全屏截图,并根据元素的位置和大小裁剪出背景图区域。 方法二:检查并升级Appium和驱动 a. 确保使用的Appium版本较新(至少1.17.0以上)。 b. 使用UIAutomator2驱动,并更新到最新版本。 c. 如果问题仍然存在,则使用方法一。 由于方法一更通用,下面详细描述方法一的代码实现: 代码修改如下: ```java // 替代:bgImg.getScreenshotAs(OutputType.FILE); // 1. 截取整个屏幕 File screenFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); // 2. 获取背景图元素的位置和尺寸 Point point = bgImg.getLocation(); Dimension dimension = bgImg.getSize(); int x = point.getX(); int y = point.getY(); int width = dimension.getWidth(); int height = dimension.getHeight(); // 3. 使用OpenCV读取全屏截图并裁剪 Mat screenMat = Imgcodecs.imread(screenFile.getAbsolutePath()); Mat bgMat = new Mat(screenMat, new Rect(x, y, width, height)); // 注意:这里裁剪的区域是矩形,坐标(x,y)和宽高(width, height) ``` 但是,这里需要注意: - 如果页面有滚动,需要先滚动到元素可见位置,否则截图位置可能不正确。 - 另外,如果有多个屏幕(例如在WebView中嵌套了多个iframe),需要确保元素在当前视口中。 此外,还可能需要考虑设备缩放比例(DPR)的问题。因为Appium返回的坐标和尺寸可能是逻辑像素(逻辑点),而截图是全屏的物理像素(像素点),两者之间可能存在比例关系。 因此,我们需要乘以一个缩放因子(scale factor)。通常,可以通过获取设备的像素密度(density)来计算。 在Appium中,我们可以通过获取设备的屏幕密度来计算缩放因子: ```java // 获取设备屏幕密度(density) Object densityObj = driver.getSessionDetail("devicePixelRatio"); double density = 1.0; if (densityObj != null) { density = Double.parseDouble(densityObj.toString()); } // 然后调整坐标和尺寸 x = (int) (x * density); y = (int) (y * density); width = (int) (width * density); height = (int) (height * density); ``` 但是,请注意:Appium的`getLocation`和`getSize`方法返回的是逻辑像素,而截图是物理像素,所以需要转换。 另外,在Android中,我们还可以通过`driver.getDisplayDensity()`来获取密度,但这个方法可能返回的是整数(如320代表320dpi),我们需要将其转换为缩放因子(相对于基准密度160dpi): ```java float scaleFactor = driver.getDisplayDensity() / 160.0f; ``` 然而,更简单的方式是获取屏幕的物理像素和逻辑像素,然后计算缩放因子: ```java int screenWidthPixels = driver.manage().window().getSize().getWidth(); // 逻辑像素(点) // 获取设备的屏幕分辨率(物理像素) // 在Appium中,可以通过执行adb命令或者获取屏幕截图后获取物理像素尺寸? // 另一种方式:从截图中获取物理像素尺寸 BufferedImage screenImage = ImageIO.read(screenFile); int physicalWidth = screenImage.getWidth(); int physicalHeight = screenImage.getHeight(); // 逻辑像素尺寸(通过Appium获取) Dimension screenSize = driver.manage().window().getSize(); int logicalWidth = screenSize.getWidth(); int logicalHeight = screenSize.getHeight(); // 缩放因子 double scaleFactorWidth = (double) physicalWidth / logicalWidth; double scaleFactorHeight = (double) physicalHeight / logicalHeight; // 通常宽度和高度缩放因子相同,我们取宽度缩放因子 double scaleFactor = scaleFactorWidth; ``` 在实际使用中,我们可以使用这个缩放因子来调整元素的位置和尺寸: ```java x = (int) (x * scaleFactor); y = (int) (y * scaleFactor); width = (int) (width * scaleFactor); height = (int) (height * scaleFactor); ``` 因此,完整的代码步骤: 1. 获取全屏截图(物理像素)。 2. 获取元素的位置和大小(逻辑像素)。 3. 计算物理像素和逻辑像素的比例(缩放因子)。 4. 将元素的位置和大小乘以缩放因子,得到在物理像素截图上的实际区域。 5. 裁剪该区域。 注意:如果截图是全屏,但Appium返回的坐标是相对于当前视口的,那么如果页面有滚动,我们需要先滚动到元素位置并记录滚动偏移。但在我们的场景中,验证码通常是固定在页面上的,所以假设不需要滚动。 代码示例: ```java // 截取全屏 File screenFile = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); BufferedImage screenImage = ImageIO.read(screenFile); int physicalWidth = screenImage.getWidth(); int physicalHeight = screenImage.getHeight(); // 获取屏幕逻辑尺寸 Dimension screenSize = driver.manage().window().getSize(); int logicalWidth = screenSize.getWidth(); int logicalHeight = screenSize.getHeight(); // 计算缩放因子(假设宽高缩放因子相同) double scaleFactor = (double) physicalWidth / logicalWidth; // 获取背景图元素 WebElement bgImg = driver.findElement(By.id("slideBg")); Point point = bgImg.getLocation(); Dimension bgSize = bgImg.getSize(); // 转换为物理像素坐标和尺寸 int x = (int) (point.getX() * scaleFactor); int y = (int) (point.getY() * scaleFactor); int width = (int) (bgSize.getWidth() * scaleFactor); int height = (int) (bgSize.getHeight() * scaleFactor); // 使用OpenCV读取全屏截图 Mat screenMat = Imgcodecs.imread(screenFile.getAbsolutePath()); // 确保裁剪区域在图像范围内 if (x < 0) x = 0; if (y < 0) y = 0; if (x + width > physicalWidth) width = physicalWidth - x; if (y + height > physicalHeight) height = physicalHeight - y; // 裁剪 Rect roi = new Rect(x, y, width, height); Mat bgMat = new Mat(screenMat, roi); // 保存裁剪后的背景图(可选,用于调试) Imgcodecs.imwrite("cropped_bg.png", bgMat); ``` 这样,我们就得到了背景图的Mat对象,可以继续后续的OpenCV处理。 另外,如果问题是因为Appium版本过低,可以尝试升级Appium到最新版本,并确保使用UIAutomator2驱动。 升级依赖(在Maven中): ```xml <dependency> <groupId>io.appium</groupId> <artifactId>java-client</artifactId> <version>8.3.0</version> </dependency> ``` 在初始化驱动时,指定automationName为UiAutomator2: ```java DesiredCapabilities capabilities = new DesiredCapabilities(); capabilities.setCapability(MobileCapabilityType.AUTOMATION_NAME, "UiAutomator2"); ``` 如果升级后仍然出现同样的错误,则使用全屏截图裁剪的方法更可靠。 总结:两种解决方案,推荐先尝试升级Appium和驱动,如果不行则使用全屏截图裁剪的方法。 注意:在裁剪时,一定要处理边界情况(例如裁剪区域超出截图范围),否则会报错。 最后,我们修改后的步骤: 1. 使用全屏截图代替元素截图。 2. 计算缩放因子,调整元素位置和尺寸。 3. 裁剪出背景图。 4. 继续使用OpenCV处理。 这样应该能解决`UnsupportedCommandException`的问题。 相关问题: §§相关问题§§ 1. 在移动端自动化测试中,如何准确获取元素的物理像素坐标? 2. 除了全屏截图裁剪,还有哪些方法可以解决元素截图不支持的问题? 3. 如何确保裁剪出的图像与元素实际显示一致(包括滚动位置、缩放等因素)? </think>### 解决 `UnsupportedCommandException` 的完整方案 #### 问题分析 报错 `org.openqa.selenium.UnsupportedCommandException: The requested uri doesn't exist` 通常由以下原因导致: 1. **Appium 版本兼容性问题**:较旧的 Appium 版本(< 1.22)不支持元素级截图 API[^1] 2. **驱动配置错误**:未正确启用 UIAutomator2 驱动 3. **WebView 上下文切换问题**:未正确处理混合应用中的上下文切换[^3] 4. **元素状态异常**:尝试截图时元素已不可见或被覆盖 #### 解决方案 ##### 1. 升级 Appium 并配置驱动(推荐) ```java // 升级 Appium Java 客户端 <dependency> <groupId>io.appium</groupId> <artifactId>java-client</artifactId> <version>8.6.0</version> <!-- 最低需 8.0+ --> </dependency> // 启用 UIAutomator2 驱动 DesiredCapabilities cap = new DesiredCapabilities(); cap.setCapability("automationName", "UiAutomator2"); // 关键配置[^1] cap.setCapability("appium:uiautomator2ServerInstallTimeout", 60000); ``` ##### 2. 替代截图方案:使用全屏截图裁剪 ```java // 获取元素位置和尺寸 WebElement bgImg = driver.findElement(By.id("slideBg")); Point location = bgImg.getLocation(); Dimension size = bgImg.getSize(); // 截取全屏并裁剪元素区域 File fullScreen = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE); BufferedImage fullImg = ImageIO.read(fullScreen); BufferedImage elementImg = fullImg.getSubimage( location.getX(), location.getY(), size.getWidth(), size.getHeight() ); // 保存裁剪后的图像 File bgFile = new File("cropped_bg.png"); ImageIO.write(elementImg, "png", bgFile); // 转换为 OpenCV Mat Mat bgMat = Imgcodecs.imread(bgFile.getPath()); ``` ##### 3. WebView 上下文验证(混合应用专用) ```java // 检查当前上下文 if (driver.getContext().contains("WEBVIEW")) { // 获取所有可用上下文 Set<String> contexts = driver.getContextHandles(); // 切换到最新上下文 String newContext = contexts.toArray()[contexts.size()-1].toString(); driver.context(newContext); // 关键步骤[^3] // 重新获取元素 bgImg = driver.findElement(By.id("slideBg")); } ``` #### 关键优化点 1. **动态等待元素稳定** ```java new WebDriverWait(driver, Duration.ofSeconds(10)) .until(d -> { try { return bgImg.isDisplayed() && bgImg.getSize().width > 50; } catch (StaleElementReferenceException e) { return false; } }); ``` 2. **缩放因子补偿** ```java // 计算设备像素比 double dpr = driver.getSessionDetail("devicePixelRatio") != null ? Double.parseDouble(driver.getSessionDetail("devicePixelRatio").toString()) : 1.0; // 调整裁剪坐标 location.setX((int)(location.getX() * dpr)); location.setY((int)(location.getY() * dpr)); ``` 3. **异常重试机制** ```java int retry = 0; while (retry++ < 3) { try { // 截图操作代码 break; } catch (UnsupportedCommandException e) { driver.resetApp(); // 重置应用状态 } } ``` #### 注意事项 1. 确保 Appium Server 版本 ≥ 2.0 2. ChromeDriver 版本需与设备 Chrome 版本匹配 3. 在真机测试时开启 USB 调试安全设置: ```bash adb shell appops set com.android.chrome WEBVIEW_VERIFY ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值