如何使用Capybara选取表格指定行并校验tooltip与Excel数据
我之前发布过几个相关提问,后续会将其删除,在本提问中我会更清晰地说明问题。首先我目前正在努力学习技术,此前靠家政保洁维持生计,所以如果我学习存在一定难度、进度未达理想速度,还请谅解。
核心需求
逐行捕获页面表格内容,和Excel电子表格中的信息做校验。目前初步思路是捕获tooltip文本,和工作表存储的对应文本做比对,已经有朋友给了能列出所有tooltip文本的示例代码,但不知道怎么定位指定行号,没法实现逐行匹配校验。
现有代码
- 朋友提供的获取所有tooltip的示例代码:
page.all('tr.tvRow .tvCell:nth-child(6) img[tooltip]').map { |el| el['tooltip'] }
- 目前写的零散校验代码,只能校验页面存在对应文本,没法绑定到指定行:
expect(page).to have_css('tr.tvRow .tvCell:nth-child(6) img[tooltip]', exact_text: "Invisível") expect(page).to have_css('tr.tvRow .tvCell:nth-child(6) img[tooltip]', exact_text: "Facultativo") expect(page).to have_css('tr.tvRow .tvCell:nth-child(6) img[tooltip]', exact_text: "Obrigatório")
页面DOM结构参考
<td valign="top" colspan="1" rowspan="1"> <div id="AFRAME_790" onmousedown="event.preventMoving=true;" style="display: inline-block; box-sizing: border-box; min-width: 100%;" class="tv"> <a name="TVXWFMTGINSTATOCE"></a> <div class="tvCaption"> <div style="display:inline-block;width:100%"> <img src="r/std/static/pixel.gif" class="icon Enabled icon_grid" draggable="false" onclick="sendEvent(0,event,this,true,0,'','790','ControlMenu#','','','','','');"> <img src="r/std/static/pixel.gif" class="icon Enabled icon_norows" draggable="false" onclick="sendEvent(0,event,this,searchImage(this),0,'','790','SelectNone#','','','','','');" alt="Selecionar Nenhum" tooltip="Selecionar Nenhum"> <img src="r/std/static/pixel.gif" class="icon Enabled icon_allrows" draggable="false" onclick="sendEvent(0,event,this,searchImage(this),0,'','790','SelectAll#','','','','','');" alt="Selecionar todos" tooltip="Selecionar todos"> Lista campos <div onclick="if(notSelecting()) sendEvent(0,event,this,searchImage(this),0,'','790','TMMenu#','','','','','');" style="white-space: nowrap;" class="tvMsg tvMsgEna"><img src="r/std/icons/menu64.png" class="icon" draggable="false"></div> <div onclick="if(notSelecting()) sendEvent(0,event,this,searchImage(this),0,'','790','CambioLabel#','','','','','');" class="tvMsg tvMsgEna" style="white-space: nowrap;" tooltip="Editar Label"> <img src="r/std/static/label16.gif" class="icon" draggable="false"> <div class="tvCaptionSep"></div> </div> </div> </div> <div> <div style="" class="tvContent"> <div style="display:table;width:100%; background:inherit;"> <div style="display:table-cell;"> <table width="100%" class="tvGrid"> <tbody> <tr> <th colspan="1" class="tvHeader">Nome</th> <th colspan="1" class="tvHeader">Etiqueta</th> <th colspan="1" class="tvHeader">Tipo Dado</th> <th colspan="1" class="tvHeader">Valores </th> <th colspan="1" width="1" class="tvHeader">Preenchimento automático</th> <th colspan="1" class="tvHeader">Status </th> <th colspan="1"></th> </tr> <tr oncontextmenu="if(!this.cancelEvent) {sendEvent(0,event,this,true,0,'TVXWFMTGINSTATOCE','790','BtnDropDownRow#',0,'','','');return false;} delete this.cancelEvent;" onclick="if(notSelecting()) sendEvent(0,event,this,true,0,'TVXWFMTGINSTATOCE','790','Select#',0,'','','');" onmousedown="if(event.ctrlKey) this.cancelEvent=true;" class="tvRow tvRowOdd tvRoll" style="cursor:pointer;"> <td valign="center" align="left" class="tvCell">FLAG_INSERIMENTO_LETTURA_E</td> <td valign="center" align="left" class="tvCell"> Leitura Existente</td> <td align="left" class="tvCell" valign="center" nowrap=""></td> <td valign="center" align="left" class="tvCell"></td> <td align="left" width="1" class="tvCell" valign="center" tooltip=""></td> <td valign="center" align="center" class="tvCell"><img tooltip="Invisível" src="r/std/static/minus16.gif"></td> <td align="center" width="30" style="line-height:1px;padding:0px;" class="tvCell" valign="center"> <div onclick="event.cancelBubble=true;sendEvent(0,event,this,searchImage(this),0,'','790','BtnDropDownRow#',0,'','','','');" class="but"> <div><img src="r/std/icons/menu64.png" class="icon" draggable="false" align="absmiddle"></div> </div> </td> </tr> <tr oncontextmenu="if(!this.cancelEvent) {sendEvent(0,event,this,true,0,'TVXWFMTGINSTATOCE','790','BtnDropDownRow#',1,'','','');return false;} delete this.cancelEvent;" onclick="if(notSelecting()) sendEvent(0,event,this,true,0,'TVXWFMTGINSTATOCE','790','Select#',1,'','','');" onmousedown="if(event.ctrlKey) this.cancelEvent=true;" class="tvRow tvRowEven tvRoll" style="cursor:pointer;"> <td valign="center" align="left" class="tvCell">XWFMSLETMATRICOLA_E</td> <td valign="center" align="left" class="tvCell"> Nº Hidrômetro</td> <td align="left" class="tvCell" valign="center" nowrap=""></td> <td valign="center" align="left" class="tvCell"></td> <td align="left" width="1" class="tvCell" valign="center" tooltip="\[AIMPMATRICOLA\]"> <button onclick="event.cancelBubble=true;sendEvent(0,event,this,searchImage(this),0,'','790','AUTOFILLBTN#',1,'','','','');" class="but butAct" tabindex="0" tooltip="Preenchimento automático ativado" type="BUTTON"> <div><img src="r/std/icons/checkboxselected64.png" class="icon" draggable="false" align="absmiddle"></div> </button> </td> <td valign="center" align="center" class="tvCell"><img tooltip="Invisível" src="r/std/static/minus16.gif"></td> <td align="center" width="30" style="line-height:1px;padding:0px;" class="tvCell" valign="center"> <div onclick="event.cancelBubble=true;sendEvent(0,event,this,searchImage(this),0,'','790','BtnDropDownRow#',1,'','','','');" class="but"> <div><img src="r/std/icons/menu64.png" class="icon" draggable="false" align="absmiddle"></div> </div> </td> </tr> </tbody> </table> </div> </div> </div> </div> </div> </td>
现有代码的问题是选择器没有限定行范围,所以只要页面任意位置存在匹配的tooltip就会通过校验,没法和行号绑定。Capybara支持通过CSS的nth-of-type选择器直接定位指定序号的表格行,也支持先拿到所有行的集合再按索引取值,两种方式都可以实现逐行校验。
方法1:直接通过行号定位元素校验
CSS选择器里用tr.tvRow:nth-of-type(行序号)就能选中对应的数据行,注意这里的序号从1开始计数,对应表格里的第一行、第二行数据。
比如校验第1行(序号1)的Status列tooltip:
# 校验第1行的tooltip值为"Invisível" expect(page).to have_css('tr.tvRow:nth-of-type(1) .tvCell:nth-child(6) img[tooltip="Invisível"]') # 校验第2行的tooltip值为"Invisível" expect(page).to have_css('tr.tvRow:nth-of-type(2) .tvCell:nth-child(6) img[tooltip="Invisível"]')
可以把Excel里读出来的行号和对应预期值做循环,自动生成校验逻辑,不用手动写每一行的断言。
方法2:先获取所有行集合,再逐行读取校验
如果需要批量处理所有行,和Excel数据按顺序一一比对,可以先拿到所有数据行的集合,再遍历每一行提取tooltip值,和Excel对应行的预期值直接断言,这种方式更适合批量校验场景:
# 先拿到所有数据行的元素集合 all_rows = page.all('tr.tvRow') # 假设excel_data是你从Excel读出来的数组,每个元素是对应行预期的Status文本 excel_data.each_with_index do |expected_status, index| # 从当前行里找第6列的img元素,取tooltip属性 current_row_tooltip = all_rows[index].find('.tvCell:nth-child(6) img[tooltip]')['tooltip'] expect(current_row_tooltip).to eq(expected_status) end
补充说明
如果表格存在分页、动态加载的情况,执行page.all之前记得加等待逻辑,确保所有行都加载完成再做校验,避免拿到的行数量不全导致断言失败。如果不想用行序号匹配,也可以用第一列的字段名(比如FLAG_INSERIMENTO_LETTURA_E)作为定位锚点找对应行,就算行顺序变了也能正确匹配:
# 找第一列文本为指定字段名的行,再校验它的tooltip target_row = page.find('tr.tvRow', text: 'FLAG_INSERIMENTO_LETTURA_E') expect(target_row).to have_css('.tvCell:nth-child(6) img[tooltip="Invisível"]')
内容的提问来源于stack exchange,提问作者Elis

