start-vibing 4.3.4 → 4.4.0

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.
Files changed (44) hide show
  1. package/package.json +2 -2
  2. package/template/.claude/agents/sd-audit.md +32 -0
  3. package/template/.claude/skills/e2e-audit/DESIGN.md +294 -0
  4. package/template/.claude/skills/e2e-audit/SKILL.md +660 -0
  5. package/template/.claude/skills/e2e-audit/e2e/fixtures/auth.setup.ts +70 -0
  6. package/template/.claude/skills/e2e-audit/e2e/fixtures/auth.ts +21 -0
  7. package/template/.claude/skills/e2e-audit/e2e/fixtures/base.ts +90 -0
  8. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/.gitkeep +0 -0
  9. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/admin.json +50 -0
  10. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/manager.json +50 -0
  11. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/member.json +50 -0
  12. package/template/.claude/skills/e2e-audit/e2e/fixtures/storage/owner.json +50 -0
  13. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-admin.page.ts +141 -0
  14. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-billing.page.ts +47 -0
  15. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-chat.page.ts +35 -0
  16. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-home.page.ts +134 -0
  17. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-integrations.page.ts +334 -0
  18. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-knowledge.page.ts +30 -0
  19. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-ontology.page.ts +71 -0
  20. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-profile.page.ts +38 -0
  21. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-teams.page.ts +123 -0
  22. package/template/.claude/skills/e2e-audit/e2e/pages/dashboard-transcripts.page.ts +109 -0
  23. package/template/.claude/skills/e2e-audit/e2e/specs/auth/login.spec.ts +59 -0
  24. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-admin.spec.ts +233 -0
  25. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-billing.spec.ts +44 -0
  26. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-chat.spec.ts +50 -0
  27. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-home.spec.ts +243 -0
  28. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-integrations.spec.ts +472 -0
  29. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-knowledge.spec.ts +57 -0
  30. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-ontology.spec.ts +72 -0
  31. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-profile.spec.ts +48 -0
  32. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-teams.spec.ts +247 -0
  33. package/template/.claude/skills/e2e-audit/e2e/specs/dashboard-transcripts.spec.ts +122 -0
  34. package/template/.claude/skills/e2e-audit/e2e/specs/security/headers.spec.ts +39 -0
  35. package/template/.claude/skills/e2e-audit/e2e/specs/security/rbac.spec.ts +92 -0
  36. package/template/.claude/skills/e2e-audit/e2e/specs/security/xss.spec.ts +74 -0
  37. package/template/.claude/skills/e2e-audit/e2e/utils/console-collector.ts +89 -0
  38. package/template/.claude/skills/e2e-audit/e2e/utils/security-helpers.ts +114 -0
  39. package/template/.claude/skills/e2e-audit/e2e/utils/test-data.ts +64 -0
  40. package/template/.claude/skills/e2e-audit/runbook.md +115 -0
  41. package/template/.claude/skills/super-design/SKILL.md +42 -4
  42. package/template/.claude/skills/super-design/scripts/discover-surfaces.sh +197 -0
  43. package/template/.claude/skills/super-design/scripts/extract-project-rules.sh +240 -0
  44. package/template/.claude/skills/super-design/scripts/verify-audit.sh +34 -1
@@ -0,0 +1,472 @@
1
+ import { test, expect } from '../fixtures/base'
2
+ import {
3
+ DashboardIntegrationsPage,
4
+ CreateIntegrationPage,
5
+ IntegrationDetailPage,
6
+ } from '../pages/dashboard-integrations.page'
7
+ import { createConsoleCollector } from '../utils/console-collector'
8
+
9
+ test.describe('Dashboard Integrations - Page Load @smoke', () => {
10
+ let integrations: DashboardIntegrationsPage
11
+
12
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
13
+ integrations = new DashboardIntegrationsPage(authenticatedPage)
14
+ await integrations.goto()
15
+ await integrations.waitForLoad()
16
+ })
17
+
18
+ test('displays page heading and subheading', async () => {
19
+ await expect(integrations.heading, 'Page heading should be visible after load').toBeVisible()
20
+ await expect(integrations.subheading, 'Page subheading should be visible').toBeVisible()
21
+ })
22
+
23
+ test('displays header card with usage meter', async () => {
24
+ await expect(integrations.headerCardTitle, 'Usage header card should render').toBeVisible()
25
+ await expect(integrations.usageMeter, 'Usage meter should show integration count').toBeVisible()
26
+ })
27
+
28
+ test('displays add integration button', async () => {
29
+ await expect(integrations.addIntegrationButton, 'Add integration CTA should be visible').toBeVisible()
30
+ })
31
+
32
+ test('displays search and filter toolbar', async () => {
33
+ await expect(integrations.searchInput, 'Search input should be visible').toBeVisible()
34
+ await expect(integrations.refreshButton, 'Refresh button should be visible').toBeVisible()
35
+ await expect(integrations.statusFilter, 'Status filter should be visible').toBeVisible()
36
+ await expect(integrations.typeFilter, 'Type filter should be visible').toBeVisible()
37
+ })
38
+
39
+ test('displays integration cards', async () => {
40
+ const count = await integrations.getCardCount()
41
+ expect(count, 'At least 1 integration card should render (is integration.list returning data?)').toBeGreaterThan(0)
42
+ })
43
+
44
+ test('displays pagination text', async () => {
45
+ await expect(integrations.paginationText, 'Pagination text should show total count').toBeVisible()
46
+ })
47
+ })
48
+
49
+ test.describe('Dashboard Integrations - Search @functional', () => {
50
+ let integrations: DashboardIntegrationsPage
51
+
52
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
53
+ integrations = new DashboardIntegrationsPage(authenticatedPage)
54
+ await integrations.goto()
55
+ await integrations.waitForLoad()
56
+ })
57
+
58
+ test('search filters results by name', async ({ authenticatedPage }) => {
59
+ const initialCount = await integrations.getCardCount()
60
+
61
+ await integrations.search('slack')
62
+ await authenticatedPage.waitForTimeout(1000)
63
+
64
+ const filteredCount = await integrations.getCardCount()
65
+ expect(filteredCount, 'Search for "slack" should return at least 1 result').toBeGreaterThan(0)
66
+ expect(filteredCount, 'Filtered count should be <= initial count').toBeLessThanOrEqual(initialCount)
67
+
68
+ await expect(authenticatedPage, 'URL should contain search param').toHaveURL(/search=slack/)
69
+ })
70
+
71
+ test('search shows empty state for non-matching query', async ({ authenticatedPage }) => {
72
+ await integrations.search('xyznonexistent123')
73
+ await authenticatedPage.waitForTimeout(1000)
74
+
75
+ await expect(integrations.emptyStateHeading, 'Empty state should show for non-matching search').toBeVisible()
76
+ await expect(integrations.emptyStateText, 'Empty state description should be visible').toBeVisible()
77
+ })
78
+
79
+ test('clear filters resets search', async ({ authenticatedPage }) => {
80
+ await integrations.search('slack')
81
+ await authenticatedPage.waitForTimeout(1000)
82
+
83
+ const filteredCount = await integrations.getCardCount()
84
+
85
+ await integrations.clearFilters()
86
+ await authenticatedPage.waitForTimeout(1000)
87
+
88
+ const resetCount = await integrations.getCardCount()
89
+ expect(resetCount, 'Clearing filters should restore all integrations').toBeGreaterThanOrEqual(filteredCount)
90
+
91
+ await expect(authenticatedPage, 'URL should not contain search param after clear').not.toHaveURL(/search=/)
92
+ })
93
+ })
94
+
95
+ test.describe('Dashboard Integrations - Status Filter @functional', () => {
96
+ let integrations: DashboardIntegrationsPage
97
+
98
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
99
+ integrations = new DashboardIntegrationsPage(authenticatedPage)
100
+ await integrations.goto()
101
+ await integrations.waitForLoad()
102
+ })
103
+
104
+ test('status filter "Ativo" shows active integrations', async () => {
105
+ const initialCount = await integrations.getCardCount()
106
+
107
+ await integrations.selectStatusFilter('Ativo')
108
+
109
+ const activeCount = await integrations.getCardCount()
110
+ expect(activeCount, 'Active filter should show at least 1 active integration').toBeGreaterThan(0)
111
+ expect(activeCount, 'Active count should be <= total count').toBeLessThanOrEqual(initialCount)
112
+ })
113
+
114
+ test('status filter "Pausado" shows empty state when none paused', async () => {
115
+ await integrations.selectStatusFilter('Pausado')
116
+
117
+ await expect(integrations.emptyStateHeading, 'No paused integrations should show empty state').toBeVisible()
118
+ })
119
+
120
+ test('status filter "Erro" shows empty state when none errored', async () => {
121
+ await integrations.selectStatusFilter('Erro')
122
+
123
+ await expect(integrations.emptyStateHeading, 'No errored integrations should show empty state').toBeVisible()
124
+ })
125
+
126
+ test('status filter "Reconexão Necessária" shows empty state', async () => {
127
+ await integrations.selectStatusFilter('Reconexão Necessária')
128
+
129
+ await expect(integrations.emptyStateHeading, 'No disconnected integrations should show empty state').toBeVisible()
130
+ })
131
+
132
+ test('clearing filters after status filter restores all', async () => {
133
+ const initialCount = await integrations.getCardCount()
134
+
135
+ await integrations.selectStatusFilter('Pausado')
136
+ await expect(integrations.emptyStateHeading, 'Paused filter should show empty state').toBeVisible()
137
+
138
+ await integrations.clearFilters()
139
+
140
+ const resetCount = await integrations.getCardCount()
141
+ expect(resetCount, 'Clearing filters should restore original integration count').toBe(initialCount)
142
+ })
143
+ })
144
+
145
+ test.describe('Dashboard Integrations - Navigation @functional', () => {
146
+ let integrations: DashboardIntegrationsPage
147
+
148
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
149
+ integrations = new DashboardIntegrationsPage(authenticatedPage)
150
+ await integrations.goto()
151
+ await integrations.waitForLoad()
152
+ })
153
+
154
+ test('add integration button navigates to /integrations/new', async ({ authenticatedPage }) => {
155
+ await integrations.clickAddIntegration()
156
+ await expect(authenticatedPage, 'Should navigate to new integration page').toHaveURL(/dashboard\/integrations\/new/)
157
+ })
158
+
159
+ test('clicking integration card navigates to detail page', async ({ authenticatedPage }) => {
160
+ const firstCard = integrations.page.getByRole('heading', { level: 3 }).first()
161
+ const cardName = await firstCard.textContent()
162
+ expect(cardName, 'First integration card should have a name').toBeTruthy()
163
+
164
+ await firstCard.click()
165
+ await expect(authenticatedPage, 'Should navigate to integration detail page').toHaveURL(/dashboard\/integrations\/[a-f0-9-]+/)
166
+ })
167
+ })
168
+
169
+ test.describe('Create Integration - Wizard @functional', () => {
170
+ let wizard: CreateIntegrationPage
171
+
172
+ test.beforeEach(async ({ authenticatedPage }) => {
173
+ wizard = new CreateIntegrationPage(authenticatedPage)
174
+ await wizard.goto()
175
+ await wizard.waitForLoad()
176
+ })
177
+
178
+ test('displays page heading and step 1', async () => {
179
+ await expect(wizard.heading, 'Create integration heading should be visible').toBeVisible()
180
+ await expect(wizard.typeSelectionHeading, 'Type selection heading should be visible').toBeVisible()
181
+ await expect(wizard.stepIndicator, 'Step indicator should show step 1').toContainText(/1/)
182
+ })
183
+
184
+ test('displays all integration type options', async () => {
185
+ await expect(wizard.uploadButton, 'Upload type option should be visible').toBeVisible()
186
+ await expect(wizard.googleDriveButton, 'Google Drive option should be visible').toBeVisible()
187
+ await expect(wizard.oneDriveButton, 'OneDrive option should be visible').toBeVisible()
188
+ await expect(wizard.notionButton, 'Notion option should be visible').toBeVisible()
189
+ await expect(wizard.slackButton, 'Slack option should be visible').toBeVisible()
190
+ await expect(wizard.githubButton, 'GitHub option should be visible').toBeVisible()
191
+ })
192
+
193
+ test('Google Meet button is disabled with "Em Breve" badge', async () => {
194
+ await expect(wizard.googleMeetButton, 'Google Meet button should be visible').toBeVisible()
195
+ await expect(wizard.googleMeetButton, 'Google Meet should be disabled (coming soon)').toBeDisabled()
196
+ })
197
+
198
+ test('next button is disabled until type selected', async () => {
199
+ await expect(wizard.nextButton, 'Next button should be disabled before selecting a type').toBeDisabled()
200
+
201
+ await wizard.selectType('upload')
202
+
203
+ await expect(wizard.nextButton, 'Next button should be enabled after selecting upload type').toBeEnabled()
204
+ })
205
+
206
+ test('cancel button navigates back to integrations', async ({ authenticatedPage }) => {
207
+ await wizard.clickCancel()
208
+ await expect(authenticatedPage, 'Cancel should return to integrations list').toHaveURL(/dashboard\/integrations$/)
209
+ })
210
+
211
+ test('back link navigates to integrations list', async ({ authenticatedPage }) => {
212
+ await wizard.backButton.click()
213
+ await expect(authenticatedPage, 'Back link should return to integrations list').toHaveURL(/dashboard\/integrations$/)
214
+ })
215
+
216
+ test('displays integration limit card', async () => {
217
+ await expect(wizard.limitHeading, 'Integration limit card heading should be visible').toBeVisible()
218
+ await expect(wizard.limitProgress, 'Integration limit progress bar should be visible').toBeVisible()
219
+ })
220
+
221
+ test('upload type shows 4-step wizard (no auth step)', async () => {
222
+ await wizard.selectType('upload')
223
+ await wizard.clickNext()
224
+
225
+ await expect(wizard.stepIndicator, 'Should advance to step 2 after selecting upload').toContainText(/2/)
226
+
227
+ const nameInput = wizard.page.getByRole('textbox', { name: /nome|name/i })
228
+ await expect(nameInput, 'Name input should be visible on step 2').toBeVisible()
229
+ })
230
+
231
+ test('google drive type shows 5-step wizard', async () => {
232
+ await wizard.selectType('google-drive')
233
+ await wizard.clickNext()
234
+
235
+ await expect(wizard.stepIndicator, 'Should advance to step 2 for Google Drive').toContainText(/2/)
236
+ })
237
+
238
+ test('wizard step navigation with previous button', async () => {
239
+ await wizard.selectType('upload')
240
+ await wizard.clickNext()
241
+ await expect(wizard.stepIndicator, 'Should be on step 2').toContainText(/2/)
242
+
243
+ await wizard.clickPrevious()
244
+ await expect(wizard.stepIndicator, 'Previous should go back to step 1').toContainText(/1/)
245
+ })
246
+ })
247
+
248
+ test.describe('Integration Detail - Tabs @functional', () => {
249
+ test('detail page shows integration name and action buttons', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
250
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
251
+ await integrations.goto()
252
+ await integrations.waitForLoad()
253
+
254
+ const firstCard = authenticatedPage.getByRole('heading', { level: 3 }).first()
255
+ await firstCard.click()
256
+ await authenticatedPage.waitForURL(/dashboard\/integrations\/[a-f0-9-]+/)
257
+
258
+ const detail = new IntegrationDetailPage(authenticatedPage)
259
+ await detail.waitForLoad()
260
+
261
+ await expect(detail.integrationName, 'Integration name should be visible on detail page').toBeVisible()
262
+ await expect(detail.syncNowButton, 'Sync Now button should be visible').toBeVisible()
263
+ await expect(detail.statusBadge, 'Status badge should be visible').toBeVisible()
264
+ })
265
+
266
+ test('tabs switch content and update URL', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
267
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
268
+ await integrations.goto()
269
+ await integrations.waitForLoad()
270
+
271
+ const firstCard = authenticatedPage.getByRole('heading', { level: 3 }).first()
272
+ await firstCard.click()
273
+ await authenticatedPage.waitForURL(/dashboard\/integrations\/[a-f0-9-]+/)
274
+
275
+ const detail = new IntegrationDetailPage(authenticatedPage)
276
+ await detail.waitForLoad()
277
+
278
+ await detail.clickTab('history')
279
+ await expect(authenticatedPage, 'History tab should update URL to ?tab=timeline').toHaveURL(/tab=timeline/)
280
+
281
+ await detail.clickTab('settings')
282
+ await expect(authenticatedPage, 'Settings tab should update URL to ?tab=settings').toHaveURL(/tab=settings/)
283
+
284
+ await detail.clickTab('files')
285
+ await expect(authenticatedPage, 'Files tab should remove tab param from URL').not.toHaveURL(/tab=/)
286
+ })
287
+
288
+ test('back button returns to integrations list', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
289
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
290
+ await integrations.goto()
291
+ await integrations.waitForLoad()
292
+
293
+ const firstCard = authenticatedPage.getByRole('heading', { level: 3 }).first()
294
+ await firstCard.click()
295
+ await authenticatedPage.waitForURL(/dashboard\/integrations\/[a-f0-9-]+/)
296
+
297
+ const detail = new IntegrationDetailPage(authenticatedPage)
298
+ await detail.waitForLoad()
299
+
300
+ await detail.backButton.click()
301
+ await expect(authenticatedPage, 'Back button should return to integrations list').toHaveURL(/dashboard\/integrations$/)
302
+ })
303
+ })
304
+
305
+ test.describe('Create Integration - Name Validation @functional', () => {
306
+ let wizard: CreateIntegrationPage
307
+
308
+ test.beforeEach(async ({ authenticatedPage }) => {
309
+ wizard = new CreateIntegrationPage(authenticatedPage)
310
+ await wizard.goto()
311
+ await wizard.waitForLoad()
312
+ await wizard.selectType('upload')
313
+ await wizard.clickNext()
314
+ })
315
+
316
+ test('next button disabled with empty name', async () => {
317
+ await expect(wizard.nextButton, 'Next should be disabled when name is empty').toBeDisabled()
318
+ })
319
+
320
+ test('shows error for name shorter than 3 characters', async ({ authenticatedPage }) => {
321
+ const nameInput = authenticatedPage.getByRole('textbox', { name: /nome|name/i })
322
+ await nameInput.fill('ab')
323
+ await authenticatedPage.waitForTimeout(500)
324
+
325
+ await expect(
326
+ authenticatedPage.getByText(/pelo menos 3 caracteres|at least 3 characters/i),
327
+ 'Should show min length validation error',
328
+ ).toBeVisible()
329
+ })
330
+
331
+ test('shows available message for valid unique name', async ({ authenticatedPage }) => {
332
+ const nameInput = authenticatedPage.getByRole('textbox', { name: /nome|name/i })
333
+ await nameInput.fill('E2E Test Unique Name ' + Date.now())
334
+ await authenticatedPage.waitForTimeout(1500)
335
+
336
+ await expect(
337
+ authenticatedPage.getByText(/nome disponível|name available/i),
338
+ 'Should confirm name is available (is checkNameAvailability API working?)',
339
+ ).toBeVisible()
340
+ await expect(wizard.nextButton, 'Next should be enabled with valid name').toBeEnabled()
341
+ })
342
+ })
343
+
344
+ test.describe('Create Integration - Upload Step 3 @functional', () => {
345
+ test('upload wizard step 3 shows file drop zone', async ({ authenticatedPage }) => {
346
+ const wizard = new CreateIntegrationPage(authenticatedPage)
347
+ await wizard.goto()
348
+ await wizard.waitForLoad()
349
+
350
+ await wizard.selectType('upload')
351
+ await wizard.clickNext()
352
+
353
+ const nameInput = authenticatedPage.getByRole('textbox', { name: /nome|name/i })
354
+ await nameInput.fill('E2E Upload Step3 Test ' + Date.now())
355
+ await authenticatedPage.waitForTimeout(1500)
356
+ await wizard.clickNext()
357
+
358
+ await expect(
359
+ authenticatedPage.getByRole('heading', { name: /upload de arquivos/i }),
360
+ 'Upload step 3 heading should be visible',
361
+ ).toBeVisible()
362
+ await expect(
363
+ authenticatedPage.getByText(/arraste e solte/i),
364
+ 'Drag and drop text should be visible',
365
+ ).toBeVisible()
366
+ await expect(
367
+ authenticatedPage.getByRole('button', { name: /procurar arquivos/i }),
368
+ 'Browse files button should be visible',
369
+ ).toBeVisible()
370
+ await expect(
371
+ authenticatedPage.getByRole('button', { name: /procurar pasta/i }),
372
+ 'Browse folder button should be visible',
373
+ ).toBeVisible()
374
+ await expect(wizard.nextButton, 'Next should be disabled until files are uploaded').toBeDisabled()
375
+ })
376
+ })
377
+
378
+ test.describe('Integration Detail - Settings Tab @functional', () => {
379
+ test('settings tab shows access control with department and team comboboxes', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
380
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
381
+ await integrations.goto()
382
+ await integrations.waitForLoad()
383
+
384
+ const firstCard = authenticatedPage.getByRole('heading', { level: 3 }).first()
385
+ await firstCard.click()
386
+ await authenticatedPage.waitForURL(/dashboard\/integrations\/[a-f0-9-]+/)
387
+
388
+ const detail = new IntegrationDetailPage(authenticatedPage)
389
+ await detail.waitForLoad()
390
+ await detail.clickTab('settings')
391
+
392
+ await expect(
393
+ authenticatedPage.getByRole('heading', { name: /configurações da integração/i }),
394
+ 'Settings heading should be visible',
395
+ ).toBeVisible()
396
+ await expect(detail.newDepartmentButton, 'New department button should be visible').toBeVisible()
397
+ await expect(detail.newTeamButton, 'New team button should be visible').toBeVisible()
398
+ await expect(detail.saveConfigButton, 'Save config button should be visible').toBeVisible()
399
+ })
400
+
401
+ test('new department button opens creation dialog', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
402
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
403
+ await integrations.goto()
404
+ await integrations.waitForLoad()
405
+
406
+ const firstCard = authenticatedPage.getByRole('heading', { level: 3 }).first()
407
+ await firstCard.click()
408
+ await authenticatedPage.waitForURL(/dashboard\/integrations\/[a-f0-9-]+/)
409
+
410
+ const detail = new IntegrationDetailPage(authenticatedPage)
411
+ await detail.waitForLoad()
412
+ await detail.clickTab('settings')
413
+
414
+ await detail.newDepartmentButton.click()
415
+
416
+ const dialog = authenticatedPage.getByRole('dialog', { name: /criar departamento/i })
417
+ await expect(dialog, 'Create department dialog should open').toBeVisible()
418
+ await expect(dialog.getByRole('textbox', { name: /nome/i }), 'Department name input should be visible').toBeVisible()
419
+ await expect(dialog.getByRole('button', { name: /criar departamento/i }), 'Create department button should be visible').toBeVisible()
420
+
421
+ await dialog.getByRole('button', { name: /close/i }).click()
422
+ await expect(dialog, 'Dialog should close after clicking close').not.toBeVisible()
423
+ })
424
+
425
+ test('new team button opens creation dialog', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
426
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
427
+ await integrations.goto()
428
+ await integrations.waitForLoad()
429
+
430
+ const firstCard = authenticatedPage.getByRole('heading', { level: 3 }).first()
431
+ await firstCard.click()
432
+ await authenticatedPage.waitForURL(/dashboard\/integrations\/[a-f0-9-]+/)
433
+
434
+ const detail = new IntegrationDetailPage(authenticatedPage)
435
+ await detail.waitForLoad()
436
+ await detail.clickTab('settings')
437
+
438
+ await detail.newTeamButton.click()
439
+
440
+ const dialog = authenticatedPage.getByRole('dialog', { name: /criar equipe/i })
441
+ await expect(dialog, 'Create team dialog should open').toBeVisible()
442
+ await expect(dialog.getByRole('textbox', { name: /nome/i }), 'Team name input should be visible').toBeVisible()
443
+ await expect(dialog.getByRole('button', { name: /criar equipe/i }), 'Create team button should be visible').toBeVisible()
444
+
445
+ await dialog.getByRole('button', { name: /close/i }).click()
446
+ await expect(dialog, 'Dialog should close after clicking close').not.toBeVisible()
447
+ })
448
+ })
449
+
450
+ test.describe('Dashboard Integrations - Security @security', () => {
451
+ test('no sensitive data in console', async ({ authenticatedPage }) => {
452
+ const collector = createConsoleCollector(authenticatedPage)
453
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
454
+ await integrations.goto()
455
+ await integrations.waitForLoad()
456
+ collector.assertNoSensitiveLeaks()
457
+ })
458
+
459
+ test('search input handles XSS payload safely', async ({ authenticatedPage }) => {
460
+ const integrations = new DashboardIntegrationsPage(authenticatedPage)
461
+ await integrations.goto()
462
+ await integrations.waitForLoad()
463
+
464
+ await integrations.search('<script>alert("xss")</script>')
465
+ await authenticatedPage.waitForTimeout(500)
466
+
467
+ const scriptExecuted = await authenticatedPage.evaluate(() => {
468
+ return (window as Record<string, unknown>).__xss_triggered === true
469
+ })
470
+ expect(scriptExecuted, 'XSS script should NOT execute in search input').toBe(false)
471
+ })
472
+ })
@@ -0,0 +1,57 @@
1
+ import { test, expect } from '../fixtures/base'
2
+ import { DashboardKnowledgePage } from '../pages/dashboard-knowledge.page'
3
+ import { createConsoleCollector } from '../utils/console-collector'
4
+ import { testXssOnInput } from '../utils/security-helpers'
5
+
6
+ test.describe('Dashboard Knowledge @smoke', () => {
7
+ let knowledge: DashboardKnowledgePage
8
+
9
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
10
+ knowledge = new DashboardKnowledgePage(authenticatedPage)
11
+ await knowledge.goto()
12
+ await knowledge.waitForLoad()
13
+ })
14
+
15
+ test('page loads successfully', async ({ authenticatedPage }) => {
16
+ await expect(authenticatedPage, 'Should navigate to knowledge page').toHaveURL(/dashboard\/knowledge/)
17
+ })
18
+
19
+ test('displays page heading', async () => {
20
+ await expect(knowledge.heading).toBeVisible()
21
+ })
22
+
23
+ test('search input is visible', async () => {
24
+ // Search may not exist on empty state, conditional check
25
+ const searchVisible = await knowledge.searchInput.isVisible().catch(() => false)
26
+ if (searchVisible) {
27
+ await expect(knowledge.searchInput).toBeEnabled()
28
+ }
29
+ })
30
+
31
+ test('shows file list or empty state', async () => {
32
+ const hasFiles = await knowledge.fileList.isVisible().catch(() => false)
33
+ const hasEmptyState = await knowledge.emptyState.isVisible().catch(() => false)
34
+ expect(hasFiles || hasEmptyState).toBe(true)
35
+ })
36
+ })
37
+
38
+ test.describe('Dashboard Knowledge - Security @security', () => {
39
+ test('no sensitive data in console', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
40
+ const collector = createConsoleCollector(authenticatedPage)
41
+ const knowledge = new DashboardKnowledgePage(authenticatedPage)
42
+ await knowledge.goto()
43
+ await knowledge.waitForLoad()
44
+ collector.assertNoSensitiveLeaks()
45
+ })
46
+
47
+ test('search input resists XSS', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
48
+ const knowledge = new DashboardKnowledgePage(authenticatedPage)
49
+ await knowledge.goto()
50
+ await knowledge.waitForLoad()
51
+
52
+ const searchVisible = await knowledge.searchInput.isVisible().catch(() => false)
53
+ if (searchVisible) {
54
+ await testXssOnInput(authenticatedPage, knowledge.searchInput)
55
+ }
56
+ })
57
+ })
@@ -0,0 +1,72 @@
1
+ import { test, expect } from '../fixtures/base'
2
+ import { DashboardOntologyPage } from '../pages/dashboard-ontology.page'
3
+ import { createConsoleCollector } from '../utils/console-collector'
4
+
5
+ test.describe('Dashboard Ontology @smoke', () => {
6
+ let ontology: DashboardOntologyPage
7
+
8
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
9
+ ontology = new DashboardOntologyPage(authenticatedPage)
10
+ await ontology.goto()
11
+ await ontology.waitForLoad()
12
+ })
13
+
14
+ test('page loads with heading and description', async ({ authenticatedPage }) => {
15
+ await expect(authenticatedPage, 'Should navigate to ontology page').toHaveURL(/dashboard\/ontology/)
16
+ await expect(ontology.heading, 'Ontology heading should be visible').toBeVisible()
17
+ await expect(ontology.description).toBeVisible()
18
+ })
19
+
20
+ test('shows stats cards', async () => {
21
+ await expect(ontology.entitiesStat).toBeVisible()
22
+ await expect(ontology.factsStat).toBeVisible()
23
+ await expect(ontology.episodesStat).toBeVisible()
24
+ await expect(ontology.entityTypesStat).toBeVisible()
25
+ })
26
+
27
+ test('shows five tabs with graph selected by default', async () => {
28
+ await expect(ontology.graphTab).toBeVisible()
29
+ await expect(ontology.entitiesTab).toBeVisible()
30
+ await expect(ontology.factsTab).toBeVisible()
31
+ await expect(ontology.timelineTab).toBeVisible()
32
+ await expect(ontology.searchTab).toBeVisible()
33
+ await expect(ontology.graphTab).toHaveAttribute('aria-selected', 'true')
34
+ })
35
+
36
+ test('graph tab shows empty state', async () => {
37
+ await expect(ontology.graphEmptyState).toBeVisible()
38
+ })
39
+
40
+ test('tabs switch via URL', async ({ authenticatedPage }) => {
41
+ await ontology.switchTab('entities')
42
+ await expect(authenticatedPage).toHaveURL(/tab=entities/)
43
+
44
+ await ontology.switchTab('facts')
45
+ await expect(authenticatedPage).toHaveURL(/tab=facts/)
46
+
47
+ await ontology.switchTab('timeline')
48
+ await expect(authenticatedPage).toHaveURL(/tab=timeline/)
49
+
50
+ await ontology.switchTab('search')
51
+ await expect(authenticatedPage).toHaveURL(/tab=search/)
52
+ })
53
+ })
54
+
55
+ test.describe('Dashboard Ontology - Search Tab', () => {
56
+ test('shows search input', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
57
+ const ontology = new DashboardOntologyPage(authenticatedPage)
58
+ await ontology.goto('search')
59
+ await ontology.waitForLoad()
60
+ await expect(ontology.searchInput).toBeVisible()
61
+ })
62
+ })
63
+
64
+ test.describe('Dashboard Ontology - Security @security', () => {
65
+ test('no sensitive data in console', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
66
+ const collector = createConsoleCollector(authenticatedPage)
67
+ const ontology = new DashboardOntologyPage(authenticatedPage)
68
+ await ontology.goto()
69
+ await ontology.waitForLoad()
70
+ collector.assertNoSensitiveLeaks()
71
+ })
72
+ })
@@ -0,0 +1,48 @@
1
+ import { test, expect } from '../fixtures/base'
2
+ import { DashboardProfilePage } from '../pages/dashboard-profile.page'
3
+ import { createConsoleCollector } from '../utils/console-collector'
4
+
5
+ test.describe('Dashboard Profile @smoke', () => {
6
+ let profile: DashboardProfilePage
7
+
8
+ test.beforeEach(async ({ authenticatedPage, apiErrors: _apiErrors }) => {
9
+ profile = new DashboardProfilePage(authenticatedPage)
10
+ await profile.goto()
11
+ await profile.waitForLoad()
12
+ })
13
+
14
+ test('page loads with heading and user info', async ({ authenticatedPage }) => {
15
+ await expect(authenticatedPage, 'Should navigate to profile page').toHaveURL(/dashboard\/profile/)
16
+ await expect(profile.heading, 'Profile heading should be visible').toBeVisible()
17
+ await expect(profile.avatar).toBeVisible()
18
+ await expect(profile.displayName).toBeVisible()
19
+ await expect(profile.displayEmail).toBeVisible()
20
+ })
21
+
22
+ test('account card shows correct user data', async () => {
23
+ await expect(profile.accountCardTitle).toBeVisible()
24
+ await expect(profile.nameInput).toHaveValue('João Lima')
25
+ await expect(profile.emailInput).toHaveValue('joao.lima@hakutaku.ai')
26
+ })
27
+
28
+ test('user ID field contains a UUID', async () => {
29
+ const userId = await profile.userIdInput.inputValue()
30
+ expect(userId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
31
+ })
32
+
33
+ test('all fields are read-only', async () => {
34
+ await expect(profile.nameInput).toHaveAttribute('readonly', '')
35
+ await expect(profile.emailInput).toHaveAttribute('readonly', '')
36
+ await expect(profile.userIdInput).toHaveAttribute('readonly', '')
37
+ })
38
+ })
39
+
40
+ test.describe('Dashboard Profile - Security @security', () => {
41
+ test('no sensitive data in console', async ({ authenticatedPage, apiErrors: _apiErrors }) => {
42
+ const collector = createConsoleCollector(authenticatedPage)
43
+ const profile = new DashboardProfilePage(authenticatedPage)
44
+ await profile.goto()
45
+ await profile.waitForLoad()
46
+ collector.assertNoSensitiveLeaks()
47
+ })
48
+ })