当前位置:首页>python>通过Python实现需求转为playwright测试脚本

通过Python实现需求转为playwright测试脚本

  • 2026-07-03 23:33:16
通过Python实现需求转为playwright测试脚本

结构

本文介绍如何使用Python实现需求转为playwright测试脚本,本文的思路是:

读取需求->生成测试脚本->是够通过—是—>测试结束       ^     |        |————否—————

本程序的代码结果如下

|——logs|     ||     ———test_run.log(运行后自动生成)|——outputs|     ||     ——— /test_history  (目录、运行后自动生成)|   test_current.py(运行后生成的测试程序)|   fix_report.json(运行后自动生成)||——skills|        ||———/test-orchestrator|     ||——————skill.md: 能力文件|   /scripts|   ||—————————executor.py :运行测试程序|        fixer.py:修复测试程序|        generator.py:生成测试程序|        orchestrator.py:调度者|——main.py  主程序|——req.txt  需求文档

实现程序

main.py

主测试程序python

#!/usr/bin/env python# -*- coding: utf-8 -*-"""一键启动测试自动化系统"""import sysfrom pathlib import Path# 添加脚本路径sys.path.insert(0, str(Path(__file__).parent / "skills" / "test-orchestrator" / "scripts"))from orchestrator import TestOrchestratordef main():    orchestrator = TestOrchestrator(        req_file="req.txt",        max_retry=5,        headless=True    )    success = orchestrator.run()    if success:        print("\n测试自动化完成,所有用例通过!")        print(f"   最终测试文件: outputs/test_current.py")    else:        print("\n达到最大重试次数,请人工介入检查")        print(f"   修复报告: outputs/fix_report.json")    return 0 if success else 1if __name__ == "__main__":    sys.exit(main())

executor.py

测试执行器python

#!/usr/bin/env python"""测试执行器 - 运行 pytest 并解析结果"""import subprocessimport jsonimport refrom pathlib import Pathfrom typing import Dict, Listclass TestExecutor:    def __init__(self, headless: bool = True):        self.headless = headless    def run(self, test_file: str) -> Dict:        """        执行测试文件,返回结构化结果        """        test_path = Path(test_file)        if not test_path.exists():            return {                "success": False,                "error": f"测试文件不存在: {test_file}",                "exit_code": -1,                "passed": [],                "failed": []            }        # 构建命令        cmd = ["pytest", str(test_path), "-v", "--tb=short", "--color=no"]        # 修复:headed 是 flag,不加 =value        if not self.headless:            cmd.append("--headed")        # 添加 JSON 报告        report_file = test_path.parent / ".pytest_report.json"        cmd.extend(["--json-report", f"--json-report-file={report_file}"])        try:            import time            start = time.time()            result = subprocess.run(                cmd,                capture_output=True,                text=True,                timeout=120,                env={**subprocess.os.environ, "PYTHONUNBUFFERED": "1"}            )            duration = time.time() - start            # 解析结果            passed = []            failed = []            for line in result.stdout.split("\n"):                if "PASSED" in line and "::" in line:                    match = re.search(r'(\w+)::(\w+)', line)                    if match:                        passed.append(match.group(2))                elif "FAILED" in line and "::" in line:                    match = re.search(r'(\w+)::(\w+)', line)                    if match:                        failed.append(match.group(2))            error_msg = ""            if report_file.exists():                try:                    with open(report_file) as f:                        report = json.load(f)                    for test in report.get("tests", []):                        if test.get("outcome") == "failed":                            error_msg += test.get("longrepr", "")[:1000]                except:                    pass            if not error_msg:                error_msg = result.stdout + "\n" + result.stderr            return {                "success": result.returncode == 0,                "passed": passed,                "failed": failed,                "error": error_msg[:2000],                "exit_code": result.returncode,                "duration": duration            }        except subprocess.TimeoutExpired:            return {                "success": False,                "error": "测试执行超时(超过 120 秒)",                "exit_code": -2,                "passed": [],                "failed": []            }        except Exception as e:            return {                "success": False,                "error": f"执行异常: {str(e)}",                "exit_code": -3,                "passed": [],                "failed": []            }

fixer.py

修复测试程序python

import osimport reimport loggingfrom typing import Dict, Optional, List, Tuplefrom openai import OpenAIimport ast  logger = logging.getLogger(__name__)class TestFixer:    def __init__(self):        self.client = OpenAI(            api_key=os.getenv("DASHSCOPE_API_KEY"),            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"        )        self.model = os.getenv("QWEN_MODEL", "qwen-plus")        self.max_retries = 3    def _classify_error(self, error_msg: str, traceback: str = "") -> Dict:        """精细化错误分类"""        error_lower = error_msg.lower()        traceback_lower = traceback.lower()        # Playwright 特有错误        if "timeout" in error_lower:            if "expect" in error_lower:                return {"type": "AssertionTimeout", "severity": "high"}            return {"type": "NavigationTimeout", "severity": "medium"}        elif "locator" in error_lower and "not found" in error_lower:            return {"type": "ElementNotFound", "severity": "high"}        elif "not visible" in error_lower or "hidden" in error_lower:            return {"type": "ElementNotVisible", "severity": "medium"}        elif "assertion" in error_lower or "expected" in error_lower:            # 提取实际值和期望值            expected = re.search(r"expected:?\s*['\"](.+?)['\"]", error_msg, re.I)            actual = re.search(r"actual:?\s*['\"](.+?)['\"]", error_msg, re.I)            return {                "type": "AssertionError",                "severity": "high",                "expected": expected.group(1) if expected else None,                "actual": actual.group(1) if actual else None            }        elif "database" in traceback_lower or "mysql" in traceback_lower:            return {"type": "DatabaseError", "severity": "critical"}        else:            return {"type": "UnknownError", "severity": "low"}    def _extract_code_block(self, content: str) -> Optional[str]:        """提取代码块,支持多种格式"""        # 尝试提取 ```python ... ``` 代码块        # 修复了正则表达式中的引号转义问题        patterns = [            r"```python\n(.*?)```",            r"```\n(.*?)```",             "```python\n(.*?)```",            "```\n(.*?)```"        ]        for pattern in patterns:            match = re.search(pattern, content, re.DOTALL)            if match:                return match.group(1).strip()        # 如果没有代码块标记,尝试提取Python代码        if "def test_" in content or "class Test" in content:            return content.strip()        return None    def _generate_fix_prompt(self, code: str, error_info: Dict, requirements: str,                             html_context: str = "", db_context: str = "") -> str:        """生成修复提示词"""        error_detail = error_info.get("error", "无详细信息")        traceback = error_info.get("traceback", "")        error_analysis = self._classify_error(error_detail, traceback)        prompt = f"""            你是一个 Playwright + Pytest 自动化测试专家。请分析错误并修复测试代码。            ## 错误分析            - 错误类型: {error_analysis['type']}            - 严重程度: {error_analysis['severity']}            - 错误信息: {error_detail}            ## 原始需求            {requirements}            ## 失败的测试代码            ```python            {code}            # 完整错误堆栈            {traceback or error_detail}            ```            """        # 添加HTML上下文(如果有)        if html_context:            truncated_html = html_context[:2000] if len(html_context) > 2000 else html_context            prompt += f"""                ## 页面HTML结构(调试信息)                ```html                {truncated_html}                ```            """        # 添加数据库上下文(如果有)        if db_context:            truncated_db = db_context[:2000] if len(db_context) > 2000 else db_context            prompt += f"""            ## 数据库状态            {truncated_db}            """        prompt += """            ## 修复要求            1. 标点符号保护:错误消息必须与需求文档完全一致,保留所有标点符号(,。!)            2. 等待策略:添加合适的等待(wait_for_selector, wait_for_timeout)            3. 错误处理:添加 try-except 和重试机制            4. 数据库清理:确保测试数据正确清理            5. 断言优化:使用 expect() 而不是 assert            ## 常见问题修复指南            - 超时错误:增加 timeout 参数,添加等待条件            - 元素未找到:检查选择器,添加等待,确保页面已加载            - 断言失败:检查实际值格式(如换行符、空格),使用 to_contain_text 而不是 to_have_text            - 数据库错误:检查外键约束,确保测试数据完整            请输出修复后的完整 Python 代码,只返回代码,不要解释。        """        return prompt    def fix(self, code: str, error_info: Dict, requirements: str,    html_context: str = "", db_context: str = "") -> str:        """        修复测试代码        Args:        code: 原始测试代码        error_info: 错误信息 {'error': '错误消息', 'traceback': '堆栈信息'}        requirements: 产品需求文档        html_context: 页面HTML上下文(可选)        db_context: 数据库状态上下文(可选)        """        for attempt in range(self.max_retries):            try:                prompt = self._generate_fix_prompt(                code, error_info, requirements, html_context, db_context                )                response = self.client.chat.completions.create(                model=self.model,                messages=[{"role": "user", "content": prompt}],                temperature=0.1,                max_tokens=4000                )                content = response.choices[0].message.content                fixed_code = self._extract_code_block(content)                if not fixed_code:                    logger.warning(f"尝试 {attempt + 1}: 无法提取代码块,使用原始内容")                    fixed_code = content.strip()                # 验证修复后的代码基本结构                if self._validate_code(fixed_code):                    logger.info(f"修复成功 (尝试 {attempt + 1}/{self.max_retries})")                    logger.info(f"代码长度: {len(fixed_code)} 字符")                    return fixed_code                else:                    logger.warning(f"尝试 {attempt + 1}: 代码验证失败,重试...")            except Exception as e:                logger.error(f"修复尝试 {attempt + 1} 失败: {e}")                if attempt == self.max_retries - 1:                    logger.error("达到最大重试次数,返回原始代码")                    return code        return code    def _validate_code(self, code: str) -> bool:        """验证修复后的代码基本结构"""        required_elements = [            "def test",            "self.page",            "expect("        ]        # 修复逻辑:只有在发现缺失元素时才返回 False,否则循环结束后返回 True        for element in required_elements:            if element not in code:                logger.warning(f"代码缺少必要元素: {element}")                return False # 只有在缺失时才返回 False        return True # 所有元素都存在,返回 True

generator.py

产生测试程序python

#!/usr/bin/env python# -*- coding: utf-8 -*-"""测试代码生成器 - 调用 LLM 从需求生成 Playwright + Pytest 代码"""import osimport sysimport jsonimport reimport loggingfrom typing import List, Dictfrom openai import OpenAIlogger = logging.getLogger(__name__)class TestGenerator:    def __init__(self):        self.client = OpenAI(            api_key=os.getenv("DASHSCOPE_API_KEY"),            base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"        )        self.model = os.getenv("QWEN_MODEL", "qwen-plus")    def _sanitize_text(self, text: str) -> str:        """清理文本中的特殊字符,但保留标点符号"""        if not text:            return text        # 只移除表情符号和特殊 Unicode 字符,保留中文标点保留的标点:,。!?;:、""''()【】        cleaned = re.sub(r'[^\u4e00-\u9fff\u3400-\u4dbf\u0000-\u007f\u0080-\u00ff\u3000-\u303f\uff00-\uffef]', '', text)        return cleaned    def _extract_code(self, content: str) -> str:        """从 LLM 返回内容中提取代码"""        content = content.strip()        # 查找代码块        if "```python" in content:            start = content.find("```python") + 9            end = content.find("```", start)            if end != -1:                return content[start:end].strip()        elif "```" in content:            parts = content.split("```")            if len(parts) >= 2:                code = parts[1].strip()                # 移除语言标识                lines = code.split("\n")                if lines and lines[0].lower() in ["python", "py"]:                    code = "\n".join(lines[1:]).lstrip()                return code        return content    def generate(self, requirements: str) -> str:        """让 LLM 直接生成 Playwright 测试代码 - 使用参数化形式"""        # 清理输入        requirements = self._sanitize_text(requirements)        # 强调使用参数化的 Prompt        prompt = f"""            你是一个 Playwright + Pytest 自动化测试专家。请根据以下产品需求,直接生成可执行的端到端测试代码。            ## 产品需求            {requirements}            ## 核心要求            ## 标点符号保护规则(最高优先级 - 违反将导致测试失败)            【严重警告】你必须严格遵守以下规则,否则测试代码将无法通过:            1. 绝对保留所有标点符号:逗号(,)、句号(。)、感叹号(!)、问号(?)、分号(;)、冒号(:)、顿号(、)            2. 严禁删除、替换或省略任何标点符号            3. 错误消息必须与需求文档中的文本完全一致,包括标点符号            【正确示例 - 必须这样做】:               ERROR_MESSAGES = {{                   "CONTACT_NOT_FOUND": "您输入的手机号或Email不存在,请重新输入!",                   "VERICODE_ERROR": "验证码错误,请重新输入!",                   "PASSWORD_USED_BEFORE": "这个密码以前设置过,请用一个新密码!",               }}            【错误示例 - 绝对禁止这样做】:               ERROR_MESSAGES = {{                   "CONTACT_NOT_FOUND": "您输入的手机号或Email不存在请重新输入",  # 错误!缺少逗号和感叹号                   "VERICODE_ERROR": "验证码错误请重新输入",  # 错误!缺少逗号和感叹号                   "PASSWORD_USED_BEFORE": "这个密码以前设置过请用一个新密码",  # 错误!缺少逗号和感叹号               }}            4. 所有断言中使用的错误消息字符串必须包含完整标点符号            5. 生成 ERROR_MESSAGES 字典时,必须逐字复制需求文档中的错误消息,包括标点            - 分析需求:理解需求中的功能点、业务流程、验证规则、边界条件            - 设计测试用例:覆盖正常流程、异常流程、边界条件            - 使用参数化:必须使用 `from parameterized import parameterized` 和 `@parameterized.expand`            - 生成代码:为相似场景使用参数化,不同场景创建独立测试函数            - 所有测试方法必须放在测试类中            -  不测试字段为空的情形            ## 技术规范            - 使用 Playwright 同步 API:`from playwright.sync_api import Page, expect`            - 使用 pytest 框架            - 使用 `@pytest.fixture(autouse=True)` 在每个测试前后执行数据库清理            - 使用 `@pytest.mark.usefixtures("db_cleanup")` 应用到测试类            - 选择器优先级:              * `page.get_by_role("button", name="按钮文字")` - 按角色定位              * `page.get_by_label("字段标签")` - 按标签定位              * `page.get_by_placeholder("提示文字")` - 按占位符定位              * `page.locator("#id")` - 按 ID 定位              * `page.locator(".class")` - 按class定位            - URL 地址:根据需求中的描述推断,默认使用 `http://localhost:3000`            - 等待策略:优先使用 `expect(...).to_be_visible()` 而非固定延时            - 对于反向测试用例,一次经允许出现一处错误的数据            - 判断URL请使用语句:assert self.page.url.startswith(REGISTER_URL)            ## 代码模板            ```            import pytest            from playwright.sync_api import Page, expect            import pymysql            DB_CONFIG = {{                'host': 'localhost',                'user': 'root',                'password': '123456',                'database': 'chatgptebusiness'            }}            REGISTER_URL = "http://127.0.0.1:8080/ChatGPTEbusiness/jsp/RegisterPage.jsp"            MESSAGES = {{                "LOGIN_PAGE": "登录页面",                "INVALID_USERNAME": "账号必须是5-20位的字母或数字",                "INVALID_PASSWORD": "密码必须包含大小写字母数字和特殊字符,长度在5-30之间",                            "INVALID_REPASSWORD": "密码确认不一致",                "INVALID_PHONE": "请输入有效的中国手机号",                "INVALID_EMAIL": "请输入有效的Email地址",                "USERNAME_CONFLICT": "注册用户的用户名必须唯一",                "PHONE_CONFLICT": "注册用户的手机必须唯一",                "EMAIL_CONFLICT": "注册用户的邮箱必须唯一",            }}            class TestUserRegistration:            @pytest.fixture(autouse=True)            def setup_and_teardown(self, page: Page):                \"\"\"每个测试前后执行数据库清理,并注入page\"\"\"                self.page = page                self.db = pymysql.connect(**DB_CONFIG)                self._clear_tables()                yield                self.db.close()            def _clear_tables(self):                \"\"\"清空数据库表确保测试环境干净\"\"\"                try:                    with self.db.cursor() as cursor:                        cursor.execute("SET FOREIGN_KEY_CHECKS = 0")                        cursor.execute("DELETE FROM user")                        cursor.execute("SET FOREIGN_KEY_CHECKS = 1")                        self.db.commit()                except Exception as e:                    pytest.fail(f"数据库表清除失败: {{str(e)}}")            def _fill_registration_form(self, username: str, password: str,                                        repassword: str, phone: str, email: str):                \"\"\"填写注册表单并点击注册按钮 - 使用正确的定位器\"\"\"                # 等待页面完全加载                self.page.wait_for_load_state("networkidle")                # 使用 ID 定位器                self.page.locator("#username").fill(username)                self.page.locator("#password").fill(password)                self.page.locator("#confirmPassword").fill(repassword)                self.page.locator("#phone").fill(phone)                self.page.locator("#email").fill(email)                # 点击注册按钮                self.page.locator("button[type='submit']").click()            @pytest.mark.parametrize("username,password,repassword,phone,email,expected_message", [                # GTC-001: Valid registration                ("jerrygu", "Zxcv123@", "Zxcv123@", "13687654321", "a@126.com", MESSAGES["LOGIN_PAGE"]),                # GTC-002: Username too short                ("abc", "Zxcv123@", "Zxcv123@", "13687654321", "a@126.com", MESSAGES["INVALID_USERNAME"]),                ...                # GTC-007: Password too long                ("jerrygu", "A" * 31 + "Zxcv123@", "A" * 31 + "Zxcv123@", "13687654321", "a@126.com", MESSAGES["INVALID_PASSWORD"]),                ...                ("jerrygu", "Zxcv123@", "Zxcv123@", "1368765432", "a@126.com", MESSAGES["INVALID_PHONE"]),                ...                ("jerrygu", "Zxcv123@", "Zxcv123@", "13687654321", "a126@com", MESSAGES["INVALID_EMAIL"]),                ...            ])            def test_registration_validation(self, username, password, repassword, phone, email, expected_message):                \"\"\"测试注册表单各字段验证规则参数化\"\"\"                self.page.goto(REGISTER_URL)                # 等待页面加载                self.page.wait_for_load_state("networkidle")                # 填写表单                self._fill_registration_form(username, password, repassword, phone, email)                # 根据预期结果进行断言                if expected_message == MESSAGES["LOGIN_PAGE"]:                    # 成功注册,验证跳转到登录页                    expect(self.page).to_have_title(MESSAGES["LOGIN_PAGE"], timeout=10000)                    # 验证用户已写入数据库                    with self.db.cursor() as cursor:                        cursor.execute("SELECT COUNT(*) FROM user WHERE username = %s", (username,))                        count = cursor.fetchone()[0]                        assert count == 1, f"Expected 1 user with username '{{username}}' but found {{count}}"                else:                    # 等待错误信息出现                    # 验证错误提示                    if "账号必须是" in expected_message:                        error_locator = self.page.locator("#usernameError")                    elif "密码必须包含" in expected_message:                        error_locator = self.page.locator("#passwordError")                    elif "密码确认不一致" in expected_message:                        error_locator = self.page.locator("#confirmPasswordError")                    elif "请输入有效的中国手机号" in expected_message:                        error_locator = self.page.locator("#phoneError")                    elif "请输入有效的Email地址" in expected_message:                        error_locator = page.locator("#emailError")                    else:                        error_locator = self.page.locator("#registerError")                    expect(error_locator).to_contain_text(expected_message, timeout=5000)                    # 验证停留在注册页面                    assert self.page.url.startswith(REGISTER_URL)            @pytest.mark.parametrize("username,password,repassword,phone,email,dup_username,dup_phone,dup_email,expected_message", [                # GTC-021: Duplicate username                ("jerrygu", "Zxcv123@", "Zxcv123@", "13687654321", "a@126.com",                  "jerrygu", "18767896543", "b@126.com", MESSAGES["USERNAME_CONFLICT"]),                # GTC-022: Duplicate phone                ("jerrygu", "Zxcv123@", "Zxcv123@", "13687654321", "a@126.com",                  "peter", "13687654321", "c@126.com", MESSAGES["PHONE_CONFLICT"]),                # GTC-023: Duplicate email                ("jerrygu", "Zxcv123@", "Zxcv123@", "13687654321", "a@126.com",                  "peter", "18767896543", "a@126.com", MESSAGES["EMAIL_CONFLICT"]),            ])            def test_duplicate_constraint(self, username, password, repassword, phone, email,                                          dup_username, dup_phone, dup_email, expected_message):                \"\"\"测试唯一性约束:用户名、手机号、邮箱重复注册\"\"\"                # 第一次注册 - 应该成功                self.page.goto(REGISTER_URL)                self.page.wait_for_load_state("networkidle")                self._fill_registration_form(username, password, repassword, phone, email)                expect(self.page).to_have_title(MESSAGES["LOGIN_PAGE"], timeout=10000)                # 第二次注册 - 应该失败并显示冲突错误                self.page.goto(REGISTER_URL)                self.page.wait_for_load_state("networkidle")                self._fill_registration_form(dup_username, password, repassword, dup_phone, dup_email)                # 检查注册错误信息                expect(self.page.locator("#registerError")).to_contain_text(expected_message, timeout=5000)                assert self.page.url.startswith(REGISTER_URL)                # 验证没有创建重复用户                with self.db.cursor() as cursor:                    cursor.execute("SELECT COUNT(*) FROM user WHERE username = %s OR phone = %s OR email = %s",                                   (dup_username, dup_phone, dup_email))                    count = cursor.fetchone()[0]                    assert count == 1, f"重复注册意外插入了用户,找到 {{count}} 条记录"            def test_password_confirmation_mismatch_after_typing(self):                \"\"\"测试密码确认框实时校验输入不一致时显示错误\"\"\"                ...                ```                **重要提醒**                - 测试类必须使用 @pytest.mark.usefixtures("db_cleanup") 装饰器                - 测试类名使用 PascalCase,如 TestUserLogin、TestRegistrationPage                - 测试方法名使用 snake_case,如 test_valid_login、test_password_validation                - 如果测试方法不在类中使用参数化,则 page: Page 是唯一参数                **输出要求**                - 只返回 Python 代码,不要任何解释                - 代码必须可以直接执行                - 测试函数命名使用英文,清晰描述测试场景                - 添加必要的注释说明关键步骤                - 不要使用任何表情符号或特殊 Unicode 字符                - 优先使用 @pytest.mark.parametrize 处理多组测试数据                - 请生成测试代码        """        response = self.client.chat.completions.create(            model=self.model,            messages=[{"role": "user", "content": prompt}],            temperature=0.0,            max_tokens=8000        )        content = response.choices[0].message.content        content = self._sanitize_text(content)        #提取代码        code = self._extract_code(content)        #确保有必要的导入        if "from parameterized import parameterized" not in code:            #在导入部分添加 parameterized            if "from playwright.sync_api" in code:                code = code.replace(                    "from playwright.sync_api import Page, expect",                    "from playwright.sync_api import Page, expect\nfrom parameterized import parameterized"                )            else:                code = "import re\nimport pytest\nfrom playwright.sync_api import Page, expect\nfrom parameterized import parameterized\n\n" + code        return code    def fix(self, code: str, error_info: Dict, requirements: str) -> str:        """修复错误的测试代码 - 保持参数化形式"""        #清理输入        requirements = self._sanitize_text(requirements)        code = self._sanitize_text(code)        error_msg = error_info.get('error', '无详细信息')        prompt = f"""            你是一个 Playwright + Pytest 自动化测试专家。下面的测试代码运行失败了,请分析错误并修复。            **原始产品需求**            {requirements}   **当前测试代码(有错误)**            ```            {code}            ```   **实际运行错误**            ```            {error_msg}            ```            请根据错误信息输出修复后的完整 Python 代码,只返回代码。            """        try:            response = self.client.chat.completions.create(                model=self.model,                messages=[{"role": "user", "content": prompt}],                temperature=0.1,                max_tokens=4000            )            content = response.choices[0].message.content            content = self._sanitize_text(content)            fixed_code = self._extract_code(content)            #确保修复后的代码也包含 parameterized 导入            if "from parameterized import parameterized" not in fixed_code:                if "from playwright.sync_api" in fixed_code:                    fixed_code = fixed_code.replace(                        "from playwright.sync_api import Page, expect",                        "from playwright.sync_api import Page, expect\nfrom parameterized import parameterized"                    )                else:                    fixed_code = "from parameterized import parameterized\n" + fixed_code            return fixed_code        except Exception as e:            logger.error(f"修复代码时出错: {e}")            return self._generate_fallback(requirements)    def _generate_fallback(self, requirements: str) -> str:        """生成降级模板 - 使用参数化形式"""        requirements = self._sanitize_text(requirements)        return f'''            """            自动生成测试模板 - 请根据以下需求手动完善            需求摘要:{requirements}            **使用说明: **                          使用 @pytest.mark.parametrize 装饰器组织测试数据            根据需求中的功能点设计测试用例            使用 Playwright 定位器选择页面元素            添加适当的断言验证            """            import re            import pytest            from playwright.sync_api import Page, expect            from parameterized import parameterized            定义测试数据            TEST_DATA = [                ("正常流程", "valid_input", "expected_success"),                ("异常流程", "invalid_input", "expected_error"),            ]            @pytest.mark.parametrize(TEST_DATA)            def test_feature_scenarios(page: Page, scenario_name, test_input, expected_output):                """参数化测试 - {scenario_name}"""                导航到页面                page.goto("http://localhost:3000/")                TODO: 根据需求添加测试步骤                page.get_by_label("字段名").fill(test_input)                page.get_by_role("button", name="按钮文字").click()                断言验证                expect(page.get_by_text(expected_output)).to_be_visible()                pass            '''

orchestrator.py

总调度python

#核心编排器#!/usr/bin/env python"""测试编排器 - 实现"生成→运行→修复"闭环"""import osimport sysimport jsonimport timeimport shutilimport loggingimport subprocessfrom pathlib import Pathfrom datetime import datetimefrom typing import Optional, Dict, List, Tupleimport re# 添加项目根目录到路径sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent))from generator import TestGeneratorfrom executor import TestExecutorfrom fixer import TestFixer# ========== 日志配置 ==========logging.basicConfig(    level=logging.INFO,    format='%(asctime)s | %(levelname)-8s | %(message)s',    handlers=[        logging.FileHandler('logs/test_run.log'),        logging.StreamHandler()    ])logger = logging.getLogger(__name__)class TestOrchestrator:    """测试编排器 - 协调生成、执行、修复的完整流程"""    def __init__(        self,        req_file: str = "req.txt",        max_retry: int = 5,        headless: bool = True,        output_dir: str = "outputs",        llm_config: Optional[Dict] = None,        custom_rules: Optional[str] = None    ):        self.req_file = Path(req_file)        self.max_retry = max_retry        self.headless = headless        self.output_dir = Path(output_dir)        self.history_dir = self.output_dir / "test_history"        # 保存新增的参数        self.llm_config = llm_config or {}        self.custom_rules = custom_rules or ""        # 确保目录存在        self.output_dir.mkdir(parents=True, exist_ok=True)        self.history_dir.mkdir(parents=True, exist_ok=True)        Path("logs").mkdir(exist_ok=True)        # 初始化组件 - 传递配置        self.generator = TestGenerator()        self.executor = TestExecutor(headless=headless)        self.fixer = TestFixer()        # 状态记录        self.current_test_file = self.output_dir / "test_current.py"        self.fix_report_file = self.output_dir / "fix_report.json"        self.fix_history: List[Dict] = []        self.iteration = 0        self.preserve_punctuation = True    def read_requirements(self) -> str:        """读取需求文件"""        if not self.req_file.exists():            raise FileNotFoundError(f"需求文件不存在: {self.req_file}")        content = self.req_file.read_text(encoding="utf-8")        logger.info(f"已读取需求文件 ({len(content)} 字符)")        return content    def save_version(self, code: str, iteration: int, status: str):        """保存代码版本到历史目录"""        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")        version_file = self.history_dir / f"test_v{iteration:02d}_{status}_{timestamp}.py"        version_file.write_text(code, encoding="utf-8")        logger.debug(f"已保存版本: {version_file.name}")    def run_single_iteration(self, requirements: str, previous_code: Optional[str] = None) -> Tuple[bool, str, Dict]:        """        执行单次迭代:生成/修复 → 运行 → 返回结果        Returns:            (success, code, execution_result)        """        self.iteration += 1        logger.info(f"\n{'='*50}")        logger.info(f"第 {self.iteration} 轮迭代开始")        logger.info(f"{'='*50}")        # Step 1: 生成或获取代码        if previous_code is None:            logger.info("首次生成测试代码...")            code = self.generator.generate(requirements)            action = "generate"        else:            logger.info("尝试修复代码...")            # 需要上次执行的错误信息            last_result = self.fix_history[-1] if self.fix_history else {}            code = self.fixer.fix(previous_code, last_result, requirements)            action = "fix"        # 保存版本        self.save_version(code, self.iteration, action)        # 写入当前测试文件        self.current_test_file.write_text(code, encoding="utf-8")        logger.info(f"测试代码已写入: {self.current_test_file}")        # Step 2: 执行测试        logger.info("执行测试...")        exec_result = self.executor.run(str(self.current_test_file))        # Step 3: 记录结果        record = {            "iteration": self.iteration,            "action": action,            "timestamp": datetime.now().isoformat(),            "success": exec_result["success"],            "passed_tests": exec_result.get("passed", []),            "failed_tests": exec_result.get("failed", []),            "error": exec_result.get("error"),            "exit_code": exec_result.get("exit_code")        }        self.fix_history.append(record)        # 更新修复报告        self._save_fix_report()        # 打印结果        if exec_result["success"]:            logger.info(f"第 {self.iteration} 轮测试全部通过!")            logger.info(f"通过用例: {exec_result.get('passed', [])}")        else:            logger.warning(f"第 {self.iteration} 轮测试失败")            logger.warning(f"失败用例: {exec_result.get('failed', [])}")            if exec_result.get("error"):                logger.warning(f" 错误摘要: {exec_result['error'][:200]}...")        return exec_result["success"], code, exec_result    def _save_fix_report(self):        """保存修复过程报告"""        report = {            "req_file": str(self.req_file),            "max_retry": self.max_retry,            "final_success": self.fix_history[-1]["success"] if self.fix_history else False,            "total_iterations": self.iteration,            "history": self.fix_history        }        self.fix_report_file.write_text(            json.dumps(report, indent=2, ensure_ascii=False),            encoding="utf-8"        )    def run(self) -> bool:        """        主流程:持续迭代直到全部通过或达到最大重试        Returns:            True 如果最终全部通过,False 如果达到上限仍有失败        """        logger.info("="*60)        logger.info("启动测试编排器")        logger.info(f"需求文件: {self.req_file}")        logger.info(f"最大重试: {self.max_retry}")        logger.info(f"无头模式: {self.headless}")        logger.info("="*60)        # 读取需求        requirements = self.read_requirements()        success = False        current_code = None        last_result = None        # 迭代循环        while self.iteration < self.max_retry:            success, current_code, last_result = self.run_single_iteration(                requirements, current_code            )            if success:                break        # 最终结果处理        logger.info("\n" + "="*60)        if success:            logger.info(" 所有测试通过!")            logger.info(f"最终代码: {self.current_test_file}")            logger.info(f"总迭代次数: {self.iteration}")            logger.info(f"修复报告: {self.fix_report_file}")            return True        else:            logger.error(f"达到最大重试次数 ({self.max_retry}),仍有测试失败")            logger.error(f"失败的用例: {last_result.get('failed', []) if last_result else '未知'}")            logger.error(f"请查看修复报告: {self.fix_report_file}")            return Falsedef main():    import argparse    parser = argparse.ArgumentParser(description="测试编排器 - 自动生成、执行、修复测试")    parser.add_argument("--req_file", type=str, default="req.txt")    parser.add_argument("--max_retry", type=int, default=5)    parser.add_argument("--headless", action="store_true", default=True)    parser.add_argument("--no-headless", action="store_false", dest="headless")    parser.add_argument("--verbose", action="store_true")    args = parser.parse_args()    if args.verbose:        logging.getLogger().setLevel(logging.DEBUG)    orchestrator = TestOrchestrator(        req_file=args.req_file,        max_retry=args.max_retry,        headless=args.headless    )    success = orchestrator.run()    sys.exit(0 if success else 1)if __name__ == "__main__":    main()

SKILL.md

markdown

---name: test-orchestratordescription: |  端到端测试自动化编排器。  读取需求 → 生成测试 → 执行 → 失败自动修复 → 重试,直至全部通过。version: 1.0.0author: AI Agent---# 测试编排技能## 能力概述本技能实现了一个闭环的测试自动化流程:1. 从 `req.txt` 解析产品需求2. 调用 LLM 设计测试用例并生成 Playwright + Pytest 代码3. 自动执行生成的测试4. 如果失败,分析错误并调用 LLM 修复代码5. 重复步骤 3-4 直到全部通过或达到最大重试次数(默认 5 次)## 输入参数- `--req_file`: 需求文件路径,默认 `req.txt`- `--max_retry`: 最大修复重试次数,默认 `5`- `--headless`: 是否无头模式运行,默认 `true`- `--verbose`: 详细日志输出## 输出产物- `outputs/test_current.py`: 最终通过的测试代码- `outputs/fix_report.json`: 修复过程记录- `logs/test_run.log`: 完整运行日志## 编排流程┌─────────────┐│ 读取 req.txt              │└──────┬──────┘┌─────────────┐│ LLM 生成代码         │◄─────────────────┐└──────┬──────┘ │▼ │┌─────────────┐ ││ 执行测试 │               │└──────┬──────┘ │▼ │┌───────┐ 否 ││ 通过?       ├────────┐ │└───┬───┘ ▼ ││是 ┌─────────────┐ │▼ │ 分析错误 │ │┌────────┐ │ LLM 修复代码│────┘│ 完成! │ └─────────────┘└────────┘ (最多 5 次)## 退出码- `0`: 所有测试通过- `1`: 达到最大重试次数仍有失败- `2`: 需求文件不存在或格式错误

需求文档

注册需求

****基本需求****输入username(#username)、password(#password)、confirmPassword(#confirmPassword)、phone(#phone)、email(#email单击【注册】按键注册成功,进入登录页面,标题为“登录页面”,URL为:http://127.0.0.1:8080/ChatGPTEbusiness/jsp/LoginPage.jsp****格式要求****- 账号(必填):文本框,长度为5-20位,可以包含大小写英文字符(必填)或数字(选填)。<label for="username">账号 (5-20位字母或数字):</label><input type="text" id="username" name="username" placeholder="输入账号" autocomplete="username" required>- 密码(必填):密码框,长度为5-30位,必须包含大小写英文字符、数字和特殊字符,密码通过SHA256散列进行传输和存储。<label for="password">密码 (5-30位,包含大小写字母、数字和特殊字符):</label><input type="password" id="password" name="password" placeholder="输入密码" autocomplete="current-password" required>- 确认密码(必填):密码框,确认密码的值必须与密码的值一致。<label for="confirmPassword">密码确认:</label><input type="password" id="confirmPassword" name="confirmPassword"  placeholder="输入确认密码" autocomplete="current-password" required>- 手机号(必填):手机框,需符合中国手机号码格式。<label for="phone">手机号 (中国):</label><input type="tel" id="phone" name="phone"  placeholder="输入手机号" autocomplete="tel" required>- Email(必填):Email框,需符合国际标准Email格式<label for="email">邮箱:</label><input type="email" id="email" name="email"  placeholder="输入邮箱" autocomplete="email" required><label for="email">邮箱:</label>****错误提示****-  输入非法格式的账号,比如长度<5位、长度<20位 、不包含大小写英文字符,提交后报'账号必须是5-20位的字母或数字'错误信息,停留在注册页面<div id="usernameError" class="error"></div>-  输入非法格式的密码,比如长度<5位、长度<30位 、不包含大小写英文字符、数字、特殊字符,提交后报'密码必须包含大小写字母数字和特殊字符,长度在5-30之间'错误信息,停留在注册页面<div id="passwordError" class="error"></div>-  输入合法的密码,但是确认密码与密码不一致,提交后报'密码确认不一致'错误信息,停留在注册页面<div id="confirmPasswordError" class="error"></div>-  输入非法格式的手机号,比如长度不为11的数字、非1开始的数值,电话号中含有大小写英文字符,提交后报'请输入有效的中国手机号'错误信息,停留在注册页面<div id="phoneError" class="error"></div>-  输入非法格式的email,比如a@com邮件(其他错误格式HTML5会自动校验)、提交后报'请输入有效的Email地址'错误信息,停留在注册页面<div id="emailError" class="error"></div>-  注册的用户账号已经存在,报'注册用户的用户名必须唯一'错误信息,停留在注册页面-  注册的手机已经存在,报'注册用户的手机必须唯一'错误信息,停留在注册页面-  注册的Email已经存在,报'注册用户的邮箱必须唯一'错误信息,停留在注册页面<div id="registerError" class="error"></div>- 测试注册按钮在必填字段为空时不被禁用****数据库信息****# 测试环境配置SQL  ```DB_CONFIG = {    'host': 'localhost',    'user': 'root',    'password': '123456',    'database': 'chatgptebusiness'}- user表格式   id int 自增1   username varchar(50)   password varchar(100)   phone         varchar(50)   email         varchar(50) ```- 请执行每个测试用例前,建立数据库连接,清除user表;执行每个测试用例后,请断开数据库连接- 注册用户成功,请进入数据库中进行检查,注册的数据是否正确存在数据库中****URL****- url=http://127.0.0.1:8080/ChatGPTEbusiness/jsp/RegisterPage.jsp

需求包括:

  • 基本需求:描述 正常情况
  • 格式要求:包括每个字段的格式以及在HTML中的位置
  • 错误提示:什么情况下报什么错误,错误出现在什么位置
  • 数据库信息:由于在测试中需要操作数据库,所有必须把数据库信息告诉智能体
  • URL:产品信息关系到的URL

登录需求

****基本需求****- 输入username(#username)、password(#password)单击【登录】按键- 登录成功,进入欢迎页面,标题为“系统欢迎您”,URL为:127.0.0.1:8080/ChatGPTEbusiness/jsp/WelcomePage.jsp****格式要求****- 用户名(必填):文本框,长度为5-20位,必须包含大写或小写英文字符,也可以包含数字,不允许包含除大小写英文字符、数字其他外的字符(/^[a-zA-Z0-9]{5,20}$/)。<label for="username">用户名:</label><input type="text" id="username" name="username" placeholder="输入账号" autocomplete="username" required>- 密码(必填):密码框,长度为5-30位,必须包含大小写英文字符、数字和特殊字符(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/)。密码通过SHA256散列进行传输和存储。<label for="password">密码:</label><input type="password" id="password" name="password" placeholder="输入密码" autocomplete="current-password" required>****错误提示****-  输入非法格式的账号,比如长度<5位、长度<20位 、不包含大小写英文字符,报'账号必须是5-20位的字母或数字'错误信息,停留在注册页面<div id="usernameError" class="error"></div>-  输入非法格式的密码,比如长度<5位、长度<30位 、不包含大小写英文字符、数字、特殊字符,报'密码必须包含大小写字母数字和特殊字符,长度在5-30之间'错误信息,停留在注册页面- 用户名或密码错误<div id="loginError" class="error"></div>- 登录失败****数据库信息****SQL  ```# 测试环境配置DB_CONFIG = {    'host': 'localhost',    'user': 'root',    'password': '123456',    'database': 'chatgptebusiness'}- user表格式   id int 自增1   username varchar(50)   password varchar(100)   phone         varchar(50)   email         varchar(50) ```- 请执行每个测试用例前,建立数据库连接,在user表中插入一条user表信息(password通过SHA256散列);执行每个测试用例后,请断开数据库连接****URL****- url=http://127.0.0.1:8080/ChatGPTEbusiness/jsp/LoginPage.jsp

注意

  • 登录需求与注册需求相当,变动对应部分即可

找回密码需求

***找回密码产品需求***- 1.进入输入手机号或Email页面- 2.如果手机号或Email格式不正确,前端返回:"请输入有效的中国手机号或Email"- 3.将手机号或Email传入后端,后端验证是否存在这个手机号或Email- 4.如果不存在后端返回:"您输入的手机号或Email不存在,请重新输入!" **注意**不是“您输入的手机号或Email不存在请重新输入”- 5.如果手机号存在,记录这个手机号所属于的用户ID号(uid);如果Email存在,记录这个Email所属于的用户ID号(uid)- 6.生成验证码(6位数字),存储在表code中,值为(id,uid,code),其中id自增一、uid用户外键、code验证码- 7.如果是手机号向用户手机发送验证码;如果是Email向Email发送验证码。- 8.前端进入重置密码页面- 9.用户输入验证码(6位数字)、新密码(长度为5-30位,必须包含大小写英文字符、数字和特殊字符,密码通过SHA256散列进行传输和存储)和新密码确认码。- 10.验证码不符合格式,前端返回:"验证码必须是6位数字"- 11. 新密码不符合格式,前端返回:"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"- 12.新密码与新密码确认码,前端返回:"密码确认不一致!"- 13.将验证码、新密码与新密码确认码传入后端- 14. 如果验证码不正确,后端返回提示信息:"验证码错误"。- 15.与password比较,如果新密码以前使用过,后端返回提示信息:"新密码以前使用过,请设置其他密码"。- 16.否则,将旧密码添加至密码历史password表中(id,uid,password)。其中id自增一、uid用户外键、password旧密码。- 17.将新密码更新至当前用户user表中。- 18.删除验证码code表中的临时验证码记录。- 19.设置成功后,重定向至登录页面。***各字段格式要求*******VeriCodePage.jsp****- 手机号码或电子邮箱,格式:手机号码或电子邮箱 <label for="contact">手机号码或电子邮箱:</label> <input type="text" id="contact" name="contact" placeholder="输入手机号码或邮箱" required>- 提交按钮:<button type="submit" id="sendCode">发送验证码</button>- 网页标题:输入手机号码或电子邮箱 验证请用:assert CONTRACT_URL in self.page.url****RecoverPage.jsp****- 验证码:6位数字:/^\d{6}$/ <label for="identifyingCode">验证码:</label> <input type="text" id="identifyingCode" name="identifyingCode" placeholder="输入验证码" maxlength="6" required>- 新密码,密码框,长度为5-30位,必须包含大小写英文字符、数字和特殊字符(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/) <label for="newPassword">新密码:</label>  <input type="password" id="newPassword" name="newPassword" placeholder="输入新密码" required>- 新密码确认码,密码框,长度为5-30位,必须包含大小写英文字符、数字和特殊字符(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[\W_]).{5,30}$/) <label for="confirmPassword">确认新密码:</label> <input type="password" id="confirmPassword" name="confirmPassword" placeholder="确认新密码" required> - 提交按钮 <button type="submit">确定</button>- 建议:定位"输入新密码"与"确认新密码"使用 self.page.locator("#newPassword").fill(password) self.page.locator("#confirmPassword").fill(confirm_password)- 网页标题:找回密码 验证请用:assert VERICODE_URL in self.page.url***断言信息*******VeriCodePage.jsp****- 提交成功,进入重置密码页面,页面"找回密码"- 您输入的手机号或Email不存在,请重新输入!**注意**不是“您输入的手机号或Email不存在请重新输入” <div id="VeriCodeError" class="error"></div>- 不考虑字段为空的情形****RecoverPage.jsp****- 提交成功,进入登录页面,页面"登录页面"- 验证码必须是6位数字 <div id="identifyingCodeError" class="error"></div>- 密码必须包含大小写字母、数字和特殊字符,长度在5-30之间 <div id="newPasswordError" class="error"></div>- 密码确认不一致! <div id="confirmPasswordError" class="error"></div>- 验证码错误,请重新输入!- 这个密码以前设置过,请用一个新密码! <div id="recoverError" class="error"></div>- 不考虑字段为空的情形***数据库信息***```    DB_CONFIG = {    'host': 'localhost',    'user': 'root',    'password': '123456',    'database': 'chatgptebusiness'}```****user表格式****SQL  ``` CREATE TABLE IF NOT EXISTS user( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL, password VARCHAR(100) NOT NULL, phone VARCHAR(50) NOT NULL, email VARCHAR(50) NOT NULL); ```  ****code表格式****SQL   ``` CREATE TABLE code( id INT AUTO_INCREMENT PRIMARY KEY, uid INT NOT NULL, code CHAR(6) NOT NULL, FOREIGN KEY(uid) REFERENCES user(id));```****password表格式****SQL  ```CREATE TABLE password( id INT AUTO_INCREMENT PRIMARY KEY, uid INT NOT NULL, password VARCHAR(100) NOT NULL, FOREIGN KEY(uid) REFERENCES user(id));```- 发送验证码存入code表- 当输入的新密码验证正确,将旧密码存入password表,更新用户表中密码,删除code表中数据。***URL***- 输入phone或者Email:http://127.0.0.1:8080/ChatGPTEbusiness/jsp/VeriCodePage.jsp- 重置密码:http://127.0.0.1:8080/ChatGPTEbusiness/jsp/RecoverPage.jsp***测试流程*******正确流程1****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的手机信息。- 3,点击【发送验证码】按键。- 4,向数据库code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入上一步生成的验证码、新密码:Zxcv@123、确认新密码:Zxcv@123。- 6,点击【确认】按键。- 7,进入登录页面,页面标题为“登录页面”****正确流程2****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的Email信息。- 3,点击【发送验证码】按键。- 4,向数据库code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入上一步生成的验证码、新密码:Zxcv@123、确认新密码:Zxcv@123。- 6,点击【确认】按键。- 7,进入登录页面,页面标题为“登录页面”****错误流程1:非有效的手机号和Email字符****- 1,在VeriCodePage.jsp中输入非手机非Email字符,比如jerrygu。- 2,点击【发送验证码】按键。- 3,在<div id="contactError" class="error"></div>显示"请输入有效的中国手机号或Email"****错误流程2:手机号或Email不存在****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入非第1步用户信息的Email和Phone信息。- 3,点击【发送验证码】按键。- 4,在<div id="VeriCodeError" class="error"></div>显示"您输入的手机号或Email不存在,请重新输入!" **注意**不是“您输入的手机号或Email不存在请重新输入”****错误流程3:验证码格式错误****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,输入验证码:A12345、新密码:Zxcv@123、确认新密码:Zxcv@123。- 5,点击【确定】按钮- 6,在<div id="identifyingCodeError" class="error"></div>显示:"验证码必须是6位数字"****错误流程4:验证码不是所生成的****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,输入验证码:123456、新密码:Zxcv@123:Zxcv@123。- 5,点击【确定】按钮- 6,在<div id="recoverError" class="error"></div>显示:"验证码错误,请重新输入!"****错误流程5:密码确认不一致!****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,向数据库表code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入上一步生成的验证码、新密码:Zxcv@123、确认新密码:Zxcv@124。- 6,点击【确定】按钮- 7,在<div id="confirmPasswordError" class="error"></div>显示:"密码确认不一致!"****错误流程6:密码以前设置过****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,向数据库表code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入上一步生成的验证码、新密码:Zxcv@123、确认新密码:Zxcv@123。- 6,在password表中插入(uid与SHA256后的字符Zxcv@123)。- 7,点击【确定】按钮- 8,在<div id="recoverError" class="error"></div>显示:"这个密码以前设置过,请用一个新密码!"****错误流程7:新密码无特殊字符****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,向数据库表code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入验证码、新密码:Zxcv123、确认新密码:Zxcv123。- 6,点击【确定】按钮- 7,在<div id="newPasswordError" class="error"></div>显示:"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"****错误流程8:新密码无大写字符****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,向数据库表code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入验证码、新密码:zxcv@123、确认新密码:zxcv@123。- 6,点击【确定】按钮- 7,在<div id="newPasswordError" class="error"></div>显示:"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"****错误流程9:新密码无小写字符****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,向数据库code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入验证码、新密码:ZXCV@123、确认新密码:ZXCV@123。- 6,点击【确定】按钮- 7,在<div id="newPasswordError" class="error"></div>显示:"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"****错误流程10:新密码无数字字符****- 1,向user表中注册表一条用户信息。- 2,在VeriCodePage.jsp中输入第1步用户信息的邮件信息。- 3,点击【发送验证码】按键。- 4,向数据库表code表插入数据(uid,code) ,其中uid为user ID,code为6位数字的验证码- 5,在RecoverPage.jsp验证码中输入验证码、新密码:ZXCV@、确认新密码:ZXCV@。- 6,点击【确定】按钮- 7,在<div id="newPasswordError" class="error"></div>显示:"密码必须包含大小写字母、数字和特殊字符,长度在5-30之间"
  • 由于这个功能的流程比较复杂,需要给出各种情况,相当一use case

注意

  • 找回密码功能比较复杂,所以加上了“测试流程”一个环节。
  • 执行main.py的时候将注册需求、登录需求、找回密码需求,定义在名为req.txt文件中

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-04 05:31:08 HTTP/2.0 GET : https://f.mffb.com.cn/a/489258.html
  2. 运行时间 : 0.099549s [ 吞吐率:10.05req/s ] 内存消耗:4,838.36kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=3c8daa7f9eba3b633b710d901fcfa138
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000568s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000746s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000285s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.001655s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000500s ]
  6. SELECT * FROM `set` [ RunTime:0.000375s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000582s ]
  8. SELECT * FROM `article` WHERE `id` = 489258 LIMIT 1 [ RunTime:0.007068s ]
  9. UPDATE `article` SET `lasttime` = 1783114268 WHERE `id` = 489258 [ RunTime:0.008331s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 66 LIMIT 1 [ RunTime:0.000863s ]
  11. SELECT * FROM `article` WHERE `id` < 489258 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000415s ]
  12. SELECT * FROM `article` WHERE `id` > 489258 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.001003s ]
  13. SELECT * FROM `article` WHERE `id` < 489258 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.003299s ]
  14. SELECT * FROM `article` WHERE `id` < 489258 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.006001s ]
  15. SELECT * FROM `article` WHERE `id` < 489258 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001867s ]
0.101161s