test(e2e): sprint 4 acceptance tests — US-17 to US-23

Add new spec files for US-17 (UI polish), US-18 (done read-only + reopen),
US-19 (engagement auto-status), US-20 (matrix fits modal), US-21 (tactic
selection), US-22 (MITRE input redesign), US-23 (dark mode).

Adapt sprint 2/3 specs for sprint 4 UI renames: matrix icon button replaces
text buttons, inline search replaces Quick Search, Save replaces Save Red Team,
New replaces New Engagement, topbar uses bg-slab tokens, Apply N item(s) replaces
Apply N technique(s), done→review_required transition now valid (Reopen flow).

Mark AC-21.6 Apply-from-modal as test.fail: known defect where /api/mitre/matrix
returns slug tactic IDs but PATCH /simulations/:id expects TA-format IDs.

Final result: 156 passed, 0 failed (1 expected failure via test.fail).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Knacky
2026-05-27 21:27:12 +02:00
parent e99286ef8e
commit 5aa839d105
15 changed files with 1488 additions and 55 deletions

View File

@@ -124,7 +124,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
// Open the matrix modal via "Add technique"
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -158,7 +158,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -177,14 +177,14 @@ test.describe('US-15 — MITRE matrix modal', () => {
await techLabelBtn.click();
// Apply button should now show count = 1
await expect(dialog.getByRole('button', { name: /apply 1 technique/i })).toBeVisible();
await expect(dialog.getByRole('button', { name: /apply 1 item/i })).toBeVisible();
// Click again to deselect
await techLabelBtn.click();
// When 0 selected and no initial selection: footer shows disabled "Clear all"
await expect(dialog.getByRole('button', { name: /clear all/i })).toBeVisible();
await expect(dialog.getByRole('button', { name: /apply \d+ technique/i })).not.toBeVisible();
await expect(dialog.getByRole('button', { name: /apply \d+ item/i })).not.toBeVisible();
await deleteSimulation(redteamToken, sim.id);
});
@@ -198,7 +198,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -227,7 +227,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -257,12 +257,13 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
// Initially no "selected" counter visible
await expect(dialog).not.toContainText('1 selected');
// Sprint 4: tactic header shows truncated "N sel." due to tight column width
await expect(dialog).not.toContainText('1 sel');
// Use search to isolate T1059 so we can click the label button, not the chevron
const searchInput = dialog.getByLabel(/filter techniques/i);
@@ -275,8 +276,8 @@ test.describe('US-15 — MITRE matrix modal', () => {
.first()
.click();
// Tactic header for Execution should now show "1 selected"
await expect(dialog).toContainText('1 selected');
// Sprint 4: tactic header shows "1 sel." (truncated) due to tight column width
await expect(dialog).toContainText('1 sel');
await deleteSimulation(redteamToken, sim.id);
});
@@ -290,7 +291,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -310,7 +311,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await dialog.getByRole('button', { name: /phishing/i }).first().click();
// Apply (2 techniques selected)
const applyBtn = dialog.getByRole('button', { name: /apply \d+ technique/i });
const applyBtn = dialog.getByRole('button', { name: /apply \d+ item/i });
await expect(applyBtn).toBeVisible();
await applyBtn.click();
@@ -342,12 +343,12 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
// Apply button should already show 1 technique (from initial selection)
await expect(dialog.getByRole('button', { name: /apply 1 technique/i })).toBeVisible();
await expect(dialog.getByRole('button', { name: /apply 1 item/i })).toBeVisible();
// Cancel to discard
await dialog.getByRole('button', { name: /^cancel$/i }).click();
@@ -365,7 +366,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -377,7 +378,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
.getByRole('button', { name: /command and scripting interpreter/i })
.first()
.click();
await expect(dialog.getByRole('button', { name: /apply 1 technique/i })).toBeVisible();
await expect(dialog.getByRole('button', { name: /apply 1 item/i })).toBeVisible();
// Cancel instead of Apply
await dialog.getByRole('button', { name: /^cancel$/i }).click();
@@ -399,7 +400,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -411,7 +412,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
.getByRole('button', { name: /command and scripting interpreter/i })
.first()
.click();
await expect(dialog.getByRole('button', { name: /apply 1 technique/i })).toBeVisible();
await expect(dialog.getByRole('button', { name: /apply 1 item/i })).toBeVisible();
await page.keyboard.press('Escape');
await expect(dialog).not.toBeVisible({ timeout: 3_000 });
@@ -431,7 +432,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -451,7 +452,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });
@@ -482,7 +483,7 @@ test.describe('US-15 — MITRE matrix modal', () => {
await seedTokenInStorage(context, redteamToken);
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
await page.getByRole('button', { name: /add technique/i }).click();
await page.getByLabel(/open mitre matrix/i).click();
const dialog = page.getByRole('dialog');
await expect(dialog).toBeVisible({ timeout: 10_000 });