@zaip-ai/cli 1.3.2 → 1.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -3
- package/README.md +8 -2
- package/dist/services/codex-pre-tool-hook-template.js +1 -1
- package/dist/services/runtime-template.test.js +9 -8
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +2 -2
- package/templates/.agents/dashboard/.zaip-managed.json +414 -394
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-ovxurkIk.js → CostCalculationGuidePage-D1mpyYwh.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-BXy6nti1.js → DashboardHelpDialog-CKO47D5s.js} +30 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-vSvvOHQp.js → PricingSettingsDialog-BuSlCbyZ.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-zH758vfs.js → ProjectSettingsPage-CUHFkAhZ.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-CTH2wDs5.js → TraceabilityWorkspace-BmASq2gW.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Csj6LiAt.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-B_lDDHn1.js → icons-vendor-VyTTl_ph.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DIwDvCu5.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-B3TJrD4O.js → react-vendor-CTC6vtln.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-7f1j5wx3.js → traceability-9Mtvbwrg.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/src/App.tsx +74 -10
- package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +44 -0
- package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +103 -2
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-capability-nav.css +11 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +14 -0
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +3 -6
- package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +3 -2
- package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.test.ts +60 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostDeveloperAttributionPanel.tsx +26 -9
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorNavigation.tsx +189 -0
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +122 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +70 -155
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +2 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +12 -4
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +67 -24
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +19 -4
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +59 -0
- package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +55 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +25 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +29 -0
- package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +3 -2
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +12 -2
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +274 -246
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +255 -227
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +263 -235
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.test.ts +282 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-dimension-facts.ts +71 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-launch-progress.ts +18 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-stale-seed.ts +50 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +9 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +45 -73
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-snapshot-seed.test.ts +79 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-work-item-index.test.ts +56 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.mjs +26 -8
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-launch-page.test.mjs +45 -4
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.mjs +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-lifecycle.test.mjs +81 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +171 -71
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +152 -11
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +4 -9
- package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +9 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +1 -1
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +4 -0
- package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +238 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +4 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +60 -17
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +1 -1
- package/templates/.agents/requirements/public-distribution.md +1 -1
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +7 -7
- package/templates/.agents/runtime/checksums.sha256 +3 -3
- package/templates/.agents/runtime/provenance.json +3 -3
- package/templates/.agents/runtime/sbom.cdx.json +1595 -3491
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +16 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +19 -20
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.test.mjs +143 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +14 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +3 -1
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +21 -0
- package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +3 -0
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +29 -0
- package/templates/template-manifest.json +539 -935
- package/templates/.agents/dashboard/.tmp-run-list.json +0 -254
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-DKwP1stV.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-CuMcoUni.js +0 -2
|
@@ -79,14 +79,10 @@ namespace ZaipDashboardLauncher
|
|
|
79
79
|
|
|
80
80
|
private static int WaitForLaunchStatus(Process process, string statusPath, string workspaceRoot)
|
|
81
81
|
{
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
// the current leader, then still needs the bounded 180 s server
|
|
87
|
-
// startup window if that leader fails before publishing a session.
|
|
88
|
-
var deadline = DateTime.UtcNow.AddSeconds(420);
|
|
89
|
-
while (DateTime.UtcNow < deadline)
|
|
82
|
+
// A cold local read model has no useful fixed wall-clock limit.
|
|
83
|
+
// Keep this wrapper attached until Node publishes a real result;
|
|
84
|
+
// errors still surface when Node reports them or exits.
|
|
85
|
+
while (true)
|
|
90
86
|
{
|
|
91
87
|
try
|
|
92
88
|
{
|
|
@@ -125,7 +121,6 @@ namespace ZaipDashboardLauncher
|
|
|
125
121
|
}
|
|
126
122
|
Thread.Sleep(100);
|
|
127
123
|
}
|
|
128
|
-
return ReportFailure("O dashboard ainda não publicou uma sessão pronta após 420 segundos. O processo local pode continuar registrando detalhes no log temporário; feche esta mensagem e tente novamente após verificar o log.");
|
|
129
124
|
}
|
|
130
125
|
|
|
131
126
|
private static bool VerifyDashboardLaunch(string workspaceRoot, out string error)
|
|
@@ -11,6 +11,9 @@ export default defineConfig({
|
|
|
11
11
|
timeout: 180_000,
|
|
12
12
|
expect: { timeout: 60_000 },
|
|
13
13
|
retries: process.env.CI ? 1 : 0,
|
|
14
|
+
// CI shares one SQLite runtime across the suite. Serializing browser workers
|
|
15
|
+
// keeps the measured latency gates independent from runner CPU count.
|
|
16
|
+
...(process.env.CI ? { workers: 1 } : {}),
|
|
14
17
|
reporter: process.env.CI ? "github" : "list",
|
|
15
18
|
use: {
|
|
16
19
|
baseURL: `${baseURL}/?token=e2e-token`,
|
|
@@ -22,7 +25,10 @@ export default defineConfig({
|
|
|
22
25
|
cwd: workspaceRoot,
|
|
23
26
|
url: `${baseURL}/api/v2/health`,
|
|
24
27
|
reuseExistingServer: false,
|
|
25
|
-
|
|
28
|
+
// The canonical SQLite rebuild is intentionally unbounded in production.
|
|
29
|
+
// Keep only a broad test-runner safety envelope so a large clean workspace
|
|
30
|
+
// is not killed by the former two-minute startup deadline.
|
|
31
|
+
timeout: 3_000_000,
|
|
26
32
|
env: {
|
|
27
33
|
ZAIP_UI_TOKEN: "e2e-token",
|
|
28
34
|
ZAIP_SERVER_PORT: serverPort,
|
|
@@ -31,6 +37,8 @@ export default defineConfig({
|
|
|
31
37
|
// Match the Windows launcher: first paint uses the latest validated
|
|
32
38
|
// immutable seed while canonical refresh work stays off the served path.
|
|
33
39
|
ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "1",
|
|
40
|
+
// Keep pre-bind progress observable if the web-server health gate times out.
|
|
41
|
+
ZAIP_DASHBOARD_LAUNCH_PROGRESS: "1",
|
|
34
42
|
},
|
|
35
43
|
},
|
|
36
44
|
});
|
|
@@ -126,7 +126,7 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
|
|
|
126
126
|
|
|
127
127
|
async function expectGlobalMonthPreset(page: import("@playwright/test").Page) {
|
|
128
128
|
await expect(
|
|
129
|
-
page.locator(".dashboard-global-controls").
|
|
129
|
+
page.locator(".dashboard-global-controls").getByLabel("Período"),
|
|
130
130
|
).toHaveValue("this-month");
|
|
131
131
|
}
|
|
132
132
|
|
|
@@ -156,6 +156,10 @@ async function openAllTimeCostMonitor(page: Page) {
|
|
|
156
156
|
);
|
|
157
157
|
await page.goto("/?token=e2e-token");
|
|
158
158
|
await operations;
|
|
159
|
+
await page
|
|
160
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
161
|
+
.getByRole("button", { name: /Operação/ })
|
|
162
|
+
.click();
|
|
159
163
|
const period = page
|
|
160
164
|
.locator(".dashboard-global-controls")
|
|
161
165
|
.getByRole("combobox", { name: "Período" });
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { expect, type Page, test } from "@playwright/test";
|
|
2
|
+
|
|
3
|
+
const COST_MONITOR_PATH = "/api/v2/cost-monitor";
|
|
4
|
+
const STALE_SUMMARY_SENTINEL = "SENTINELA-RESPOSTA-ANTIGA";
|
|
5
|
+
const fiveSeconds = 5_000;
|
|
6
|
+
|
|
7
|
+
test("refreshes Cost Monitor within five seconds when Todo o Período is selected in place", async ({
|
|
8
|
+
page,
|
|
9
|
+
}) => {
|
|
10
|
+
await openDashboard(page);
|
|
11
|
+
await openCostMonitor(page);
|
|
12
|
+
|
|
13
|
+
const summary = waitForCostSummary(page, "all");
|
|
14
|
+
const startedAt = Date.now();
|
|
15
|
+
await periodControl(page).selectOption("all");
|
|
16
|
+
await summary;
|
|
17
|
+
await expectCostMonitorReady(page);
|
|
18
|
+
|
|
19
|
+
expect(Date.now() - startedAt).toBeLessThan(fiveSeconds);
|
|
20
|
+
await expect(periodControl(page)).toHaveValue("all");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("loads Cost Monitor within five seconds when the user enters after choosing Todo o Período", async ({
|
|
24
|
+
page,
|
|
25
|
+
}) => {
|
|
26
|
+
await openDashboard(page);
|
|
27
|
+
|
|
28
|
+
await page
|
|
29
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
30
|
+
.getByRole("button", { name: /Operação/ })
|
|
31
|
+
.click();
|
|
32
|
+
await expect(periodControl(page)).toBeVisible();
|
|
33
|
+
|
|
34
|
+
await periodControl(page).selectOption("all");
|
|
35
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
|
|
36
|
+
|
|
37
|
+
const summary = waitForCostSummary(page, "all");
|
|
38
|
+
const startedAt = Date.now();
|
|
39
|
+
await page
|
|
40
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
41
|
+
.getByRole("button", { name: /Monitor de Custos/ })
|
|
42
|
+
.click();
|
|
43
|
+
await summary;
|
|
44
|
+
await expectCostMonitorReady(page);
|
|
45
|
+
|
|
46
|
+
expect(Date.now() - startedAt).toBeLessThan(fiveSeconds);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("shows one retry after a summary 503 and does not retry in a loop", async ({ page }) => {
|
|
50
|
+
let summaries = 0;
|
|
51
|
+
await page.route("**/api/v2/cost-monitor**", async (route) => {
|
|
52
|
+
const url = new URL(route.request().url());
|
|
53
|
+
if (url.searchParams.get("scope") !== "summary") return route.continue();
|
|
54
|
+
summaries += 1;
|
|
55
|
+
if (summaries === 1)
|
|
56
|
+
return route.fulfill({
|
|
57
|
+
status: 503,
|
|
58
|
+
contentType: "application/json",
|
|
59
|
+
body: JSON.stringify({ error: "summary temporarily unavailable" }),
|
|
60
|
+
});
|
|
61
|
+
return route.continue();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
await openDashboard(page);
|
|
65
|
+
await openCostMonitor(page, false);
|
|
66
|
+
const retry = page.locator(".app-loading-panel-error").getByRole("button", {
|
|
67
|
+
name: "Tentar novamente",
|
|
68
|
+
});
|
|
69
|
+
await expect(retry).toBeVisible();
|
|
70
|
+
|
|
71
|
+
await retry.click();
|
|
72
|
+
await expectCostMonitorReady(page);
|
|
73
|
+
await page.waitForTimeout(750);
|
|
74
|
+
expect(summaries).toBe(2);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test("does not let a late all-time summary overwrite the newer period", async ({ page }) => {
|
|
78
|
+
let priorPeriodBody: string | null = null;
|
|
79
|
+
page.on("response", (response) => {
|
|
80
|
+
const url = new URL(response.url());
|
|
81
|
+
if (
|
|
82
|
+
response.status() === 200 &&
|
|
83
|
+
url.pathname === COST_MONITOR_PATH &&
|
|
84
|
+
url.searchParams.get("scope") === "summary" &&
|
|
85
|
+
url.searchParams.has("from")
|
|
86
|
+
)
|
|
87
|
+
void response.text().then((body) => {
|
|
88
|
+
priorPeriodBody ??= body;
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
await page.addInitScript(() => {
|
|
92
|
+
type Probe = {
|
|
93
|
+
allTimeBody: string | null;
|
|
94
|
+
allTimeFinished: boolean;
|
|
95
|
+
allTimeStarted: boolean;
|
|
96
|
+
releaseAllTime: null | (() => Promise<void>);
|
|
97
|
+
};
|
|
98
|
+
const probe: Probe = {
|
|
99
|
+
allTimeBody: null,
|
|
100
|
+
allTimeFinished: false,
|
|
101
|
+
allTimeStarted: false,
|
|
102
|
+
releaseAllTime: null,
|
|
103
|
+
};
|
|
104
|
+
const originalFetch = window.fetch.bind(window);
|
|
105
|
+
Object.assign(window, { __costSummaryStaleProbe: probe });
|
|
106
|
+
window.fetch = (input, init) => {
|
|
107
|
+
const url = new URL(
|
|
108
|
+
typeof input === "string" ? input : input instanceof URL ? input.href : input.url,
|
|
109
|
+
window.location.href,
|
|
110
|
+
);
|
|
111
|
+
const isAllTimeSummary =
|
|
112
|
+
url.pathname === "/api/v2/cost-monitor" &&
|
|
113
|
+
url.searchParams.get("scope") === "summary" &&
|
|
114
|
+
!url.searchParams.has("from") &&
|
|
115
|
+
!url.searchParams.has("to");
|
|
116
|
+
if (!isAllTimeSummary || probe.allTimeStarted) return originalFetch(input, init);
|
|
117
|
+
|
|
118
|
+
probe.allTimeStarted = true;
|
|
119
|
+
return new Promise<Response>((resolve, reject) => {
|
|
120
|
+
probe.releaseAllTime = async () => {
|
|
121
|
+
try {
|
|
122
|
+
resolve(
|
|
123
|
+
new Response(probe.allTimeBody, {
|
|
124
|
+
headers: { "content-type": "application/json" },
|
|
125
|
+
status: 200,
|
|
126
|
+
}),
|
|
127
|
+
);
|
|
128
|
+
} catch (error) {
|
|
129
|
+
reject(error);
|
|
130
|
+
} finally {
|
|
131
|
+
probe.allTimeFinished = true;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await openDashboard(page);
|
|
139
|
+
await openCostMonitor(page);
|
|
140
|
+
await expect.poll(() => priorPeriodBody).not.toBeNull();
|
|
141
|
+
const staleBody = withStaleSummarySentinel(priorPeriodBody!);
|
|
142
|
+
await page.evaluate((body) => {
|
|
143
|
+
window.__costSummaryStaleProbe.allTimeBody = body;
|
|
144
|
+
}, staleBody);
|
|
145
|
+
|
|
146
|
+
await periodControl(page).selectOption("all");
|
|
147
|
+
await expect
|
|
148
|
+
.poll(() => page.evaluate(() => window.__costSummaryStaleProbe.allTimeStarted))
|
|
149
|
+
.toBe(true);
|
|
150
|
+
|
|
151
|
+
const latestSummary = waitForCostSummary(page, "this-month");
|
|
152
|
+
await periodControl(page).selectOption("this-month");
|
|
153
|
+
await page.evaluate(async () => await window.__costSummaryStaleProbe.releaseAllTime?.());
|
|
154
|
+
await expect
|
|
155
|
+
.poll(() => page.evaluate(() => window.__costSummaryStaleProbe.allTimeFinished))
|
|
156
|
+
.toBe(true);
|
|
157
|
+
await latestSummary;
|
|
158
|
+
await expect(periodControl(page)).toHaveValue("this-month");
|
|
159
|
+
await expectCostMonitorReady(page);
|
|
160
|
+
await expect(page.locator(".cost-content-daily")).not.toContainText(STALE_SUMMARY_SENTINEL);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
declare global {
|
|
164
|
+
interface Window {
|
|
165
|
+
__costSummaryStaleProbe: {
|
|
166
|
+
allTimeBody: string | null;
|
|
167
|
+
allTimeFinished: boolean;
|
|
168
|
+
allTimeStarted: boolean;
|
|
169
|
+
releaseAllTime: null | (() => Promise<void>);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async function openDashboard(page: Page) {
|
|
175
|
+
const operations = page.waitForResponse(
|
|
176
|
+
(response) =>
|
|
177
|
+
response.status() === 200 &&
|
|
178
|
+
response.url().includes("/api/v2/operations") &&
|
|
179
|
+
response.url().includes("scope=quick"),
|
|
180
|
+
);
|
|
181
|
+
await page.goto("/?token=e2e-token");
|
|
182
|
+
await operations;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async function openCostMonitor(page: Page, expectReady = true) {
|
|
186
|
+
const summary = expectReady ? waitForCostSummary(page, "this-month") : null;
|
|
187
|
+
await page
|
|
188
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
189
|
+
.getByRole("button", { name: /Monitor de Custos/ })
|
|
190
|
+
.click();
|
|
191
|
+
if (!expectReady) return;
|
|
192
|
+
await summary;
|
|
193
|
+
await expectCostMonitorReady(page);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function periodControl(page: Page) {
|
|
197
|
+
return page.locator(".dashboard-global-controls").getByRole("combobox", { name: "Período" });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function waitForCostSummary(page: Page, preset: "all" | "this-month") {
|
|
201
|
+
return page.waitForResponse((response) => {
|
|
202
|
+
if (response.status() !== 200) return false;
|
|
203
|
+
const url = new URL(response.url());
|
|
204
|
+
if (url.pathname !== COST_MONITOR_PATH || url.searchParams.get("scope") !== "summary")
|
|
205
|
+
return false;
|
|
206
|
+
return preset === "all"
|
|
207
|
+
? !url.searchParams.has("from") && !url.searchParams.has("to")
|
|
208
|
+
: url.searchParams.has("from") && url.searchParams.has("to");
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function expectCostMonitorReady(page: Page) {
|
|
213
|
+
await expect(page.locator(".cost-tabs")).toBeVisible({ timeout: fiveSeconds });
|
|
214
|
+
const dailyTable = page.locator(".cost-content-daily table");
|
|
215
|
+
await expect(dailyTable).toBeVisible({ timeout: fiveSeconds });
|
|
216
|
+
await expect(dailyTable).not.toHaveAttribute("aria-busy", "true");
|
|
217
|
+
await expect(page.locator(".cost-content-daily tbody tr").first()).toBeVisible({
|
|
218
|
+
timeout: fiveSeconds,
|
|
219
|
+
});
|
|
220
|
+
await expect(page.locator(".app-loading-panel[aria-busy='true']")).toHaveCount(0);
|
|
221
|
+
await expect(page.locator(".period-transition-overlay")).toHaveCount(0);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function withStaleSummarySentinel(body: string) {
|
|
225
|
+
const projection = JSON.parse(body) as {
|
|
226
|
+
groups?: { daily?: Array<{ key: string; label: string }> };
|
|
227
|
+
};
|
|
228
|
+
const firstDailyRow = projection.groups?.daily?.[0];
|
|
229
|
+
if (firstDailyRow === undefined)
|
|
230
|
+
throw new Error(
|
|
231
|
+
"The Cost summary fixture must contain a daily row for stale-response detection.",
|
|
232
|
+
);
|
|
233
|
+
projection.groups!.daily = [
|
|
234
|
+
{ ...firstDailyRow, key: STALE_SUMMARY_SENTINEL, label: STALE_SUMMARY_SENTINEL },
|
|
235
|
+
...projection.groups!.daily!,
|
|
236
|
+
];
|
|
237
|
+
return JSON.stringify(projection);
|
|
238
|
+
}
|
|
@@ -174,6 +174,10 @@ async function openDashboard(page: Page) {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
async function selectPeriod(page: Page, period: "this-month" | "all") {
|
|
177
|
+
await page
|
|
178
|
+
.getByRole("navigation", { name: "Áreas do dashboard" })
|
|
179
|
+
.getByRole("button", { name: /Operação/ })
|
|
180
|
+
.click();
|
|
177
181
|
const control = page
|
|
178
182
|
.locator(".dashboard-global-controls")
|
|
179
183
|
.getByRole("combobox", { name: "Período" });
|
|
@@ -163,7 +163,12 @@ test("exports Kanban, Lista and Cost Monitor through a save picker while the das
|
|
|
163
163
|
});
|
|
164
164
|
|
|
165
165
|
test("renders the modern read-only dashboard and meets accessibility gates", async ({ page }) => {
|
|
166
|
-
await openDashboard(page);
|
|
166
|
+
const initialResponse = await openDashboard(page);
|
|
167
|
+
const globalPeriod = page.getByRole("combobox", { name: /Per/ }).first();
|
|
168
|
+
await expect(globalPeriod).toHaveValue("all");
|
|
169
|
+
const initialUrl = new URL(initialResponse.url());
|
|
170
|
+
expect(initialUrl.searchParams.has("from")).toBe(false);
|
|
171
|
+
expect(initialUrl.searchParams.has("to")).toBe(false);
|
|
167
172
|
await expect(page.getByRole("navigation", { name: "Dashboard Zaip AIOS menu" })).toBeVisible();
|
|
168
173
|
const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
|
|
169
174
|
const globalControls = page.locator(".dashboard-global-controls");
|
|
@@ -179,6 +184,13 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
179
184
|
buttons.map((button) => button.getBoundingClientRect().top),
|
|
180
185
|
);
|
|
181
186
|
expect(new Set(buttonBoxes.map((top) => Math.round(top))).size).toBe(1);
|
|
187
|
+
await expect(globalControls.getByRole("textbox", { name: "Busca" })).toBeHidden();
|
|
188
|
+
await expect(globalControls.getByLabel("Data inicial")).toBeHidden();
|
|
189
|
+
await expect(globalControls.getByLabel("Data final")).toBeHidden();
|
|
190
|
+
await expect(globalControls.getByRole("combobox", { name: "Período" })).toBeHidden();
|
|
191
|
+
await expect(page.getByLabel("Kanban de trabalho")).toHaveCount(0);
|
|
192
|
+
await expect(page.locator(".operations-task-card")).toHaveCount(0);
|
|
193
|
+
await selectOperations(page);
|
|
182
194
|
const searchInput = globalControls.getByRole("textbox", { name: "Busca" });
|
|
183
195
|
await expect(searchInput).toBeVisible();
|
|
184
196
|
await expect(globalControls.getByRole("button", { name: "Limpar busca" })).toHaveCount(0);
|
|
@@ -204,9 +216,6 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
204
216
|
elements.map((element) => Math.round(element.getBoundingClientRect().bottom)),
|
|
205
217
|
);
|
|
206
218
|
expect(new Set(controlRowBottoms).size).toBe(1);
|
|
207
|
-
await expect(page.getByLabel("Kanban de trabalho")).toHaveCount(0);
|
|
208
|
-
await expect(page.locator(".operations-task-card")).toHaveCount(0);
|
|
209
|
-
await selectOperations(page);
|
|
210
219
|
await expect(page.getByLabel("Kanban de trabalho")).toBeVisible();
|
|
211
220
|
await expect(page.locator(".operations-risk.is-standard").first()).toBeVisible();
|
|
212
221
|
const visibleCards = page.locator(".operations-task-card:visible");
|
|
@@ -233,7 +242,7 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
233
242
|
const period = globalControls.getByRole("combobox", { name: "Período" });
|
|
234
243
|
await expect(startDate).toHaveValue(/^\d{4}-\d{2}-\d{2}$/);
|
|
235
244
|
await expect(endDate).toHaveValue(/^\d{4}-\d{2}-\d{2}$/);
|
|
236
|
-
await expect(period).toHaveValue("
|
|
245
|
+
await expect(period).toHaveValue("all");
|
|
237
246
|
const initialStart = await startDate.inputValue();
|
|
238
247
|
const today = await endDate.inputValue();
|
|
239
248
|
await expect(page.getByLabel("Data inicial nos filtros")).toHaveValue(initialStart);
|
|
@@ -263,6 +272,13 @@ test("renders the modern read-only dashboard and meets accessibility gates", asy
|
|
|
263
272
|
|
|
264
273
|
const results = await new AxeBuilder({ page }).analyze();
|
|
265
274
|
expect(results.violations).toEqual([]);
|
|
275
|
+
|
|
276
|
+
await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
|
|
277
|
+
await expect(page.locator(".cost-tabs")).toBeVisible();
|
|
278
|
+
await expect(searchInput).toBeVisible();
|
|
279
|
+
await expect(startDate).toBeVisible();
|
|
280
|
+
await expect(endDate).toBeVisible();
|
|
281
|
+
await expect(period).toBeVisible();
|
|
266
282
|
});
|
|
267
283
|
|
|
268
284
|
test("loads a quick Operations summary without full snapshot hydration", async ({ page }) => {
|
|
@@ -274,6 +290,7 @@ test("loads a quick Operations summary without full snapshot hydration", async (
|
|
|
274
290
|
});
|
|
275
291
|
|
|
276
292
|
await openDashboard(page);
|
|
293
|
+
await selectPopulatedAllTime(page);
|
|
277
294
|
expect(operationsRequests.some((url) => url.includes("scope=quick"))).toBe(true);
|
|
278
295
|
expect(operationsRequests.some((url) => url.includes("scope=detail"))).toBe(false);
|
|
279
296
|
expect(operationsRequests.some((url) => url.includes("/snapshot"))).toBe(false);
|
|
@@ -403,6 +420,7 @@ test("embeds traceability and keeps global settings and help available", async (
|
|
|
403
420
|
|
|
404
421
|
test("groups macro work in All and reveals descendant Tasks only in Tasks", async ({ page }) => {
|
|
405
422
|
await openDashboard(page);
|
|
423
|
+
await selectPopulatedAllTime(page);
|
|
406
424
|
await selectOperations(page);
|
|
407
425
|
const board = page.getByLabel("Kanban de trabalho");
|
|
408
426
|
await expect(board.locator(".operations-task-card").first()).toBeVisible();
|
|
@@ -448,10 +466,14 @@ test("groups macro work in All and reveals descendant Tasks only in Tasks", asyn
|
|
|
448
466
|
}
|
|
449
467
|
});
|
|
450
468
|
|
|
451
|
-
test("shows the observed
|
|
469
|
+
test("shows the observed all-time macro duration consistently for SPEC and PRD", async ({
|
|
452
470
|
page,
|
|
453
471
|
}) => {
|
|
454
472
|
const response = await openDashboard(page);
|
|
473
|
+
await expect(page.getByRole("combobox", { name: /Per/ }).first()).toHaveValue("all");
|
|
474
|
+
const responseUrl = new URL(response.url());
|
|
475
|
+
expect(responseUrl.searchParams.has("from")).toBe(false);
|
|
476
|
+
expect(responseUrl.searchParams.has("to")).toBe(false);
|
|
455
477
|
const projection = (await response.json()) as {
|
|
456
478
|
summary: {
|
|
457
479
|
overview_metrics?: {
|
|
@@ -466,8 +488,6 @@ test("shows the observed default-month macro duration consistently for SPEC and
|
|
|
466
488
|
};
|
|
467
489
|
};
|
|
468
490
|
};
|
|
469
|
-
await expect(page.getByRole("combobox", { name: /Per/ }).first()).toHaveValue("this-month");
|
|
470
|
-
|
|
471
491
|
for (const kind of ["SPECs", "PRDs"] as const) {
|
|
472
492
|
const macroKey = kind === "SPECs" ? "specs" : "prds";
|
|
473
493
|
const macroMetric = projection.summary.overview_metrics?.macro_metrics?.[macroKey];
|
|
@@ -521,20 +541,26 @@ test("loads monthly overview quickly and keeps PRD and SPEC chart totals reconci
|
|
|
521
541
|
page,
|
|
522
542
|
}) => {
|
|
523
543
|
const openedAt = Date.now();
|
|
524
|
-
await openDashboard(page);
|
|
544
|
+
const initialQuickResponse = await openDashboard(page);
|
|
525
545
|
expect(Date.now() - openedAt).toBeLessThan(3_000);
|
|
546
|
+
await selectOperations(page);
|
|
526
547
|
|
|
527
548
|
const period = page.getByRole("combobox", { name: /Per/ }).first();
|
|
528
549
|
for (const preset of ["all", "this-month"] as const) {
|
|
529
|
-
const response = page.waitForResponse(
|
|
530
|
-
(candidate) =>
|
|
531
|
-
candidate.status() === 200 &&
|
|
532
|
-
candidate.url().includes("/api/v2/operations") &&
|
|
533
|
-
candidate.url().includes("scope=quick"),
|
|
534
|
-
);
|
|
535
550
|
const selectedAt = Date.now();
|
|
536
|
-
|
|
537
|
-
|
|
551
|
+
const quickResponse =
|
|
552
|
+
preset === "all"
|
|
553
|
+
? initialQuickResponse
|
|
554
|
+
: await (async () => {
|
|
555
|
+
const response = page.waitForResponse(
|
|
556
|
+
(candidate) =>
|
|
557
|
+
candidate.status() === 200 &&
|
|
558
|
+
candidate.url().includes("/api/v2/operations") &&
|
|
559
|
+
candidate.url().includes("scope=quick"),
|
|
560
|
+
);
|
|
561
|
+
await period.selectOption(preset);
|
|
562
|
+
return response;
|
|
563
|
+
})();
|
|
538
564
|
const quickUrl = new URL(quickResponse.url());
|
|
539
565
|
const dateControls = page.locator(".dashboard-global-controls");
|
|
540
566
|
const expectedFrom =
|
|
@@ -654,6 +680,23 @@ async function openDashboard(page: import("@playwright/test").Page) {
|
|
|
654
680
|
return operations;
|
|
655
681
|
}
|
|
656
682
|
|
|
683
|
+
async function selectPopulatedAllTime(page: import("@playwright/test").Page) {
|
|
684
|
+
const response = page.waitForResponse((candidate) => {
|
|
685
|
+
if (
|
|
686
|
+
candidate.status() !== 200 ||
|
|
687
|
+
!candidate.url().includes("/api/v2/operations") ||
|
|
688
|
+
!candidate.url().includes("scope=quick")
|
|
689
|
+
) {
|
|
690
|
+
return false;
|
|
691
|
+
}
|
|
692
|
+
const url = new URL(candidate.url());
|
|
693
|
+
return !url.searchParams.has("from") && !url.searchParams.has("to");
|
|
694
|
+
});
|
|
695
|
+
await selectOperations(page);
|
|
696
|
+
await page.getByRole("combobox", { name: /Per/ }).first().selectOption("all");
|
|
697
|
+
return response;
|
|
698
|
+
}
|
|
699
|
+
|
|
657
700
|
async function selectOperations(page: import("@playwright/test").Page) {
|
|
658
701
|
await page
|
|
659
702
|
.getByRole("navigation", { name: "Áreas do dashboard" })
|