chore: release v1.40.0

This commit is contained in:
coso
2026-05-16 02:05:26 +08:00
parent 446c7b0c1f
commit 403e1f449b
338 changed files with 43095 additions and 37870 deletions
+441
View File
@@ -0,0 +1,441 @@
#!/usr/bin/env node
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { chromium } from "playwright";
const DEFAULTS = {
appUrl: "http://127.0.0.1:1420/",
healthUrl: "http://127.0.0.1:3030/health",
timeoutMs: 120_000,
intervalMs: 1_000,
evidenceDir: path.join(process.cwd(), ".lime", "qc", "gui-evidence", "agent-app-lab"),
prefix: "agent-app-lab-smoke",
};
function parseArgs(argv) {
const options = { ...DEFAULTS };
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--app-url" && argv[index + 1]) {
options.appUrl = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--health-url" && argv[index + 1]) {
options.healthUrl = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--timeout-ms" && argv[index + 1]) {
options.timeoutMs = Number(argv[index + 1]);
index += 1;
continue;
}
if (arg === "--interval-ms" && argv[index + 1]) {
options.intervalMs = Number(argv[index + 1]);
index += 1;
continue;
}
if (arg === "--evidence-dir" && argv[index + 1]) {
options.evidenceDir = path.resolve(String(argv[index + 1]).trim());
index += 1;
continue;
}
if (arg === "--prefix" && argv[index + 1]) {
options.prefix = String(argv[index + 1]).trim();
index += 1;
}
}
return options;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function logStage(stage) {
console.log(`[smoke:agent-app-lab] stage=${stage}`);
}
async function launchSmokeContext(userDataDir) {
try {
return await chromium.launchPersistentContext(userDataDir, {
channel: "chrome",
headless: true,
});
} catch (error) {
console.warn(
`[smoke:agent-app-lab] Chrome channel 启动失败,尝试 Playwright Chromium: ${
error instanceof Error ? error.message : String(error)
}`,
);
return chromium.launchPersistentContext(userDataDir, {
headless: true,
});
}
}
async function waitForHealth(options) {
const startedAt = Date.now();
let lastError = null;
while (Date.now() - startedAt < options.timeoutMs) {
try {
const response = await fetch(options.healthUrl);
const payload = await response.json();
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
console.log(
`[smoke:agent-app-lab] DevBridge 已就绪 (${Date.now() - startedAt}ms) status=${payload?.status ?? "unknown"}`,
);
return payload;
} catch (error) {
lastError = error;
await sleep(options.intervalMs);
}
}
throw new Error(
`[smoke:agent-app-lab] DevBridge 未就绪: ${
lastError instanceof Error ? lastError.message : String(lastError)
}`,
);
}
async function runFlagOffRegression(options) {
logStage("flag-off-regression");
const userDataDir = fs.mkdtempSync(
path.join(os.tmpdir(), "lime-agent-app-lab-flag-off-"),
);
const context = await launchSmokeContext(userDataDir);
const consoleErrors = [];
const failedRequests = [];
await context.addInitScript(() => {
window.localStorage.removeItem("lime.agentAppHost.flags");
window.localStorage.removeItem("lime.agentAppHost.labEnabled");
});
const page = await context.newPage();
page.on("console", (message) => {
if (message.type() === "error") {
consoleErrors.push(message.text());
}
});
page.on("requestfailed", (request) => {
failedRequests.push({
url: request.url(),
method: request.method(),
failure: request.failure()?.errorText ?? "unknown",
});
});
try {
await page.goto(options.appUrl, {
waitUntil: "domcontentloaded",
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="app-sidebar-main-nav"]', {
timeout: options.timeoutMs,
});
const assertions = {
labNavHidden: (await page.locator('button[title="Agent App Lab"]').count()) === 0,
labPageHidden: (await page.locator('[data-testid="agent-app-lab-page"]').count()) === 0,
agentAppsNavVisible: (await page.locator('button[title="Agent Apps"]').count()) > 0,
noConsoleErrors: consoleErrors.length === 0,
};
Object.entries(assertions).forEach(([key, value]) => {
if (!value && key === "noConsoleErrors") {
console.error(
`[smoke:agent-app-lab] flagOffConsoleErrors=${JSON.stringify(consoleErrors, null, 2)}`,
);
}
assert(Boolean(value), `Flag-off assertion failed: ${key}`);
});
const screenshotPath = path.join(options.evidenceDir, `${options.prefix}-flag-off.png`);
await page.screenshot({ path: screenshotPath, fullPage: true });
return {
assertions,
consoleErrors,
failedRequests,
screenshot: screenshotPath,
};
} finally {
await context.close();
fs.rmSync(userDataDir, { recursive: true, force: true });
}
}
async function main() {
const options = parseArgs(process.argv.slice(2));
fs.mkdirSync(options.evidenceDir, { recursive: true });
await waitForHealth(options);
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "lime-agent-app-lab-smoke-"));
let context;
context = await launchSmokeContext(userDataDir);
const consoleErrors = [];
const failedRequests = [];
await context.addInitScript(() => {
window.localStorage.setItem(
"lime.agentAppHost.flags",
JSON.stringify({
labEnabled: true,
realAdapterEnabled: true,
uiRuntimeEnabled: true,
}),
);
});
const page = await context.newPage();
page.on("console", (message) => {
if (message.type() === "error") {
consoleErrors.push(message.text());
}
});
page.on("requestfailed", (request) => {
failedRequests.push({
url: request.url(),
method: request.method(),
failure: request.failure()?.errorText ?? "unknown",
});
});
try {
logStage("open-app");
await page.goto(options.appUrl, { waitUntil: "domcontentloaded", timeout: options.timeoutMs });
await page.waitForSelector('button[title="Agent App Lab"]', {
timeout: options.timeoutMs,
});
logStage("open-agent-app-lab");
await page.click('button[title="Agent App Lab"]');
await page.waitForSelector('[data-testid="agent-app-lab-page"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-install-flow"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-manager"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-manager-repository"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-manager-repository-list"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector(
'[data-testid="agent-app-manager-repository-app-content-factory-playbook-app"]',
{
timeout: options.timeoutMs,
},
);
consoleErrors.length = 0;
failedRequests.length = 0;
logStage("resolve-lab-setup");
await page.click('[data-testid="agent-app-lab-resolve-setup"]');
await page.waitForFunction(
() => document.body.textContent?.includes("Guard 已允许") ||
document.body.textContent?.includes("Guard allows launch") ||
document.body.textContent?.includes("agentApp.lab.installFlow.launchReady"),
undefined,
{ timeout: options.timeoutMs },
);
logStage("select-companion-app");
await page.click(
'[data-testid="agent-app-manager-repository-app-content-factory-playbook-app"]',
);
await page.waitForFunction(
() =>
document
.querySelector('[data-testid="agent-app-manager-selected-app"]')
?.textContent?.includes("fixture:content-factory-playbook-app"),
undefined,
{ timeout: options.timeoutMs },
);
logStage("verify-disable-blocker");
await page.click('[data-testid="agent-app-manager-disable"]');
await page.waitForFunction(
() =>
document
.querySelector('[data-testid="agent-app-manager-launch-entry-dashboard"]')
?.hasAttribute("disabled") &&
!document
.querySelector('[data-testid="agent-app-manager-enable"]')
?.hasAttribute("disabled"),
undefined,
{ timeout: options.timeoutMs },
);
const managerDisableBlocked = await page.isDisabled(
'[data-testid="agent-app-manager-launch-entry-dashboard"]',
);
const managerEnableAvailable = !(await page.isDisabled(
'[data-testid="agent-app-manager-enable"]',
));
await page.click('[data-testid="agent-app-manager-enable"]');
await page.waitForFunction(
() =>
!document
.querySelector('[data-testid="agent-app-manager-launch-entry-dashboard"]')
?.hasAttribute("disabled"),
undefined,
{ timeout: options.timeoutMs },
);
const managerReenabled = !(await page.isDisabled(
'[data-testid="agent-app-manager-launch-entry-dashboard"]',
));
logStage("mount-dashboard");
await page.click('[data-testid="agent-app-manager-launch-entry-dashboard"]');
await page.waitForSelector('[data-testid="agent-app-ui-runtime-result"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-entry-runtime-guard-allow"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-manager-cleanup-evidence"]', {
timeout: options.timeoutMs,
});
logStage("preview-delete-data-cleanup-evidence");
await page.click('[data-testid="agent-app-manager-uninstall-delete-data"]');
await page.waitForSelector('[data-testid="agent-app-manager-evidence-json"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-manager-residual-audit"]', {
timeout: options.timeoutMs,
});
const cleanupEvidenceText = await page.textContent(
'[data-testid="agent-app-manager-evidence-json"]',
);
const cleanupEvidence = JSON.parse(cleanupEvidenceText ?? "{}");
const residualPendingText = await page.textContent(
'[data-testid="agent-app-manager-residual-pending"]',
);
const pageText = await page.textContent("body");
const uiRuntimeText = await page.textContent('[data-testid="agent-app-ui-runtime-result"]');
const assertions = {
labVisible: Boolean(await page.$('[data-testid="agent-app-lab-page"]')),
installFlowVisible: Boolean(await page.$('[data-testid="agent-app-install-flow"]')),
managerVisible: Boolean(await page.$('[data-testid="agent-app-manager"]')),
managerRepository: Boolean(await page.$('[data-testid="agent-app-manager-repository"]')),
managerRepositoryList: Boolean(
await page.$('[data-testid="agent-app-manager-repository-list"]'),
),
managerMultiApp: Boolean(
await page.$('[data-testid="agent-app-manager-repository-app-content-factory-playbook-app"]'),
),
managerSelectedApp: Boolean(
pageText?.includes("fixture:content-factory-playbook-app"),
),
managerDisableBlocked,
managerEnableAvailable,
managerReenabled,
selectedRuntimeApp: Boolean(
uiRuntimeText?.includes("content-factory-playbook-app"),
),
setupResolved: pageText?.includes("Lab 示例 setup 已解决") ||
pageText?.includes("Lab sample setup") ||
pageText?.includes("agentApp.lab.installFlow.setupResolved"),
guardAllowed: Boolean(await page.$('[data-testid="agent-app-entry-runtime-guard-allow"]')),
uiMounted: Boolean(await page.$('[data-testid="agent-app-ui-runtime-result"]')),
managerEvidence: Boolean(await page.$('[data-testid="agent-app-manager-cleanup-evidence"]')),
cleanupEvidenceJson: Boolean(
await page.$('[data-testid="agent-app-manager-evidence-json"]'),
),
residualAuditVisible: Boolean(
await page.$('[data-testid="agent-app-manager-residual-audit"]'),
),
residualAuditPending: Boolean(
residualPendingText?.includes("10") ||
residualPendingText?.includes(
"agentApp.lab.manager.evidence.residual.pendingDeletion",
),
),
cleanupEvidenceSelectedApp: cleanupEvidence.appId === "content-factory-playbook-app",
cleanupEvidenceStrategy: cleanupEvidence.strategy === "delete-data",
cleanupEvidenceBlockedCount: cleanupEvidence.blockedTargetCount === 0,
launchedStatus: pageText?.includes("已在 Lab 启动") ||
pageText?.includes("Launched in Lab") ||
pageText?.includes("agentApp.lab.installFlow.status.launched"),
cleanupPreview: Boolean(
await page.$('[data-testid="agent-app-install-flow-stage-cleanup-preview"]'),
),
noConsoleErrors: consoleErrors.length === 0,
};
Object.entries(assertions).forEach(([key, value]) => {
if (!value && key === "noConsoleErrors") {
console.error(
`[smoke:agent-app-lab] consoleErrors=${JSON.stringify(consoleErrors, null, 2)}`,
);
}
assert(Boolean(value), `Assertion failed: ${key}`);
});
const flagOff = await runFlagOffRegression(options);
const flagOffAssertions = {
flagOffLabNavHidden: flagOff.assertions.labNavHidden,
flagOffLabPageHidden: flagOff.assertions.labPageHidden,
flagOffAgentAppsNavVisible: flagOff.assertions.agentAppsNavVisible,
flagOffNoConsoleErrors: flagOff.assertions.noConsoleErrors,
};
Object.entries(flagOffAssertions).forEach(([key, value]) => {
assert(Boolean(value), `Assertion failed: ${key}`);
});
const screenshotPath = path.join(options.evidenceDir, `${options.prefix}.png`);
const summaryPath = path.join(options.evidenceDir, `${options.prefix}-summary.json`);
await page.screenshot({ path: screenshotPath, fullPage: true });
fs.writeFileSync(
summaryPath,
`${JSON.stringify(
{
scenarioId: "agent-app-lab-smoke",
appUrl: options.appUrl,
assertions: {
...assertions,
...flagOffAssertions,
},
cleanupEvidence,
residualAudit: {
pendingText: residualPendingText,
},
flagOff,
consoleErrors,
failedRequests,
screenshot: screenshotPath,
},
null,
2,
)}\n`,
);
console.log(`[smoke:agent-app-lab] summary=${summaryPath}`);
console.log("[smoke:agent-app-lab] 通过");
} finally {
await context.close();
fs.rmSync(userDataDir, { recursive: true, force: true });
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
+394
View File
@@ -0,0 +1,394 @@
#!/usr/bin/env node
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import process from "node:process";
import { chromium } from "playwright";
const DEFAULTS = {
appUrl: "http://127.0.0.1:1420/",
healthUrl: "http://127.0.0.1:3030/health",
timeoutMs: 120_000,
intervalMs: 1_000,
evidenceDir: path.join(process.cwd(), ".lime", "qc", "gui-evidence", "agent-apps"),
prefix: "agent-apps-smoke",
};
function parseArgs(argv) {
const options = { ...DEFAULTS };
for (let index = 0; index < argv.length; index += 1) {
const arg = argv[index];
if (arg === "--app-url" && argv[index + 1]) {
options.appUrl = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--health-url" && argv[index + 1]) {
options.healthUrl = String(argv[index + 1]).trim();
index += 1;
continue;
}
if (arg === "--timeout-ms" && argv[index + 1]) {
options.timeoutMs = Number(argv[index + 1]);
index += 1;
continue;
}
if (arg === "--interval-ms" && argv[index + 1]) {
options.intervalMs = Number(argv[index + 1]);
index += 1;
continue;
}
if (arg === "--evidence-dir" && argv[index + 1]) {
options.evidenceDir = path.resolve(String(argv[index + 1]).trim());
index += 1;
continue;
}
if (arg === "--prefix" && argv[index + 1]) {
options.prefix = String(argv[index + 1]).trim();
index += 1;
}
}
return options;
}
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
function assert(condition, message) {
if (!condition) {
throw new Error(message);
}
}
function logStage(stage) {
console.log(`[smoke:agent-apps] stage=${stage}`);
}
async function launchSmokeContext(userDataDir) {
try {
return await chromium.launchPersistentContext(userDataDir, {
channel: "chrome",
headless: true,
});
} catch (error) {
console.warn(
`[smoke:agent-apps] Chrome channel 启动失败,尝试 Playwright Chromium: ${
error instanceof Error ? error.message : String(error)
}`,
);
return chromium.launchPersistentContext(userDataDir, {
headless: true,
});
}
}
async function waitForHealth(options) {
const startedAt = Date.now();
let lastError = null;
while (Date.now() - startedAt < options.timeoutMs) {
try {
const response = await fetch(options.healthUrl);
const payload = await response.json();
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
console.log(
`[smoke:agent-apps] DevBridge 已就绪 (${Date.now() - startedAt}ms) status=${payload?.status ?? "unknown"}`,
);
return payload;
} catch (error) {
lastError = error;
await sleep(options.intervalMs);
}
}
throw new Error(
`[smoke:agent-apps] DevBridge 未就绪: ${
lastError instanceof Error ? lastError.message : String(lastError)
}`,
);
}
function activeCloudBootstrapPayload() {
return {
schemaVersion: "agent-app-cloud-bootstrap/v1",
tenantId: "smoke-formal-entry",
generatedAt: "2026-05-16T00:00:00.000Z",
apps: [
{
appId: "content-factory-app",
displayName: "内容工厂",
version: "0.3.0",
releaseId: "smoke-content-factory-app-0.3.0",
channel: "smoke",
licenseState: "active",
registrationRequired: true,
registrationState: "active",
enabled: true,
packageUrl: "https://lime.local/agent-apps/content-factory-app/releases/0.3.0/package.zip",
packageHash: "package-fnv1a-smoke-formal",
manifestHash: "manifest-fnv1a-smoke-formal",
capabilityRequirements: {},
defaultEntries: ["dashboard", "content_scenario_planning"],
policyDefaults: {},
toolAvailability: [],
},
],
};
}
async function runFlagOffRegression(options) {
logStage("flag-off-regression");
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "lime-agent-apps-flag-off-"));
const context = await launchSmokeContext(userDataDir);
const page = await context.newPage();
const consoleErrors = [];
page.on("console", (message) => {
if (message.type() === "error") {
consoleErrors.push(message.text());
}
});
try {
await page.goto(options.appUrl, {
waitUntil: "domcontentloaded",
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="app-sidebar-main-nav"]', {
timeout: options.timeoutMs,
});
const assertions = {
agentAppsNavVisible: (await page.locator('button[title="Agent Apps"]').count()) > 0,
labNavHidden: (await page.locator('button[title="Agent App Lab"]').count()) === 0,
noConsoleErrors: consoleErrors.length === 0,
};
Object.entries(assertions).forEach(([key, value]) => {
assert(Boolean(value), `Flag-off assertion failed: ${key}`);
});
const screenshotPath = path.join(options.evidenceDir, `${options.prefix}-flag-off.png`);
await page.screenshot({ path: screenshotPath, fullPage: true });
return {
assertions,
consoleErrors,
screenshot: screenshotPath,
};
} finally {
await context.close();
fs.rmSync(userDataDir, { recursive: true, force: true });
}
}
async function main() {
const options = parseArgs(process.argv.slice(2));
fs.mkdirSync(options.evidenceDir, { recursive: true });
await waitForHealth(options);
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), "lime-agent-apps-smoke-"));
const context = await launchSmokeContext(userDataDir);
const consoleErrors = [];
const failedRequests = [];
const bootstrap = activeCloudBootstrapPayload();
await context.addInitScript((payload) => {
window.localStorage.removeItem("lime.agentAppHost.flags");
window.localStorage.removeItem("lime.agentAppHost.labEnabled");
window.__LIME_AGENT_APPS_SMOKE_BOOTSTRAP__ = payload;
}, bootstrap);
const page = await context.newPage();
page.on("console", (message) => {
if (message.type() === "error") {
consoleErrors.push(message.text());
}
});
page.on("requestfailed", (request) => {
failedRequests.push({
url: request.url(),
method: request.method(),
failure: request.failure()?.errorText ?? "unknown",
});
});
try {
logStage("open-app");
await page.goto(options.appUrl, { waitUntil: "domcontentloaded", timeout: options.timeoutMs });
await page.waitForSelector('[data-testid="app-sidebar-main-nav"]', {
timeout: options.timeoutMs,
});
assert(
(await page.locator('button[title="Agent Apps"]').count()) > 0,
"Agent Apps nav should be visible",
);
assert(
(await page.locator('button[title="Agent App Lab"]').count()) === 0,
"Agent App Lab nav should stay hidden in formal smoke",
);
logStage("open-agent-apps");
await page.click('button[title="Agent Apps"]');
await page.waitForSelector('[data-testid="agent-apps-page"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-apps-installed-content-factory-app"]', {
timeout: options.timeoutMs,
});
logStage("verify-registration-required");
await page.waitForSelector('[data-testid="agent-apps-registration-content-factory-app"]', {
timeout: options.timeoutMs,
});
const registrationInstallBlocked = await page.isDisabled(
'[data-testid="agent-apps-install-cloud-content-factory-app"]',
);
logStage("activate-bootstrap-catalog");
await page.evaluate((payload) => {
window.__LIME_BOOTSTRAP__ = payload;
}, bootstrap);
await page.click('[data-testid="agent-apps-refresh"]');
await page.waitForFunction(
() =>
!document
.querySelector('[data-testid="agent-apps-install-cloud-content-factory-app"]')
?.hasAttribute("disabled"),
undefined,
{ timeout: options.timeoutMs },
);
logStage("install-cloud-review");
await page.click('[data-testid="agent-apps-install-cloud-content-factory-app"]');
await page.waitForSelector('[data-testid="agent-apps-install-review"]', {
timeout: options.timeoutMs,
});
await page.click('[data-testid="agent-apps-install-review-confirm"]');
await page.waitForSelector('[data-testid="agent-apps-installed-content-factory-app"]', {
timeout: options.timeoutMs,
});
logStage("disable-enable");
await page.click('[data-testid="agent-apps-installed-content-factory-app"]');
await page.click('[data-testid="agent-apps-disable"]');
await page.waitForFunction(
() =>
document
.querySelector('[data-testid="agent-apps-launch-entry-dashboard"]')
?.hasAttribute("disabled") &&
!document.querySelector('[data-testid="agent-apps-enable"]')?.hasAttribute("disabled"),
undefined,
{ timeout: options.timeoutMs },
);
const disabledLaunchBlocked = await page.isDisabled(
'[data-testid="agent-apps-launch-entry-dashboard"]',
);
await page.click('[data-testid="agent-apps-enable"]');
await page.waitForFunction(
() =>
!document
.querySelector('[data-testid="agent-apps-launch-entry-dashboard"]')
?.hasAttribute("disabled"),
undefined,
{ timeout: options.timeoutMs },
);
logStage("launch-runtime-surface");
await page.click('[data-testid="agent-apps-launch-entry-dashboard"]');
await page.waitForSelector('[data-testid="agent-app-runtime-surface"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-app-runtime-frame"]', {
timeout: options.timeoutMs,
});
const runtimeFrameSrc = await page.getAttribute(
'[data-testid="agent-app-runtime-frame"]',
"src",
);
logStage("return-agent-apps");
await page.click('button[title="Agent Apps"]');
await page.waitForSelector('[data-testid="agent-apps-page"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-apps-installed-content-factory-app"]', {
timeout: options.timeoutMs,
});
logStage("uninstall-rehearsal");
await page.click('[data-testid="agent-apps-uninstall-delete-data"]');
await page.waitForSelector('[data-testid="agent-apps-uninstall-preview"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-apps-cleanup-evidence-json"]', {
timeout: options.timeoutMs,
});
await page.waitForSelector('[data-testid="agent-apps-residual-audit"]', {
timeout: options.timeoutMs,
});
const cleanupEvidenceText = await page.textContent(
'[data-testid="agent-apps-cleanup-evidence-json"]',
);
const cleanupEvidence = JSON.parse(cleanupEvidenceText ?? "{}");
await page.click('[data-testid="agent-apps-uninstall-confirm"]');
await page.waitForSelector('[data-testid="agent-apps-launch-summary"]', {
timeout: options.timeoutMs,
});
const stillInstalledAfterRehearsal =
(await page.locator('[data-testid="agent-apps-installed-content-factory-app"]').count()) > 0;
const flagOff = await runFlagOffRegression(options);
const assertions = {
formalPageVisible: Boolean(await page.$('[data-testid="agent-apps-page"]')),
installedVisible: stillInstalledAfterRehearsal,
registrationRequiredBlocked: registrationInstallBlocked,
cloudInstallReviewVisible: true,
disabledLaunchBlocked,
runtimeSurfaceVisible: Boolean(runtimeFrameSrc),
cleanupEvidenceSelectedApp: cleanupEvidence.appId === "content-factory-app",
cleanupEvidenceStrategy: cleanupEvidence.strategy === "delete-data",
cleanupEvidenceRehearsalOnly: cleanupEvidence.completionEffect === "rehearsal-only",
cleanupEvidenceBlockedCount: cleanupEvidence.blockedTargetCount === 0,
flagOffAgentAppsNavVisible: flagOff.assertions.agentAppsNavVisible,
flagOffLabNavHidden: flagOff.assertions.labNavHidden,
flagOffNoConsoleErrors: flagOff.assertions.noConsoleErrors,
};
Object.entries(assertions).forEach(([key, value]) => {
assert(Boolean(value), `Assertion failed: ${key}`);
});
const screenshotPath = path.join(options.evidenceDir, `${options.prefix}.png`);
const summaryPath = path.join(options.evidenceDir, `${options.prefix}-summary.json`);
await page.screenshot({ path: screenshotPath, fullPage: true });
fs.writeFileSync(
summaryPath,
`${JSON.stringify(
{
scenarioId: "agent-apps-smoke",
appUrl: options.appUrl,
assertions,
runtimeFrameSrc,
cleanupEvidence,
flagOff,
consoleErrors,
failedRequests,
screenshot: screenshotPath,
},
null,
2,
)}\n`,
);
console.log(`[smoke:agent-apps] summary=${summaryPath}`);
console.log("[smoke:agent-apps] 通过");
} finally {
await context.close();
fs.rmSync(userDataDir, { recursive: true, force: true });
}
}
main().catch((error) => {
console.error(error instanceof Error ? error.message : String(error));
process.exit(1);
});
+123 -17
View File
@@ -459,6 +459,98 @@ async function clickPageControl(page, { text, ariaLabel, index = 0 }) {
}
}
async function waitForInputbarKnowledgePackToggle(page, label, timeoutMs) {
const locator = page.locator('[data-testid="inputbar-knowledge-pack-toggle"]');
try {
await locator.first().waitFor({ state: "visible", timeout: timeoutMs });
} catch (error) {
const pageText = await page
.locator("body")
.innerText()
.catch(() => "");
const knowledgeControls = await page
.locator("[data-testid^='inputbar-knowledge-']")
.evaluateAll((items) =>
items.slice(0, 40).map((item) => ({
testId: item.getAttribute("data-testid"),
text: (item.textContent || "").trim().replace(/\s+/g, " "),
aria: item.getAttribute("aria-label"),
title: item.getAttribute("title"),
})),
)
.catch(() => []);
throw new Error(
`[smoke:knowledge-gui] ${label} 等待默认项目资料控件失败 ${JSON.stringify({
knowledgeControls,
})},页面文本预览: ${pageText.slice(0, 2_000)}`,
{ cause: error },
);
}
}
async function clickTestId(page, { testId, label }) {
const locator = page.locator(`[data-testid="${testId}"]`).first();
try {
await locator.click({ timeout: DEFAULT_ACTION_TIMEOUT_MS });
} catch (error) {
const pageText = await page
.locator("body")
.innerText()
.catch(() => "");
const candidates = await page
.locator("[data-testid]")
.evaluateAll((items) =>
items.slice(0, 100).map((item) => ({
testId: item.getAttribute("data-testid"),
text: (item.textContent || "").trim().replace(/\s+/g, " "),
aria: item.getAttribute("aria-label"),
title: item.getAttribute("title"),
})),
)
.catch(() => []);
throw new Error(
`[smoke:knowledge-gui] ${label} 点击失败 ${JSON.stringify({
testId,
candidates,
})},页面文本预览: ${pageText.slice(0, 2_000)}`,
{ cause: error },
);
}
}
async function waitForInputbarKnowledgeHub(page, label, timeoutMs) {
try {
await page
.locator('[data-testid="inputbar-knowledge-hub"]')
.first()
.waitFor({ state: "visible", timeout: timeoutMs });
} catch (error) {
const pageText = await page
.locator("body")
.innerText()
.catch(() => "");
const knowledgeControls = await page
.locator("[data-testid^='inputbar-knowledge-']")
.evaluateAll((items) =>
items.slice(0, 40).map((item) => ({
testId: item.getAttribute("data-testid"),
text: (item.textContent || "").trim().replace(/\s+/g, " "),
aria: item.getAttribute("aria-label"),
title: item.getAttribute("title"),
})),
)
.catch(() => []);
throw new Error(
`[smoke:knowledge-gui] ${label} 等待项目资料浮层失败 ${JSON.stringify({
knowledgeControls,
})},页面文本预览: ${pageText.slice(0, 2_000)}`,
{ cause: error },
);
}
}
async function closeFileManagerIfOpen(page) {
const closeButton = page
.getByRole("button", { name: "关闭文件管理器" })
@@ -793,11 +885,25 @@ async function openKnowledgePageFromMainNav(page, options) {
throw new Error(
`[smoke:knowledge-gui] 项目资料导航失败: ${
lastError instanceof Error ? lastError.message : String(lastError)
lastError instanceof Error ? lastError.message : String(lastError)
}`,
);
}
async function openHomeFromMainNav(page, options, label) {
await syncSmokeProjectStorage(page, options);
await clickScopedButton(page, {
scope: '[data-testid="app-sidebar-main-nav"]',
ariaLabel: "新建任务",
});
await waitForPageText(
page,
`${label} 新建任务页加载`,
["青柠一下,灵感即来"],
options.timeoutMs,
);
}
async function waitForKnowledgePack(options, label, matcher) {
const startedAt = Date.now();
let lastError = null;
@@ -960,7 +1066,20 @@ async function runPlaywrightGuiFlow(options) {
);
logStage("open-home-knowledge-hub");
await clickPageControl(page, { text: "添加资料" });
await waitForInputbarKnowledgePackToggle(
page,
"首页默认项目资料控件加载",
options.timeoutMs,
);
await clickTestId(page, {
testId: "entry-home-knowledge-import",
label: "首页添加资料入口",
});
await waitForInputbarKnowledgeHub(
page,
"首页添加资料入口打开浮层",
options.timeoutMs,
);
logStage("wait-home-knowledge-hub");
await waitForPageText(
@@ -1037,16 +1156,7 @@ async function runPlaywrightGuiFlow(options) {
options.timeoutMs,
);
await assertNoUserFacingInternalText(page, "项目资料状态说明页");
await clickScopedButton(page, {
scope: '[data-testid="app-sidebar-main-nav"]',
ariaLabel: "灵感",
});
await waitForPageText(
page,
"灵感页加载",
["收藏过的想法"],
options.timeoutMs,
);
await openHomeFromMainNav(page, options, "状态说明后返回");
await openKnowledgePageFromMainNav(page, options);
await waitForPageText(
page,
@@ -1133,11 +1243,7 @@ async function runPlaywrightGuiFlow(options) {
);
logStage("return-knowledge-overview");
await clickScopedButton(page, {
scope: '[data-testid="app-sidebar-main-nav"]',
ariaLabel: "灵感",
});
await waitForPageText(page, "灵感页加载", ["灵感"], options.timeoutMs);
await openHomeFromMainNav(page, options, "Agent 保存后返回");
await openKnowledgePageFromMainNav(page, options);
logStage("wait-captured-agent-result");