mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:20:43 +00:00
test: harden qa-lab concurrent web scenarios
This commit is contained in:
@@ -214,6 +214,16 @@ describe("qa suite failure reply handling", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("enables Control UI only for Control UI scenario workers", () => {
|
||||
expect(
|
||||
qaSuiteTesting.scenarioRequiresControlUi({
|
||||
...makeScenario("control-ui"),
|
||||
surface: "control-ui",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(qaSuiteTesting.scenarioRequiresControlUi(makeScenario("plain"))).toBe(false);
|
||||
});
|
||||
|
||||
it("filters provider-specific scenarios from an implicit live lane", () => {
|
||||
const scenarios = [
|
||||
makeScenario("generic"),
|
||||
|
||||
@@ -68,7 +68,7 @@ import { readQaBootstrapScenarioCatalog } from "./scenario-catalog.js";
|
||||
import { runScenarioFlow } from "./scenario-flow-runner.js";
|
||||
import { createQaScenarioRuntimeApi } from "./scenario-runtime-api.js";
|
||||
import {
|
||||
closeAllQaWebSessions,
|
||||
closeQaWebSessions,
|
||||
qaWebEvaluate,
|
||||
qaWebOpenPage,
|
||||
qaWebSnapshot,
|
||||
@@ -98,6 +98,7 @@ type QaSuiteEnvironment = {
|
||||
providerMode: "mock-openai" | "live-frontier";
|
||||
primaryModel: string;
|
||||
alternateModel: string;
|
||||
webSessionIds: Set<string>;
|
||||
};
|
||||
|
||||
export type QaSuiteStartLabFn = (params?: QaLabServerStartParams) => Promise<QaLabServerHandle>;
|
||||
@@ -340,6 +341,12 @@ function collectQaSuiteGatewayRuntimeOptions(
|
||||
return forwardHostHome ? { forwardHostHome: true } : undefined;
|
||||
}
|
||||
|
||||
function scenarioRequiresControlUi(
|
||||
scenario: ReturnType<typeof readQaBootstrapScenarioCatalog>["scenarios"][number],
|
||||
) {
|
||||
return normalizeLowercaseStringOrEmpty(scenario.surface) === "control-ui";
|
||||
}
|
||||
|
||||
function liveTurnTimeoutMs(env: QaSuiteEnvironment, fallbackMs: number) {
|
||||
return resolveQaLiveTurnTimeoutMs(env, fallbackMs);
|
||||
}
|
||||
@@ -1268,7 +1275,11 @@ function createScenarioFlowApi(
|
||||
browserOpenTab: qaBrowserOpenTab,
|
||||
browserSnapshot: qaBrowserSnapshot,
|
||||
browserAct: qaBrowserAct,
|
||||
webOpenPage: qaWebOpenPage,
|
||||
webOpenPage: async (params: Parameters<typeof qaWebOpenPage>[0]) => {
|
||||
const opened = await qaWebOpenPage(params);
|
||||
env.webSessionIds.add(opened.pageId);
|
||||
return opened;
|
||||
},
|
||||
webWait: qaWebWait,
|
||||
webType: qaWebType,
|
||||
webSnapshot: qaWebSnapshot,
|
||||
@@ -1330,6 +1341,7 @@ export const qaSuiteTesting = {
|
||||
mapQaSuiteWithConcurrency,
|
||||
normalizeQaSuiteConcurrency,
|
||||
scenarioMatchesLiveLane,
|
||||
scenarioRequiresControlUi,
|
||||
selectQaSuiteScenarios,
|
||||
readTransportTranscript,
|
||||
formatTransportTranscript,
|
||||
@@ -1575,10 +1587,10 @@ export async function runQaSuite(params?: QaSuiteRunParams): Promise<QaSuiteResu
|
||||
scenarioIds: [scenario.id],
|
||||
concurrency: 1,
|
||||
startLab,
|
||||
// Isolated workers do not need their own Control UI proxy. The
|
||||
// outer lab already owns the watch surface, so skip per-worker
|
||||
// Control UI asset resolution and startup overhead.
|
||||
controlUiEnabled: false,
|
||||
// Most isolated workers do not need their own Control UI proxy.
|
||||
// Control UI scenarios do, because they open the worker's
|
||||
// gateway-backed app directly.
|
||||
controlUiEnabled: scenarioRequiresControlUi(scenario),
|
||||
});
|
||||
const scenarioResult: QaSuiteScenarioResult =
|
||||
result.scenarios[0] ??
|
||||
@@ -1740,6 +1752,7 @@ export async function runQaSuite(params?: QaSuiteRunParams): Promise<QaSuiteResu
|
||||
providerMode,
|
||||
primaryModel,
|
||||
alternateModel,
|
||||
webSessionIds: new Set(),
|
||||
};
|
||||
|
||||
let preserveGatewayRuntimeDir: string | undefined;
|
||||
@@ -1849,7 +1862,7 @@ export async function runQaSuite(params?: QaSuiteRunParams): Promise<QaSuiteResu
|
||||
preserveGatewayRuntimeDir = path.join(outputDir, "artifacts", "gateway-runtime");
|
||||
throw error;
|
||||
} finally {
|
||||
await closeAllQaWebSessions();
|
||||
await closeQaWebSessions(env.webSessionIds);
|
||||
const keepTemp = process.env.OPENCLAW_QA_KEEP_TEMP === "1" || false;
|
||||
await gateway.stop({
|
||||
keepTemp,
|
||||
|
||||
@@ -44,6 +44,7 @@ vi.mock("playwright-core", () => ({
|
||||
|
||||
import {
|
||||
closeAllQaWebSessions,
|
||||
closeQaWebSessions,
|
||||
qaWebEvaluate,
|
||||
qaWebOpenPage,
|
||||
qaWebSnapshot,
|
||||
@@ -114,4 +115,19 @@ describe("qa web runtime", () => {
|
||||
expect(contextClose).toHaveBeenCalledTimes(1);
|
||||
expect(browserClose).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("can close only selected page sessions", async () => {
|
||||
const first = await qaWebOpenPage({ url: "http://127.0.0.1:3000/one" });
|
||||
const second = await qaWebOpenPage({ url: "http://127.0.0.1:3000/two" });
|
||||
|
||||
await closeQaWebSessions([first.pageId]);
|
||||
|
||||
await expect(qaWebSnapshot({ pageId: first.pageId })).rejects.toThrow(
|
||||
`unknown web session: ${first.pageId}`,
|
||||
);
|
||||
await expect(qaWebSnapshot({ pageId: second.pageId })).resolves.toMatchObject({
|
||||
text: "hello from body",
|
||||
});
|
||||
await closeAllQaWebSessions();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -144,11 +144,23 @@ export async function qaWebEvaluate<T = unknown>(params: QaWebEvaluateParams): P
|
||||
])) as T;
|
||||
}
|
||||
|
||||
export async function closeAllQaWebSessions(): Promise<void> {
|
||||
const active = [...sessions.values()];
|
||||
sessions.clear();
|
||||
export async function closeQaWebSessions(pageIds?: Iterable<string>): Promise<void> {
|
||||
const active = pageIds
|
||||
? [...pageIds].flatMap((pageId) => {
|
||||
const session = sessions.get(pageId);
|
||||
sessions.delete(pageId);
|
||||
return session ? [session] : [];
|
||||
})
|
||||
: [...sessions.values()];
|
||||
if (!pageIds) {
|
||||
sessions.clear();
|
||||
}
|
||||
for (const session of active) {
|
||||
await session.context.close().catch(() => {});
|
||||
await session.browser.close().catch(() => {});
|
||||
}
|
||||
}
|
||||
|
||||
export async function closeAllQaWebSessions(): Promise<void> {
|
||||
await closeQaWebSessions();
|
||||
}
|
||||
|
||||
@@ -207,7 +207,7 @@ steps:
|
||||
args:
|
||||
- lambda:
|
||||
async: true
|
||||
expr: "await (async () => { const store = await readRawQaSessionStore(env); const entry = store[activeSessionKey]; if (!entry || !Array.isArray(entry.pluginDebugEntries)) return undefined; return entry.pluginDebugEntries.some((pluginEntry) => pluginEntry?.pluginId === 'active-memory' && Array.isArray(pluginEntry.lines) && pluginEntry.lines.some((line) => line.includes('Active Memory: ok'))) ? entry : undefined; })()"
|
||||
expr: "await (async () => { const store = await readRawQaSessionStore(env); const entry = store[activeSessionKey]; if (!entry || !Array.isArray(entry.pluginDebugEntries)) return undefined; return entry.pluginDebugEntries.some((pluginEntry) => pluginEntry?.pluginId === 'active-memory' && Array.isArray(pluginEntry.lines) && pluginEntry.lines.some((line) => line.includes('Active Memory: status=ok'))) ? entry : undefined; })()"
|
||||
- 10000
|
||||
- if:
|
||||
expr: "Boolean(env.mock)"
|
||||
|
||||
Reference in New Issue
Block a user