docs(ops): occupy integration slot for governance authz

G5 starts merging and smoke on the shared dev stack; Playwright matrix
is ready for the real page pass after rebuild.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-14 08:04:34 +00:00
co-authored by Cursor
parent c048060bae
commit 5ee898e2e4
2 changed files with 178 additions and 1 deletions
+1 -1
View File
@@ -57,7 +57,7 @@
| holder | phase | since | scope | queue |
|--------|-------|-------|-------|-------|
| idle | idle | - | - | - |
| implementer_cursor | merging | 2026-09-14 | governance-action-level-authz | - |
**phase**: `idle` | `merging` | `smoke` | `e2e`
@@ -0,0 +1,177 @@
import { test, expect, type Page } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
import { currentProjectBannerName } from '../src/lib/currentProjectRestore';
import { GOVERNANCE_BLOCKED_HINT } from '../src/lib/blockedGovernanceCopy';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const runAcceptance = process.env.RUN_UI_ACCEPTANCE === '1';
const ASSETS = process.env.UI_ACCEPTANCE_ASSETS_DIR
? path.resolve(process.env.UI_ACCEPTANCE_ASSETS_DIR)
: path.resolve(
__dirname,
'../../.doc/test-reports/assets/2026-09-14-governance-action-level-authz',
);
const SCREENSHOTS = path.join(ASSETS, 'screenshots');
fs.mkdirSync(SCREENSHOTS, { recursive: true });
const ADMIN_USER = process.env.ADMIN_USERNAME?.trim() || '';
const ADMIN_PASS = process.env.ADMIN_PASSWORD?.trim() || '';
const suffix = `${Date.now().toString(36)}`;
const USER_B = `gab_${suffix}`;
const USER_C = `gac_${suffix}`;
const INIT_PASS = `Init-${suffix}9`;
const PROJECT = `GOVAUTH-${suffix}`;
test.skip(!runAcceptance, 'set RUN_UI_ACCEPTANCE=1 against the real dev stack');
async function shot(page: Page, name: string) {
await page.screenshot({ path: path.join(SCREENSHOTS, name), fullPage: true });
}
async function login(page: Page, username: string, password: string) {
await page.goto('/');
await page.fill('input[placeholder*="系统用户名"]', username);
await page.fill('input[placeholder*="登录密码"]', password);
await page.click('button:has-text("进入医疗协同工作台")');
await expect(page.getByText('新建立项会话')).toBeVisible({ timeout: 30_000 });
}
async function goToDutyStep(page: Page) {
const approval = page.getByText('纳入人工审批').first();
const selectHint = page.getByText('选择要激活的已发布工作流版本');
await expect(selectHint.or(approval)).toBeVisible({ timeout: 20_000 });
if (await approval.isVisible().catch(() => false)) {
return;
}
await page.getByRole('button', { name: /下一步/ }).click();
await expect(approval).toBeVisible({ timeout: 20_000 });
}
function dutyRoleSelect(page: Page, placeholder: '作者' | '审核' | '批准') {
return page.locator('select').filter({ has: page.getByRole('option', { name: placeholder, exact: true }) }).first();
}
test.describe.serial('governance action-level authz', () => {
test('real page matrix G5', async ({ browser }) => {
if (!ADMIN_USER || !ADMIN_PASS) {
throw new Error('ADMIN_USERNAME and ADMIN_PASSWORD are required');
}
const page = await browser.newPage();
const runCreates: { status: number; body: string }[] = [];
page.on('response', async (res) => {
if (res.url().includes('/api/v1/tasks/runs') && res.request().method() === 'POST') {
runCreates.push({ status: res.status(), body: await res.text().catch(() => '') });
}
});
await login(page, ADMIN_USER, ADMIN_PASS);
await page.locator('button[title*="系统管理与准入治理"]').click();
for (const [user, name] of [
[USER_B, `审核协作者-${suffix}`],
[USER_C, `批准协作者-${suffix}`],
] as const) {
await page.getByRole('button', { name: '开通受控员工账号' }).click();
const form = page.locator('form').filter({ hasText: '用户名 (系统账号)' });
await form.locator('input[placeholder="如 li_si"]').fill(user);
await form.locator('input[placeholder="如 李四"]').fill(name);
const passBox = form.locator('xpath=.//label[contains(.,"临时初始密码")]/following::input[1]');
await passBox.fill(INIT_PASS);
await form.locator('select').selectOption('role-engineer');
await form.getByRole('button', { name: '确认开通账号' }).click();
await expect(form).toBeHidden({ timeout: 15_000 });
}
const adminHeading = page.getByText('系统管理控制台', { exact: true });
const adminModal = adminHeading.locator('xpath=ancestor::div[contains(@style,"position: fixed")][1]');
await adminModal.locator('button').first().click();
await expect(adminHeading).toBeHidden({ timeout: 10_000 });
await page.getByRole('button', { name: '新建立项会话' }).click();
const heading = page.getByText('发起医疗器械新项目立项', { exact: true });
await expect(heading).toBeVisible({ timeout: 20_000 });
const modal = heading.locator('xpath=ancestor::div[contains(@style,"position: fixed")][1]');
await modal.locator('input[placeholder*="无线便携式"]').fill(PROJECT);
await modal.locator('textarea').fill('治理鉴权 G5:零治理创建后先对话再配置。');
for (const name of [`审核协作者-${suffix}`, `批准协作者-${suffix}`]) {
const memberName = modal.getByText(name, { exact: true });
await expect(memberName).toBeVisible({ timeout: 15_000 });
await memberName.locator('xpath=ancestor::div[contains(@style,"cursor: pointer")][1]').click();
}
await modal.getByRole('button', { name: /确认立项/ }).click();
await expect(currentProjectBannerName(page, PROJECT)).toBeVisible({ timeout: 30_000 });
await shot(page, '01-zero-gov-created.png');
const input = page.getByPlaceholder(/向项目助手提问/);
await input.fill('今天天气如何,先闲聊一句。');
await page.getByRole('button', { name: /发送/ }).click();
await expect(page.locator('[data-chat-role="user"]').filter({ hasText: '今天天气如何' })).toBeVisible({
timeout: 20_000,
});
await expect(page.locator('[data-chat-role="assistant"]').last()).toBeVisible({ timeout: 45_000 });
await shot(page, '02-casual-chat.png');
await input.fill('帮我生成立项文档');
await page.getByRole('button', { name: /发送/ }).click();
await expect(page.getByText('帮我生成立项文档')).toBeVisible({ timeout: 20_000 });
await expect(page.getByText(GOVERNANCE_BLOCKED_HINT)).toBeVisible({ timeout: 45_000 });
await expect(page.getByText('待激活工作流')).toBeVisible({ timeout: 15_000 });
const blocked = runCreates.find((row) => row.body.includes('blocked_pending_governance'));
if (!blocked || blocked.status !== 202) {
throw new Error(`expected 202 blocked run, got ${JSON.stringify(runCreates)}`);
}
await shot(page, '03-blocked-generate.png');
await page.getByRole('button', { name: '配置工作流' }).click();
await goToDutyStep(page);
const toggles = page.locator('label', { hasText: '纳入人工审批' });
const toggleCount = await toggles.count();
for (let i = 1; i < toggleCount; i += 1) {
const box = toggles.nth(i).locator('input[type="checkbox"]');
if (await box.isChecked()) await box.uncheck();
}
const firstToggle = toggles.first().locator('input[type="checkbox"]');
if (!(await firstToggle.isChecked())) await firstToggle.check();
const author = dutyRoleSelect(page, '作者');
await expect(author).toBeVisible({ timeout: 10_000 });
const values = (
await author.locator('option').evaluateAll((nodes) => nodes.map((n) => (n as HTMLOptionElement).value))
).filter(Boolean);
if (values.length < 3) {
throw new Error(`need 3 members for full duties, got ${values.length}`);
}
await author.selectOption(values[0]);
await dutyRoleSelect(page, '审核').selectOption(values[1]);
await dutyRoleSelect(page, '批准').selectOption(values[2]);
await page.getByRole('button', { name: /校验并预览/ }).click();
await expect(page.getByText('激活预览')).toBeVisible({ timeout: 20_000 });
await expect(page.getByText('不纳入人工审批的文档激活后不可补配三职')).toBeVisible();
await shot(page, '04-activation-confirm.png');
await page.getByRole('button', { name: '确认激活' }).click();
await expect(page.getByText('运行中')).toBeVisible({ timeout: 20_000 });
await shot(page, '05-activated-full-none.png');
await page.getByRole('button', { name: /工作流|文档|交付/ }).first().click().catch(() => undefined);
const unmanagedHint = page.getByText('该文档未纳入人工审批,不提供送审、审核或批准');
const submitBtn = page.getByRole('button', { name: /提交送审/ });
// Creator is author of the first governed doc, so submit may exist there; none docs must hide it.
const workflowDocs = page.locator('[data-doc-key], button, [title]').filter({ hasText: /研究|法规|竞品|可行性/ });
if ((await workflowDocs.count()) > 0) {
await workflowDocs.first().click();
}
if (await unmanagedHint.isVisible().catch(() => false)) {
await expect(submitBtn).toHaveCount(0);
}
await shot(page, '06-duty-gated-actions.png');
await page.getByRole('complementary').getByTitle('对话协同会话', { exact: true }).click();
await expect(page.getByRole('banner')).toContainText('对话协同会话', { timeout: 15_000 });
await page.getByRole('button', { name: '查看工作流设置' }).click();
await expect(page.getByText('当前工作流已冻结,只读查看')).toBeVisible({ timeout: 15_000 });
await expect(page.getByRole('button', { name: '确认终止当前实例' })).toBeVisible();
await shot(page, '07-project-143-readonly.png');
await page.getByRole('button', { name: '关闭' }).click();
await page.close();
});
});