深入Laracasts/Cypress源码:/__cypress__八大端点与Node任务桥接的实现原理
【免费下载链接】cypressLaravel Cypress Integration项目地址: https://gitcode.com/gh_mirrors/cyp/cypress
Laracasts/Cypress是一款 Laravel 与 Cypress 的集成包,让你在浏览器里用几行 JS 就能建数据、登录用户、执行 Artisan 命令,完成完整的 E2E 验收测试。深入源码可以发现,它的全部架构只靠两根支柱:一组由 Laravel 暴露的__cypress__测试专用端点,以及一套跑在本机 Node 进程里的任务桥接机制。看懂这两处,你就看懂了整个项目。
🧭 先搞懂:为什么必须搭"桥"
Cypress 的测试代码运行在浏览器里,而 Laravel 的 Model、Factory、Artisan 命令全部活在PHP 进程里。两个世界之间没有共享内存,唯一合法的沟通方式就是 HTTP。
所以这个包的设计思路极其直白,三层各司其职:
| 层 | 运行位置 | 职责 | 核心文件 |
|---|---|---|---|
| PHP 端 | Laravel 应用 | 提供 8 个__cypress__端点 | src/routes/cypress.php、src/Controllers/CypressController.php |
| JS 端 | 浏览器 | cy.login()等自定义命令,本质是发 HTTP 请求 | src/stubs/support/laravel-commands.js |
| Node 端 | Cypress 进程 | 浏览器做不到的本地操作(如换 env 文件) | src/stubs/plugins/swap-env.js、src/stubs/cypress.config.js |
一句话记住:命令在浏览器、逻辑在 PHP、文件在 Node。
🔌__cypress__八大端点速查表
8 条路由集中注册在src/routes/cypress.php中,全部由CypressController统一实现:
| 端点 | 方法 | 干什么 | 对应 JS 命令 |
|---|---|---|---|
/__cypress__/login | POST | 按条件查找用户,查不到就用 Factory 造一个并登录 | cy.login() |
/__cypress__/current-user | POST | 返回当前已登录用户 | cy.currentUser() |
/__cypress__/logout | POST | 登出当前用户 | cy.logout() |
/__cypress__/factory | POST | 按 Model + state + 数量批量创建 Factory 数据 | cy.create() |
/__cypress__/artisan | POST | 执行任意 Artisan 命令 | cy.artisan()、cy.seed()、cy.refreshDatabase() |
/__cypress__/run-php | POST | 直接 eval 任意 PHP 表达式并返回结果 | cy.php() |
/__cypress__/csrf_token | GET | 获取 CSRF Token(其余 POST 的前置步骤) | cy.csrfToken() |
/__cypress__/routes | POST | 导出全部命名路由 | cy.refreshRoutes() |
认证三件套:login / current-user / logout
login端点是设计最巧的一个:传入属性时先在数据库里where($attributes)查找;查不到再用User::factory()带 state 造一个并创建,最后调用auth()->login()完成登录。也就是说浏览器里一句cy.login({ username: 'JohnDoe' }),等价于在 PHP 侧执行了"查用户 → 造用户 → 登录"三连。
current-user和logout分别等价于auth()->user()和auth()->logout(),让浏览器侧能断言登录态。
数据与命令三驾马车:factory / artisan / run-php
- factory:支持
model + state + count + attributes + load五个参数,能复现 PHP 侧Model::factory(3)->guest()->create([...])->load('profile')的完整能力,这是 Cypress 测试里造测试数据的绝对主力。 - artisan:一个"命令通道",
cy.artisan('migrate:fresh')、cy.seed('PlansTableSeeder')其实都是它的包装。 - run-php:先自动给代码补上
return,然后直接eval()执行并返回结果。它是调试神器(比如随手App\User::count()),也是风险最高的端点——正因如此,它才有下面这套安全设计兜底。
路由端点与"命名路由访问"
routes端点会把 Laravel 全部命名路由映射成{ name, domain, uri, method }结构返回。JS 侧收到后既缓存在Cypress.Laravel.routes内存对象里,也落盘一份routes.json(见src/stubs/support/laravel-routes.js)。这就解锁了一个很 Laravel 的体验——cy.visit({ route: 'team.dashboard', parameters: { team: 1 } }),用路由名代替硬编码 URL。
一个容易被忽略的细节:CSRF Token
注意csrf_token是唯一一个GET端点。因为CypressServiceProvider注册路由时套了web中间件组,所有 POST 都会触发 CSRF 校验。所以 JS 侧每个自定义命令的模式都是:先cy.csrfToken()拿 token,再拼进请求体的_token字段。这套"先取令牌、再发请求"的链式结构,是laravel-commands.js里反复出现的固定套路。
🧊 Node 任务桥接:浏览器摸不到本地文件怎么办
这是整个项目最精妙的部分。
Cypress 测试运行在浏览器沙箱里,无法读写项目目录下的文件。而 Laravel 做 E2E 测试又有个经典诉求:测试期间要用独立的.env.cypress(比如指向专门的测试库),测试结束后还原。换文件这事,只能交给有文件系统权限的 Node 进程——Cypress 恰好提供了cy.task()这个浏览器 → Node 的官方通道。
整个桥接链只有三步:
- 注册任务:
src/stubs/plugins/index.js里一行on('task', require('./swap-env')),把两个本地任务挂到 Node 侧。 - 实现任务:
src/stubs/plugins/swap-env.js提供activateCypressEnvFile和activateLocalEnvFile,本质只是fs.renameSync——前者把.env备份后让.env.cypress顶上,后者反向还原。 - 编排时机:
src/stubs/support/index.js在before()里调用切换任务,after()里还原。
还有一个细节非常值得注意:切换 env 文件后,before()和after()都会紧跟一条cy.artisan('config:clear')。为什么?因为 Laravel 会缓存配置,env 换掉后必须清缓存,新数据库连接才会生效——这里正好复用了__cypress__/artisan端点,Node 桥和 PHP 端点在这里形成了闭环。
💡 另一种玩法:用
php artisan serve --env=cypress起服务,手动指定环境,就不再需要换文件,before/after里的任务可以删掉。
📦 一键脚手架:cypress:boilerplate 干了什么
这些文件从哪来?src/CypressBoilerplateCommand.php定义了cypress:boilerplate命令,一次执行完成:
- 检查
package.json里是否装了 cypress,没装会提示先npm install cypress --save-dev; - 把
src/stubs/下的support、plugins、integration三个目录拷进你的tests/cypress; - 生成
cypress.config.js:自动把APP_URL注入baseUrl,设置 spec 匹配规则,并通过setupNodeEvents把plugins/index.js接进 Node 侧——这正是上面"任务桥"的接线点; - 顺手创建
.env.cypress(先拷贝一份.env作为起点)。
🛡️ 三层安全设计:生产环境自动隐身
__cypress__/run-php这种能执行任意 PHP 的端点,暴露到生产环境就是灾难。源码里其实只有三道闸门:
- 环境闸门:
src/CypressServiceProvider.php的boot()开头直接判断——production环境直接return,8 条路由根本不会被注册; - 中间件闸门:所有端点都套在
web中间件组里; - CSRF 闸门:POST 端点全部要求携带 Token。
三道叠加,意味着这套"后门"只存在于本地开发环境,且无法被匿名跨站请求利用。
🧾 总结
回到源码,这个包其实讲了一个非常干净的故事:
- 7 个业务端点 + 1 个 Token 端点:
src/routes/cypress.php里 8 行路由,撑起浏览器侧cy.login()、cy.create()、cy.artisan()等全套 Laravel 风格命令; - Node 任务桥:
swap-env.js用两次文件重命名 + 两次清配置,解决"浏览器碰不到本地磁盘"的根本矛盾; - 一条命令落地:
cypress:boilerplate把 stubs、配置、env 文件一次铺好。
理解了这个"HTTP 端点 + 任务桥"的双通道模型,你以后面对任何"前端测试框架如何操作后端环境"的问题,都会有现成的思路了。
【免费下载链接】cypressLaravel Cypress Integration项目地址: https://gitcode.com/gh_mirrors/cyp/cypress
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考