Selenium/Python:定位表格指定门禁文本并点击对应删除按钮
问题场景
我正在使用Selenium/Python开发公司门禁权限管理Web应用,需求是仅删除用户权限列表中指定的门禁:需要先定位表格内的指定门禁文本,再点击同一行的删除按钮完成权限移除。
相关信息:
- 门禁文本的XPath示例:
//*[@id="GrdDeviceListrow1"]/td[3]/label,其中td[]会随行数变化 - 删除按钮XPath:
//td[14]/i - 曾尝试通过文本定位元素(如
browser.find_element(By.XPATH,'//button[text()="Outliers"]')),但无法识别目标门禁文本,还会误触发删除操作 - 第一行门禁文本的HTML代码:
<label ng-if="(orderByData[$index].type!='dynamicImg' && orderByData[$index].type!='dynamicIcon' && orderByData[$index].type!='colorpicker' && orderByData[$index].type!='tooltipLabel' && orderByData[$index].type !='editDelete' && orderByData[$index].type != 'dynamicType')" ng-class="{'cursor' : enablerawselectiongrid === 'true' && !defaulteditmode,'grid-raw-disable' : enablerawselectiongrid === 'false','anchorLinkEffectForLabel':orderByData[$index].underLine == value,'anchorLinkEffect': orderByData[$index].otherCSS == value ,'greenColorText' : orderByData[$index].greenColorText == value,'redColorText' : orderByData[$index].redColorText == value}" class="grid-raw-font mx-input-theme ng-binding ng-scope label-text" ng-hide="((SelectedIndexforedit == data.SrIndex && orderByData[$index].type!='' && isEditClick))||(defaulteditmode && orderByData[$index].type=='text' && !showcheckboxinedit)||(defaulteditmode && orderByData[$index].type=='timepicker')||(defaulteditmode && orderByData[$index].type=='checkbox') || (defaulteditmode && orderByData[$index].type=='twoChk') " type="label">ENZ1-GABIO-GAT-00026</label>
现有代码可以删除表格中所有门禁,需要优化为仅删除指定门禁:
import pytest import time import json import pandas as pd from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.common.exceptions import NoSuchElementException class TestRemoveAccess(): def setup_method(self, method): self.driver = webdriver.Chrome() self.vars = {} def teardown_method(self, method): self.driver.quit() def test_remove_access(self): # Read Excel file and extract user codes from the second column df = pd.read_excel("user_list.xlsx", usecols=[1]) user_codes = df.iloc[:, 0].tolist() self.driver.get("http://**********/Login") self.driver.maximize_window() self.driver.find_element(By.ID, "loginid").click() time.sleep(5) self.driver.find_element(By.ID, "loginid").send_keys("******") time.sleep(5) self.driver.find_element(By.ID, "pwd").click() time.sleep(5) self.driver.find_element(By.ID, "pwd").send_keys("*****") time.sleep(5) self.driver.find_element(By.ID, "btnlogin").click() time.sleep(10) element = self.driver.find_element(By.CSS_SELECTOR, "#Link_7 > img") time.sleep(8) actions = ActionChains(self.driver) time.sleep(8) actions.move_to_element(element).perform() time.sleep(8) self.driver.find_element(By.CSS_SELECTOR, "#Link_7 > img").click() time.sleep(8) self.driver.find_element(By.CSS_SELECTOR, ".selectedDiv:nth-child(1) > .cursor").click() time.sleep(8) self.driver.find_element(By.ID, "autoSearchTxt").click() time.sleep(8) self.driver.find_element(By.ID, "autoSearchTxt").click() self.driver.execute_script("window.scrollTo(0,45.33333206176758)") self.driver.execute_script("window.scrollTo(0,45.33333206176758)") for user_code in user_codes: self.driver.find_element(By.ID, "autoSearchTxt").clear() self.driver.find_element(By.ID, "autoSearchTxt").send_keys(user_code) time.sleep(8) self.driver.find_element(By.ID, "autoSearchTxt").send_keys(Keys.ENTER) time.sleep(8) self.driver.find_element(By.LINK_TEXT, "Devices").click() time.sleep(8) # Loop until the XPath is not available count = 0 while self.is_element_present(By.XPATH, "//td[14]/i"): self.driver.find_element(By.XPATH, "//td[14]/i").click() time.sleep(4) assert self.driver.switch_to.alert.text == "Record Will be Deleted. Are You Sure?" self.driver.switch_to.alert.accept() time.sleep(5) self.driver.find_element(By.CSS_SELECTOR, ".fa-save").click() time.sleep(4) count += 1 self.driver.find_element(By.CSS_SELECTOR, ".fa-pencil").click() time.sleep(4) print(f"The loop performed {count} times for user {user_code}.") def is_element_present(self, by, locator): try: self.driver.find_element(by, locator) return True except NoSuchElementException: return False
优化方案
核心修改点
- 定义需删除的门禁列表:可直接定义或从Excel读取,明确目标门禁编号
- 精准定位目标行:通过门禁文本的XPath定位元素,再向上遍历找到所在的
<tr>行 - 行内定位删除按钮:在目标行内查找第14列的删除按钮,避免误操作
- 替换固定等待为显式等待:提升脚本稳定性,减少不必要的等待时间
修改后的完整代码
import pytest import pandas as pd from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.wait import WebDriverWait from selenium.webdriver.common.keys import Keys from selenium.common.exceptions import NoSuchElementException, TimeoutException class TestRemoveAccess(): def setup_method(self, method): self.driver = webdriver.Chrome() self.wait = WebDriverWait(self.driver, 15) # 显式等待超时时间 self.vars = {} # 定义需要删除的门禁列表,可改为从Excel读取 self.target_doors = ["ENZ1-GABIO-GAT-00026", "ENZ1-GABIO-GAT-00027"] def teardown_method(self, method): self.driver.quit() def test_remove_access(self): # 读取用户列表 df = pd.read_excel("user_list.xlsx", usecols=[1]) user_codes = df.iloc[:, 0].tolist() # 登录流程 self.driver.get("http://**********/Login") self.driver.maximize_window() login_id = self.wait.until(EC.element_to_be_clickable((By.ID, "loginid"))) login_id.click() login_id.send_keys("******") pwd = self.wait.until(EC.element_to_be_clickable((By.ID, "pwd"))) pwd.click() pwd.send_keys("*****") self.wait.until(EC.element_to_be_clickable((By.ID, "btnlogin"))).click() # 导航到设备权限页面 element = self.wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, "#Link_7 > img"))) actions = ActionChains(self.driver) actions.move_to_element(element).perform() element.click() self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".selectedDiv:nth-child(1) > .cursor"))).click() search_box = self.wait.until(EC.element_to_be_clickable((By.ID, "autoSearchTxt"))) search_box.click() self.driver.execute_script("window.scrollTo(0,45.33333206176758)") for user_code in user_codes: search_box.clear() search_box.send_keys(user_code) search_box.send_keys(Keys.ENTER) self.wait.until(EC.element_to_be_clickable((By.LINK_TEXT, "Devices"))).click() count = 0 # 遍历需要删除的门禁 for door in self.target_doors: try: # 定位目标门禁的label元素,通过文本精确匹配 door_label = self.wait.until(EC.presence_of_element_located( (By.XPATH, f"//label[text()='{door}']") )) # 找到所在的表格行 row = door_label.find_element(By.XPATH, "./ancestor::tr") # 定位该行的删除按钮 delete_btn = row.find_element(By.XPATH, "./td[14]/i") delete_btn.click() # 处理确认弹窗 alert = self.wait.until(EC.alert_is_present()) assert alert.text == "Record Will be Deleted. Are You Sure?" alert.accept() # 保存修改 self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".fa-save"))).click() count += 1 # 重新进入编辑模式(如果需要) self.wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".fa-pencil"))).click() except (NoSuchElementException, TimeoutException): print(f"门禁 {door} 不存在于用户 {user_code} 的权限列表中,跳过") continue print(f"用户 {user_code} 已删除 {count} 个指定门禁") def is_element_present(self, by, locator): try: self.driver.find_element(by, locator) return True except NoSuchElementException: return False
关键说明
- 文本定位修正:使用
//label[text()='{door}']精确匹配门禁文本,解决之前无法识别的问题 - 行内定位:通过
ancestor::tr找到门禁所在的表格行,再在该行内找删除按钮,确保只删除目标行的门禁 - 显式等待:替换所有
time.sleep()为WebDriverWait,脚本会等待元素可操作后再执行,提升稳定性和效率 - 异常处理:添加对门禁不存在的异常捕获,避免脚本中断
内容的提问来源于stack exchange,提问作者Suresh Kumar
相关产品推荐
相关产品推荐

