Java Selenide - For-Looping ElementsCollection 在循环期间进行修改

Java Selenide - For-Looping ElementsCollection with Modifications During Loop

背景

我已经在 Java 编程了 3 年多了,但这是我第一次觉得我仍然不了解基本的 for 循环是如何工作的,天哪!

所以我有一个 Selenide 项目。 Selenide 是一个基于 SeleniumHQ 的 Web 自动化测试库。

我有一个简单的 for 循环,它循环遍历图像元素列表。


项目设置

依赖性:

compile group: 'com.codeborne', name: 'selenide', version: '5.2.2'

进口:

import static com.codeborne.selenide.Selenide.$$;
import static com.codeborne.selenide.Selectors.byXpath;

邪恶代码:

final ElementsCollection list = $$(byXpath("//img[@src='original src']"));
for (int i=0; i<list.size(); i++) {
    doSth(list.get(i));
}

doSth方法:

void doSth(SelenideElement element) {
    Selenide.executeJavaScript("arguments[0].src = 'new src';", element);
}

当然,上面的代码是我正在处理的代码的简单版本,但核心结构仍然存在。


问题

我发现循环 count/iteration 比预期的少,在我的例子中是 预期 4实际 2

根本原因分析

doSth 方法尝试更改来自参数的 img 元素的 src 属性。更改后,img 元素将不再满足 XPath。 img 元素在修改后是否满足 XPath 很重要,因为 Selenide 如何检索元素。

由于 Selenide 的 XPath 选择的元素集合就像单个元素对象一样被惰性求值,集合中的每个元素只存储元素索引和集合 XPath(这在 Selenide 的 Java文档)。当其中一个集合元素的 src 属性更新时,它会导致动态集合大小缩小,从而影响后续循环迭代。

读完ElementsCollectionclass的源码,我们就会知道ElementsCollection是一个重写了方法的AbstractList。由于在每次迭代中都会评估 Java for 循环中的终止条件,因此覆盖的 size() 方法实际上会在每次循环迭代后返回一个较小的数字,并递减 1


解决方案

由于列表的第一个(0 的索引)项的 src 已修改,列表将在每次迭代后将其大小缩小 1n+1th 元素将占据第 nth 元素的位置。我总能得到第一个(0 的索引)项目,它实际上给了我下一个项目,因为前一个项目将不再保留在动态列表中。

  • 第一次迭代:
    • [0] = element A
    • [1] = element B
    • [2] = element C
    • [3] = element D
  • 第二次迭代:
    • [0] = element B
    • [1] = element C
    • [2] = element D

修改循环如下:

final ElementsCollection list = $$(byXpath("//img[@src='original src']"));
final int count = list.size(); // extract to evaluate once only
for (int i=0; i<count; i++) {
    doSth(list.get(0)); // change i to 0
}