You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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' &amp;&amp; orderByData[$index].type!='dynamicIcon' &amp;&amp; orderByData[$index].type!='colorpicker' &amp;&amp; orderByData[$index].type!='tooltipLabel' &amp;&amp; orderByData[$index].type !='editDelete' &amp;&amp; orderByData[$index].type != 'dynamicType')" ng-class="{'cursor' : enablerawselectiongrid === 'true' &amp;&amp; !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 &amp;&amp; orderByData[$index].type!='' &amp;&amp; isEditClick))||(defaulteditmode &amp;&amp; orderByData[$index].type=='text'  &amp;&amp; !showcheckboxinedit)||(defaulteditmode &amp;&amp; orderByData[$index].type=='timepicker')||(defaulteditmode &amp;&amp; orderByData[$index].type=='checkbox') || (defaulteditmode &amp;&amp; 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
优化方案

核心修改点

  1. 定义需删除的门禁列表:可直接定义或从Excel读取,明确目标门禁编号
  2. 精准定位目标行:通过门禁文本的XPath定位元素,再向上遍历找到所在的<tr>行
  3. 行内定位删除按钮:在目标行内查找第14列的删除按钮,避免误操作
  4. 替换固定等待为显式等待:提升脚本稳定性,减少不必要的等待时间

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 15:32:23