code-abyss 1.6.16 → 1.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -6
  2. package/bin/install.js +59 -163
  3. package/bin/lib/ccline.js +82 -0
  4. package/bin/lib/utils.js +61 -0
  5. package/package.json +5 -2
  6. package/skills/SKILL.md +24 -16
  7. package/skills/domains/ai/SKILL.md +2 -2
  8. package/skills/domains/ai/prompt-and-eval.md +279 -0
  9. package/skills/domains/architecture/SKILL.md +2 -3
  10. package/skills/domains/architecture/security-arch.md +87 -0
  11. package/skills/domains/data-engineering/SKILL.md +188 -26
  12. package/skills/domains/development/SKILL.md +1 -4
  13. package/skills/domains/devops/SKILL.md +3 -5
  14. package/skills/domains/devops/performance.md +63 -0
  15. package/skills/domains/devops/testing.md +97 -0
  16. package/skills/domains/frontend-design/SKILL.md +12 -3
  17. package/skills/domains/frontend-design/claymorphism/SKILL.md +117 -0
  18. package/skills/domains/frontend-design/claymorphism/references/tokens.css +52 -0
  19. package/skills/domains/frontend-design/engineering.md +287 -0
  20. package/skills/domains/frontend-design/glassmorphism/SKILL.md +138 -0
  21. package/skills/domains/frontend-design/glassmorphism/references/tokens.css +32 -0
  22. package/skills/domains/frontend-design/liquid-glass/SKILL.md +135 -0
  23. package/skills/domains/frontend-design/liquid-glass/references/tokens.css +81 -0
  24. package/skills/domains/frontend-design/neubrutalism/SKILL.md +141 -0
  25. package/skills/domains/frontend-design/neubrutalism/references/tokens.css +44 -0
  26. package/skills/domains/infrastructure/SKILL.md +174 -34
  27. package/skills/domains/mobile/SKILL.md +211 -21
  28. package/skills/domains/orchestration/SKILL.md +1 -0
  29. package/skills/domains/security/SKILL.md +4 -6
  30. package/skills/domains/security/blue-team.md +57 -0
  31. package/skills/domains/security/red-team.md +54 -0
  32. package/skills/domains/security/threat-intel.md +50 -0
  33. package/skills/orchestration/multi-agent/SKILL.md +195 -46
  34. package/skills/run_skill.js +139 -0
  35. package/skills/tools/gen-docs/SKILL.md +6 -4
  36. package/skills/tools/gen-docs/scripts/doc_generator.js +363 -0
  37. package/skills/tools/lib/shared.js +98 -0
  38. package/skills/tools/verify-change/SKILL.md +8 -6
  39. package/skills/tools/verify-change/scripts/change_analyzer.js +289 -0
  40. package/skills/tools/verify-module/SKILL.md +6 -4
  41. package/skills/tools/verify-module/scripts/module_scanner.js +171 -0
  42. package/skills/tools/verify-quality/SKILL.md +5 -3
  43. package/skills/tools/verify-quality/scripts/quality_checker.js +337 -0
  44. package/skills/tools/verify-security/SKILL.md +7 -5
  45. package/skills/tools/verify-security/scripts/security_scanner.js +283 -0
  46. package/skills/__pycache__/run_skill.cpython-312.pyc +0 -0
  47. package/skills/domains/COVERAGE_PLAN.md +0 -232
  48. package/skills/domains/ai/model-evaluation.md +0 -790
  49. package/skills/domains/ai/prompt-engineering.md +0 -703
  50. package/skills/domains/architecture/compliance.md +0 -299
  51. package/skills/domains/architecture/data-security.md +0 -184
  52. package/skills/domains/data-engineering/data-pipeline.md +0 -762
  53. package/skills/domains/data-engineering/data-quality.md +0 -894
  54. package/skills/domains/data-engineering/stream-processing.md +0 -791
  55. package/skills/domains/development/dart.md +0 -963
  56. package/skills/domains/development/kotlin.md +0 -834
  57. package/skills/domains/development/php.md +0 -659
  58. package/skills/domains/development/swift.md +0 -755
  59. package/skills/domains/devops/e2e-testing.md +0 -914
  60. package/skills/domains/devops/performance-testing.md +0 -734
  61. package/skills/domains/devops/testing-strategy.md +0 -667
  62. package/skills/domains/frontend-design/build-tools.md +0 -743
  63. package/skills/domains/frontend-design/performance.md +0 -734
  64. package/skills/domains/frontend-design/testing.md +0 -699
  65. package/skills/domains/infrastructure/gitops.md +0 -735
  66. package/skills/domains/infrastructure/iac.md +0 -855
  67. package/skills/domains/infrastructure/kubernetes.md +0 -1018
  68. package/skills/domains/mobile/android-dev.md +0 -979
  69. package/skills/domains/mobile/cross-platform.md +0 -795
  70. package/skills/domains/mobile/ios-dev.md +0 -931
  71. package/skills/domains/security/secrets-management.md +0 -834
  72. package/skills/domains/security/supply-chain.md +0 -931
  73. package/skills/domains/security/threat-modeling.md +0 -828
  74. package/skills/run_skill.py +0 -153
  75. package/skills/tests/README.md +0 -225
  76. package/skills/tests/SUMMARY.md +0 -362
  77. package/skills/tests/__init__.py +0 -3
  78. package/skills/tests/__pycache__/test_change_analyzer.cpython-312.pyc +0 -0
  79. package/skills/tests/__pycache__/test_doc_generator.cpython-312.pyc +0 -0
  80. package/skills/tests/__pycache__/test_module_scanner.cpython-312.pyc +0 -0
  81. package/skills/tests/__pycache__/test_quality_checker.cpython-312.pyc +0 -0
  82. package/skills/tests/__pycache__/test_security_scanner.cpython-312.pyc +0 -0
  83. package/skills/tests/test_change_analyzer.py +0 -558
  84. package/skills/tests/test_doc_generator.py +0 -538
  85. package/skills/tests/test_module_scanner.py +0 -376
  86. package/skills/tests/test_quality_checker.py +0 -516
  87. package/skills/tests/test_security_scanner.py +0 -426
  88. package/skills/tools/gen-docs/scripts/__pycache__/doc_generator.cpython-312.pyc +0 -0
  89. package/skills/tools/gen-docs/scripts/doc_generator.py +0 -520
  90. package/skills/tools/verify-change/scripts/__pycache__/change_analyzer.cpython-312.pyc +0 -0
  91. package/skills/tools/verify-change/scripts/change_analyzer.py +0 -529
  92. package/skills/tools/verify-module/scripts/__pycache__/module_scanner.cpython-312.pyc +0 -0
  93. package/skills/tools/verify-module/scripts/module_scanner.py +0 -321
  94. package/skills/tools/verify-quality/scripts/__pycache__/quality_checker.cpython-312.pyc +0 -0
  95. package/skills/tools/verify-quality/scripts/quality_checker.py +0 -481
  96. package/skills/tools/verify-security/scripts/__pycache__/security_scanner.cpython-312.pyc +0 -0
  97. package/skills/tools/verify-security/scripts/security_scanner.py +0 -374
@@ -1,914 +0,0 @@
1
- ---
2
- name: e2e-testing
3
- description: 端到端测试。Playwright、Cypress、Selenium、页面对象模式、可视化回归测试、跨浏览器测试。当用户提到E2E测试、Playwright、Cypress、端到端测试、可视化回归、UI测试时使用。
4
- ---
5
-
6
- # 🎭 端到端测试 · E2E Testing
7
-
8
- ## Playwright vs Cypress
9
-
10
- | 特性 | Playwright | Cypress |
11
- |------|-----------|---------|
12
- | 浏览器支持 | Chromium/Firefox/WebKit | Chromium/Firefox/Edge |
13
- | 多标签页 | ✅ 原生支持 | ❌ 不支持 |
14
- | iframe | ✅ 完整支持 | ⚠️ 有限支持 |
15
- | 文件上传/下载 | ✅ 原生支持 | ⚠️ 需插件 |
16
- | 网络拦截 | ✅ 强大 | ✅ 强大 |
17
- | 并行执行 | ✅ 原生支持 | ⚠️ 需付费 |
18
- | 调试体验 | ⚠️ 一般 | ✅ 优秀 |
19
- | 学习曲线 | 平缓 | 平缓 |
20
- | 执行速度 | 快 | 快 |
21
-
22
- ## Playwright 基础
23
-
24
- ### 安装与配置
25
- ```bash
26
- npm init playwright@latest
27
-
28
- # 安装浏览器
29
- npx playwright install
30
-
31
- # 运行测试
32
- npx playwright test
33
- npx playwright test --headed # 显示浏览器
34
- npx playwright test --debug # 调试模式
35
- ```
36
-
37
- ```typescript
38
- // playwright.config.ts
39
- import { defineConfig, devices } from '@playwright/test';
40
-
41
- export default defineConfig({
42
- testDir: './tests',
43
- timeout: 30000,
44
- retries: process.env.CI ? 2 : 0,
45
- workers: process.env.CI ? 1 : undefined,
46
-
47
- use: {
48
- baseURL: 'http://localhost:3000',
49
- trace: 'on-first-retry',
50
- screenshot: 'only-on-failure',
51
- video: 'retain-on-failure',
52
- },
53
-
54
- projects: [
55
- { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
56
- { name: 'firefox', use: { ...devices['Desktop Firefox'] } },
57
- { name: 'webkit', use: { ...devices['Desktop Safari'] } },
58
- { name: 'mobile', use: { ...devices['iPhone 13'] } },
59
- ],
60
-
61
- webServer: {
62
- command: 'npm run start',
63
- port: 3000,
64
- reuseExistingServer: !process.env.CI,
65
- },
66
- });
67
- ```
68
-
69
- ### 基础测试
70
- ```typescript
71
- import { test, expect } from '@playwright/test';
72
-
73
- test('用户登录流程', async ({ page }) => {
74
- // 导航
75
- await page.goto('/login');
76
-
77
- // 填写表单
78
- await page.fill('input[name="username"]', 'alice');
79
- await page.fill('input[name="password"]', 'password123');
80
-
81
- // 点击按钮
82
- await page.click('button[type="submit"]');
83
-
84
- // 等待导航
85
- await page.waitForURL('/dashboard');
86
-
87
- // 断言
88
- await expect(page.locator('h1')).toHaveText('欢迎, Alice');
89
- });
90
-
91
- test('搜索功能', async ({ page }) => {
92
- await page.goto('/');
93
-
94
- // 输入搜索
95
- await page.fill('[data-testid="search-input"]', 'Playwright');
96
- await page.press('[data-testid="search-input"]', 'Enter');
97
-
98
- // 等待结果
99
- await page.waitForSelector('.search-results');
100
-
101
- // 断言结果数量
102
- const results = page.locator('.search-result-item');
103
- await expect(results).toHaveCount(10);
104
- });
105
- ```
106
-
107
- ### 高级选择器
108
- ```typescript
109
- // CSS 选择器
110
- await page.click('button.submit');
111
-
112
- // 文本选择器
113
- await page.click('text=登录');
114
- await page.click('button:has-text("提交")');
115
-
116
- // XPath
117
- await page.click('xpath=//button[@type="submit"]');
118
-
119
- // 组合选择器
120
- await page.click('form >> button:has-text("登录")');
121
-
122
- // data-testid (推荐)
123
- await page.click('[data-testid="login-button"]');
124
-
125
- // 角色选择器
126
- await page.click('role=button[name="登录"]');
127
-
128
- // 链式定位
129
- await page
130
- .locator('.user-card')
131
- .filter({ hasText: 'Alice' })
132
- .locator('button')
133
- .click();
134
- ```
135
-
136
- ### 等待策略
137
- ```typescript
138
- // 等待元素可见
139
- await page.waitForSelector('.modal', { state: 'visible' });
140
-
141
- // 等待元素消失
142
- await page.waitForSelector('.loading', { state: 'hidden' });
143
-
144
- // 等待网络请求
145
- await page.waitForResponse(resp =>
146
- resp.url().includes('/api/users') && resp.status() === 200
147
- );
148
-
149
- // 等待函数返回 true
150
- await page.waitForFunction(() =>
151
- document.querySelectorAll('.item').length > 5
152
- );
153
-
154
- // 自动等待 (推荐)
155
- await page.click('button'); // 自动等待可点击
156
- await expect(page.locator('h1')).toBeVisible(); // 自动等待可见
157
- ```
158
-
159
- ## Cypress 基础
160
-
161
- ### 安装与配置
162
- ```bash
163
- npm install cypress --save-dev
164
-
165
- # 打开 Cypress
166
- npx cypress open
167
-
168
- # 运行测试
169
- npx cypress run
170
- ```
171
-
172
- ```javascript
173
- // cypress.config.js
174
- const { defineConfig } = require('cypress');
175
-
176
- module.exports = defineConfig({
177
- e2e: {
178
- baseUrl: 'http://localhost:3000',
179
- viewportWidth: 1280,
180
- viewportHeight: 720,
181
- video: true,
182
- screenshotOnRunFailure: true,
183
- retries: {
184
- runMode: 2,
185
- openMode: 0,
186
- },
187
- },
188
- });
189
- ```
190
-
191
- ### 基础测试
192
- ```javascript
193
- describe('用户登录', () => {
194
- beforeEach(() => {
195
- cy.visit('/login');
196
- });
197
-
198
- it('成功登录', () => {
199
- cy.get('[data-cy="username"]').type('alice');
200
- cy.get('[data-cy="password"]').type('password123');
201
- cy.get('[data-cy="submit"]').click();
202
-
203
- cy.url().should('include', '/dashboard');
204
- cy.contains('欢迎, Alice').should('be.visible');
205
- });
206
-
207
- it('密码错误', () => {
208
- cy.get('[data-cy="username"]').type('alice');
209
- cy.get('[data-cy="password"]').type('wrongpassword');
210
- cy.get('[data-cy="submit"]').click();
211
-
212
- cy.contains('用户名或密码错误').should('be.visible');
213
- });
214
- });
215
-
216
- describe('购物车', () => {
217
- it('添加商品到购物车', () => {
218
- cy.visit('/products');
219
-
220
- // 添加商品
221
- cy.get('[data-cy="product-1"]').within(() => {
222
- cy.contains('加入购物车').click();
223
- });
224
-
225
- // 验证购物车
226
- cy.get('[data-cy="cart-count"]').should('have.text', '1');
227
-
228
- // 打开购物车
229
- cy.get('[data-cy="cart-icon"]').click();
230
- cy.get('[data-cy="cart-items"]').should('have.length', 1);
231
- });
232
- });
233
- ```
234
-
235
- ### Cypress 命令
236
- ```javascript
237
- // 导航
238
- cy.visit('/page');
239
- cy.go('back');
240
- cy.reload();
241
-
242
- // 查找元素
243
- cy.get('.class');
244
- cy.contains('text');
245
- cy.find('.child');
246
-
247
- // 交互
248
- cy.click();
249
- cy.type('text');
250
- cy.clear();
251
- cy.check();
252
- cy.select('option');
253
-
254
- // 断言
255
- cy.should('be.visible');
256
- cy.should('have.text', 'Hello');
257
- cy.should('have.class', 'active');
258
- cy.should('have.length', 3);
259
-
260
- // 别名
261
- cy.get('.user').as('user');
262
- cy.get('@user').click();
263
- ```
264
-
265
- ## 页面对象模式 (Page Object Model)
266
-
267
- ### Playwright POM
268
- ```typescript
269
- // pages/LoginPage.ts
270
- export class LoginPage {
271
- constructor(private page: Page) {}
272
-
273
- // 定位器
274
- get usernameInput() {
275
- return this.page.locator('[data-testid="username"]');
276
- }
277
-
278
- get passwordInput() {
279
- return this.page.locator('[data-testid="password"]');
280
- }
281
-
282
- get submitButton() {
283
- return this.page.locator('button[type="submit"]');
284
- }
285
-
286
- get errorMessage() {
287
- return this.page.locator('.error-message');
288
- }
289
-
290
- // 操作方法
291
- async goto() {
292
- await this.page.goto('/login');
293
- }
294
-
295
- async login(username: string, password: string) {
296
- await this.usernameInput.fill(username);
297
- await this.passwordInput.fill(password);
298
- await this.submitButton.click();
299
- }
300
-
301
- async getErrorText() {
302
- return await this.errorMessage.textContent();
303
- }
304
- }
305
-
306
- // tests/login.spec.ts
307
- import { test, expect } from '@playwright/test';
308
- import { LoginPage } from '../pages/LoginPage';
309
-
310
- test('用户登录', async ({ page }) => {
311
- const loginPage = new LoginPage(page);
312
-
313
- await loginPage.goto();
314
- await loginPage.login('alice', 'password123');
315
-
316
- await expect(page).toHaveURL('/dashboard');
317
- });
318
- ```
319
-
320
- ### Cypress POM
321
- ```javascript
322
- // cypress/pages/LoginPage.js
323
- export class LoginPage {
324
- visit() {
325
- cy.visit('/login');
326
- }
327
-
328
- fillUsername(username) {
329
- cy.get('[data-cy="username"]').type(username);
330
- return this;
331
- }
332
-
333
- fillPassword(password) {
334
- cy.get('[data-cy="password"]').type(password);
335
- return this;
336
- }
337
-
338
- submit() {
339
- cy.get('[data-cy="submit"]').click();
340
- return this;
341
- }
342
-
343
- getErrorMessage() {
344
- return cy.get('.error-message');
345
- }
346
- }
347
-
348
- // cypress/e2e/login.cy.js
349
- import { LoginPage } from '../pages/LoginPage';
350
-
351
- describe('登录测试', () => {
352
- const loginPage = new LoginPage();
353
-
354
- it('成功登录', () => {
355
- loginPage
356
- .visit()
357
- .fillUsername('alice')
358
- .fillPassword('password123')
359
- .submit();
360
-
361
- cy.url().should('include', '/dashboard');
362
- });
363
- });
364
- ```
365
-
366
- ## 网络拦截与 Mock
367
-
368
- ### Playwright 网络拦截
369
- ```typescript
370
- test('Mock API 响应', async ({ page }) => {
371
- // 拦截并 Mock
372
- await page.route('**/api/users', route => {
373
- route.fulfill({
374
- status: 200,
375
- contentType: 'application/json',
376
- body: JSON.stringify([
377
- { id: 1, name: 'Alice' },
378
- { id: 2, name: 'Bob' }
379
- ])
380
- });
381
- });
382
-
383
- await page.goto('/users');
384
- await expect(page.locator('.user-item')).toHaveCount(2);
385
- });
386
-
387
- test('修改请求', async ({ page }) => {
388
- await page.route('**/api/login', route => {
389
- const request = route.request();
390
- route.continue({
391
- headers: {
392
- ...request.headers(),
393
- 'X-Custom-Header': 'test'
394
- }
395
- });
396
- });
397
-
398
- await page.goto('/login');
399
- });
400
-
401
- test('等待 API 响应', async ({ page }) => {
402
- const responsePromise = page.waitForResponse('**/api/users');
403
-
404
- await page.goto('/users');
405
-
406
- const response = await responsePromise;
407
- expect(response.status()).toBe(200);
408
-
409
- const data = await response.json();
410
- expect(data).toHaveLength(10);
411
- });
412
- ```
413
-
414
- ### Cypress 网络拦截
415
- ```javascript
416
- describe('API Mock', () => {
417
- it('拦截并 Mock', () => {
418
- cy.intercept('GET', '/api/users', {
419
- statusCode: 200,
420
- body: [
421
- { id: 1, name: 'Alice' },
422
- { id: 2, name: 'Bob' }
423
- ]
424
- }).as('getUsers');
425
-
426
- cy.visit('/users');
427
- cy.wait('@getUsers');
428
-
429
- cy.get('.user-item').should('have.length', 2);
430
- });
431
-
432
- it('使用 Fixture', () => {
433
- cy.intercept('GET', '/api/users', { fixture: 'users.json' });
434
- cy.visit('/users');
435
- });
436
-
437
- it('动态响应', () => {
438
- cy.intercept('POST', '/api/users', (req) => {
439
- req.reply({
440
- statusCode: 201,
441
- body: {
442
- id: 999,
443
- ...req.body
444
- }
445
- });
446
- });
447
-
448
- cy.visit('/users/new');
449
- cy.get('[data-cy="name"]').type('Charlie');
450
- cy.get('[data-cy="submit"]').click();
451
- });
452
- });
453
- ```
454
-
455
- ## 可视化回归测试
456
-
457
- ### Playwright 截图对比
458
- ```typescript
459
- test('页面截图对比', async ({ page }) => {
460
- await page.goto('/');
461
-
462
- // 全页面截图
463
- await expect(page).toHaveScreenshot('homepage.png');
464
-
465
- // 元素截图
466
- await expect(page.locator('.header')).toHaveScreenshot('header.png');
467
-
468
- // 自定义配置
469
- await expect(page).toHaveScreenshot('homepage-full.png', {
470
- fullPage: true,
471
- mask: [page.locator('.dynamic-content')], // 遮罩动态内容
472
- });
473
- });
474
-
475
- test('跨浏览器截图', async ({ page, browserName }) => {
476
- await page.goto('/');
477
- await expect(page).toHaveScreenshot(`homepage-${browserName}.png`);
478
- });
479
- ```
480
-
481
- ### Percy 集成
482
- ```typescript
483
- // Playwright + Percy
484
- import { test } from '@playwright/test';
485
- import percySnapshot from '@percy/playwright';
486
-
487
- test('Percy 可视化测试', async ({ page }) => {
488
- await page.goto('/');
489
-
490
- // 拍摄快照
491
- await percySnapshot(page, 'Homepage');
492
-
493
- // 交互后再拍摄
494
- await page.click('[data-testid="menu"]');
495
- await percySnapshot(page, 'Homepage - Menu Open');
496
- });
497
- ```
498
-
499
- ```javascript
500
- // Cypress + Percy
501
- describe('可视化回归', () => {
502
- it('首页快照', () => {
503
- cy.visit('/');
504
- cy.percySnapshot('Homepage');
505
- });
506
-
507
- it('响应式快照', () => {
508
- cy.visit('/');
509
- cy.percySnapshot('Homepage Desktop', {
510
- widths: [1280, 1920]
511
- });
512
-
513
- cy.viewport('iphone-x');
514
- cy.percySnapshot('Homepage Mobile');
515
- });
516
- });
517
- ```
518
-
519
- ### Chromatic (Storybook)
520
- ```javascript
521
- // .storybook/main.js
522
- module.exports = {
523
- stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
524
- addons: ['@storybook/addon-essentials'],
525
- };
526
-
527
- // Button.stories.tsx
528
- export default {
529
- title: 'Components/Button',
530
- component: Button,
531
- };
532
-
533
- export const Primary = () => <Button variant="primary">Click me</Button>;
534
- export const Secondary = () => <Button variant="secondary">Click me</Button>;
535
-
536
- // package.json
537
- {
538
- "scripts": {
539
- "chromatic": "chromatic --project-token=<token>"
540
- }
541
- }
542
- ```
543
-
544
- ## 跨浏览器测试
545
-
546
- ### Playwright 多浏览器
547
- ```typescript
548
- // playwright.config.ts
549
- export default defineConfig({
550
- projects: [
551
- {
552
- name: 'chromium',
553
- use: { ...devices['Desktop Chrome'] },
554
- },
555
- {
556
- name: 'firefox',
557
- use: { ...devices['Desktop Firefox'] },
558
- },
559
- {
560
- name: 'webkit',
561
- use: { ...devices['Desktop Safari'] },
562
- },
563
- {
564
- name: 'mobile-chrome',
565
- use: { ...devices['Pixel 5'] },
566
- },
567
- {
568
- name: 'mobile-safari',
569
- use: { ...devices['iPhone 13'] },
570
- },
571
- ],
572
- });
573
-
574
- // 运行特定浏览器
575
- // npx playwright test --project=firefox
576
- ```
577
-
578
- ### 设备模拟
579
- ```typescript
580
- test('移动端测试', async ({ page }) => {
581
- await page.goto('/');
582
-
583
- // 验证移动端布局
584
- const menu = page.locator('.mobile-menu');
585
- await expect(menu).toBeVisible();
586
-
587
- // 触摸操作
588
- await page.locator('.swipeable').swipe({ direction: 'left' });
589
- });
590
-
591
- test('平板测试', async ({ browser }) => {
592
- const context = await browser.newContext({
593
- ...devices['iPad Pro'],
594
- });
595
-
596
- const page = await context.newPage();
597
- await page.goto('/');
598
- });
599
- ```
600
-
601
- ## 文件上传与下载
602
-
603
- ### Playwright 文件操作
604
- ```typescript
605
- test('文件上传', async ({ page }) => {
606
- await page.goto('/upload');
607
-
608
- // 单文件上传
609
- await page.setInputFiles('input[type="file"]', 'path/to/file.pdf');
610
-
611
- // 多文件上传
612
- await page.setInputFiles('input[type="file"]', [
613
- 'file1.jpg',
614
- 'file2.jpg'
615
- ]);
616
-
617
- // 从 Buffer 上传
618
- await page.setInputFiles('input[type="file"]', {
619
- name: 'test.txt',
620
- mimeType: 'text/plain',
621
- buffer: Buffer.from('file content')
622
- });
623
- });
624
-
625
- test('文件下载', async ({ page }) => {
626
- const downloadPromise = page.waitForEvent('download');
627
-
628
- await page.click('a[download]');
629
-
630
- const download = await downloadPromise;
631
- const path = await download.path();
632
-
633
- // 验证文件
634
- expect(download.suggestedFilename()).toBe('report.pdf');
635
- });
636
- ```
637
-
638
- ### Cypress 文件操作
639
- ```javascript
640
- describe('文件操作', () => {
641
- it('文件上传', () => {
642
- cy.visit('/upload');
643
-
644
- // 需要 cypress-file-upload 插件
645
- cy.get('input[type="file"]').attachFile('example.json');
646
-
647
- cy.contains('上传成功').should('be.visible');
648
- });
649
-
650
- it('文件下载', () => {
651
- cy.visit('/download');
652
-
653
- cy.get('a[download]').click();
654
-
655
- // 验证下载
656
- cy.readFile('cypress/downloads/report.pdf').should('exist');
657
- });
658
- });
659
- ```
660
-
661
- ## 认证与状态管理
662
-
663
- ### Playwright 认证
664
- ```typescript
665
- // global-setup.ts
666
- async function globalSetup() {
667
- const browser = await chromium.launch();
668
- const page = await browser.newPage();
669
-
670
- await page.goto('/login');
671
- await page.fill('[name="username"]', 'alice');
672
- await page.fill('[name="password"]', 'password123');
673
- await page.click('button[type="submit"]');
674
-
675
- // 保存认证状态
676
- await page.context().storageState({ path: 'auth.json' });
677
- await browser.close();
678
- }
679
-
680
- // playwright.config.ts
681
- export default defineConfig({
682
- globalSetup: require.resolve('./global-setup'),
683
- use: {
684
- storageState: 'auth.json',
685
- },
686
- });
687
-
688
- // 测试自动使用已登录状态
689
- test('访问受保护页面', async ({ page }) => {
690
- await page.goto('/dashboard'); // 已登录
691
- });
692
- ```
693
-
694
- ### Cypress Session
695
- ```javascript
696
- // cypress/support/commands.js
697
- Cypress.Commands.add('login', (username, password) => {
698
- cy.session([username, password], () => {
699
- cy.visit('/login');
700
- cy.get('[data-cy="username"]').type(username);
701
- cy.get('[data-cy="password"]').type(password);
702
- cy.get('[data-cy="submit"]').click();
703
- cy.url().should('include', '/dashboard');
704
- });
705
- });
706
-
707
- // 测试中使用
708
- describe('Dashboard', () => {
709
- beforeEach(() => {
710
- cy.login('alice', 'password123');
711
- cy.visit('/dashboard');
712
- });
713
-
714
- it('显示用户信息', () => {
715
- cy.contains('Alice').should('be.visible');
716
- });
717
- });
718
- ```
719
-
720
- ## CI/CD 集成
721
-
722
- ### GitHub Actions - Playwright
723
- ```yaml
724
- name: Playwright Tests
725
-
726
- on: [push, pull_request]
727
-
728
- jobs:
729
- test:
730
- runs-on: ubuntu-latest
731
- steps:
732
- - uses: actions/checkout@v3
733
-
734
- - uses: actions/setup-node@v3
735
- with:
736
- node-version: 18
737
-
738
- - name: Install dependencies
739
- run: npm ci
740
-
741
- - name: Install Playwright
742
- run: npx playwright install --with-deps
743
-
744
- - name: Run tests
745
- run: npx playwright test
746
-
747
- - uses: actions/upload-artifact@v3
748
- if: always()
749
- with:
750
- name: playwright-report
751
- path: playwright-report/
752
- retention-days: 30
753
- ```
754
-
755
- ### GitHub Actions - Cypress
756
- ```yaml
757
- name: Cypress Tests
758
-
759
- on: [push]
760
-
761
- jobs:
762
- test:
763
- runs-on: ubuntu-latest
764
- steps:
765
- - uses: actions/checkout@v3
766
-
767
- - name: Cypress run
768
- uses: cypress-io/github-action@v5
769
- with:
770
- start: npm start
771
- wait-on: 'http://localhost:3000'
772
- browser: chrome
773
-
774
- - uses: actions/upload-artifact@v3
775
- if: failure()
776
- with:
777
- name: cypress-screenshots
778
- path: cypress/screenshots
779
-
780
- - uses: actions/upload-artifact@v3
781
- if: failure()
782
- with:
783
- name: cypress-videos
784
- path: cypress/videos
785
- ```
786
-
787
- ### Docker 运行
788
- ```dockerfile
789
- # Playwright Dockerfile
790
- FROM mcr.microsoft.com/playwright:v1.40.0-focal
791
-
792
- WORKDIR /app
793
- COPY package*.json ./
794
- RUN npm ci
795
- COPY . .
796
-
797
- CMD ["npx", "playwright", "test"]
798
- ```
799
-
800
- ```yaml
801
- # docker-compose.yml
802
- services:
803
- e2e:
804
- build: .
805
- environment:
806
- - CI=true
807
- volumes:
808
- - ./playwright-report:/app/playwright-report
809
- ```
810
-
811
- ## 调试技巧
812
-
813
- ### Playwright 调试
814
- ```typescript
815
- // 调试模式
816
- test('调试测试', async ({ page }) => {
817
- await page.goto('/');
818
-
819
- // 暂停执行
820
- await page.pause();
821
-
822
- // 慢速执行
823
- await page.click('button', { delay: 1000 });
824
-
825
- // 打印日志
826
- console.log(await page.title());
827
-
828
- // 截图
829
- await page.screenshot({ path: 'debug.png' });
830
- });
831
-
832
- // 命令行调试
833
- // npx playwright test --debug
834
- // npx playwright test --headed --slowMo=1000
835
- ```
836
-
837
- ### Cypress 调试
838
- ```javascript
839
- describe('调试', () => {
840
- it('调试测试', () => {
841
- cy.visit('/');
842
-
843
- // 暂停
844
- cy.pause();
845
-
846
- // 打印日志
847
- cy.get('.user').then($el => {
848
- console.log($el.text());
849
- });
850
-
851
- // 调试命令
852
- cy.get('.user').debug();
853
-
854
- // 截图
855
- cy.screenshot('debug');
856
- });
857
- });
858
- ```
859
-
860
- ## 最佳实践
861
-
862
- ### 选择器优先级
863
- ```
864
- 1. data-testid (推荐)
865
- 2. role + accessible name
866
- 3. 稳定的 class/id
867
- 4. 文本内容 (谨慎)
868
- 5. CSS 选择器 (避免)
869
- 6. XPath (避免)
870
- ```
871
-
872
- ### 测试独立性
873
- ```typescript
874
- // ❌ 测试依赖
875
- test('创建用户', async ({ page }) => {
876
- // 创建用户
877
- });
878
-
879
- test('编辑用户', async ({ page }) => {
880
- // 依赖上一个测试
881
- });
882
-
883
- // ✅ 独立测试
884
- test('编辑用户', async ({ page }) => {
885
- // 通过 API 准备数据
886
- await request.post('/api/users', { data: testUser });
887
-
888
- // 执行测试
889
- await page.goto(`/users/${testUser.id}/edit`);
890
- });
891
- ```
892
-
893
- ### 减少等待时间
894
- ```typescript
895
- // ❌ 固定等待
896
- await page.waitForTimeout(5000);
897
-
898
- // ✅ 智能等待
899
- await page.waitForSelector('.loaded');
900
- await page.waitForLoadState('networkidle');
901
- ```
902
-
903
- ## 工具清单
904
-
905
- | 工具 | 用途 | 特点 |
906
- |------|------|------|
907
- | Playwright | E2E 测试 | 多浏览器、强大 API |
908
- | Cypress | E2E 测试 | 优秀调试体验 |
909
- | Selenium | E2E 测试 | 老牌、多语言 |
910
- | Percy | 可视化回归 | 云端对比 |
911
- | Chromatic | Storybook 可视化 | 组件级测试 |
912
- | Puppeteer | 浏览器自动化 | Chrome DevTools |
913
-
914
- ---