Playwrite(Rpa魔改)

版本1

python 复制代码
"""
可视化工作流录制器 - GUI 界面
功能:启动 codegen,实时解析代码为可视化步骤
"""
import tkinter as tk
from tkinter import ttk, scrolledtext, messagebox
import threading
import subprocess
import time
import os
import re
import json
from pathlib import Path


class WorkflowRecorder:
    def __init__(self, root):
        self.root = root
        self.root.title("FingerAgent - 可视化工作流录制器")
        self.root.geometry("600x500")
        
        # 状态变量
        self.is_recording = False
        self.is_paused = False
        self.codegen_process = None
        self.output_file = "temp_recorded.py"
        self.steps = []  # 存储步骤列表
        self.last_code = ""  # 上一次解析的代码
        
        self.setup_ui()
    
    def setup_ui(self):
        """创建 UI 组件"""
        # 顶部按钮区域
        btn_frame = ttk.Frame(self.root, padding="10")
        btn_frame.pack(fill=tk.X)
        
        self.btn_record = ttk.Button(btn_frame, text="● 开始录制", command=self.toggle_recording)
        self.btn_record.pack(side=tk.LEFT, padx=5)
        
        self.btn_pause = ttk.Button(btn_frame, text="⏸ 暂停", command=self.toggle_pause, state=tk.DISABLED)
        self.btn_pause.pack(side=tk.LEFT, padx=5)
        
        self.btn_save = ttk.Button(btn_frame, text="💾 保存脚本", command=self.save_script, state=tk.DISABLED)
        self.btn_save.pack(side=tk.LEFT, padx=5)
        
        self.btn_clear = ttk.Button(btn_frame, text="🗑️ 清空", command=self.clear_steps)
        self.btn_clear.pack(side=tk.LEFT, padx=5)
        
        # 状态显示
        self.status_label = ttk.Label(btn_frame, text="状态: 未开始", foreground="gray")
        self.status_label.pack(side=tk.RIGHT, padx=10)
        
        # 分隔线
        ttk.Separator(self.root, orient=tk.HORIZONTAL).pack(fill=tk.X, padx=10, pady=5)
        
        # 步骤列表区域
        steps_label = ttk.Label(self.root, text="录制的步骤:", font=("Microsoft YaHei", 10, "bold"))
        steps_label.pack(anchor=tk.W, padx=15, pady=(10, 5))
        
        # 步骤列表(Listbox)
        list_frame = ttk.Frame(self.root, padding="10")
        list_frame.pack(fill=tk.BOTH, expand=True)
        
        self.steps_listbox = tk.Listbox(
            list_frame, 
            font=("Microsoft YaHei", 10),
            selectmode=tk.EXTENDED,
            height=15
        )
        scrollbar = ttk.Scrollbar(list_frame, orient=tk.VERTICAL, command=self.steps_listbox.yview)
        self.steps_listbox.configure(yscrollcommand=scrollbar.set)
        
        self.steps_listbox.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
        
        # 底部提示
        tip_label = ttk.Label(
            self.root, 
            text="💡 提示:录制时操作浏览器,所有步骤会自动显示在这里",
            font=("Microsoft YaHei", 9),
            foreground="gray"
        )
        tip_label.pack(pady=10)
    
    def toggle_recording(self):
        """切换录制状态"""
        if not self.is_recording:
            self.start_recording()
        else:
            self.stop_recording()
    
    def start_recording(self):
        """开始录制"""
        self.is_recording = True
        self.is_paused = False
        self.btn_record.config(text="■ 停止录制")
        self.btn_pause.config(text="⏸ 暂停", state=tk.NORMAL)
        self.btn_save.config(state=tk.NORMAL)
        self.status_label.config(text="状态: 录制中...", foreground="green")
        
        # 清空之前的数据
        self.steps = []
        self.steps_listbox.delete(0, tk.END)
        self.last_code = ""
        
        # 启动 codegen 线程
        self.codegen_thread = threading.Thread(target=self.run_codegen, daemon=True)
        self.codegen_thread.start()
        
        # 启动监听线程
        self.monitor_thread = threading.Thread(target=self.monitor_output, daemon=True)
        self.monitor_thread.start()
    
    def stop_recording(self):
        """停止录制"""
        self.is_recording = False
        
        # 终止 codegen 进程
        if self.codegen_process:
            self.codegen_process.terminate()
            try:
                self.codegen_process.wait(timeout=3)
            except:
                self.codegen_process.kill()
        
        self.btn_record.config(text="● 开始录制")
        self.btn_pause.config(state=tk.DISABLED)
        self.status_label.config(text="状态: 已停止", foreground="gray")
    
    def toggle_pause(self):
        """切换暂停状态"""
        self.is_paused = not self.is_paused
        if self.is_paused:
            self.btn_pause.config(text="▶ 继续")
            self.status_label.config(text="状态: 已暂停", foreground="orange")
        else:
            self.btn_pause.config(text="⏸ 暂停")
            self.status_label.config(text="状态: 录制中...", foreground="green")
    
    def run_codegen(self):
        """运行 codegen 进程"""
        try:
            # 删除旧文件
            if os.path.exists(self.output_file):
                os.remove(self.output_file)
            
            # 启动 codegen
            cmd = [
                "playwright", "codegen",
                "-o", self.output_file,
                "--target=python",
                "-b", "chromium"
            ]
            
            self.codegen_process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            
            # 等待进程结束
            self.codegen_process.wait()
            
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("错误", f"启动 codegen 失败: {e}"))
    
    def monitor_output(self):
        """监控输出文件变化"""
        last_mtime = 0
        while self.is_recording:
            try:
                if os.path.exists(self.output_file):
                    # 获取文件修改时间
                    mtime = os.path.getmtime(self.output_file)
                    
                    # 文件有变化
                    if mtime > last_mtime:
                        last_mtime = mtime
                        
                        # 读取文件内容
                        with open(self.output_file, "r", encoding="utf-8") as f:
                            current_code = f.read()
                        
                        # 每次都从头解析整个文件
                        all_steps = self.parse_code_to_steps(current_code)
                        
                        # 如果步骤数量增加了,更新 UI
                        if len(all_steps) > len(self.steps):
                            # 获取新增的步骤
                            new_steps = all_steps[len(self.steps):]
                            self.root.after(0, lambda s=new_steps: self.update_steps(s))
                
                time.sleep(0.3)  # 每 0.3 秒检查一次
                
            except Exception as e:
                print(f"监控错误: {e}")
                time.sleep(1)
    
    def parse_code_to_steps(self, code: str) -> list:
        """解析 Python 代码为可视化步骤"""
        steps = []
        
        if not code:
            return steps
        
        # 预处理:合并多行语句
        lines = code.split('\n')
        processed_lines = []
        current_line = ""
        indent_count = 0
        
        for line in lines:
            stripped = line.strip()
            
            # 跳过空行、注释、import
            if not stripped or stripped.startswith('#') or stripped.startswith('import ') or stripped.startswith('from '):
                continue
            
            # 如果是 with 语句的 continuation(缩进行)
            if stripped and (stripped.startswith('page.') or stripped.startswith('page1.') or stripped.startswith('page2.')):
                current_line += " " + stripped
            else:
                if current_line:
                    processed_lines.append(current_line)
                current_line = stripped
        
        # 最后一行
        if current_line:
            processed_lines.append(current_line)
        
        for line in processed_lines:
            step = self.parse_action_line(line)
            if step:
                steps.append(step)
        
        return steps
    
    def parse_action_line(self, line: str) -> str:
        """解析单行代码为中文描述"""
        line = line.strip()
        import re  # 移到这里避免作用域问题
        
        # page.goto(...) - 导航
        if '.goto(' in line:
            # 提取 URL
            import re
            match = re.search(r'["\'](https?://[^"\']+)["\']', line)
            if match:
                return f"🌐 打开网页: {match.group(1)}"
        
        # get_by_role(...).click() - 按角色点击
        if 'get_by_role' in line and '.click()' in line:
            # 提取角色和名称
            import re
            role_match = re.search(r'["\'](link|button|checkbox|radio|input|menuitem|heading)["\']', line)
            name_match = re.search(r'name=["\']([^"\']+)["\']', line)
            if role_match and name_match:
                return f"🖱️ 点击 [{role_match.group(1)}]: {name_match.group(1)}"
            elif name_match:
                return f"🖱️ 点击: {name_match.group(1)}"
            return "🖱️ 点击元素"
        
        # get_by_text(...).click()
        if 'get_by_text' in line and '.click()' in line:
            import re
            match = re.search(r'["\']([^"\']+)["\']', line)
            if match:
                return f"🖱️ 点击文本: {match.group(1)}"
            return "🖱️ 点击文本"
        
        # get_by_label
        if 'get_by_label' in line:
            import re
            match = re.search(r'["\']([^"\']+)["\']', line)
            if match:
                return f"🖱️ 点击标签: {match.group(1)}"
        
        # locator(...).click()
        if re.match(r'\w+\.locator\(', line) and '.click()' in line:
            match = re.search(r'locator\(["\']([^"\']+)["\']', line)
            if match:
                return f"🖱️ 点击: {match.group(1)}"
        
        # page.click(...)
        if re.match(r'\w+\.click\(', line) and 'get_by' not in line:
            import re
            match = re.search(r'["\']([^"\']+)["\']', line)
            if match:
                return f"🖱️ 点击: {match.group(1)}"
        
        # page.fill - 输入
        if '.fill(' in line:
            import re
            match = re.search(r'["\']([^"\']+)["\']', line)
            if match:
                selector = match.group(1)
                # 尝试找第二个参数(值)
                value_match = re.search(r',\s*["\']([^"\']+)["\']', line)
                if value_match:
                    return f"⌨️ 输入: {selector} → '{value_match.group(1)}'"
                return f"⌨️ 输入: {selector}"
        
        # wait_for_timeout
        if 'wait_for_timeout' in line:
            import re
            match = re.search(r'(\d+)', line)
            if match:
                ms = int(match.group(1))
                return f"⏱️ 等待 {ms/1000:.1f} 秒"
        
        # wait_for_load_state
        if 'wait_for_load_state' in line:
            return "⏳ 等待加载"
        
        # screenshot
        if 'screenshot' in line:
            return "📷 截图"
        
        # evaluate
        if 'evaluate' in line:
            return "⚡ 执行脚本"
        
        # expect
        if 'expect(' in line:
            return "✅ 断言"
        
        # expect_popup
        if 'expect_popup' in line:
            return "🔔 等待弹窗"
        
        return None
    
    def update_steps(self, new_steps: list):
        """更新步骤列表"""
        # 找到新增的步骤(从上次的位置之后)
        start_idx = len(self.steps)
        self.steps.extend(new_steps)
        
        # 添加到 Listbox
        for step in new_steps:
            self.steps_listbox.insert(tk.END, step)
        
        # 自动滚动到底部
        if new_steps:
            self.steps_listbox.see(tk.END)
    
    def clear_steps(self):
        """清空步骤列表"""
        self.steps = []
        self.steps_listbox.delete(0, tk.END)
        self.last_code = ""
        
        # 删除临时文件
        if os.path.exists(self.output_file):
            os.remove(self.output_file)
    
    def save_script(self):
        """保存脚本"""
        if not self.steps:
            messagebox.showwarning("警告", "没有可保存的步骤!")
            return
        
        # 读取生成的代码
        if os.path.exists(self.output_file):
            with open(self.output_file, "r", encoding="utf-8") as f:
                code = f.read()
            
            # 让用户选择保存位置
            from tkinter import filedialog
            save_path = filedialog.asksaveasfilename(
                defaultextension=".py",
                filetypes=[("Python 文件", "*.py")],
                initialfile="recorded_workflow.py"
            )
            
            if save_path:
                with open(save_path, "w", encoding="utf-8") as f:
                    f.write(code)
                
                messagebox.showinfo("成功", f"脚本已保存到:\n{save_path}")
        else:
            messagebox.showwarning("警告", "未找到生成的代码文件!")


def main():
    root = tk.Tk()
    app = WorkflowRecorder(root)
    root.mainloop()


if __name__ == "__main__":
    main()

版本2

记录.py

python 复制代码
"""
可视化工作流录制器 - PySide6 现代界面版
功能:启动 codegen,实时解析代码为可视化步骤
"""
import sys
from PySide6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QListWidget, QLabel, QFrame, QListWidgetItem
)
from PySide6.QtCore import Qt, QThread, Signal, QTimer
from PySide6.QtGui import QIcon, QColor, QPalette, QLinearGradient, QPainter, QFont
import subprocess
import time
import os
import re

from workflow_parser import parse_workflow


class WorkflowRecorder(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("FingerAgent - 可视化工作流录制器")
        self.setGeometry(100, 100, 700, 550)
        
        # 状态变量
        self.is_recording = False
        self.codegen_process = None
        self.output_file = "temp_recorded.py"
        self.steps = []
        
        self.setup_ui()
        self.apply_styles()
    
    def setup_ui(self):
        """创建 UI 组件"""
        # 中心部件
        central_widget = QWidget()
        self.setCentralWidget(central_widget)
        
        # 主布局
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(20, 20, 20, 20)
        main_layout.setSpacing(15)
        
        # ===== 顶部标题栏 =====
        title_layout = QHBoxLayout()
        
        # 标题
        title_label = QLabel("🎬 工作流录制器")
        title_label.setFont(QFont("Microsoft YaHei", 16, QFont.Bold))
        title_layout.addWidget(title_label)
        
        title_layout.addStretch()
        
        # 状态指示灯 + 状态文字
        status_container = QFrame()
        status_container.setFixedSize(120, 30)
        status_layout = QHBoxLayout(status_container)
        status_layout.setContentsMargins(10, 0, 10, 0)
        
        self.status_indicator = QLabel("●")
        self.status_indicator.setFont(QFont("Arial", 14))
        self.status_indicator.setStyleSheet("color: #666666;")
        status_layout.addWidget(self.status_indicator)
        
        self.status_label = QLabel("未开始")
        self.status_label.setFont(QFont("Microsoft YaHei", 10))
        self.status_label.setStyleSheet("color: #1a1a1a;")
        status_layout.addWidget(self.status_label)
        
        title_layout.addWidget(status_container)
        
        main_layout.addLayout(title_layout)
        
        # ===== 分隔线 =====
        separator = QFrame()
        separator.setFrameShape(QFrame.HLine)
        separator.setStyleSheet("background-color: #333; border: none; height: 1px;")
        main_layout.addWidget(separator)
        
        # ===== 步骤列表区域 =====
        steps_title = QLabel("📋 录制的步骤")
        steps_title.setFont(QFont("Microsoft YaHei", 12, QFont.Bold))
        main_layout.addWidget(steps_title)
        
        # 步骤列表
        self.steps_list = QListWidget()
        self.steps_list.setFont(QFont("Microsoft YaHei", 10))
        self.steps_list.setSpacing(5)
        main_layout.addWidget(self.steps_list, 1)
        
        # ===== 底部按钮区域 =====
        btn_layout = QHBoxLayout()
        btn_layout.setSpacing(15)
        
        # 录制按钮
        self.btn_record = QPushButton("▶ 开始录制")
        self.btn_record.setFixedHeight(40)
        self.btn_record.setFont(QFont("Microsoft YaHei", 11))
        self.btn_record.clicked.connect(self.toggle_recording)
        btn_layout.addWidget(self.btn_record)
        
        # 清空按钮
        self.btn_clear = QPushButton("🗑️ 清空")
        self.btn_clear.setFixedHeight(40)
        self.btn_clear.setFont(QFont("Microsoft YaHei", 11))
        self.btn_clear.clicked.connect(self.clear_steps)
        btn_layout.addWidget(self.btn_clear)
        
        btn_layout.addStretch()
        
        main_layout.addLayout(btn_layout)
        
        # ===== 底部提示 =====
        tip_label = QLabel("💡 提示:录制时操作浏览器,所有步骤会自动显示在这里")
        tip_label.setFont(QFont("Microsoft YaHei", 9))
        tip_label.setStyleSheet("color: #666666;")
        tip_label.setAlignment(Qt.AlignCenter)
        main_layout.addWidget(tip_label)
    
    def apply_styles(self):
        """应用亮色主题样式"""
        self.setStyleSheet("""
            QMainWindow {
                background-color: #ffffff;
            }
            
            QLabel {
                color: #1a1a1a;
            }
            
            QListWidget {
                background-color: #f5f5f5;
                border: 1px solid #e0e0e0;
                border-radius: 8px;
                padding: 5px;
                color: #1a1a1a;
            }
            
            QListWidget::item {
                padding: 8px;
                border-radius: 5px;
                margin: 2px;
            }
            
            QListWidget::item:selected {
                background-color: #0078d4;
                color: white;
            }
            
            QListWidget::item:hover {
                background-color: #e8e8e8;
            }
            
            QPushButton {
                background-color: #0078d4;
                border: none;
                border-radius: 6px;
                color: white;
                padding: 8px 20px;
                font-weight: bold;
            }
            
            QPushButton:hover {
                background-color: #106ebe;
            }
            
            QPushButton:pressed {
                background-color: #005a9e;
            }
            
            QPushButton:disabled {
                background-color: #cccccc;
                color: #666666;
            }
            
            #btn_stop {
                background-color: #d32f2f;
            }
            
            #btn_stop:hover {
                background-color: #e53935;
            }
        """)
        
        # 设置按钮对象名称
        self.btn_record.setObjectName("btn_start")
    
    def toggle_recording(self):
        """切换录制状态"""
        if not self.is_recording:
            self.start_recording()
        else:
            self.stop_recording()
    
    def start_recording(self):
        """开始录制"""
        self.is_recording = True
        
        # 更新按钮
        self.btn_record.setText("■ 停止录制")
        self.btn_record.setObjectName("btn_stop")
        self.btn_record.style().unpolish(self.btn_record)
        self.btn_record.style().polish(self.btn_record)
        
        # 更新状态
        self.status_indicator.setText("●")
        self.status_indicator.setStyleSheet("color: #0078d4;")
        self.status_label.setText("录制中...")
        self.status_label.setStyleSheet("color: #0078d4;")
        
        # 更新按钮
        self.btn_record.setText("■ 停止录制")
        self.btn_record.setObjectName("btn_stop")
        self.btn_record.style().unpolish(self.btn_record)
        self.btn_record.style().polish(self.btn_record)
        
        # 清空之前的数据
        self.steps = []
        self.steps_list.clear()
        
        # 启动 codegen 线程
        self.codegen_thread = Thread(target=self.run_codegen)
        self.codegen_thread.start()
        
        # 启动监听线程
        self.monitor_thread = Thread(target=self.monitor_output)
        self.monitor_thread.start()
    
    def stop_recording(self):
        """停止录制并保存"""
        self.is_recording = False
        
        # 终止 codegen 进程
        if self.codegen_process:
            self.codegen_process.terminate()
            try:
                self.codegen_process.wait(timeout=3)
            except:
                self.codegen_process.kill()
        
        # 自动保存脚本
        self.save_script()
        
        # 更新按钮
        self.btn_record.setText("▶ 开始录制")
        self.btn_record.setObjectName("btn_start")
        self.btn_record.style().unpolish(self.btn_record)
        self.btn_record.style().polish(self.btn_record)
        
        # 更新状态
        self.status_indicator.setText("●")
        self.status_indicator.setStyleSheet("color: #666666;")
        self.status_label.setText("已保存")
        self.status_label.setStyleSheet("color: #1a1a1a;")
    
    def run_codegen(self):
        """运行 codegen 进程"""
        try:
            # 删除旧文件
            if os.path.exists(self.output_file):
                os.remove(self.output_file)
            
            # 启动 codegen
            cmd = [
                "playwright", "codegen",
                "-o", self.output_file,
                "--target=python",
                "-b", "chromium"
            ]
            
            self.codegen_process = subprocess.Popen(
                cmd,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True
            )
            
            # 等待进程结束
            self.codegen_process.wait()
            
        except Exception as e:
            self.status_label.setText(f"错误: {str(e)}")
    
    def monitor_output(self):
        """监控输出文件变化"""
        last_mtime = 0
        while self.is_recording:
            try:
                if os.path.exists(self.output_file):
                    mtime = os.path.getmtime(self.output_file)
                    
                    if mtime > last_mtime:
                        last_mtime = mtime
                        
                        # 直接调用 workflow_parser 解析文件
                        all_steps = parse_workflow(self.output_file)
                        
                        if len(all_steps) > len(self.steps):
                            new_steps = all_steps[len(self.steps):]
                            self.update_steps(new_steps)
                
                time.sleep(0.3)
                
            except Exception as e:
                print(f"监控错误: {e}")
                time.sleep(1)
    
    def update_steps(self, new_steps: list):
        """更新步骤列表"""
        self.steps.extend(new_steps)
        
        for step in new_steps:
            item = QListWidgetItem(step)
            self.steps_list.addItem(item)
        
        if new_steps:
            self.steps_list.scrollToBottom()
    
    def clear_steps(self):
        """清空步骤列表"""
        self.steps = []
        self.steps_list.clear()
        
        if os.path.exists(self.output_file):
            os.remove(self.output_file)
    
    def save_script(self):
        """保存脚本"""
        if not self.steps:
            from PySide6.QtWidgets import QMessageBox
            QMessageBox.warning(self, "警告", "没有可保存的步骤!")
            return
        
        if os.path.exists(self.output_file):
            with open(self.output_file, "r", encoding="utf-8") as f:
                code = f.read()
            
            save_path = "recorded_workflow.py"
            with open(save_path, "w", encoding="utf-8") as f:
                f.write(code)
            
            from PySide6.QtWidgets import QMessageBox
            QMessageBox.information(self, "成功", f"脚本已保存到:\n{save_path}")


class Thread(QThread):
    """简单的线程包装"""
    def __init__(self, target):
        super().__init__()
        self._target = target
        self.start()
    
    def run(self):
        self._target()


def main():
    app = QApplication(sys.argv)
    app.setStyle("Fusion")
    
    window = WorkflowRecorder()
    window.show()
    
    sys.exit(app.exec())


if __name__ == "__main__":
    main()

解析.py

python 复制代码
import re
import sys


def translate_action(action: str) -> str:
    """翻译动作名称为中文"""
    action_map = {
        'click': '点击',
        'fill': '输入',
        'check': '勾选',
        'uncheck': '取消勾选',
        'select_option': '选择',
        'press': '按键',
        'dblclick': '双击',
        'hover': '悬停',
    }
    return action_map.get(action, action)


def extract_locator(line: str) -> dict:
    """提取定位方式"""
    result = {}
    
    # get_by_role
    role_match = re.search(r'\.get_by_role\("([^"]+)",\s*name="([^"]+)"\)', line)
    if role_match:
        result['定位'] = 'get_by_role'
        result['角色'] = role_match.group(1)
        result['名字'] = role_match.group(2)
        
        # 修饰符
        modifier_match = re.search(r'\.(first|last|nth)\b', line)
        if modifier_match:
            result['修饰符'] = f".{modifier_match.group(1)}"
        return result
    
    # get_by_text
    text_match = re.search(r'\.get_by_text\("([^"]+)"(?:,\s*exact=True)?\)', line)
    if text_match:
        result['定位'] = 'get_by_text'
        result['文本'] = text_match.group(1)
        
        # 检查 exact=True
        if 'exact=True' in line:
            result['精确'] = '是'
        return result
    
    # locator (CSS选择器)
    locator_match = re.search(r'\.locator\("([^"]+)"\)', line)
    if locator_match:
        result['定位'] = 'locator'
        result['选择器'] = locator_match.group(1)
        
        # 可能还有 get_by_text 链式调用
        chained_text = re.search(r'\.get_by_text\("([^"]+)"\)\.click\(\)', line)
        if chained_text:
            result['文本'] = chained_text.group(1)
        return result
    
    return result


def extract_action(line: str) -> dict:
    """提取动作"""
    result = {}
    
    # .click()
    if '.click()' in line:
        result['动作'] = '点击'
    
    # .fill("xxx")
    fill_match = re.search(r'\.fill\("([^"]*)"\)', line)
    if fill_match:
        result['动作'] = '输入'
        result['值'] = fill_match.group(1)
    
    # .press("xxx")
    press_match = re.search(r'\.press\("([^"]+)"\)', line)
    if press_match:
        result['动作'] = f"按键({press_match.group(1)})"
    
    # .check()
    if '.check()' in line:
        result['动作'] = '勾选'
    
    # .uncheck()
    if '.uncheck()' in line:
        result['动作'] = '取消勾选'
    
    return result


def parse_single_line(line: str) -> str:
    """解析单行代码,返回中文描述"""
    line = line.strip()
    
    if not line or line.startswith('#'):
        return None
    
    # 解析 goto
    goto_match = re.search(r'(page\d*)\.goto\("([^"]+)"\)', line)
    if goto_match:
        return f"URL: {goto_match.group(2)}"
    
    # 提取定位信息
    locator_info = extract_locator(line)
    
    # 提取动作信息
    action_info = extract_action(line)
    
    # 只有当有定位或动作时才输出
    if locator_info or action_info:
        parts = []
        
        for key, value in locator_info.items():
            parts.append(f"{key}: {value}")
        
        for key, value in action_info.items():
            parts.append(f"{key}: {value}")
        
        return ', '.join(parts)
    
    return None


def parse_workflow(file_path: str) -> list:
    """解析 Playwright 录制的 workflow 文件,返回步骤列表"""
    
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()
    
    lines = content.split('\n')
    
    steps = []
    step_num = 0
    
    for line in lines:
        result = parse_single_line(line)
        if result:
            step_num += 1
            steps.append(f"步骤{step_num}: {result}")
    
    return steps


if __name__ == "__main__":
    # 默认解析当前目录的 recorded_workflow.py
    file_path = "recorded_workflow.py"
    
    # 可以通过命令行参数指定文件
    if len(sys.argv) > 1:
        file_path = sys.argv[1]
    
    try:
        steps = parse_workflow(file_path)
        for step in steps:
            print(step)
    except FileNotFoundError:
        print(f"错误: 找不到文件 {file_path}")
    except Exception as e:
        print(f"错误: {e}")

123

相关推荐
RPA机器人就用八爪鱼19 小时前
RPA+AI赋能数字化办公:告别机械劳作,解锁全场景自动化
人工智能·机器人·自动化·rpa
RPA机器人就用八爪鱼2 天前
RPA+AI融合趋势下,数字化内容运营的自动化升级路径
机器人·自动化·rpa
GJGCY3 天前
2026企业级AI智能体架构对比:RPA+大模型融合在财务场景的表现
大数据·人工智能·ai·rpa·智能体
GJGCY3 天前
2026制造业RPA技术落地指南:7大核心场景架构对比与跨系统集成实践
人工智能·ai·自动化·制造·rpa·制造业·智能体
GEO_Huang3 天前
企业转型无从下手?数谷的定制化 AI 方案能否指点迷津?
大数据·人工智能·aigc·rpa·geo·企业智能体定制·企业ai定制
曲辕RPA3 天前
RPA多网页并行自动化深度对比:影刀的坑与曲辕的解法
python·ai·自动化·rpa
GEO_Huang4 天前
扎根珠三角,数谷 AI 定制助千企数智化转型
人工智能·aigc·rpa·geo·ai+rpa
GEO_Huang4 天前
想要排名稳?数谷 GEO 优化助力企业品牌升级
大数据·人工智能·百度·aigc·rpa·geo
GEO_Huang4 天前
工作流定制选数谷,Agentoffice 让办公快人一步
大数据·人工智能·aigc·rpa·geo