本文目录导读:

我来为您详细介绍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测试脚本的主要用法,您可以根据项目需求进行调整和扩展,记得定期更新测试以确保覆盖新功能。