Skip to content

Commit eb48efc

Browse files
committed
fix(qa): isolate script scenarios from flow gateways
1 parent bc802ce commit eb48efc

2 files changed

Lines changed: 97 additions & 6 deletions

File tree

extensions/qa-lab/src/suite-launch.runtime.test.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,78 @@ describe("qa suite runtime launcher", () => {
374374
expect(runQaTestFileScenarios).toHaveBeenCalledTimes(1);
375375
});
376376

377+
it("runs script scenarios after flow Gateways stop without serializing Playwright", async () => {
378+
const repoRoot = await makeTempRepo("qa-suite-script-isolation-");
379+
let releaseFlow!: () => void;
380+
let markFlowStarted!: () => void;
381+
const flowStarted = new Promise<void>((resolve) => {
382+
markFlowStarted = resolve;
383+
});
384+
const flowBlocked = new Promise<void>((resolve) => {
385+
releaseFlow = resolve;
386+
});
387+
runQaFlowSuite.mockImplementationOnce(
388+
async (params: { outputDir?: string; scenarioIds?: string[] } | undefined) => {
389+
markFlowStarted();
390+
await flowBlocked;
391+
const outputDir = params?.outputDir ?? "/tmp/qa-flow";
392+
const evidencePath = path.join(outputDir, "qa-evidence.json");
393+
await writeEvidence(evidencePath);
394+
const scenarioIds = params?.scenarioIds ?? ["channel-chat-baseline"];
395+
return {
396+
outputDir,
397+
evidencePath,
398+
reportPath: path.join(outputDir, "qa-suite-report.md"),
399+
summaryPath: path.join(outputDir, "qa-suite-summary.json"),
400+
report: "# QA Suite Report\n",
401+
scenarios: scenarioIds.map((scenarioId) => ({
402+
name: scenarioId,
403+
status: "pass",
404+
steps: [],
405+
})),
406+
watchUrl: "http://127.0.0.1:43124",
407+
};
408+
},
409+
);
410+
411+
const runPromise = runQaSuite({
412+
repoRoot,
413+
outputDir: ".artifacts/qa-e2e/script-isolation",
414+
concurrency: 8,
415+
scenarioIds: [
416+
"channel-chat-baseline",
417+
"control-ui-chat-flow-playwright",
418+
"docker-npm-onboard-channel-agent",
419+
],
420+
});
421+
await flowStarted;
422+
await vi.waitFor(() => {
423+
expect(runQaTestFileScenarios).toHaveBeenCalledTimes(1);
424+
});
425+
426+
expect(runQaTestFileScenarios).toHaveBeenNthCalledWith(
427+
1,
428+
expect.objectContaining({
429+
scenarios: [
430+
expect.objectContaining({ execution: expect.objectContaining({ kind: "playwright" }) }),
431+
],
432+
}),
433+
);
434+
435+
releaseFlow();
436+
await runPromise;
437+
438+
expect(runQaTestFileScenarios).toHaveBeenCalledTimes(2);
439+
expect(runQaTestFileScenarios).toHaveBeenNthCalledWith(
440+
2,
441+
expect.objectContaining({
442+
scenarios: [
443+
expect.objectContaining({ execution: expect.objectContaining({ kind: "script" }) }),
444+
],
445+
}),
446+
);
447+
});
448+
377449
it("keeps multiple isolated flow scenarios in separate serial partitions", async () => {
378450
const repoRoot = await makeTempRepo("qa-suite-serial-isolated-");
379451
await runQaSuite({

extensions/qa-lab/src/suite-launch.runtime.ts

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -451,6 +451,7 @@ async function runUnifiedQaSuite(params: {
451451
const sharedFlowPartitionTasks: QaUnifiedPartitionTask[] = [];
452452
const isolatedFlowPartitionTasks: QaUnifiedPartitionTask[] = [];
453453
const testFilePartitionTasks: QaUnifiedPartitionTask[] = [];
454+
const scriptPartitionTasks: QaUnifiedPartitionTask[] = [];
454455
if (params.plan.flowScenarios.length > 0) {
455456
const sharedFlowScenarios = params.plan.flowScenarios.filter(
456457
(scenario) => !scenarioRequiresIsolatedQaSuiteWorker(scenario),
@@ -535,13 +536,15 @@ async function runUnifiedQaSuite(params: {
535536
}
536537
}
537538
}
538-
if (params.plan.testFileScenariosByKind.size > 0) {
539-
testFilePartitionTasks.push({
539+
const createTestFilePartitionTask = (
540+
scenariosByKind: ReadonlyMap<QaTestFileExecutionKind, QaTestFileScenario[]>,
541+
) =>
542+
({
540543
weight: 1,
541544
run: async () => {
542545
const testFileEvidenceSummaries: QaEvidenceSummaryJson[] = [];
543546
const testFileScenarioResults: QaUnifiedPartitionResult["scenarioResults"] = [];
544-
for (const [kind, testFileScenarios] of params.plan.testFileScenariosByKind) {
547+
for (const [kind, testFileScenarios] of scenariosByKind) {
545548
const result = await runQaTestFileSuiteFromRuntime({
546549
runParams: {
547550
...params.runParams,
@@ -566,14 +569,30 @@ async function runUnifiedQaSuite(params: {
566569
scenarioResults: testFileScenarioResults,
567570
};
568571
},
569-
});
572+
}) satisfies QaUnifiedPartitionTask;
573+
const concurrentTestFileScenariosByKind = new Map(
574+
[...params.plan.testFileScenariosByKind].filter(([kind]) => kind !== "script"),
575+
);
576+
if (concurrentTestFileScenariosByKind.size > 0) {
577+
testFilePartitionTasks.push(createTestFilePartitionTask(concurrentTestFileScenariosByKind));
578+
}
579+
const scriptScenarios = params.plan.testFileScenariosByKind.get("script");
580+
if (scriptScenarios?.length) {
581+
scriptPartitionTasks.push(createTestFilePartitionTask(new Map([["script", scriptScenarios]])));
570582
}
571-
const partitionTasks = [
583+
const concurrentPartitionTasks = [
572584
...sharedFlowPartitionTasks,
573585
...testFilePartitionTasks,
574586
...isolatedFlowPartitionTasks,
575587
];
576-
const partitionResults = await runWeightedUnifiedPartitionTasks(partitionTasks, concurrency);
588+
const concurrentPartitionResults = await runWeightedUnifiedPartitionTasks(
589+
concurrentPartitionTasks,
590+
concurrency,
591+
);
592+
// Script scenarios may rebuild the checkout's shared dist tree. Wait until every
593+
// flow Gateway has stopped so package postbuild cannot invalidate its loaded chunks.
594+
const scriptPartitionResults = await runWeightedUnifiedPartitionTasks(scriptPartitionTasks, 1);
595+
const partitionResults = [...concurrentPartitionResults, ...scriptPartitionResults];
577596
for (const partitionResult of partitionResults) {
578597
for (const scenarioResult of partitionResult.scenarioResults) {
579598
scenarioResultsById.set(scenarioResult.scenarioId, scenarioResult.result);

0 commit comments

Comments
 (0)