adserver-dashboard 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ci/staging.yml +191 -0
- package/.dockerignore +117 -0
- package/.env +40 -0
- package/.env.staging +38 -0
- package/.gitlab-ci.yml +16 -0
- package/DEMO_STATUS.md +579 -0
- package/Dockerfile +61 -0
- package/Influence-MW-AdServer-12-02-2026/client/index.html +17 -0
- package/Influence-MW-AdServer-12-02-2026/client/public/favicon.png +0 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/App.tsx +91 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/advanced-map-drawer.tsx +1131 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ai-recommendation-panel.tsx +379 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/app-sidebar.tsx +183 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/auto-optimize-button.tsx +184 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/availability-drawer.tsx +385 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/brand-insights-panel.tsx +87 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/create-agency-drawer.tsx +198 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/create-brand-drawer.tsx +275 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/creative-assignment.tsx +526 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table-toolbar.tsx +148 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/data-table.tsx +158 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/filter-drawer.tsx +356 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/form-insights-panel.tsx +82 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/geography-selector.tsx +699 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/header-user-menu.tsx +178 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/history-drawer.tsx +313 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-availability-section.tsx +176 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-format-drawer.tsx +173 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/inventory-selector.tsx +401 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/manual-inventory-drawer.tsx +368 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/mapbox-map.tsx +368 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/market-insights-panel.tsx +202 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/media-owner-drawer.tsx +217 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/metric-card.tsx +58 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/page-header.tsx +27 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/player-status-indicator.tsx +137 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/poi-targeting-drawer.tsx +298 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/recommendation-score-badge.tsx +102 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/recommended-inventories-panel.tsx +248 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/searchable-combobox.tsx +134 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/signal-visualizations.tsx +407 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/status-badge.tsx +35 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-provider.tsx +73 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/theme-toggle.tsx +37 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/traffic-slider.tsx +75 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/accordion.tsx +56 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert-dialog.tsx +139 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/alert.tsx +59 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/aspect-ratio.tsx +5 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/avatar.tsx +51 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/badge.tsx +38 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/breadcrumb.tsx +115 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/button.tsx +62 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/calendar.tsx +68 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/card.tsx +85 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/carousel.tsx +260 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/chart.tsx +365 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/checkbox.tsx +28 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/collapsible.tsx +11 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/command.tsx +151 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/context-menu.tsx +198 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dialog.tsx +122 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/drawer.tsx +118 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/dropdown-menu.tsx +198 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/form.tsx +178 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/hover-card.tsx +29 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input-otp.tsx +69 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/input.tsx +23 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/label.tsx +24 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/menubar.tsx +256 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/navigation-menu.tsx +128 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/pagination.tsx +117 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/popover.tsx +29 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/progress.tsx +28 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/radio-group.tsx +42 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/resizable.tsx +45 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/scroll-area.tsx +46 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/select.tsx +160 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/separator.tsx +29 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sheet.tsx +140 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/sidebar.tsx +727 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/skeleton.tsx +15 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/slider.tsx +26 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/switch.tsx +27 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/table.tsx +117 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tabs.tsx +53 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/textarea.tsx +22 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toast.tsx +127 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toaster.tsx +33 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle-group.tsx +61 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/toggle.tsx +43 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/ui/tooltip.tsx +30 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/vendor-stores-modal.tsx +336 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-drawer.tsx +359 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/components/venue-type-selector.tsx +436 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-mobile.tsx +19 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/hooks/use-toast.ts +191 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/index.css +244 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/lib/queryClient.ts +57 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/lib/utils.ts +39 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/lib/venue-taxonomy.ts +532 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/main.tsx +5 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/assign-creative.tsx +781 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/content-hub.tsx +995 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/custom-pois.tsx +431 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/dashboard.tsx +620 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-detail.tsx +1062 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/deal-form.tsx +1570 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/deals.tsx +716 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/edit-creative-assignment.tsx +1051 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/geotargeting.tsx +675 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/integrations.tsx +425 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-creatives.tsx +622 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-item-form.tsx +3132 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/line-items.tsx +530 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/not-found.tsx +21 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play-upload.tsx +479 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/proof-of-play.tsx +880 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/reports.tsx +235 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/settings.tsx +652 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/signal-form.tsx +1117 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/signals.tsx +366 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/tags.tsx +332 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/pages/venues.tsx +381 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/types/mapbox-gl-draw.d.ts +37 -0
- package/Influence-MW-AdServer-12-02-2026/client/src/types/react-simple-maps.d.ts +57 -0
- package/Influence-MW-AdServer-12-02-2026/components.json +20 -0
- package/Influence-MW-AdServer-12-02-2026/docs/PRD.md +3373 -0
- package/Influence-MW-AdServer-12-02-2026/docs/influence-feature-mapping.csv +498 -0
- package/Influence-MW-AdServer-12-02-2026/drizzle.config.ts +14 -0
- package/Influence-MW-AdServer-12-02-2026/package-lock.json +9672 -0
- package/Influence-MW-AdServer-12-02-2026/package.json +118 -0
- package/Influence-MW-AdServer-12-02-2026/postcss.config.js +6 -0
- package/Influence-MW-AdServer-12-02-2026/replit.md +91 -0
- package/Influence-MW-AdServer-12-02-2026/script/build.ts +67 -0
- package/Influence-MW-AdServer-12-02-2026/scripts/create-miro-diagrams.cjs +318 -0
- package/Influence-MW-AdServer-12-02-2026/scripts/create-remaining-diagrams.cjs +270 -0
- package/Influence-MW-AdServer-12-02-2026/server/index.ts +103 -0
- package/Influence-MW-AdServer-12-02-2026/server/recommendation-service.ts +319 -0
- package/Influence-MW-AdServer-12-02-2026/server/routes.ts +1890 -0
- package/Influence-MW-AdServer-12-02-2026/server/static.ts +19 -0
- package/Influence-MW-AdServer-12-02-2026/server/storage.ts +2058 -0
- package/Influence-MW-AdServer-12-02-2026/server/vite.ts +58 -0
- package/Influence-MW-AdServer-12-02-2026/shared/schema.ts +1595 -0
- package/Influence-MW-AdServer-12-02-2026/tailwind.config.ts +107 -0
- package/Influence-MW-AdServer-12-02-2026/tsconfig.json +23 -0
- package/Influence-MW-AdServer-12-02-2026/vite.config.ts +40 -0
- package/LINE_ITEM_BUDGET_FIELD_MAPPING.md +178 -0
- package/PCM/.env.example +92 -0
- package/PCM/README.md +558 -0
- package/PCM/docs/TEST_CASES.md +422 -0
- package/PCM/index.js +106 -0
- package/PCM/package-lock.json +3282 -0
- package/PCM/package.json +32 -0
- package/PCM/replit.md +64 -0
- package/PCM/schema.sql +495 -0
- package/PCM/scripts/export-schema.js +183 -0
- package/PCM/scripts/seed-comprehensive.js +631 -0
- package/PCM/scripts/seed-production.js +477 -0
- package/PCM/src/config/db.js +56 -0
- package/PCM/src/config/swagger.js +5975 -0
- package/PCM/src/dto/EmailRequestDTO.js +166 -0
- package/PCM/src/middleware/errorHandler.js +52 -0
- package/PCM/src/middleware/logger.js +26 -0
- package/PCM/src/migrations/001_add_campaign_mode_fields.sql +36 -0
- package/PCM/src/migrations/002_create_deal_id_counters.sql +22 -0
- package/PCM/src/migrations/003_update_publishers_column.sql +15 -0
- package/PCM/src/migrations/004_add_direct_dealtype_and_advertiser.sql +5 -0
- package/PCM/src/migrations/005_add_programmatic_fields_and_update_enums.sql +31 -0
- package/PCM/src/migrations/006_add_line_item_programmatic_fields.sql +12 -0
- package/PCM/src/migrations/007_add_line_item_direct_fields.sql +15 -0
- package/PCM/src/migrations/008_add_inventory_fields.sql +45 -0
- package/PCM/src/migrations/009_move_inventory_fields_to_metadata.sql +32 -0
- package/PCM/src/migrations/010_add_draft_status_and_line_items_count.sql +23 -0
- package/PCM/src/migrations/011_add_planning_field.sql +21 -0
- package/PCM/src/migrations/012_fix_inventory_composite_pk.sql +17 -0
- package/PCM/src/migrations/013_make_external_id_optional.sql +3 -0
- package/PCM/src/migrations/014_create_change_history.sql +38 -0
- package/PCM/src/migrations/016_create_publisher_insertion_orders.sql +33 -0
- package/PCM/src/migrations/017_fix_line_item_id_fk_reference.sql +86 -0
- package/PCM/src/migrations/018_create_approval_tables.sql +44 -0
- package/PCM/src/migrations/019_add_encrypted_token_column.sql +2 -0
- package/PCM/src/migrations/020_add_rejection_reason_to_deals.sql +10 -0
- package/PCM/src/migrations/021_add_publisher_external_id_to_inventories.sql +12 -0
- package/PCM/src/migrations/022_add_line_item_extended_fields.sql +24 -0
- package/PCM/src/migrations/023_add_base_price_fields.sql +8 -0
- package/PCM/src/migrations/run-migrations.js +46 -0
- package/PCM/src/models/ApprovalOTP.js +51 -0
- package/PCM/src/models/ApprovalToken.js +79 -0
- package/PCM/src/models/ChangeHistory.js +107 -0
- package/PCM/src/models/Deal.js +186 -0
- package/PCM/src/models/DealIdCounter.js +28 -0
- package/PCM/src/models/LineItem.js +227 -0
- package/PCM/src/models/LineItemCreative.js +89 -0
- package/PCM/src/models/LineItemInventory.js +115 -0
- package/PCM/src/models/PublisherInsertionOrder.js +93 -0
- package/PCM/src/models/TransactionHistory.js +34 -0
- package/PCM/src/models/associations.js +81 -0
- package/PCM/src/routes/approval.js +321 -0
- package/PCM/src/routes/creatives.js +437 -0
- package/PCM/src/routes/deals.js +1638 -0
- package/PCM/src/routes/digitalSignage.js +242 -0
- package/PCM/src/routes/insertionOrders.js +380 -0
- package/PCM/src/routes/lineItems.js +926 -0
- package/PCM/src/routes/system.js +384 -0
- package/PCM/src/services/ApprovalService.js +885 -0
- package/PCM/src/services/CampaignImportConverter.js +631 -0
- package/PCM/src/services/CampaignModeService.js +273 -0
- package/PCM/src/services/CampaignStatusService.js +395 -0
- package/PCM/src/services/ChangeHistoryService.js +316 -0
- package/PCM/src/services/DealIdService.js +94 -0
- package/PCM/src/services/DealResponseFormatter.js +90 -0
- package/PCM/src/services/EmailNotificationService.js +315 -0
- package/PCM/src/services/LineItemResponseFormatter.js +122 -0
- package/PCM/src/services/LineItemStatusService.js +380 -0
- package/PCM/src/tests/comprehensiveTestRunner.js +360 -0
- package/PCM/src/tests/comprehensiveTests.js +1277 -0
- package/PCM/src/tests/dealTypeUnitTests.js +1058 -0
- package/PCM/src/tests/testRunner.js +248 -0
- package/PCM/src/utils/caseConverter.js +92 -0
- package/PCM/src/utils/dealCalculations.js +206 -0
- package/PCM/src/utils/lineItemPayloadNormalizer.js +41 -0
- package/PCM/src/utils/payloadNormalizer.js +34 -0
- package/PCM/src/utils/sourceNormalizer.js +56 -0
- package/PCM/src/validators/creativeValidator.js +27 -0
- package/PCM/src/validators/dealValidator.js +203 -0
- package/PCM/src/validators/lineItemValidator.js +489 -0
- package/PCM/tests/approval-flows.test.js +238 -0
- package/PCM/tests/approval-workflow.test.js +291 -0
- package/PCM/tests/campaign-import-converter.test.js +543 -0
- package/PCM/tests/campaign-import-e2e.test.js +520 -0
- package/PCM/tests/campaign-status.test.js +539 -0
- package/PCM/tests/direct-publisher-split-reimport.test.js +460 -0
- package/PCM/tests/e2e/digital-signage.test.js +145 -0
- package/PCM/tests/e2e/search-filter-pagination.test.js +399 -0
- package/PCM/tests/e2e-comprehensive.test.js +3446 -0
- package/PCM/tests/edge-cases.test.js +340 -0
- package/PCM/tests/line-item-status.test.js +340 -0
- package/PCM/tests/seller-account-external-ids.test.js +877 -0
- package/PCM/tests/source-validation.test.js +324 -0
- package/PRD.md +3373 -0
- package/README.md +186 -0
- package/client/index.html +35 -0
- package/client/public/DEMO_STATUS.md +579 -0
- package/client/public/img/MW-logo-trans_1754045676555.png +0 -0
- package/client/public/locales/ar/approval.json +144 -0
- package/client/public/locales/ar/buyer.json +61 -0
- package/client/public/locales/ar/campaigns.json +1 -0
- package/client/public/locales/ar/common.json +218 -0
- package/client/public/locales/ar/contentHub.json +266 -0
- package/client/public/locales/ar/creatives.json +79 -0
- package/client/public/locales/ar/dashboard.json +57 -0
- package/client/public/locales/ar/deals.json +886 -0
- package/client/public/locales/ar/dsp.json +131 -0
- package/client/public/locales/ar/inventory.json +201 -0
- package/client/public/locales/ar/lineItems.json +553 -0
- package/client/public/locales/ar/navigation.json +48 -0
- package/client/public/locales/ar/wizard.json +1 -0
- package/client/public/locales/en/approval.json +144 -0
- package/client/public/locales/en/buyer.json +65 -0
- package/client/public/locales/en/campaigns.json +1 -0
- package/client/public/locales/en/common.json +218 -0
- package/client/public/locales/en/contentHub.json +266 -0
- package/client/public/locales/en/creatives.json +79 -0
- package/client/public/locales/en/dashboard.json +57 -0
- package/client/public/locales/en/deals.json +886 -0
- package/client/public/locales/en/dsp.json +131 -0
- package/client/public/locales/en/inventory.json +201 -0
- package/client/public/locales/en/lineItems.json +659 -0
- package/client/public/locales/en/navigation.json +48 -0
- package/client/public/locales/en/wizard.json +1 -0
- package/client/public/locales/ja/approval.json +144 -0
- package/client/public/locales/ja/buyer.json +61 -0
- package/client/public/locales/ja/campaigns.json +1 -0
- package/client/public/locales/ja/common.json +218 -0
- package/client/public/locales/ja/contentHub.json +266 -0
- package/client/public/locales/ja/creatives.json +79 -0
- package/client/public/locales/ja/dashboard.json +57 -0
- package/client/public/locales/ja/deals.json +886 -0
- package/client/public/locales/ja/dsp.json +131 -0
- package/client/public/locales/ja/inventory.json +201 -0
- package/client/public/locales/ja/lineItems.json +553 -0
- package/client/public/locales/ja/navigation.json +48 -0
- package/client/public/locales/ja/wizard.json +1 -0
- package/client/public/locales/zh/approval.json +144 -0
- package/client/public/locales/zh/buyer.json +61 -0
- package/client/public/locales/zh/campaigns.json +1 -0
- package/client/public/locales/zh/common.json +218 -0
- package/client/public/locales/zh/contentHub.json +266 -0
- package/client/public/locales/zh/creatives.json +79 -0
- package/client/public/locales/zh/dashboard.json +57 -0
- package/client/public/locales/zh/deals.json +886 -0
- package/client/public/locales/zh/dsp.json +131 -0
- package/client/public/locales/zh/inventory.json +201 -0
- package/client/public/locales/zh/lineItems.json +553 -0
- package/client/public/locales/zh/navigation.json +48 -0
- package/client/public/locales/zh/wizard.json +1 -0
- package/client/public/manifest.json +36 -0
- package/client/src/App.tsx +464 -0
- package/client/src/components/app-sidebar.tsx +312 -0
- package/client/src/components/approval/approval-decision-form.test.tsx +294 -0
- package/client/src/components/approval/approval-decision-form.tsx +326 -0
- package/client/src/components/approval/approval-sheet.tsx +631 -0
- package/client/src/components/approval/line-item-details-sheet.tsx +371 -0
- package/client/src/components/approval/otp-verification.test.tsx +337 -0
- package/client/src/components/approval/otp-verification.tsx +180 -0
- package/client/src/components/content-hub/bulk-transcode-dialog.tsx +379 -0
- package/client/src/components/content-hub/content-hub-manager-v2.tsx +574 -0
- package/client/src/components/content-hub/content-hub-manager.tsx +330 -0
- package/client/src/components/content-hub/creative-card.tsx +456 -0
- package/client/src/components/content-hub/creative-detail-sheet.tsx +685 -0
- package/client/src/components/content-hub/creative-filters.tsx +457 -0
- package/client/src/components/content-hub/creative-grid.tsx +329 -0
- package/client/src/components/content-hub/creative-selector.tsx +415 -0
- package/client/src/components/content-hub/creative-upload.tsx +547 -0
- package/client/src/components/content-hub/folder-dialogs.tsx +445 -0
- package/client/src/components/content-hub/folder-list.tsx +280 -0
- package/client/src/components/content-hub/review-dialogs.tsx +268 -0
- package/client/src/components/content-hub/transcode-dialog.tsx +226 -0
- package/client/src/components/creative-library/creative-details-view.tsx +446 -0
- package/client/src/components/creative-library/creative-filters-panel.tsx +203 -0
- package/client/src/components/creative-library/creative-list.tsx +360 -0
- package/client/src/components/creative-library/creative-status-badge.tsx +71 -0
- package/client/src/components/creative-library/folder-card.tsx +78 -0
- package/client/src/components/creative-library/index.ts +27 -0
- package/client/src/components/creative-library/new-creative-card.tsx +211 -0
- package/client/src/components/creative-library/upload-creative-dialog.tsx +261 -0
- package/client/src/components/dashboard-overview.tsx +109 -0
- package/client/src/components/deals/approval-history-panel.test.tsx +240 -0
- package/client/src/components/deals/approval-history-panel.tsx +156 -0
- package/client/src/components/deals/deal-status-badge.tsx +92 -0
- package/client/src/components/deals/import-from-planner-dialog.tsx +399 -0
- package/client/src/components/deals/market-insights-panel.tsx +237 -0
- package/client/src/components/deals/reopen-deal-sheet.tsx +191 -0
- package/client/src/components/deals/request-approval-sheet.test.tsx +323 -0
- package/client/src/components/deals/request-approval-sheet.tsx +136 -0
- package/client/src/components/deals/resend-approval-sheet.tsx +201 -0
- package/client/src/components/direct-campaigns/campaign-card.tsx +283 -0
- package/client/src/components/direct-campaigns/deal-filter-panel.tsx +325 -0
- package/client/src/components/inventory/advanced-filters-panel.tsx +273 -0
- package/client/src/components/inventory/csv-upload-modal.tsx +639 -0
- package/client/src/components/inventory/inventory-availability-view.tsx +486 -0
- package/client/src/components/inventory/inventory-details-sheet.tsx +376 -0
- package/client/src/components/inventory/inventory-map-view.tsx +596 -0
- package/client/src/components/inventory/inventory-settings-menu.tsx +52 -0
- package/client/src/components/language-switcher.tsx +53 -0
- package/client/src/components/line-items/campaign-forecast-panel.tsx +138 -0
- package/client/src/components/line-items/form-insights.tsx +89 -0
- package/client/src/components/line-items/geofencing/LocationCsvUploadDrawer.tsx +100 -0
- package/client/src/components/line-items/geofencing/POIDropdown.tsx +379 -0
- package/client/src/components/line-items/geofencing/SelectedLocationsSidebar.tsx +436 -0
- package/client/src/components/line-items/geofencing/ViewFileLocationDrawer.tsx +199 -0
- package/client/src/components/line-items/geofencing/components/ExistingFilesTab.tsx +268 -0
- package/client/src/components/line-items/geofencing/components/TemplateDownloadSection.tsx +59 -0
- package/client/src/components/line-items/geofencing/components/UploadTab.tsx +215 -0
- package/client/src/components/line-items/geofencing-map.tsx +1270 -0
- package/client/src/components/line-items/inventory-availability-section.tsx +178 -0
- package/client/src/components/line-items/line-item-schedule-manager.tsx +313 -0
- package/client/src/components/line-items/manual-inventory-drawer.tsx +346 -0
- package/client/src/components/line-items/planner-inventory-card.tsx +495 -0
- package/client/src/components/line-items/planner-schedule-grid.tsx +495 -0
- package/client/src/components/line-items/schedule-rule-editor.tsx +649 -0
- package/client/src/components/line-items/schedule-rule-types.ts +122 -0
- package/client/src/components/line-items/steps/creatives-step.tsx +681 -0
- package/client/src/components/line-items/steps/inventory-schedule-step.tsx +1596 -0
- package/client/src/components/line-items/steps/inventory-step.tsx +1533 -0
- package/client/src/components/line-items/steps/line-item-details-step.tsx +916 -0
- package/client/src/components/line-items/steps/schedule-step.tsx +273 -0
- package/client/src/components/line-items/steps/summary-step.tsx +680 -0
- package/client/src/components/line-items/steps/targeting-step.tsx +1708 -0
- package/client/src/components/product-switcher.tsx +105 -0
- package/client/src/components/protected-route.tsx +49 -0
- package/client/src/components/skip-link.tsx +22 -0
- package/client/src/components/stat-card.tsx +53 -0
- package/client/src/components/status-badge.tsx +96 -0
- package/client/src/components/ui/hierarchical-venue-selector.tsx +389 -0
- package/client/src/components/ui/toaster.tsx +111 -0
- package/client/src/contexts/auth-context.tsx +181 -0
- package/client/src/contexts/sidebar-state.tsx +50 -0
- package/client/src/contexts/theme-context.tsx +66 -0
- package/client/src/data/campaign-data.json +107 -0
- package/client/src/data/countries.json +22 -0
- package/client/src/hooks/use-approval.ts +366 -0
- package/client/src/hooks/use-keyboard-shortcuts.ts +74 -0
- package/client/src/hooks/use-media-query.ts +46 -0
- package/client/src/hooks/use-mobile.tsx +19 -0
- package/client/src/hooks/use-page-title.ts +21 -0
- package/client/src/hooks/use-toast.ts +195 -0
- package/client/src/index.css +694 -0
- package/client/src/lib/__tests__/accessibility.test.ts +104 -0
- package/client/src/lib/__tests__/date-utils.test.ts +199 -0
- package/client/src/lib/__tests__/dsp-buyer-api.test.ts +127 -0
- package/client/src/lib/__tests__/dsp-buyer-integration.test.ts +247 -0
- package/client/src/lib/__tests__/storage-utils.test.ts +167 -0
- package/client/src/lib/__tests__/utils.test.ts +57 -0
- package/client/src/lib/accessibility.ts +141 -0
- package/client/src/lib/api-config.ts +9 -0
- package/client/src/lib/auth-service.ts +209 -0
- package/client/src/lib/campaign-creative-api.ts +82 -0
- package/client/src/lib/company-api.ts +61 -0
- package/client/src/lib/content-hub-api.ts +407 -0
- package/client/src/lib/creative-mapper.ts +61 -0
- package/client/src/lib/date-utils.ts +119 -0
- package/client/src/lib/deal-helpers.ts +220 -0
- package/client/src/lib/dsp-buyer-api.ts +196 -0
- package/client/src/lib/geo-import-api.ts +151 -0
- package/client/src/lib/google-poi-api.ts +305 -0
- package/client/src/lib/i18n/__tests__/formatting.test.ts +202 -0
- package/client/src/lib/i18n/formatting.ts +130 -0
- package/client/src/lib/i18n/index.ts +8 -0
- package/client/src/lib/i18n-compat.ts +76 -0
- package/client/src/lib/influence-deals-api.ts +896 -0
- package/client/src/lib/inventory-api.ts +399 -0
- package/client/src/lib/oauth-service.ts +678 -0
- package/client/src/lib/poi-types.ts +75 -0
- package/client/src/lib/queryClient.ts +144 -0
- package/client/src/lib/recommendation-api.ts +380 -0
- package/client/src/lib/storage-utils.ts +104 -0
- package/client/src/lib/tolgee.ts +85 -0
- package/client/src/lib/utils.ts +0 -0
- package/client/src/main.tsx +67 -0
- package/client/src/mapbox-draw-modes.d.ts +32 -0
- package/client/src/pages/all-folders.tsx +203 -0
- package/client/src/pages/auth-callback.tsx +115 -0
- package/client/src/pages/buyer-form.tsx +339 -0
- package/client/src/pages/buyer-list.tsx +622 -0
- package/client/src/pages/content-hub.tsx +1358 -0
- package/client/src/pages/create-deal.tsx +2093 -0
- package/client/src/pages/creative-assignment-page.tsx +548 -0
- package/client/src/pages/creatives.tsx +5 -0
- package/client/src/pages/custom-pois.tsx +425 -0
- package/client/src/pages/dashboard.tsx +615 -0
- package/client/src/pages/deal-history.tsx +434 -0
- package/client/src/pages/deal-line-items.tsx +1703 -0
- package/client/src/pages/demo-status.tsx +113 -0
- package/client/src/pages/direct-campaign-details.tsx +361 -0
- package/client/src/pages/direct-campaigns-new.tsx +824 -0
- package/client/src/pages/dsp-form.tsx +803 -0
- package/client/src/pages/dsp-list.tsx +239 -0
- package/client/src/pages/folder-content.tsx +336 -0
- package/client/src/pages/integrations.tsx +429 -0
- package/client/src/pages/line-item-creatives.tsx +789 -0
- package/client/src/pages/line-item-detail-page.tsx +684 -0
- package/client/src/pages/line-item-form-page.tsx +3261 -0
- package/client/src/pages/line-item-wizard.tsx +1207 -0
- package/client/src/pages/login.tsx +154 -0
- package/client/src/pages/not-found.tsx +23 -0
- package/client/src/pages/proof-of-play.tsx +397 -0
- package/client/src/pages/public-approval.tsx +551 -0
- package/client/src/pages/reports.tsx +231 -0
- package/client/src/pages/settings.tsx +760 -0
- package/client/src/pages/signals.tsx +389 -0
- package/client/src/pages/tags.tsx +318 -0
- package/client/src/pages/test-results.tsx +328 -0
- package/client/src/store/hooks.ts +5 -0
- package/client/src/store/index.ts +15 -0
- package/client/src/store/mapMarkerLocationsSlice.ts +241 -0
- package/client/src/styles/design-tokens.css +324 -0
- package/client/src/test/setup.ts +261 -0
- package/client/src/test/test-utils.tsx +40 -0
- package/client/src/types/approval.ts +221 -0
- package/client/src/types/content-hub.ts +209 -0
- package/client/src/types/geofencing.ts +67 -0
- package/client/src/types/transcoding.ts +140 -0
- package/client/src/vite-env.d.ts +18 -0
- package/components.json +20 -0
- package/creative-api.json +1 -0
- package/docs/AI_REFERENCE.md +459 -0
- package/docs/MWDesign-Prompt.md +132 -0
- package/docs/MWDesign-System.md +344 -0
- package/docs/test-plan.md +277 -0
- package/e2e/AUTONOMOUS-TESTING.md +406 -0
- package/e2e/README.md +219 -0
- package/e2e/autonomous-flow.spec.ts +308 -0
- package/e2e/debug-sso.spec.ts +163 -0
- package/e2e/direct-campaigns.spec.ts +219 -0
- package/e2e/explore-sso.spec.ts +149 -0
- package/e2e/fixtures/auth.ts +26 -0
- package/e2e/fixtures/enhanced-test.ts +331 -0
- package/e2e/pagination.spec.ts +280 -0
- package/e2e/view-toggle.spec.ts +312 -0
- package/generated-icon.png +0 -0
- package/i18next-scanner.config.cjs +46 -0
- package/package.json +141 -0
- package/playwright.config.ts +93 -0
- package/postcss.config.js +6 -0
- package/replit.md +196 -0
- package/screenshot-after-login.png +0 -0
- package/screenshot-contenthub-grid.png +0 -0
- package/screenshot-contenthub-list-fixed.png +0 -0
- package/screenshot-contenthub-list.png +0 -0
- package/screenshot-create-deal.png +0 -0
- package/screenshot-dashboard.png +0 -0
- package/screenshot-deals.png +0 -0
- package/screenshot-login-filled.png +0 -0
- package/screenshot-login.png +0 -0
- package/screenshot.mjs +24 -0
- package/scripts/deploy-stg.sh +185 -0
- package/shared/direct-io-schema.ts +383 -0
- package/shared/schema.ts +439 -0
- package/shared/screen-types.ts +149 -0
- package/springdocDefault.json +1 -0
- package/swagger-ui-bundle.js +2 -0
- package/swagger-ui-init.js +10316 -0
- package/tailwind.config.ts +282 -0
- package/terraform/README.md +306 -0
- package/terraform/cloudfront.tf +289 -0
- package/terraform/ecs.tf +727 -0
- package/terraform/environments/dev.tfvars +59 -0
- package/terraform/environments/production.tfvars +60 -0
- package/terraform/main.tf +47 -0
- package/terraform/outputs.tf +145 -0
- package/terraform/s3.tf +192 -0
- package/terraform/variables.tf +226 -0
- package/terraform/waf.tf +165 -0
- package/terraform-frontend/.terraform.lock.hcl +25 -0
- package/terraform-frontend/README.md +85 -0
- package/terraform-frontend/cloudfront.tf +125 -0
- package/terraform-frontend/main.tf +31 -0
- package/terraform-frontend/outputs.tf +24 -0
- package/terraform-frontend/terraform.tfvars +12 -0
- package/terraform-frontend/variables.tf +53 -0
- package/tsconfig.json +23 -0
- package/vite.config.ts +226 -0
- package/vitest.config.ts +56 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { test, expect, takeScreenshot, saveDOMSnapshot, waitForNetworkIdle, waitForAPIResponse } from './fixtures/enhanced-test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Autonomous End-to-End Flow Test
|
|
5
|
+
* Complete test with SSO login, screenshots, DOM snapshots, console logs, and network monitoring
|
|
6
|
+
*/
|
|
7
|
+
test.describe('Autonomous Complete Flow Test', () => {
|
|
8
|
+
test('should complete full Direct Campaigns workflow with full observability', async ({ authenticatedPage: page, testInfo }) => {
|
|
9
|
+
console.log('\n🚀 Starting Autonomous Complete Flow Test\n');
|
|
10
|
+
console.log('=' .repeat(80));
|
|
11
|
+
|
|
12
|
+
// Step 1: Navigate to Direct Campaigns
|
|
13
|
+
console.log('\n📍 Step 1: Navigate to Direct Campaigns page');
|
|
14
|
+
await page.goto('/direct-campaigns');
|
|
15
|
+
await waitForNetworkIdle(page);
|
|
16
|
+
await takeScreenshot(page, 'step-01-campaigns-page', testInfo);
|
|
17
|
+
await saveDOMSnapshot(page, 'step-01-campaigns-page', testInfo);
|
|
18
|
+
|
|
19
|
+
// Verify page loaded
|
|
20
|
+
await expect(page.getByRole('heading', { name: 'Direct Campaigns' })).toBeVisible();
|
|
21
|
+
console.log('✅ Direct Campaigns page loaded');
|
|
22
|
+
|
|
23
|
+
// Step 2: Wait for campaigns API response
|
|
24
|
+
console.log('\n📍 Step 2: Wait for campaigns data to load');
|
|
25
|
+
const campaignsResponse = await waitForAPIResponse(page, '/direct-campaigns');
|
|
26
|
+
const campaignsData = await campaignsResponse.json();
|
|
27
|
+
console.log('📊 Campaigns loaded:', campaignsData.result?.pagination || campaignsData.pagination);
|
|
28
|
+
await page.waitForSelector('table tbody tr, .grid.grid-cols-1', { timeout: 10000 });
|
|
29
|
+
await takeScreenshot(page, 'step-02-campaigns-loaded', testInfo);
|
|
30
|
+
|
|
31
|
+
// Step 3: Test Search Functionality
|
|
32
|
+
console.log('\n📍 Step 3: Test search functionality');
|
|
33
|
+
const searchInput = page.getByPlaceholder('Search here...');
|
|
34
|
+
await searchInput.fill('Campaign');
|
|
35
|
+
console.log('⌨️ Typed search query: "Campaign"');
|
|
36
|
+
|
|
37
|
+
await page.waitForTimeout(600); // Wait for debounce
|
|
38
|
+
await waitForNetworkIdle(page);
|
|
39
|
+
await takeScreenshot(page, 'step-03-search-results', testInfo);
|
|
40
|
+
console.log('✅ Search results displayed');
|
|
41
|
+
|
|
42
|
+
// Clear search
|
|
43
|
+
await searchInput.clear();
|
|
44
|
+
await page.waitForTimeout(600);
|
|
45
|
+
await waitForNetworkIdle(page);
|
|
46
|
+
console.log('🧹 Search cleared');
|
|
47
|
+
|
|
48
|
+
// Step 4: Test View Toggle - Switch to List View
|
|
49
|
+
console.log('\n📍 Step 4: Switch to List View');
|
|
50
|
+
const listViewButton = page.locator('button[title="List View"]');
|
|
51
|
+
await listViewButton.click();
|
|
52
|
+
await page.waitForTimeout(300);
|
|
53
|
+
await page.waitForSelector('table', { timeout: 5000 });
|
|
54
|
+
await takeScreenshot(page, 'step-04-list-view', testInfo);
|
|
55
|
+
await saveDOMSnapshot(page, 'step-04-list-view', testInfo);
|
|
56
|
+
console.log('✅ Switched to List View');
|
|
57
|
+
|
|
58
|
+
// Verify table columns
|
|
59
|
+
const columns = ['Campaign Name', 'Brand', 'Flight Dates', 'Client Type', 'Status', 'Actions'];
|
|
60
|
+
for (const column of columns) {
|
|
61
|
+
await expect(page.getByRole('columnheader', { name: column })).toBeVisible();
|
|
62
|
+
}
|
|
63
|
+
console.log('✅ All table columns visible');
|
|
64
|
+
|
|
65
|
+
// Step 5: Test Row Selection
|
|
66
|
+
console.log('\n📍 Step 5: Test row selection');
|
|
67
|
+
const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
|
|
68
|
+
await firstRowCheckbox.check();
|
|
69
|
+
await expect(firstRowCheckbox).toBeChecked();
|
|
70
|
+
await takeScreenshot(page, 'step-05-row-selected', testInfo);
|
|
71
|
+
console.log('✅ Row selected');
|
|
72
|
+
|
|
73
|
+
await firstRowCheckbox.uncheck();
|
|
74
|
+
console.log('✅ Row unselected');
|
|
75
|
+
|
|
76
|
+
// Step 6: Test Select All
|
|
77
|
+
console.log('\n📍 Step 6: Test select all functionality');
|
|
78
|
+
const selectAllCheckbox = page.locator('#table-select-all-checkbox');
|
|
79
|
+
await selectAllCheckbox.check();
|
|
80
|
+
await expect(selectAllCheckbox).toBeChecked();
|
|
81
|
+
await takeScreenshot(page, 'step-06-all-selected', testInfo);
|
|
82
|
+
console.log('✅ All rows selected');
|
|
83
|
+
|
|
84
|
+
await selectAllCheckbox.uncheck();
|
|
85
|
+
console.log('✅ All rows unselected');
|
|
86
|
+
|
|
87
|
+
// Step 7: Test Pagination
|
|
88
|
+
console.log('\n📍 Step 7: Test pagination controls');
|
|
89
|
+
const paginationSection = page.locator('.bg-white.dark\\:bg-mw-neutral-900').filter({ hasText: /Page \d+ of \d+/ });
|
|
90
|
+
await expect(paginationSection).toBeVisible();
|
|
91
|
+
console.log('✅ Pagination controls visible');
|
|
92
|
+
|
|
93
|
+
// Check page info
|
|
94
|
+
const pageInfo = page.getByText(/Page \d+ of \d+/);
|
|
95
|
+
const pageText = await pageInfo.textContent();
|
|
96
|
+
console.log('📄 Current page info:', pageText);
|
|
97
|
+
|
|
98
|
+
// Test Next button if available
|
|
99
|
+
const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
|
|
100
|
+
if (!(await nextButton.isDisabled())) {
|
|
101
|
+
console.log('⏭️ Clicking Next button');
|
|
102
|
+
await nextButton.click();
|
|
103
|
+
await waitForNetworkIdle(page);
|
|
104
|
+
await takeScreenshot(page, 'step-07-next-page', testInfo);
|
|
105
|
+
|
|
106
|
+
const newPageText = await pageInfo.textContent();
|
|
107
|
+
console.log('📄 New page info:', newPageText);
|
|
108
|
+
console.log('✅ Pagination navigation successful');
|
|
109
|
+
|
|
110
|
+
// Go back to first page
|
|
111
|
+
const firstButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-first') }).first();
|
|
112
|
+
await firstButton.click();
|
|
113
|
+
await waitForNetworkIdle(page);
|
|
114
|
+
console.log('⏮️ Returned to first page');
|
|
115
|
+
} else {
|
|
116
|
+
console.log('ℹ️ Only one page available, skipping pagination test');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Step 8: Test Campaign Actions Menu
|
|
120
|
+
console.log('\n📍 Step 8: Test campaign actions menu');
|
|
121
|
+
const firstRow = page.locator('table tbody tr').first();
|
|
122
|
+
const actionsButton = firstRow.getByRole('button').last();
|
|
123
|
+
await actionsButton.click();
|
|
124
|
+
await page.waitForTimeout(300);
|
|
125
|
+
await takeScreenshot(page, 'step-08-actions-menu', testInfo);
|
|
126
|
+
|
|
127
|
+
// Verify menu items
|
|
128
|
+
await expect(page.getByText('View Details')).toBeVisible();
|
|
129
|
+
await expect(page.getByText('Edit Campaign')).toBeVisible();
|
|
130
|
+
await expect(page.getByText('Delete')).toBeVisible();
|
|
131
|
+
console.log('✅ Actions menu opened with all options');
|
|
132
|
+
|
|
133
|
+
// Close menu by clicking outside
|
|
134
|
+
await page.click('body');
|
|
135
|
+
await page.waitForTimeout(300);
|
|
136
|
+
console.log('✅ Actions menu closed');
|
|
137
|
+
|
|
138
|
+
// Step 9: Test Campaign Row Click Navigation
|
|
139
|
+
console.log('\n📍 Step 9: Test campaign row click navigation');
|
|
140
|
+
const campaignNameCell = firstRow.locator('td').nth(1);
|
|
141
|
+
const campaignName = await campaignNameCell.textContent();
|
|
142
|
+
console.log('🎯 Clicking on campaign:', campaignName);
|
|
143
|
+
|
|
144
|
+
await campaignNameCell.click();
|
|
145
|
+
await page.waitForURL(/\/direct-campaigns\/.+/, { timeout: 5000 });
|
|
146
|
+
await waitForNetworkIdle(page);
|
|
147
|
+
await takeScreenshot(page, 'step-09-campaign-details', testInfo);
|
|
148
|
+
await saveDOMSnapshot(page, 'step-09-campaign-details', testInfo);
|
|
149
|
+
|
|
150
|
+
const detailsUrl = page.url();
|
|
151
|
+
console.log('✅ Navigated to campaign details:', detailsUrl);
|
|
152
|
+
|
|
153
|
+
// Go back to campaigns list
|
|
154
|
+
await page.goBack();
|
|
155
|
+
await waitForNetworkIdle(page);
|
|
156
|
+
console.log('⬅️ Navigated back to campaigns list');
|
|
157
|
+
|
|
158
|
+
// Step 10: Test Grid View
|
|
159
|
+
console.log('\n📍 Step 10: Switch to Grid View');
|
|
160
|
+
const gridViewButton = page.locator('button[title="Grid View"]');
|
|
161
|
+
await gridViewButton.click();
|
|
162
|
+
await page.waitForTimeout(300);
|
|
163
|
+
await page.waitForSelector('.grid.grid-cols-1', { timeout: 5000 });
|
|
164
|
+
await takeScreenshot(page, 'step-10-grid-view', testInfo);
|
|
165
|
+
await saveDOMSnapshot(page, 'step-10-grid-view', testInfo);
|
|
166
|
+
console.log('✅ Switched to Grid View');
|
|
167
|
+
|
|
168
|
+
// Verify campaign cards are visible
|
|
169
|
+
const campaignCards = page.locator('[class*="rounded-lg"][class*="border"]');
|
|
170
|
+
const cardCount = await campaignCards.count();
|
|
171
|
+
console.log(`✅ ${cardCount} campaign cards visible in grid view`);
|
|
172
|
+
|
|
173
|
+
// Step 11: Test Filter Modal
|
|
174
|
+
console.log('\n📍 Step 11: Test filter modal');
|
|
175
|
+
const filterButton = page.getByRole('button', { name: /Filter/i });
|
|
176
|
+
await filterButton.click();
|
|
177
|
+
await page.waitForTimeout(500);
|
|
178
|
+
await takeScreenshot(page, 'step-11-filter-modal', testInfo);
|
|
179
|
+
|
|
180
|
+
// Check if filter modal opened (if available)
|
|
181
|
+
const filterModal = page.locator('[role="dialog"]');
|
|
182
|
+
if (await filterModal.isVisible().catch(() => false)) {
|
|
183
|
+
console.log('✅ Filter modal opened');
|
|
184
|
+
await saveDOMSnapshot(page, 'step-11-filter-modal', testInfo);
|
|
185
|
+
|
|
186
|
+
// Close modal
|
|
187
|
+
const closeButton = page.locator('button:has-text("Close"), button[aria-label="Close"]');
|
|
188
|
+
if (await closeButton.isVisible().catch(() => false)) {
|
|
189
|
+
await closeButton.click();
|
|
190
|
+
console.log('✅ Filter modal closed');
|
|
191
|
+
} else {
|
|
192
|
+
// Click outside to close
|
|
193
|
+
await page.keyboard.press('Escape');
|
|
194
|
+
console.log('✅ Filter modal closed with Escape');
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
console.log('ℹ️ Filter modal not implemented yet');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Step 12: Test Create Campaign Button
|
|
201
|
+
console.log('\n📍 Step 12: Test Create Campaign button');
|
|
202
|
+
const createButton = page.getByRole('button', { name: /Create Campaign/i });
|
|
203
|
+
await expect(createButton).toBeVisible();
|
|
204
|
+
await takeScreenshot(page, 'step-12-create-button', testInfo);
|
|
205
|
+
console.log('✅ Create Campaign button visible');
|
|
206
|
+
|
|
207
|
+
// Optional: Click if you want to test the create flow
|
|
208
|
+
// await createButton.click();
|
|
209
|
+
// await waitForNetworkIdle(page);
|
|
210
|
+
// console.log('✅ Navigated to Create Campaign page');
|
|
211
|
+
|
|
212
|
+
// Final Step: Summary
|
|
213
|
+
console.log('\n📍 Final Step: Test Summary');
|
|
214
|
+
console.log('=' .repeat(80));
|
|
215
|
+
console.log('✅ Test completed successfully!');
|
|
216
|
+
console.log(`📸 Screenshots saved to: ${testInfo.screenshotDir}`);
|
|
217
|
+
console.log(`📝 Logs saved to: ${testInfo.logDir}`);
|
|
218
|
+
console.log(`🌐 Network requests: ${testInfo.networkLogs.length}`);
|
|
219
|
+
console.log(`🖥️ Console logs: ${testInfo.consoleLogs.length}`);
|
|
220
|
+
console.log(`❌ Errors: ${testInfo.errors.length}`);
|
|
221
|
+
|
|
222
|
+
// Take final screenshot
|
|
223
|
+
await takeScreenshot(page, 'step-13-final-state', testInfo);
|
|
224
|
+
await saveDOMSnapshot(page, 'step-13-final-state', testInfo);
|
|
225
|
+
|
|
226
|
+
console.log('\n🎉 All steps completed successfully!');
|
|
227
|
+
console.log('=' .repeat(80) + '\n');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('should verify pagination maintains state correctly across navigation', async ({ authenticatedPage: page, testInfo }) => {
|
|
231
|
+
console.log('\n🚀 Testing Pagination State Persistence\n');
|
|
232
|
+
|
|
233
|
+
// Navigate to campaigns
|
|
234
|
+
await page.goto('/direct-campaigns');
|
|
235
|
+
await waitForNetworkIdle(page);
|
|
236
|
+
await takeScreenshot(page, 'pagination-test-01-initial', testInfo);
|
|
237
|
+
|
|
238
|
+
// Switch to list view
|
|
239
|
+
const listViewButton = page.locator('button[title="List View"]');
|
|
240
|
+
await listViewButton.click();
|
|
241
|
+
await page.waitForTimeout(300);
|
|
242
|
+
|
|
243
|
+
// Navigate to page 2 if available
|
|
244
|
+
const nextButton = page.locator('button').filter({ has: page.locator('svg.lucide-chevron-right') }).first();
|
|
245
|
+
if (!(await nextButton.isDisabled())) {
|
|
246
|
+
await nextButton.click();
|
|
247
|
+
await waitForNetworkIdle(page);
|
|
248
|
+
await takeScreenshot(page, 'pagination-test-02-page-2', testInfo);
|
|
249
|
+
|
|
250
|
+
// Verify we're on page 2
|
|
251
|
+
const pageInfo = page.getByText(/Page \d+ of \d+/);
|
|
252
|
+
await expect(pageInfo).toContainText('Page 2');
|
|
253
|
+
console.log('✅ Navigated to page 2');
|
|
254
|
+
|
|
255
|
+
// Navigate to a campaign detail page
|
|
256
|
+
const firstCampaignCell = page.locator('table tbody tr').first().locator('td').nth(1);
|
|
257
|
+
await firstCampaignCell.click();
|
|
258
|
+
await page.waitForURL(/\/direct-campaigns\/.+/);
|
|
259
|
+
await waitForNetworkIdle(page);
|
|
260
|
+
console.log('✅ Opened campaign details');
|
|
261
|
+
|
|
262
|
+
// Go back
|
|
263
|
+
await page.goBack();
|
|
264
|
+
await waitForNetworkIdle(page);
|
|
265
|
+
await takeScreenshot(page, 'pagination-test-03-returned', testInfo);
|
|
266
|
+
|
|
267
|
+
// Verify still on page 2
|
|
268
|
+
await expect(pageInfo).toContainText('Page 2');
|
|
269
|
+
console.log('✅ Pagination state maintained after navigation');
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
console.log('✅ Pagination state persistence test completed');
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
test('should handle empty search results gracefully', async ({ authenticatedPage: page, testInfo }) => {
|
|
276
|
+
console.log('\n🚀 Testing Empty Search Results\n');
|
|
277
|
+
|
|
278
|
+
await page.goto('/direct-campaigns');
|
|
279
|
+
await waitForNetworkIdle(page);
|
|
280
|
+
|
|
281
|
+
// Search for non-existent campaign
|
|
282
|
+
const searchInput = page.getByPlaceholder('Search here...');
|
|
283
|
+
await searchInput.fill('NONEXISTENT_CAMPAIGN_XYZ_999');
|
|
284
|
+
await page.waitForTimeout(600);
|
|
285
|
+
await waitForNetworkIdle(page);
|
|
286
|
+
await takeScreenshot(page, 'empty-search-01-no-results', testInfo);
|
|
287
|
+
|
|
288
|
+
// Verify empty state message
|
|
289
|
+
const emptyMessage = page.getByText(/No campaigns found/i);
|
|
290
|
+
await expect(emptyMessage).toBeVisible();
|
|
291
|
+
console.log('✅ Empty state message displayed');
|
|
292
|
+
|
|
293
|
+
// Verify pagination is hidden
|
|
294
|
+
const paginationSection = page.locator('.bg-white.dark\\:bg-mw-neutral-900').filter({ hasText: /Page \d+ of \d+/ });
|
|
295
|
+
await expect(paginationSection).not.toBeVisible();
|
|
296
|
+
console.log('✅ Pagination hidden for empty results');
|
|
297
|
+
|
|
298
|
+
// Clear search
|
|
299
|
+
await searchInput.clear();
|
|
300
|
+
await page.waitForTimeout(600);
|
|
301
|
+
await waitForNetworkIdle(page);
|
|
302
|
+
await takeScreenshot(page, 'empty-search-02-results-restored', testInfo);
|
|
303
|
+
|
|
304
|
+
// Verify campaigns reappeared
|
|
305
|
+
await expect(page.locator('table tbody tr, .grid.grid-cols-1')).toBeVisible();
|
|
306
|
+
console.log('✅ Results restored after clearing search');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Debug SSO Login with Network and Console Monitoring
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
test('Debug SSO with Network/Console Logs', async ({ page }) => {
|
|
8
|
+
// Capture console messages
|
|
9
|
+
const consoleLogs: string[] = [];
|
|
10
|
+
page.on('console', (msg) => {
|
|
11
|
+
const logMsg = `[${msg.type()}] ${msg.text()}`;
|
|
12
|
+
consoleLogs.push(logMsg);
|
|
13
|
+
console.log('🖥️ CONSOLE:', logMsg);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
// Capture network requests
|
|
17
|
+
const networkLogs: any[] = [];
|
|
18
|
+
page.on('response', async (response) => {
|
|
19
|
+
const url = response.url();
|
|
20
|
+
const status = response.status();
|
|
21
|
+
const method = response.request().method();
|
|
22
|
+
|
|
23
|
+
const log = { method, status, url };
|
|
24
|
+
networkLogs.push(log);
|
|
25
|
+
|
|
26
|
+
console.log(`🌐 ${method} ${status}: ${url}`);
|
|
27
|
+
|
|
28
|
+
// Log response body for failed requests
|
|
29
|
+
if (status >= 400) {
|
|
30
|
+
try {
|
|
31
|
+
const body = await response.text();
|
|
32
|
+
console.log(` ❌ Response body:`, body.substring(0, 500));
|
|
33
|
+
} catch (e) {
|
|
34
|
+
console.log(` ❌ Could not read response body`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Capture page errors
|
|
40
|
+
page.on('pageerror', (error) => {
|
|
41
|
+
console.log('❌ PAGE ERROR:', error.message);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
console.log('\n=== STEP 1: Navigate to Login ===');
|
|
45
|
+
await page.goto('/login', { waitUntil: 'networkidle' });
|
|
46
|
+
await page.screenshot({ path: 'debug-01-login.png', fullPage: true });
|
|
47
|
+
|
|
48
|
+
console.log('\n=== STEP 2: Click SSO Button ===');
|
|
49
|
+
const ssoButton = page.locator('button:has-text("Sign in with MW ID")');
|
|
50
|
+
await ssoButton.click();
|
|
51
|
+
await page.waitForLoadState('networkidle');
|
|
52
|
+
await page.screenshot({ path: 'debug-02-sso-page.png', fullPage: true });
|
|
53
|
+
|
|
54
|
+
console.log('\n=== STEP 3: Fill Credentials ===');
|
|
55
|
+
const emailInput = page.locator('input[type="email"]').first();
|
|
56
|
+
const passwordInput = page.locator('input[type="password"]').first();
|
|
57
|
+
|
|
58
|
+
const testEmail = process.env.SSO_TEST_USER || 'jeki@jeki.com';
|
|
59
|
+
const testPassword = process.env.SSO_TEST_PASSWORD || 'jeki@123';
|
|
60
|
+
|
|
61
|
+
console.log(` Using: ${testEmail} / ${testPassword}`);
|
|
62
|
+
|
|
63
|
+
await emailInput.click();
|
|
64
|
+
await emailInput.fill(testEmail);
|
|
65
|
+
await passwordInput.click();
|
|
66
|
+
await passwordInput.fill(testPassword);
|
|
67
|
+
|
|
68
|
+
await page.waitForTimeout(1000);
|
|
69
|
+
await page.screenshot({ path: 'debug-03-filled.png', fullPage: true });
|
|
70
|
+
|
|
71
|
+
const emailValue = await emailInput.inputValue();
|
|
72
|
+
const passwordValue = await passwordInput.inputValue();
|
|
73
|
+
console.log(` Email: "${emailValue}"`);
|
|
74
|
+
console.log(` Password: ${passwordValue.length} chars`);
|
|
75
|
+
|
|
76
|
+
console.log('\n=== STEP 4: Submit Form (watching network) ===');
|
|
77
|
+
const submitButton = page.locator('button[type="submit"]').first();
|
|
78
|
+
|
|
79
|
+
// Listen for any form submission errors
|
|
80
|
+
const requestPromise = page.waitForResponse(
|
|
81
|
+
(response) => response.url().includes('login') || response.url().includes('auth'),
|
|
82
|
+
{ timeout: 10000 }
|
|
83
|
+
).catch(() => null);
|
|
84
|
+
|
|
85
|
+
await submitButton.click();
|
|
86
|
+
console.log(' ✅ Submit button clicked');
|
|
87
|
+
|
|
88
|
+
// Wait for response
|
|
89
|
+
const response = await requestPromise;
|
|
90
|
+
if (response) {
|
|
91
|
+
console.log(` 📥 Response: ${response.status()} ${response.url()}`);
|
|
92
|
+
try {
|
|
93
|
+
const contentType = response.headers()['content-type'];
|
|
94
|
+
console.log(` Content-Type: ${contentType}`);
|
|
95
|
+
|
|
96
|
+
if (contentType?.includes('json')) {
|
|
97
|
+
const body = await response.json();
|
|
98
|
+
console.log(` Response body:`, JSON.stringify(body, null, 2));
|
|
99
|
+
} else {
|
|
100
|
+
const text = await response.text();
|
|
101
|
+
console.log(` Response text (first 500 chars):`, text.substring(0, 500));
|
|
102
|
+
}
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.log(` Could not read response`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Wait for navigation or error
|
|
109
|
+
await page.waitForTimeout(5000);
|
|
110
|
+
await page.screenshot({ path: 'debug-04-after-submit.png', fullPage: true });
|
|
111
|
+
|
|
112
|
+
console.log('\n=== STEP 5: Check Current State ===');
|
|
113
|
+
console.log(` Current URL: ${page.url()}`);
|
|
114
|
+
|
|
115
|
+
// Check for error messages
|
|
116
|
+
const errorSelectors = [
|
|
117
|
+
'.error',
|
|
118
|
+
'[role="alert"]',
|
|
119
|
+
'.text-red-500',
|
|
120
|
+
'.alert-error',
|
|
121
|
+
'[class*="error"]',
|
|
122
|
+
'[class*="Error"]'
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (const selector of errorSelectors) {
|
|
126
|
+
const errors = await page.locator(selector).all();
|
|
127
|
+
if (errors.length > 0) {
|
|
128
|
+
console.log(`\n ❌ Found errors with selector "${selector}":`);
|
|
129
|
+
for (const error of errors) {
|
|
130
|
+
const text = await error.textContent();
|
|
131
|
+
const isVisible = await error.isVisible();
|
|
132
|
+
if (isVisible && text) {
|
|
133
|
+
console.log(` - ${text.trim()}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if still on login page
|
|
140
|
+
const isAuthenticated = !page.url().includes('login');
|
|
141
|
+
console.log(`\n Authentication successful: ${isAuthenticated}`);
|
|
142
|
+
|
|
143
|
+
console.log('\n=== NETWORK LOG SUMMARY ===');
|
|
144
|
+
const authRequests = networkLogs.filter(log =>
|
|
145
|
+
log.url.includes('login') || log.url.includes('auth') || log.url.includes('oauth')
|
|
146
|
+
);
|
|
147
|
+
console.log(`Total auth-related requests: ${authRequests.length}`);
|
|
148
|
+
authRequests.forEach(log => {
|
|
149
|
+
console.log(` ${log.method} ${log.status}: ${log.url}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log('\n=== CONSOLE LOG SUMMARY ===');
|
|
153
|
+
const errorLogs = consoleLogs.filter(log => log.includes('error') || log.includes('Error'));
|
|
154
|
+
if (errorLogs.length > 0) {
|
|
155
|
+
console.log('Error logs found:');
|
|
156
|
+
errorLogs.forEach(log => console.log(` ${log}`));
|
|
157
|
+
} else {
|
|
158
|
+
console.log('No error logs found');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
console.log('\n=== DEBUG COMPLETE ===');
|
|
162
|
+
console.log('Check screenshots: debug-*.png');
|
|
163
|
+
});
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Direct Campaigns Page E2E Tests
|
|
5
|
+
* Tests the complete functionality of the Direct Campaigns listing page
|
|
6
|
+
*/
|
|
7
|
+
test.describe('Direct Campaigns Page', () => {
|
|
8
|
+
test.beforeEach(async ({ page }) => {
|
|
9
|
+
// Navigate to the Direct Campaigns page
|
|
10
|
+
await page.goto('/direct-campaigns');
|
|
11
|
+
|
|
12
|
+
// Wait for the page to load
|
|
13
|
+
await page.waitForLoadState('networkidle');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
test.describe('Page Layout and Elements', () => {
|
|
17
|
+
test('should display page header with title and description', async ({ page }) => {
|
|
18
|
+
await expect(page.getByRole('heading', { name: 'Direct Campaigns' })).toBeVisible();
|
|
19
|
+
await expect(page.getByText('Manage your direct IO campaigns')).toBeVisible();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('should display Create Campaign button', async ({ page }) => {
|
|
23
|
+
const createButton = page.getByRole('button', { name: /Create Campaign/i });
|
|
24
|
+
await expect(createButton).toBeVisible();
|
|
25
|
+
await expect(createButton).toBeEnabled();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('should display search input', async ({ page }) => {
|
|
29
|
+
const searchInput = page.getByPlaceholder('Search here...');
|
|
30
|
+
await expect(searchInput).toBeVisible();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('should display view toggle buttons (list/grid)', async ({ page }) => {
|
|
34
|
+
// Check for list view button
|
|
35
|
+
const listViewButton = page.locator('button[title="List View"]');
|
|
36
|
+
await expect(listViewButton).toBeVisible();
|
|
37
|
+
|
|
38
|
+
// Check for grid view button
|
|
39
|
+
const gridViewButton = page.locator('button[title="Grid View"]');
|
|
40
|
+
await expect(gridViewButton).toBeVisible();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('should display filter button', async ({ page }) => {
|
|
44
|
+
const filterButton = page.getByRole('button', { name: /Filter/i });
|
|
45
|
+
await expect(filterButton).toBeVisible();
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test.describe('Campaign List Display', () => {
|
|
50
|
+
test('should display campaigns in the table', async ({ page }) => {
|
|
51
|
+
// Wait for campaigns to load
|
|
52
|
+
await page.waitForSelector('table tbody tr', { timeout: 10000 });
|
|
53
|
+
|
|
54
|
+
// Check if at least one campaign row is visible
|
|
55
|
+
const rows = page.locator('table tbody tr');
|
|
56
|
+
await expect(rows.first()).toBeVisible();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test('should display correct table columns', async ({ page }) => {
|
|
60
|
+
// Check for table headers
|
|
61
|
+
await expect(page.getByRole('columnheader', { name: 'Campaign Name' })).toBeVisible();
|
|
62
|
+
await expect(page.getByRole('columnheader', { name: 'Brand' })).toBeVisible();
|
|
63
|
+
await expect(page.getByRole('columnheader', { name: 'Flight Dates' })).toBeVisible();
|
|
64
|
+
await expect(page.getByRole('columnheader', { name: 'Client Type' })).toBeVisible();
|
|
65
|
+
await expect(page.getByRole('columnheader', { name: 'Status' })).toBeVisible();
|
|
66
|
+
await expect(page.getByRole('columnheader', { name: 'Actions' })).toBeVisible();
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('should display campaign data in rows', async ({ page }) => {
|
|
70
|
+
// Wait for first campaign row
|
|
71
|
+
const firstRow = page.locator('table tbody tr').first();
|
|
72
|
+
await expect(firstRow).toBeVisible();
|
|
73
|
+
|
|
74
|
+
// Verify campaign has required data displayed
|
|
75
|
+
await expect(firstRow.locator('td').nth(1)).not.toBeEmpty();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test.describe('Search Functionality', () => {
|
|
80
|
+
test('should filter campaigns based on search input', async ({ page }) => {
|
|
81
|
+
const searchInput = page.getByPlaceholder('Search here...');
|
|
82
|
+
|
|
83
|
+
// Wait for initial campaigns to load
|
|
84
|
+
await page.waitForSelector('table tbody tr');
|
|
85
|
+
|
|
86
|
+
// Get initial count
|
|
87
|
+
const initialCount = await page.locator('table tbody tr').count();
|
|
88
|
+
|
|
89
|
+
// Type search query (minimum 3 characters required based on code)
|
|
90
|
+
await searchInput.fill('Campaign');
|
|
91
|
+
|
|
92
|
+
// Wait for debounce (500ms based on code)
|
|
93
|
+
await page.waitForTimeout(600);
|
|
94
|
+
|
|
95
|
+
// Wait for network request to complete
|
|
96
|
+
await page.waitForLoadState('networkidle');
|
|
97
|
+
|
|
98
|
+
// Verify search was applied (results may change)
|
|
99
|
+
const searchResults = page.locator('table tbody tr');
|
|
100
|
+
await expect(searchResults.first()).toBeVisible();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test('should not search with less than 3 characters', async ({ page }) => {
|
|
104
|
+
const searchInput = page.getByPlaceholder('Search here...');
|
|
105
|
+
|
|
106
|
+
// Type less than 3 characters
|
|
107
|
+
await searchInput.fill('Ca');
|
|
108
|
+
|
|
109
|
+
// Wait for debounce
|
|
110
|
+
await page.waitForTimeout(600);
|
|
111
|
+
|
|
112
|
+
// Should still show all campaigns (no filtering)
|
|
113
|
+
const rows = page.locator('table tbody tr');
|
|
114
|
+
await expect(rows.first()).toBeVisible();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test('should clear search results when input is cleared', async ({ page }) => {
|
|
118
|
+
const searchInput = page.getByPlaceholder('Search here...');
|
|
119
|
+
|
|
120
|
+
// Search for something
|
|
121
|
+
await searchInput.fill('Campaign');
|
|
122
|
+
await page.waitForTimeout(600);
|
|
123
|
+
await page.waitForLoadState('networkidle');
|
|
124
|
+
|
|
125
|
+
// Clear search
|
|
126
|
+
await searchInput.clear();
|
|
127
|
+
await page.waitForTimeout(600);
|
|
128
|
+
await page.waitForLoadState('networkidle');
|
|
129
|
+
|
|
130
|
+
// Should show all campaigns again
|
|
131
|
+
const rows = page.locator('table tbody tr');
|
|
132
|
+
await expect(rows.first()).toBeVisible();
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test.describe('Row Selection', () => {
|
|
137
|
+
test('should allow selecting individual campaigns', async ({ page }) => {
|
|
138
|
+
// Wait for campaigns to load
|
|
139
|
+
await page.waitForSelector('table tbody tr');
|
|
140
|
+
|
|
141
|
+
// Find first checkbox in tbody (skip header checkbox)
|
|
142
|
+
const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
|
|
143
|
+
await expect(firstRowCheckbox).toBeVisible();
|
|
144
|
+
|
|
145
|
+
// Select the row
|
|
146
|
+
await firstRowCheckbox.check();
|
|
147
|
+
await expect(firstRowCheckbox).toBeChecked();
|
|
148
|
+
|
|
149
|
+
// Unselect the row
|
|
150
|
+
await firstRowCheckbox.uncheck();
|
|
151
|
+
await expect(firstRowCheckbox).not.toBeChecked();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('should allow selecting all campaigns', async ({ page }) => {
|
|
155
|
+
// Wait for campaigns to load
|
|
156
|
+
await page.waitForSelector('table tbody tr');
|
|
157
|
+
|
|
158
|
+
// Find select all checkbox in header
|
|
159
|
+
const selectAllCheckbox = page.locator('#table-select-all-checkbox');
|
|
160
|
+
await expect(selectAllCheckbox).toBeVisible();
|
|
161
|
+
|
|
162
|
+
// Select all
|
|
163
|
+
await selectAllCheckbox.check();
|
|
164
|
+
await expect(selectAllCheckbox).toBeChecked();
|
|
165
|
+
|
|
166
|
+
// Verify first row is selected
|
|
167
|
+
const firstRowCheckbox = page.locator('table tbody tr').first().locator('input[type="checkbox"]');
|
|
168
|
+
await expect(firstRowCheckbox).toBeChecked();
|
|
169
|
+
|
|
170
|
+
// Unselect all
|
|
171
|
+
await selectAllCheckbox.uncheck();
|
|
172
|
+
await expect(firstRowCheckbox).not.toBeChecked();
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test.describe('Campaign Actions', () => {
|
|
177
|
+
test('should open actions menu for a campaign', async ({ page }) => {
|
|
178
|
+
// Wait for campaigns to load
|
|
179
|
+
await page.waitForSelector('table tbody tr');
|
|
180
|
+
|
|
181
|
+
// Find first actions button (three dots)
|
|
182
|
+
const actionsButton = page.locator('table tbody tr').first().getByRole('button').last();
|
|
183
|
+
await actionsButton.click();
|
|
184
|
+
|
|
185
|
+
// Wait for dropdown menu to appear
|
|
186
|
+
await expect(page.getByText('View Details')).toBeVisible();
|
|
187
|
+
await expect(page.getByText('Edit Campaign')).toBeVisible();
|
|
188
|
+
await expect(page.getByText('Delete')).toBeVisible();
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test('should navigate to campaign details when clicking View Details', async ({ page }) => {
|
|
192
|
+
// Wait for campaigns to load
|
|
193
|
+
await page.waitForSelector('table tbody tr');
|
|
194
|
+
|
|
195
|
+
// Open actions menu
|
|
196
|
+
const actionsButton = page.locator('table tbody tr').first().getByRole('button').last();
|
|
197
|
+
await actionsButton.click();
|
|
198
|
+
|
|
199
|
+
// Click View Details
|
|
200
|
+
await page.getByText('View Details').click();
|
|
201
|
+
|
|
202
|
+
// Should navigate to campaign details page
|
|
203
|
+
await expect(page).toHaveURL(/\/direct-campaigns\/.+/);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('should navigate to campaign details when clicking on row', async ({ page }) => {
|
|
207
|
+
// Wait for campaigns to load
|
|
208
|
+
await page.waitForSelector('table tbody tr');
|
|
209
|
+
|
|
210
|
+
// Click on first row (not on checkbox or action button)
|
|
211
|
+
const firstRow = page.locator('table tbody tr').first();
|
|
212
|
+
const campaignNameCell = firstRow.locator('td').nth(1);
|
|
213
|
+
await campaignNameCell.click();
|
|
214
|
+
|
|
215
|
+
// Should navigate to campaign details page
|
|
216
|
+
await expect(page).toHaveURL(/\/direct-campaigns\/.+/);
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|