From cad45a870dac5cef98b899cddc3b9da45287d407 Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Thu, 11 Jun 2026 14:45:25 +0100 Subject: [PATCH] UPDATED TESTS --- bootstrap/app.php | 15 +- package.json | 7 +- tests/Browser/AuthBrowserTest.php | 75 +++++++++ .../Concerns/InteractsWithWmsBrowser.php | 46 +++++ .../Browser/Concerns/InteractsWithWmsData.php | 158 ++++++++++++++++++ .../Browser/DesktopNavigationBrowserTest.php | 109 ++++++++++++ tests/Browser/ExampleTest.php | 24 ++- tests/Browser/MobileNavigationBrowserTest.php | 81 +++++++++ .../PlatformAdministrationBrowserTest.php | 101 +++++++++++ tests/Browser/PlatformPricingBrowserTest.php | 96 +++++++++++ tests/Browser/README_DUSK_WMS.md | 35 ++++ .../Browser/ResponsiveBaselineBrowserTest.php | 62 +++++++ tests/Browser/WarehouseBrowserTest.php | 87 ++++++++++ 13 files changed, 888 insertions(+), 8 deletions(-) create mode 100644 tests/Browser/AuthBrowserTest.php create mode 100644 tests/Browser/Concerns/InteractsWithWmsBrowser.php create mode 100644 tests/Browser/Concerns/InteractsWithWmsData.php create mode 100644 tests/Browser/DesktopNavigationBrowserTest.php create mode 100644 tests/Browser/MobileNavigationBrowserTest.php create mode 100644 tests/Browser/PlatformAdministrationBrowserTest.php create mode 100644 tests/Browser/PlatformPricingBrowserTest.php create mode 100644 tests/Browser/README_DUSK_WMS.md create mode 100644 tests/Browser/ResponsiveBaselineBrowserTest.php create mode 100644 tests/Browser/WarehouseBrowserTest.php diff --git a/bootstrap/app.php b/bootstrap/app.php index 39525755..37457e4c 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -12,11 +12,24 @@ return Application::configure(basePath: dirname(__DIR__)) health: '/up', ) ->withMiddleware(function (Middleware $middleware): void { + + $middleware->trustProxies( + + at: '*', + + headers: Request::HEADER_X_FORWARDED_FOR + + | Request::HEADER_X_FORWARDED_HOST + + | Request::HEADER_X_FORWARDED_PORT + + | Request::HEADER_X_FORWARDED_PROTO + + ); $environment = $_ENV['APP_ENV'] ?? $_SERVER['APP_ENV'] ?? getenv('APP_ENV') ?: null; - if ($environment === 'testing') { $middleware->validateCsrfTokens(except: ['*']); } diff --git a/package.json b/package.json index b5f3c39c..6b1bfce5 100644 --- a/package.json +++ b/package.json @@ -7,17 +7,20 @@ "dev": "vite" }, "dependencies": { + "@fortawesome/fontawesome-free": "^7.1.0", "@laravel/passkeys": "^0.2.0", "@tailwindcss/vite": "^4.1.11", "concurrently": "^9.0.1", "laravel-vite-plugin": "^3.1", "tailwindcss": "^4.0.7", - "vite": "^8.0.0", - "@fortawesome/fontawesome-free": "^7.1.0" + "vite": "^8.0.0" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", "lightningcss-linux-x64-gnu": "^1.29.1" + }, + "devDependencies": { + "chokidar": "^5.0.0" } } diff --git a/tests/Browser/AuthBrowserTest.php b/tests/Browser/AuthBrowserTest.php new file mode 100644 index 00000000..30217b94 --- /dev/null +++ b/tests/Browser/AuthBrowserTest.php @@ -0,0 +1,75 @@ +browse(function (Browser $browser): void { + $browser->visit('/login') + ->assertSee('Log in') + ->assertPresent('input[name="email"]') + ->assertPresent('input[name="password"]') + ->assertPresent('[data-test="login-button"]'); + }); + } + + public function test_invalid_login_stays_on_login_page(): void + { + $this->platformAdminUser(); + + $this->browse(function (Browser $browser): void { + $browser->visit('/login') + ->type('email', 'platform.admin@example.test') + ->type('password', 'wrong-password') + ->press('Log in') + ->waitForLocation('/login') + ->assertPathIs('/login'); + }); + } + + public function test_platform_admin_can_login_and_logout(): void + { + $this->platformAdminUser(); + + $this->browse(function (Browser $browser): void { + $browser->visit('/login') + ->type('email', 'platform.admin@example.test') + ->type('password', $this->browserPassword) + ->press('Log in') + ->waitForLocation('/admin') + ->assertPathIs('/admin') + ->assertSee('Platform') + ->click('.wms-user-card-button') + ->waitFor('[data-test="logout-button"]') + ->click('[data-test="logout-button"]') + ->waitForLocation('/') + ->assertPathIs('/'); + }); + } + + public function test_tenant_user_login_routes_to_warehouse_dashboard(): void + { + $this->tenantUser(); + $this->seedWarehouse(); + + $this->browse(function (Browser $browser): void { + $browser->visit('/login') + ->type('email', 'tenant.user@example.test') + ->type('password', $this->browserPassword) + ->press('Log in') + ->waitForLocation('/dashboard') + ->assertPathIs('/dashboard') + ->assertSee('Main Warehouse'); + }); + } +} diff --git a/tests/Browser/Concerns/InteractsWithWmsBrowser.php b/tests/Browser/Concerns/InteractsWithWmsBrowser.php new file mode 100644 index 00000000..78cf617b --- /dev/null +++ b/tests/Browser/Concerns/InteractsWithWmsBrowser.php @@ -0,0 +1,46 @@ +assertSee($title) + ->assertPresent('.wms-page-hero') + ->assertScript('document.querySelectorAll(".wms-page-hero").length === 1'); + + if ($description !== null) { + $browser->assertSee($description); + } + } + + protected function assertDesktopSidebarIsAvailable(Browser $browser): void + { + $browser->resize(1440, 1000) + ->waitFor('.wms-sidebar', 5) + ->assertVisible('.wms-sidebar') + ->assertPresent('[data-wms-sidebar-toggle]') + ->assertVisible('.wms-content'); + } + + protected function openMobileNavigation(Browser $browser): void + { + $browser->resize(390, 844) + ->waitFor('.wms-mobile-header', 5) + ->assertVisible('.wms-mobile-header') + ->assertVisible('[data-wms-mobile-open]') + ->click('[data-wms-mobile-open]') + ->pause(250) + ->assertScript('document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); + } + + protected function closeMobileNavigation(Browser $browser): void + { + $browser->click('[data-wms-mobile-close]') + ->pause(250) + ->assertScript('! document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); + } +} diff --git a/tests/Browser/Concerns/InteractsWithWmsData.php b/tests/Browser/Concerns/InteractsWithWmsData.php new file mode 100644 index 00000000..c14c1c7c --- /dev/null +++ b/tests/Browser/Concerns/InteractsWithWmsData.php @@ -0,0 +1,158 @@ +firstOrCreate( + ['slug' => 'mpba-limited'], + [ + 'name' => 'MPBA Limited', + 'status' => 'active', + 'timezone' => 'Europe/London', + 'is_system_company' => true, + 'billing_email' => 'mark.gregory@mpba.co.uk', + ], + ); + + return User::query()->updateOrCreate( + ['email' => $overrides['email'] ?? 'platform.admin@example.test'], + [ + 'company_id' => $company->id, + 'name' => $overrides['name'] ?? 'Platform Admin', + 'password' => Hash::make($this->browserPassword), + 'email_verified_at' => now(), + 'is_platform_admin' => true, + 'is_company_admin' => true, + 'status' => 'active', + ...$overrides, + ], + ); + } + + protected function tenantUser(array $overrides = []): User + { + $company = $this->tenantCompany(); + + return User::query()->updateOrCreate( + ['email' => $overrides['email'] ?? 'tenant.user@example.test'], + [ + 'company_id' => $company->id, + 'name' => $overrides['name'] ?? 'Tenant User', + 'password' => Hash::make($this->browserPassword), + 'email_verified_at' => now(), + 'is_platform_admin' => false, + 'is_company_admin' => true, + 'status' => 'active', + ...$overrides, + ], + ); + } + + protected function tenantCompany(array $overrides = []): Company + { + $company = Company::query()->updateOrCreate( + ['slug' => $overrides['slug'] ?? 'test-company'], + [ + 'name' => $overrides['name'] ?? 'Test Company', + 'status' => $overrides['status'] ?? 'active', + 'timezone' => 'Europe/London', + 'is_system_company' => false, + 'billing_email' => $overrides['billing_email'] ?? 'billing@test-company.example', + ...$overrides, + ], + ); + + if (! $company->hasActiveLicense()) { + LicenseKey::issueFor($company, 'Browser Test Licence', 'enterprise', 250); + } + + return $company; + } + + protected function seedPlatformPrices(): void + { + foreach ([ + ['basic', 'Basic', 'Starter WMS access', 'Core warehouse management for small teams.', 4900, 49000, 10, false, true, ['2 warehouses', '10 users', 'Standard support']], + ['advanced', 'Advanced', 'Growing WMS operations', 'More users, warehouses and automation controls.', 14900, 149000, 20, true, true, ['5 warehouses', '100 users', 'Advanced support']], + ['pro', 'Pro', 'Professional WMS command centre', 'Full operational visibility for larger teams.', 29900, 299000, 30, false, true, ['15 warehouses', 'Unlimited dashboards', 'Priority support']], + ] as [$code, $name, $strapline, $description, $monthly, $annual, $sortOrder, $featured, $active, $features]) { + PlatformPrice::query()->updateOrCreate( + ['code' => $code], + [ + 'name' => $name, + 'strapline' => $strapline, + 'description' => $description, + 'monthly_amount' => $monthly, + 'annual_amount' => $annual, + 'currency' => 'gbp', + 'badge_label' => $featured ? 'Popular' : null, + 'features' => $features, + 'sort_order' => $sortOrder, + 'is_featured' => $featured, + 'is_active' => $active, + ], + ); + } + } + + protected function seedWarehouse(?Company $company = null): Warehouse + { + $company ??= $this->tenantCompany(); + + $warehouse = Warehouse::query()->updateOrCreate( + ['company_id' => $company->id, 'code' => 'MAIN'], + [ + 'name' => 'Main Warehouse', + 'address' => 'Main Road, Nottingham', + 'status' => 'active', + 'timezone' => 'Europe/London', + ], + ); + + WarehouseValidationRule::query()->updateOrCreate( + ['warehouse_id' => $warehouse->id, 'rule_key' => 'batch-required'], + [ + 'name' => 'Require batch number', + 'severity' => 'blocker', + 'is_active' => true, + 'configuration' => ['field' => 'batch_no'], + ], + ); + + return $warehouse; + } + + protected function seedPlatformDashboardData(): void + { + $company = $this->tenantCompany(['name' => 'Customer One', 'slug' => 'customer-one']); + $this->seedWarehouse($company); + $this->seedPlatformPrices(); + + Payment::query()->updateOrCreate( + ['stripe_payment_intent_id' => 'pi_browser_paid'], + [ + 'company_id' => $company->id, + 'amount' => 12345, + 'currency' => 'gbp', + 'status' => 'paid', + 'description' => 'Browser test payment', + 'paid_at' => now(), + 'metadata' => ['source' => 'dusk'], + ], + ); + } +} diff --git a/tests/Browser/DesktopNavigationBrowserTest.php b/tests/Browser/DesktopNavigationBrowserTest.php new file mode 100644 index 00000000..1a11a46c --- /dev/null +++ b/tests/Browser/DesktopNavigationBrowserTest.php @@ -0,0 +1,109 @@ +platformAdminUser(); + $this->seedPlatformDashboardData(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin'); + + $this->assertDesktopSidebarIsAvailable($browser); + + foreach ([ + 'Platform', 'Analytics', 'Inventory', 'Replenishment', 'Shipping', 'Transportation', 'Carriers', + 'M3 Integration', 'Sentinel', 'Documents', 'Reporting', 'Warehouses', 'Validation Rules', + 'Pricing', 'Configuration', 'Security', 'Audit', 'Companies', 'Licences', 'Payments', 'Prices', + ] as $label) { + $browser->assertSee($label); + } + }); + } + + public function test_desktop_sidebar_collapse_and_brand_expand_preserve_navigation(): void + { + $admin = $this->platformAdminUser(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->resize(1440, 1000) + ->visit('/admin') + ->waitFor('.wms-sidebar') + ->click('[data-wms-sidebar-toggle]') + ->pause(250) + ->assertScript('document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")') + ->assertPresent('[data-wms-brand-toggle]') + ->click('[data-wms-brand-toggle]') + ->pause(250) + ->assertScript('! document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")') + ->clickLink('Analytics') + ->waitForLocation('/analytics') + ->assertSee('Analytics'); + }); + } + + /** + * @dataProvider navigatorRoutes + */ + public function test_every_desktop_navigator_page_has_one_professional_header(string $path, string $title): void + { + $admin = $this->platformAdminUser(); + $this->seedPlatformDashboardData(); + + $this->browse(function (Browser $browser) use ($admin, $path, $title): void { + $browser->loginAs($admin) + ->resize(1440, 1000) + ->visit($path) + ->waitFor('.wms-page-hero', 5); + + $this->assertSingleProfessionalHeader($browser, $title); + $browser->assertVisible('.wms-sidebar'); + }); + } + + /** + * @return array + */ + public static function navigatorRoutes(): array + { + return [ + 'platform' => ['/admin', 'Platform'], + 'platform pricing' => ['/platform/pricing', 'Pricing'], + 'companies' => ['/admin/companies', 'Companies'], + 'licences' => ['/admin/licenses', 'Licence Keys'], + 'payments' => ['/admin/payments', 'Payments'], + 'price crud' => ['/admin/prices', 'Platform Prices'], + 'warehouse dashboard' => ['/dashboard', 'Warehouse'], + 'warehouses' => ['/warehouses', 'Warehouses'], + 'validation rules' => ['/warehouse-rules', 'Validation Rules'], + 'analytics' => ['/analytics', 'Analytics'], + 'inventory' => ['/inventory-management', 'Inventory'], + 'replenishment' => ['/replenishment', 'Replenishment'], + 'shipping' => ['/shipping', 'Shipping'], + 'transportation' => ['/transportation', 'Transportation'], + 'carriers' => ['/carrier-management', 'Carriers'], + 'm3' => ['/m3-integration', 'M3 Integration'], + 'sentinel' => ['/sentinel-integration', 'Sentinel'], + 'documents' => ['/document-archive', 'Documents'], + 'reporting' => ['/reporting', 'Reporting'], + 'configuration' => ['/configuration', 'Configuration'], + 'security' => ['/security', 'Security'], + 'audit' => ['/audit', 'Audit'], + ]; + } +} diff --git a/tests/Browser/ExampleTest.php b/tests/Browser/ExampleTest.php index 2d451fc5..288f061f 100644 --- a/tests/Browser/ExampleTest.php +++ b/tests/Browser/ExampleTest.php @@ -4,18 +4,32 @@ namespace Tests\Browser; use Illuminate\Foundation\Testing\DatabaseMigrations; use Laravel\Dusk\Browser; +use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\DuskTestCase; class ExampleTest extends DuskTestCase { - /** - * A basic browser test example. - */ - public function test_basic_example(): void + use DatabaseMigrations; + use InteractsWithWmsData; + + public function test_application_homepage_is_reachable_in_browser(): void { - $this->browse(function (Browser $browser) { + $this->browse(function (Browser $browser): void { $browser->visit('/') ->assertSee('Laravel'); }); } + + public function test_authenticated_application_shell_is_reachable_in_browser(): void + { + $admin = $this->platformAdminUser(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin') + ->waitFor('.wms-app-shell', 5) + ->assertPresent('.wms-sidebar') + ->assertPresent('.wms-content'); + }); + } } diff --git a/tests/Browser/MobileNavigationBrowserTest.php b/tests/Browser/MobileNavigationBrowserTest.php new file mode 100644 index 00000000..32080bd2 --- /dev/null +++ b/tests/Browser/MobileNavigationBrowserTest.php @@ -0,0 +1,81 @@ +platformAdminUser(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin'); + + $this->openMobileNavigation($browser); + $browser->assertVisible('.wms-sidebar') + ->assertSee('Platform') + ->assertSee('Administration') + ->assertSee('Prices'); + + $this->closeMobileNavigation($browser); + }); + } + + public function test_mobile_drawer_navigates_to_every_major_menu_group(): void + { + $admin = $this->platformAdminUser(); + $this->seedPlatformDashboardData(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin'); + + foreach ([ + 'Analytics' => '/analytics', + 'Inventory' => '/inventory-management', + 'Documents' => '/document-archive', + 'Warehouses' => '/warehouses', + 'Prices' => '/admin/prices', + ] as $label => $path) { + $this->openMobileNavigation($browser); + $browser->clickLink($label) + ->waitForLocation($path) + ->waitFor('.wms-page-hero', 5) + ->assertPathIs($path) + ->assertSee($label === 'Prices' ? 'Platform Prices' : $label) + ->assertScript('! document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); + } + }); + } + + public function test_mobile_tablet_and_desktop_breakpoints_keep_navigation_accessible(): void + { + $admin = $this->platformAdminUser(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin') + ->resize(390, 844) + ->waitFor('.wms-mobile-header', 5) + ->assertVisible('[data-wms-mobile-open]') + ->resize(820, 1180) + ->pause(250) + ->assertVisible('[data-wms-mobile-open]') + ->resize(1440, 1000) + ->pause(250) + ->assertVisible('.wms-sidebar') + ->assertPresent('[data-wms-sidebar-toggle]'); + }); + } +} diff --git a/tests/Browser/PlatformAdministrationBrowserTest.php b/tests/Browser/PlatformAdministrationBrowserTest.php new file mode 100644 index 00000000..26c52fdb --- /dev/null +++ b/tests/Browser/PlatformAdministrationBrowserTest.php @@ -0,0 +1,101 @@ +platformAdminUser(); + $this->seedPlatformDashboardData(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin') + ->waitFor('.wms-page-hero', 5); + + $this->assertSingleProfessionalHeader($browser, 'Platform'); + + $browser->assertSee('Basic') + ->assertSee('Advanced') + ->assertSee('Pro') + ->assertSee('Customer One') + ->assertSee('Prices'); + }); + } + + public function test_company_administration_can_create_company_and_show_validation_errors(): void + { + $admin = $this->platformAdminUser(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin/companies') + ->waitFor('.wms-page-hero') + ->press('Create') + ->waitForText('The name field is required', 5) + ->type('name', 'Abbott Logistics Limited') + ->type('billing_email', 'billing@abbott.example') + ->select('status', 'active') + ->press('Create') + ->waitForText('Abbott Logistics Limited', 5) + ->assertSee('Company created.') + ->assertSee('Abbott Logistics Limited'); + }); + } + + public function test_license_administration_can_issue_license(): void + { + $admin = $this->platformAdminUser(); + $this->tenantCompany(['name' => 'Licence Customer', 'slug' => 'licence-customer']); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin/licenses') + ->waitFor('.wms-page-hero') + ->type('label', 'Enterprise Licence') + ->select('plan', 'enterprise') + ->type('max_users', '500') + ->press('Issue') + ->waitForText('Licence key issued', 5) + ->assertSee('Enterprise Licence') + ->assertSee('enterprise'); + }); + } + + public function test_payment_administration_can_record_paid_pending_and_validation_paths(): void + { + $admin = $this->platformAdminUser(); + $this->tenantCompany(['name' => 'Payment Customer', 'slug' => 'payment-customer']); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin/payments') + ->waitFor('.wms-page-hero') + ->type('amount', '12345') + ->type('currency', 'gbp') + ->select('status', 'paid') + ->type('description', 'Manual bank transfer') + ->press('Add') + ->waitForText('Payment record created.', 5) + ->assertSee('Manual bank transfer') + ->assertSee('GBP 123.45') + ->select('status', 'pending') + ->type('amount', '5000') + ->type('description', 'Awaiting payment') + ->press('Add') + ->waitForText('Awaiting payment', 5) + ->assertSee('pending'); + }); + } +} diff --git a/tests/Browser/PlatformPricingBrowserTest.php b/tests/Browser/PlatformPricingBrowserTest.php new file mode 100644 index 00000000..8e95164a --- /dev/null +++ b/tests/Browser/PlatformPricingBrowserTest.php @@ -0,0 +1,96 @@ +tenantUser(); + $this->seedPlatformPrices(); + + PlatformPrice::query()->updateOrCreate( + ['code' => 'hidden'], + [ + 'name' => 'Hidden', + 'monthly_amount' => 1, + 'currency' => 'gbp', + 'features' => [], + 'sort_order' => 99, + 'is_featured' => false, + 'is_active' => false, + ], + ); + + $this->browse(function (Browser $browser) use ($user): void { + $browser->loginAs($user) + ->visit('/platform/pricing') + ->waitFor('.wms-page-hero', 5); + + $this->assertSingleProfessionalHeader($browser, 'Pricing'); + + $browser->assertSee('Basic') + ->assertSee('Advanced') + ->assertSee('Pro') + ->assertSee('£49.00') + ->assertDontSee('Hidden'); + }); + } + + public function test_admin_price_crud_create_edit_delete_and_validation_paths(): void + { + $admin = $this->platformAdminUser(); + $this->seedPlatformPrices(); + + $this->browse(function (Browser $browser) use ($admin): void { + $browser->loginAs($admin) + ->visit('/admin/prices') + ->waitFor('.wms-page-hero') + ->assertSee('Basic') + ->clickLink('Create Price') + ->waitForLocation('/admin/prices/create') + ->waitFor('.wms-page-hero') + ->press('Save Price') + ->waitForText('The name field is required', 5) + ->type('name', 'Ultimate Plan') + ->type('code', 'ultimate-plan') + ->type('strapline', 'Unlimited WMS operations') + ->type('badge_label', 'Enterprise') + ->type('monthly_amount', '499.00') + ->type('annual_amount', '4990.00') + ->type('currency', 'gbp') + ->type('sort_order', '40') + ->type('description', 'Unlimited warehouses and users for enterprise operators.') + ->type('features', "Unlimited warehouses\nUnlimited users\nPriority support") + ->check('is_featured') + ->check('is_active') + ->press('Save Price') + ->waitForLocation('/admin/prices') + ->assertSee('Price created.') + ->assertSee('Ultimate Plan') + ->clickLink('Edit') + ->waitFor('.wms-page-hero') + ->type('name', 'Ultimate Plus') + ->type('monthly_amount', '599.00') + ->press('Update Price') + ->waitForLocation('/admin/prices') + ->assertSee('Price updated.') + ->assertSee('Ultimate Plus') + ->press('Delete') + ->acceptDialog() + ->waitForText('Price deleted.', 5) + ->assertDontSee('Ultimate Plus'); + }); + } +} diff --git a/tests/Browser/README_DUSK_WMS.md b/tests/Browser/README_DUSK_WMS.md new file mode 100644 index 00000000..a976792b --- /dev/null +++ b/tests/Browser/README_DUSK_WMS.md @@ -0,0 +1,35 @@ +# WMS Dusk coverage pack + +This pack adds browser coverage for the current WMS UI baseline: + +- Authentication browser flows. +- Desktop sidebar navigation and collapse / expand behaviour. +- Mobile hamburger menu and slide-out drawer behaviour. +- Single professional page hero/header on navigator pages. +- Platform dashboard and pricing card display. +- Platform pricing customer grid. +- Administration pricing CRUD. +- Company, licence and payment administration forms. +- Warehouse dashboard, warehouse form and validation rule form. +- Mobile, tablet and desktop responsive rendering checks. + +Run with: + +```bash +php artisan dusk +``` + +Recommended local setup before running Dusk: + +```bash +php artisan optimize:clear +php artisan migrate +php artisan dusk:chrome-driver --detect +``` + +If the app uses database sessions, make sure the sessions table exists: + +```bash +php artisan session:table +php artisan migrate +``` diff --git a/tests/Browser/ResponsiveBaselineBrowserTest.php b/tests/Browser/ResponsiveBaselineBrowserTest.php new file mode 100644 index 00000000..2b8467ab --- /dev/null +++ b/tests/Browser/ResponsiveBaselineBrowserTest.php @@ -0,0 +1,62 @@ +platformAdminUser(); + $this->seedPlatformDashboardData(); + + $this->browse(function (Browser $browser) use ($admin, $path, $title): void { + $browser->loginAs($admin); + + foreach ([[390, 844], [820, 1180], [1440, 1000]] as [$width, $height]) { + $browser->resize($width, $height) + ->visit($path) + ->waitFor('.wms-page-hero', 5); + + $this->assertSingleProfessionalHeader($browser, $title); + + if ($width < 1024) { + $browser->assertVisible('[data-wms-mobile-open]'); + } else { + $browser->assertVisible('.wms-sidebar'); + } + } + }); + } + + /** + * @return array + */ + public static function responsivePages(): array + { + return [ + 'platform dashboard' => ['/admin', 'Platform'], + 'platform pricing' => ['/platform/pricing', 'Pricing'], + 'admin prices' => ['/admin/prices', 'Platform Prices'], + 'warehouse dashboard' => ['/dashboard', 'Warehouse'], + 'warehouses' => ['/warehouses', 'Warehouses'], + 'validation rules' => ['/warehouse-rules', 'Validation Rules'], + 'analytics' => ['/analytics', 'Analytics'], + 'inventory' => ['/inventory-management', 'Inventory'], + 'documents' => ['/document-archive', 'Documents'], + 'configuration' => ['/configuration', 'Configuration'], + ]; + } +} diff --git a/tests/Browser/WarehouseBrowserTest.php b/tests/Browser/WarehouseBrowserTest.php new file mode 100644 index 00000000..eccdc6e7 --- /dev/null +++ b/tests/Browser/WarehouseBrowserTest.php @@ -0,0 +1,87 @@ +tenantUser(); + $this->seedWarehouse(); + + $this->browse(function (Browser $browser) use ($user): void { + $browser->loginAs($user) + ->visit('/dashboard') + ->waitFor('.wms-page-hero') + ->assertSee('Main Warehouse') + ->assertSee('Require batch number') + ->visit('/warehouses') + ->waitFor('.wms-page-hero'); + + $this->assertSingleProfessionalHeader($browser, 'Warehouses'); + + $browser->assertSee('Main Warehouse') + ->visit('/warehouse-rules') + ->waitFor('.wms-page-hero'); + + $this->assertSingleProfessionalHeader($browser, 'Validation Rules'); + + $browser->assertSee('Require batch number') + ->assertSee('batch-required'); + }); + } + + public function test_warehouse_crud_forms_cover_create_and_validation_paths(): void + { + $user = $this->tenantUser(); + + $this->browse(function (Browser $browser) use ($user): void { + $browser->loginAs($user) + ->visit('/warehouses') + ->waitFor('.wms-page-hero') + ->press('Create') + ->waitForText('The code field is required', 5) + ->type('code', 'EAST') + ->type('name', 'East Midlands DC') + ->type('address', 'Main Road, Nottingham') + ->press('Create') + ->waitForText('Warehouse created.', 5) + ->assertSee('East Midlands DC'); + }); + } + + public function test_validation_rule_form_covers_create_and_validation_paths(): void + { + $user = $this->tenantUser(); + $this->seedWarehouse(); + + $this->browse(function (Browser $browser) use ($user): void { + $browser->loginAs($user) + ->visit('/warehouse-rules') + ->waitFor('.wms-page-hero') + ->type('name', '') + ->type('rule_key', '') + ->type('configuration', '{bad json') + ->press('Add rule') + ->waitForText('The name field is required', 5) + ->type('name', 'Require serial number') + ->type('rule_key', 'serial-required') + ->select('severity', 'error') + ->type('configuration', '{"field":"serial_no"}') + ->press('Add rule') + ->waitForText('Validation rule created.', 5) + ->assertSee('Require serial number') + ->assertSee('serial-required'); + }); + } +}