PHP项目Laravel Dusk测试脚本编写

wen PHP项目 3

本文目录导读:

PHP项目Laravel Dusk测试脚本编写

  1. 环境准备与安装
  2. 基础测试类编写
  3. 页面交互测试
  4. 复杂交互测试
  5. 高级测试场景
  6. 测试执行与配置
  7. 调试技巧
  8. 最佳实践建议
  9. 扩展配置

我来为您详细介绍Laravel Dusk测试脚本的编写方法,包含从基础到进阶的完整示例。

环境准备与安装

# 安装Dusk
composer require laravel/dusk --dev
# 注册服务提供者(Laravel 5.8以下版本需要手动注册)
# 在 App\Providers\AppServiceProvider 中添加:
// use Laravel\Dusk\DuskServiceProvider;
// DuskServiceProvider::class
# 安装Dusk相关文件
php artisan dusk:install
# 修改 .env.dusk.local 或 .env.dusk.testing 数据库配置
# 推荐使用独立的测试数据库

基础测试类编写

1 创建测试类

# 生成测试类
php artisan dusk:make LoginTest
php artisan dusk:make UserManagementTest

2 基础登录测试

<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use App\Models\User;
use Illuminate\Foundation\Testing\DatabaseMigrations;
class LoginTest extends DuskTestCase
{
    use DatabaseMigrations;
    /**
     * 测试用户登录
     *
     * @return void
     */
    public function test_user_can_login()
    {
        // 创建测试用户
        $user = User::factory()->create([
            'email' => 'test@example.com',
            'password' => bcrypt('password123'),
        ]);
        $this->browse(function (Browser $browser) use ($user) {
            $browser->visit('/login')
                    ->type('email', $user->email)
                    ->type('password', 'password123')
                    ->press('Login')
                    ->assertPathIs('/dashboard')
                    ->assertSee('Welcome back!');
        });
    }
    /**
     * 测试登录失败
     *
     * @return void
     */
    public function test_user_cannot_login_with_invalid_credentials()
    {
        $this->browse(function (Browser $browser) {
            $browser->visit('/login')
                    ->type('email', 'invalid@example.com')
                    ->type('password', 'wrong-password')
                    ->press('Login')
                    ->assertPathIs('/login')
                    ->assertSee('These credentials do not match our records.');
        });
    }
}

页面交互测试

1 CRUD操作测试

<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use App\Models\Product;
use App\Models\User;
class ProductManagementTest extends DuskTestCase
{
    protected $user;
    protected function setUp(): void
    {
        parent::setUp();
        // 创建登录用户
        $this->user = User::factory()->create([
            'email' => 'admin@example.com',
            'password' => bcrypt('password123'),
        ]);
    }
    /**
     * 测试创建产品
     */
    public function test_admin_can_create_product()
    {
        $this->browse(function (Browser $browser) {
            $browser->loginAs($this->user)
                    ->visit('/admin/products')
                    ->click('#create-product')
                    ->waitForText('Create New Product')
                    ->type('name', 'Test Product')
                    ->type('description', 'This is a test product')
                    ->type('price', '99.99')
                    ->select('category_id', '1')
                    ->attach('image', __DIR__.'/files/test-image.jpg')
                    ->press('Submit')
                    ->waitForText('Product created successfully')
                    ->assertSee('Test Product')
                    ->assertSee('99.99');
        });
    }
    /**
     * 测试编辑产品
     */
    public function test_admin_can_edit_product()
    {
        $product = Product::factory()->create([
            'name' => 'Original Product',
            'price' => 50.00
        ]);
        $this->browse(function (Browser $browser) use ($product) {
            $browser->loginAs($this->user)
                    ->visit("/admin/products/{$product->id}/edit")
                    ->assertInputValue('name', 'Original Product')
                    ->type('name', 'Updated Product')
                    ->type('price', '75.00')
                    ->press('Update')
                    ->waitForText('Product updated successfully')
                    ->assertSee('Updated Product')
                    ->assertSee('75.00');
        });
    }
    /**
     * 测试删除产品
     */
    public function test_admin_can_delete_product()
    {
        $product = Product::factory()->create();
        $this->browse(function (Browser $browser) use ($product) {
            $browser->loginAs($this->user)
                    ->visit('/admin/products')
                    ->click("#delete-{$product->id}")
                    ->acceptDialog()
                    ->waitForText('Product deleted successfully')
                    ->assertDontSee($product->name);
        });
    }
}

复杂交互测试

1 模态框和弹出窗口处理

/**
 * 测试模态框交互
 */
public function test_modals_and_popups()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/settings')
                // 打开模态框
                ->click('.open-settings-modal')
                ->waitFor('.modal')
                // 模态框内容断言
                ->assertSeeIn('.modal', 'Settings')
                // 填写表单
                ->type('.modal input[name="site_name"]', 'My Site')
                ->select('.modal select[name="timezone"]', 'Asia/Shanghai')
                // 提交
                ->press('Save Settings')
                // 等待模态框关闭
                ->waitUntilMissing('.modal')
                // 断言成功消息
                ->assertSee('Settings saved successfully');
    });
}
/**
 * 测试iframe和嵌套页面
 */
public function test_iframe_handling()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/page-with-iframe')
                // 切换到iframe
                ->withinFrame('#payment-form', function ($frame) {
                    $frame->type('card_number', '4242424242424242')
                          ->type('expiry', '12/25')
                          ->type('cvc', '123')
                          ->press('Submit Payment');
                })
                // 切换回主框架
                ->assertSee('Payment Successful');
    });
}

2 文件上传测试

/**
 * 测试文件上传
 */
public function test_file_upload()
{
    // 创建测试文件
    $this->makeTestFiles();
    $this->browse(function (Browser $browser) {
        $browser->loginAs($this->user)
                ->visit('/admin/documents')
                ->attach('document', storage_path('app/test-files/sample.pdf'))
                ->attach('cover_image', storage_path('app/test-files/image.jpg'))
                ->press('Upload')
                ->waitForText('File uploaded successfully')
                ->assertSee('sample.pdf')
                ->assertSee('image.jpg');
    });
}
protected function makeTestFiles()
{
    $path = storage_path('app/test-files');
    // 创建PDF示例文件
    $pdf = "C:\xampp\htdocs\YourLaravelProject\tests\Browser\files\sample.pdf";
    copy($pdf, $path.'/sample.pdf');
    // 创建图片示例文件
    $image = "C:\xampp\htdocs\YourLaravelProject\tests\Browser\files\image.jpg";
    copy($image, $path.'/image.jpg');
}

高级测试场景

1 AJAX请求测试

/**
 * 测试AJAX请求和loading状态
 */
public function test_ajax_requests()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/reports')
                // 选择过滤器
                ->select('date_range', 'last_30_days')
                ->click('#load-report')
                // 等待loading state出现
                ->waitFor('.loading-spinner')
                // 等待AJAX完成
                ->waitForText('Report Results', 10)
                // 数据加载完成
                ->assertSee('Total Sales')
                ->assertSee('$10,000');
    });
}
/**
 * 测试无限滚动加载
 */
public function test_infinite_scroll()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/products')
                ->assertSee('Product 1');
        // 滚动到底部触发加载更多
        $browser->scrollTo('.list-bottom')
                ->pause(2000)
                ->assertSee('Product 11')
                ->scrollTo('.new-items')
                ->pause(2000)
                ->assertSee('Product 21');
    });
}

2 实现页面对象模型

<?php
namespace Tests\Browser\Pages;
use Laravel\Dusk\Browser;
use Laravel\Dusk\Page;
class LoginPage extends Page
{
    /**
     * 获取页面URL
     */
    public function url()
    {
        return '/login';
    }
    /**
     * 断言页面元素
     */
    public function assert(Browser $browser)
    {
        $browser->assertPathIs('/login')
                ->assertSee('Login')
                ->assertVisible('input[name="email"]')
                ->assertVisible('input[name="password"]');
    }
    /**
     * 获取页面元素选择器
     */
    public function elements()
    {
        return [
            '@email' => 'input[name="email"]',
            '@password' => 'input[name="password"]',
            '@submit' => 'button[type="submit"]',
        ];
    }
    /**
     * 登录操作
     */
    public function login(Browser $browser, $email, $password)
    {
        $browser->type('@email', $email)
                ->type('@password', $password)
                ->click('@submit');
    }
}

3 测试基类重写

<?php
namespace Tests\Browser;
use Laravel\Dusk\Browser;
use Tests\DuskTestCase;
use App\Models\User;
abstract class BaseBrowserTestCase extends DuskTestCase
{
    protected $user;
    protected function setUp(): void
    {
        parent::setUp();
        // 初始化公共测试数据
        $this->setupTestData();
    }
    protected function setupTestData()
    {
        // 创建基础测试数据
        $this->user = User::factory()->create([
            'email' => 'test@example.com',
            'password' => bcrypt('password123'),
            'role' => 'admin'
        ]);
    }
    /**
     * 登录用户
     */
    protected function loginAsAdmin(Browser $browser)
    {
        return $browser->loginAs($this->user)
                       ->visit('/dashboard')
                       ->waitForText('Welcome');
    }
    /**
     * 截图工具
     */
    protected function captureScreen(Browser $browser, $name)
    {
        $browser->screenshot("failures/{$name}");
    }
    /**
     * 保存失败测试的HTML
     */
    protected function saveHtmlOnFailure(Browser $browser, $name)
    {
        $html = $browser->driver->getPageSource();
        file_put_contents(storage_path("app/failures/{$name}.html"), $html);
    }
}

测试执行与配置

1 运行测试

# 运行所有测试
php artisan dusk
# 运行特定测试文件
php artisan dusk --filter=LoginTest
# 运行特定测试方法
php artisan dusk --filter=test_admin_can_create_product
# 指定环境运行
php artisan dusk --env=testing
# 配合并行测试
php artisan dusk --parallel

2 测试配置文件示例

// tests/DuskTestCase.php
abstract class DuskTestCase extends BaseTestCase
{
    use CreatesApplication;
    /**
     * 配置浏览器驱动
     */
    protected function driver()
    {
        $options = (new ChromeOptions)->addArguments([
            '--disable-gpu',
            '--headless',
            '--no-sandbox',
            '--disable-dev-shm-usage',
            '--window-size=1920,1080',
        ]);
        return RemoteWebDriver::create(
            'http://localhost:9515',
            DesiredCapabilities::chrome()->setCapability(
                ChromeOptions::CAPABILITY,
                $options
            )
        );
    }
    /**
     * 创建Dusk浏览器实例
     */
    protected function newBrowser($driver)
    {
        return (new Browser($driver))
            ->resize(1920, 1080)
            ->acceptDialog();
    }
    /**
     * 配置并发环境
     */
    protected function prepareDirectories()
    {
        if (! $this->runningInSail()) {
            $this->prepareChromeDriver();
        }
        foreach (static::$browsers as $browser) {
            $browser->driver->manage()->deleteAllCookies();
        }
    }
    /**
     * 启动Chrome驱动
     */
    protected function prepareChromeDriver()
    {
        $driver = base_path('vendor/laravel/dusk/bin/chromedriver');
        if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
            $driver .= '.exe';
        }
        // 检查是否已运行
        $ports = [9515];
        // 启动逻辑...
    }
}

调试技巧

/**
 * 调试测试脚本
 */
public function test_debugging_techniques()
{
    $this->browse(function (Browser $browser) {
        $browser->visit('/dashboard')
                // 暂停执行并在浏览器中查看
                ->pause(2000)
                // 调试:打印到控制台
                ->driver->manage()->getLog('browser')
                // 使用JavaScript调试
                ->script("console.log('Debug info');")
                // 临时截图调试
                ->screenshot('debug-screenshot')
                // 获取网页内容
                ->assertSee('Dashboard');
    });
}

最佳实践建议

class BestPracticesTest extends DuskTestCase
{
    /**
     * 使用数据提供器创建数据矩阵测试
     */
    public function test_admin_can_manage_users()
    {
        $this->browse(function ($browser) {
            $browser->loginAs($this->user)
                    ->visit('/admin/users');
            // 创建用户
            $browser->click('#add-user')
                    ->waitFor('.modal')
                    ->type('#name', 'John Doe')
                    ->type('#email', 'john@example.com')
                    ->select('#role', 'admin')
                    ->press('Save')
                    ->waitForText('User created');
            // 查询用户
            $browser->type('#search', 'John')
                    ->click('#search-btn')
                    ->pause(1000)
                    ->assertSee('john@example.com');
            // 编辑用户
            $browser->click('.edit-user')
                    ->waitFor('.modal')
                    ->type('#name', 'John Smith')
                    ->press('Save')
                    ->waitForText('User updated')
                    ->assertSee('John Smith');
        });
    }
    /**
     * 隔离测试数据
     */
    protected function setupTestData()
    {
        // 使用事务或清理测试数据
        DB::beginTransaction();
        parent::setupTestData();
    }
    protected function tearDown(): void
    {
        DB::rollBack();
        parent::tearDown();
    }
}

扩展配置

# 配置多个浏览器窗口
php artisan dusk:make MultipleBrowsersTest
# 生成页面对象
php artisan dusk:page LoginPage
# 生成组件
php artisan dusk:component Navbar

这些示例覆盖了Laravel Dusk测试脚本的主要用法,您可以根据项目需求进行调整和扩展,记得定期更新测试以确保覆盖新功能。

抱歉,评论功能暂时关闭!