| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495 |
- /**
- * 结构化用例脚本化规格(自动生成,请勿手改)
- * ================================================================
- * 来源:tests/test-cases/*.md 共 784 条 `| TC-` 表格用例
- * 生成器:tests/scripts/scriptize_tc.py
- * 数据:./cases.json(唯一数据源,含来源文件/行号/分级/原因)
- *
- * 分级:
- * active-api 步骤/预期可提取明确 /api 路径 + 可判定状态码 → 真实 apiPost + code 集合断言
- * active-probe 有 /api 路径但预期无状态码 → 真实 apiPost + 「不应 5xx」弱契约探针
- * auth-probe 权限流派生用例,路由来自源码/文档证据 → 不带 token 访问,断言 401/403
- * linked-e2e 文档已关联既有 spec → 仅作文档锚点,执行请跑原 spec
- * 其余 design-* 运行时 test.fixme(true, reason) 阻断,不打远程、不伪造结果
- *
- * 路由证据:cases.json 的 route_level / route_evidence 字段记录每条路径的来源
- * inline(用例原文)> inherit(派生继承)> controller(源码 Controller 名)
- * > doc-fuzzy(接口文档章节,模块级关联需复核)> linked-spec(既有规格实际调用)
- *
- * 授权边界(重要):
- * - 默认 TC_LIVE != 1:全部用例为 fixme,零网络副作用,可安全 --list / 空跑。
- * - 仅显式 `TC_LIVE=1` 且已确认目标环境时,active-api 才真实请求后端。
- * - 目标环境取自 playwright 配置 baseURL;执行前须由测试负责人确认可写/可压。
- *
- * 运行:
- * npx playwright test e2e/api/generated/tc-scripted.spec.js -c playwright.config.api.js --list
- * TC_LIVE=1 npx playwright test e2e/api/generated/tc-scripted.spec.js -c playwright.config.api.js --project=desktop
- */
- const { test, expect } = require('@playwright/test');
- const { registerRole, apiPost } = require('../../helpers/api');
- const CASES = require('./cases.json');
- const LIVE = process.env.TC_LIVE === '1';
- const _tokenCache = {};
- /** 惰性注册角色会话;游客返回 null(用于未授权探针) */
- async function tokenFor(request, roleKey) {
- if (roleKey === 'guest') return null;
- if (_tokenCache[roleKey]) return _tokenCache[roleKey];
- const ctx = await registerRole(request, roleKey, 'QA-SCRIPT');
- _tokenCache[roleKey] = ctx;
- return ctx;
- }
- const groups = {};
- for (const tc of CASES) {
- (groups[tc.role || '未标注'] = groups[tc.role || '未标注'] || []).push(tc);
- }
- for (const [role, list] of Object.entries(groups)) {
- test.describe('脚本化用例 · ' + role + ' (' + list.length + ')', () => {
- for (const tc of list) {
- test(tc.title, async ({ request }, testInfo) => {
- // 把文档用例的结构化元数据注入测试报告,使设计态用例也具备可追溯性
- testInfo.annotations.push(
- { type: 'TC', description: tc.id + ' @ ' + tc.source_file + ':' + tc.source_line },
- { type: 'section', description: tc.section },
- { type: 'priority', description: tc.priority },
- { type: 'type', description: tc.type },
- { type: 'tier', description: tc.tier },
- { type: 'precondition', description: tc.precondition },
- { type: 'steps', description: tc.steps },
- { type: 'expected', description: tc.expected }
- );
- const executable = tc.tier === 'active-api' || tc.tier === 'active-probe'
- || tc.tier === 'auth-probe';
- const blocked = !executable
- ? (tc.reason || '设计态:缺少可自动化条件')
- : (LIVE ? null : '未授权联网:设置 TC_LIVE=1 且确认目标环境后执行');
- if (blocked) {
- test.fixme(true, blocked);
- return;
- }
- // auth-probe 刻意不带 token:验证「未授权访问受保护接口」;其余按角色惰性注册
- const ctx = tc.tier === 'auth-probe' ? null : await tokenFor(request, tc.role_key);
- const r = await apiPost(request, tc.api.path, tc.api.body || {}, ctx ? ctx.token : null);
- if (tc.tier === 'active-probe') {
- // 弱契约探针:预期未写状态码,只守住「不应 5xx / 非 JSON」这条底线
- expect(
- Number(r.code) < 500,
- tc.id + ' 弱契约探针:不应 5xx/非 JSON,实际 code=' + r.code +
- ' msg=' + r.message + '(' + tc.source_file + ':' + tc.source_line + ')'
- ).toBeTruthy();
- } else {
- const codes = tc.api.codes.map(Number);
- expect(
- codes.includes(Number(r.code)),
- tc.id + ' [' + tc.tier + '] 期望 code ∈ [' + codes.join(',') + '],实际 code=' + r.code +
- ' msg=' + r.message + '(' + tc.source_file + ':' + tc.source_line + ')'
- ).toBeTruthy();
- }
- });
- }
- });
- }
|