feature - 4 - Set up e2e with basic tests
This commit is contained in:
parent
94abe140e1
commit
3c32d49977
12 changed files with 233 additions and 2 deletions
24
.env.dusk.local
Normal file
24
.env.dusk.local
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
APP_NAME=DishPlanner
|
||||||
|
APP_ENV=testing
|
||||||
|
APP_KEY=base64:KSKZNT+cJuaBRBv4Y2HQqav6hzREKoLkNIKN8yszU1Q=
|
||||||
|
APP_DEBUG=true
|
||||||
|
APP_URL=http://dishplanner_app:8000
|
||||||
|
|
||||||
|
LOG_CHANNEL=single
|
||||||
|
|
||||||
|
# Test database
|
||||||
|
DB_CONNECTION=mysql
|
||||||
|
DB_HOST=db
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=dishplanner_test
|
||||||
|
DB_USERNAME=dishplanner
|
||||||
|
DB_PASSWORD=dishplanner
|
||||||
|
|
||||||
|
BROADCAST_DRIVER=log
|
||||||
|
CACHE_DRIVER=array
|
||||||
|
FILESYSTEM_DISK=local
|
||||||
|
QUEUE_CONNECTION=sync
|
||||||
|
SESSION_DRIVER=array
|
||||||
|
SESSION_LIFETIME=120
|
||||||
|
|
||||||
|
MAIL_MAILER=array
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"fakerphp/faker": "^1.23",
|
"fakerphp/faker": "^1.23",
|
||||||
|
"laravel/dusk": "^8.3",
|
||||||
"laravel/pail": "^1.1",
|
"laravel/pail": "^1.1",
|
||||||
"laravel/pint": "^1.13",
|
"laravel/pint": "^1.13",
|
||||||
"laravel/sail": "^1.26",
|
"laravel/sail": "^1.26",
|
||||||
|
|
|
||||||
|
|
@ -63,8 +63,8 @@ services:
|
||||||
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD:-root}"
|
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD:-root}"
|
||||||
volumes:
|
volumes:
|
||||||
- db_data:/var/lib/mysql
|
- db_data:/var/lib/mysql
|
||||||
# Optional: Initialize with SQL dump
|
# Initialize with SQL scripts
|
||||||
# - ./database/dumps:/docker-entrypoint-initdb.d
|
- ./docker/mysql-init:/docker-entrypoint-initdb.d
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
|
|
@ -84,6 +84,21 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- dishplanner
|
- dishplanner
|
||||||
|
|
||||||
|
# Selenium for E2E testing with Dusk
|
||||||
|
selenium:
|
||||||
|
image: selenium/standalone-chrome:latest
|
||||||
|
container_name: dishplanner_selenium
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "4444:4444" # Selenium server
|
||||||
|
- "7900:7900" # VNC server for debugging
|
||||||
|
volumes:
|
||||||
|
- /dev/shm:/dev/shm
|
||||||
|
networks:
|
||||||
|
- dishplanner
|
||||||
|
environment:
|
||||||
|
- SE_VNC_PASSWORD=secret
|
||||||
|
|
||||||
# Optional: Redis for caching/sessions
|
# Optional: Redis for caching/sessions
|
||||||
# redis:
|
# redis:
|
||||||
# image: redis:alpine
|
# image: redis:alpine
|
||||||
|
|
|
||||||
8
docker/mysql-init/01-create-test-database.sql
Normal file
8
docker/mysql-init/01-create-test-database.sql
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
-- Create test database for Dusk E2E tests
|
||||||
|
CREATE DATABASE IF NOT EXISTS dishplanner_test;
|
||||||
|
|
||||||
|
-- Grant all privileges on test database to the dishplanner user
|
||||||
|
GRANT ALL PRIVILEGES ON dishplanner_test.* TO 'dishplanner'@'%' IDENTIFIED BY 'dishplanner';
|
||||||
|
GRANT ALL PRIVILEGES ON dishplanner_test.* TO 'dishplanner'@'localhost' IDENTIFIED BY 'dishplanner';
|
||||||
|
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
36
tests/Browser/Pages/HomePage.php
Normal file
36
tests/Browser/Pages/HomePage.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Pages;
|
||||||
|
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
|
||||||
|
class HomePage extends Page
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the URL for the page.
|
||||||
|
*/
|
||||||
|
public function url(): string
|
||||||
|
{
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assert that the browser is on the page.
|
||||||
|
*/
|
||||||
|
public function assert(Browser $browser): void
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the element shortcuts for the page.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function elements(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'@element' => '#selector',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
20
tests/Browser/Pages/Page.php
Normal file
20
tests/Browser/Pages/Page.php
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser\Pages;
|
||||||
|
|
||||||
|
use Laravel\Dusk\Page as BasePage;
|
||||||
|
|
||||||
|
abstract class Page extends BasePage
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Get the global element shortcuts for the site.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function siteElements(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'@element' => '#selector',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
38
tests/Browser/RedirectTest.php
Normal file
38
tests/Browser/RedirectTest.php
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||||
|
|
||||||
|
class RedirectTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
use DatabaseTransactions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that unauthenticated users are redirected to login
|
||||||
|
*/
|
||||||
|
public function testUnauthenticatedRedirectsToLogin()
|
||||||
|
{
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser->visit('http://dishplanner_app:8000/dashboard')
|
||||||
|
->assertPathIs('/login')
|
||||||
|
->assertSee('Login');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that login page loads correctly
|
||||||
|
*/
|
||||||
|
public function testLoginPageLoads()
|
||||||
|
{
|
||||||
|
$this->browse(function (Browser $browser) {
|
||||||
|
$browser->visit('http://dishplanner_app:8000/login')
|
||||||
|
->assertPathIs('/login')
|
||||||
|
->assertSee('Login')
|
||||||
|
->assertSee('Email')
|
||||||
|
->assertSee('Password');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
36
tests/Browser/RegistrationOnlyTest.php
Normal file
36
tests/Browser/RegistrationOnlyTest.php
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Tests\Browser;
|
||||||
|
|
||||||
|
use Laravel\Dusk\Browser;
|
||||||
|
use Tests\DuskTestCase;
|
||||||
|
use App\Models\Planner;
|
||||||
|
|
||||||
|
class RegistrationOnlyTest extends DuskTestCase
|
||||||
|
{
|
||||||
|
public function testUserRegistration(): void
|
||||||
|
{
|
||||||
|
// Generate unique test data with timestamp to avoid conflicts
|
||||||
|
$timestamp = now()->format('YmdHis');
|
||||||
|
$testData = [
|
||||||
|
'name' => "Test User {$timestamp}",
|
||||||
|
'email' => "test.{$timestamp}@example.com",
|
||||||
|
'password' => 'SecurePassword123!',
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->browse(function (Browser $browser) use ($testData) {
|
||||||
|
$browser->visit('http://dishplanner_app:8000/register')
|
||||||
|
->waitFor('input[id="name"]', 5)
|
||||||
|
->type('input[id="name"]', $testData['name'])
|
||||||
|
->type('input[id="email"]', $testData['email'])
|
||||||
|
->type('input[id="password"]', $testData['password'])
|
||||||
|
->type('input[id="password_confirmation"]', $testData['password'])
|
||||||
|
->screenshot('filled-form')
|
||||||
|
->click('button[type="submit"]')
|
||||||
|
->pause(3000) // Give more time for processing
|
||||||
|
->screenshot('after-submit')
|
||||||
|
->assertSee("Welcome {$testData['name']}!") // Verify successful registration and login
|
||||||
|
->assertPathIs('/dashboard'); // Should be on dashboard
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
2
tests/Browser/console/.gitignore
vendored
Normal file
2
tests/Browser/console/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
tests/Browser/screenshots/.gitignore
vendored
Normal file
2
tests/Browser/screenshots/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
2
tests/Browser/source/.gitignore
vendored
Normal file
2
tests/Browser/source/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
*
|
||||||
|
!.gitignore
|
||||||
47
tests/DuskTestCase.php
Normal file
47
tests/DuskTestCase.php
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
<?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
|
||||||
|
{
|
||||||
|
// Don't start ChromeDriver - we're using Selenium
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the RemoteWebDriver instance.
|
||||||
|
*/
|
||||||
|
protected function driver(): RemoteWebDriver
|
||||||
|
{
|
||||||
|
$options = (new ChromeOptions)->addArguments([
|
||||||
|
'--window-size=1920,1080',
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-gpu',
|
||||||
|
'--headless=new',
|
||||||
|
'--disable-extensions',
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
|
'--disable-backgrounding-occluded-windows',
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
]);
|
||||||
|
|
||||||
|
return RemoteWebDriver::create(
|
||||||
|
'http://selenium:4444/wd/hub', // Connect to Selenium container
|
||||||
|
DesiredCapabilities::chrome()->setCapability(
|
||||||
|
ChromeOptions::CAPABILITY, $options
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue