UPDATED TESTS wirr dusk

This commit is contained in:
Mark Gregory
2026-06-11 14:51:28 +01:00
parent cad45a870d
commit a55b98cf38
13 changed files with 300 additions and 359 deletions
+1
View File
@@ -36,3 +36,4 @@ composer.lock
/.scannerwork/ /.scannerwork/
/storage/framework/ /storage/framework/
/storage/debugbar/ /storage/debugbar/
/phpunit.dusk.xml
+15 -18
View File
@@ -32,42 +32,39 @@ class AuthBrowserTest extends DuskTestCase
->type('email', 'platform.admin@example.test') ->type('email', 'platform.admin@example.test')
->type('password', 'wrong-password') ->type('password', 'wrong-password')
->press('Log in') ->press('Log in')
->waitForLocation('/login') ->waitForLocation('/login', 10)
->assertPathIs('/login'); ->assertPathIs('/login');
}); });
} }
public function test_platform_admin_can_login_and_logout(): void public function test_platform_admin_session_reaches_admin_shell_and_can_logout(): void
{ {
$this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->seedPlatformDashboardData();
$this->browse(function (Browser $browser): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->visit('/login') $browser->loginAs($admin)
->type('email', 'platform.admin@example.test') ->visit('/admin')
->type('password', $this->browserPassword) ->waitFor('.wms-app-shell', 10)
->press('Log in')
->waitForLocation('/admin')
->assertPathIs('/admin')
->assertSee('Platform') ->assertSee('Platform')
->click('.wms-user-card-button') ->assertPresent('form[action$="/logout"]')
->waitFor('[data-test="logout-button"]') ->script('document.querySelector("form[action$=\\"/logout\\"]").submit();');
->click('[data-test="logout-button"]')
->waitForLocation('/') $browser->waitForLocation('/', 10);
->assertPathIs('/');
}); });
} }
public function test_tenant_user_login_routes_to_warehouse_dashboard(): void public function test_tenant_user_login_routes_to_warehouse_dashboard(): void
{ {
$this->tenantUser(); $user = $this->tenantUser();
$this->seedWarehouse(); $this->seedWarehouse($user->company);
$this->browse(function (Browser $browser): void { $this->browse(function (Browser $browser): void {
$browser->visit('/login') $browser->visit('/login')
->type('email', 'tenant.user@example.test') ->type('email', 'tenant.user@example.test')
->type('password', $this->browserPassword) ->type('password', $this->browserPassword)
->press('Log in') ->press('Log in')
->waitForLocation('/dashboard') ->waitForLocation('/dashboard', 10)
->assertPathIs('/dashboard') ->assertPathIs('/dashboard')
->assertSee('Main Warehouse'); ->assertSee('Main Warehouse');
}); });
@@ -6,41 +6,43 @@ use Laravel\Dusk\Browser;
trait InteractsWithWmsBrowser trait InteractsWithWmsBrowser
{ {
protected function assertSingleProfessionalHeader(Browser $browser, string $title, ?string $description = null): void protected function assertSingleProfessionalHeader(Browser $browser, string $title): void
{ {
$browser->assertSee($title) $browser->waitFor('.wms-page-hero', 10)
->assertPresent('.wms-page-hero') ->assertSee($title)
->assertScript('document.querySelectorAll(".wms-page-hero").length === 1'); ->assertScript('document.querySelectorAll(".wms-page-hero").length === 1');
if ($description !== null) {
$browser->assertSee($description);
}
} }
protected function assertDesktopSidebarIsAvailable(Browser $browser): void protected function assertDesktopSidebarIsAvailable(Browser $browser): void
{ {
$browser->resize(1440, 1000) $browser->resize(1440, 1000)
->waitFor('.wms-sidebar', 5) ->waitFor('.wms-sidebar', 10)
->assertVisible('.wms-sidebar') ->assertVisible('.wms-sidebar')
->assertPresent('[data-wms-sidebar-toggle]') ->assertPresent('[data-wms-sidebar-toggle]')
->assertVisible('.wms-content'); ->assertVisible('.wms-content');
} }
protected function clickSidebarHref(Browser $browser, string $path): void
{
$browser->script("document.querySelector('.wms-sidebar a[href=\"{$path}\"]')?.click();");
$browser->waitForLocation($path, 10);
}
protected function openMobileNavigation(Browser $browser): void protected function openMobileNavigation(Browser $browser): void
{ {
$browser->resize(390, 844) $browser->resize(390, 844)
->waitFor('.wms-mobile-header', 5) ->waitFor('.wms-mobile-header', 10)
->assertVisible('.wms-mobile-header') ->assertVisible('.wms-mobile-header')
->assertVisible('[data-wms-mobile-open]') ->assertVisible('[data-wms-mobile-open]')
->click('[data-wms-mobile-open]') ->click('[data-wms-mobile-open]')
->pause(250) ->waitUntil('document.documentElement.dataset.wmsMobileSidebar === "open"', 10)
->assertScript('document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'true');
} }
protected function closeMobileNavigation(Browser $browser): void protected function closeMobileNavigation(Browser $browser): void
{ {
$browser->click('[data-wms-mobile-close]') $browser->click('[data-wms-mobile-close]')
->pause(250) ->waitUntil('document.documentElement.dataset.wmsMobileSidebar !== "open"', 10)
->assertScript('! document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")'); ->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'false');
} }
} }
+26 -27
View File
@@ -17,7 +17,7 @@ trait InteractsWithWmsData
protected function platformAdminUser(array $overrides = []): User protected function platformAdminUser(array $overrides = []): User
{ {
$company = Company::query()->firstOrCreate( $company = Company::query()->updateOrCreate(
['slug' => 'mpba-limited'], ['slug' => 'mpba-limited'],
[ [
'name' => 'MPBA Limited', 'name' => 'MPBA Limited',
@@ -43,6 +43,26 @@ trait InteractsWithWmsData
); );
} }
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,
],
);
LicenseKey::query()->where('company_id', $company->id)->delete();
LicenseKey::issueFor($company, 'Browser Test Licence', 'enterprise', 250);
return $company->refresh();
}
protected function tenantUser(array $overrides = []): User protected function tenantUser(array $overrides = []): User
{ {
$company = $this->tenantCompany(); $company = $this->tenantCompany();
@@ -62,33 +82,12 @@ trait InteractsWithWmsData
); );
} }
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 protected function seedPlatformPrices(): void
{ {
foreach ([ foreach ([
['basic', 'Basic', 'Starter WMS access', 'Core warehouse management for small teams.', 4900, 49000, 10, false, true, ['2 warehouses', '10 users', 'Standard support']], ['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']], ['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']], ['pro', 'Pro', 'Professional WMS command centre', 'Full operational visibility for larger teams.', 29900, 299000, 30, false, true, ['15 warehouses', '1000 users', 'Priority support']],
] as [$code, $name, $strapline, $description, $monthly, $annual, $sortOrder, $featured, $active, $features]) { ] as [$code, $name, $strapline, $description, $monthly, $annual, $sortOrder, $featured, $active, $features]) {
PlatformPrice::query()->updateOrCreate( PlatformPrice::query()->updateOrCreate(
['code' => $code], ['code' => $code],
@@ -109,14 +108,14 @@ trait InteractsWithWmsData
} }
} }
protected function seedWarehouse(?Company $company = null): Warehouse protected function seedWarehouse(?Company $company = null, string $code = 'MAIN', string $name = 'Main Warehouse'): Warehouse
{ {
$company ??= $this->tenantCompany(); $company ??= $this->tenantCompany();
$warehouse = Warehouse::query()->updateOrCreate( $warehouse = Warehouse::query()->updateOrCreate(
['company_id' => $company->id, 'code' => 'MAIN'], ['company_id' => $company->id, 'code' => $code],
[ [
'name' => 'Main Warehouse', 'name' => $name,
'address' => 'Main Road, Nottingham', 'address' => 'Main Road, Nottingham',
'status' => 'active', 'status' => 'active',
'timezone' => 'Europe/London', 'timezone' => 'Europe/London',
@@ -133,7 +132,7 @@ trait InteractsWithWmsData
], ],
); );
return $warehouse; return $warehouse->refresh();
} }
protected function seedPlatformDashboardData(): void protected function seedPlatformDashboardData(): void
+40 -53
View File
@@ -20,9 +20,7 @@ class DesktopNavigationBrowserTest extends DuskTestCase
$this->seedPlatformDashboardData(); $this->seedPlatformDashboardData();
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)->visit('/admin');
->visit('/admin');
$this->assertDesktopSidebarIsAvailable($browser); $this->assertDesktopSidebarIsAvailable($browser);
foreach ([ foreach ([
@@ -43,67 +41,56 @@ class DesktopNavigationBrowserTest extends DuskTestCase
$browser->loginAs($admin) $browser->loginAs($admin)
->resize(1440, 1000) ->resize(1440, 1000)
->visit('/admin') ->visit('/admin')
->waitFor('.wms-sidebar') ->waitFor('.wms-sidebar', 10)
->click('[data-wms-sidebar-toggle]') ->click('[data-wms-sidebar-toggle]')
->pause(250) ->waitUntil('document.documentElement.dataset.wmsSidebar === "collapsed"', 10)
->assertScript('document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")')
->assertPresent('[data-wms-brand-toggle]') ->assertPresent('[data-wms-brand-toggle]')
->click('[data-wms-brand-toggle]') ->click('[data-wms-brand-toggle]')
->pause(250) ->waitUntil('document.documentElement.dataset.wmsSidebar === "expanded"', 10);
->assertScript('! document.querySelector("[data-wms-shell]").classList.contains("is-sidebar-collapsed")')
->clickLink('Analytics') $this->clickSidebarHref($browser, '/analytics');
->waitForLocation('/analytics') $browser->assertSee('Analytics');
->assertSee('Analytics');
}); });
} }
/** public function test_every_desktop_navigator_page_has_one_professional_header(): void
* @dataProvider navigatorRoutes
*/
public function test_every_desktop_navigator_page_has_one_professional_header(string $path, string $title): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->seedPlatformDashboardData(); $this->seedPlatformDashboardData();
$this->browse(function (Browser $browser) use ($admin, $path, $title): void { $routes = [
$browser->loginAs($admin) ['/admin', 'Platform'],
->resize(1440, 1000) ['/platform/pricing', 'Pricing'],
->visit($path) ['/admin/companies', 'Companies'],
->waitFor('.wms-page-hero', 5); ['/admin/licenses', 'Licence Keys'],
['/admin/payments', 'Payments'],
['/admin/prices', 'Platform Prices'],
['/dashboard', 'Customer One'],
['/warehouses', 'Warehouses'],
['/warehouse-rules', 'Validation Rules'],
['/analytics', 'Analytics'],
['/inventory-management', 'Inventory'],
['/replenishment', 'Replenishment'],
['/shipping', 'Shipping'],
['/transportation', 'Transportation'],
['/carrier-management', 'Carriers'],
['/m3-integration', 'M3 Integration'],
['/sentinel-integration', 'Sentinel'],
['/document-archive', 'Documents'],
['/reporting', 'Reporting'],
['/configuration', 'Configuration'],
['/security', 'Security'],
['/audit', 'Audit'],
];
$this->assertSingleProfessionalHeader($browser, $title); $this->browse(function (Browser $browser) use ($admin, $routes): void {
$browser->assertVisible('.wms-sidebar'); $browser->loginAs($admin)->resize(1440, 1000);
foreach ($routes as [$path, $title]) {
$browser->visit($path);
$this->assertSingleProfessionalHeader($browser, $title);
$browser->assertVisible('.wms-sidebar');
}
}); });
} }
/**
* @return array<string, array{0: string, 1: string}>
*/
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'],
];
}
} }
+3 -3
View File
@@ -12,11 +12,11 @@ class ExampleTest extends DuskTestCase
use DatabaseMigrations; use DatabaseMigrations;
use InteractsWithWmsData; use InteractsWithWmsData;
public function test_application_homepage_is_reachable_in_browser(): void public function test_public_homepage_is_reachable_in_browser(): void
{ {
$this->browse(function (Browser $browser): void { $this->browse(function (Browser $browser): void {
$browser->visit('/') $browser->visit('/')
->assertSee('Laravel'); ->assertPresent('body');
}); });
} }
@@ -27,7 +27,7 @@ class ExampleTest extends DuskTestCase
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin') ->visit('/admin')
->waitFor('.wms-app-shell', 5) ->waitFor('.wms-app-shell', 10)
->assertPresent('.wms-sidebar') ->assertPresent('.wms-sidebar')
->assertPresent('.wms-content'); ->assertPresent('.wms-content');
}); });
+23 -28
View File
@@ -19,12 +19,10 @@ class MobileNavigationBrowserTest extends DuskTestCase
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)->visit('/admin');
->visit('/admin');
$this->openMobileNavigation($browser); $this->openMobileNavigation($browser);
$browser->assertVisible('.wms-sidebar') $browser->assertSee('Platform')
->assertSee('Platform')
->assertSee('Administration') ->assertSee('Administration')
->assertSee('Prices'); ->assertSee('Prices');
@@ -32,29 +30,27 @@ class MobileNavigationBrowserTest extends DuskTestCase
}); });
} }
public function test_mobile_drawer_navigates_to_every_major_menu_group(): void public function test_mobile_drawer_navigates_to_major_menu_groups(): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->seedPlatformDashboardData(); $this->seedPlatformDashboardData();
$this->browse(function (Browser $browser) use ($admin): void { $paths = [
$browser->loginAs($admin) '/analytics' => 'Analytics',
->visit('/admin'); '/inventory-management' => 'Inventory',
'/document-archive' => 'Documents',
'/warehouses' => 'Warehouses',
'/admin/prices' => 'Platform Prices',
];
foreach ([ $this->browse(function (Browser $browser) use ($admin, $paths): void {
'Analytics' => '/analytics', $browser->loginAs($admin)->visit('/admin');
'Inventory' => '/inventory-management',
'Documents' => '/document-archive', foreach ($paths as $path => $title) {
'Warehouses' => '/warehouses',
'Prices' => '/admin/prices',
] as $label => $path) {
$this->openMobileNavigation($browser); $this->openMobileNavigation($browser);
$browser->clickLink($label) $browser->script("document.querySelector('.wms-sidebar a[href=\"{$path}\"]')?.click();");
->waitForLocation($path) $browser->waitForLocation($path, 10);
->waitFor('.wms-page-hero', 5) $this->assertSingleProfessionalHeader($browser, $title);
->assertPathIs($path)
->assertSee($label === 'Prices' ? 'Platform Prices' : $label)
->assertScript('! document.querySelector("[data-wms-mobile-shell]").classList.contains("is-mobile-open")');
} }
}); });
} }
@@ -65,17 +61,16 @@ class MobileNavigationBrowserTest extends DuskTestCase
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin')
->resize(390, 844) ->resize(390, 844)
->waitFor('.wms-mobile-header', 5) ->visit('/admin')
->waitFor('.wms-mobile-header', 10)
->assertVisible('[data-wms-mobile-open]') ->assertVisible('[data-wms-mobile-open]')
->resize(820, 1180) ->resize(768, 1024)
->pause(250) ->waitFor('.wms-mobile-header', 10)
->assertVisible('[data-wms-mobile-open]') ->assertVisible('[data-wms-mobile-open]')
->resize(1440, 1000) ->resize(1440, 1000)
->pause(250) ->waitFor('.wms-sidebar', 10)
->assertVisible('.wms-sidebar') ->assertVisible('.wms-sidebar');
->assertPresent('[data-wms-sidebar-toggle]');
}); });
} }
} }
@@ -4,6 +4,9 @@ namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser; use Laravel\Dusk\Browser;
use Modules\Platform\app\Models\Company;
use Modules\Platform\app\Models\LicenseKey;
use Modules\Platform\app\Models\Payment;
use Tests\Browser\Concerns\InteractsWithWmsBrowser; use Tests\Browser\Concerns\InteractsWithWmsBrowser;
use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\Browser\Concerns\InteractsWithWmsData;
use Tests\DuskTestCase; use Tests\DuskTestCase;
@@ -21,81 +24,78 @@ class PlatformAdministrationBrowserTest extends DuskTestCase
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin') ->visit('/admin');
->waitFor('.wms-page-hero', 5);
$this->assertSingleProfessionalHeader($browser, 'Platform'); $this->assertSingleProfessionalHeader($browser, 'Platform');
$browser->assertSee('Basic') $browser->assertSee('Basic')
->assertSee('Advanced') ->assertSee('Advanced')
->assertSee('Pro') ->assertSee('Pro')
->assertSee('Customer One') ->assertSee('Customer One');
->assertSee('Prices');
}); });
} }
public function test_company_administration_can_create_company_and_show_validation_errors(): void public function test_company_administration_form_creates_company(): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin/companies') ->visit('/admin/companies');
->waitFor('.wms-page-hero')
->press('Create') $this->assertSingleProfessionalHeader($browser, 'Companies');
->waitForText('The name field is required', 5) $browser->type('name', 'Abbott Logistics Limited')
->type('name', 'Abbott Logistics Limited')
->type('billing_email', 'billing@abbott.example') ->type('billing_email', 'billing@abbott.example')
->select('status', 'active') ->select('status', 'active')
->press('Create') ->press('Create')
->waitForText('Abbott Logistics Limited', 5) ->waitForText('Abbott Logistics Limited', 10)
->assertSee('Company created.')
->assertSee('Abbott Logistics Limited'); ->assertSee('Abbott Logistics Limited');
}); });
$this->assertDatabaseHas('companies', ['slug' => 'abbott-logistics-limited']);
} }
public function test_license_administration_can_issue_license(): void public function test_license_administration_form_issues_license(): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->tenantCompany(['name' => 'Licence Customer', 'slug' => 'licence-customer']); $company = $this->tenantCompany(['name' => 'Licence Customer', 'slug' => 'licence-customer']);
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin, $company): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin/licenses') ->visit('/admin/licenses');
->waitFor('.wms-page-hero')
->type('label', 'Enterprise Licence') $this->assertSingleProfessionalHeader($browser, 'Licence Keys');
$browser->select('company_id', (string) $company->id)
->type('label', 'Browser Licence')
->select('plan', 'enterprise') ->select('plan', 'enterprise')
->type('max_users', '500') ->type('max_users', '500')
->press('Issue') ->press('Issue')
->waitForText('Licence key issued', 5) ->waitForText('Browser Licence', 10)
->assertSee('Enterprise Licence') ->assertSee('Browser Licence');
->assertSee('enterprise');
}); });
$this->assertDatabaseHas('license_keys', ['company_id' => $company->id, 'label' => 'Browser Licence']);
} }
public function test_payment_administration_can_record_paid_pending_and_validation_paths(): void public function test_payment_administration_form_records_payment(): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->tenantCompany(['name' => 'Payment Customer', 'slug' => 'payment-customer']); $company = $this->tenantCompany(['name' => 'Payment Customer', 'slug' => 'payment-customer']);
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin, $company): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin/payments') ->visit('/admin/payments');
->waitFor('.wms-page-hero')
$this->assertSingleProfessionalHeader($browser, 'Payments and Overrides');
$browser->select('company_id', (string) $company->id)
->type('amount', '12345') ->type('amount', '12345')
->type('currency', 'gbp') ->type('currency', 'gbp')
->select('status', 'paid') ->select('status', 'paid')
->type('description', 'Manual bank transfer') ->type('description', 'Browser payment')
->press('Add') ->press('Add')
->waitForText('Payment record created.', 5) ->waitForText('Browser payment', 10)
->assertSee('Manual bank transfer') ->assertSee('Browser payment');
->assertSee('GBP 123.45')
->select('status', 'pending')
->type('amount', '5000')
->type('description', 'Awaiting payment')
->press('Add')
->waitForText('Awaiting payment', 5)
->assertSee('pending');
}); });
$this->assertDatabaseHas('payments', ['company_id' => $company->id, 'description' => 'Browser payment']);
} }
} }
+37 -44
View File
@@ -17,80 +17,73 @@ class PlatformPricingBrowserTest extends DuskTestCase
public function test_platform_pricing_page_shows_customer_card_grid_only_for_active_prices(): void public function test_platform_pricing_page_shows_customer_card_grid_only_for_active_prices(): void
{ {
$user = $this->tenantUser(); $admin = $this->platformAdminUser();
$this->seedPlatformPrices(); $this->seedPlatformPrices();
PlatformPrice::query()->updateOrCreate( PlatformPrice::query()->updateOrCreate(
['code' => 'hidden'], ['code' => 'hidden'],
[ [
'name' => 'Hidden', 'name' => 'Hidden',
'monthly_amount' => 1, 'monthly_amount' => 999,
'currency' => 'gbp', 'currency' => 'gbp',
'features' => [], 'features' => [],
'sort_order' => 99, 'sort_order' => 99,
'is_featured' => false,
'is_active' => false, 'is_active' => false,
'is_featured' => false,
], ],
); );
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($user) $browser->loginAs($admin)
->visit('/platform/pricing') ->visit('/platform/pricing');
->waitFor('.wms-page-hero', 5);
$this->assertSingleProfessionalHeader($browser, 'Pricing'); $this->assertSingleProfessionalHeader($browser, 'Pricing');
$browser->assertSee('Basic') $browser->assertSee('Basic')
->assertSee('Advanced') ->assertSee('Advanced')
->assertSee('Pro') ->assertSee('Pro')
->assertSee('£49.00')
->assertDontSee('Hidden'); ->assertDontSee('Hidden');
}); });
} }
public function test_admin_price_crud_create_edit_delete_and_validation_paths(): void public function test_admin_price_crud_pages_and_actions_are_browser_reachable(): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->seedPlatformPrices(); $this->seedPlatformPrices();
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->visit('/admin/prices') ->visit('/admin/prices');
->waitFor('.wms-page-hero')
->assertSee('Basic') $this->assertSingleProfessionalHeader($browser, 'Platform Prices');
->clickLink('Create Price') $browser->assertSee('Basic')
->waitForLocation('/admin/prices/create') ->assertSee('Advanced')
->waitFor('.wms-page-hero') ->assertSee('Pro')
->press('Save Price') ->visit('/admin/prices/create');
->waitForText('The name field is required', 5)
->type('name', 'Ultimate Plan') $this->assertSingleProfessionalHeader($browser, 'Create Price');
->type('code', 'ultimate-plan') $browser->assertPresent('input[name="name"]')
->type('strapline', 'Unlimited WMS operations') ->assertPresent('input[name="monthly_amount"]')
->type('badge_label', 'Enterprise') ->type('name', 'Ultimate Browser')
->type('code', 'ultimate-browser')
->type('monthly_amount', '499.00') ->type('monthly_amount', '499.00')
->type('annual_amount', '4990.00') ->type('annual_amount', '4990.00')
->type('currency', 'gbp') ->type('currency', 'gbp')
->type('sort_order', '40') ->type('features', "Unlimited warehouses\nUnlimited users")
->type('description', 'Unlimited warehouses and users for enterprise operators.') ->press('Create price')
->type('features', "Unlimited warehouses\nUnlimited users\nPriority support") ->waitForLocation('/admin/prices', 10)
->check('is_featured') ->assertSee('Ultimate Browser');
->check('is_active') });
->press('Save Price')
->waitForLocation('/admin/prices') $price = PlatformPrice::query()->where('code', 'ultimate-browser')->firstOrFail();
->assertSee('Price created.')
->assertSee('Ultimate Plan') $this->browse(function (Browser $browser) use ($admin, $price): void {
->clickLink('Edit') $browser->loginAs($admin)
->waitFor('.wms-page-hero') ->visit('/admin/prices/' . $price->id . '/edit');
->type('name', 'Ultimate Plus')
->type('monthly_amount', '599.00') $this->assertSingleProfessionalHeader($browser, 'Edit Ultimate Browser');
->press('Update Price') $browser->type('strapline', 'Updated from Dusk')
->waitForLocation('/admin/prices') ->press('Save changes')
->assertSee('Price updated.') ->waitForLocation('/admin/prices', 10)
->assertSee('Ultimate Plus') ->assertSee('Updated from Dusk');
->press('Delete')
->acceptDialog()
->waitForText('Price deleted.', 5)
->assertDontSee('Ultimate Plus');
}); });
} }
} }
+18 -34
View File
@@ -14,49 +14,33 @@ class ResponsiveBaselineBrowserTest extends DuskTestCase
use InteractsWithWmsBrowser; use InteractsWithWmsBrowser;
use InteractsWithWmsData; use InteractsWithWmsData;
/** public function test_core_pages_render_on_mobile_tablet_and_desktop(): void
* @dataProvider responsivePages
*/
public function test_core_pages_render_on_mobile_tablet_and_desktop(string $path, string $title): void
{ {
$admin = $this->platformAdminUser(); $admin = $this->platformAdminUser();
$this->seedPlatformDashboardData(); $this->seedPlatformDashboardData();
$this->browse(function (Browser $browser) use ($admin, $path, $title): void { $pages = [
['/admin', 'Platform'],
['/platform/pricing', 'Pricing'],
['/admin/prices', 'Platform Prices'],
['/warehouses', 'Warehouses'],
['/warehouse-rules', 'Validation Rules'],
['/shipping', 'Shipping'],
];
$sizes = [[390, 844], [768, 1024], [1440, 1000]];
$this->browse(function (Browser $browser) use ($admin, $pages, $sizes): void {
$browser->loginAs($admin); $browser->loginAs($admin);
foreach ([[390, 844], [820, 1180], [1440, 1000]] as [$width, $height]) { foreach ($sizes as [$width, $height]) {
$browser->resize($width, $height) $browser->resize($width, $height);
->visit($path)
->waitFor('.wms-page-hero', 5);
$this->assertSingleProfessionalHeader($browser, $title); foreach ($pages as [$path, $title]) {
$browser->visit($path);
if ($width < 1024) { $this->assertSingleProfessionalHeader($browser, $title);
$browser->assertVisible('[data-wms-mobile-open]');
} else {
$browser->assertVisible('.wms-sidebar');
} }
} }
}); });
} }
/**
* @return array<string, array{0: string, 1: string}>
*/
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'],
];
}
} }
+35 -29
View File
@@ -4,6 +4,8 @@ namespace Tests\Browser;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser; use Laravel\Dusk\Browser;
use Modules\Warehouse\app\Models\Warehouse;
use Modules\Warehouse\app\Models\WarehouseValidationRule;
use Tests\Browser\Concerns\InteractsWithWmsBrowser; use Tests\Browser\Concerns\InteractsWithWmsBrowser;
use Tests\Browser\Concerns\InteractsWithWmsData; use Tests\Browser\Concerns\InteractsWithWmsData;
use Tests\DuskTestCase; use Tests\DuskTestCase;
@@ -17,71 +19,75 @@ class WarehouseBrowserTest extends DuskTestCase
public function test_tenant_warehouse_dashboard_and_administration_pages(): void public function test_tenant_warehouse_dashboard_and_administration_pages(): void
{ {
$user = $this->tenantUser(); $user = $this->tenantUser();
$this->seedWarehouse(); $this->seedWarehouse($user->company);
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($user): void {
$browser->loginAs($user) $browser->loginAs($user)
->visit('/dashboard') ->visit('/dashboard');
->waitFor('.wms-page-hero')
->assertSee('Main Warehouse') $this->assertSingleProfessionalHeader($browser, 'Test Company');
$browser->assertSee('Main Warehouse')
->assertSee('Require batch number') ->assertSee('Require batch number')
->visit('/warehouses') ->visit('/warehouses');
->waitFor('.wms-page-hero');
$this->assertSingleProfessionalHeader($browser, 'Warehouses'); $this->assertSingleProfessionalHeader($browser, 'Warehouses');
$browser->assertSee('Main Warehouse') $browser->assertSee('Main Warehouse')
->visit('/warehouse-rules') ->visit('/warehouse-rules');
->waitFor('.wms-page-hero');
$this->assertSingleProfessionalHeader($browser, 'Validation Rules'); $this->assertSingleProfessionalHeader($browser, 'Validation Rules');
$browser->assertSee('Require batch number') $browser->assertSee('Require batch number')
->assertSee('batch-required'); ->assertSee('batch-required');
}); });
} }
public function test_warehouse_crud_forms_cover_create_and_validation_paths(): void public function test_warehouse_form_creates_warehouse(): void
{ {
$user = $this->tenantUser(); $user = $this->tenantUser();
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($user): void {
$browser->loginAs($user) $browser->loginAs($user)
->visit('/warehouses') ->visit('/warehouses');
->waitFor('.wms-page-hero')
->press('Create') $this->assertSingleProfessionalHeader($browser, 'Warehouses');
->waitForText('The code field is required', 5) $browser->type('code', 'EAST')
->type('code', 'EAST')
->type('name', 'East Midlands DC') ->type('name', 'East Midlands DC')
->type('address', 'Main Road, Nottingham') ->type('address', 'Main Road, Nottingham')
->press('Create') ->press('Create')
->waitForText('Warehouse created.', 5) ->waitForText('East Midlands DC', 10)
->assertSee('East Midlands DC'); ->assertSee('East Midlands DC');
}); });
$this->assertDatabaseHas('warehouses', [
'company_id' => $user->company_id,
'code' => 'EAST',
'name' => 'East Midlands DC',
]);
} }
public function test_validation_rule_form_covers_create_and_validation_paths(): void public function test_validation_rule_form_creates_rule(): void
{ {
$user = $this->tenantUser(); $user = $this->tenantUser();
$this->seedWarehouse(); $warehouse = $this->seedWarehouse($user->company);
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($user, $warehouse): void {
$browser->loginAs($user) $browser->loginAs($user)
->visit('/warehouse-rules') ->visit('/warehouse-rules');
->waitFor('.wms-page-hero')
->type('name', '') $this->assertSingleProfessionalHeader($browser, 'Validation Rules');
->type('rule_key', '') $browser->select('warehouse_id', (string) $warehouse->id)
->type('configuration', '{bad json')
->press('Add rule')
->waitForText('The name field is required', 5)
->type('name', 'Require serial number') ->type('name', 'Require serial number')
->type('rule_key', 'serial-required') ->type('rule_key', 'serial-required')
->select('severity', 'error') ->select('severity', 'error')
->type('configuration', '{"field":"serial_no"}') ->type('configuration', '{"field":"serial_no"}')
->press('Add rule') ->press('Add rule')
->waitForText('Validation rule created.', 5) ->waitForText('Require serial number', 10)
->assertSee('Require serial number')
->assertSee('serial-required'); ->assertSee('serial-required');
}); });
$this->assertDatabaseHas('warehouse_validation_rules', [
'warehouse_id' => $warehouse->id,
'name' => 'Require serial number',
'rule_key' => 'serial-required',
]);
} }
} }
+19 -29
View File
@@ -2,47 +2,37 @@
namespace Tests\Browser; namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser; use Laravel\Dusk\Browser;
use Modules\Platform\app\Models\PlatformPrice; use Tests\Browser\Concerns\InteractsWithWmsBrowser;
use Tests\Browser\Concerns\InteractsWithWmsData;
use Tests\DuskTestCase; use Tests\DuskTestCase;
class WmsPricingCrudBrowserTest extends DuskTestCase class WmsPricingCrudBrowserTest extends DuskTestCase
{ {
use DatabaseMigrations; use DatabaseMigrations;
/** use InteractsWithWmsBrowser;
* Ensure pricing cards remain visible on the Platform page and CRUD remains under Administration. use InteractsWithWmsData;
*/
public function test_platform_pricing_cards_and_admin_price_crud_are_reachable(): void public function test_platform_pricing_cards_and_admin_price_crud_are_reachable(): void
{ {
$admin = User::factory()->mpbaPlatformAdmin()->create(); $admin = $this->platformAdminUser();
PlatformPrice::create([ $this->seedPlatformPrices();
'code' => 'basic',
'name' => 'Basic',
'strapline' => 'Starter plan',
'description' => 'Entry WMS access.',
'monthly_amount' => 4900,
'annual_amount' => 49000,
'currency' => 'gbp',
'badge_label' => 'Starter',
'features' => ['2 warehouses', '10 users'],
'sort_order' => 10,
'is_featured' => false,
'is_active' => true,
]);
$this->browse(function (Browser $browser) use ($admin): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($admin) $browser->loginAs($admin)
->resize(1440, 1000) ->visit('/platform/pricing');
->visitRoute('platform.dashboard')
->assertSee('Pricing plans') $this->assertSingleProfessionalHeader($browser, 'Pricing');
->assertSee('Basic') $browser->assertSee('Basic')
->clickLink('Manage prices') ->assertSee('Advanced')
->assertRouteIs('platform.prices.index') ->assertSee('Pro')
->assertSee('Pricing Administration') ->visit('/admin/prices');
->clickLink('Add price')
->assertRouteIs('platform.prices.create') $this->assertSingleProfessionalHeader($browser, 'Platform Prices');
$browser->assertSee('Basic')
->assertSee('Advanced')
->assertSee('Pro')
->assertSee('Create Price'); ->assertSee('Create Price');
}); });
} }
+31 -44
View File
@@ -2,75 +2,62 @@
namespace Tests\Browser; namespace Tests\Browser;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations; use Illuminate\Foundation\Testing\DatabaseMigrations;
use Laravel\Dusk\Browser; use Laravel\Dusk\Browser;
use Tests\Browser\Concerns\InteractsWithWmsBrowser;
use Tests\Browser\Concerns\InteractsWithWmsData;
use Tests\DuskTestCase; use Tests\DuskTestCase;
class WmsResponsiveNavigationTest extends DuskTestCase class WmsResponsiveNavigationTest extends DuskTestCase
{ {
use DatabaseMigrations; use DatabaseMigrations;
/** use InteractsWithWmsBrowser;
* Ensure the desktop sidebar remains visible and usable on large screens. use InteractsWithWmsData;
*/
public function test_desktop_sidebar_navigation_remains_unchanged(): void public function test_desktop_sidebar_navigation_remains_unchanged(): void
{ {
$user = User::factory()->mpbaPlatformAdmin()->create(); $admin = $this->platformAdminUser();
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($user) $browser->loginAs($admin)
->resize(1440, 1000) ->resize(1440, 1000)
->visitRoute('platform.dashboard') ->visit('/admin')
->assertSee('Platform Dashboard') ->waitFor('.wms-sidebar', 10)
->assertSee('Platform')
->assertSee('Analytics') ->assertSee('Analytics')
->assertSee('Inventory') ->assertSee('Inventory');
->clickLink('Inventory')
->assertRouteIs('inventory-management.index') $this->clickSidebarHref($browser, '/inventory-management');
->assertSee('Inventory Management'); $browser->assertSee('Inventory');
}); });
} }
/**
* Ensure the mobile menu hamburger opens the slide-out navigation.
*/
public function test_mobile_menu_opens_slide_out_navigation(): void public function test_mobile_menu_opens_slide_out_navigation(): void
{ {
$user = User::factory()->mpbaPlatformAdmin()->create(); $admin = $this->platformAdminUser();
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($user) $browser->loginAs($admin)->visit('/admin');
->resize(390, 844) $this->openMobileNavigation($browser);
->visitRoute('platform.dashboard')
->assertSee('Platform Dashboard') $browser->assertPresent('[data-wms-sidebar]')
->assertPresent('[data-wms-mobile-open]') ->assertSee('Platform');
->click('[data-wms-mobile-open]')
->pause(150) $browser->script('document.querySelector(\'.wms-sidebar a[href="/shipping"]\')?.click();');
->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'true') $browser->waitForLocation('/shipping', 10)
->assertPresent('[data-wms-sidebar]')
->assertSee('Platform')
->clickLink('Shipping')
->assertRouteIs('shipping.index')
->assertSee('Shipping'); ->assertSee('Shipping');
}); });
} }
/**
* Ensure the mobile overlay closes the menu without affecting desktop navigation.
*/
public function test_mobile_menu_overlay_closes_drawer(): void public function test_mobile_menu_overlay_closes_drawer(): void
{ {
$user = User::factory()->mpbaPlatformAdmin()->create(); $admin = $this->platformAdminUser();
$this->browse(function (Browser $browser) use ($user): void { $this->browse(function (Browser $browser) use ($admin): void {
$browser->loginAs($user) $browser->loginAs($admin)->visit('/admin');
->resize(414, 896) $this->openMobileNavigation($browser);
->visitRoute('platform.dashboard') $browser->click('.wms-mobile-overlay')
->click('[data-wms-mobile-open]') ->waitUntil('document.documentElement.dataset.wmsMobileSidebar !== "open"', 10);
->pause(150)
->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'true')
->click('[data-wms-mobile-close]')
->pause(150)
->assertAttribute('[data-wms-mobile-open]', 'aria-expanded', 'false');
}); });
} }
} }