Appium/Selenide合并序列调用androidDriver.perform()触发InvalidElementStateException
问题:Appium合并执行多个动作序列触发InvalidElementStateException
我使用Appium和Selenide进行Android应用自动化测试,需要对两个不同按钮执行操作。分别调用androidDriver.perform()执行每个动作序列时一切正常,但将两个序列传入同一个androidDriver.perform()执行时,会触发InvalidElementStateException。
合并调用报错的代码
if (isAndroid()) { SelenideElement buttonsKeyBoard1 = $x("//android.widget.Button[contains(@resource-id, 'Keyboard_1Button')]").should(Condition.visible); SelenideElement buttonsKeyBoard2 = $x("//android.widget.Button[contains(@resource-id, 'Keyboard_2Button')]"); List<SelenideElement> buttonsKeyBoard = new ArrayList<>(); buttonsKeyBoard.add(buttonsKeyBoard1); buttonsKeyBoard.add(buttonsKeyBoard2); // Create a list to hold the sequences List<Sequence> sequences = new ArrayList<>(); // Process the first button PointerInput finger1 = new PointerInput(PointerInput.Kind.TOUCH, "finger1"); PointerInput finger2 = new PointerInput(PointerInput.Kind.TOUCH, "finger2"); Sequence tap1 = new Sequence(finger1, 1); Sequence tap2 = new Sequence(finger2, 1); Point sourceLocation = buttonsKeyBoard1.getLocation(); Point sourceLocation2 = buttonsKeyBoard2.getLocation(); int centerX = sourceLocation.getX() + buttonsKeyBoard1.getSize().getWidth() / 2; int centerY = sourceLocation.getY() + buttonsKeyBoard1.getSize().getHeight() / 2; System.out.println("x1 and y1 " + centerX + " " + centerY); int centerX2 = sourceLocation2.getX() + buttonsKeyBoard2.getSize().getWidth() / 2; int centerY2= sourceLocation2.getY() + buttonsKeyBoard2.getSize().getHeight() / 2; System.out.println("x2 and y2 " + centerX2 + " " + centerY2); tap1.addAction(finger1.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), centerX, centerY)); tap1.addAction(finger1.createPointerDown(LEFT.asArg())); tap1.addAction(finger1.createPointerUp(LEFT.asArg())); sequences.add(tap1); // Add the sequence to the list of sequences tap2.addAction(finger2.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), centerX2, centerY2)); tap2.addAction(finger2.createPointerDown(LEFT.asArg())); tap2.addAction(finger2.createPointerUp(LEFT.asArg())); sequences.add(tap2); // Add the sequence to the list of sequences androidDriver.perform(sequences); }
错误信息
Caused by: org.openqa.selenium.InvalidElementStateException: Unable to perform W3C actions. Check the logcat output for possible error reports and make sure your input actions chain is valid.
分开调用正常的代码
sequences.add(tap1); // Add the sequence to the list of sequences tap2.addAction(finger2.createPointerMove(Duration.ofMillis(10), PointerInput.Origin.viewport(), centerX2, centerY2)); tap2.addAction(finger2.createPointerDown(LEFT.asArg())); tap2.addAction(finger2.createPointerUp(LEFT.asArg())); sequences2.add(tap2); // Add the sequence to the list of sequences androidDriver.perform(sequences); androidDriver.perform(sequences2);
问题原因及解决办法
问题原因
当在同一个perform()中传入多个Sequence时,Appium会将这些序列视为并行执行的多手指操作(比如双指同时点击),而非串行执行的两个独立点击。你的代码使用了两个不同的PointerInput(finger1和finger2),系统会判定为双指同时操作,但实际需求是串行点击两个按钮,这种并行逻辑会导致:
- 第一个按钮点击后界面可能发生变化,导致第二个元素的预先计算坐标失效
- 系统不允许同时触发两个按钮的点击事件,进而触发元素状态异常
- 两个序列的500ms移动时长冲突,系统无法同步处理并行输入
解决办法
办法1:串行执行(单手指顺序点击)
如果需求是按顺序点击两个按钮,直接使用同一个PointerInput创建动作序列,将两个点击动作按顺序加入同一个Sequence,Appium会自动串行执行:
if (isAndroid()) { SelenideElement buttonsKeyBoard1 = $x("//android.widget.Button[contains(@resource-id, 'Keyboard_1Button')]").should(Condition.visible); SelenideElement buttonsKeyBoard2 = $x("//android.widget.Button[contains(@resource-id, 'Keyboard_2Button')]"); PointerInput finger = new PointerInput(PointerInput.Kind.TOUCH, "finger"); Sequence sequence = new Sequence(finger, 1); // 第一个按钮点击动作 Point sourceLocation = buttonsKeyBoard1.getLocation(); int centerX = sourceLocation.getX() + buttonsKeyBoard1.getSize().getWidth() / 2; int centerY = sourceLocation.getY() + buttonsKeyBoard1.getSize().getHeight() / 2; sequence.addAction(finger.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), centerX, centerY)); sequence.addAction(finger.createPointerDown(LEFT.asArg())); sequence.addAction(finger.createPointerUp(LEFT.asArg())); // 加入短暂延迟,确保第一个点击完成后再执行第二个 sequence.addAction(finger.createPointerMove(Duration.ofMillis(200), PointerInput.Origin.viewport(), centerX, centerY)); // 第二个按钮点击动作 Point sourceLocation2 = buttonsKeyBoard2.getLocation(); int centerX2 = sourceLocation2.getX() + buttonsKeyBoard2.getSize().getWidth() / 2; int centerY2= sourceLocation2.getY() + buttonsKeyBoard2.getSize().getHeight() / 2; sequence.addAction(finger.createPointerMove(Duration.ofMillis(500), PointerInput.Origin.viewport(), centerX2, centerY2)); sequence.addAction(finger.createPointerDown(LEFT.asArg())); sequence.addAction(finger.createPointerUp(LEFT.asArg())); androidDriver.perform(Collections.singletonList(sequence)); }
办法2:并行执行(双指同时点击)
如果确实需要同时点击两个按钮,需确保两个序列的时间线同步,且元素在操作时处于可用状态:
if (isAndroid()) { // 元素定位部分不变 SelenideElement buttonsKeyBoard1 = $x("//android.widget.Button[contains(@resource-id, 'Keyboard_1Button')]").should(Condition.visible); SelenideElement buttonsKeyBoard2 = $x("//android.widget.Button[contains(@resource-id, 'Keyboard_2Button')]").should(Condition.visible); Point sourceLocation = buttonsKeyBoard1.getLocation(); int centerX = sourceLocation.getX() + buttonsKeyBoard1.getSize().getWidth() / 2; int centerY = sourceLocation.getY() + buttonsKeyBoard1.getSize().getHeight() / 2; Point sourceLocation2 = buttonsKeyBoard2.getLocation(); int centerX2 = sourceLocation2.getX() + buttonsKeyBoard2.getSize().getWidth() / 2; int centerY2= sourceLocation2.getY() + buttonsKeyBoard2.getSize().getHeight() / 2; PointerInput finger1 = new PointerInput(PointerInput.Kind.TOUCH, "finger1"); PointerInput finger2 = new PointerInput(PointerInput.Kind.TOUCH, "finger2"); Sequence tap1 = new Sequence(finger1, 1); Sequence tap2 = new Sequence(finger2, 1); // 两个动作同步执行,从原点直接移动到目标位置 tap1.addAction(finger1.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), centerX, centerY)); tap1.addAction(finger1.createPointerDown(LEFT.asArg())); tap1.addAction(finger1.createPointerUp(LEFT.asArg())); tap2.addAction(finger2.createPointerMove(Duration.ZERO, PointerInput.Origin.viewport(), centerX2, centerY2)); tap2.addAction(finger2.createPointerDown(LEFT.asArg())); tap2.addAction(finger2.createPointerUp(LEFT.asArg())); List<Sequence> sequences = Arrays.asList(tap1, tap2); androidDriver.perform(sequences); }
额外注意点
- 调用
getLocation()和getSize()获取的坐标是即时的,若动作执行前界面发生变化,坐标会失效,建议在创建动作序列前重新校验元素状态 - 检查Appium和UiAutomator2的版本,部分旧版本对W3C动作序列的支持存在bug,升级到稳定版本可能解决问题
内容的提问来源于stack exchange,提问作者User1751
相关产品推荐
相关产品推荐

