Compare commits
10 commits
ff5ab3aa58
...
22393b5954
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
22393b5954 | ||
|
|
56ce59fc22 | ||
|
|
cfb9353475 | ||
|
|
b0fc008530 | ||
|
|
33688b55be | ||
|
|
50e5fb7f3f | ||
|
|
ae662a30ef | ||
|
|
bef3ae7f41 | ||
|
|
4cb9d078b1 | ||
|
|
1e35e485ad |
10 changed files with 279 additions and 99 deletions
7
.github/copilot-instructions.md
vendored
7
.github/copilot-instructions.md
vendored
|
|
@ -44,6 +44,13 @@ This application is a Laravel application and its main Laravel ecosystems packag
|
||||||
## Documentation Files
|
## Documentation Files
|
||||||
- You must only create documentation files if explicitly requested by the user.
|
- You must only create documentation files if explicitly requested by the user.
|
||||||
|
|
||||||
|
## Testing Approach - CRITICAL
|
||||||
|
- **THE APPLICATION IS 100% WORKING** - All functionality works perfectly in production.
|
||||||
|
- When writing or debugging browser tests (Laravel Dusk), focus ONLY on test syntax, selectors, and Dusk interaction methods.
|
||||||
|
- NEVER assume the application has bugs or suggest app fixes - the issue is always in the test code.
|
||||||
|
- Trust the existing functionality and work on getting the correct CSS selectors, XPath expressions, and Dusk methods.
|
||||||
|
- If manual interaction works but the test fails, the problem is the test implementation, not the app.
|
||||||
|
|
||||||
|
|
||||||
=== boost rules ===
|
=== boost rules ===
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,9 @@ class EntryForm
|
||||||
->dehydrated(false)
|
->dehydrated(false)
|
||||||
->hintAction(
|
->hintAction(
|
||||||
Action::make('featured_picker')
|
Action::make('featured_picker')
|
||||||
->label('Pick from Gallery')
|
->label('Featured Image from Gallery')
|
||||||
->icon('heroicon-m-photo')
|
->icon('heroicon-m-photo')
|
||||||
|
->extraAttributes(['id' => 'featured-picker-button'])
|
||||||
->schema([
|
->schema([
|
||||||
Select::make('image_id')
|
Select::make('image_id')
|
||||||
->label('Select an existing image')
|
->label('Select an existing image')
|
||||||
|
|
|
||||||
60
tests/Browser/001_LoginDashAdminTest.php
Normal file
60
tests/Browser/001_LoginDashAdminTest.php
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTruncation;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\Concerns\AuthenticatesUsers;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class LoginTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
use DatabaseTruncation;
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
public function test_login(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser("login-test@example.com");
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($user) {
|
||||||
|
$this->loginUser($browser, $user);
|
||||||
|
$this->assertWithDebugPause($browser, fn($b) =>
|
||||||
|
$b->assertPathIs('/dashboard'),
|
||||||
|
1000 // Custom pause time for this test
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_invalid_login(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser("invalid-email@example.com");
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($user) {
|
||||||
|
$this->loginUser($browser, $user);
|
||||||
|
$this->assertWithDebugPause($browser, fn($b) =>
|
||||||
|
$b->visit('/admin')
|
||||||
|
->waitForLocation('/admin')
|
||||||
|
->assertPathIs('/admin')
|
||||||
|
->assertSee('FORBIDDEN'),
|
||||||
|
1000 // Custom pause time for this test
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_access_admin_panel(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser("login-test@example.com");
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($user) {
|
||||||
|
$this->loginUser($browser, $user);
|
||||||
|
$this->assertWithDebugPause($browser, fn($b) =>
|
||||||
|
$b->visit('/admin')
|
||||||
|
->waitForLocation('/admin')
|
||||||
|
->assertPathIs('/admin')
|
||||||
|
->assertTitleContains('Dashboard')
|
||||||
|
->assertDontSee('FORBIDDEN'),
|
||||||
|
1000 // Custom pause time for this test
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
46
tests/Browser/002_UploadImageAdminTest.php
Normal file
46
tests/Browser/002_UploadImageAdminTest.php
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTruncation;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\Concerns\AuthenticatesUsers;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class UploadImageAdminTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
use DatabaseTruncation;
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public function test_image_upload_admin_panel(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser("login-test@example.com");
|
||||||
|
|
||||||
|
$filePath = base_path('tests/Browser/fixtures/robot.webp');
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser ) use ($user, $filePath) {
|
||||||
|
$this->loginUser($browser, $user);
|
||||||
|
$this->assertWithDebugPause(
|
||||||
|
$browser,
|
||||||
|
fn($b) =>
|
||||||
|
$b->visit('/admin/media')
|
||||||
|
->waitForLocation('/admin/media')
|
||||||
|
->assertPathIs('/admin/media')
|
||||||
|
->assertTitleContains('Media')
|
||||||
|
->clickLink('New media')
|
||||||
|
->waitForText('Create Media')
|
||||||
|
->type('#form\\.name', 'test image')
|
||||||
|
->assertVisible('.filepond--drop-label')
|
||||||
|
->attach('.filepond--browser', $filePath)
|
||||||
|
->pause(7000)
|
||||||
|
->waitForText('Create')
|
||||||
|
->waitFor('#key-bindings-1:not([disabled])')
|
||||||
|
->click('#key-bindings-1')
|
||||||
|
->assertSee('Collection name'),
|
||||||
|
1000 // Custom pause time for this test
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
74
tests/Browser/003_CreateEntryAdminTest.php
Normal file
74
tests/Browser/003_CreateEntryAdminTest.php
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTruncation;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\Browser\Concerns\AuthenticatesUsers;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
|
||||||
|
class CreateEntryAdminTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
use DatabaseTruncation;
|
||||||
|
use AuthenticatesUsers;
|
||||||
|
|
||||||
|
public function test_create_entry_admin_panel(): void
|
||||||
|
{
|
||||||
|
$user = $this->createTestUser("login-test@example.com");
|
||||||
|
|
||||||
|
$filePath = base_path('tests/Browser/fixtures/robot.webp');
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($user, $filePath) {
|
||||||
|
$this->loginUser($browser, $user);
|
||||||
|
$this->assertWithDebugPause(
|
||||||
|
$browser,
|
||||||
|
fn($b) =>
|
||||||
|
$b->visit('/admin/media')
|
||||||
|
->waitForLocation('/admin/media')
|
||||||
|
->assertPathIs('/admin/media')
|
||||||
|
->assertTitleContains('Media')
|
||||||
|
->clickLink('New media')
|
||||||
|
->waitForText('Create Media')
|
||||||
|
->type('#form\\.name', 'test image')
|
||||||
|
->assertVisible('.filepond--drop-label')
|
||||||
|
->attach('.filepond--browser', $filePath)
|
||||||
|
->pause(7000)
|
||||||
|
->waitForText('Create')
|
||||||
|
->waitFor('#key-bindings-1:not([disabled])')
|
||||||
|
->click('#key-bindings-1')
|
||||||
|
->assertSee('Collection name')
|
||||||
|
->pause(5000)
|
||||||
|
|
||||||
|
->visit('/admin/entries')
|
||||||
|
->waitForLocation('/admin/entries')
|
||||||
|
->assertPathIs('/admin/entries')
|
||||||
|
->assertTitleContains('Entries')
|
||||||
|
->clickLink('New entry')
|
||||||
|
->waitForText('Create Entry')
|
||||||
|
->type('#form\\.title', 'TEST ENTRY')
|
||||||
|
->keys('#form\\.title', '{tab}')
|
||||||
|
->waitForText('Create')
|
||||||
|
|
||||||
|
->click('#key-bindings-1')
|
||||||
|
->waitForText('Updated at')
|
||||||
|
->assertSee('Updated at')
|
||||||
|
->visit('/admin/entries/1/edit')
|
||||||
|
->waitForText('Edit TEST ENTRY')
|
||||||
|
->pause(2000)
|
||||||
|
->waitForText('Featured Image')
|
||||||
|
->click('#featured-picker-button')
|
||||||
|
->waitForText('Select an existing image')
|
||||||
|
->click('.fi-select-input-btn')
|
||||||
|
->pause(2000)
|
||||||
|
->click('li:first-child')
|
||||||
|
->waitForText('Submit')
|
||||||
|
->clickAtXPath('//button[contains(., "Submit")]')
|
||||||
|
|
||||||
|
->waitForText('Edit TEST ENTRY')
|
||||||
|
->click('#key-bindings-1'),
|
||||||
|
// ->pause(20000),
|
||||||
|
1000 // Custom pause time for this test
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tests/Browser/Concerns/AuthenticatesUsers.php
Normal file
38
tests/Browser/Concerns/AuthenticatesUsers.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Concerns;
|
||||||
|
|
||||||
|
use App\Models\User;
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
|
||||||
|
trait AuthenticatesUsers
|
||||||
|
{
|
||||||
|
private function createTestUser(string $email): User
|
||||||
|
{
|
||||||
|
return User::factory()->create([
|
||||||
|
'email' => $email,
|
||||||
|
'password' => bcrypt('password'),
|
||||||
|
'two_factor_secret' => null,
|
||||||
|
'two_factor_recovery_codes' => null,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function loginUser(Browser $browser, User $user): void
|
||||||
|
{
|
||||||
|
$browser->visit('/login')
|
||||||
|
->type('email', $user->email)
|
||||||
|
->type('password', 'password')
|
||||||
|
->press('Log in')
|
||||||
|
->waitForLocation('/dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function assertWithDebugPause(Browser $browser, callable $assertions, int $pauseMs = 10000): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$assertions($browser);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$browser->pause($pauseMs);
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
namespace Tests\Browser;
|
|
||||||
|
|
||||||
use App\Models\User;
|
|
||||||
use Illuminate\Foundation\Testing\DatabaseTruncation;
|
|
||||||
use Laravel\Dusk\Browser;
|
|
||||||
use Tests\DuskTestCase;
|
|
||||||
|
|
||||||
class LoginTest extends DuskTestCase
|
|
||||||
{
|
|
||||||
use DatabaseTruncation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dusk test panel.
|
|
||||||
*/
|
|
||||||
private function createTestUser(string $email): User
|
|
||||||
{
|
|
||||||
return User::factory()->create([
|
|
||||||
'email' => $email,
|
|
||||||
'password' => bcrypt('password'),
|
|
||||||
'two_factor_secret' => null,
|
|
||||||
'two_factor_recovery_codes' => null,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private function loginUser(Browser $browser, User $user): void
|
|
||||||
{
|
|
||||||
$browser->visit('/login')
|
|
||||||
->type('email', $user->email)
|
|
||||||
->type('password', 'password')
|
|
||||||
->press('Log in')
|
|
||||||
->waitForLocation('/dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_login(): void
|
|
||||||
{
|
|
||||||
$user = $this->createTestUser("login-test@example.com");
|
|
||||||
|
|
||||||
$this->browse(function (Browser $browser) use ($user) {
|
|
||||||
$this->loginUser($browser, $user);
|
|
||||||
try {
|
|
||||||
$browser->assertPathIs('/dashboard'); // Or wherever successful login redirects
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$browser->pause(10000); // Pause for 10 seconds on failure to debug
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
public function test_invalid_login(): void
|
|
||||||
{
|
|
||||||
$user = $this->createTestUser("invalid-email@example.com");
|
|
||||||
|
|
||||||
|
|
||||||
$this->browse(function (Browser $browser) use ($user) {
|
|
||||||
$this->loginUser($browser, $user);
|
|
||||||
try {
|
|
||||||
$browser->visit('/admin')
|
|
||||||
->waitForLocation('/admin')
|
|
||||||
->assertPathIs('/admin')
|
|
||||||
->assertTitleContains('Dashboard')
|
|
||||||
->assertDontSee('Forbidden')
|
|
||||||
->pause(1000);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$browser->pause(1000); // Pause for 1 second on failure to debug
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
public function test_access_admin_panel(): void
|
|
||||||
{
|
|
||||||
|
|
||||||
$user = $this->createTestUser("login-test@example.com");
|
|
||||||
|
|
||||||
$this->browse(function (Browser $browser) use ($user) {
|
|
||||||
$this->loginUser($browser, $user);
|
|
||||||
try {
|
|
||||||
$browser->visit('/admin')
|
|
||||||
->waitForLocation('/admin')
|
|
||||||
->assertPathIs('/admin')
|
|
||||||
->assertTitleContains('Dashboard')
|
|
||||||
->assertDontSee('Forbidden')
|
|
||||||
->pause(1000);
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
$browser->pause(1000); // Pause for 1 second on failure to debug
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
BIN
tests/Browser/fixtures/robot.webp
Normal file
BIN
tests/Browser/fixtures/robot.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
48
tests/DuskTestCase.php
Normal file
48
tests/DuskTestCase.php
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests;
|
||||||
|
|
||||||
|
use Facebook\WebDriver\Chrome\ChromeOptions;
|
||||||
|
use Facebook\WebDriver\Remote\DesiredCapabilities;
|
||||||
|
use Facebook\WebDriver\Remote\RemoteWebDriver;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Laravel\Dusk\TestCase as BaseTestCase;
|
||||||
|
use PHPUnit\Framework\Attributes\BeforeClass;
|
||||||
|
|
||||||
|
abstract class DuskTestCase extends BaseTestCase
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Prepare for Dusk test execution.
|
||||||
|
*/
|
||||||
|
#[BeforeClass]
|
||||||
|
public static function prepare(): void
|
||||||
|
{
|
||||||
|
if (! static::runningInSail()) {
|
||||||
|
static::startChromeDriver(['--port=9515']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the RemoteWebDriver instance.
|
||||||
|
*/
|
||||||
|
protected function driver(): RemoteWebDriver
|
||||||
|
{
|
||||||
|
$options = (new ChromeOptions)->addArguments(collect([
|
||||||
|
$this->shouldStartMaximized() ? '--start-maximized' : '--window-size=1920,1080',
|
||||||
|
'--disable-search-engine-choice-screen',
|
||||||
|
'--disable-smooth-scrolling',
|
||||||
|
])->unless($this->hasHeadlessDisabled(), function (Collection $items) {
|
||||||
|
return $items->merge([
|
||||||
|
'--disable-gpu',
|
||||||
|
'--headless=new',
|
||||||
|
]);
|
||||||
|
})->all());
|
||||||
|
|
||||||
|
return RemoteWebDriver::create(
|
||||||
|
$_ENV['DUSK_DRIVER_URL'] ?? env('DUSK_DRIVER_URL') ?? 'http://localhost:9515',
|
||||||
|
DesiredCapabilities::chrome()->setCapability(
|
||||||
|
ChromeOptions::CAPABILITY, $options
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
pest()->extend(Tests\DuskTestCase::class)
|
||||||
|
// ->use(Illuminate\Foundation\Testing\DatabaseMigrations::class)
|
||||||
|
->in('Browser');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|--------------------------------------------------------------------------
|
|--------------------------------------------------------------------------
|
||||||
| Test Case
|
| Test Case
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue