diff --git a/.github/workflows/run-e2e.yaml b/.github/workflows/run-e2e.yaml new file mode 100644 index 000000000000..3fef8663cc29 --- /dev/null +++ b/.github/workflows/run-e2e.yaml @@ -0,0 +1,62 @@ +name: e2eci + +on: + workflow_dispatch: + inputs: + userRole: + description: "Role of the user (ADMIN, EDITOR, VIEWER)" + required: true + type: choice + options: + - ADMIN + - EDITOR + - VIEWER + +jobs: + test: + name: Run Playwright Tests + runs-on: ubuntu-latest + timeout-minutes: 60 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: lts/* + + - name: Mask secrets and input + run: | + echo "::add-mask::${{ secrets.BASE_URL }}" + echo "::add-mask::${{ secrets.LOGIN_USERNAME }}" + echo "::add-mask::${{ secrets.LOGIN_PASSWORD }}" + echo "::add-mask::${{ github.event.inputs.userRole }}" + + - name: Install dependencies + working-directory: frontend + run: | + npm install -g yarn + yarn + + - name: Install Playwright Browsers + working-directory: frontend + run: yarn playwright install --with-deps + + - name: Run Playwright Tests + working-directory: frontend + run: | + BASE_URL="${{ secrets.BASE_URL }}" \ + LOGIN_USERNAME="${{ secrets.LOGIN_USERNAME }}" \ + LOGIN_PASSWORD="${{ secrets.LOGIN_PASSWORD }}" \ + USER_ROLE="${{ github.event.inputs.userRole }}" \ + yarn playwright test + + - name: Upload Playwright Report + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: frontend/playwright-report/ + retention-days: 30 diff --git a/frontend/.gitignore b/frontend/.gitignore index a54896394062..cfd8dac366fe 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -2,3 +2,30 @@ # Sentry Config File .env.sentry-build-plugin .qodo + +# Playwright +node_modules/ +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ +/playwright/test-results/ +/playwright/blob-report/ +/playwright/playwright-report/ + +e2e/test-plan/alerts/ +e2e/test-plan/dashboards/ +e2e/test-plan/exceptions/ +e2e/test-plan/external-apis/ +e2e/test-plan/help-support/ +e2e/test-plan/infrastructure/ +e2e/test-plan/logs/ +e2e/test-plan/messaging-queues/ +e2e/test-plan/metrics/ +e2e/test-plan/navigation/ +e2e/test-plan/onboarding/ +e2e/test-plan/saved-views/ +e2e/test-plan/service-map/ +e2e/test-plan/services/ +e2e/test-plan/traces/ +e2e/test-plan/user-preferences/ \ No newline at end of file diff --git a/frontend/e2e/test-plan/README.md b/frontend/e2e/test-plan/README.md new file mode 100644 index 000000000000..6853834d1899 --- /dev/null +++ b/frontend/e2e/test-plan/README.md @@ -0,0 +1,29 @@ +# SigNoz E2E Test Plan + +This directory contains the structured test plan for the SigNoz application. Each subfolder corresponds to a main module or feature area, and contains scenario files for all user journeys, edge cases, and cross-module flows. These documents serve as the basis for generating Playwright MCP-driven E2E tests. + +## Structure + +- Each main module (e.g., logs, traces, dashboards, alerts, settings, etc.) has its own folder or markdown file. +- Each file contains detailed scenario templates, including preconditions, step-by-step actions, and expected outcomes. +- Use these documents to write, review, and update test cases as the application evolves. + +## Folders & Files + +- `logs/` — Logs module scenarios +- `traces/` — Traces module scenarios +- `metrics/` — Metrics module scenarios +- `dashboards/` — Dashboards module scenarios +- `alerts/` — Alerts module scenarios +- `services/` — Services module scenarios +- `settings/` — Settings and all sub-settings scenarios +- `onboarding/` — Onboarding and signup flows +- `navigation/` — Navigation, sidebar, and cross-module flows +- `exceptions/` — Exception and error handling scenarios +- `external-apis/` — External API monitoring scenarios +- `messaging-queues/` — Messaging queue scenarios +- `infrastructure/` — Infrastructure monitoring scenarios +- `help-support/` — Help & support scenarios +- `user-preferences/` — User preferences and personalization scenarios +- `service-map/` — Service map scenarios +- `saved-views/` — Saved views scenarios diff --git a/frontend/e2e/test-plan/settings/README.md b/frontend/e2e/test-plan/settings/README.md new file mode 100644 index 000000000000..2e424598e9d9 --- /dev/null +++ b/frontend/e2e/test-plan/settings/README.md @@ -0,0 +1,16 @@ +# Settings Module Test Plan + +This folder contains E2E test scenarios for the Settings module and all sub-settings. + +## Scenario Categories + +- General settings (org/workspace, branding, version info) +- Billing settings +- Members & SSO +- Custom domain +- Integrations +- Notification channels +- API keys +- Ingestion +- Account settings (profile, password, preferences) +- Keyboard shortcuts diff --git a/frontend/e2e/test-plan/settings/account-settings.md b/frontend/e2e/test-plan/settings/account-settings.md new file mode 100644 index 000000000000..63677d662f82 --- /dev/null +++ b/frontend/e2e/test-plan/settings/account-settings.md @@ -0,0 +1,43 @@ +# Account Settings E2E Scenarios (Updated) + +## 1. Update Name + +- **Precondition:** User is logged in +- **Steps:** + 1. Click 'Update name' button + 2. Edit name field in the modal/dialog + 3. Save changes +- **Expected:** Name is updated in the UI + +## 2. Update Email + +- **Note:** The email field is not editable in the current UI. + +## 3. Reset Password + +- **Precondition:** User is logged in +- **Steps:** + 1. Click 'Reset password' button + 2. Complete reset flow (modal/dialog or external flow) +- **Expected:** Password is reset + +## 4. Toggle 'Adapt to my timezone' + +- **Precondition:** User is logged in +- **Steps:** + 1. Toggle 'Adapt to my timezone' switch +- **Expected:** Timezone adapts accordingly (UI feedback/confirmation should be checked) + +## 5. Toggle Theme (Dark/Light) + +- **Precondition:** User is logged in +- **Steps:** + 1. Toggle theme radio buttons ('Dark', 'Light Beta') +- **Expected:** Theme changes + +## 6. Toggle Sidebar Always Open + +- **Precondition:** User is logged in +- **Steps:** + 1. Toggle 'Keep the primary sidebar always open' switch +- **Expected:** Sidebar remains open/closed as per toggle diff --git a/frontend/e2e/test-plan/settings/api-keys.md b/frontend/e2e/test-plan/settings/api-keys.md new file mode 100644 index 000000000000..6e2b81336f00 --- /dev/null +++ b/frontend/e2e/test-plan/settings/api-keys.md @@ -0,0 +1,26 @@ +# API Keys E2E Scenarios (Updated) + +## 1. Create a New API Key + +- **Precondition:** User is admin +- **Steps:** + 1. Click 'New Key' button + 2. Enter details in the modal/dialog + 3. Click 'Save' +- **Expected:** API key is created and listed in the table + +## 2. Revoke an API Key + +- **Precondition:** API key exists +- **Steps:** + 1. In the table, locate the API key row + 2. Click the revoke/delete button (icon button in the Action column) + 3. Confirm if prompted +- **Expected:** API key is revoked/removed from the table + +## 3. View API Key Usage + +- **Precondition:** API key exists +- **Steps:** + 1. View the 'Last used' and 'Expired' columns in the table +- **Expected:** Usage data is displayed for each API key diff --git a/frontend/e2e/test-plan/settings/billing.md b/frontend/e2e/test-plan/settings/billing.md new file mode 100644 index 000000000000..083fe6a6b2c6 --- /dev/null +++ b/frontend/e2e/test-plan/settings/billing.md @@ -0,0 +1,17 @@ +# Billing Settings E2E Scenarios (Updated) + +## 1. View Billing Information + +- **Precondition:** User is admin +- **Steps:** + 1. Navigate to Billing Settings + 2. Wait for the billing chart/data to finish loading +- **Expected:** + - Billing heading and subheading are displayed + - Usage/cost table is visible with columns: Unit, Data Ingested, Price per Unit, Cost (Billing period to date) + - "Download CSV" and "Manage Billing" buttons are present and enabled after loading + - Test clicking "Download CSV" and "Manage Billing" for expected behavior (e.g., file download, navigation, or modal) + +> Note: If these features are expected to trigger specific flows, document the observed behavior for each button. + + diff --git a/frontend/e2e/test-plan/settings/custom-domain.md b/frontend/e2e/test-plan/settings/custom-domain.md new file mode 100644 index 000000000000..ba58876ff835 --- /dev/null +++ b/frontend/e2e/test-plan/settings/custom-domain.md @@ -0,0 +1,18 @@ +# Custom Domain E2E Scenarios (Updated) + +## 1. Add or Update Custom Domain + +- **Precondition:** User is admin +- **Steps:** + 1. Click 'Customize team’s URL' button + 2. In the 'Customize your team’s URL' dialog, enter the preferred subdomain + 3. Click 'Apply Changes' +- **Expected:** Domain is set/updated for the team (UI feedback/confirmation should be checked) + +## 2. Verify Domain Ownership + +- **Note:** No explicit 'Verify' button or flow is present in the current UI. If verification is required, it may be handled automatically or via support. + +## 3. Remove a Custom Domain + +- **Note:** No explicit 'Remove' button or flow is present in the current UI. The only available action is to update the subdomain. diff --git a/frontend/e2e/test-plan/settings/general.md b/frontend/e2e/test-plan/settings/general.md new file mode 100644 index 000000000000..58786dd83136 --- /dev/null +++ b/frontend/e2e/test-plan/settings/general.md @@ -0,0 +1,31 @@ +# General Settings E2E Scenarios + +## 1. View General Settings + +- **Precondition:** User is logged in +- **Steps:** + 1. Navigate to General Settings +- **Expected:** General settings are displayed + +## 2. Update Organization/Workspace Name + +- **Precondition:** User is admin +- **Steps:** + 1. Edit organization/workspace name + 2. Save changes +- **Expected:** Name is updated and visible + +## 3. Update Logo or Branding + +- **Precondition:** User is admin +- **Steps:** + 1. Upload new logo/branding + 2. Save changes +- **Expected:** Branding is updated + +## 4. View Version/Build Info + +- **Precondition:** User is logged in +- **Steps:** + 1. View version/build info section +- **Expected:** Version/build info is displayed diff --git a/frontend/e2e/test-plan/settings/ingestion.md b/frontend/e2e/test-plan/settings/ingestion.md new file mode 100644 index 000000000000..96a600bb716a --- /dev/null +++ b/frontend/e2e/test-plan/settings/ingestion.md @@ -0,0 +1,20 @@ +# Ingestion E2E Scenarios (Updated) + +## 1. View Ingestion Sources + +- **Precondition:** User is admin +- **Steps:** + 1. Navigate to the Integrations page +- **Expected:** List of available data sources/integrations is displayed + +## 2. Configure Ingestion Sources + +- **Precondition:** User is admin +- **Steps:** + 1. Click 'Configure' for a data source/integration + 2. Complete the configuration flow (modal or page, as available) +- **Expected:** Source is configured (UI feedback/confirmation should be checked) + +## 3. Disable/Enable Ingestion + +- **Note:** No visible enable/disable toggle for ingestion sources in the current UI. Ingestion is managed via the Integrations configuration flows. diff --git a/frontend/e2e/test-plan/settings/integrations.md b/frontend/e2e/test-plan/settings/integrations.md new file mode 100644 index 000000000000..1d6bfdda7020 --- /dev/null +++ b/frontend/e2e/test-plan/settings/integrations.md @@ -0,0 +1,51 @@ +# Integrations E2E Scenarios (Updated) + +## 1. View List of Available Integrations + +- **Precondition:** User is logged in +- **Steps:** + 1. Navigate to Integrations +- **Expected:** List of integrations is displayed, each with a name, description, and 'Configure' button + +## 2. Search Integrations by Name/Type + +- **Precondition:** Integrations exist +- **Steps:** + 1. Enter search/filter criteria in the 'Search for an integration...' box +- **Expected:** Only matching integrations are shown + +## 3. Connect a New Integration + +- **Precondition:** User is admin +- **Steps:** + 1. Click 'Configure' for an integration + 2. Complete the configuration flow (modal or page, as available) +- **Expected:** Integration is connected/configured (UI feedback/confirmation should be checked) + +## 4. Disconnect an Integration + +- **Note:** No visible 'Disconnect' button in the main list. This may be available in the configuration flow for a connected integration. + +## 5. Configure Integration Settings + +- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration. + +## 6. Test Integration Connection + +- **Note:** No visible 'Test Connection' button in the main list. This may be available in the configuration flow. + +## 7. View Integration Status/Logs + +- **Note:** No visible status/logs section in the main list. This may be available in the configuration flow. + +## 8. Filter Integrations by Category + +- **Note:** No explicit category filter in the current UI, only a search box. + +## 9. View Integration Documentation/Help + +- **Note:** No visible 'Help/Docs' button in the main list. This may be available in the configuration flow. + +## 10. Update Integration Configuration + +- **Note:** Configuration is handled in the flow after clicking 'Configure' for an integration. diff --git a/frontend/e2e/test-plan/settings/keyboard-shortcuts.md b/frontend/e2e/test-plan/settings/keyboard-shortcuts.md new file mode 100644 index 000000000000..753624e4670b --- /dev/null +++ b/frontend/e2e/test-plan/settings/keyboard-shortcuts.md @@ -0,0 +1,19 @@ +# Keyboard Shortcuts E2E Scenarios (Updated) + +## 1. View Keyboard Shortcuts + +- **Precondition:** User is logged in +- **Steps:** + 1. Navigate to Keyboard Shortcuts +- **Expected:** Shortcuts are displayed in categorized tables (Global, Logs Explorer, Query Builder, Dashboard) + +## 2. Customize Keyboard Shortcuts (if supported) + +- **Note:** Customization is not available in the current UI. Shortcuts are view-only. + +## 3. Use Keyboard Shortcuts for Navigation/Actions + +- **Precondition:** User is logged in +- **Steps:** + 1. Use shortcut for navigation/action (e.g., shift+s for Services, cmd+enter for running query) +- **Expected:** Navigation/action is performed as per shortcut diff --git a/frontend/e2e/test-plan/settings/members-sso.md b/frontend/e2e/test-plan/settings/members-sso.md new file mode 100644 index 000000000000..8fd84f391c73 --- /dev/null +++ b/frontend/e2e/test-plan/settings/members-sso.md @@ -0,0 +1,49 @@ +# Members & SSO E2E Scenarios (Updated) + +## 1. Invite a New Member + +- **Precondition:** User is admin +- **Steps:** + 1. Click 'Invite Members' button + 2. In the 'Invite team members' dialog, enter email address, name (optional), and select role + 3. (Optional) Click 'Add another team member' to invite more + 4. Click 'Invite team members' to send invite(s) +- **Expected:** Pending invite appears in the 'Pending Invites' table + +## 2. Remove a Member + +- **Precondition:** User is admin, member exists +- **Steps:** + 1. In the 'Members' table, locate the member row + 2. Click 'Delete' in the Action column + 3. Confirm removal if prompted +- **Expected:** Member is removed from the table + +## 3. Update Member Roles + +- **Precondition:** User is admin, member exists +- **Steps:** + 1. In the 'Members' table, locate the member row + 2. Click 'Edit' in the Action column + 3. Change role in the edit dialog/modal + 4. Save changes +- **Expected:** Member role is updated in the table + +## 4. Configure SSO + +- **Precondition:** User is admin +- **Steps:** + 1. In the 'Authenticated Domains' section, locate the domain row + 2. Click 'Configure SSO' or 'Edit Google Auth' as available + 3. Complete SSO provider configuration in the modal/dialog + 4. Save settings +- **Expected:** SSO is configured for the domain + +## 5. Login via SSO + +- **Precondition:** SSO is configured +- **Steps:** + 1. Log out from the app + 2. On the login page, click 'Login with SSO' + 3. Complete SSO login flow +- **Expected:** User is logged in via SSO diff --git a/frontend/e2e/test-plan/settings/notification-channels.md b/frontend/e2e/test-plan/settings/notification-channels.md new file mode 100644 index 000000000000..36e4098b8d51 --- /dev/null +++ b/frontend/e2e/test-plan/settings/notification-channels.md @@ -0,0 +1,39 @@ +# Notification Channels E2E Scenarios (Updated) + +## 1. Add a New Notification Channel + +- **Precondition:** User is admin +- **Steps:** + 1. Click 'New Alert Channel' button + 2. In the 'New Notification Channel' form, fill in required fields (Name, Type, Webhook URL, etc.) + 3. (Optional) Toggle 'Send resolved alerts' + 4. (Optional) Click 'Test' to send a test notification + 5. Click 'Save' to add the channel +- **Expected:** Channel is added and listed in the table + +## 2. Test Notification Channel + +- **Precondition:** Channel is being created or edited +- **Steps:** + 1. In the 'New Notification Channel' or 'Edit Notification Channel' form, click 'Test' +- **Expected:** Test notification is sent (UI feedback/confirmation should be checked) + +## 3. Remove a Notification Channel + +- **Precondition:** Channel is added +- **Steps:** + 1. In the table, locate the channel row + 2. Click 'Delete' in the Action column + 3. Confirm removal if prompted +- **Expected:** Channel is removed from the table + +## 4. Update Notification Channel Settings + +- **Precondition:** Channel is added +- **Steps:** + 1. In the table, locate the channel row + 2. Click 'Edit' in the Action column + 3. In the 'Edit Notification Channel' form, update fields as needed + 4. (Optional) Click 'Test' to send a test notification + 5. Click 'Save' to update the channel +- **Expected:** Settings are updated diff --git a/frontend/e2e/test-plan/validation-report.md b/frontend/e2e/test-plan/validation-report.md new file mode 100644 index 000000000000..29dd62f4bb69 --- /dev/null +++ b/frontend/e2e/test-plan/validation-report.md @@ -0,0 +1,199 @@ +# SigNoz Test Plan Validation Report + +This report documents the validation of the E2E test plan against the current live application using Playwright MCP. Each module is reviewed for coverage, gaps, and required updates. + +--- + +## Home Module + +- **Coverage:** + - Widgets for logs, traces, metrics, dashboards, alerts, services, saved views, onboarding checklist + - Quick access buttons: Explore Logs, Create dashboard, Create an alert +- **Gaps/Updates:** + - Add scenarios for checklist interactions (e.g., “I’ll do this later”, progress tracking) + - Add scenarios for Saved Views and cross-module links + - Add scenario for onboarding checklist completion + +--- + +## Logs Module + +- **Coverage:** + - Explorer, Pipelines, Views tabs + - Filtering by service, environment, severity, host, k8s, etc. + - Search, save view, create alert, add to dashboard, export, view mode switching +- **Gaps/Updates:** + - Add scenario for quick filter customization + - Add scenario for “Old Explorer” button + - Add scenario for frequency chart toggle + - Add scenario for “Stage & Run Query” workflow + +--- + +## Traces Module + +- **Coverage:** + - Tabs: Explorer, Funnels, Views + - Filtering by name, error status, duration, environment, function, service, RPC, status code, HTTP, trace ID, etc. + - Search, save view, create alert, add to dashboard, export, view mode switching (List, Traces, Time Series, Table) + - Pagination, quick filter customization, group by, aggregation +- **Gaps/Updates:** + - Add scenario for quick filter customization + - Add scenario for “Stage & Run Query” workflow + - Add scenario for all view modes (List, Traces, Time Series, Table) + - Add scenario for group by/aggregation + - Add scenario for trace detail navigation (clicking on trace row) + - Add scenario for Funnels tab (create/edit/delete funnel) + - Add scenario for Views tab (manage saved views) + +--- + +## Metrics Module + +- **Coverage:** + - Tabs: Summary, Explorer, Views + - Filtering by metric, type, unit, etc. + - Search, save view, add to dashboard, export, view mode switching (chart, table, proportion view) + - Pagination, group by, aggregation, custom queries +- **Gaps/Updates:** + - Add scenario for Proportion View in Summary + - Add scenario for all view modes (chart, table, proportion) + - Add scenario for group by/aggregation + - Add scenario for custom queries in Explorer + - Add scenario for Views tab (manage saved views) + +--- + +## Dashboards Module + +- **Coverage:** + - List, search, and filter dashboards + - Create new dashboard (button and template link) + - Edit, delete, and view dashboard details + - Add/edit/delete widgets (implied by dashboard detail) + - Pagination through dashboards +- **Gaps/Updates:** + - Add scenario for browsing dashboard templates (external link) + - Add scenario for requesting new template + - Add scenario for dashboard owner and creation info + - Add scenario for dashboard tags and filtering by tags + - Add scenario for dashboard sharing (if available) + - Add scenario for dashboard image/preview + +--- + +## Messaging Queues Module + +- **Coverage:** + - Overview tab: queue metrics, filters (Service Name, Span Name, Msg System, Destination, Kind) + - Search across all columns + - Pagination of queue data + - Sync and Share buttons + - Tabs for Kafka and Celery +- **Gaps/Updates:** + - Add scenario for Kafka tab (detailed metrics, actions) + - Add scenario for Celery tab (detailed metrics, actions) + - Add scenario for filter combinations and edge cases + - Add scenario for sharing queue data + - Add scenario for time range selection + +--- + +## External APIs Module + +- **Coverage:** + - Accessed via side navigation under MORE + - Explorer tab: domain, endpoints, last used, rate, error %, avg. latency + - Filters: Deployment Environment, Service Name, Rpc Method, Show IP addresses + - Table pagination + - Share and Stage & Run Query buttons +- **Gaps/Updates:** + - Add scenario for customizing quick filters + - Add scenario for running and staging queries + - Add scenario for sharing API data + - Add scenario for edge cases in filters and table data + +--- + +## Alerts Module + +- **Coverage:** + - Alert Rules tab: list, search, create (New Alert), edit, delete, enable/disable, severity, labels, actions + - Triggered Alerts tab (visible in tablist) + - Configuration tab (visible in tablist) + - Table pagination +- **Gaps/Updates:** + - Add scenario for triggered alerts (view, acknowledge, resolve) + - Add scenario for alert configuration (settings, integrations) + - Add scenario for edge cases in alert creation and management + - Add scenario for searching and filtering alerts + +--- + +## Integrations Module + +- **Coverage:** + - Integrations tab: list, search, configure (e.g., AWS), request new integration + - One-click setup for AWS monitoring + - Request more integrations (form) +- **Gaps/Updates:** + - Add scenario for configuring integrations (step-by-step) + - Add scenario for searching and filtering integrations + - Add scenario for requesting new integrations + - Add scenario for edge cases (e.g., failed configuration) + +--- + +## Exceptions Module + +- **Coverage:** + - All Exceptions: list, search, filter (Deployment Environment, Service Name, Host Name, K8s Cluster/Deployment/Namespace, Net Peer Name) + - Table: Exception Type, Error Message, Count, Last Seen, First Seen, Application + - Pagination + - Exception detail links + - Share and Stage & Run Query buttons +- **Gaps/Updates:** + - Add scenario for exception detail view + - Add scenario for advanced filtering and edge cases + - Add scenario for sharing and running queries + - Add scenario for error grouping and navigation + +--- + +## Service Map Module + +- **Coverage:** + - Service Map visualization (main graph) + - Filters: environment, resource attributes + - Time range selection + - Sync and Share buttons +- **Gaps/Updates:** + - Add scenario for interacting with the map (zoom, pan, select service) + - Add scenario for filtering and edge cases + - Add scenario for sharing the map + - Add scenario for time range and environment combinations + +--- + +## Billing Module + +- **Coverage:** + - Billing overview: cost monitoring, invoices, CSV download (disabled), manage billing (disabled) + - Teams Cloud section + - Billing table: Unit, Data Ingested, Price per Unit, Cost (Billing period to date) +- **Gaps/Updates:** + - Add scenario for invoice download and management (when enabled) + - Add scenario for cost monitoring and edge cases + - Add scenario for billing table data validation + - Add scenario for permissions and access control + +--- + +## Usage Explorer Module + +- **Status:** + - Not accessible in the current environment. Removing from test plan flows. + +--- + +## [Next modules will be filled as validation proceeds] diff --git a/frontend/e2e/tests/settings/account-settings/account-settings-settings.spec.ts b/frontend/e2e/tests/settings/account-settings/account-settings-settings.spec.ts new file mode 100644 index 000000000000..0cf15e66cec3 --- /dev/null +++ b/frontend/e2e/tests/settings/account-settings/account-settings-settings.spec.ts @@ -0,0 +1,42 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('Account Settings - View and Assert Static Controls', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Assert General section and controls (confirmed by DOM) + await expect( + page.getByLabel('My Settings').getByText('General'), + ).toBeVisible(); + await expect(page.getByText('Manage your account settings.')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Update name' })).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Reset password' }), + ).toBeVisible(); + + // Assert User Preferences section and controls (confirmed by DOM) + await expect(page.getByText('User Preferences')).toBeVisible(); + await expect( + page.getByText('Tailor the SigNoz console to work according to your needs.'), + ).toBeVisible(); + await expect(page.getByText('Select your theme')).toBeVisible(); + + const themeSelector = page.getByTestId('theme-selector'); + + await expect(themeSelector.getByText('Dark')).toBeVisible(); + await expect(themeSelector.getByText('Light')).toBeVisible(); + await expect(themeSelector.getByText('System')).toBeVisible(); + + await expect(page.getByTestId('timezone-adaptation-switch')).toBeVisible(); + await expect(page.getByTestId('side-nav-pinned-switch')).toBeVisible(); +}); diff --git a/frontend/e2e/tests/settings/api-keys/api-keys-settings.spec.ts b/frontend/e2e/tests/settings/api-keys/api-keys-settings.spec.ts new file mode 100644 index 000000000000..e91742e47ae5 --- /dev/null +++ b/frontend/e2e/tests/settings/api-keys/api-keys-settings.spec.ts @@ -0,0 +1,42 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('API Keys Settings - View and Interact', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click API Keys tab in the settings sidebar (by data-testid) + await page.getByTestId('api-keys').click(); + + // Assert heading and subheading + await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible(); + await expect( + page.getByText('Create and manage API keys for the SigNoz API'), + ).toBeVisible(); + + // Assert presence of New Key button + const newKeyBtn = page.getByRole('button', { name: 'New Key' }); + await expect(newKeyBtn).toBeVisible(); + + // Assert table columns + await expect(page.getByText('Last used').first()).toBeVisible(); + await expect(page.getByText('Expired').first()).toBeVisible(); + + // Assert at least one API key row with action buttons + // Select the first action cell's first button (icon button) + const firstActionCell = page.locator('table tr').nth(1).locator('td').last(); + const deleteBtn = firstActionCell.locator('button').first(); + await expect(deleteBtn).toBeVisible(); +}); diff --git a/frontend/e2e/tests/settings/billing/billing-settings.spec.ts b/frontend/e2e/tests/settings/billing/billing-settings.spec.ts new file mode 100644 index 000000000000..b8805426d2df --- /dev/null +++ b/frontend/e2e/tests/settings/billing/billing-settings.spec.ts @@ -0,0 +1,71 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +// E2E: Billing Settings - View Billing Information and Button Actions + +test('View Billing Information and Button Actions', async ({ + page, + context, +}) => { + // Ensure user is logged in + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click Billing tab in the settings sidebar (by data-testid) + await page.getByTestId('billing').click(); + + // Wait for billing chart/data to finish loading + await page.getByText('loading').first().waitFor({ state: 'hidden' }); + + // Assert visibility of subheading (unique) + await expect( + page.getByText( + 'Manage your billing information, invoices, and monitor costs.', + ), + ).toBeVisible(); + // Assert visibility of Teams Cloud heading + await expect(page.getByRole('heading', { name: 'Teams Cloud' })).toBeVisible(); + + // Assert presence of summary and detailed tables + await expect(page.getByText('TOTAL SPENT')).toBeVisible(); + await expect(page.getByText('Data Ingested')).toBeVisible(); + await expect(page.getByText('Price per Unit')).toBeVisible(); + await expect(page.getByText('Cost (Billing period to date)')).toBeVisible(); + + // Assert presence of alert and note + await expect( + page.getByText('Your current billing period is from', { exact: false }), + ).toBeVisible(); + await expect( + page.getByText('Billing metrics are updated once every 24 hours.'), + ).toBeVisible(); + + // Test Download CSV button + const [download] = await Promise.all([ + page.waitForEvent('download'), + page.getByRole('button', { name: 'cloud-download Download CSV' }).click(), + ]); + // Optionally, check download file name + expect(download.suggestedFilename()).toContain('billing_usage'); + + // Test Manage Billing button (opens Stripe in new tab) + const [newPage] = await Promise.all([ + context.waitForEvent('page'), + page.getByTestId('header-billing-button').click(), + ]); + await newPage.waitForLoadState(); + expect(newPage.url()).toContain('stripe.com'); + await newPage.close(); +}); diff --git a/frontend/e2e/tests/settings/custom-domain/custom-domain-settings.spec.ts b/frontend/e2e/tests/settings/custom-domain/custom-domain-settings.spec.ts new file mode 100644 index 000000000000..19a0247b4538 --- /dev/null +++ b/frontend/e2e/tests/settings/custom-domain/custom-domain-settings.spec.ts @@ -0,0 +1,52 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('Custom Domain Settings - View and Interact', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click Custom Domain tab in the settings sidebar (by data-testid) + await page.getByTestId('custom-domain').click(); + + // Wait for custom domain chart/data to finish loading + await page.getByText('loading').first().waitFor({ state: 'hidden' }); + + // Assert heading and subheading + await expect( + page.getByRole('heading', { name: 'Custom Domain Settings' }), + ).toBeVisible(); + await expect( + page.getByText('Personalize your workspace domain effortlessly.'), + ).toBeVisible(); + + // Assert presence of Customize team’s URL button + const customizeBtn = page.getByRole('button', { + name: 'Customize team’s URL', + }); + await expect(customizeBtn).toBeVisible(); + await customizeBtn.click(); + + // Assert modal/dialog fields and buttons + await expect( + page.getByRole('dialog', { name: 'Customize your team’s URL' }), + ).toBeVisible(); + await expect(page.getByLabel('Team’s URL subdomain')).toBeVisible(); + await expect( + page.getByRole('button', { name: 'Apply Changes' }), + ).toBeVisible(); + await expect(page.getByRole('button', { name: 'Close' })).toBeVisible(); + // Close the modal + await page.getByRole('button', { name: 'Close' }).click(); +}); diff --git a/frontend/e2e/tests/settings/general/general-settings.spec.ts b/frontend/e2e/tests/settings/general/general-settings.spec.ts new file mode 100644 index 000000000000..346659fe3b3e --- /dev/null +++ b/frontend/e2e/tests/settings/general/general-settings.spec.ts @@ -0,0 +1,32 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('View General Settings', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click General tab in the settings sidebar (by data-testid) + await page.getByTestId('general').click(); + + // Wait for General tab to be visible + await page.getByRole('tabpanel', { name: 'General' }).waitFor(); + + // Assert visibility of definitive/static elements + await expect(page.getByRole('heading', { name: 'Metrics' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Traces' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Logs' })).toBeVisible(); + await expect(page.getByText('Please')).toBeVisible(); + await expect(page.getByRole('link', { name: 'email us' })).toBeVisible(); +}); diff --git a/frontend/e2e/tests/settings/ingestion/ingestion-settings.spec.ts b/frontend/e2e/tests/settings/ingestion/ingestion-settings.spec.ts new file mode 100644 index 000000000000..c18fee0c8042 --- /dev/null +++ b/frontend/e2e/tests/settings/ingestion/ingestion-settings.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('Ingestion Settings - View and Interact', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click Ingestion tab in the settings sidebar (by data-testid) + await page.getByTestId('ingestion').click(); + + // Assert heading and subheading (Integrations page) + await expect( + page.getByRole('heading', { name: 'Integrations' }), + ).toBeVisible(); + await expect( + page.getByText('Manage Integrations for this workspace'), + ).toBeVisible(); + + // Assert presence of search box + await expect( + page.getByPlaceholder('Search for an integration...'), + ).toBeVisible(); + + // Assert at least one data source with Configure button + const configureBtn = page.getByRole('button', { name: 'Configure' }).first(); + await expect(configureBtn).toBeVisible(); + + // Assert Request more integrations section + await expect( + page.getByText( + "Can't find what you’re looking for? Request more integrations", + ), + ).toBeVisible(); + await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible(); +}); diff --git a/frontend/e2e/tests/settings/integrations/integrations-settings.spec.ts b/frontend/e2e/tests/settings/integrations/integrations-settings.spec.ts new file mode 100644 index 000000000000..3c55614cec50 --- /dev/null +++ b/frontend/e2e/tests/settings/integrations/integrations-settings.spec.ts @@ -0,0 +1,48 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('Integrations Settings - View and Interact', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click Integrations tab in the settings sidebar (by data-testid) + await page.getByTestId('integrations').click(); + + // Assert heading and subheading + await expect( + page.getByRole('heading', { name: 'Integrations' }), + ).toBeVisible(); + await expect( + page.getByText('Manage Integrations for this workspace'), + ).toBeVisible(); + + // Assert presence of search box + await expect( + page.getByPlaceholder('Search for an integration...'), + ).toBeVisible(); + + // Assert at least one integration with Configure button + const configureBtn = page.getByRole('button', { name: 'Configure' }).first(); + await expect(configureBtn).toBeVisible(); + + // Assert Request more integrations section + await expect( + page.getByText( + "Can't find what you’re looking for? Request more integrations", + ), + ).toBeVisible(); + await expect(page.getByPlaceholder('Enter integration name...')).toBeVisible(); + await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible(); +}); diff --git a/frontend/e2e/tests/settings/members-sso/members-sso-settings.spec.ts b/frontend/e2e/tests/settings/members-sso/members-sso-settings.spec.ts new file mode 100644 index 000000000000..02d9880fbfe6 --- /dev/null +++ b/frontend/e2e/tests/settings/members-sso/members-sso-settings.spec.ts @@ -0,0 +1,56 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('Members & SSO Settings - View and Interact', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click Members & SSO tab in the settings sidebar (by data-testid) + await page.getByTestId('members-sso').click(); + + // Assert headings and tables + await expect( + page.getByRole('heading', { name: /Members \(\d+\)/ }), + ).toBeVisible(); + await expect( + page.getByRole('heading', { name: /Pending Invites \(\d+\)/ }), + ).toBeVisible(); + await expect( + page.getByRole('heading', { name: 'Authenticated Domains' }), + ).toBeVisible(); + + // Assert Invite Members button is visible and clickable + const inviteBtn = page.getByRole('button', { name: /Invite Members/ }); + await expect(inviteBtn).toBeVisible(); + await inviteBtn.click(); + // Assert Invite Members modal/dialog appears (modal title is unique) + await expect(page.getByText('Invite team members').first()).toBeVisible(); + // Close the modal (use unique 'Close' button) + await page.getByRole('button', { name: 'Close' }).click(); + + // Assert Edit and Delete buttons are present for at least one member + const editBtn = page.getByRole('button', { name: /Edit/ }).first(); + const deleteBtn = page.getByRole('button', { name: /Delete/ }).first(); + await expect(editBtn).toBeVisible(); + await expect(deleteBtn).toBeVisible(); + + // Assert Add Domains button is visible + await expect(page.getByRole('button', { name: /Add Domains/ })).toBeVisible(); + // Assert Configure SSO or Edit Google Auth button is visible for at least one domain + const ssoBtn = page + .getByRole('button', { name: /Configure SSO|Edit Google Auth/ }) + .first(); + await expect(ssoBtn).toBeVisible(); +}); diff --git a/frontend/e2e/tests/settings/notification-channels/notification-channels-settings.spec.ts b/frontend/e2e/tests/settings/notification-channels/notification-channels-settings.spec.ts new file mode 100644 index 000000000000..432597671f1f --- /dev/null +++ b/frontend/e2e/tests/settings/notification-channels/notification-channels-settings.spec.ts @@ -0,0 +1,57 @@ +import { expect, test } from '@playwright/test'; + +import { ensureLoggedIn } from '../../../utils/login.util'; + +test('Notification Channels Settings - View and Interact', async ({ page }) => { + await ensureLoggedIn(page); + + // 1. Open the sidebar settings menu using data-testid + await page.getByTestId('settings-nav-item').click(); + + // 2. Click Account Settings in the dropdown (by role/name or data-testid if available) + await page.getByRole('menuitem', { name: 'Account Settings' }).click(); + + // Assert the main tabpanel/heading (confirmed by DOM) + await expect(page.getByTestId('settings-page-title')).toBeVisible(); + + // Focus on the settings page sidenav + await page.getByTestId('settings-page-sidenav').focus(); + + // Click Notification Channels tab in the settings sidebar (by data-testid) + await page.getByTestId('notification-channels').click(); + + // Wait for loading to finish + await page.getByText('loading').first().waitFor({ state: 'hidden' }); + + // Assert presence of New Alert Channel button + const newChannelBtn = page.getByRole('button', { name: /New Alert Channel/ }); + await expect(newChannelBtn).toBeVisible(); + + // Assert table columns + await expect(page.getByText('Name')).toBeVisible(); + await expect(page.getByText('Type')).toBeVisible(); + await expect(page.getByText('Action')).toBeVisible(); + + // Click New Alert Channel and assert modal fields/buttons + await newChannelBtn.click(); + await expect( + page.getByRole('heading', { name: 'New Notification Channel' }), + ).toBeVisible(); + await expect(page.getByLabel('Name')).toBeVisible(); + await expect(page.getByLabel('Type')).toBeVisible(); + await expect(page.getByLabel('Webhook URL')).toBeVisible(); + await expect( + page.getByRole('switch', { name: 'Send resolved alerts' }), + ).toBeVisible(); + await expect(page.getByRole('button', { name: 'Save' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Test' })).toBeVisible(); + await expect(page.getByRole('button', { name: 'Back' })).toBeVisible(); + // Close modal + await page.getByRole('button', { name: 'Back' }).click(); + + // Assert Edit and Delete buttons for at least one channel + const editBtn = page.getByRole('button', { name: 'Edit' }).first(); + const deleteBtn = page.getByRole('button', { name: 'Delete' }).first(); + await expect(editBtn).toBeVisible(); + await expect(deleteBtn).toBeVisible(); +}); diff --git a/frontend/e2e/utils/login.util.ts b/frontend/e2e/utils/login.util.ts new file mode 100644 index 000000000000..c425e31e72e1 --- /dev/null +++ b/frontend/e2e/utils/login.util.ts @@ -0,0 +1,35 @@ +import { Page } from '@playwright/test'; + +// Read credentials from environment variables +const username = process.env.LOGIN_USERNAME; +const password = process.env.LOGIN_PASSWORD; +const baseURL = process.env.BASE_URL; + +/** + * Ensures the user is logged in. If not, performs the login steps. + * Follows the MCP process step-by-step. + */ +export async function ensureLoggedIn(page: Page): Promise { + // if already in home page, return + if (await page.url().includes('/home')) { + return; + } + + if (!username || !password) { + throw new Error( + 'E2E_EMAIL and E2E_PASSWORD environment variables must be set.', + ); + } + + await page.goto(`${baseURL}/login`); + await page.getByTestId('email').click(); + await page.getByTestId('email').fill(username); + await page.getByTestId('initiate_login').click(); + await page.getByTestId('password').click(); + await page.getByTestId('password').fill(password); + await page.getByRole('button', { name: 'Login' }).click(); + + await page + .getByText('Hello there, Welcome to your') + .waitFor({ state: 'visible' }); +} diff --git a/frontend/package.json b/frontend/package.json index 900e637cadc8..f1792ca6173f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "@mdx-js/loader": "2.3.0", "@mdx-js/react": "2.3.0", "@monaco-editor/react": "^4.3.1", + "@playwright/test": "1.54.1", "@radix-ui/react-tabs": "1.0.4", "@radix-ui/react-tooltip": "1.0.7", "@sentry/react": "8.41.0", diff --git a/frontend/playwright.config.ts b/frontend/playwright.config.ts new file mode 100644 index 000000000000..88ee733baa1b --- /dev/null +++ b/frontend/playwright.config.ts @@ -0,0 +1,95 @@ +import { defineConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; +import path from 'path'; + +// Read from ".env" file. +dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './e2e/tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Run tests in parallel even in CI - optimized for GitHub Actions free tier */ + workers: process.env.CI ? 2 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: + process.env.SIGNOZ_E2E_BASE_URL || 'https://app.us.staging.signoz.cloud', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + colorScheme: 'dark', + locale: 'en-US', + viewport: { width: 1280, height: 720 }, + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { + launchOptions: { args: ['--start-maximized'] }, + viewport: null, + colorScheme: 'dark', + locale: 'en-US', + baseURL: 'https://app.us.staging.signoz.cloud', + trace: 'on-first-retry', + }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://localhost:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/frontend/prompts/generate-e2e-test.md b/frontend/prompts/generate-e2e-test.md new file mode 100644 index 000000000000..29caf5ab61bb --- /dev/null +++ b/frontend/prompts/generate-e2e-test.md @@ -0,0 +1,16 @@ +RULE: All test code for this repo must be generated by following the step-by-step Playwright MCP process as described below. + +- You are a playwright test generator. +- You are given a scenario and you need to generate a playwright test for it. +- Use login util if not logged in. +- DO NOT generate test code based on the scenario alone. +- DO run steps one by one using the tools provided by the Playwright MCP. +- Only after all steps are completed, emit a Playwright TypeScript test that uses @playwright/test based on message history +- Gather correct selectors before writing the test +- DO NOT valiate for dynamic content in the tests, only validate for the correctness with meta data +- Always inspect the DOM at each navigation or interaction step to determine the correct selector for the next action. Do not assume selectors, confirm via inspection before proceeding. +- Assert visibility of definitive/static elements in the UI (such as labels, headings, or section titles) rather than dynamic values or content that may change between runs. +- Save generated test file in the tests directory +- Execute the test file and iterate until the test passes + + diff --git a/frontend/src/components/ChangelogModal/ChangelogModal.tsx b/frontend/src/components/ChangelogModal/ChangelogModal.tsx index db755eb2598b..129ca9897188 100644 --- a/frontend/src/components/ChangelogModal/ChangelogModal.tsx +++ b/frontend/src/components/ChangelogModal/ChangelogModal.tsx @@ -118,12 +118,6 @@ function ChangelogModal({ changelog, onClose }: Props): JSX.Element {
- {changelog?.features && changelog.features.length > 0 && ( - - {changelog.features.length} new  - {changelog.features.length > 1 ? 'features' : 'feature'} - - )} {!isCloudUser && (
diff --git a/frontend/src/container/MySettings/index.tsx b/frontend/src/container/MySettings/index.tsx index 4fd4d2b163bf..b2650da7adbd 100644 --- a/frontend/src/container/MySettings/index.tsx +++ b/frontend/src/container/MySettings/index.tsx @@ -200,6 +200,7 @@ function MySettings(): JSX.Element { checked={sideNavPinned} onChange={handleSideNavPinnedChange} loading={isUpdatingUserPreference} + data-testid="side-nav-pinned-switch" />
diff --git a/frontend/src/container/SideNav/NavItem/NavItem.tsx b/frontend/src/container/SideNav/NavItem/NavItem.tsx index 5473aae35e37..a928b71a8d63 100644 --- a/frontend/src/container/SideNav/NavItem/NavItem.tsx +++ b/frontend/src/container/SideNav/NavItem/NavItem.tsx @@ -16,6 +16,7 @@ export default function NavItem({ onTogglePin, isPinned, showIcon, + dataTestId, }: { item: SidebarItem; isActive: boolean; @@ -24,6 +25,7 @@ export default function NavItem({ onTogglePin?: (item: SidebarItem) => void; isPinned?: boolean; showIcon?: boolean; + dataTestId?: string; }): JSX.Element { const { label, icon, isBeta, isNew } = item; @@ -47,6 +49,7 @@ export default function NavItem({ } onClick(event); }} + data-testid={dataTestId} > {showIcon &&
}
@@ -96,4 +99,5 @@ NavItem.defaultProps = { onTogglePin: undefined, isPinned: false, showIcon: false, + dataTestId: undefined, }; diff --git a/frontend/src/container/SideNav/SideNav.styles.scss b/frontend/src/container/SideNav/SideNav.styles.scss index 6a1ad75ff6a0..fdbe6193c139 100644 --- a/frontend/src/container/SideNav/SideNav.styles.scss +++ b/frontend/src/container/SideNav/SideNav.styles.scss @@ -830,7 +830,7 @@ & span { display: block; - max-width: 170px; + max-width: 286px; white-space: nowrap; /* Prevents line breaks */ overflow: hidden; /* Hides overflowing content */ text-overflow: ellipsis; @@ -841,7 +841,7 @@ .ant-dropdown-menu { margin-left: 8px !important; padding: 0px !important; - width: 216px !important; + width: 360px !important; border-radius: 3px !important; // Glass blur diff --git a/frontend/src/container/SideNav/SideNav.tsx b/frontend/src/container/SideNav/SideNav.tsx index 5fcbe51e6be5..8d857b06cbfa 100644 --- a/frontend/src/container/SideNav/SideNav.tsx +++ b/frontend/src/container/SideNav/SideNav.tsx @@ -465,22 +465,26 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element {
), disabled: true, + dataTestId: 'logged-in-as-nav-item', }, { type: 'divider' as const }, { key: 'account', label: 'Account Settings', + dataTestId: 'account-settings-nav-item', }, { key: 'workspace', label: 'Workspace Settings', disabled: isWorkspaceBlocked, + dataTestId: 'workspace-settings-nav-item', }, ...(isEnterpriseSelfHostedUser || isCommunityEnterpriseUser ? [ { key: 'license', label: 'Manage License', + dataTestId: 'manage-license-nav-item', }, ] : []), @@ -490,6 +494,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { label: ( Sign out ), + dataTestId: 'logout-nav-item', }, ].filter(Boolean), [ @@ -561,6 +566,9 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { if (dropdownItems.length === 0) { return [ ...prevState, + { + type: 'divider', + }, { key: changelogKey, label: ( @@ -571,12 +579,17 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { ), icon: , itemKey: changelogKey, + isExternal: true, + url: 'https://signoz.io/changelog/', }, ]; } return [ ...prevState, + { + type: 'divider', + }, { type: 'group', label: "WHAT's NEW", @@ -592,6 +605,8 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { ), icon: , itemKey: changelogKey, + isExternal: true, + url: 'https://signoz.io/changelog/', }, ]; }); @@ -764,7 +779,6 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { break; case 'changelog-1': case 'changelog-2': - case CHANGELOG_LABEL.toLowerCase().replace(' ', '-'): toggleChangelogModal(); break; default: @@ -1035,7 +1049,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { trigger={['click']} >
-
+
{helpSupportMenuItem.icon}
{helpSupportMenuItem.label}
@@ -1055,7 +1069,7 @@ function SideNav({ isPinned }: { isPinned: boolean }): JSX.Element { trigger={['click']} >
-
+
{userSettingsMenuItem.icon}
{userSettingsMenuItem.label}
diff --git a/frontend/src/container/SideNav/menuItems.tsx b/frontend/src/container/SideNav/menuItems.tsx index 7c20e98a4551..ca4f20fb11db 100644 --- a/frontend/src/container/SideNav/menuItems.tsx +++ b/frontend/src/container/SideNav/menuItems.tsx @@ -1,6 +1,7 @@ import { RocketOutlined } from '@ant-design/icons'; import ROUTES from 'constants/routes'; import { + ArrowUpRight, BarChart2, BellDot, Binoculars, @@ -355,7 +356,12 @@ export const settingsMenuItems: SidebarItem[] = [ export const helpSupportDropdownMenuItems: SidebarItem[] = [ { key: 'documentation', - label: 'Documentation', + label: ( +
+ Documentation + +
+ ), icon: , isExternal: true, url: 'https://signoz.io/docs', @@ -363,7 +369,13 @@ export const helpSupportDropdownMenuItems: SidebarItem[] = [ }, { key: 'github', - label: 'GitHub', + label: ( +
+ GitHub + +
+ ), + icon: , isExternal: true, url: 'https://github.com/signoz/signoz', @@ -371,7 +383,12 @@ export const helpSupportDropdownMenuItems: SidebarItem[] = [ }, { key: 'slack', - label: 'Community Slack', + label: ( +
+ Community Slack + +
+ ), icon: , isExternal: true, url: 'https://signoz.io/slack', diff --git a/frontend/src/pages/Settings/Settings.tsx b/frontend/src/pages/Settings/Settings.tsx index 0b5252e519fc..7643b54c531e 100644 --- a/frontend/src/pages/Settings/Settings.tsx +++ b/frontend/src/pages/Settings/Settings.tsx @@ -232,14 +232,17 @@ function SettingsPage(): JSX.Element { return (
-
+
Settings
-
+
{settingsMenuItems .filter((item) => item.isEnabled) .map((item) => ( @@ -256,6 +259,7 @@ function SettingsPage(): JSX.Element { }); handleMenuItemClick((event as unknown) as MouseEvent, item); }} + dataTestId={item.itemKey} /> ))}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index 96d2f8f388ec..051040298c21 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -48,6 +48,8 @@ "./webpack.config.prod.js", "./jest.setup.ts", "./tests/**.ts", - "./**/*.d.ts" + "./**/*.d.ts", + "./playwright.config.ts", + "./e2e/**/*.ts" ] } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index a08d50896a61..9156191cd4cc 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3404,6 +3404,13 @@ resolved "https://registry.npmjs.org/@petamoriken/float16/-/float16-3.8.0.tgz" integrity sha512-AhVAm6SQ+zgxIiOzwVdUcDmKlu/qU39FiYD2UD6kQQaVenrn0dGZewIghWAENGQsvC+1avLCuT+T2/3Gsp/W3w== +"@playwright/test@1.54.1": + version "1.54.1" + resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.54.1.tgz#a76333e5c2cba5f12f96a6da978bba3ab073c7e6" + integrity sha512-FS8hQ12acieG2dYSksmLOF7BNxnVf2afRJdCuM1eMSxj6QTSE6G4InGF7oApGgDb65MX7AwMVlIkpru0yZA4Xw== + dependencies: + playwright "1.54.1" + "@polka/url@^1.0.0-next.20": version "1.0.0-next.21" resolved "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz" @@ -9075,7 +9082,7 @@ fscreen@^1.0.2: resolved "https://registry.yarnpkg.com/fscreen/-/fscreen-1.2.0.tgz#1a8c88e06bc16a07b473ad96196fb06d6657f59e" integrity sha512-hlq4+BU0hlPmwsFjwGGzZ+OZ9N/wq9Ljg/sq3pX+2CD7hrJsX9tJgWWK/wiNTFM212CLHWhicOoqwXyZGGetJg== -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -13447,6 +13454,20 @@ pkg-dir@^7.0.0: dependencies: find-up "^6.3.0" +playwright-core@1.54.1: + version "1.54.1" + resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.54.1.tgz#d32edcce048c9d83ceac31e294a7b60ef586960b" + integrity sha512-Nbjs2zjj0htNhzgiy5wu+3w09YetDx5pkrpI/kZotDlDUaYk0HVA5xrBVPdow4SAUIlhgKcJeJg4GRKW6xHusA== + +playwright@1.54.1: + version "1.54.1" + resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.54.1.tgz#128d66a8d5182b5330e6440be3a72ca313362788" + integrity sha512-peWpSwIBmSLi6aW2auvrUtf2DqY16YYcCMO8rTVx486jKmDTJg7UAhyrraP98GB8BoPURZP8+nxO7TSd4cPr5g== + dependencies: + playwright-core "1.54.1" + optionalDependencies: + fsevents "2.3.2" + polished@4: version "4.2.2" resolved "https://registry.npmjs.org/polished/-/polished-4.2.2.tgz"