Selenium WebDriver XPath 指向第二行,但 findElements returns 第一行
Selenium WebDriver XPath points to second row, but findElements returns first row
我有 HTML 看起来像这样:
<table id="data-table" class="table table-striped table-bordered table-hover dataTable no-footer" role="grid" style="width: 100%;">
<thead>
<tr role="row" style="height: 0px;"><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 277px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Store Number</div></th><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 241px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Store Name</div></th><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 156px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Active</div></th><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 237px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Action</div></th></tr>
</thead>
<tbody>
<tr id="store-3" data-model="{"Id":3,"Name":"a","IsActive":true,"DBVersion":"","Address":{"Address":"","Address2":"","State":"","City":"","Zip":0},"Phone":""}" role="row" class="odd">
<th scope="row" class="sorting_1">3</th>
<td>a</td>
<td>
<label class="tgl">
<input onclick="ToggleStoreActive(this,event)" class="form-isactive" type="checkbox" checked="">
<span class="tgl_body">
<span class="tgl_switch"></span>
<span class="tgl_track">
<span class="tgl_bgd"></span>
<span class="tgl_bgd tgl_bgd-negative"></span>
</span>
</span>
</label>
</td>
<td>
<button class="btn btn-danger" onclick="DeleteStore(this)">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
<button class="btn btn-primary" onclick="PopModel(this)">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</td>
</tr><tr id="store-4" data-model="{"Id":4,"Name":"b","IsActive":true,"DBVersion":"","Address":{"Address":"","Address2":"","State":"","City":"","Zip":0},"Phone":""}" role="row" class="even">
<th scope="row" class="sorting_1">4</th>
<td>b</td>
<td>
<label class="tgl">
<input onclick="ToggleStoreActive(this,event)" class="form-isactive" type="checkbox" checked="">
<span class="tgl_body">
<span class="tgl_switch"></span>
<span class="tgl_track">
<span class="tgl_bgd"></span>
<span class="tgl_bgd tgl_bgd-negative"></span>
</span>
</span>
</label>
</td>
<td>
<button class="btn btn-danger" onclick="DeleteStore(this)">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
<button class="btn btn-primary" onclick="PopModel(this)">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</td>
</tr><tr id="store-5" data-model="{"Id":5,"Name":"c","IsActive":true,"DBVersion":"","Address":{"Address":"","Address2":"","State":"","City":"","Zip":0},"Phone":""}" role="row" class="odd">
<th scope="row" class="sorting_1">5</th>
<td>c</td>
<td>
<label class="tgl">
<input onclick="ToggleStoreActive(this,event)" class="form-isactive" type="checkbox" checked="">
<span class="tgl_body">
<span class="tgl_switch"></span>
<span class="tgl_track">
<span class="tgl_bgd"></span>
<span class="tgl_bgd tgl_bgd-negative"></span>
</span>
</span>
</label>
</td>
<td>
<button class="btn btn-danger" onclick="DeleteStore(this)">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
<button class="btn btn-primary" onclick="PopModel(this)">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</td>
</tr></tbody>
</table>
并试图获取第二行("Store Number" 4 和 "Store Name" 'b' 的行),使用以下 Groovy 代码:
'get the initial number of data rows'
String dataRowsSelector = '//table[contains(@id, "data-table")]/tbody/tr'
List<WebElement> dataRows = driver.findElements(By.xpath(dataRowsSelector))
int initialRowCount = dataRows.size()
assertNotEquals(index, 0)
'find the row pointed to by the index'
String rowSelector = String.format('//table[contains(@id, "data-table")]/tbody/tr[%d]', (index + 1))
assertEquals(rowSelector, '//table[contains(@id, "data-table")]/tbody/tr[2]')
WebElement rowToDelete = driver.findElement(By.xpath(rowSelector))
'get the delete button'
String deleteButtonSelector = '//button[./i[contains(concat(" ", @class, " "), " fa-trash-o ")]]'
WebElement deleteButton = rowToDelete.findElement(By.xpath(deleteButtonSelector))
println(rowToDelete.findElement(By.xpath('//td[1]')).getAttribute('innerText'))
assertTrue(rowToDelete.findElement(By.xpath('//td[1]')).getAttribute('innerText').contains('b'))
我运行那个代码,rowToDelete
变成了第一行数据而不是第二行(我用3
作为索引再次尝试,它仍然是第一行),即使针对目标 xpath 的断言有效。此外,WebElement
的第一个 <td>
包含 a
而不是 b
。 (当我尝试使用 3
作为索引时,我再次得到 a
,即使我期待 c
。
如何解决这种奇怪的行为?
问题显然出在我的 XPath 本身。当尝试 select WebElement
s 相对于其他 WebElement
s 时,使用 XPath,我应该在 //
前面放一个点来给它范围。
我有 HTML 看起来像这样:
<table id="data-table" class="table table-striped table-bordered table-hover dataTable no-footer" role="grid" style="width: 100%;">
<thead>
<tr role="row" style="height: 0px;"><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 277px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Store Number</div></th><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 241px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Store Name</div></th><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 156px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Active</div></th><th scope="col" class="sorting" aria-controls="data-table" rowspan="1" colspan="1" style="width: 237px; padding-top: 0px; padding-bottom: 0px; border-top-width: 0px; border-bottom-width: 0px; height: 0px;"><div class="dataTables_sizing" style="height:0;overflow:hidden;">Action</div></th></tr>
</thead>
<tbody>
<tr id="store-3" data-model="{"Id":3,"Name":"a","IsActive":true,"DBVersion":"","Address":{"Address":"","Address2":"","State":"","City":"","Zip":0},"Phone":""}" role="row" class="odd">
<th scope="row" class="sorting_1">3</th>
<td>a</td>
<td>
<label class="tgl">
<input onclick="ToggleStoreActive(this,event)" class="form-isactive" type="checkbox" checked="">
<span class="tgl_body">
<span class="tgl_switch"></span>
<span class="tgl_track">
<span class="tgl_bgd"></span>
<span class="tgl_bgd tgl_bgd-negative"></span>
</span>
</span>
</label>
</td>
<td>
<button class="btn btn-danger" onclick="DeleteStore(this)">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
<button class="btn btn-primary" onclick="PopModel(this)">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</td>
</tr><tr id="store-4" data-model="{"Id":4,"Name":"b","IsActive":true,"DBVersion":"","Address":{"Address":"","Address2":"","State":"","City":"","Zip":0},"Phone":""}" role="row" class="even">
<th scope="row" class="sorting_1">4</th>
<td>b</td>
<td>
<label class="tgl">
<input onclick="ToggleStoreActive(this,event)" class="form-isactive" type="checkbox" checked="">
<span class="tgl_body">
<span class="tgl_switch"></span>
<span class="tgl_track">
<span class="tgl_bgd"></span>
<span class="tgl_bgd tgl_bgd-negative"></span>
</span>
</span>
</label>
</td>
<td>
<button class="btn btn-danger" onclick="DeleteStore(this)">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
<button class="btn btn-primary" onclick="PopModel(this)">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</td>
</tr><tr id="store-5" data-model="{"Id":5,"Name":"c","IsActive":true,"DBVersion":"","Address":{"Address":"","Address2":"","State":"","City":"","Zip":0},"Phone":""}" role="row" class="odd">
<th scope="row" class="sorting_1">5</th>
<td>c</td>
<td>
<label class="tgl">
<input onclick="ToggleStoreActive(this,event)" class="form-isactive" type="checkbox" checked="">
<span class="tgl_body">
<span class="tgl_switch"></span>
<span class="tgl_track">
<span class="tgl_bgd"></span>
<span class="tgl_bgd tgl_bgd-negative"></span>
</span>
</span>
</label>
</td>
<td>
<button class="btn btn-danger" onclick="DeleteStore(this)">
<i class="fa fa-trash-o" aria-hidden="true"></i>
</button>
<button class="btn btn-primary" onclick="PopModel(this)">
<i class="fa fa-pencil" aria-hidden="true"></i>
</button>
</td>
</tr></tbody>
</table>
并试图获取第二行("Store Number" 4 和 "Store Name" 'b' 的行),使用以下 Groovy 代码:
'get the initial number of data rows'
String dataRowsSelector = '//table[contains(@id, "data-table")]/tbody/tr'
List<WebElement> dataRows = driver.findElements(By.xpath(dataRowsSelector))
int initialRowCount = dataRows.size()
assertNotEquals(index, 0)
'find the row pointed to by the index'
String rowSelector = String.format('//table[contains(@id, "data-table")]/tbody/tr[%d]', (index + 1))
assertEquals(rowSelector, '//table[contains(@id, "data-table")]/tbody/tr[2]')
WebElement rowToDelete = driver.findElement(By.xpath(rowSelector))
'get the delete button'
String deleteButtonSelector = '//button[./i[contains(concat(" ", @class, " "), " fa-trash-o ")]]'
WebElement deleteButton = rowToDelete.findElement(By.xpath(deleteButtonSelector))
println(rowToDelete.findElement(By.xpath('//td[1]')).getAttribute('innerText'))
assertTrue(rowToDelete.findElement(By.xpath('//td[1]')).getAttribute('innerText').contains('b'))
我运行那个代码,rowToDelete
变成了第一行数据而不是第二行(我用3
作为索引再次尝试,它仍然是第一行),即使针对目标 xpath 的断言有效。此外,WebElement
的第一个 <td>
包含 a
而不是 b
。 (当我尝试使用 3
作为索引时,我再次得到 a
,即使我期待 c
。
如何解决这种奇怪的行为?
问题显然出在我的 XPath 本身。当尝试 select WebElement
s 相对于其他 WebElement
s 时,使用 XPath,我应该在 //
前面放一个点来给它范围。