Midscene.js:AI驱动的下一代UI自动化工具深度解析与实战思考

Midscene.js深度解析:AI驱动的下一代UI自动化工具,解决传统痛点并实现自然语言操作。文章细剖其工作原理、实践挑战与未来应用,重塑UI自动化范式。

原文标题:Midscene.js 实战与源码剖析:如何重塑 UI 自动化

原文作者:阿里云开发者

冷月清谈:

本文深入介绍了Midscene.js,这是一款基于AI的下一代UI自动化工具。它旨在解决传统UI自动化面临的诸多痛点,包括元素选择器脆弱性、高昂的维护成本、糟糕的调试体验以及跨平台支持不足等。Midscene.js利用AI技术,特别是视觉语言模型(VL模型)和大型语言模型(LLM模型),实现了通过自然语言描述操作意图的自动化。

其核心工作原理是:首先,用户通过自然语言发出指令;Midscene.js会获取当前页面的完整UI上下文,包括页面截图和经过精简的DOM树结构,并为页面元素生成稳定的哈希ID。接着,系统进行第一次AI调用,由大模型根据用户指令进行任务规划和元素预定位。随后进入关键的元素验证阶段,通过XPath、缓存、预定位结果和二次AI调用(AI Fallback)等四层策略,确保目标元素被精确识别。最终,通过浏览器协议(如Chrome DevTools Protocol)执行自动化操作,完成用户指令。

文章还探讨了Midscene.js在实际应用中遇到的一些问题,例如DOM信息抓取不完整(如style中的图片信息丢失)、大模型返回内容截断、LLM模型处理iframe的局限性、VL模型在可视区域外准确率下降以及配置不当导致模型不生效等。针对这些问题,文章分析了其技术根源并提供了潜在的解决方案。

最后,作者对Midscene.js的业务落地进行了思考,认为其潜力远不止于自动化测试。它可以在新手引导、智能营销、智能发品等场景中,通过AI驱动浏览器自动化,大幅提升效率和用户体验,甚至为AI代码生成提供更丰富的运行时上下文,推动AI辅助开发进入新阶段。

怜星夜思:

1、Midscene.js这种“自然语言操作UI”的模式,对传统软件开发、测试流程会带来哪些颠覆性的影响?未来可能有哪些我们现在难以想象的应用场景?
2、AI驱动的UI自动化工具强大到能理解意图并执行复杂操作,那它在企业级应用中可能面临哪些潜在的安全和伦理风险?比如数据隐私、误操作导致的关键业务中断等。
3、虽然Midscene.js解决了传统痛点,但文章也提到不少“使用中遇到的问题”。你觉得这些问题(比如DOM信息抓取不全、iframe限制、LLM/VL模型选择)在实际项目中,哪个最考验开发者,最影响推广和普及?有什么办法可以缓解?

原文内容

阿里妹导读


本文系统性地介绍了 Midscene.js —— 一款基于 AI 的下一代 UI 自动化工具,深入剖析其设计动机、核心架构、工作原理及源码实现,同时结合业务场景落地过程,分享一些问题总结及落地思考。

一、Midscene.js简介

1.1. 为什么需要Midscene.js

现代软件开发中,UI自动化测试和作已为保产品质量的重要环节。然而,传统的UI自动化工具面临着诸多挑战,这 Midscene.js 诞生的原因。

1.1.1. 传统UI自动化的痛点
1.1.1.1. 元素选择器的脆弱性

传统工具赖CSS选择器、XPath或ID来定位元素,这些选择器在面变化时易失

// 传统方式 - 脆弱且难以维护
driver.findElement(By.xpath("//div[@class='search-box']/input[1]"))
driver.findElement(By.css("#header > nav > ul > li:nth-child(2) > a"))
// Midscene方式 - 语义化且稳定
await agent.aiInput('拖鞋', '搜索框')
await agent.aiTap("页面顶部的图搜,非'以图搜款'")
1.1.1.2. 高昂的维护成本
  • 面结构变化:当UI,大量测脚本需要重写;
  • 态内容处理:处理异步、动画效果需要复杂的等待逻辑;
  • 环境适配:同分辨率、浏览器版本的兼容性问题;
1.1.1.3. 调试体验差

传统工具的调试过痛苦

  • 脚本失败时难以快速定位问题;
  • 缺乏可视化的行过程回放;
  • 误信抽象,难以理解败原因;
1.1.1.4. 跨平台支持不足

多数工具专注于单一平台

  • Web具无法处理移动端;
  • 动端工具无法处理Web;
  • 缺乏统一的API和开发体验;
1.1.2. AI代的新需求

随着AI技术的发展,我们对自动化工具有了新的期望,希望用自然语言描述操作意图,而不是学习复杂的选择器语法:

// 理想方式
await aiAction('在搜索框中输入"拖鞋",然后点击搜索按钮')

// 而不是
const searchBox = await page.waitForSelector(‘#search-input’)
await searchBox.type(‘拖鞋’)
const searchBtn = await page.waitForSelector(‘button[type=“submit”]’)
await searchBtn.click()

尤其是目前VL模型及多模态模型能力的提升,AI更能理解页面内容和用户意图,自动处理变化,包括:
  • 智能识别功能相元素;
  • UI局的微调;
  • 理解业务语义而非仅仅是技术实现;

1.2. 其他类似的UI自动化工具

聊到Midscene.js,大家不得不提到Browser Use

这里的对比,大家不必太关注细节,整体使用下来,大部分自动化能力,二者都能支持。

核心关注的是使用场景,个人感觉:

  • 如果是专注于本机或者云端的自动化测试,选择Browser Use;
  • 如果专注于可视化用例生成,部署在用户个人机器上的发品等可视化操作,选择Midscene.js。

二、Midscene.js chrome插件试用

Midscene.js提供了多种方式去试用和集成开发。如果你想零代码体验下web版本的功能,推荐安装chrome 插件,以下以chrome插件的试用来作说明。

2.1. 视觉语言模型(VL 模型)

VL模型它提供视觉定位能力,可以准确返回页面上目标元素的坐标。

VL模型是官方推荐的模型,无需依赖 DOM 信息就能精确定位页面上目标元素的坐标,且相对而言成本也更低,因为在与大模型的交互过程中,完全不会把DOM信息带过去,节省了大量的token。

与模型的调用,仍旧遵循 Chat Completion API 规范。

配置:

OPENAI_API_KEY="***"
OPENAI_BASE_URL=""  
MIDSCENE_MODEL_NAME="qwen-vl-max-latest"//写死,或者更高级的qwen3-vl-plus等
MIDSCENE_USE_QWEN_VL=1  //写死,vl模型必填

注意这里的MIDSCENE_MODEL_NAME要从你的服务提供方那里找到确定的名字,否则会提示找不到该模型。

2.2. LLM 模型

能够理解文本和图像输入的多模态 LLM 模型。GPT-4o 就是这种类型的模型。

官方说将在下个大版本中移除对于LLM的支持,但是个人觉得LLM模型成本最高,但是在解决页面完整信息(尤其是内容区域在非可视范围内)提取方面,是VL模型无法替代的。

配置如下:

OPENAI_API_KEY="***"
OPENAI_BASE_URL="***"
MIDSCENE_MODEL_NAME="gpt-4o-0806-global"

三、源码解析

3.1. 整体仓库结构

3.1.1. 项目结构概览

项目地址:https://github.com/web-infra-dev/midscene

Midscene 是一个使用 pnpm workspace 管理的 Monorepo 项目,采用分层架构设计,主要分为两大类目录:

3.1.2. 包分类详解
3.1.2.1. 公开发布包(Published Packages)

这些包会布到 npm registry,供部用户使用:

判断标准包含 publishConfig.access: "public" 配置。

3.1.2.2. 内部工具包(Internal Packages)

这些包主要供内部使用或为其他包的依赖:

3.1.2.3. 应用程序包(Application Packages)

这些是完整的应用程序,不发布为 npm 包,比如我们在插件市场里面看到的Midscene.js插件,他的代码就在chrome-extension里面。

3.2. 工作原理解析

下面以大家最快能接触到的Midscene.js插件功能,讲解一下它的工作原理。注意:为了把整个流程讲解得更加详实,这里使用的是GPT4o模型,非VL模型。

用户场景:比如我在插件里面选择Action模式,在taobao.com站点输入‘帮我到搜索框里面搜索“拖鞋”,并敲击Enter’,这个过程中,发生了什么?

整体架构:

完整流程时序图

公众号后台回复【流程时序图】查看原图
3.2.1. 阶段一:页面上下文获取
3.2.1.1. 用户指令输入
// 用户在Chrome扩展界面输入
const userInstruction = "帮我到搜索框里面搜索'拖鞋',并敲击Enter";

// 扩展将指令发送给Midscene Agent
await agent.aiAction(userInstruction);

在执行任何操作之前,Midscene 需要"看到"当前页面的完整信息,下面几个部分会详细说明,需要哪些信息。

// packages/core/src/agent/agent.ts
async getUIContext(action?: InsightAction): Promise<UIContext> {
  // 1. 检查是否有冻结的上下文(用于保持一致性)
  if (this.frozenUIContext) {
    returnthis.frozenUIContext;
  }

  // 2. 优先使用接口的getContext方法
  if (this.interface.getContext) {
    return await this.interface.getContext();
  } else {
    // 3. 回退到基础实现:分别获取截图和DOM树
    const screenshot = await this.interface.screenshotBase64();
    const tree = await this.interface.getElementsNodeTree();
    const size = await this.interface.getPageSize();
    
    return {
      screenshotBase64: screenshot,
      tree,
      size,
      // …其他上下文信息
    };
  }
}

3.2.1.2. 页面截图获取

Chrome扩展实现

// packages/web-integration/src/chrome-extension/page.ts
async screenshotBase64(){
  await this.hideMousePointer(); // 隐藏鼠标指针避免干扰
  
  const base64 = await this.sendCommandToDebugger('Page.captureScreenshot', {
    format: 'jpeg',
    quality: 90,
  });
  
  return createImgBase64ByFormat('jpeg', base64.data);
}

结果:获得淘宝首页的高清截图,格式为 Base64 编码的 JPEG 图像。这张图长这样:

注意看,我们发现给大模型的截图,里面对元素进行了标识,只有语言模型才会在截图上标识。标记元素的本质目的如下:

  • 桥接视觉与结构:将AI的视觉识别与精确的DOM结构关联起来;
  • 提高准确性:避免坐标漂移和模糊匹配;
  • 支持复杂场景:处理动态内容、相似元素、部分遮挡等情况;
  • 降低模型要求:让非专业视觉模型也能精确定位;
3.2.1.3. DOM树结构提取

获取页面的完整DOM结构信息:

// 1. 注入元素提取脚本
const script = await getHtmlElementScript();
await this.sendCommandToDebugger('Runtime.evaluate', {
  expression: script,
});

// 2. 执行DOM树提取
const expression = () => {
  window.midscene_element_inspector.setNodeHashCacheListOnWindow();
  const tree = window.midscene_element_inspector.webExtractNodeTree();
  
  return {
    tree,
    size: {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight,
      dpr: window.devicePixelRatio,
    },
  };
};

const result = await this.sendCommandToDebugger(‘Runtime.evaluate’, {
  expression: (${expression.toString()})(),
  returnByValue: true,
});

DOM提取的核心算法

// packages/shared/src/extractor/web-extractor.ts
export function extractTreeNode(initNode: globalThis.Node, debugMode = false): WebElementNode {
  const topDocument = getTopDocument(); // document.body || document
  const startNode = initNode || topDocument;
  
  // 深度优先搜索函数
  function dfs(
    node: globalThis.Node,
    currentWindow: typeof globalThis.window,
    currentDocument: typeof globalThis.document,
    baseZoom = 1,
    basePoint: Point = { left: 0, top: 0 },
  ): WebElementNode | null {
    
    if (node.nodeType === Node.ELEMENT_NODE) {
      // 收集元素信息:位置、样式、属性
      const elementInfo = collectElementInfo(node, currentWindow, currentDocument, baseZoom, basePoint);
      if (!elementInfo) return null;
      
      // 生成唯一ID并缓存到全局
      const nodeId = midsceneGenerateHash(node, elementInfo.content, elementInfo.rect);
      setNodeToCacheList(node, nodeId);
      
      // 递归处理子节点
      const children: (WebElementNode | null)[] = [];
      for (const child of node.childNodes) {
        const childResult = dfs(child, currentWindow, currentDocument, baseZoom, basePoint);
        if (childResult) children.push(childResult);
      }
      
      return {
        ...elementInfo,
        id: nodeId,
        children: children.filter(Boolean),
        nodeName: node.nodeName.toLowerCase(),
        nodeType: node.nodeType
      };
      
    } elseif (node.nodeType === Node.TEXT_NODE) {
      // 处理文本节点
      const textContent = node.textContent?.trim();
      if (textContent && textContent.length > 0) {
        return {
          nodeType: Node.TEXT_NODE,
          content: textContent,
          // ... 其他文本节点信息
        };
      }
    }
    
    return null;
  }
  
  return dfs(startNode, window, document) || { children: [], nodeType: Node.DOCUMENT_NODE };
}

处理完的dom节点信息如下,他会把样式全部清除,只保留节点property相关的信息,然后给他添加一个独立的id,比如这里的"mofkb",后续和大模型相关的所有交互,都通过这个唯一标识来处理。

最终的UI上下文

const uiContext: UIContext = {
  screenshotBase64: "data:image/jpeg;base64,/9j/4AAQSkZJRgABA...", // 淘宝页面截图
  size: { width: 1920, height: 1080, dpr: 1 },
  tree: {
    node: null,
    children: [
      // 包含搜索框在内的所有页面元素信息
    ]
  }
};
3.2.1.4. ID映射机制

这一步很重要,上述处理DOM结构的时候,"id":"mofkb"是Midscene内部生成的哈希ID,不是DOM原生ID。这里有一个ID的映射机制

  • 页面扫描时:为每个DOM元素生成稳定的哈希ID(基于内容和位置
  • 缓存建立:将ID与真实DOM节点的映射关系存储在浏览器window对象中
  • AI预测:第一次AI调用返回预测的元素ID(如"mofkb")
  • 元素定位:通过ID从缓存中直接找到对应的真实DOM节
  • 容错机制:果ID查找失败,自动降级到第二次AI调用进行视觉定位

这种设计既保证了高效性(避免频繁的AI调用),又确保了准确性(多层验证机制)。

大概的流程如下:

3.2.2. 阶段二:第一次AI调用 - Planning + 预定位
3.2.2.1. 大模型入参准备
// packages/core/src/agent/tasks.ts
privateplanningTaskFromPrompt(
  userInstruction: string,
  opts: {
    log?: string;
    actionContext?: string;
    modelConfig: IModelConfig;
  },
) {
  const { log, actionContext, modelConfig } = opts;
  const task: ExecutionTaskPlanningApply = {
    type: 'Planning',
    subType: 'Plan',
    locate: null,
    param: { userInstruction, log },
    
    // Planning任务的执行器 - 第一次AI调用在这里
    executor: async (param, executorContext) => {
      const startTime = Date.now();
      
      // 1. 获取页面上下文(调用上面阶段一的逻辑)
      const { uiContext } = await this.setupPlanningContext(executorContext);
      
      // 2. 获取设备支持的操作空间
      const actionSpace = await this.interface.actionSpace();
      
      // 3. 调用AI进行任务规划 - 第一次AI调用的核心
      const planResult = await plan(param.userInstruction, {
        context: uiContext,           // 包含截图和DOM树
        log: param.log,
        actionContext,
        interfaceType: this.interface.interfaceType,
        actionSpace,                 // 可用操作:Input、KeyboardPress等
        modelConfig,
      });
      
      return {
        ...planResult,
        actions: planResult.actions || [],
        timeCost: Date.now() - startTime,
      };
    }
  };
  
  return task;
}

通过查看network,我们能看到,第一次和大模型的交互payload如下:

主要三部分:

  • system prompt:角色&目标定义
  • user prompt:用户query诉求
  • user prompt:image&text 上下文
3.2.2.2. prompt解析

这段system prompt翻译如下,最后面还有一些exmple,我这里就截断了,没有展示。

#角色
你是软件UI自动化领域的多才多艺专业人员。你的杰出贡献将影响数十亿用户的体验。
目标
● 将用户要求的指令分解为一系列操作
● 尽可能定位目标元素
● 如果无法完成指令,提供进一步计划。

#工作流程
1. 接收截图、截图的元素描述(如果有)、用户指令和之前的日志。
2. 将用户任务分解为一系列可行的操作,并放置在actions字段中。有不同类型的操作(点击/右键点击/双击/悬停/输入/键盘按键/滚动/拖放/长按/滑动)。下面"关于操作"部分将给你更多详细信息。
3. 考虑你组合的操作执行后是否完成了用户指令。
  ○ 如果指令已完成,将more_actions_needed_by_instruction设置为false。
  ○ 如果需要更多操作,将more_actions_needed_by_instruction设置为true。在log字段中仔细记录已完成的内容,下一位与你类似的人才将根据你的日志继续任务。
4. 如果在此页面上任务不可行,在error字段中设置原因。

#约束条件
● 你组合的所有操作必须是可行的,这意味着所有操作字段都可以用你获得的页面上下文信息填充。如果不行,不要计划此操作。
● 相信"已完成的内容"字段中关于任务的内容(如果有),不要重复其中的操作。
● 只响应有效的JSON。不要写引言、总结或markdown前缀如json。
● 如果截图和指令完全不相关,在error字段中设置原因。

#关于actions字段
locate参数通常在操作的param字段中使用,表示定位要执行操作的目标元素,它符合以下方案:
type LocateParam = { “id”: string, // 找到的元素的id。应该是在截图中用矩形标记的id或描述中描述的id。 “prompt”?: string // 要查找元素的描述。只有当locate为null时才可以省略。 } | null // 如果不在页面上,LocateParam应该为null

#支持的操作
每个操作都有一个type和相应的param。详细如下:
● 点击,点击元素
  ○ type: “Tap”
  ○ param:
    ■ locate: {“id”: string, “prompt”: string} // 要点击的元素
● 右键点击,右键点击元素
  ○ type: “RightClick”
  ○ param:
    ■ locate: {“id”: string, “prompt”: string} // 要右键点击的元素
● 双击,双击元素
  ○ type: “DoubleClick”
  ○ param:
    ■ locate: {“id”: string, “prompt”: string} // 要双击的元素
● 悬停,将鼠标移至元素上
  ○ type: “Hover”
  ○ param:
    ■ locate: {“id”: string, “prompt”: string} // 要悬停的元素
● 输入,将值输入到元素中
  ○ type: “Input”
  ○ param:
    ■ value: string// 要输入的值
    ■ locate?: {“id”: string, “prompt”: string} // 要输入的元素
● 键盘按键,按功能键,如"Enter"、“Tab”、“Escape”。不要使用此操作输入文本。
  ○ type: “KeyboardPress”
  ○ param:
    ■ locate?: {“id”: string, “prompt”: string} // 按键前要点击的元素
    ■ keyName: string// 要按的键
● 滚动,滚动页面或元素。滚动方向、滚动类型和滚动距离。距离是滚动的像素数。如果未指定,使用down方向、once滚动类型和null距离。
  ○ type: “Scroll”
  ○ param:
    ■ direction?: enum(‘down’, ‘up’, ‘right’, ‘left’) // 滚动方向
    ■ scrollType?: enum(‘once’, ‘untilBottom’, ‘untilTop’, ‘untilRight’, ‘untilLeft’) // 滚动类型
    ■ distance?: number // 滚动的像素距离
    ■ locate?: {“id”: string, “prompt”: string} // 要滚动的元素
● 拖放,拖放元素
  ○ type: “DragAndDrop”
  ○ param:
    ■ from: {“id”: string, “prompt”: string} // 拖动的位置
    ■ to: {“id”: string, “prompt”: string} // 放置的位置
● 长按,长按元素
  ○ type: “LongPress”
  ○ param:
    ■ locate: {“id”: string, “prompt”: string} // 要长按的元素
    ■ duration?: number // 长按持续时间(毫秒)
● 滑动,执行滑动手势。你必须指定"end"(目标位置)或"distance" + “direction” - 它们是互斥的。使用"end"进行精确基于位置的滑动,或使用"distance" + "direction"进行相对移动。
  ○ type: “Swipe”
  ○ param:
    ■ start?: {“id”: string, “prompt”: string} // 滑动手势的起点,如果未指定,将使用页面中心
    ■ direction?: enum(‘up’, ‘down’, ‘left’, ‘right’) // 滑动方向(使用distance时需要)。方向表示手指滑动的方向。
    ■ distance?: number // 滑动的像素距离(与end互斥)
    ■ end?: {“id”: string, “prompt”: string} // 滑动手势的终点(与distance互斥)
    ■ duration?: number // 滑动手势的持续时间(毫秒)
    ■ repeat?: number // 重复滑动手势的次数。默认为1,0表示无限(例如无尽滑动直到页面结束)

#输出JSON格式
JSON格式如下:
{ “actions”: [ // … 一些操作 ],
“log”: string, // 根据截图和指令记录你下一步可以做什么操作。典型的日志看起来像"现在我想使用’{ action-type }'操作来做.."。如果不应该做任何操作,记录原因。使用与用户指令相同的语言。 
“error”?: string, // 关于意外情况的错误消息(如果有)。只有当根据指令无法预见的情况才认为是错误。使用与用户指令相同的语言。
“more_actions_needed_by_instruction”: boolean, // 考虑在"Log"中的操作完成后,根据指令是否还需要更多操作。如果是,将此字段设置为true。否则,设置为false。
 }

3.2.2.3. 大模型输出

在prompt中已经对于输出有约束了,大模型的输出如下:

{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": {
          "actions":[
            {"thought":"找到搜索框并输入关键词“拖鞋”。","type":"Input","param":{"value":"拖鞋","locate":{"id":"mofkb","prompt":"搜索框"}}},
            {"thought":"在输入关键词后,敲击Enter键进行搜索。","type":"KeyboardPress","param":{"keyName":"Enter"}}
          ],
          "log":"现在我想使用动作 'Input' 在搜索框中输入“拖鞋”,然后使用动作 'KeyboardPress' 敲击Enter键进行搜索。",
          "more_actions_needed_by_instruction":false}
      },
      "index": 0,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "audioTokens": null,
    "completion_tokens": 116,
    "prompt_tokens": 34149,
    "total_tokens": 34265,
    "completion_tokens_details": {
      "audio_tokens": 0,
      "reasoning_tokens": 0,
      "rejected_prediction_tokens": 0
    },
    "prompt_tokens_details": {
      "audio_tokens": 0,
      "cached_tokens": 0
    },
    "cache_creation_input_tokens": null,
    "cache_read_input_tokens": null
  }
}

到这一步,大家仔细观察,这一步,其实大模型已经找到了输入框的元素id。

3.2.3. 阶段三:元素验证机制

基于上述大模型的返回,进一步填充真实的元素,得到可以执行的action

actions.forEach((action) => {
  const actionInActionSpace = opts.actionSpace.find(a => a.name === action.type);
  const locateFields = findAllMidsceneLocatorField(actionInActionSpace?.paramSchema);

  locateFields.forEach((field) => {
    const locateResult = action.param[field];
    if (locateResult && !vlMode) {
      // 通过DOM树查找元素,填充真实的元素ID
      const element = elementById(locateResult);
      if (element) {
        action.param[field].id = element.id; // 关键:填充DOM元素ID为"mofkb"
      }
    }
  });
});

基于action,开始验证元素的定位

// packages/core/src/agent/tasks.ts
public async convertPlanToExecutable(
  plans: PlanningAction[],
  modelConfig: IModelConfig,
) {
  const tasks: ExecutionTaskApply[] = [];

  const taskForLocatePlan = (plan: PlanningAction<PlanningLocateParam>) => {
    // 为每个需要定位的操作创建定位任务
    const taskFind: ExecutionTaskInsightLocateApply = {
      type: ‘Insight’,
      subType: ‘Locate’, 
      locate: plan.locate,
      thought: plan.thought,
      
      // 元素定位的执行器 - 核心验证逻辑
      executor: async (param, taskContext) => {
        const { uiContext } = await this.setupPlanningContext(taskContext);
        
        // 四层验证策略开始!
        
        // 1. XPath验证 (最高优先级)
        const elementFromXpath = param.xpath && this.interface.getElementInfoByXpath
          ? await this.interface.getElementInfoByXpath(param.xpath)
          : undefined;
        const userExpectedPathHitFlag = !!elementFromXpath;

        // 2. 缓存验证 (第二优先级)  
        const cachePrompt = param.prompt;
        const locateCacheRecord = this.taskCache?.matchLocateCache(cachePrompt);
        const xpaths = locateCacheRecord?.cacheContent?.xpaths;
        const elementFromCache = userExpectedPathHitFlag ? null : 
          await matchElementFromCache(this, xpaths, cachePrompt, param.cacheable);
        const cacheHitFlag = !!elementFromCache;

        // 3. Plan结果验证 (第三优先级) - 验证第一次AI的预定位结果
        const elementFromPlan = !userExpectedPathHitFlag && !cacheHitFlag
          ? matchElementFromPlan(param, uiContext.tree)
          : undefined;
        const planHitFlag = !!elementFromPlan;

        // 4. AI Fallback验证 (保底机制) - 第二次AI调用在这里触发!
        const elementFromAiLocate = !userExpectedPathHitFlag && !cacheHitFlag && !planHitFlag
          ? (await this.insight.locate(param, {context: uiContext}, modelConfig)).element
          : undefined;
        const aiLocateHitFlag = !!elementFromAiLocate;

        // 最终选择: xpath > cache > plan > AI fallback
        const element = elementFromXpath || elementFromCache || elementFromPlan || elementFromAiLocate;

        if (!element) {
          thrownew Error(无法定位元素:&nbsp;${param.prompt});
        }

        return { element, timeCost: Date.now() - startTime };
      }
    };
    
    return taskFind;
  };

  // 为每个Planning Action创建对应的执行任务
  plans.forEach((plan) => {
    if (plan.locate) {
      tasks.push(taskForLocatePlan(plan)); // 先定位元素
    }
    tasks.push(this.taskForActionPlan(plan));  // 再执行操作
  });

  return tasks;
}

基于上述代码,我们可以看到,元素验证的优先级xpath > cache > plan > AI fallback,最后,落到了AI fallback逻辑(因为前面的大模型返回只有id,没有定位信息等),开始调用大模型,继续验证元素的准确性。

3.2.4. 阶段四:元素验证二次调用LLM
3.2.4.1. 构建参数&模型调用
// packages/core/src/insight/index.ts
async locate(
  query: DetailedLocateParam,
  opt: LocateOpts,
  modelConfig: IModelConfig,
): Promise<LocateResult> {
  const { context } = opt;
  const queryPrompt = parsePrompt(query.prompt);

  // 可选的深度思考定位(区域搜索)
  let searchArea: Rect | undefined;
  let searchAreaResponse: Awaited<ReturnType<typeof AiLocateSection>> | undefined;
  if (query.deepThink) {
    searchAreaResponse = await AiLocateSection({
      context,
      sectionDescription: queryPrompt,
      modelConfig,
    });
    searchArea = searchAreaResponse.rect;
  }

  const startTime = Date.now();
  
  // 核心:调用AiLocateElement进行第二次AI定位
  const {
    parseResult,
    rect,
    elementById,
    rawResponse,
    usage,
    isOrderSensitive,
  } = await AiLocateElement({
    callAIFn: this.aiVendorFn,
    context,
    targetElementDescription: queryPrompt, // “搜索框”
    searchConfig: searchAreaResponse,
    modelConfig,
  });

  const elements: BaseElement = ;
  (parseResult.elements || ).forEach((item) => {
    if (‘id’ in item) {
      const element = elementById(item?.id);
      if (!element) {
        console.warn(locate: cannot find element id=${item.id}. Maybe an unstable response from AI model);
        return;
      }
      elements.push(element);
    }
  });

  if (elements.length === 1) {
    return {
      element: elements[0],
      // … 其他返回信息
    };
  }

  thrownew Error(定位失败或找到多个元素:&nbsp;${elements.length});
}

从上述的代码,以及我们从network里面的抓包,可以看到,这一次LLM的调用,整体的结构和第一差不多。

主要的区别如下:

  • system prompt:改变为验证逻辑
  • image_url: 无变化
  • text:改变为
Here is the item user want to find:
=====================================
搜索框
=====================================
${这里是原来的dom结构}
3.2.4.2. prompt解析

Output Format之前,已翻译成中文。

## 角色:
你是软件页面图像(2D)和页面元素文本分析专家。

目标:

- 识别截图和文本中与用户描述匹配的元素
- 返回包含选择原因和元素ID的JSON数据
- 判断用户的描述是否对顺序敏感(例如,包含"列表中的第三项"、"最后一个按钮"等短语)

技能:

- 图像分析和识别
- 多语言文本理解 
- 软件UI设计和测试

工作流程:

  1. 接收用户的元素描述、截图和元素描述信息。注意文本可能包含非英语字符(例如中文),表明应用程序可能是非英语的。
  2. 基于用户的描述,在元素描述列表和截图中定位目标元素ID。
  3. 找到所需数量的元素
  4. 返回包含选择原因和元素ID的JSON数据。
  5. 判断用户的描述是否对顺序敏感(见下文定义和示例)。

约束条件:

- 描述所需元素时严格遵守指定位置;不要从其他位置选择元素。
- 图像中NodeType不是"TEXT Node"的元素已被高亮显示,以在多个非文本元素中识别元素。
- 根据用户的描述准确识别元素信息,并从元素描述信息中返回相应的元素ID,而不是从图像中提取。
- 如果找不到元素,"elements"数组应为空。
- 返回的数据必须符合指定的JSON格式。
- 返回值id信息必须使用来自元素信息的id(重要:使用id而不是indexId,id是哈希内容)

顺序敏感定义:

- 如果描述包含"列表中的第三项"、“最后一个按钮”、“第一个输入框”、“第二行"等短语,则是顺序敏感的(isOrderSensitive = true)。
- 如果描述像"确认按钮”、“搜索框”、"密码输入"等,则不是顺序敏感的(isOrderSensitive = false)。

## Output Format:

Please return the result in JSON format as follows:

```json
{
“elements”: [
// If no matching elements are found, return an empty array
    {
“reason”: “PLACEHOLDER”, // The thought process for finding the element, replace PLACEHOLDER with your thought process
“text”: “PLACEHOLDER”, // Replace PLACEHOLDER with the text of elementInfo, if none, leave empty
“id”: “PLACEHOLDER”// Replace PLACEHOLDER with the ID (important: use id not indexId, id is hash content) of elementInfo
    }
// More elements…
  ],
“isOrderSensitive”: true, // or false, depending on the user’s description
“errors”:  // Array of strings containing any error messages
}
```

## Example:
Example 1:
Input Example:
```json
// Description: “Shopping cart icon in the upper right corner”
{
“description”: “PLACEHOLDER”, // Description of the target element
“screenshot”: “path/screenshot.png”,
“text”: '{
      “pageSize”: {
        “width”: 400, // Width of the page
        “height”: 905 // Height of the page
      },
      “elementInfos”: [
        {
          “id”: “1231”, // ID of the element
          “indexId”: “0”, // Index of the element,The image is labeled to the left of the element
          “attributes”: { // Attributes of the element
            “nodeType”: “IMG Node”, // Type of element, types include: TEXT Node, IMG Node, BUTTON Node, INPUT Node
            “src”: “https://ap-southeast-3.m”,
            “class”: “.img”
          },
          “content”: “”, // Text content of the element
          “rect”: {
            “left”: 280, // Distance from the left side of the page
            “top”: 8, // Distance from the top of the page
            “width”: 44, // Width of the element
            “height”: 44 // Height of the element
          }
        },
        {
          “id”: “66551”, // ID of the element
          “indexId”: “1”, // Index of the element,The image is labeled to the left of the element
          “attributes”: { // Attributes of the element
            “nodeType”: “IMG Node”, // Type of element, types include: TEXT Node, IMG Node, BUTTON Node, INPUT Node
            “src”: “data:image/png;base64,iVBORw0KGgoAAAANSU…”,
            “class”: “.icon”
          },
          “content”: “”, // Text content of the element
          “rect”: {
            “left”: 350, // Distance from the left side of the page
            “top”: 16, // Distance from the top of the page
            “width”: 25, // Width of the element
            “height”: 25 // Height of the element
          }
        },
        …
        {
          “id”: “12344”,
          “indexId”: “2”, // Index of the element,The image is labeled to the left of the element
          “attributes”: {
            “nodeType”: “TEXT Node”,
            “class”: “.product-name”
          },
          “center”: [
            288,
            834
          ],
          “content”: “Mango Drink”,
          “rect”: {
            “left”: 188,
            “top”: 827,
            “width”: 199,
            “height”: 13
          }
        },
        …
      ]
    }
  ’
}
```
Output Example:
```json
{
“elements”: [
    {
// Describe the reason for finding this element, replace with actual value in practice
“reason”: “Reason for finding element 4: It is located in the upper right corner, is an image type, and according to the screenshot, it is a shopping cart icon button”,
“text”: “”,
// ID(use id not indexId) of this element, replace with actual value in practice, use id not indexId
“id”: “1231”
    }
  ],
“isOrderSensitive”: true,
“errors”:
}
```

通过这里的prompt其实可以看出,这一步主要是验证元素。

3.2.4.3. 大模型输出
{
  "choices": [
    {
      "message": {
        "role": "assistant",
        "content": {"elements":[{"reason":"The element with ID 'mofkb' is an input box with the class '.search-suggest-combobox-imageSearch-input', located at the top of the page, which matches the description of a search input box.","text":"拖鞋","id":"mofkb"}],"isOrderSensitive":false,"errors":[]}
      },
      "index": 0,
      "finish_reason": "stop"
    }
  ],
  "usage": {
    "audioTokens": null,
    "completion_tokens": 73,
    "prompt_tokens": 83840,
    "total_tokens": 83913,
    "completion_tokens_details": {
      "audio_tokens": 0,
      "reasoning_tokens": 0,
      "rejected_prediction_tokens": 0
    },
    "prompt_tokens_details": {
      "audio_tokens": 0,
      "cached_tokens": 0
    },
    "cache_creation_input_tokens": null,
    "cache_read_input_tokens": null
  }
}

到这里,已经确定"id":"mofkb"是我们要找到的input输入框。

3.2.5. 阶段五:自动化操作执行
3.2.5.1. 输入操作执行

第一个动作:在搜索框中输入"拖鞋"

// 1. 清空搜索框
await page.clearInput(element);

// 2. 输入"拖鞋"
await page.keyboard.type(“拖鞋”);

Chrome扩展的具体实现

// packages/web-integration/src/chrome-extension/page.ts
async clearInput(element){
  // 点击搜索框获得焦点
  await this.mouse.click(element.center[0], element.center[1]);
  
  // 全选现有内容
  await this.sendCommandToDebugger('Input.dispatchKeyEvent', {
    type: 'keyDown',
    commands: ['selectAll'],
  });
  
  // 删除选中内容
  await this.keyboard.press({ key: 'Backspace' });
}

async keyboardType(text){
  // 通过Chrome DevTools Protocol输入文本
  for (constchar of text) {
    await this.sendCommandToDebugger(‘Input.dispatchKeyEvent’, {
      type: ‘char’,
      text: char,
    });
  }
}

3.2.5.2. 回车键执行

第二个动作:按下Enter键

由于AI规划的是在搜索框上按Enter,系统会:

// 1. 确保搜索框仍有焦点
await this.mouse.click(element.center[0], element.center[1]);

// 2. 发送Enter键事件
await this.keyboard.press({ key: ‘Enter’ });

CDP键盘事件实现

// packages/web-integration/src/chrome-extension/cdpInput.ts
async press(keyOptions){
  const { key } = keyOptions;
  
  // 发送按键按下事件
  await this.sendCommandToDebugger('Input.dispatchKeyEvent', {
    type: 'keyDown',
    code: `Key${key}`,
    key: key,
    windowsVirtualKeyCode: this.getKeyCode(key),
  });
  
  // 发送按键释放事件
  await this.sendCommandToDebugger('Input.dispatchKeyEvent', {
    type: 'keyUp',
    code: `Key${key}`,
    key: key,
    windowsVirtualKeyCode: this.getKeyCode(key),
  });
}

四、使用中遇到的问题

目前我们在使用Midscene.js用于业务落地的过程中,遇到了一些问题,这里做一个记录:

4.1. 抓取信息,内容丢失

某些页面,比如1688的搜索页,在抓取商品图片的时候,他不是用image来实现的,用的是style的background-image。

这个时候如果想获取图片地址,不管你怎么调试你的prompt,都是无用的。

在上述3.2.1.3中,我们分析过,在给大模型之前,他会提取dom结构,然后把所有的css,style都过滤掉,保留text node相关的节点。如果你的图片是写在style中的,那么他处理完dom结构的时候,已经把信息丢失了,所有100%会提取失败。

我们看下源码 packages/shared/src/extractor/tree.ts

可以修改下条件判断:

if ('htmlTagName' === currentKey || 'nodeType' === currentKey || 
    ('style' === currentKey && !attributeVal.includes("background-image"))) 
{
  return res;
}

4.2. 长度截断问题

在与AI交互的过程中,发现返回的内容有时候有截断的情况。

这里第一想法是调整prompt,比如“返回完整的图片地址,禁止截断”,无论怎么调整prompt,发现一点用都没有。最后看源码:

问题出在这里,在提取dom信息构建上下文的过程中,为了防止上下文过长,对传入大模型的内容,做了截断。类似于下面这个情况:

<div id="article123" markerId="5">
  这是一篇非常长的文章内容,包含了大量的文字信息,可能有成千上万个字符,这些内容如果不进行截断处理,会导致发送给AI模型的提示词变得非常庞大,不仅影响处理速度,还可能超出模型的上下文限制,同时也会增加API调用的成本,不仅影响处理速度,还可能超出模型的上下文限制,同时也会增加API调用的成本...
</div>

根据自己实际的诉求,可以调整这个值的长度。

4.3. LLM模型下,部分操作不生效

在im场景,尤其是聊天对话框存在时,发现点击“发送”按钮无反应,原因是这类im场景,大部分用iframe实现的,语言模型情况下,对于iframe内的dom获取和元素定位会有限制。

这个时候,切换到VL模型即可。

4.4. VL模型,可视区域外准确率差

如果你要总结页面的评价,类似于下面的query。

总结页面的“用户评价”,如果存在“更多”,先打开“更多”后总结。

但是评价不在可视区域内,你会发现VL模型会尝试滚动,确实也操作了滚动。然后不停地尝试找到更多的区域,尝试10次,就失败了。大模型的返回如下:

The user wants to summarize the '用户评价' section on the page. If there is a '更多' option, it should be opened first before summarizing. According to the screenshot, the '用户评价' section is visible, but there is a hint indicating that more content can be viewed by swiping right in the right-side area. Therefore, the next step is to swipe right in the specified area to reveal more user reviews.

类似于这种不在视窗内的Query操作,建议直接试用LLM模型,获取页面的dom结构去操作,准确性会高很多。

4.5. 设置VL模型不生效

明明配置了VL模型,但是发现很多时候,没有返回"bbox": [340, 65, 981, 97]坐标,原因是配置了MIDSCENE_MODEL_NAME,但是没有配置vlMode,MIDSCENE_USE_QWEN_VL=1

MIDSCENE_MODEL_NAME="qwen-vl-max-latest"
MIDSCENE_USE_QWEN_VL=1  //使用qwen vl模型时,必选

4.6. domIncluded设置可见元素异常

在做大模型返回时长优化的时候,想通过减少dom的大小,来提升大模型返回的时效,但是设置了domIncluded以后,发现提取信息的准确率有大幅的下降。

const dataD = await agent.aiQuery(
  '{name: string, age: string, avatarUrl: string}[], 列表中的数据记录',
  { domIncluded: 'visible-only' },
);

原因是设置了'visible-only'以后,提取的dom只有视窗范围内的dom,并不是把display:none等元素过滤掉,这样对于结果的准确率肯定是有较大的影响的。

五、对业务落地的思考

Midscene.js本质上是一个自然语言操作浏览器执行的智能体,prompt组装、跨平台的适配、缓存机制、CDP封装、报告&回放等是他的核心价值,能力的上限还是在于大模型的准确性和执行效率。

除了在自动化领域,我们能做一些测试回归工作,能否类似于Google's Project Mariner,落地一些商业产品呢?

以下是我的一些思考:

5.1. 新手引导

传统的新手引导,都是基于dom录制引导步骤(手动硬编码或者配置化),这里可以考虑利用AI驱动浏览器自动化,通过自然语言的方式,引导商家完成一件事情,比如“帮我发第一个品”,“帮我分析xx品为什么转化率这么低”进而进行自动化分析,并引导商家配置任务。

5.2. 智能营销

在上面新手引导思路之下,更进一步,能否做到 GUI Agent 模式呢,比如帮助用户生成营销海报、营销文本后,一键执行“帮我到FACEBOOK发送一条营销信息”。其实也能做,官方提供UI-TARS 等模型,大家可以尝试。在效果不稳定的情况下,也可以预制一些流程,通过即时操作接口(agent.aiTap)来提升准确率。

5.3. 智能发品

传统的自动化发品走的是api调用,把发品信息映射到api的字段,或者修改前端代码,提供一些暴露在window上面的function,拿到信息后,调用function,setValue,去自动化的填写表单。

但有了midscene这套基建后,完全可以基于用户给出的一些信息,在无侵入源码的基础上,帮助用户完成表单的填写。

5.4. AI code提供上下文

AI code领域,无论是claude code等命令行式的工具,还是cursor 等类vscode ide的工具,client、codebase、基座模型等能力已经比较成熟了,目前在准确率上面唯一的瓶颈,就是代码运行时了。如果能获取到当前代码的entry,运行时的console,network,dom信息等,这将对ai coding的准确率有质的提升。

目前在新版本的cursor中,已经集成Browser能力,可以帮助你打开浏览器,获取报错信息等。


Qwen-Image,生图告别文字乱码


针对AI绘画文字生成不准确的普遍痛点,本方案搭载业界领先的Qwen-Image系列模型,提供精准的图文生成和图像编辑能力,助您轻松创作清晰美观的中英文海报、Logo与创意图。此外,本方案还支持一键图生视频,为内容创作全面赋能。


点击阅读原文查看详情。


我肯定倾向于混合策略!纯用VL模型,成本虽低但在处理非可视区域或复杂文本信息时确实会受限;纯用LLM模型,虽然能力强但成本太高,不划算。理想的方案应该是VL模型为主,处理大部分常规、可视操作;当VL分析不确定或任务明确涉及大量文本、非可视区域时,再调用LLM进行“深度思考”,这样既能兼顾效率和准确性,又能控制成本,达到最佳平衡。有点像先用高速公路,关键时刻再走专家通道的感觉。

我觉得最现实的还是团队的学习曲线和投入产出比。虽然AI工具听起来很美,但如果团队里大部分人对AI原理、模型调优都不熟悉,那初期培训成本和出错了定位问题的难度都会很高。另外,这类工具往往依赖云服务和大模型API,长期看成本是否可控?尤其是数据隐私,如果涉及到生产环境的操作和数据采集,合规性绝对是头等大事,不能因为方便就松懈。

哈哈,如果真能做到文章里说的这种智能程度,那未来我可能就不用自己打游戏了!AI可以帮我刷副本、做日常任务,甚至帮我抢限量版皮肤了!:rofl: 再严肃点想,在金融行业,可以自动化处理繁琐的银行开户流程、贷款审批,或者智能识别交易异常并自动触发风控响应。在客户服务领域,AI能模拟客户路径去测试聊天机器人、客服系统的用户体验,确保对话流程顺畅,并且针对常见问题自动化生成解决方案。这简直就是“懒人福音”和“效率神器”啊!

脑洞一下,AI驱动的UI自动化在个人生产力方面潜力巨大。想象一下,它可以成为你的“数字分身”,帮你完成那些重复且需要多应用协作的任务。比如,每天早上自动整理邮件中的待办事项,然后自动在项目管理工具中创建任务,并同步到日程表;或者帮你监控电商平台的价格波动,在达到心理价位时自动完成下单支付。甚至可以帮助老年人或残障人士,通过简单的语音指令来操作复杂的智能居家设备或在线服务,极大地提升生活便利性。

从项目管理角度看,引入AI自动化首先要做好充分的POC(概念验证)和风险评估。技术栈更新是必然的,需要规划专门的培训和知识分享,培养“AI自动化工程师”这样的新角色。至于成本,不能只看API调用费,还要把前期调研、集成、维护、问题解决的人力成本都算进去。数据安全和合规性更是重中之重,尤其是针对敏感业务数据,必须确保模型输入输出不泄露,甚至可能需要考虑私有化部署的方案。长远来看,还要考虑模型的迭代速度,避免今天刚熟练,明天模型API又大变样。

在权衡成本与准确性时,我会倾向于构建一个“智能调度”系统。默认情况下,优先使用VL模型进行元素定位,因为它成本低且足以满足大部分可视区域内的操作。当VL模型连续定位失败,或用户明确指令涉及滚动、查找非可视区域元素、或需要更复杂的语义理解时,再动态切换到LLM模型进行“深度扫描”或“意图分析”。
VL模型的劣势在处理高度动态、布局变化剧烈或非可视区域元素时会特别明显。它依赖视觉信息,一旦元素不在视野或视觉特征变化,就容易失效。LLM模型的劣势在于高昂的token成本,以及处理非常大、复杂的DOM结构时,可能出现截断或理解偏差,导致准确性反而下降。最优解是在两者之间找到一个智能的平衡点,扬长避短。

嗯,除了技术和钱的问题,我觉得“老板的期望值管理”也很重要!AI来了,是不是就以为能一劳永逸了?得给领导和业务方普及清楚,AI自动化是提升效率,但不是万能药,初期可能还会踩不少坑。还有,测试人员会不会担心自己的饭碗?怎么转型做更高级的测试或者AI模型验证,这也是团队士气和职业发展需要考虑的。别最后自动化没搞好,团队还人心惶惶的。

我觉得在教育领域,AI驱动的UI自动化能大放异彩。比如可以模拟学生操作虚拟实验平台,自动批改实验报告中的步骤和结果,或者智能辅导学生完成在线编程练习,甚至可以模拟不同用户路径来测试教育App的功能和易用性。在医疗领域,可以帮助医生自动化录入患者信息到不同系统,或者辅助进行远程诊疗平台的操作,减轻医护人员的非核心工作负担。

这问题问到点子上了!作为穷B开发者,当然是能省则省!初期肯定先用VL模型,能解决百分之八十的问题就阿弥陀佛了。实在不行,再手动优化,或者加个判定逻辑:如果VL模型连续失败N次,或者遇到某个特定页面类型(比如iframe巨多的IM聊天页),就自动切换到LLM模型。
VL模型劣势就是“瞎子摸象”,看不见就抓不着,或者抓不准。LLM模型呢,劣势就是“话痨”,你给它一堆DOM它能跟你聊半天,聊完了还可能说错,而且钱包先顶不住。所以,两者配合,让VL干体力活,LLM干烧脑活,但仅限烧少量脑的活,才能把成本压下去。

从实践角度出发,初创项目或小型团队在资源有限的情况下,传统工具无疑门槛更低、学习曲线更平缓。AI驱动的工具涉及到模型部署、API调用成本,以及可能需要更强的AI理解和调优能力,这些对小团队来说是额外的负担。而且,如果自动化需求仅仅是简单的表单填写、按钮点击,写几行Playwright脚本可能比配置和调试AI模型来得更快、更有效率。毕竟,杀鸡焉用牛刀,适合的才是最好的。

从项目落地的角度来看,“VL模型可视区域外准确率差”和“LLM模型成本最高”这两个问题,我感觉最影响Midscene.js的推广和普及。你想啊,我们做自动化,很多时候就是要处理长页面、滚动加载或者复杂交互。如果VL模型搞不定滚屏外的元素,而LLM又太贵不能随便用,那项目预算和ROI就会是很大的挑战,限制了AI自动化的应用广度。这就像你买了一辆特别好的车,但在市区跑不开,郊区又没油。要缓解的话,我觉得可以设计一个更智能的动态决策机制:当VL模型无法确定但成本较低时,先尝试智能滚动,如果多次滚动后仍找不到,再自动或半自动地切换到LLM获取完整DOM信息,实现成本和准确率的动态平衡。同时,也要考虑模型的优化,如何在保证效果的前提下降低LLM的token消耗。

说实话,AI驱动的UI自动化工具越智能,风险也越大。就好比你给个特别聪明的实习生,让他去办公室帮你处理文件。你让他“把那个报告发给大家”,结果他把“机密”报告发给了“所有人” :joy:!这就是所谓的AI误解意图或误操作。所以,在“安全和伦理风险”方面,首先权限控制要做到极致,AI能操作什么、不能操作什么,必须有明确的范围和严格的审批。其次,要有审查机制和紧急停止按钮,操作完能立即撤销,最好还有个“后悔药”机制。数据隐私方面就更不用说了,AI看过的每一个页面,和它聊过的每一句话,都可能成为敏感信息,企业必须有严格的内外部数据使用规范,防止滥用和泄露。

针对“Midscene.js这种自然语言操作UI的模式对传统软件开发、测试流程的影响和未来应用场景”这个话题,我觉得最直接的就是HCI(人机交互)进入了新的阶段,从GUI层面的直接操作转向更高级的语义理解。对前端开发来说,未来可能会出现“意图驱动开发”(Intent-driven Development),通过描述业务目标而非写具体UI组件代码就能生成界面。测试领域将从“脚本维护者”转变为“测试策略设计者”,更关注高层次的业务流程和用户体验,而不是底层元素定位。应用场景上,除了文章提到的新手引导、智能营销、自动化发品,我还在想,是不是个人AI日程助手能自动处理各种网页事务,比如预订、填表、甚至跨平台处理邮件、社交媒体等等,真正的AIGC(AI Generated Content/Code)可能会直接生成可执行的自动化流程。这种自动化程度将是前所未有的。

哇!“自然语言操作UI”听起来就超酷!最直接的感觉就是,测试部门要解放生产力了!以前改动一点UI,QA的测试脚本就得跟着改,烦死人!现在AI自己理解页面,维护成本肯定大大降低。开发这边,一些原型验证或者快速DEMO,是不是可以直接跟AI说“在屏幕顶部放个搜索框,旁边加个提交按钮”,然后页面就出来了:joy: 简直是懒人福音!

至于未来嘛,我觉得像电商平台的客服,现在很多是真人+机器人混合模式,以后估计AI可以直接用自然语言进入后台操作用户的购物车、退换货流程了,效率更高、更流畅!甚至可能出现“AI私人管家”,帮你处理各种线上业务,什么抢票啊,比价购物啊,简直是懒人到极致的梦想实现了!

关于“自然语言操作UI对开发测试流程的颠覆性以及未来应用场景”,颠覆性改变肯定会有,最明显的是降低了自动化脚本的编写门槛,让非技术或业务人员也能通过自然语言创建自动化流程。这意味着测试工程师可以更高效率地完成回归测试,开发人员在自测时也能更快地验证功能。但要说完全取代传统方式,道阻且长。

未来的应用场景,我觉得更多的是现有场景的“智能化增强”。比如在B端业务中,高度标准化、重复性的操作会普及得很快,比如财务系统的自动对账、信息录入等。更进一步,是跨系统(Web、桌面、移动)的业务流自动化,比如AI连接CRM、ERP和办公软件完成一个复杂的销售流程。但面对需要创意、复杂推理或强逻辑判断的场景,还得是人来主导结合AI辅助。

讨论“AI驱动的UI自动化工具在企业级应用中面临的潜在安全和伦理风险”,这个真是个大问题,必须高度重视。首先是“权限泛化”风险,AI获取的UI上下文信息(截图、DOM)可能包含敏感业务数据甚至用户隐私,如果工具本身或模型被攻破,就成了高价值的攻击目标。其次是“意图漂移”或“误操作”,比如AI将用户指令“清空购物车”错误理解为“清空订单”,在高并发或关键业务流程(如财务支付、数据删除)中,这可能导致灾难性后果,造成重大经济损失甚至法律责任。伦理层面,如果AI操作行为不透明,难以追溯,一旦出现责任事故,归因将非常困难。此外,AI的“决策”也可能被恶意利用,比如刻意引导用户到某个特定页面进行欺诈或不公平的竞争行为,这涉及到很复杂的伦理边界问题。

问到“AI驱动的UI自动化工具在企业级应用中的安全和伦理风险”,我最怕的就是账号安全和数据泄露这两块。你想啊,AI能帮你登录、操作,那万一它的API key或者模型接口被别人搞到,不就相当于把我的账号和操作权限完全送出去了吗?黑客拿到这些,可以直接假冒我的身份进行各种非法操作。还有误操作导致数据丢失或业务中断,比如AI把一个生产环境的删除按钮当成测试环境的点下去了,那可就凉凉了!几百万甚至上亿的数据没了,谁来负责?伦理方面,如果AI利用它“看到”的这些信息做用户画像,或者自动推送一些不合时宜的内容给我,甚至利用这些信息进行某种形式的“操纵”,那也是够呛的,感觉像是被一个无形的眼睛监视着。

我个人觉得最让人头疼的可能是“长度截断问题”和“设置VL模型不生效”这种“配置陷阱”吧!“哪个最考验开发者,最影响推广和普及?”——我选它们!你以为你配置好了,结果它自己给你截断了重要的DOM信息,或者明明想用VL却还在用LLM,这种隐性的问题调试起来实在太费劲了,而且往往不是业务逻辑的问题,而是底层工具的“脾气”。对于普通用户来说,这种不透明的“黑箱”体验,会大大降低他们对工具的信任感,觉得AI不可控。最好的解决办法可能是在Midscene.js的界面层,提供更友好的可视化反馈和错误提示,让开发者能清楚地知道当前AI“看到了什么”、“基于什么模型在思考”、以及“为什么失败了”,比如截断了哪些DOM,哪些元素识别失败的原因是什么。多一点透明度,少一点黑箱操作。