You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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),系统会判定为双指同时操作,但实际需求是串行点击两个按钮,这种并行逻辑会导致:

  1. 第一个按钮点击后界面可能发生变化,导致第二个元素的预先计算坐标失效
  2. 系统不允许同时触发两个按钮的点击事件,进而触发元素状态异常
  3. 两个序列的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 22:05:05