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,149 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SSO Flow Exploration Script
|
|
5
|
+
*
|
|
6
|
+
* Run this with: npm run test:e2e:headed explore-sso.spec.ts
|
|
7
|
+
* This will show you the actual flow and help understand the selectors
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
test('Explore SSO Login Flow', async ({ page }) => {
|
|
11
|
+
// Enable slow motion to see what's happening
|
|
12
|
+
await page.goto('/login', { waitUntil: 'networkidle' });
|
|
13
|
+
|
|
14
|
+
console.log('\n=== STEP 1: Login Page ===');
|
|
15
|
+
console.log('URL:', page.url());
|
|
16
|
+
|
|
17
|
+
// Take screenshot
|
|
18
|
+
await page.screenshot({ path: 'explore-01-login-page.png', fullPage: true });
|
|
19
|
+
console.log('Screenshot saved: explore-01-login-page.png');
|
|
20
|
+
|
|
21
|
+
// Find all buttons on the page
|
|
22
|
+
const buttons = await page.locator('button').all();
|
|
23
|
+
console.log(`\nFound ${buttons.length} buttons:`);
|
|
24
|
+
for (let i = 0; i < buttons.length; i++) {
|
|
25
|
+
const text = await buttons[i].textContent();
|
|
26
|
+
const isVisible = await buttons[i].isVisible();
|
|
27
|
+
console.log(` Button ${i + 1}: "${text}" (visible: ${isVisible})`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Look for SSO button
|
|
31
|
+
const ssoButton = page.locator('button:has-text("Sign in with MW ID")');
|
|
32
|
+
const ssoExists = await ssoButton.count();
|
|
33
|
+
console.log(`\nSSO Button exists: ${ssoExists > 0}`);
|
|
34
|
+
|
|
35
|
+
if (ssoExists > 0) {
|
|
36
|
+
console.log('\n=== STEP 2: Clicking SSO Button ===');
|
|
37
|
+
await ssoButton.click();
|
|
38
|
+
|
|
39
|
+
// Wait for navigation
|
|
40
|
+
await page.waitForLoadState('networkidle');
|
|
41
|
+
console.log('After SSO click URL:', page.url());
|
|
42
|
+
|
|
43
|
+
// Take screenshot
|
|
44
|
+
await page.screenshot({ path: 'explore-02-sso-provider.png', fullPage: true });
|
|
45
|
+
console.log('Screenshot saved: explore-02-sso-provider.png');
|
|
46
|
+
|
|
47
|
+
// Find all input fields
|
|
48
|
+
console.log('\n=== STEP 3: Analyzing SSO Form ===');
|
|
49
|
+
const inputs = await page.locator('input').all();
|
|
50
|
+
console.log(`Found ${inputs.length} input fields:`);
|
|
51
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
52
|
+
const type = await inputs[i].getAttribute('type');
|
|
53
|
+
const name = await inputs[i].getAttribute('name');
|
|
54
|
+
const placeholder = await inputs[i].getAttribute('placeholder');
|
|
55
|
+
const isVisible = await inputs[i].isVisible();
|
|
56
|
+
console.log(` Input ${i + 1}: type="${type}", name="${name}", placeholder="${placeholder}", visible=${isVisible}`);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Find email input
|
|
60
|
+
const emailInput = page.locator('input[type="email"]').first();
|
|
61
|
+
const emailExists = await emailInput.count();
|
|
62
|
+
console.log(`\nEmail input exists: ${emailExists > 0}`);
|
|
63
|
+
|
|
64
|
+
if (emailExists > 0) {
|
|
65
|
+
console.log('\n=== STEP 4: Filling Form ===');
|
|
66
|
+
|
|
67
|
+
// Get credentials from env
|
|
68
|
+
const testEmail = process.env.SSO_TEST_USER || 'jeki@jeki.com';
|
|
69
|
+
const testPassword = process.env.SSO_TEST_PASSWORD || 'jeki@123';
|
|
70
|
+
|
|
71
|
+
console.log(`Using email: ${testEmail}`);
|
|
72
|
+
|
|
73
|
+
// Try different methods to fill
|
|
74
|
+
const passwordInput = page.locator('input[type="password"]').first();
|
|
75
|
+
|
|
76
|
+
// Method 1: Direct fill
|
|
77
|
+
await emailInput.click();
|
|
78
|
+
await emailInput.fill(testEmail);
|
|
79
|
+
await page.waitForTimeout(500);
|
|
80
|
+
|
|
81
|
+
await passwordInput.click();
|
|
82
|
+
await passwordInput.fill(testPassword);
|
|
83
|
+
await page.waitForTimeout(500);
|
|
84
|
+
|
|
85
|
+
// Check if filled
|
|
86
|
+
const emailValue = await emailInput.inputValue();
|
|
87
|
+
const passwordValue = await passwordInput.inputValue();
|
|
88
|
+
console.log(`Email filled: "${emailValue}" (length: ${emailValue.length})`);
|
|
89
|
+
console.log(`Password filled: ${passwordValue.length} characters`);
|
|
90
|
+
|
|
91
|
+
// Take screenshot
|
|
92
|
+
await page.screenshot({ path: 'explore-03-form-filled.png', fullPage: true });
|
|
93
|
+
console.log('Screenshot saved: explore-03-form-filled.png');
|
|
94
|
+
|
|
95
|
+
// Find submit button
|
|
96
|
+
const submitButtons = await page.locator('button[type="submit"], button:has-text("Sign in")').all();
|
|
97
|
+
console.log(`\nFound ${submitButtons.length} submit buttons`);
|
|
98
|
+
for (let i = 0; i < submitButtons.length; i++) {
|
|
99
|
+
const text = await submitButtons[i].textContent();
|
|
100
|
+
console.log(` Submit button ${i + 1}: "${text}"`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (emailValue && passwordValue) {
|
|
104
|
+
console.log('\n=== STEP 5: Submitting Form ===');
|
|
105
|
+
const submitButton = page.locator('button[type="submit"]').first();
|
|
106
|
+
await submitButton.click();
|
|
107
|
+
console.log('Form submitted!');
|
|
108
|
+
|
|
109
|
+
// Wait for navigation
|
|
110
|
+
await page.waitForTimeout(3000);
|
|
111
|
+
console.log('After submit URL:', page.url());
|
|
112
|
+
|
|
113
|
+
// Take screenshot
|
|
114
|
+
await page.screenshot({ path: 'explore-04-after-submit.png', fullPage: true });
|
|
115
|
+
console.log('Screenshot saved: explore-04-after-submit.png');
|
|
116
|
+
|
|
117
|
+
// Check if we're authenticated
|
|
118
|
+
const isAuthenticated = !page.url().includes('login');
|
|
119
|
+
console.log(`\nAuthentication successful: ${isAuthenticated}`);
|
|
120
|
+
|
|
121
|
+
if (isAuthenticated) {
|
|
122
|
+
console.log('\n✅ SSO LOGIN SUCCESSFUL!');
|
|
123
|
+
console.log('Final URL:', page.url());
|
|
124
|
+
} else {
|
|
125
|
+
console.log('\n❌ Still on login page');
|
|
126
|
+
|
|
127
|
+
// Check for errors
|
|
128
|
+
const errorMessages = await page.locator('.error, [role="alert"], .text-red-500').all();
|
|
129
|
+
if (errorMessages.length > 0) {
|
|
130
|
+
console.log('\nError messages found:');
|
|
131
|
+
for (const error of errorMessages) {
|
|
132
|
+
const text = await error.textContent();
|
|
133
|
+
console.log(` - ${text}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
console.log('\n❌ Form fields not filled properly!');
|
|
139
|
+
console.log('This is a React form - we need to use different methods');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Keep browser open for inspection
|
|
145
|
+
await page.waitForTimeout(5000);
|
|
146
|
+
|
|
147
|
+
console.log('\n=== EXPLORATION COMPLETE ===');
|
|
148
|
+
console.log('Check the screenshots to understand the flow');
|
|
149
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { test as base, Page } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Authentication fixture for tests
|
|
5
|
+
* Handles login and maintains authenticated state
|
|
6
|
+
*/
|
|
7
|
+
export const test = base.extend<{ authenticatedPage: Page }>({
|
|
8
|
+
authenticatedPage: async ({ page }, use) => {
|
|
9
|
+
// Navigate to login page
|
|
10
|
+
await page.goto('/login');
|
|
11
|
+
|
|
12
|
+
// TODO: Add your authentication logic here
|
|
13
|
+
// For now, we'll just wait for navigation to complete
|
|
14
|
+
// You'll need to update this with actual login credentials
|
|
15
|
+
|
|
16
|
+
// Example login flow (update with your actual selectors):
|
|
17
|
+
// await page.fill('input[name="email"]', 'test@example.com');
|
|
18
|
+
// await page.fill('input[name="password"]', 'password');
|
|
19
|
+
// await page.click('button[type="submit"]');
|
|
20
|
+
// await page.waitForURL('/');
|
|
21
|
+
|
|
22
|
+
await use(page);
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export { expect } from '@playwright/test';
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { test as base, expect, Page } from '@playwright/test';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Enhanced test fixture with:
|
|
7
|
+
* - Automatic screenshot capture
|
|
8
|
+
* - Console log collection
|
|
9
|
+
* - Network monitoring
|
|
10
|
+
* - DOM snapshots
|
|
11
|
+
* - SSO authentication
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
interface EnhancedTestFixtures {
|
|
15
|
+
authenticatedPage: Page;
|
|
16
|
+
testInfo: {
|
|
17
|
+
screenshotDir: string;
|
|
18
|
+
logDir: string;
|
|
19
|
+
consoleLogs: string[];
|
|
20
|
+
networkLogs: Array<{ url: string; status: number; method: string }>;
|
|
21
|
+
errors: string[];
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const test = base.extend<EnhancedTestFixtures>({
|
|
26
|
+
testInfo: async ({}, use, testInfo) => {
|
|
27
|
+
// Create directories for test artifacts
|
|
28
|
+
const testDir = path.join('test-results', testInfo.titlePath.join('-').replace(/[^a-z0-9]/gi, '_'));
|
|
29
|
+
const screenshotDir = path.join(testDir, 'screenshots');
|
|
30
|
+
const logDir = path.join(testDir, 'logs');
|
|
31
|
+
|
|
32
|
+
fs.mkdirSync(screenshotDir, { recursive: true });
|
|
33
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
34
|
+
|
|
35
|
+
const info = {
|
|
36
|
+
screenshotDir,
|
|
37
|
+
logDir,
|
|
38
|
+
consoleLogs: [] as string[],
|
|
39
|
+
networkLogs: [] as Array<{ url: string; status: number; method: string }>,
|
|
40
|
+
errors: [] as string[],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
await use(info);
|
|
44
|
+
|
|
45
|
+
// Save logs at the end of the test
|
|
46
|
+
fs.writeFileSync(
|
|
47
|
+
path.join(logDir, 'console.log'),
|
|
48
|
+
info.consoleLogs.join('\n')
|
|
49
|
+
);
|
|
50
|
+
fs.writeFileSync(
|
|
51
|
+
path.join(logDir, 'network.json'),
|
|
52
|
+
JSON.stringify(info.networkLogs, null, 2)
|
|
53
|
+
);
|
|
54
|
+
if (info.errors.length > 0) {
|
|
55
|
+
fs.writeFileSync(
|
|
56
|
+
path.join(logDir, 'errors.log'),
|
|
57
|
+
info.errors.join('\n')
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
authenticatedPage: async ({ page, testInfo: info }, use) => {
|
|
63
|
+
// Set up console log capture
|
|
64
|
+
page.on('console', (msg) => {
|
|
65
|
+
const logMessage = `[${msg.type()}] ${msg.text()}`;
|
|
66
|
+
info.consoleLogs.push(logMessage);
|
|
67
|
+
console.log('🖥️ Console:', logMessage);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Set up error capture
|
|
71
|
+
page.on('pageerror', (error) => {
|
|
72
|
+
const errorMessage = `[Page Error] ${error.message}\n${error.stack}`;
|
|
73
|
+
info.errors.push(errorMessage);
|
|
74
|
+
console.error('❌ Page Error:', error.message);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Set up network monitoring
|
|
78
|
+
page.on('request', (request) => {
|
|
79
|
+
console.log('🌐 Request:', request.method(), request.url());
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
page.on('response', async (response) => {
|
|
83
|
+
const url = response.url();
|
|
84
|
+
const status = response.status();
|
|
85
|
+
const method = response.request().method();
|
|
86
|
+
|
|
87
|
+
info.networkLogs.push({ url, status, method });
|
|
88
|
+
|
|
89
|
+
if (status >= 400) {
|
|
90
|
+
console.error(`❌ HTTP ${status}:`, method, url);
|
|
91
|
+
try {
|
|
92
|
+
const body = await response.text();
|
|
93
|
+
info.errors.push(`[HTTP ${status}] ${method} ${url}\nResponse: ${body}`);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
// Some responses can't be read
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
console.log(`✅ HTTP ${status}:`, method, url);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Navigate to login page
|
|
103
|
+
console.log('\n🔐 Starting authentication flow...');
|
|
104
|
+
await page.goto('/login', { waitUntil: 'networkidle' });
|
|
105
|
+
|
|
106
|
+
// Take screenshot of login page
|
|
107
|
+
await page.screenshot({
|
|
108
|
+
path: path.join(info.screenshotDir, '01-login-page.png'),
|
|
109
|
+
fullPage: true,
|
|
110
|
+
});
|
|
111
|
+
console.log('📸 Screenshot: login page');
|
|
112
|
+
|
|
113
|
+
// Check if we're already logged in (redirect to home)
|
|
114
|
+
const currentUrl = page.url();
|
|
115
|
+
if (!currentUrl.includes('/login')) {
|
|
116
|
+
console.log('✅ Already authenticated, skipping login');
|
|
117
|
+
await use(page);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Look for SSO login button or form
|
|
122
|
+
console.log('🔍 Looking for login form...');
|
|
123
|
+
|
|
124
|
+
// Get SSO credentials from environment
|
|
125
|
+
const ssoUser = process.env.SSO_TEST_USER || process.env.TEST_EMAIL || '';
|
|
126
|
+
const ssoPassword = process.env.SSO_TEST_PASSWORD || process.env.TEST_PASSWORD || '';
|
|
127
|
+
const ssoProvider = process.env.SSO_PROVIDER || 'Sign in with MW ID';
|
|
128
|
+
|
|
129
|
+
// Check for different login scenarios
|
|
130
|
+
const ssoButton = page.locator(`button:has-text("${ssoProvider}")`);
|
|
131
|
+
const emailInput = page.locator('input[name="email"], input[type="email"]');
|
|
132
|
+
const passwordInput = page.locator('input[name="password"], input[type="password"]');
|
|
133
|
+
|
|
134
|
+
// Scenario 1: SSO Button (primary method)
|
|
135
|
+
if (await ssoButton.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
136
|
+
console.log(`🎯 SSO button found: "${ssoProvider}", clicking...`);
|
|
137
|
+
|
|
138
|
+
// Click SSO button and wait for navigation to SSO provider
|
|
139
|
+
await ssoButton.click();
|
|
140
|
+
console.log('⏳ Waiting for SSO provider page...');
|
|
141
|
+
await page.waitForLoadState('networkidle', { timeout: 15000 });
|
|
142
|
+
|
|
143
|
+
await page.screenshot({
|
|
144
|
+
path: path.join(info.screenshotDir, '02-after-sso-click.png'),
|
|
145
|
+
fullPage: true,
|
|
146
|
+
});
|
|
147
|
+
console.log('📸 Screenshot: SSO provider page');
|
|
148
|
+
|
|
149
|
+
// Now on SSO provider page - enter credentials
|
|
150
|
+
console.log('🔍 Looking for SSO login form...');
|
|
151
|
+
const ssoEmailInput = page.locator('input[name="email"], input[type="email"], input[name="username"]').first();
|
|
152
|
+
const ssoPasswordInput = page.locator('input[name="password"], input[type="password"]').first();
|
|
153
|
+
|
|
154
|
+
if (await ssoEmailInput.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
155
|
+
console.log('📝 Filling SSO credentials...');
|
|
156
|
+
|
|
157
|
+
// Fill email field using pressSequentially for React forms
|
|
158
|
+
await ssoEmailInput.click();
|
|
159
|
+
await page.waitForTimeout(100);
|
|
160
|
+
await ssoEmailInput.clear();
|
|
161
|
+
await page.waitForTimeout(100);
|
|
162
|
+
|
|
163
|
+
// Type character by character for React forms
|
|
164
|
+
await ssoEmailInput.pressSequentially(ssoUser, { delay: 50 });
|
|
165
|
+
await page.waitForTimeout(200);
|
|
166
|
+
console.log(` Email filled: ${ssoUser}`);
|
|
167
|
+
|
|
168
|
+
// Fill password field using pressSequentially for React forms
|
|
169
|
+
await ssoPasswordInput.click();
|
|
170
|
+
await page.waitForTimeout(100);
|
|
171
|
+
await ssoPasswordInput.clear();
|
|
172
|
+
await page.waitForTimeout(100);
|
|
173
|
+
|
|
174
|
+
// Type character by character for React forms
|
|
175
|
+
await ssoPasswordInput.pressSequentially(ssoPassword, { delay: 50 });
|
|
176
|
+
await page.waitForTimeout(200);
|
|
177
|
+
console.log(' Password filled');
|
|
178
|
+
|
|
179
|
+
// Wait for form validation
|
|
180
|
+
await page.waitForTimeout(500);
|
|
181
|
+
|
|
182
|
+
await page.screenshot({
|
|
183
|
+
path: path.join(info.screenshotDir, '02-sso-form-filled.png'),
|
|
184
|
+
fullPage: true,
|
|
185
|
+
});
|
|
186
|
+
console.log('📸 Screenshot: SSO form filled');
|
|
187
|
+
|
|
188
|
+
// Verify fields are filled
|
|
189
|
+
const emailValue = await ssoEmailInput.inputValue();
|
|
190
|
+
const passwordValue = await ssoPasswordInput.inputValue();
|
|
191
|
+
console.log(` Email value: ${emailValue}`);
|
|
192
|
+
console.log(` Password length: ${passwordValue.length}`);
|
|
193
|
+
|
|
194
|
+
if (!emailValue || !passwordValue) {
|
|
195
|
+
console.error('❌ Form fields not properly filled!');
|
|
196
|
+
await page.screenshot({
|
|
197
|
+
path: path.join(info.screenshotDir, '02-sso-form-fill-failed.png'),
|
|
198
|
+
fullPage: true,
|
|
199
|
+
});
|
|
200
|
+
throw new Error('SSO form fields not properly filled');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Find and click SSO submit button
|
|
204
|
+
const ssoSubmitButton = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in"), button:has-text("Continue")').first();
|
|
205
|
+
console.log('🔍 Clicking submit button...');
|
|
206
|
+
await ssoSubmitButton.click();
|
|
207
|
+
console.log('🚀 SSO login form submitted');
|
|
208
|
+
|
|
209
|
+
// Wait for redirect back to application
|
|
210
|
+
console.log('⏳ Waiting for redirect back to application...');
|
|
211
|
+
|
|
212
|
+
// Wait for navigation with a more flexible condition
|
|
213
|
+
try {
|
|
214
|
+
await page.waitForNavigation({ timeout: 30000, waitUntil: 'networkidle' });
|
|
215
|
+
console.log('✅ Navigation detected');
|
|
216
|
+
} catch (e) {
|
|
217
|
+
console.log('⚠️ Navigation timeout, checking current URL...');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Verify we're no longer on login page
|
|
221
|
+
const currentUrl = page.url();
|
|
222
|
+
console.log(` Current URL: ${currentUrl}`);
|
|
223
|
+
|
|
224
|
+
if (currentUrl.includes('login') || currentUrl.includes('sign')) {
|
|
225
|
+
console.error('❌ Still on login page after submit');
|
|
226
|
+
await page.screenshot({
|
|
227
|
+
path: path.join(info.screenshotDir, '02-sso-submit-failed.png'),
|
|
228
|
+
fullPage: true,
|
|
229
|
+
});
|
|
230
|
+
throw new Error('SSO authentication failed - still on login page');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
console.log('✅ SSO authentication successful');
|
|
234
|
+
} else {
|
|
235
|
+
console.log('⚠️ SSO form not found on provider page');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
// Scenario 2: Direct Email/Password form (no SSO)
|
|
239
|
+
else if (await emailInput.isVisible({ timeout: 5000 }).catch(() => false)) {
|
|
240
|
+
console.log('📝 Direct email/password form found');
|
|
241
|
+
|
|
242
|
+
await emailInput.fill(ssoUser);
|
|
243
|
+
await passwordInput.fill(ssoPassword);
|
|
244
|
+
|
|
245
|
+
await page.screenshot({
|
|
246
|
+
path: path.join(info.screenshotDir, '02-login-form-filled.png'),
|
|
247
|
+
fullPage: true,
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Find and click submit button
|
|
251
|
+
const submitButton = page.locator('button[type="submit"], button:has-text("Login"), button:has-text("Sign in")').first();
|
|
252
|
+
await submitButton.click();
|
|
253
|
+
console.log('🚀 Login form submitted');
|
|
254
|
+
|
|
255
|
+
// Wait for navigation to complete
|
|
256
|
+
await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
|
|
257
|
+
await page.waitForURL((url) => !url.toString().includes('login'), { timeout: 10000 }).catch(() => {});
|
|
258
|
+
}
|
|
259
|
+
// Scenario 3: No login required (dev mode)
|
|
260
|
+
else {
|
|
261
|
+
console.log('⚠️ No login form found - might be in dev mode');
|
|
262
|
+
await page.goto('/');
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Take screenshot after login
|
|
266
|
+
await page.screenshot({
|
|
267
|
+
path: path.join(info.screenshotDir, '03-authenticated-home.png'),
|
|
268
|
+
fullPage: true,
|
|
269
|
+
});
|
|
270
|
+
console.log('✅ Authentication complete\n');
|
|
271
|
+
|
|
272
|
+
await use(page);
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
export { expect };
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Helper function to take annotated screenshots
|
|
280
|
+
*/
|
|
281
|
+
export async function takeScreenshot(page: Page, name: string, info: EnhancedTestFixtures['testInfo']) {
|
|
282
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
283
|
+
const filename = `${timestamp}-${name}.png`;
|
|
284
|
+
const filepath = path.join(info.screenshotDir, filename);
|
|
285
|
+
|
|
286
|
+
await page.screenshot({ path: filepath, fullPage: true });
|
|
287
|
+
console.log(`📸 Screenshot: ${name}`);
|
|
288
|
+
return filepath;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Helper function to save DOM snapshot
|
|
293
|
+
*/
|
|
294
|
+
export async function saveDOMSnapshot(page: Page, name: string, info: EnhancedTestFixtures['testInfo']) {
|
|
295
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
296
|
+
const filename = `${timestamp}-${name}.html`;
|
|
297
|
+
const filepath = path.join(info.logDir, filename);
|
|
298
|
+
|
|
299
|
+
const html = await page.content();
|
|
300
|
+
fs.writeFileSync(filepath, html);
|
|
301
|
+
console.log(`💾 DOM Snapshot: ${name}`);
|
|
302
|
+
return filepath;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Helper to wait and log network idle
|
|
307
|
+
*/
|
|
308
|
+
export async function waitForNetworkIdle(page: Page) {
|
|
309
|
+
console.log('⏳ Waiting for network idle...');
|
|
310
|
+
await page.waitForLoadState('networkidle');
|
|
311
|
+
console.log('✅ Network idle');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Helper to check for specific API responses
|
|
316
|
+
*/
|
|
317
|
+
export async function waitForAPIResponse(page: Page, urlPattern: string | RegExp) {
|
|
318
|
+
console.log(`⏳ Waiting for API response: ${urlPattern}`);
|
|
319
|
+
const response = await page.waitForResponse(
|
|
320
|
+
(resp) => {
|
|
321
|
+
const url = resp.url();
|
|
322
|
+
if (typeof urlPattern === 'string') {
|
|
323
|
+
return url.includes(urlPattern);
|
|
324
|
+
}
|
|
325
|
+
return urlPattern.test(url);
|
|
326
|
+
},
|
|
327
|
+
{ timeout: 30000 }
|
|
328
|
+
);
|
|
329
|
+
console.log(`✅ API response received: ${response.status()}`);
|
|
330
|
+
return response;
|
|
331
|
+
}
|