影刀RPA 自动化测试入门:用RPA做接口和界面回归测试

作者:林焱

在这里插入图片描述


写在前面

影刀RPA不只能做数据采集和办公自动化,还能做自动化测试。对于没有专职QA团队的小团队来说,用影刀搭建一套轻量的接口+界面回归测试,成本极低,效果明显。
在这里插入图片描述

本文讲清楚两类测试的搭建方法:接口自动化测试(验证后端接口正确性)和界面自动化测试(验证前端页面交互)。


一、接口自动化测试

拼多多店群自动化报活动上架!

在这里插入图片描述

测试框架思路

import requests
import json
import datetime

class ApiTestRunner:
    """简单的接口测试框架"""
    
    def __init__(self, base_url, auth_token=None):
        self.base_url = base_url
        self.headers = {"Content-Type": "application/json"}
        if auth_token:
            self.headers["Authorization"] = f"Bearer {auth_token}"
        self.results = []
    
    def run_test(self, test_name, method, path, payload=None, 
                 expected_status=200, expected_fields=None, expected_values=None):
        """
        执行单个接口测试
        expected_fields: 期望响应中存在的字段列表
        expected_values: 期望的键值对,如 {"code": 0, "message": "success"}
        """
        url = f"{self.base_url}{path}"
        
        try:
            if method.upper() == "GET":
                resp = requests.get(url, headers=self.headers, params=payload)
            elif method.upper() == "POST":
                resp = requests.post(url, headers=self.headers, json=payload)
            elif method.upper() == "PUT":
                resp = requests.put(url, headers=self.headers, json=payload)
            elif method.upper() == "DELETE":
                resp = requests.delete(url, headers=self.headers)
            
            result = {
                "test_name": test_name,
                "url": url,
                "method": method,
                "status_code": resp.status_code,
                "response_time_ms": round(resp.elapsed.total_seconds() * 1000),
                "passed": True,
                "errors": []
            }
            
            # 检查状态码
            if resp.status_code != expected_status:
                result["passed"] = False
                result["errors"].append(f"状态码期望{expected_status},实际{resp.status_code}")
            
            # 解析响应
            try:
                resp_data = resp.json()
            except:
                resp_data = {}
                if expected_fields or expected_values:
                    result["passed"] = False
                    result["errors"].append("响应不是有效JSON")
            
            # 检查字段存在
            if expected_fields:
                for field in expected_fields:
                    if field not in resp_data:
                        result["passed"] = False
                        result["errors"].append(f"响应缺少字段:{field}")
            
            # 检查字段值
            if expected_values:
                for key, expected_val in expected_values.items():
                    actual_val = resp_data.get(key)
                    if actual_val != expected_val:
                        result["passed"] = False
                        result["errors"].append(f"字段{key}期望{expected_val},实际{actual_val}")
            
            result["response_body"] = str(resp_data)[:500]  # 记录部分响应
            
        except Exception as e:
            result = {
                "test_name": test_name,
                "url": url,
                "method": method,
                "passed": False,
                "errors": [str(e)],
                "response_time_ms": 0,
                "response_body": ""
            }
        
        self.results.append(result)
        status = "✅ PASS" if result["passed"] else "❌ FAIL"
        print(f"{status} [{result.get('response_time_ms', 0)}ms] {test_name}")
        if not result["passed"]:
            for err in result["errors"]:
                print(f"    ↳ {err}")
        
        return result
    
    def generate_report(self, output_file="test_report.xlsx"):
        """生成测试报告"""
        import openpyxl
        from openpyxl.styles import PatternFill, Font
        
        wb = openpyxl.Workbook()
        ws = wb.active
        ws.title = "测试结果"
        
        # 统计
        total = len(self.results)
        passed = sum(1 for r in self.results if r["passed"])
        failed = total - passed
        
        ws.append(["测试报告", f"生成时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"])
        ws.append([f"总用例:{total}", f"通过:{passed}", f"失败:{failed}"])
        ws.append([])
        ws.append(["测试名称", "请求方法", "URL", "状态码", "响应时间(ms)", "结果", "错误信息"])
        
        green_fill = PatternFill(start_color="90EE90", fill_type="solid")
        red_fill = PatternFill(start_color="FF6B6B", fill_type="solid")
        
        for result in self.results:
            row = [
                result["test_name"],
                result.get("method", ""),
                result.get("url", ""),
                result.get("status_code", ""),
                result.get("response_time_ms", ""),
                "PASS" if result["passed"] else "FAIL",
                "; ".join(result.get("errors", []))
            ]
            ws.append(row)
            # 着色
            fill = green_fill if result["passed"] else red_fill
            for cell in ws[ws.max_row]:
                cell.fill = fill
        
        wb.save(output_file)
        print(f"\n📊 测试报告已生成:{output_file}")
        print(f"通过率:{passed}/{total} ({round(passed/total*100)}%)")

定义并执行测试用例

在这里插入图片描述

# 初始化测试运行器
runner = ApiTestRunner("https://api.yourapp.com", auth_token="你的token")

# 用户模块测试
runner.run_test("获取用户信息", "GET", "/api/user/profile",
    expected_status=200,
    expected_fields=["id", "name", "email"],
    expected_values={"code": 0}
)

runner.run_test("更新用户信息", "PUT", "/api/user/profile",
    payload={"name": "测试用户", "phone": "13800138000"},
    expected_status=200,
    expected_values={"code": 0, "message": "更新成功"}
)

# 订单模块测试
runner.run_test("创建订单", "POST", "/api/orders",
    payload={
        "product_id": "PROD_001",
        "quantity": 2,
        "address": "北京市朝阳区xxx"
    },
    expected_status=201,
    expected_fields=["order_id", "status", "created_at"]
)

runner.run_test("获取订单列表", "GET", "/api/orders",
    payload={"page": 1, "size": 10},
    expected_status=200,
    expected_fields=["total", "items"]
)

# 边界条件测试
runner.run_test("越权访问测试", "GET", "/api/admin/users",
    expected_status=403  # 期望返回403禁止访问
)

runner.run_test("无效参数测试", "POST", "/api/orders",
    payload={"product_id": "", "quantity": -1},
    expected_status=400  # 期望返回400参数错误
)

# 生成报告
runner.generate_report("api_test_report.xlsx")

二、界面自动化测试

在这里插入图片描述

界面测试核心方法

import pyautogui
import time

class UITestRunner:
    """界面自动化测试框架"""
    
    def __init__(self):
        self.results = []
        pyautogui.FAILSAFE = True
        pyautogui.PAUSE = 0.3
    
    def find_element(self, image_file, timeout=10, confidence=0.85):
        """等待并查找界面元素"""
        start = time.time()
        while time.time() - start < timeout:
            location = pyautogui.locateOnScreen(image_file, confidence=confidence)
            if location:
                return location
            time.sleep(0.5)
        return None
    
    def click_element(self, image_file, test_step, timeout=10):
        """点击界面元素"""
        location = self.find_element(image_file, timeout)
        if location:
            pyautogui.click(pyautogui.center(location))
            return True
        print(f"⚠️ 步骤失败:{test_step} - 未找到元素 {image_file}")
        return False
    
    def input_text(self, image_file, text, clear_first=True):
        """在输入框中输入文本"""
        location = self.find_element(image_file)
        if location:
            pyautogui.click(pyautogui.center(location))
            if clear_first:
                pyautogui.hotkey('ctrl', 'a')
            pyautogui.typewrite(text, interval=0.05)
            return True
        return False
    
    def verify_text_visible(self, expected_text, screenshot=None):
        """验证文字是否在界面上可见(OCR识别)"""
        # 使用影刀的OCR指令或pytesseract
        try:
            import pytesseract
            from PIL import Image
            
            if screenshot is None:
                screenshot = pyautogui.screenshot()
            
            text_found = pytesseract.image_to_string(screenshot, lang='chi_sim+eng')
            return expected_text in text_found
        except ImportError:
            # 如果没有tesseract,用图像匹配替代
            print("提示:安装pytesseract可以做更精确的文字识别")
            return False
    
    def run_test_case(self, test_name, test_func):
        """执行测试用例并记录结果"""
        print(f"\n▶ 执行测试:{test_name}")
        start_time = time.time()
        
        try:
            success = test_func()
            elapsed = round((time.time() - start_time) * 1000)
            result = {"name": test_name, "passed": success, "time_ms": elapsed, "error": ""}
            print(f"{'✅ PASS' if success else '❌ FAIL'} [{elapsed}ms]")
        except Exception as e:
            elapsed = round((time.time() - start_time) * 1000)
            result = {"name": test_name, "passed": False, "time_ms": elapsed, "error": str(e)}
            print(f"❌ ERROR [{elapsed}ms]: {e}")
            # 截图保存错误现场
            pyautogui.screenshot(f"test_error_{test_name}_{int(time.time())}.png")
        
        self.results.append(result)
        return result["passed"]

示例:登录功能回归测试

在这里插入图片描述

ui_runner = UITestRunner()

def test_login_success():
    """测试正常登录流程"""
    # 点击登录按钮
    if not ui_runner.click_element("login_btn.png", "点击登录按钮"):
        return False
    time.sleep(1)
    
    # 输入用户名
    if not ui_runner.input_text("username_field.png", "test@example.com"):
        return False
    
    # 输入密码
    if not ui_runner.input_text("password_field.png", "Test@12345"):
        return False
    
    # 点击确认
    if not ui_runner.click_element("submit_btn.png", "点击提交"):
        return False
    time.sleep(2)
    
    # 验证登录成功(检查首页元素是否出现)
    dashboard = ui_runner.find_element("dashboard_title.png", timeout=10)
    return dashboard is not None

def test_login_with_wrong_password():
    """测试错误密码时的提示"""
    if not ui_runner.click_element("login_btn.png", "点击登录"):
        return False
    
    ui_runner.input_text("username_field.png", "test@example.com")
    ui_runner.input_text("password_field.png", "wrongpassword")
    ui_runner.click_element("submit_btn.png", "提交")
    time.sleep(1)
    
    # 检查是否出现错误提示
    error_msg = ui_runner.find_element("error_message.png", timeout=5)
    return error_msg is not None

# 执行测试套件
ui_runner.run_test_case("正常登录", test_login_success)
ui_runner.run_test_case("错误密码登录", test_login_with_wrong_password)

# 打印结果
passed = sum(1 for r in ui_runner.results if r["passed"])
print(f"\n=== 测试完成 ===")
print(f"通过:{passed}/{len(ui_runner.results)}")

三、持续集成:每次发版前自动运行测试

在这里插入图片描述

在影刀中创建一个定时任务,在每次发布前(比如下午5点)自动运行测试套件:

import subprocess
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders

def run_all_tests_and_notify():
    """运行所有测试并通过邮件发送报告"""
    print("开始执行自动化测试套件...")
    
    # 执行接口测试
    runner = ApiTestRunner("https://api.yourapp.com", "token")
    # ... 定义所有用例 ...
    runner.generate_report("api_report.xlsx")
    
    total = len(runner.results)
    passed = sum(1 for r in runner.results if r["passed"])
    
    # 发送邮件通知
    if passed < total:
        subject = f"⚠️ 自动化测试失败 - {passed}/{total}通过"
    else:
        subject = f"✅ 自动化测试全通过 - {total}/{total}"
    
    # 通过影刀邮件指令或Python smtplib发送
    print(f"测试完成:{subject}")

run_all_tests_and_notify()

在这里插入图片描述

TEMU店群矩阵自动化运营核价报活动

四、踩坑记录

坑1:图像识别受分辨率和缩放影响
UI测试用的图片素材必须在测试运行的机器上截取,屏幕缩放比例(125%/150%)会导致图像匹配失败。建议将系统缩放设为100%来截图。
在这里插入图片描述

坑2:测试环境数据污染
自动化测试会创建测试订单、测试用户等数据。需要有专门的测试环境或测试完毕后清理数据,不要在生产环境跑测试。

坑3:界面测试速度太慢
界面测试比接口测试慢10-20倍。全量界面回归建议在凌晨离峰时间执行,平时只执行核心路径(冒烟测试)。

坑4:弹窗和loading遮挡
页面加载动画或意外弹窗会导致元素找不到。在关键操作后加time.sleep(2),或等待loading消失再继续。


在这里插入图片描述

总结

用影刀做自动化测试的核心价值:不需要专职QA,发版前10分钟自动跑完回归测试,有问题立即通知。接口测试优先(快、准、稳),界面测试补充核心流程。

署名:林焱

更多推荐