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
属性更新时,它会导致动态集合大小缩小,从而影响后续循环迭代。
读完ElementsCollection
class的源码,我们就会知道ElementsCollection
是一个重写了方法的AbstractList
。由于在每次迭代中都会评估 Java for 循环中的终止条件,因此覆盖的 size()
方法实际上会在每次循环迭代后返回一个较小的数字,并递减 1
。
解决方案
由于列表的第一个(0
的索引)项的 src
已修改,列表将在每次迭代后将其大小缩小 1
,n+1
th 元素将占据第 n
th 元素的位置。我总能得到第一个(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
}
背景
我已经在 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
属性更新时,它会导致动态集合大小缩小,从而影响后续循环迭代。
读完ElementsCollection
class的源码,我们就会知道ElementsCollection
是一个重写了方法的AbstractList
。由于在每次迭代中都会评估 Java for 循环中的终止条件,因此覆盖的 size()
方法实际上会在每次循环迭代后返回一个较小的数字,并递减 1
。
解决方案
由于列表的第一个(0
的索引)项的 src
已修改,列表将在每次迭代后将其大小缩小 1
,n+1
th 元素将占据第 n
th 元素的位置。我总能得到第一个(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
}