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>
2026-05-27 21:27:12 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* US-21 — Tactic selection (TA-id tags).
|
|
|
|
|
|
* Covers AC-21.4 → AC-21.7 (API + UI).
|
|
|
|
|
|
* AC-21.1/2/3 (model + migration + serialization) tested via API assertions.
|
|
|
|
|
|
*/
|
|
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
|
|
import {
|
|
|
|
|
|
adminToken,
|
|
|
|
|
|
createEngagement,
|
|
|
|
|
|
deleteEngagement,
|
|
|
|
|
|
deleteUserByUsername,
|
|
|
|
|
|
ensureUser,
|
|
|
|
|
|
login,
|
|
|
|
|
|
makeClient,
|
|
|
|
|
|
} from '../fixtures/api';
|
|
|
|
|
|
import { seedTokenInStorage } from '../fixtures/auth';
|
|
|
|
|
|
|
|
|
|
|
|
const REDTEAM_USER = 'us21-redteam';
|
|
|
|
|
|
const SOC_USER = 'us21-soc';
|
|
|
|
|
|
const PASS = 'us21-pass-strong';
|
|
|
|
|
|
|
|
|
|
|
|
interface Simulation { id: number; status: string; tactics: { id: string; name: string }[]; [key: string]: unknown; }
|
|
|
|
|
|
|
|
|
|
|
|
async function createSimulation(token: string, engagementId: number, name = 'US-21 sim'): Promise<Simulation> {
|
|
|
|
|
|
const r = await makeClient(token).post(`/engagements/${engagementId}/simulations`, { name });
|
|
|
|
|
|
if (r.status !== 201) throw new Error(`create sim: ${r.status}`);
|
|
|
|
|
|
return r.data as Simulation;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function deleteSimulation(token: string, simId: number): Promise<void> {
|
|
|
|
|
|
await makeClient(token).delete(`/simulations/${simId}`);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
test.describe('US-21 — tactic selection', () => {
|
|
|
|
|
|
let redteamToken: string;
|
|
|
|
|
|
let socToken: string;
|
|
|
|
|
|
let engagementId: number;
|
|
|
|
|
|
|
|
|
|
|
|
test.beforeAll(async () => {
|
|
|
|
|
|
await ensureUser(REDTEAM_USER, PASS, 'redteam');
|
|
|
|
|
|
await ensureUser(SOC_USER, PASS, 'soc');
|
|
|
|
|
|
redteamToken = (await login(REDTEAM_USER, PASS)).token;
|
|
|
|
|
|
socToken = (await login(SOC_USER, PASS)).token;
|
|
|
|
|
|
const eng = await createEngagement(redteamToken, {
|
|
|
|
|
|
name: 'US-21 Engagement',
|
|
|
|
|
|
start_date: '2026-01-01',
|
|
|
|
|
|
});
|
|
|
|
|
|
engagementId = eng.id;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test.afterAll(async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const tok = await adminToken();
|
|
|
|
|
|
await deleteEngagement(tok, engagementId);
|
|
|
|
|
|
for (const u of [REDTEAM_USER, SOC_USER]) await deleteUserByUsername(tok, u);
|
|
|
|
|
|
} catch { /* noop */ }
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// AC-21.1/2/3 — model + serialization
|
|
|
|
|
|
test('AC-21.3 — new simulation has tactics=[] in serialisation', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.3 empty');
|
|
|
|
|
|
expect(Array.isArray(sim.tactics)).toBe(true);
|
|
|
|
|
|
expect(sim.tactics).toHaveLength(0);
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// AC-21.4 — PATCH tactic_ids validation
|
|
|
|
|
|
test('AC-21.4 — PATCH tactic_ids: valid TA-id stored, enriched name in response', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.4 valid');
|
|
|
|
|
|
const r = await makeClient(redteamToken).patch(`/simulations/${sim.id}`, {
|
|
|
|
|
|
tactic_ids: ['TA0007', 'TA0001'],
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(r.status).toBe(200);
|
|
|
|
|
|
expect(Array.isArray(r.data.tactics)).toBe(true);
|
|
|
|
|
|
expect(r.data.tactics).toHaveLength(2);
|
|
|
|
|
|
const disc = r.data.tactics.find((t: { id: string }) => t.id === 'TA0007');
|
|
|
|
|
|
expect(disc).toBeTruthy();
|
|
|
|
|
|
expect(disc.name).toBe('Discovery');
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AC-21.4 — PATCH tactic_ids: unknown TA-id → 400', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.4 unknown');
|
|
|
|
|
|
const r = await makeClient(redteamToken).patch(`/simulations/${sim.id}`, {
|
|
|
|
|
|
tactic_ids: ['TA9999'],
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(r.status).toBe(400);
|
|
|
|
|
|
expect(r.data.error).toMatch(/unknown tactic id.*TA9999/i);
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AC-21.4 — PATCH tactic_ids: dedup preserves order', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.4 dedup');
|
|
|
|
|
|
const r = await makeClient(redteamToken).patch(`/simulations/${sim.id}`, {
|
|
|
|
|
|
tactic_ids: ['TA0007', 'TA0001', 'TA0007'],
|
|
|
|
|
|
});
|
|
|
|
|
|
expect(r.status).toBe(200);
|
|
|
|
|
|
const ids = r.data.tactics.map((t: { id: string }) => t.id);
|
|
|
|
|
|
expect(ids).toEqual(['TA0007', 'TA0001']);
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// AC-21.5 — SOC gate + auto-transition
|
|
|
|
|
|
test('AC-21.5 — SOC cannot PATCH tactic_ids → 403', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.5 soc block');
|
|
|
|
|
|
// Advance to review_required so SOC has access
|
|
|
|
|
|
await makeClient(redteamToken).patch(`/simulations/${sim.id}`, { name: 'trigger' });
|
|
|
|
|
|
await makeClient(redteamToken).post(`/simulations/${sim.id}/transition`, { to: 'review_required' });
|
|
|
|
|
|
|
|
|
|
|
|
const r = await makeClient(socToken).patch(`/simulations/${sim.id}`, { tactic_ids: ['TA0007'] });
|
|
|
|
|
|
expect(r.status).toBe(403);
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AC-21.5 — non-empty tactic_ids triggers auto-transition pending→in_progress', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.5 auto-transition');
|
|
|
|
|
|
expect(sim.status).toBe('pending');
|
|
|
|
|
|
const r = await makeClient(redteamToken).patch(`/simulations/${sim.id}`, { tactic_ids: ['TA0007'] });
|
|
|
|
|
|
expect(r.status).toBe(200);
|
|
|
|
|
|
expect(r.data.status).toBe('in_progress');
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AC-21.5 — empty tactic_ids does NOT trigger auto-transition', async () => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.5 no-trigger');
|
|
|
|
|
|
expect(sim.status).toBe('pending');
|
|
|
|
|
|
const r = await makeClient(redteamToken).patch(`/simulations/${sim.id}`, { tactic_ids: [] });
|
|
|
|
|
|
expect(r.status).toBe(200);
|
|
|
|
|
|
expect(r.data.status).toBe('pending');
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// AC-21.6 — matrix modal tactic header clickable
|
|
|
|
|
|
test('AC-21.6 — clicking tactic header in modal toggles tactic selection', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
context,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.6 tactic click');
|
|
|
|
|
|
|
|
|
|
|
|
await seedTokenInStorage(context, redteamToken);
|
|
|
|
|
|
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
|
|
|
|
|
|
|
|
|
|
|
|
await page.getByLabel(/open mitre matrix/i).click();
|
|
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
|
|
|
|
await expect(dialog).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
2026-05-27 21:38:17 +02:00
|
|
|
|
// Wait for matrix to load — tactic header title: "Discovery (TA0007) — click to tag this tactic"
|
|
|
|
|
|
const discoveryHeader = dialog.locator('button[title*="TA0007"]');
|
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>
2026-05-27 21:27:12 +02:00
|
|
|
|
await expect(discoveryHeader).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
await discoveryHeader.click();
|
|
|
|
|
|
|
|
|
|
|
|
// Apply button shows at least 1 selection (the tactic)
|
|
|
|
|
|
await expect(dialog.getByRole('button', { name: /apply \d+/i })).toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
// Click again to deselect
|
|
|
|
|
|
await discoveryHeader.click();
|
|
|
|
|
|
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2026-05-27 21:38:17 +02:00
|
|
|
|
test('AC-21.6 — Apply from modal includes tactic in result (auto-save)', async ({
|
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>
2026-05-27 21:27:12 +02:00
|
|
|
|
page,
|
|
|
|
|
|
context,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.6 apply tactic');
|
|
|
|
|
|
|
|
|
|
|
|
await seedTokenInStorage(context, redteamToken);
|
|
|
|
|
|
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
|
|
|
|
|
|
|
|
|
|
|
|
await page.getByLabel(/open mitre matrix/i).click();
|
|
|
|
|
|
const dialog = page.getByRole('dialog');
|
|
|
|
|
|
await expect(dialog).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
|
2026-05-27 21:38:17 +02:00
|
|
|
|
// Tactic header title: "Discovery (TA0007) — click to tag this tactic"
|
|
|
|
|
|
const discoveryBtn = dialog.locator('button[title*="TA0007"]');
|
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>
2026-05-27 21:27:12 +02:00
|
|
|
|
await expect(discoveryBtn).toBeVisible({ timeout: 10_000 });
|
|
|
|
|
|
await discoveryBtn.click();
|
|
|
|
|
|
|
|
|
|
|
|
const applyBtn = dialog.getByRole('button', { name: /apply \d+/i });
|
|
|
|
|
|
await expect(applyBtn).toBeVisible();
|
|
|
|
|
|
await applyBtn.click();
|
|
|
|
|
|
|
|
|
|
|
|
// Modal closes
|
|
|
|
|
|
await expect(dialog).not.toBeVisible({ timeout: 5_000 });
|
|
|
|
|
|
|
2026-05-27 21:38:17 +02:00
|
|
|
|
// Tactic chip appears after auto-save
|
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>
2026-05-27 21:27:12 +02:00
|
|
|
|
await expect(page.getByTestId('mitre-tactic-tag')).toBeVisible({ timeout: 8_000 });
|
|
|
|
|
|
await expect(page.getByTestId('techniques-tag-list')).toContainText('TA0007');
|
|
|
|
|
|
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// AC-21.7 — tactic chips in MitreTechniquesField
|
|
|
|
|
|
test('AC-21.7 — tactic chips display TA-id and have × for removal', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
context,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.7 tactic chip');
|
|
|
|
|
|
// Seed via API
|
|
|
|
|
|
await makeClient(redteamToken).patch(`/simulations/${sim.id}`, { tactic_ids: ['TA0007'] });
|
|
|
|
|
|
|
|
|
|
|
|
await seedTokenInStorage(context, redteamToken);
|
|
|
|
|
|
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
|
|
|
|
|
|
|
|
|
|
|
|
const tacticTag = page.getByTestId('mitre-tactic-tag');
|
|
|
|
|
|
await expect(tacticTag).toBeVisible();
|
|
|
|
|
|
await expect(tacticTag).toContainText('TA0007');
|
|
|
|
|
|
|
|
|
|
|
|
// Title attribute has id — name
|
|
|
|
|
|
const title = await tacticTag.getAttribute('title');
|
|
|
|
|
|
expect(title).toMatch(/TA0007/);
|
|
|
|
|
|
expect(title).toMatch(/Discovery/);
|
|
|
|
|
|
|
|
|
|
|
|
// × button for removal
|
|
|
|
|
|
await expect(page.getByRole('button', { name: /remove TA0007/i })).toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AC-21.7 — removing tactic chip auto-saves', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
context,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.7 remove tactic');
|
|
|
|
|
|
await makeClient(redteamToken).patch(`/simulations/${sim.id}`, { tactic_ids: ['TA0007'] });
|
|
|
|
|
|
|
|
|
|
|
|
await seedTokenInStorage(context, redteamToken);
|
|
|
|
|
|
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
|
|
|
|
|
|
|
|
|
|
|
|
await expect(page.getByTestId('mitre-tactic-tag')).toBeVisible();
|
|
|
|
|
|
await page.getByRole('button', { name: /remove TA0007/i }).click();
|
|
|
|
|
|
|
|
|
|
|
|
await expect(page.getByText(/techniques updated/i)).toBeVisible({ timeout: 5_000 });
|
|
|
|
|
|
await expect(page.getByTestId('mitre-tactic-tag')).not.toBeVisible({ timeout: 3_000 });
|
|
|
|
|
|
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
test('AC-21.7 — tactic chips visually distinct from technique chips (different class)', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
context,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.7 style');
|
|
|
|
|
|
await makeClient(redteamToken).patch(`/simulations/${sim.id}`, {
|
|
|
|
|
|
tactic_ids: ['TA0007'],
|
|
|
|
|
|
technique_ids: ['T1059'],
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await seedTokenInStorage(context, redteamToken);
|
|
|
|
|
|
await page.goto(`/engagements/${engagementId}/simulations/${sim.id}/edit`);
|
|
|
|
|
|
|
|
|
|
|
|
const tacticTag = page.getByTestId('mitre-tactic-tag');
|
|
|
|
|
|
const techTag = page.getByTestId('mitre-technique-tag');
|
|
|
|
|
|
await expect(tacticTag).toBeVisible();
|
|
|
|
|
|
await expect(techTag).toBeVisible();
|
|
|
|
|
|
|
|
|
|
|
|
// Tactic: bg-primary (filled) vs technique: bg-primary-soft
|
|
|
|
|
|
const tacticCls = await tacticTag.getAttribute('class');
|
|
|
|
|
|
const techCls = await techTag.getAttribute('class');
|
|
|
|
|
|
expect(tacticCls).toMatch(/bg-primary/);
|
|
|
|
|
|
// Technique should NOT have the solid bg-primary (just bg-primary-soft)
|
|
|
|
|
|
expect(techCls).toMatch(/bg-primary-soft/);
|
|
|
|
|
|
// They should differ visually
|
|
|
|
|
|
expect(tacticCls).not.toBe(techCls);
|
|
|
|
|
|
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
2026-05-27 21:33:18 +02:00
|
|
|
|
|
|
|
|
|
|
// NIT code-reviewer: +N suffix in SimulationList MITRE column
|
|
|
|
|
|
test('AC-21.7 — SimulationList MITRE column shows first id + "+N" for mixed tactics+techniques', async ({
|
|
|
|
|
|
page,
|
|
|
|
|
|
context,
|
|
|
|
|
|
}) => {
|
|
|
|
|
|
const sim = await createSimulation(redteamToken, engagementId, 'AC-21.7 +N suffix');
|
|
|
|
|
|
await makeClient(redteamToken).patch(`/simulations/${sim.id}`, {
|
|
|
|
|
|
tactic_ids: ['TA0007'],
|
|
|
|
|
|
technique_ids: ['T1059', 'T1078'],
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
await seedTokenInStorage(context, redteamToken);
|
|
|
|
|
|
await page.goto(`/engagements/${engagementId}`);
|
|
|
|
|
|
|
|
|
|
|
|
// The row for this simulation should show "TA0007 +2" in the MITRE column
|
|
|
|
|
|
// (1 tactic TA0007 is first, then +2 for T1059 and T1078)
|
|
|
|
|
|
const simRow = page.getByRole('row').filter({ hasText: 'AC-21.7 +N suffix' });
|
|
|
|
|
|
await expect(simRow).toBeVisible({ timeout: 5_000 });
|
|
|
|
|
|
await expect(simRow).toContainText('TA0007 +2');
|
|
|
|
|
|
|
|
|
|
|
|
await deleteSimulation(redteamToken, sim.id);
|
|
|
|
|
|
});
|
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>
2026-05-27 21:27:12 +02:00
|
|
|
|
});
|