引言

在基于 Dify 的自动化测试中,ollama 和 skyvern 的结合展现出了诸多令人瞩目的优势,为测试流程带来了前所未有的变革 。

从测试效率的角度来看,ollama 凭借其强大的自然语言处理能力,能够根据测试人员的自然语言描述,迅速生成详细且全面的测试用例。这一过程大大缩短了测试用例编写的时间,相比传统的手动编写测试用例方式,效率得到了几何级的提升。例如,在一个复杂的电商应用测试中,传统方式可能需要测试人员花费数天时间来编写各种场景下的测试用例,而 ollama 只需短短几分钟,就能根据简单的自然语言指令,生成涵盖商品搜索、购物车操作、支付流程等各个环节的大量测试用例,包括正常流程和各种异常情况的测试用例 。

skyvern 的网页自动化操作能力也极大地提高了测试执行的效率。它能够自动模拟用户在网页上的各种操作,如点击、输入、滚动等,且操作速度极快,能够在短时间内完成大量的测试任务。而且,skyvern 的自动化操作是基于视觉理解的,不受网页元素定位方式变化的影响,稳定性高,减少了因元素定位失败而导致的测试中断和重试,进一步提高了测试效率 。

在准确性方面,ollama 生成的测试用例基于其对测试需求的深入理解和丰富的测试用例模板库,能够全面覆盖各种测试场景,避免了人工编写测试用例时可能出现的遗漏和疏忽,从而提高了测试的覆盖率和准确性。例如,ollama 可以根据应用的功能特点和业务逻辑,生成各种边界条件和异常情况下的测试用例,确保应用在各种复杂情况下都能正常运行 。

skyvern 的视觉大模型和计算机视觉技术使其在网页元素识别和操作上具有极高的准确性。它能够准确地识别出网页中的各种元素,即使元素的位置、样式发生变化,也能精准定位并进行操作,避免了因元素定位不准确而导致的测试错误。同时,skyvern 在操作过程中会实时进行结果验证,确保每一步操作的正确性,进一步提高了测试结果的准确性 。

从适应性角度来看,ollama 和 skyvern 的结合使得 Dify 自动化测试能够轻松应对各种不同类型和复杂程度的应用。无论是简单的静态网页应用,还是复杂的动态 Web 应用,亦或是移动应用的 Web 端,都能通过 ollama 生成合适的测试用例,利用 skyvern 进行有效的自动化测试。而且,这种技术结合还能够适应不同的测试环境和需求,如不同的浏览器、操作系统等,具有很强的通用性和灵活性 。

实际测试流程演示

以用户登录流程测试为例,让我们来详细领略一下 ollama 与 skyvern 结合后在 Dify 自动化测试中的神奇表现 。

前几个结点看另两篇文章

集成Ollama

https://blog.csdn.net/xy345382605/article/details/159729590?fromshare=blogdetail&sharetype=blogdetail&sharerId=159729590&sharerefer=PC&sharesource=xy345382605&sharefrom=from_link

集成Skyvern

https://blog.csdn.net/xy345382605/article/details/159732579?fromshare=blogdetail&sharetype=blogdetail&sharerId=159732579&sharerefer=PC&sharesource=xy345382605&sharefrom=from_link

挂载测试报告服务

https://blog.csdn.net/xy345382605/article/details/159927004?fromshare=blogdetail&sharetype=blogdetail&sharerId=159927004&sharerefer=PC&sharesource=xy345382605&sharefrom=from_link

第1步:编辑skyvern_server.py

(1)编辑内容

#!/usr/bin/env python3
from flask import Flask, request, jsonify
from playwright.sync_api import sync_playwright
import time
import re
import os
import base64
from datetime import datetime

app = Flask(__name__)

SCREENSHOT_DIR = "/opt/screenshots"
REPORT_DIR = "/opt/reports"
os.makedirs(SCREENSHOT_DIR, exist_ok=True)
os.makedirs(REPORT_DIR, exist_ok=True)

def image_to_base64(image_path):
    try:
        with open(image_path, "rb") as f:
            image_data = f.read()
            return base64.b64encode(image_data).decode('utf-8')
    except Exception as e:
        print(f"Failed to encode image: {e}")
        return None

def generate_html_report(status, message, screenshots):
    status_text = "PASSED" if status == "success" else "FAILED"
    status_color = "#10b981" if status == "success" else "#ef4444"
    status_bg = "#d1fae5" if status == "success" else "#fee2e2"
    status_icon = "✓" if status == "success" else "✗"
    now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
    
    html = '<!DOCTYPE html>\n'
    html += '<html>\n<head>\n'
    html += '<meta charset="UTF-8">\n'
    html += '<title>Test Report</title>\n'
    html += '<style>\n'
    html += '*{margin:0;padding:0;box-sizing:border-box;}\n'
    html += 'body{font-family:Arial,sans-serif;background:linear-gradient(135deg,#667eea,#764ba2);min-height:100vh;padding:40px 20px;}\n'
    html += '.container{max-width:1200px;margin:0 auto;}\n'
    html += '.card{background:white;border-radius:24px;box-shadow:0 20px 60px rgba(0,0,0,0.3);overflow:hidden;}\n'
    html += '.header{background:linear-gradient(135deg,#667eea,#764ba2);color:white;padding:40px;text-align:center;}\n'
    html += '.header h1{font-size:32px;margin-bottom:10px;}\n'
    html += '.time{opacity:0.9;font-size:14px;}\n'
    html += '.badge{display:inline-flex;align-items:center;gap:8px;padding:8px 20px;border-radius:40px;font-size:18px;font-weight:bold;margin-top:20px;background:' + status_bg + ';color:' + status_color + ';}\n'
    html += '.stats{display:grid;grid-template-columns:repeat(3,1fr);gap:20px;padding:30px 40px;background:#f8f9fa;border-bottom:1px solid #e5e7eb;}\n'
    html += '.stat{text-align:center;padding:20px;background:white;border-radius:16px;}\n'
    html += '.stat-val{font-size:36px;font-weight:bold;color:#667eea;}\n'
    html += '.stat-label{color:#6b7280;font-size:14px;margin-top:8px;}\n'
    html += '.content{padding:40px;}\n'
    html += '.title{font-size:20px;font-weight:bold;color:#1f2937;margin-bottom:20px;padding-bottom:10px;border-bottom:3px solid #667eea;display:inline-block;}\n'
    html += '.info{background:#f8f9fa;border-radius:12px;padding:20px;margin:20px 0;}\n'
    html += '.info-row{display:flex;padding:10px 0;border-bottom:1px solid #e5e7eb;}\n'
    html += '.info-label{width:100px;font-weight:bold;color:#4b5563;}\n'
    html += '.info-value{flex:1;color:#1f2937;}\n'
    html += '.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:24px;margin-top:24px;}\n'
    html += '.item{background:#f8f9fa;border-radius:16px;overflow:hidden;box-shadow:0 4px 12px rgba(0,0,0,0.1);transition:transform 0.3s;}\n'
    html += '.item:hover{transform:translateY(-5px);}\n'
    html += '.item img{width:100%;height:auto;display:block;cursor:pointer;}\n'
    html += '.caption{padding:12px 16px;background:white;font-weight:bold;display:flex;align-items:center;gap:8px;}\n'
    html += '.num{width:28px;height:28px;background:#667eea;color:white;border-radius:50%;text-align:center;line-height:28px;font-size:12px;}\n'
    html += '.footer{text-align:center;padding:30px;background:#f8f9fa;color:#9ca3af;font-size:12px;border-top:1px solid #e5e7eb;}\n'
    html += '.modal{display:none;position:fixed;z-index:1000;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.9);cursor:pointer;}\n'
    html += '.modal-img{margin:auto;display:block;max-width:90%;max-height:90%;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);}\n'
    html += '@media(max-width:768px){.header{padding:24px;}.stats{padding:20px;gap:12px;}.content{padding:24px;}.grid{grid-template-columns:1fr;}}\n'
    html += '</style>\n</head>\n<body>\n'
    html += '<div class="container"><div class="card">\n'
    html += '<div class="header"><h1>Auto Test Report</h1>\n'
    html += '<div class="time">Time: ' + now + '</div>\n'
    html += '<div class="badge"><span>' + status_icon + '</span><span>' + status_text + '</span></div>\n</div>\n'
    html += '<div class="stats">\n'
    html += '<div class="stat"><div class="stat-val">' + str(len(screenshots)) + '</div><div class="stat-label">Screenshots</div></div>\n'
    html += '<div class="stat"><div class="stat-val">' + str(len(screenshots)) + '</div><div class="stat-label">Steps</div></div>\n'
    html += '<div class="stat"><div class="stat-val">' + datetime.now().strftime('%H:%M:%S') + '</div><div class="stat-label">Duration</div></div>\n'
    html += '</div>\n'
    html += '<div class="content">\n'
    html += '<div class="title">Test Details</div>\n'
    html += '<div class="info">\n'
    html += '<div class="info-row"><div class="info-label">Status</div><div class="info-value" style="color:' + status_color + ';font-weight:bold;">' + status_text + '</div></div>\n'
    html += '<div class="info-row"><div class="info-label">Message</div><div class="info-value">' + message + '</div></div>\n'
    html += '</div>\n'
    html += '<div class="title" style="margin-top:40px;">Screenshots</div>\n'
    html += '<div class="grid">\n'
    
    for i, ss in enumerate(screenshots, 1):
        filename = ss.split("/")[-1]
        step_name = filename.replace(".png", "").replace("step", "Step").replace("_", " ").title()
        img_base64 = image_to_base64(ss)
        if img_base64:
            html += '<div class="item">\n'
            html += '<img src="data:image/png;base64,' + img_base64 + '" onclick="openModal(this.src)">\n'
            html += '<div class="caption"><span class="num">' + str(i) + '</span><span>' + step_name + '</span></div>\n'
            html += '</div>\n'
    
    html += '</div>\n</div>\n'
    html += '<div class="footer"><p>Powered by <strong>Dify + Skyvern</strong></p></div>\n'
    html += '</div></div>\n'
    html += '<div id="modal" class="modal" onclick="closeModal()"><img class="modal-img" id="modalImg"></div>\n'
    html += '<script>\n'
    html += 'function openModal(src){document.getElementById("modal").style.display="block";document.getElementById("modalImg").src=src;}\n'
    html += 'function closeModal(){document.getElementById("modal").style.display="none";}\n'
    html += '</script>\n</body>\n</html>

(2)停止

docker stop skyvern

docker rm skyver

(3)重新构建并运行

cd /opt/skyvern
docker build -t skyvern .
docker run -d --name skyvern --network host -v /opt/skyvern_screenshots:/opt/screenshots -v /opt/skyvern_reports:/opt/reports skyvern

sleep 5
docker logs skyvern

第2步:添加报告结点

(1)输入变量

变量名:imput_data 

变量值:HTTP请求的body

(2)PYTHON3 代码

def main(input_data):

    import json

   

    # 解析输入

    if isinstance(input_data, dict):

        body = input_data.get("input_data", "{}")

    else:

        body = str(input_data)

   

    # 解析 JSON

    try:

        if isinstance(body, str):

            result = json.loads(body.strip())

        else:

            result = body

    except Exception as e:

        result = {"status": "unknown", "message": str(body)}

   

    # 提取数据

    status = result.get("status", "unknown")

    message = result.get("message", "")

    report_url = result.get("report_url", "")

   

    # 状态图标

    status_icon = "✅" if status == "success" else "❌"

    status_text = "通过" if status == "success" else "失败"

   

    # 生成报告内容

    if report_url:

        report_content = f"""

## 测试结果

- **状态**: {status_icon} {status_text}

- **信息**: {message}

- **详细报告**: [点击查看]({report_url})

"""

    else:

        report_content = f"""

## 测试结果

- **状态**: {status_icon} {status_text}

- **信息**: {message}

- **截图数量**: {len(result.get('screenshots', []))} 张

"""

   

    return {

        "result": report_content

    }

注意点:空格一定要注意,否则要报错

我们可以点击,修改代码

(3)输出变量

第3步:结束节点

(1)添加结点

(2)选择“输出”

(3)输出变量

变量名:HTML_内容

变量值:报告结点的result

第4步:运行测试

第一种:先发布

(1)点运行

(2)在测试需求描述中输入【登录系统】

(3)点击 运行

第二种:直接点测试运行

(2)在测试需求描述中输入【登录系统】

(3)点击 开始运行

实战结果

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐