mcpField
T
test-site

test-site

github.com/microsoft测试与质量1.1k221 次安装今天更新
代码仓库

关于 test-site

test-site 是用于运行时测试已部署并激活的 Power Pages 站点的技能,面向站点开发者。它通过浏览器检查页面、认证状态和 Web API 请求,捕获响应形状并生成分类报告;测试过程只读,不修改文件或数据。

主要功能

  1. 01检查站点首页加载
  2. 02爬取最多 25 个页面
  3. 03记录浏览器控制台错误
  4. 04验证 Web API 请求状态
  5. 05捕获服务器逻辑响应结构
  6. 06生成分类测试报告

适用场景

  1. 01部署后检查站点页面是否可访问
  2. 02排查 /_api/ 请求失败
  3. 03确认 API 响应结构与前端预期

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/microsoft/power-platform-skills --skill test-site

安装后在 Claude Code 中输入/test-site确认已加载。

详细介绍

插件检查:运行 node "${PLUGIN_ROOT}/scripts/check-version.js"——如果输出消息,请在继续前展示给用户。

测试 Power Pages 站点

在运行时测试已部署并激活的 Power Pages 站点。在浏览器中访问站点,遍历所有可发现的链接,验证页面是否正确加载,捕获网络流量以测试 API 请求,并生成全面的测试报告。

前提条件: 此技能要求 Power Pages 站点已部署并激活。如果站点尚未上线,请先运行 /deploy-site 和 /activate-site。

核心原则

  • 非破坏性:此技能为只读,不会创建、修改或删除任何文件或数据。它只通过浏览器观察站点。
  • API 优先测试:除页面加载外,主要目标是验证所有 /_api/(Web API / OData)请求是否返回成功响应。
  • 响应结构发现:对于 /_api/serverlogics/ 端点,测试运行还必须捕获并报告实际响应正文结构,以便前端集成能够基于真实响应而非猜测进行编写。如果前端解析或字段访问与观察到的结构不匹配,请报告此差异,并说明所需的解析或字段访问更改——此技能不会修改任何代码。
  • 由用户控制身份验证:绝不要尝试自动登录。需要身份验证时,始终要求用户通过浏览器窗口登录。
  • 限制爬取范围:页面爬取上限为 25 页,以防止在包含动态或分页 URL 的站点上无限循环。

验证测试类别

每次运行都会生成一份分类测试报告(docs/alm/last-test-site.json——参见阶段 6.7a)。稳定的类别 ID 及其对应的生成阶段如下:

类别 id显示名称来源阶段覆盖内容
site-load站点加载阶段 2主页 HTTP 状态、重定向处理、初始渲染。主页对应一张卡片;失败属于严重问题。
authentication身份验证阶段 3从匿名访问跳转到 Entra、检测私有站点访问门槛、登录流程完整性。对于私有站点至关重要。
page-crawl页面爬取阶段 4每个测试页面对应一张卡片(最多 25 页)。每张卡片包含页面 URL、HTTP 状态和任何控制台错误。严重性按 HTTP 类别划分(5xx → 严重,公开站点上的 4xx → 高)。
web-apiWeb API阶段 5运行期间观察到的每个 /_api/ 端点对应一张卡片。捕获状态码、响应结构和修复提示(表权限 / 站点设置 / 内部错误设置)。
auth-pages已通过身份验证的页面阶段 5.6仅在登录后才能访问的页面。如果用户选择不进行已验证测试,则跳过。
auth-api已通过身份验证的 API阶段 5.6仅在登录后才能调用的 API 端点。如果跳过已验证测试,则跳过。
console控制台运行状况汇总汇总所有阶段观察到的控制台错误数量。默认严重性为中。

plan-alm 的 Validation 选项卡会直接使用此结构——每个类别都会成为每阶段子选项卡中的可折叠分组,而汇总后的 runOutcome(passed / passed-with-warnings / failed)则决定 Validation 选项卡和 Execution 清单子步骤中的绿色 / 黄色 / 红色 Outcome 徽章。

初始请求: $ARGUMENTS


阶段 1:确定站点 URL

目标: 确定要测试的 Power Pages 站点的线上 URL。

操作
1.1 创建任务列表

开始任何工作前,创建包含全部 6 个阶段的完整任务列表(参见进度跟踪表)。

1.2 检查用户输入

如果用户在 $ARGUMENTS 中提供了 URL:

  1. 验证其是否以 https:// 开头。
  2. 将其存储为 SITE_URL,然后跳至阶段 2。
1.3 根据激活状态自动检测

如果未提供 URL,请尝试自动检测:

  1. 搜索 powerpages.config.json,定位项目根目录:

    **/powerpages.config.json
    
  2. 运行激活状态检查脚本:

    node "${PLUGIN_ROOT}/scripts/check-activation-status.js" --projectRoot "<PROJECT_ROOT>"
    
  3. 评估 JSON 结果:

    • 如果 activated 为 true,且存在 websiteUrl:将 websiteUrl 用作 SITE_URL。告知用户:“已检测到您的站点 URL:”
    • 如果 activated 为 false:告知用户:“您的站点尚未激活。请先运行 /activate-site,然后重新运行此技能。”停止此技能。
    • 如果存在 error:转到第 1.4 步。
1.4 询问用户

如果自动检测失败或无法确定,请使用 AskUserQuestion:

问题标题选项
您要测试的已部署 Power Pages 站点的 URL 是什么?(例如:https://contoso.powerappsportals.com)站点 URL我会粘贴 URL(说明:在下方选择“其他”,然后粘贴您的站点 URL),我不知道自己的 URL(说明:运行 /activate-site 获取站点 URL,或查看 Power Platform 管理中心)

将用户提供的 URL 保存为 SITE_URL。

输出
  • 已解析 SITE_URL,可以开始测试

第 2 阶段:启动浏览器并初始加载

**目标:**在浏览器中打开站点,确认主页已加载,并捕获基线错误。

操作
2.1 调整浏览器大小

将浏览器设置为标准桌面视口:

  • 使用 browser_resize,设置 width: 1280, height: 720。
2.2 导航到站点
  • 使用 browser_navigate 打开 SITE_URL。
2.3 等待页面加载
  • 使用 browser_wait_for,设置 time: 5 秒,以便页面完全呈现(单页应用可能需要时间进行客户端路由和 API 调用)。
2.4 验证主页
  • 使用 browser_snapshot 获取无障碍快照。
  • 检查快照中是否有页面正常运行的迹象:
    • 页面包含有意义的内容(不是空白页,也不是通用错误页面)。
    • 查找常见错误提示:“404”、“Page not found”、“500”、“Internal Server Error”、“This site can't be reached”。
  • 如果页面显示错误,请向用户报告,并询问是继续还是停止。
2.5 捕获控制台错误
  • 使用 browser_console_messages,设置 level: "error",检查初始加载时是否出现 JavaScript 错误。
  • 记录发现的所有错误,这些错误会包含在最终报告中。
2.6 捕获初始网络请求
  • 使用 browser_network_requests,设置 includeStatic: false,捕获初始页面加载的 API 调用。
  • 记录任何 /_api/ 或 OData 请求及其状态代码,供第 5 阶段分析。
输出
  • 浏览器已启动,视口大小正确
  • 已加载主页,并通过快照完成验证
  • 已记录初始控制台错误和网络请求
  • 如果主页显示登录界面,已记录,以供第 3 阶段使用

第 3 阶段:身份验证检查

目标:检测站点是否需要身份验证,并在需要时处理登录。Power Pages 站点可能有两层身份验证:

  1. 专用站点门禁——整个站点为专用站点。导航到站点时,会在显示任何站点内容之前重定向到身份提供程序(Azure AD B2C 等)。浏览器 URL 通常会变为其他域名(例如 login.microsoftonline.com、*.b2clogin.com)。
  2. 站点级身份验证——站点可公开访问(主页可以加载),但某些页面或功能需要用户登录并具有特定 Web 角色。导航中会显示“登录”链接,或者页面会显示访问受限提示。
操作
3.1 分析首页快照,检查私有站点门禁

查看第 2.4 阶段的浏览器快照和当前浏览器 URL,检查是否存在私有站点重定向:

  • 页面内容显示身份提供商登录表单(Azure AD B2C、Azure AD 等)
  • 浏览器 URL 已变为不同于 SITE_URL 的域名(例如 login.microsoftonline.com、*.b2clogin.com 或自定义身份提供商域名)
  • 网站内容加载前返回了 401/403 响应
  • 页面空白,或显示“Access denied” / “You do not have access”,且看不到网站导航
3.2 处理私有站点门禁

🚦 门禁(暂停 · test-site:3.2.private-gate-login): 等待外部操作 — 网站已重定向至身份提供商;技能暂停,直到用户完成登录或取消。

如果检测到私有站点门禁,请使用 AskUserQuestion:

问题标题选项
此站点是私有站点——在任何内容加载前,它已重定向至身份提供商登录页面。浏览器窗口应该已打开并显示登录页面。请在该页面使用有权访问此站点的凭据登录。成功登录并看到网站首页后,请选择下方的“I have logged in”。私有站点登录I have logged in (Recommended) — 我已完成登录并可以看到网站,Cancel testing — 停止测试

如果选择“I have logged in”:

  1. 使用 browser_snapshot 确认用户现在位于实际网站(可以看到网站内容和导航,且 URL 已返回 SITE_URL 域名)。

  2. 如果仍处于身份提供商登录页面:

    🚦 门禁(暂停 · test-site:3.2.login-retry): 登录尚未完成 — 重新提示或取消。

    • 再次使用 AskUserQuestion:“看起来登录尚未完成。浏览器应该仍处于打开状态——请完成登录后重试。”
    • 重复此步骤,直到确认登录完成或用户取消。
  3. 确认登录后,重新运行第 2.5 和 2.6 阶段(在当前已加载的首页上捕获控制台错误和网络请求)。

  4. 继续执行第 3.3 步,检查站点级身份验证。

如果选择“Cancel testing”:

  • 停止技能,并告知用户解决访问问题后可以重新运行。
3.3 检查站点级身份验证

首页加载后(公网站点直接加载,或通过私有站点门禁后加载),查看快照,检查是否存在站点级身份验证迹象:

  • 网站导航中有“Sign in” / “Log in” / “Register”链接或按钮
  • 页面显示“You must be signed in to view this page”或类似消息
  • 有内容表明某些区域仅限已验证身份的用户访问
3.4 处理公网站点(无需身份验证)

如果既未发现私有站点门禁,也未发现站点级身份验证迹象:

  • 告知用户:“站点可公开访问。继续进行页面和 API 测试。”
  • 跳至第 4 阶段。
3.5 处理站点级身份验证

🚦 Gate(plan · test-site:3.5.public-vs-auth): 站点有登录界面 — 以已认证用户身份测试、跳过受身份验证限制的页面,或取消。

如果检测到站点级身份验证指示(如导航中的登录链接等),请使用 AskUserQuestion:

问题标题选项
站点有 Sign in 选项,这意味着某些页面或 API 调用可能需要身份验证。浏览器窗口应该处于打开状态 — 你可以点击“Sign in”,并使用具有适当 Web 角色的用户帐户登录。成功登录后,请在下方选择“我已登录”。站点身份验证我已登录(推荐)— 我已通过站点的登录流程登录,跳过已认证页面 — 仅测试公开可访问的页面和 API,取消测试 — 停止测试

如果选择“我已登录”:

  1. 使用 browser_snapshot 验证用户现在已登录(登录链接已替换为用户名/个人资料,或可以看到已认证内容)。

  2. 如果仍显示登录表单:

    🚦 Gate(pause · test-site:3.5.login-retry): 站点级登录尚未完成 — 再次提示或取消。

    • 再次使用 AskUserQuestion:“看起来登录尚未完成。浏览器应该仍处于打开状态 — 请完成登录后重试。”
    • 重复此步骤,直到确认登录成功或用户取消。
  3. 使用 TaskCreate 创建一项用于测试已认证场景的附加任务:

    任务主题activeForm描述
    测试已认证页面和 API正在测试已认证场景以已登录用户身份重新爬取站点,验证受身份验证限制的页面能够加载,且已认证的 API 调用能够成功

如果选择“跳过已认证页面”:

  • 说明只会测试公开页面。某些 API 调用可能返回 401/403 — 这些情况会被标记,但不会视为失败。
  • 不要创建已认证测试任务。
  • 继续执行阶段 4。

如果选择“取消测试”:

  • 停止此技能,并告知用户解决身份验证问题后可以重新运行。
输出
  • 两层身份验证状态均已确定:
    • 私有站点访问门槛:已通过、不需要或已取消
    • 站点级身份验证:已登录、已跳过或不需要
  • 如果已认证:已在阶段 5.6 创建已认证测试的附加任务

阶段 4:爬取并测试页面

目标: 发现站点上所有可导航的链接,并验证每个页面均能正确加载。

操作
4.1 从当前页面发现链接

使用 browser_evaluate 提取所有站内链接:

() => {
  const links = Array.from(document.querySelectorAll('a[href]'));
  return links
    .map(a => a.href)
    .filter(href => href.startsWith(window.location.origin))
    .filter(href => !href.includes('#') || href.split('#')[0] !== window.location.href.split('#')[0])
    .map(href => href.split('#')[0])
    .filter((href, i, arr) => arr.indexOf(href) === i);
}

向用户展示发现的链接:

“在首页发现 X 个站内链接。正在逐一测试每个页面……”

4.2 测试每个页面

按顺序处理发现的每个 URL:

  1. 导航:使用 browser_navigate 前往该 URL。
  2. 等待:使用 browser_wait_for,设置 time: 3 秒,等待页面完成渲染。
  3. 快照:使用 browser_snapshot 验证页面已渲染内容。
  4. 检查错误:在快照中查找错误指示(404、500、空白页面、错误消息)。
  5. 控制台错误:使用 browser_console_messages 并设置 level: "error",检查 JavaScript 错误。
  6. 发现新链接:使用 browser_evaluate(与 4.1 相同的脚本)查找尚未加入队列的站内链接。
  7. 记录结果:URL、状态(通过/失败)、错误数量、备注。
4.3 爬取新发现的链接
  • 将步骤 4.2.6 中发现的任何新链接加入测试队列。
  • 继续测试,直到访问所有链接或达到 25 页上限。
  • 如果达到上限,请告知用户:“已达到 25 页测试上限。另发现 Y 个链接,但未进行测试。”
4.4 记录页面测试结果

建立结果列表,跟踪以下内容:

  • 已测试的 URL
  • 加载状态(通过/失败)
  • 控制台错误数量
  • 备注(错误消息、空白页面、重定向等)
输出
  • 已爬取所有可发现的页面(最多 25 个)
  • 已记录每个页面的通过/失败状态
  • 已将爬取过程中发现的新链接添加到结果中

第 5 阶段:测试 API 请求

目标: 捕获并分析网站发出的所有 API 请求,以验证它们是否正常工作。

操作
5.1 重新访问数据驱动的页面

返回可能会发出 API 调用的页面——包含数据表、列表、表单或仪表板等动态内容的页面。优先访问在第 2.6 或第 4 阶段观察到 /_api/ 请求的页面。

对于每个数据驱动的页面:

  1. 使用 browser_navigate 前往该页面。
  2. 使用 browser_wait_for 等待 time: 5 秒,让 API 调用完成。
5.2 捕获网络请求
  • 使用 browser_network_requests,并将 includeStatic: false 设为 false,以获取所有网络请求。
  • 筛选符合以下模式的 API 请求:
    • /_api/ — Power Pages Web API / OData 端点
    • /api/ — 自定义 API 端点
    • URL 中包含 odata 或 $filter、$select、$expand 查询参数
5.3 分析 API 响应

对于捕获的每个 API 请求,评估如下:

状态码类别操作
200, 201, 204通过有效的成功响应
304警告缓存响应——可以接受,但需注明
401失败未授权——缺少或身份验证令牌已过期
403失败禁止访问——表权限或站点设置存在问题
404失败未找到——实体集名称或端点不正确
500失败服务器错误——Dataverse 内部错误或插件错误
其他 4xx/5xx失败意外错误
5.3b 检查服务器逻辑响应形状

服务器逻辑端点(/_api/serverlogics/<name>)通常会返回一个标准封装,其中的 data 字段成功时通常为字符串,但并非所有响应都保证如此——在失败或边缘情况下,data 可能为 null、缺失、非 JSON 字符串或其他非字符串值。当服务器逻辑返回序列化的 JSON(较常见)时,必须解析 data 才能显示实际载荷——而该载荷本身也可能进一步嵌套。前端代码经常解析到错误的层级、读取错误的键,或将 data 当作最终对象处理,从而导致界面静默失败。测试站点必须呈现准确观察到的形状,以便修正前端,而不是假设 data 总是可以直接解析。

对于在任何受测页面上观察到的每个 /_api/serverlogics/ 请求:

  1. 记录 URL 中的端点名称(/_api/serverlogics/<endpointName>)、HTTP 方法、查询字符串参数和 HTTP 状态码。

  2. 如果请求是 GET,使用 browser_evaluate 从同一经过身份验证的站点重新执行请求,以捕获其响应形状。 Power Pages API 请求需要站点的验证令牌;GET 请求是只读的,并不免除身份验证或 CSRF 要求。 如果浏览器中可用,优先使用站点现有的已验证请求封装。 否则,从同源的 /_layout/tokenhtml 端点获取令牌,将其保存在内存中,且绝不要将其写入日志或报告。 如果令牌端点不可用,或观察到的 URL 并非同源,则报告重放受阻,不要导入 Cookie 或发出未经身份验证的请求。 逐层解析字符串类型的载荷字段,并记录每一层的形状。 使用 JSON.stringify(observedUrl) 序列化观察到的 URL,然后将 <serialized-observed-url> 替换为该完整的序列化值,包括 引号。不要再用另一个字符串字面量包裹占位符。 使用以下形式的脚本:

    async () => {
      const url = new URL(<serialized-observed-url>, window.location.origin);
      if (url.origin !== window.location.origin) throw new Error('API replay must remain on the authenticated site.');
      const tokenResponse = await fetch(new URL('/_layout/tokenhtml', window.location.origin).href, {
        credentials: 'include',
        redirect: 'error',
        headers: { Accept: 'text/html' },
      });
      if (tokenResponse.status !== 200) throw new Error('The site verification-token endpoint is unavailable; read-only replay is blocked.');
      const tokenHtml = await tokenResponse.text();
      const valueMarker = 'value="';
      const valueStart = tokenHtml.indexOf(valueMarker);
      const valueEnd = valueStart === -1 ? -1 : tokenHtml.indexOf('"', valueStart + valueMarker.length);
      const token = valueStart === -1 || valueEnd === -1
        ? '' : tokenHtml.slice(valueStart + valueMarker.length, valueEnd);
      if (typeof token !== 'string' || !token.trim()) throw new Error('The site verification token is unavailable.');
      const res = await fetch(url.href, {
        credentials: 'include',
        redirect: 'error',
        headers: { Accept: 'application/json', '__RequestVerificationToken': token },
      });
      const status = res.status;
      const text = await res.text();
      const levels = [];
      let current;
      try { current = JSON.parse(text); } catch { return { status, rawSample: text.slice(0, 2000) }; }
      levels.push({ type: Array.isArray(current) ? 'array' : typeof current, keys: current && typeof current === 'object' && !Array.isArray(current) ? Object.keys(current) : null, firstItemKeys: Array.isArray(current) && current[0] && typeof current[0] === 'object' ? Object.keys(current[0]) : null });
      // Progressively parse common nested-string fields (data, Body, body, payload, result)
      const nestedKeys = ['data', 'Body', 'body', 'payload', 'result'];
      while (current && typeof current === 'object') {
        const next = nestedKeys.find(k => typeof current[k] === 'string');
        if (!next) break;
        let parsed;
        try { parsed = JSON.parse(current[next]); } catch { break; }
        levels.push({ parsedFrom: next, type: Array.isArray(parsed) ? 'array' : typeof parsed, keys: parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? Object.keys(parsed) : null, firstItemKeys: Array.isArray(parsed) && parsed[0] && typeof parsed[0] === 'object' ? Object.keys(parsed[0]) : null });
        current = parsed;
      }
      return { status, levels, rawSample: text.slice(0, 2000) };
    }
    
  3. 对于非 GET 的服务端逻辑请求,不要重新执行(它们可能会修改数据)。请依赖已捕获的 browser_network_requests 条目,并报告可用的响应元数据。在报告中注明未重新捕获响应正文。

  4. 为 Phase 6 报告创建一个“服务端逻辑响应形状”章节。对每个端点记录:端点名称、HTTP 方法、状态码、解析层级链(每一步解析的键、解析后的类型、该层级的键,以及当该层级为数组时首个条目的键)和原始样本(前约 2000 个字符,与上述脚本中的切片一致)。如果 envelope.success === false 且存在 envelope.error,请逐字报告该错误。

  5. 将观察到的数据形状与前端实际使用方式进行比较(检查仓库中的服务文件、钩子和组件)。如果存在不匹配——例如,UI 将 envelope.data.value 作为对象读取,但实际载荷是需要解析的字符串,或者 UI 期望的字段名未出现在观察到的键中——请指出不匹配,并建议为适配实际数据形状所需的最小前端改动。请具体说明需要进行的解析或字段访问更改,但不要在此技能中修改前端代码或创建提交;将实现留给单独的编辑/修复技能或阶段。 记录发现的问题,以及 Phase 6 报告中建议的前端修复措施。

5.4 为失败提供可执行的指导

对于每个失败的 API 请求,请提供具体的修复建议:

  • 401 Unauthorized:"此端点需要身份验证。如果你在 Phase 3 中跳过了登录,请尝试重新运行并进行身份验证。否则,请检查是否正确传递了身份验证令牌。"
  • 403 Forbidden on /_api/ calls:"请检查以下内容:\n 1. 表权限——确保为此表设置了具有正确范围和权限(读取、写入等)的表权限,并将其分配给相应的 Web 角色。\n 2. 站点设置——确认 Webapi/<tablename>/enabled 已设为 true。fields 值中,普通列使用 LogicalNames,查找列读取使用 _<LogicalName>_value,通过 @odata.bind 写入关系时使用准确的 Navigation Properties。对于聚合 OData,请包含每个分组键、聚合输入、筛选列和排序列。\n 3. Web 角色分配——确认已为经过身份验证的用户分配正确的 Web 角色。"
  • 404 Not Found:"确认实体集名称(应为表逻辑名称的复数形式)。检查该表是否存在于 Dataverse 中并已发布。"
  • 500 Internal Server Error:"启用 Webapi/error/innererror 站点设置(设为 true)以获取详细的错误消息。重新部署并重新测试,以查看内部错误详情。"
5.5 测试表单提交(可选)

🚦 **Gate (同意 · test-site:5.5.form-submit):**即将在正式站点上提交表单——这可能会创建或修改 Dataverse 记录。此操作会更改共享状态(正式 Dataverse 环境);需要明确同意。

**触发条件:**在 Phase 5 中通过 browser_snapshot 检测到表单。 **询问原因:**自动将测试数据提交到生产记录会污染真实客户数据。 **取消后:**不会有任何影响——继续进行早先 5.x 阶段的只读 API 检查。

如果在任何页面上检测到表单(browser_snapshot 显示了表单元素),请先通过 AskUserQuestion 询问用户,再进行交互:

问题标题选项
我在站点上发现了可能在提交时触发 API 调用的表单。是否要尝试与这些表单交互,以测试 POST/PATCH API 端点?注意:这可能会在你的 Dataverse 环境中创建或修改数据。表单测试是,测试表单提交——我了解这可能会创建测试数据;跳过表单测试(推荐)——仅测试只读 API 调用

如果选择“是”:

  1. 使用 browser_click 与表单提交按钮交互。
  2. 使用 browser_wait_for 等待表单响应。
  3. 使用 browser_network_requests 捕获生成的 POST/PATCH 请求。
  4. 使用与 5.3 相同的标准分析响应。

如果选择“跳过”:继续进入 Phase 5.6(如果未创建经过身份验证的测试任务,则进入 Phase 6)。

输出
  • 已发现并测试的所有 API 端点
  • 记录通过/失败状态及 HTTP 状态代码
  • 为每项失败提供可执行的修复建议

5.6 测试经过身份验证的场景(仅当用户已登录时)

如果用户在 Phase 3.5 中选择了“跳过经过身份验证的页面”,或者在 Phase 3.3 中未检测到站点级身份验证,则完全跳过此步骤。

**目标:**以经过身份验证的用户身份重新爬取站点,以发现并测试仅在登录后可用的页面和 API 调用。

将“测试经过身份验证的页面和 API”任务标记为 in_progress。

5.6.1 发现已登录页面

登录后,网站导航中可能会显示匿名用户不可见或受限的其他链接(例如个人资料页、仪表板、管理面板、账户管理页面)。

  1. 返回 SITE_URL(首页)。
  2. 使用 browser_snapshot 捕获已登录状态下的导航。
  3. 使用 browser_evaluate(与阶段 4.1 相同的链接提取脚本)发现内部链接。
  4. 与阶段 4 中已测试的链接进行比较,找出身份验证前不可见的新链接。

如果发现新链接,请告知用户:

"登录后发现 X 个匿名访问时不可见的其他页面。正在逐一测试……"

5.6.2 测试已登录页面

对于每个新发现的链接,按照阶段 4.2 中相同的测试流程进行操作:

  1. 导航、等待、获取快照、检查错误并捕获控制台错误。
  2. 将结果单独记录为已登录页面测试。
  3. 遵守相同的 25 页上限(计入阶段 4 中已测试的页面)。
5.6.3 测试已登录 API 调用

对于每个会发出 /_api/ 请求的已登录页面:

  1. 使用 browser_network_requests 并设置 includeStatic: false 来捕获 API 调用。
  2. 与阶段 5 中捕获的 API 调用进行比较,找出任何新端点,或之前返回 401/403、现在成功的端点。
  3. 使用与阶段 5.3 相同的标准分析响应。
5.6.4 记录结果

单独记录已登录测试结果,以便阶段 6 在独立部分中报告:

  • 已发现并测试的已登录页面(数量、通过/失败)
  • 已登录 API 调用(数量、通过/失败,以及登录后从失败变为成功的端点)

将“测试已登录页面和 API”任务标记为 completed。

输出
  • 已抓取并测试的已登录页面
  • 已捕获并分析的已登录 API 端点
  • 单独记录的测试报告结果

阶段 6:生成测试报告

**目标:**全面总结所有测试结果并提出后续步骤建议。

操作
6.1 记录技能使用情况

参考:${PLUGIN_ROOT}/references/skill-tracking-reference.md

按照参考文档中的技能跟踪说明,记录此技能的使用情况。使用 --skillName "TestSite"。

6.2 展示页面测试结果

以清晰的表格展示结果:

## Page Test Results

| # | URL | Status | Console Errors | Notes |
|---|-----|--------|----------------|-------|
| 1 | /                | Pass | 0 | Homepage loaded successfully |
| 2 | /about           | Pass | 0 | |
| 3 | /products        | Pass | 2 | Minor JS warnings |
| 4 | /admin           | Fail | 1 | 403 Forbidden |

Pages tested: 4/4 | Passed: 3 | Failed: 1
6.3 展示 API 测试结果
## API Test Results

| # | Endpoint | Method | Status | Notes |
|---|----------|--------|--------|-------|
| 1 | /_api/cr4fc_products       | GET | 200 OK      | 12 records returned |
| 2 | /_api/cr4fc_categories     | GET | 200 OK      | 3 records returned  |
| 3 | /_api/cr4fc_orders         | GET | 403 Forbidden | Missing table permissions |

API endpoints tested: 3 | Passed: 2 | Failed: 1

如果未捕获到 API 请求,请注明:“测试期间未检测到 API 请求(/_api/ 或 OData)。此网站可能未使用 Web API,或者 API 调用需要特定的用户交互才能触发。”

6.3b 展示服务器逻辑响应结构

如果在阶段 5.3b 中捕获到任何 /_api/serverlogics/ 请求,请添加一个专门的小节,以便前端集成能够基于实际响应编写。展示完整的解析层级链——每个不同的端点各用一个代码块:

## Server Logic Response Shapes

### /_api/serverlogics/<endpoint-name>  (GET, 200)

- Level 0 (raw body): object, keys: [requestId, success, serverLogicName, data, error]
- Level 1 (parsed from `data`): object, keys: [status, items, count]
- Level 1 `items` property: array; first item keys: [id, name, ...]
- Raw sample: `{"requestId":"...","success":true,"data":"{\"status\":\"success\",\"items\":[{\"id\":\"...\",\"name\":\"...\"}],\"count\":1}", ...}`

Frontend parsing required:
    const level1 = JSON.parse(envelope.data);
    const items  = level1.items;

如果检测到前端不匹配,请列出需要修改的文件和行号,以及所需的具体解析或字段访问修正——请勿在此技能中应用或提交更改。当开发者询问“我不知道服务器逻辑返回什么结构——前端需要怎么处理?”时,本节是主要交付内容。

6.4 展示已登录测试结果(如适用)

如果已执行阶段 5.6,请在单独的表格中展示结果:

## Authenticated Page Test Results

| # | URL | Status | Console Errors | Notes |
|---|-----|--------|----------------|-------|
| 1 | /profile         | Pass | 0 | User profile loaded |
| 2 | /dashboard       | Pass | 1 | Minor JS warning |
| 3 | /admin/settings  | Fail | 0 | 403 Forbidden — insufficient web role |

Authenticated pages tested: 3 | Passed: 2 | Failed: 1
## Authenticated API Test Results

| # | Endpoint | Method | Status | Notes |
|---|----------|--------|--------|-------|
| 1 | /_api/cr4fc_orders         | GET | 200 OK      | Previously 403 — now accessible after login |
| 2 | /_api/cr4fc_userprofiles   | GET | 200 OK      | Only visible after auth |

Authenticated API endpoints tested: 2 | Passed: 2 | Failed: 0

如果登录后未发现其他页面或 API 端点,请注明:“身份验证后未发现其他页面或 API 端点。已登录用户看到的内容与匿名访客相同。”

6.5 展示总体摘要
## Overall Test Summary

| Category                 | Tested | Passed | Failed | Warnings |
|--------------------------|--------|--------|--------|----------|
| Pages (public)           | 4      | 3      | 1      | 0        |
| Pages (authenticated)    | 3      | 2      | 1      | 0        |
| API Endpoints (public)   | 3      | 2      | 1      | 0        |
| API Endpoints (auth)     | 2      | 2      | 0      | 0        |
| Console Errors           | —      | —      | —      | 2        |

Overall: X/Y checks passed

如果跳过了已登录测试,请从表格中省略已登录相关行。

6.6 提供建议

对于每个故障,重申 Phase 5.4 中的具体修复建议。按类别分组建议:

  • 表权限问题 → /create-webroles 或手动配置表权限
  • 站点设置问题 → 检查 Webapi/<table>/enabled 和 Webapi/<table>/fields 设置
  • 身份验证问题 → /setup-auth
  • 缺少端点 → 通过 /setup-datamodel 验证 Dataverse 中是否存在该表
  • 服务器错误 → 启用 Webapi/error/innererror 站点设置以进行诊断
6.7 关闭浏览器
  • 使用 browser_close 清理浏览器会话。
6.7a 写入机器可读报告(docs/alm/last-test-site.json)

始终写入结构化 JSON 报告,以便其他技能(尤其是 plan-alm)无需重新解析 Markdown 摘要即可读取本次运行结果。每次运行都会覆盖该文件。写入前确保 docs/alm/ 目录存在——node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"。

结构:

{
  "url": "https://contoso.powerappsportals.com",
  "stageName": "Staging",
  "runAt": "2026-04-29T08:50:00.000Z",
  "durationSec": 95,
  "runOutcome": "passed | passed-with-warnings | failed",
  "summary": {
    "critical": 0,
    "high": 1,
    "medium": 0,
    "low": 2,
    "total": 3,
    "automated": 2,
    "manual": 1,
    "passed": 2,
    "failed": 1,
    "skipped": 0
  },
  "categories": [
    {
      "id": "site-load",
      "name": "Site Load",
      "icon": "📦",
      "tests": [
        {
          "id": "t01",
          "name": "Homepage returns 200 OK",
          "severity": "critical | high | medium | low",
          "type": "automated | manual",
          "status": "passed | failed | skipped",
          "description": "Short why-this-matters sentence.",
          "steps": ["GET /", "Expect 200"],
          "expected": "200 OK",
          "actual": "200 OK",
          "validates": "Site activation"
        }
      ]
    }
  ]
}

类别映射 — 为产生了发现结果的每个 test-site 阶段输出一个类别。使用以下稳定的 id 值,以便使用方能够识别:

idname来源阶段
site-load站点加载Phase 2(主页 HTTP、重定向、渲染)
authentication身份验证Phase 3(登录重定向、匿名访问拦截)
page-crawl页面爬取Phase 4(每个受测页面对应一张卡片)
web-apiWeb APIPhase 5(每个受测端点对应一张卡片)
auth-pages已验证身份的页面Phase 5.6 页面测试
auth-api已验证身份的 APIPhase 5.6 API 测试
console控制台健康状况汇总所有页面的控制台错误

不要添加包含组件数量、版本号或其他先前部署运行特定数据的顶层 notes 字符串。实际故障案例:last-test-site.json 的 notes 字段硬编码了两次部署之前的 "4,037 components",随后通过 plan-alm 显示出来,尽管最新部署已发布 4,051 个组件。如果标记需要自由格式的说明,只能根据本次运行的数据撰写——切勿沿用之前运行的数字。结构化的 summary + categories[] 才是审计记录;文字摘要应放在渲染后的 HTML 中,而不是 JSON 标记中。

严重性规则(按每张测试卡片应用):

  • HTTP 5xx 响应 → critical
  • 公共页面或有文档说明的公共 API 返回 HTTP 4xx 响应 → high
  • 匿名访问仅限已验证身份的资源时返回 HTTP 4xx 响应(预期的访问拦截)→ low
  • 页面其他测试均通过,但存在控制台错误 → medium
  • 所有其他发现(仅供参考)→ low

计算 summary 对象——请仔细阅读。 必须通过统计所有 categories[].tests[] 中每项测试的 severity 字段来汇总摘要各档的数量,而不能根据类别推断,也不能默认设为 low。plan-alm 的 Validation 选项卡和渲染后 HTML 中的严重性网格都直接依赖摘要——如果所有测试无论实际严重性如何都归入 low,那么即使存在严重故障,所有阶段看起来也会是正常的。实际复现案例(Citizens 门户,2026-05-21):四项测试的严重性为 critical / high / high / low,由于 Agent 跳过了逐项统计,结果被写入 summary: {critical:0, high:0, medium:0, low:4}。

按如下方式计算:

const summary = { critical: 0, high: 0, medium: 0, low: 0, total: 0, automated: 0, manual: 0, passed: 0, failed: 0, skipped: 0 };
for (const cat of (report.categories || [])) {
  for (const test of (cat.tests || [])) {
    summary.total += 1;
    const sev = String(test.severity || '').toLowerCase();
    if (sev === 'critical') summary.critical += 1;
    else if (sev === 'high') summary.high += 1;
    else if (sev === 'medium') summary.medium += 1;
    else summary.low += 1;            // default for unknown / missing severity
    if (test.type === 'manual') summary.manual += 1; else summary.automated += 1;
    if (test.status === 'failed') summary.failed += 1;
    else if (test.status === 'skipped') summary.skipped += 1;
    else summary.passed += 1;
  }
}

plan-alm 渲染器(render-alm-plan.js 中的 buildValidationStagePane)会分别绘制四张严重性卡片——Critical / High / Medium / Low。每个摘要档位都必须反映该严重性的实际数量,不能使用合并后的“Medium / Low”总数。 状态规则:

  • passed — 断言成立(200、无控制台错误、符合预期的重定向等)
  • failed — 断言不成立(5xx、预期为 200 却返回 4xx、登录流程中断等)
  • skipped — 阶段或测试被主动跳过(例如,用户在 3.5 中选择“跳过需认证的页面”)

summary 根据 categories 计算:

  • critical/high/medium/low — 各严重级别的测试数量,无论状态如何(这样即使所有测试都通过,审阅者也能看到测试范围)。
  • total — 测试卡片总数。
  • automated/manual — 按 type 统计。
  • passed/failed/skipped — 按 status 统计。

runOutcome 是汇总后的判定结果:

  • 如果任何测试的 status: "failed" 且 severity: "critical" 或 "high",则为 failed。
  • 如果没有 critical/high 级别的失败,但 summary.failed > 0 或记录了控制台错误,则为 passed-with-warnings。
  • 其他情况为 passed。

写入文件(Node.js,从项目根目录运行):

node -e "require('fs').mkdirSync('docs/alm',{recursive:true})"
node -e "require('fs').writeFileSync('docs/alm/last-test-site.json', process.argv[1])" "$(cat <<'EOF'
{...the JSON above...}
EOF
)"

或者,由 plan-alm 调用时,编排器可以直接提供 JSON。无论哪种方式,标记文件的位置都是固定的:docs/alm/last-test-site.json(与 docs/alm/last-deploy.json 和 docs/alm/last-pipeline.json 位于同一目录)。

已知时,始终在标记中包含 stageName。 Agent 从上游上下文中获取阶段标签——docs/alm/last-deploy.json 的 stageName、计划的 stages[],或用户明确提及的内容。如果无法推断阶段(例如,独立运行 test-site 来测试任意 URL),则将 stageName 设为 null;刷新辅助工具有备用解析路径,但显式填写该字段是最可靠的信号。

6.7b 刷新 ALM 计划(如果存在)
node "${PLUGIN_ROOT}/scripts/lib/refresh-alm-plan-data.js" \
  --projectRoot "." \
  --phase test-site \
  --stageName "{stageName}" \
  --render

{stageName} 是本次运行所测试的阶段标签(例如,Staging、Production)。未知时传入空字符串——refreshTestSite 会依次回退到 (1) 标记中的 stageName 字段(在上面的 6.7a 中设置),然后是 (2) planData.stages 中唯一的目标阶段。对于没有显式 stageName、且标记中也没有 stageName 的多阶段计划,不会记录验证结果(刷新会重新渲染,但不会更新各阶段的 validationRun);尽可能始终显式传入该值。

辅助工具会读取 docs/alm/last-test-site.json,将分类后的测试结果填入 planData.validationRuns[{resolvedStage}],并重新渲染 docs/alm-plan.html,以便验证选项卡立即更新。当 docs/.alm-plan-data.json 不存在时(独立调用,项目中没有计划),辅助工具会返回 ok:false,作为软性无操作——可以放心地无条件运行。

向用户指出下一步(由用户驱动执行顺序)。 辅助工具的 stdout JSON 包含 nextStep: { name, skill: string | null } | null。当其值非 null 时,根据 skill 分支处理:当 skill 非 null 时,告诉用户 "计划已更新。计划中的下一步:{nextStep.name} → 准备好后运行 {nextStep.skill}。";当 skill 为 null(例如 Finalize 这样的内部步骤,没有用户命令)时,只显示步骤名称——"计划已更新。计划中的下一步:{nextStep.name}。"——并且绝不要输出 run null。通常包括:部署、激活或测试下一阶段。当值为 null(这是最后一步)或辅助工具返回 ok:false 时,不要提及下一步。绝不要自动调用下一项技能——由用户决定是否继续执行。

6.8 建议后续步骤

根据测试结果,建议相关技能:

  • 如果发现 API 故障:/integrate-webapi — 修复 Web API 站点设置和表权限
  • 如果存在身份验证问题:/setup-auth — 配置身份验证提供程序
  • 如果页面出现错误:检查站点代码,并使用 /deploy-site 重新部署
  • 如果所有测试都通过:站点运行正常!可考虑使用 /add-seo 进行搜索引擎优化
输出
  • 提供包含页面和 API 通过/失败结果的综合测试报告
  • 针对每项失败提供可执行的建议
  • 关闭浏览器会话
  • 提出后续步骤建议

重要说明

所有阶段须知
  • 使用 TaskCreate/TaskUpdate 在每个阶段跟踪进度
  • 此技能为只读——不会修改任何文件或数据
  • 切勿代替用户登录——务必让用户通过浏览器窗口登录
  • 清楚说明错误——页面或 API 出错时,提供具体 URL 和错误详情
关键决策点
  1. 阶段 1.3:如果网站尚未激活,请停止并转到 /activate-site
  2. 阶段 1.4:如果无法自动检测到 URL,必须询问用户
  3. 阶段 3.2:如果网站为私有网站(会重定向到身份提供方),必须要求用户登录——不得绕过
  4. 阶段 3.5:如果网站支持网站级身份验证,必须询问用户是要登录还是跳过——不得自动登录
  5. 阶段 4.3:爬取页面数达到 25 页时停止,以防止无限循环
  6. 阶段 5.5:与表单交互前(可能会创建/修改数据),必须获得用户明确许可
进度跟踪

开始阶段 1 前,使用 TaskCreate 创建包含所有阶段的任务列表:

任务主题activeForm描述
确定网站 URL正在确定网站 URL从用户输入、激活状态检查或上下文中获取 URL
启动浏览器并验证初始加载正在浏览器中加载网站导航至网站,验证首页是否加载,并记录基线错误
检查身份验证要求正在检查身份验证检测网站是否需要身份验证,并在需要时处理登录
爬取并测试所有页面正在爬取网站页面发现链接、逐页导航、验证页面加载情况并检查控制台错误
测试 API 请求正在测试 API 端点捕获网络请求、验证 API 响应并分析错误
生成测试报告正在生成测试报告汇总所有已测试的页面和 API,并提出后续建议

每项任务开始时,通过 TaskUpdate 将状态标记为 in_progress;完成时标记为 completed。


从阶段 1 开始:确定网站 URL

评论

支持 Markdown