本文目录导读:

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