From dd1895d2c43bb54f6daa04ad5d1282cbcd2266a3 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Tue, 16 Jun 2026 09:32:37 +0200 Subject: [PATCH] test(ui): remove onboarding e2e suite (#11605) --- ui/lib/onboarding/README.md | 2 - ui/tests/onboarding/onboarding-page.ts | 259 ------------------- ui/tests/onboarding/onboarding.md | 334 ------------------------- ui/tests/onboarding/onboarding.spec.ts | 245 ------------------ 4 files changed, 840 deletions(-) delete mode 100644 ui/tests/onboarding/onboarding-page.ts delete mode 100644 ui/tests/onboarding/onboarding.md delete mode 100644 ui/tests/onboarding/onboarding.spec.ts diff --git a/ui/lib/onboarding/README.md b/ui/lib/onboarding/README.md index e1e5532cd0..eab352ca2b 100644 --- a/ui/lib/onboarding/README.md +++ b/ui/lib/onboarding/README.md @@ -72,5 +72,3 @@ the sequence automatically. `target` must resolve to a real `data-tour-id` anchor within its `coversFiles`. - `pnpm exec vitest run --project unit` — pure logic (slice, helpers, registry, tour shapes). The driver primitive short-circuits in `NODE_ENV==="test"`. -- `pnpm run test:e2e tests/onboarding/` — full-system behavior (sequence, - checkpoint, replay, single-fire, refresh). Requires the Prowler stack. diff --git a/ui/tests/onboarding/onboarding-page.ts b/ui/tests/onboarding/onboarding-page.ts deleted file mode 100644 index 979810a7d2..0000000000 --- a/ui/tests/onboarding/onboarding-page.ts +++ /dev/null @@ -1,259 +0,0 @@ -import { Locator, Page, expect } from "@playwright/test"; - -import { BasePage } from "../base-page"; - -const TOUR_KEY_PREFIX = "prowler.tour"; -// Keep in sync with addProviderTour.version (lib/tours/add-provider.tour.ts). -const ADD_PROVIDER_TOUR_VERSION = 2; -const ADD_PROVIDER_TOUR_KEY = `${TOUR_KEY_PREFIX}.add-provider.v${ADD_PROVIDER_TOUR_VERSION}`; -const CHECKPOINT_MARKER_KEY = "prowler.onboarding.checkpoint"; - -// One popover per active driver.js tour; used to assert single-fire / no auto-fire. -const DRIVER_POPOVER_SELECTOR = ".driver-popover"; - -// POM for onboarding flows. URL assertions live here; the spec only orchestrates actions. -// Note: the sequence store is ephemeral (no persist) — a fresh goto/reload resets it. -export class OnboardingPage extends BasePage { - readonly welcomeModal: Locator; - readonly getStartedButton: Locator; - readonly skipButton: Locator; - - readonly checkpointDialog: Locator; - readonly checkpointContinueButton: Locator; - readonly checkpointFinishButton: Locator; - - readonly addProviderTriggerAnchor: Locator; - readonly providerTypeAnchor: Locator; - - readonly viewFirstScanLaunchAnchor: Locator; - readonly viewFirstScanTabsAnchor: Locator; - readonly exploreFindingsFiltersAnchor: Locator; - readonly exploreFindingsGroupAnchor: Locator; - readonly exploreFindingsResourcesAnchor: Locator; - readonly viewComplianceFrameworksAnchor: Locator; - readonly viewComplianceSearchAnchor: Locator; - readonly attackPathsIntroAnchor: Locator; - readonly attackPathsScanListAnchor: Locator; - - readonly accountMenuTrigger: Locator; - readonly productTourSubTrigger: Locator; - - readonly driverPopover: Locator; - - constructor(page: Page) { - super(page); - - this.welcomeModal = page.getByRole("dialog").filter({ - has: page.getByRole("heading", { name: /Add your first provider/i }), - }); - this.getStartedButton = page.getByRole("button", { name: "Get started" }); - this.skipButton = page.getByRole("button", { name: "Skip for now" }); - - this.checkpointDialog = page.getByRole("dialog").filter({ - has: page.getByRole("heading", { - name: /Provider added — keep exploring\?/i, - }), - }); - this.checkpointContinueButton = page.getByRole("button", { - name: "Continue the tour", - }); - this.checkpointFinishButton = page.getByRole("button", { - name: "Finish here", - }); - - this.addProviderTriggerAnchor = page.locator( - '[data-tour-id="add-provider-trigger"]', - ); - this.providerTypeAnchor = page.locator( - '[data-tour-id="add-provider-provider-type"]', - ); - - this.viewFirstScanLaunchAnchor = page.locator( - '[data-tour-id="view-first-scan-launch"]', - ); - this.viewFirstScanTabsAnchor = page.locator( - '[data-tour-id="view-first-scan-tabs"]', - ); - this.exploreFindingsFiltersAnchor = page.locator( - '[data-tour-id="explore-findings-filters"]', - ); - this.exploreFindingsGroupAnchor = page.locator( - '[data-tour-id="explore-findings-group"]', - ); - this.exploreFindingsResourcesAnchor = page.locator( - '[data-tour-id="explore-findings-resources"]', - ); - this.viewComplianceFrameworksAnchor = page.locator( - '[data-tour-id="view-compliance-frameworks"]', - ); - this.viewComplianceSearchAnchor = page.locator( - '[data-tour-id="view-compliance-search"]', - ); - this.attackPathsIntroAnchor = page.locator( - '[data-tour-id="attack-paths-intro"]', - ); - this.attackPathsScanListAnchor = page.locator( - '[data-tour-id="attack-paths-scan-list"]', - ); - - this.accountMenuTrigger = page.getByRole("button", { - name: "Account menu", - }); - this.productTourSubTrigger = page.getByRole("menuitem", { - name: "Product tour", - }); - - this.driverPopover = page.locator(DRIVER_POPOVER_SELECTOR); - } - - // Clears all tour records and the checkpoint marker so the gate evaluates as a fresh browser. - async clearOnboardingState(): Promise { - await this.page.evaluate( - ({ prefix, checkpointKey }) => { - const keys: string[] = []; - for (let i = 0; i < window.localStorage.length; i++) { - const key = window.localStorage.key(i); - if (key && key.startsWith(prefix)) keys.push(key); - } - keys.forEach((key) => window.localStorage.removeItem(key)); - window.localStorage.removeItem(checkpointKey); - }, - { prefix: TOUR_KEY_PREFIX, checkpointKey: CHECKPOINT_MARKER_KEY }, - ); - } - - // Seeds a completed record so the restart path can prove the tour re-fires anyway. - async seedCompletedAddProviderRecord(): Promise { - await this.page.evaluate( - ({ key, version }) => { - window.localStorage.setItem( - key, - JSON.stringify({ - tourId: "add-provider", - version, - state: "completed", - completedAt: new Date().toISOString(), - }), - ); - }, - { key: ADD_PROVIDER_TOUR_KEY, version: ADD_PROVIDER_TOUR_VERSION }, - ); - } - - async openAccountMenu(): Promise { - await this.accountMenuTrigger.click(); - } - - // Hover the sub-trigger to open the per-flow submenu. - async openProductTourSubmenu(): Promise { - await this.openAccountMenu(); - await expect(this.productTourSubTrigger).toBeVisible(); - await this.productTourSubTrigger.hover(); - } - - tourFlowMenuItem(title: string): Locator { - return this.page.getByRole("menuitem", { name: title, exact: true }); - } - - async selectTourFlow(title: string): Promise { - await this.openProductTourSubmenu(); - const item = this.tourFlowMenuItem(title); - await expect(item).toBeVisible(); - await item.click(); - } - - async clickGetStarted(): Promise { - await this.getStartedButton.click(); - } - - async clickCheckpointContinue(): Promise { - await this.checkpointContinueButton.click(); - } - - async clickCheckpointFinish(): Promise { - await this.checkpointFinishButton.click(); - } - - async closeActiveTour(): Promise { - await this.page.keyboard.press("Escape"); - } - - async verifyWelcomeModalVisible(): Promise { - await expect(this.welcomeModal).toBeVisible(); - } - - async verifyWelcomeModalNotVisible(): Promise { - await expect(this.welcomeModal).not.toBeVisible(); - } - - async verifyCheckpointDialogVisible(): Promise { - await expect(this.checkpointDialog).toBeVisible(); - } - - async verifyTriggerAnchorPresent(): Promise { - await expect(this.addProviderTriggerAnchor).toBeVisible(); - } - - async verifyAnchorsPresent(): Promise { - await expect(this.addProviderTriggerAnchor).toBeVisible(); - await expect(this.providerTypeAnchor).toBeVisible(); - } - - async verifyViewFirstScanAnchorPresent(): Promise { - await expect(this.viewFirstScanLaunchAnchor).toBeVisible(); - } - - async verifyExploreFindingsAnchorPresent(): Promise { - await expect(this.exploreFindingsFiltersAnchor).toBeVisible(); - } - - async verifyViewComplianceAnchorPresent(): Promise { - await expect(this.viewComplianceFrameworksAnchor).toBeVisible(); - } - - async verifyAttackPathsAnchorPresent(): Promise { - await expect(this.attackPathsIntroAnchor).toBeVisible(); - } - - // OB-E2E-006: page owns the driver; onboarding must not mount a second one. - async verifySingleDriverPopover(): Promise { - await expect(this.driverPopover).toHaveCount(1); - } - - // OB-E2E-007/004: no tour auto-fires after a reload or stop. - async verifyNoDriverPopover(): Promise { - await expect(this.driverPopover).toHaveCount(0); - } - - async verifyOnProvidersPage(): Promise { - await this.page.waitForURL(/\/providers(\?|$)/); - } - - async verifyOnProvidersPageWithOnboardingParam(): Promise { - await this.page.waitForURL(/\/providers\?onboarding=add-provider/); - } - - async verifyOnScansPage(): Promise { - await this.page.waitForURL(/\/scans(\?|$)/); - } - - async verifyOnFindingsPage(): Promise { - await this.page.waitForURL(/\/findings(\?|$)/); - } - - async verifyOnFindingsReplayPage(): Promise { - await this.page.waitForURL(/\/findings\?onboarding=explore-findings/); - } - - async verifyOnCompliancePage(): Promise { - await this.page.waitForURL(/\/compliance(\?|$)/); - } - - async verifyOnAttackPathsPage(): Promise { - await this.page.waitForURL(/\/attack-paths/); - } - - async verifyStillOnFindingsPage(): Promise { - await expect(this.page).toHaveURL(/\/findings/); - } -} diff --git a/ui/tests/onboarding/onboarding.md b/ui/tests/onboarding/onboarding.md deleted file mode 100644 index e6b2adba03..0000000000 --- a/ui/tests/onboarding/onboarding.md +++ /dev/null @@ -1,334 +0,0 @@ -### E2E Tests: Onboarding System - -**Suite ID:** `OB-E2E` -**Feature:** Extensible UI onboarding system: the guided add-provider tour, the -cross-route guided sequence after the first provider connects, and per-flow -manual replay from the avatar menu. - -> Behavioral assertions only: Welcome modal visibility, checkpoint dialog -> visibility, navigation to each flow route, presence of the `data-tour-id` -> anchors in the DOM, and localStorage markers. Driver.js overlay animation is -> never asserted. Waits use `expect(...).toBeVisible()` and -> `page.waitForURL()` — never `networkidle`. - ---- - -## Test Case: `OB-E2E-001` - Mandatory new-user onboarding path - -**Priority:** `critical` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** A zero-provider authenticated user is forced into the -Welcome modal on first load; accepting it navigates to the add-provider flow and -exposes the tour trigger anchor. - -**Preconditions:** - -- Admin user authentication required (`admin.auth.setup`, reused `storageState`) -- All `prowler.tour.*` localStorage keys cleared before the test -- The account has zero providers (existing providers removed via `deleteProviderIfExists`) - -### Flow Steps - -1. Navigate to a page inside `app/(prowler)/` -2. Assert the Welcome modal is visible -3. Click "Get started" -4. Assert navigation to `/providers` (the `?onboarding=add-provider` param is consumed) -5. Assert the `data-tour-id="add-provider-trigger"` anchor is present in the DOM - -### Expected Result - -- Welcome modal is displayed for the zero-provider user -- Accepting navigates to the providers route -- The add-provider trigger anchor is mounted on the providers page - -### Key verification points - -- Welcome modal visible on first authenticated load (gate forced it) -- After accept, the URL is `/providers` -- `[data-tour-id="add-provider-trigger"]` exists in the DOM - -### Notes - -- Maps to spec: "Zero-provider user on first authenticated load", "User accepts - the Welcome modal", "Every tour step target has a matching data-tour-id anchor" -- Full execution requires the Prowler stack (API + DB + auth env). The trigger - anchor proves the tour surface is reachable without asserting overlay animation - ---- - -## Test Case: `OB-E2E-002` - Restart onboarding from the avatar menu - -**Priority:** `high` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** A user who already completed the tour can restart it -from the avatar menu; the tour starts again despite the existing completion -record. - -**Preconditions:** - -- Admin user authentication required (`admin.auth.setup`, reused `storageState`) -- A `completed` record exists in localStorage for `add-provider` - (`prowler.tour.add-provider.v2`) - -### Flow Steps - -1. Navigate to a page with the user nav visible -2. Open the avatar account menu -3. Click "Product tour" -4. Assert navigation to `/providers?onboarding=add-provider` -5. Assert the `data-tour-id="add-provider-trigger"` anchor is present in the DOM - -### Expected Result - -- The restart entry navigates to the add-provider flow route with the onboarding param -- The tour starts despite the existing completion record (no Welcome modal) -- The add-provider trigger anchor is mounted on the providers page - -### Key verification points - -- "Product tour" entry is present in the avatar menu -- After selecting it, the URL is `/providers?onboarding=add-provider` -- `[data-tour-id="add-provider-trigger"]` exists in the DOM (re-trigger bypassed the completion record) - -### Notes - -- Maps to spec: "User activates the restart entry point from the avatar menu", - "Re-trigger works after a full page reload", "Re-trigger does not depend on - prior browser state" -- Full execution requires the Prowler stack (API + DB + auth env) - ---- - -## Test Case: `OB-E2E-003` - First-run guided sequence + checkpoint - -**Priority:** `critical` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** After the first provider connects, the checkpoint -dialog offers a guided sequence; "Continue the tour" chains through scans, -findings, compliance, and attack paths, advancing only on tour completion. - -**Preconditions:** - -- Admin user authentication required (`admin.auth.setup`, reused `storageState`) -- All `prowler.tour.*` keys and the `prowler.onboarding.checkpoint` marker cleared -- A connected provider exists (a `false → true` `hasProviders` flip is reachable); - guarded/skipped when `E2E_AWS_PROVIDER_ACCOUNT_ID` is unset - -### Flow Steps - -1. Start zero-provider; assert the Welcome modal is visible -2. Connect a provider (real flip via `addAWSProvider`) -3. Assert the checkpoint dialog "Provider added — keep exploring?" is visible -4. Click "Continue the tour" -5. Assert `/scans` with `data-tour-id="view-first-scan-launch"` present -6. Complete the scans tour; assert `/findings` with `explore-findings-filters` -7. Complete; assert `/compliance` with `view-compliance-frameworks` -8. Complete; assert `/attack-paths` with `attack-paths-intro` present - -### Expected Result - -- The checkpoint fires once on the real provider-connected flip -- Continuing chains through each flow route in registry order -- Each route exposes its first anchor in the DOM - -### Key verification points - -- Checkpoint dialog visible after the provider connects -- Sequence visits `/scans → /findings → /compliance → /attack-paths` -- The route-specific anchor is present at each step -- `prowler.onboarding.checkpoint` marker is set after a choice - -### Notes - -- Maps to spec `onboarding-sequence`: "Checkpoint after first provider connects", - "Continue starts the sequence", "Next flow starts after navigating to its route" -- Full execution requires the Prowler stack (API + DB + auth env) - ---- - -## Test Case: `OB-E2E-004` - Stop the sequence at any time - -**Priority:** `high` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** Closing any tour mid-sequence ends the sequence; no -further flow auto-fires and a reload does not resume it. - -**Preconditions:** - -- Admin user authentication required (reused `storageState`) -- A connected provider exists; guarded/skipped when `E2E_AWS_PROVIDER_ACCOUNT_ID` is unset -- Tour state and checkpoint marker cleared in `beforeEach` - -### Flow Steps - -1. Start the guided sequence (continue from the checkpoint) -2. On `/findings`, close the active tour (press Escape) -3. Wait briefly and assert the URL is still `/findings` (no advance to `/compliance`) -4. Reload the page -5. Assert no tour auto-fires (no `.driver-popover` in the DOM) - -### Expected Result - -- Closing the tour stops the sequence immediately -- No navigation to `/compliance` occurs -- A reload does not resume the sequence - -### Key verification points - -- URL remains `/findings` after Escape (no auto-advance) -- After reload, zero `.driver-popover` elements exist - -### Notes - -- Maps to spec `onboarding-sequence`: "Stopping any tour ends the sequence" -- Full execution requires the Prowler stack (API + DB + auth env) - ---- - -## Test Case: `OB-E2E-005` - Manual single-flow replay from the avatar menu - -**Priority:** `high` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** The avatar "Product tour" submenu lists every flow; -selecting one replays that single flow only and never chains into the sequence. - -**Preconditions:** - -- Admin user authentication required (reused `storageState`) -- `completed` records seeded for the flows so the list represents replay state - -### Flow Steps - -1. Open the avatar account menu -2. Open the "Product tour" submenu -3. Assert all five flow titles are listed (registry order) -4. Select "Explore your findings" -5. Assert navigation to `/findings?onboarding=explore-findings` -6. Assert `data-tour-id="explore-findings-filters"` present -7. Close the tour and assert no navigation to `/compliance` (no sequence chaining) - -### Expected Result - -- The submenu lists all five flows by title -- Selecting a flow replays it standalone with the `?onboarding=` param -- Closing the replayed tour does not advance to the next flow - -### Key verification points - -- Submenu contains `Add your first provider`, `Run your first scan`, - `Explore your findings`, `Check compliance`, `Visualize attack paths` -- URL is `/findings?onboarding=explore-findings` -- No advance to `/compliance` after closing - -### Notes - -- Maps to spec `onboarding` (MODIFIED): "Avatar entry opens an ordered list of - flows", "Selecting a flow replays that single flow only" -- Full execution requires the Prowler stack (API + DB + auth env) - ---- - -## Test Case: `OB-E2E-006` - Attack-paths single-fire - -**Priority:** `high` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** On `/attack-paths` only one driver popover exists at a -time — the page owns the driver and onboarding never mounts a second runner. - -**Preconditions:** - -- Admin user authentication required (reused `storageState`) -- A connected provider with at least one ready scan exists; guarded/skipped when - `E2E_AWS_PROVIDER_ACCOUNT_ID` is unset - -### Flow Steps - -1. Navigate to `/attack-paths?onboarding=attack-paths` -2. Wait for the attack-paths tour popover to appear -3. Count `.driver-popover` elements in the DOM - -### Expected Result - -- Exactly one driver popover exists (no double-fire) - -### Key verification points - -- `.driver-popover` count is exactly `1` - -### Notes - -- Maps to spec `onboarding-sequence`: "Attack-Paths Single-Fire Integration" -- Full execution requires the Prowler stack (API + DB + auth env) - ---- - -## Test Case: `OB-E2E-007` - Refresh mid-sequence does not re-fire - -**Priority:** `high` - -**Tags:** - -- type → @e2e -- feature → @onboarding - -**Description/Objective:** The sequence slice is ephemeral; a hard reload mid-tour -resets it so no tour auto-fires, and no Welcome modal appears (provider connected). - -**Preconditions:** - -- Admin user authentication required (reused `storageState`) -- A connected provider exists; guarded/skipped when `E2E_AWS_PROVIDER_ACCOUNT_ID` is unset -- Tour state and checkpoint marker cleared in `beforeEach` - -### Flow Steps - -1. Start the guided sequence and land on `/scans` -2. Hard-reload the page -3. Assert no `.driver-popover` auto-fires -4. Assert the Welcome modal is not visible - -### Expected Result - -- No tour auto-fires after the reload (ephemeral slice reset) -- No Welcome modal (the provider is connected, so the gate stays closed) - -### Key verification points - -- Zero `.driver-popover` elements after reload -- Welcome modal is not visible - -### Notes - -- Maps to spec `onboarding-sequence`: "Refresh mid-sequence does not re-fire - infinitely" -- Full execution requires the Prowler stack (API + DB + auth env) diff --git a/ui/tests/onboarding/onboarding.spec.ts b/ui/tests/onboarding/onboarding.spec.ts deleted file mode 100644 index c86c7bc0a9..0000000000 --- a/ui/tests/onboarding/onboarding.spec.ts +++ /dev/null @@ -1,245 +0,0 @@ -import { test } from "@playwright/test"; - -import { addAWSProvider, deleteProviderIfExists } from "../helpers"; -import { ProvidersPage } from "../providers/providers-page"; -import { OnboardingPage } from "./onboarding-page"; - -// Real AWS credentials for the hasProviders false→true flip; tests skip when unset. -const accountId = process.env.E2E_AWS_PROVIDER_ACCOUNT_ID ?? ""; -const accessKey = process.env.E2E_AWS_PROVIDER_ACCESS_KEY ?? ""; -const secretKey = process.env.E2E_AWS_PROVIDER_SECRET_KEY ?? ""; -const hasAwsCredentials = Boolean(accountId && accessKey && secretKey); - -// Guided onboarding is a Prowler Cloud-only feature; it never mounts in OSS. -const isCloudEnv = process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true"; - -test.describe("Onboarding", () => { - test.skip( - !isCloudEnv, - "Guided onboarding is a Prowler Cloud-only feature (NEXT_PUBLIC_IS_CLOUD_ENV != true)", - ); - test.use({ storageState: "playwright/.auth/admin_user.json" }); - - test.describe("Mandatory new-user path", () => { - test.beforeEach(async ({ page }) => { - const providersPage = new ProvidersPage(page); - if (accountId) { - await deleteProviderIfExists(providersPage, accountId); - } - - await providersPage.goto(); - const onboardingPage = new OnboardingPage(page); - await onboardingPage.clearOnboardingState(); - }); - - test( - "forces the Welcome modal and hands off to the add-provider tour", - { - tag: ["@critical", "@e2e", "@onboarding", "@OB-E2E-001"], - }, - async ({ page }) => { - test.skip( - !accountId, - "E2E_AWS_PROVIDER_ACCOUNT_ID is not set; cannot guarantee a zero-provider account", - ); - - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/providers"); - await onboardingPage.verifyWelcomeModalVisible(); - await onboardingPage.clickGetStarted(); - await onboardingPage.verifyOnProvidersPage(); - await onboardingPage.verifyTriggerAnchorPresent(); - }, - ); - }); - - test.describe("Restart path", () => { - test.beforeEach(async ({ page }) => { - const onboardingPage = new OnboardingPage(page); - await onboardingPage.goto("/providers"); - await onboardingPage.seedCompletedAddProviderRecord(); - }); - - test( - "restarts the tour from the avatar menu despite a completion record", - { - tag: ["@high", "@e2e", "@onboarding", "@OB-E2E-002"], - }, - async ({ page }) => { - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/providers"); - await onboardingPage.selectTourFlow("Add your first provider"); - await onboardingPage.verifyOnProvidersPageWithOnboardingParam(); - await onboardingPage.verifyTriggerAnchorPresent(); - }, - ); - }); - - test.describe("Guided sequence", () => { - test.beforeEach(async ({ page }) => { - const providersPage = new ProvidersPage(page); - if (accountId) { - await deleteProviderIfExists(providersPage, accountId); - } - await providersPage.goto(); - const onboardingPage = new OnboardingPage(page); - await onboardingPage.clearOnboardingState(); - }); - - test( - "runs the first-run sequence after the checkpoint and chains every flow", - { - tag: ["@critical", "@e2e", "@onboarding", "@OB-E2E-003"], - }, - async ({ page }) => { - test.skip( - !hasAwsCredentials, - "E2E AWS provider credentials are not set; cannot drive a real hasProviders flip", - ); - - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/providers"); - await onboardingPage.verifyWelcomeModalVisible(); - - // Connect a provider to drive the genuine hasProviders false→true flip. - await addAWSProvider(page, accountId, accessKey, secretKey); - - await onboardingPage.verifyCheckpointDialogVisible(); - await onboardingPage.clickCheckpointContinue(); - - await onboardingPage.verifyOnScansPage(); - await onboardingPage.verifyViewFirstScanAnchorPresent(); - await onboardingPage.closeActiveTour(); - - await onboardingPage.verifyExploreFindingsAnchorPresent(); - - await onboardingPage.goto("/compliance"); - await onboardingPage.verifyViewComplianceAnchorPresent(); - await onboardingPage.goto("/attack-paths"); - await onboardingPage.verifyAttackPathsAnchorPresent(); - }, - ); - - test( - "stops the sequence when a tour is closed and does not resume on reload", - { - tag: ["@high", "@e2e", "@onboarding", "@OB-E2E-004"], - }, - async ({ page }) => { - test.skip( - !hasAwsCredentials, - "E2E AWS provider credentials are not set; cannot drive the guided sequence", - ); - - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/providers"); - await addAWSProvider(page, accountId, accessKey, secretKey); - await onboardingPage.verifyCheckpointDialogVisible(); - await onboardingPage.clickCheckpointContinue(); - - await onboardingPage.verifyOnScansPage(); - await onboardingPage.closeActiveTour(); - await onboardingPage.verifyExploreFindingsAnchorPresent(); - await onboardingPage.closeActiveTour(); - - // Closing the tour stops the sequence — no auto-advance to compliance. - await onboardingPage.verifyStillOnFindingsPage(); - - // Ephemeral sequence must not resume after a hard reload. - await onboardingPage.refresh(); - await onboardingPage.verifyNoDriverPopover(); - }, - ); - - test( - "does not re-fire after a hard reload mid-sequence", - { - tag: ["@high", "@e2e", "@onboarding", "@OB-E2E-007"], - }, - async ({ page }) => { - test.skip( - !hasAwsCredentials, - "E2E AWS provider credentials are not set; cannot drive the guided sequence", - ); - - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/providers"); - await addAWSProvider(page, accountId, accessKey, secretKey); - await onboardingPage.verifyCheckpointDialogVisible(); - await onboardingPage.clickCheckpointContinue(); - await onboardingPage.verifyOnScansPage(); - - // Hard reload resets the ephemeral slice; provider is connected so the gate stays silent. - await onboardingPage.refresh(); - await onboardingPage.verifyNoDriverPopover(); - await onboardingPage.verifyWelcomeModalNotVisible(); - }, - ); - }); - - test.describe("Manual replay", () => { - test.beforeEach(async ({ page }) => { - const onboardingPage = new OnboardingPage(page); - await onboardingPage.goto("/providers"); - await onboardingPage.seedCompletedAddProviderRecord(); - }); - - test( - "replays a single flow from the avatar submenu without chaining", - { - tag: ["@high", "@e2e", "@onboarding", "@OB-E2E-005"], - }, - async ({ page }) => { - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/providers"); - await onboardingPage.openProductTourSubmenu(); - for (const title of [ - "Add your first provider", - "Run your first scan", - "Explore your findings", - "Check compliance", - "Visualize attack paths", - ]) { - await onboardingPage.verifyElementVisible( - onboardingPage.tourFlowMenuItem(title), - ); - } - - await onboardingPage.tourFlowMenuItem("Explore your findings").click(); - await onboardingPage.verifyOnFindingsReplayPage(); - await onboardingPage.verifyExploreFindingsAnchorPresent(); - - // Replay must not chain to the next flow on close. - await onboardingPage.closeActiveTour(); - await onboardingPage.verifyStillOnFindingsPage(); - }, - ); - }); - - test.describe("Attack-paths single-fire", () => { - test( - "renders exactly one driver popover on the attack-paths route", - { - tag: ["@high", "@e2e", "@onboarding", "@OB-E2E-006"], - }, - async ({ page }) => { - test.skip( - !hasAwsCredentials, - "E2E AWS provider credentials are not set; attack-paths needs a ready scan", - ); - - const onboardingPage = new OnboardingPage(page); - - await onboardingPage.goto("/attack-paths?onboarding=attack-paths"); - await onboardingPage.verifyOnAttackPathsPage(); - await onboardingPage.verifySingleDriverPopover(); - }, - ); - }); -});