fe-kit-cli 0.0.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.
- package/README.md +89 -0
- package/dist/cli.mjs +1738 -0
- package/dist/cli.mjs.map +1 -0
- package/dist/rules/common/typescript.mdc +21 -0
- package/dist/rules/react/component-conventions.mdc +24 -0
- package/dist/rules/react/hooks.mdc +20 -0
- package/dist/rules/react/react-router.mdc +18 -0
- package/dist/rules/react/state-management.mdc +21 -0
- package/dist/rules/vue/component-conventions.mdc +23 -0
- package/dist/rules/vue/composition-api.mdc +24 -0
- package/dist/rules/vue/state-management.mdc +16 -0
- package/dist/rules/vue/vue-router.mdc +18 -0
- package/dist/skills/app-ui-design/SKILL.md +62 -0
- package/dist/skills/app-ui-design/references/rules.md +127 -0
- package/dist/skills/e2e-testing/SKILL.md +327 -0
- package/dist/skills/eval-harness/SKILL.md +271 -0
- package/dist/skills/frontend-design/SKILL.md +43 -0
- package/dist/skills/frontend-patterns/SKILL.md +643 -0
- package/dist/skills/security-review/SKILL.md +496 -0
- package/dist/skills/tailwindcss-advanced-layouts/SKILL.md +595 -0
- package/dist/skills/tdd-workflow/SKILL.md +464 -0
- package/dist/skills/verification-loop/SKILL.md +127 -0
- package/dist/skills/wechat-ui-design/SKILL.md +64 -0
- package/dist/skills/wechat-ui-design/references/rules.md +121 -0
- package/dist/templates/react-rspack-ts/index.html +11 -0
- package/dist/templates/react-rspack-ts/package.json +20 -0
- package/dist/templates/react-rspack-ts/rspack.config.ts +23 -0
- package/dist/templates/react-rspack-ts/src/App.tsx +7 -0
- package/dist/templates/react-rspack-ts/src/main.tsx +9 -0
- package/dist/templates/react-rspack-ts/tsconfig.json +17 -0
- package/dist/templates/react-vite-ts/index.html +12 -0
- package/dist/templates/react-vite-ts/package.json +22 -0
- package/dist/templates/react-vite-ts/src/App.tsx +7 -0
- package/dist/templates/react-vite-ts/src/main.tsx +9 -0
- package/dist/templates/react-vite-ts/tsconfig.json +19 -0
- package/dist/templates/react-vite-ts/vite.config.ts +9 -0
- package/dist/templates/react-webpack-ts/index.html +11 -0
- package/dist/templates/react-webpack-ts/package.json +25 -0
- package/dist/templates/react-webpack-ts/src/App.tsx +7 -0
- package/dist/templates/react-webpack-ts/src/main.tsx +9 -0
- package/dist/templates/react-webpack-ts/tsconfig.json +17 -0
- package/dist/templates/react-webpack-ts/webpack.config.ts +29 -0
- package/dist/templates/vue-rspack-ts/index.html +11 -0
- package/dist/templates/vue-rspack-ts/package.json +18 -0
- package/dist/templates/vue-rspack-ts/rspack.config.ts +16 -0
- package/dist/templates/vue-rspack-ts/src/App.vue +7 -0
- package/dist/templates/vue-rspack-ts/src/main.ts +4 -0
- package/dist/templates/vue-rspack-ts/tsconfig.json +17 -0
- package/dist/templates/vue-vite-ts/index.html +12 -0
- package/dist/templates/vue-vite-ts/package.json +19 -0
- package/dist/templates/vue-vite-ts/src/App.vue +7 -0
- package/dist/templates/vue-vite-ts/src/main.ts +4 -0
- package/dist/templates/vue-vite-ts/tsconfig.json +19 -0
- package/dist/templates/vue-vite-ts/vite.config.ts +9 -0
- package/dist/templates/vue-webpack-ts/index.html +11 -0
- package/dist/templates/vue-webpack-ts/package.json +24 -0
- package/dist/templates/vue-webpack-ts/src/App.vue +7 -0
- package/dist/templates/vue-webpack-ts/src/main.ts +4 -0
- package/dist/templates/vue-webpack-ts/tsconfig.json +17 -0
- package/dist/templates/vue-webpack-ts/webpack.config.ts +32 -0
- package/package.json +63 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Review UI code for (App/H5)UI 设计规范 compliance
|
|
3
|
+
argument-hint: <file-or-pattern>
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 设计规范 规则说明)
|
|
7
|
+
|
|
8
|
+
输出格式遵循 Web Interface Guidelines 的“分组列规则 + 最终 ✓ pass”风格(不包含 source:loc 标记)。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 00. Document Defaults / Global Assumptions
|
|
13
|
+
|
|
14
|
+
- 设计稿尺寸基准为 750×1624px(适用于 750px)。
|
|
15
|
+
- 规范中按钮/输入框等尺寸单位均为 px(正文为节省空间已省略单位)。
|
|
16
|
+
- 行高规则:文本行高为字号的 1.5 倍;换行每增加一行,增加“文本字高度的 1.5 倍”;行高向上取偶数。
|
|
17
|
+
- 分割线(除错误标识以外)统一为 0.5px,色值 `#EAEAEA`。
|
|
18
|
+
- 页面背景色 `#F3F8FE`;页面卡片内容背景色 `#FFFFFF`。
|
|
19
|
+
- 字重仅使用【L / R / M / B】四档;未标识默认为常规 R。
|
|
20
|
+
- 若无特殊说明,默认(上下左右)居中对齐。
|
|
21
|
+
|
|
22
|
+
## 01. Layout / Grid System
|
|
23
|
+
|
|
24
|
+
- 使用等分栅格体系;除去两侧外边距后,栅格居中。
|
|
25
|
+
- 设计稿尺寸以 750×1624px 为基准。
|
|
26
|
+
- 内容区背景色 `#F3F8FE`;栏位 N=6。
|
|
27
|
+
- 栅格参数:外边距 30px;栅格间距 32px;列宽按 \((屏幕宽度 - 外边距×2 - 栅格间距×5) / 6\) 自适应。
|
|
28
|
+
|
|
29
|
+
## 02. Color System
|
|
30
|
+
|
|
31
|
+
- 主色(品牌/焦点色)为 `#006ECD`(控件色/文字链)。
|
|
32
|
+
- 页面背景色 `#F3F8FE`(首页/各 Tab 页背景)。
|
|
33
|
+
- press 状态:主色叠加 `#000000` 20%(用于按压反馈)。
|
|
34
|
+
- 渐变色:`linear-gradient(-31deg, #0077E0 60%, #00E070 100%)`。
|
|
35
|
+
- 深黑 `#323953`:标题/重要文本/Tab 切换/输入文本。
|
|
36
|
+
- 中黑 `#5B6175`:正文/表单/正文详情。
|
|
37
|
+
- 深灰 `#949698`:辅助文本/弱文字。
|
|
38
|
+
- 浅灰 `#C1C3CB`:输入框待输入状态。
|
|
39
|
+
- 内容卡片背景色 `#FFFFFF`:一级及以上页面卡片背景。
|
|
40
|
+
- 分割线色 `#EAEAEA`:输入框未输入/复选框默认/分割线。
|
|
41
|
+
- 状态色:`#00C864`(成功)、`#FFE10A`(加载/等待)、`#FB5151`(报错/角标)、`#FF940A`(警告/进行中/toast 警告)。
|
|
42
|
+
|
|
43
|
+
## 03. Typography
|
|
44
|
+
|
|
45
|
+
- 中文字号建议:最大 60px、最小 20px;字号为偶数(4 的倍数最佳);特殊场景可调整但需控制文案长度。
|
|
46
|
+
- 中文默认字体:PingFang SC。
|
|
47
|
+
- 英文/数字默认字体:DIN Alternate。
|
|
48
|
+
|
|
49
|
+
中文字号语义(示例表述以规范为准):
|
|
50
|
+
- 60px:结果页大标题(Light 300)。
|
|
51
|
+
- 52px:大标题/引导文案(Regular/Medium)。
|
|
52
|
+
- 42px:Tab 页标题(Bold 700)。
|
|
53
|
+
- 36px:一级标题/导航栏标题/正常按钮文案;32px:二级标题/列表/输入框文案。
|
|
54
|
+
|
|
55
|
+
英文/数字字号语义:
|
|
56
|
+
- 28px:主要内容/提示文字/卡片按钮文案。
|
|
57
|
+
- 26px:插卡/列表主要内容描述。
|
|
58
|
+
- 24px:摘要/次要信息。
|
|
59
|
+
- 20px:标签/角标。
|
|
60
|
+
|
|
61
|
+
## 04. Icons
|
|
62
|
+
|
|
63
|
+
- 图标一致性:设计尺寸保持饱满,保留距边 2–4px;偏长/不饱满图标可通过缩小距边保持视觉大小一致。
|
|
64
|
+
- 线性图标小尺寸:24×24px。
|
|
65
|
+
- 线性图标常规尺寸:44×44px。
|
|
66
|
+
- 金刚区图标:62×62px。
|
|
67
|
+
- 业务图标:60×60px。
|
|
68
|
+
|
|
69
|
+
## 05. Buttons
|
|
70
|
+
|
|
71
|
+
- 全局按钮:作用于整个界面的高层级操作;按钮宽度随界面宽度变化;距离两侧边距固定(遵守全局外边距)。
|
|
72
|
+
- 通栏主要按钮尺寸 650×96px;通栏次要按钮与主要按钮同栅格对齐;需提供按下/禁用等状态。
|
|
73
|
+
- 悬浮按钮:位于右下角,距离内容区边缘 30px;尺寸 100×100px。
|
|
74
|
+
- 文字按钮:纯文字或“文字+图标”;颜色必须具备可操作感;文案简短直接;与“文字链”区分(文字链可嵌入段落)。
|
|
75
|
+
- 文字常规按钮高度 60px(宽度随文字自适应);另有高度 44px 规格(宽度随文字自适应)。
|
|
76
|
+
- 吸底按钮高度 60px(主要/次要并列样式需保持统一高度与对齐)。
|
|
77
|
+
|
|
78
|
+
## 06. Navigation Bars
|
|
79
|
+
|
|
80
|
+
- 导航栏应用样式:导航栏高度 100px。
|
|
81
|
+
|
|
82
|
+
## 07. Tabs
|
|
83
|
+
|
|
84
|
+
- 标签栏形态:页面顶部标签栏(可返回上一级);页面内部一级/二级标签栏;二级标签栏支持“更多/展开收起”形态,选中/未选中状态需明确可区分。
|
|
85
|
+
|
|
86
|
+
## 08. Forms
|
|
87
|
+
|
|
88
|
+
- 表单组合需覆盖“输入前/输入后”两种状态;提示占位、已输入内容、选择器、日期段等控件状态需一致。
|
|
89
|
+
- 表单控件类型至少包含:标题输入框、日期段选择框、多行文本框、选择框、单行文本框;同一页面内控件规格需统一(避免混用不同高度/字体/间距)。
|
|
90
|
+
- 表单提交区需包含明确主按钮与协议勾选/说明区域(布局需稳定且不挤压主要操作)。
|
|
91
|
+
|
|
92
|
+
## 09. Lists / Cards
|
|
93
|
+
|
|
94
|
+
- 列表形态需覆盖:客户列表(含首字母索引)、日历列表等;展开/收起样式需一致且可预期。
|
|
95
|
+
- 卡片型列表(排行榜/产品卡片等)需保证信息层级清晰、操作区(预览/编辑/删除等)布局一致。
|
|
96
|
+
- 展开/收起信息卡(如保单)需保证关键信息在收起态仍可读;展开态信息块对齐一致。
|
|
97
|
+
|
|
98
|
+
## 10. Toast / Notice / Badges
|
|
99
|
+
|
|
100
|
+
- 纯文字吐司最小尺寸 `min: 320×120px`;背景 `rgba(0,0,0,0.70)`。
|
|
101
|
+
- 图标+文字吐司尺寸 320×280px;图标用于强化反馈状态(成功/加载/失败等)。
|
|
102
|
+
- 数字红点:一位数 36×36px;多位数为圆角矩形自适应宽度,圆角直径 36px。
|
|
103
|
+
- 角标红点:18×18px,背景 `#FB5151`;带描边时为 `border: 2px solid #FFFFFF`。
|
|
104
|
+
|
|
105
|
+
## 11. Dialogs
|
|
106
|
+
|
|
107
|
+
- 对话框标题最长支持 12 字。
|
|
108
|
+
- 对话框按钮文案最长支持 10 字。
|
|
109
|
+
- 多按钮对话框:多于 1 个操作时,后两个为灰色(保持主次清晰)。
|
|
110
|
+
|
|
111
|
+
## 12. Masks / Overlays
|
|
112
|
+
|
|
113
|
+
- 遮罩(底部弹窗):`rgba(0,0,0,0.60)`。
|
|
114
|
+
- 底部弹窗遮罩需预留最小高度 60px(避免贴底造成不可操作/压迫)。
|
|
115
|
+
- 全屏遮罩:`rgba(7,7,7,0.80)`。
|
|
116
|
+
|
|
117
|
+
## 13. Hero / Carousel (焦点图)
|
|
118
|
+
|
|
119
|
+
- 焦点图宽度 750px(随屏宽适配时需保持等比/裁切策略一致)。
|
|
120
|
+
- 焦点图顶部遮挡 88px;底部遮挡 73px(内容安全区需避开遮挡)。
|
|
121
|
+
- 关键内容展示建议区间 192px(主信息需落在安全区内)。
|
|
122
|
+
- 焦点图高度示例 353px(若业务调整高度,必须同步更新安全区与遮挡规则)。
|
|
123
|
+
|
|
124
|
+
## 99. Quick Sanity Check
|
|
125
|
+
|
|
126
|
+
✓ pass - 若页面满足:750 设计稿体系、30px 外边距与 6 栏栅格、背景 `#F3F8FE` + 卡片 `#FFFFFF`、分割线 `0.5px #EAEAEA`、主色 `#006ECD` 与 press 叠加规则、字号 20–60 且偶数(4 倍数优先)、关键组件尺寸(按钮/吐司/角标/遮罩)与状态一致、交互主次清晰。
|
|
127
|
+
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: e2e-testing
|
|
3
|
+
description: Playwright E2E testing patterns, Page Object Model, configuration, CI/CD integration, artifact management, and flaky test strategies.
|
|
4
|
+
origin: ECC
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# E2E Testing Patterns
|
|
8
|
+
|
|
9
|
+
Comprehensive Playwright patterns for building stable, fast, and maintainable E2E test suites.
|
|
10
|
+
|
|
11
|
+
## Test File Organization
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
tests/
|
|
15
|
+
├── e2e/
|
|
16
|
+
│ ├── auth/
|
|
17
|
+
│ │ ├── login.spec.ts
|
|
18
|
+
│ │ ├── logout.spec.ts
|
|
19
|
+
│ │ └── register.spec.ts
|
|
20
|
+
│ ├── features/
|
|
21
|
+
│ │ ├── browse.spec.ts
|
|
22
|
+
│ │ ├── search.spec.ts
|
|
23
|
+
│ │ └── create.spec.ts
|
|
24
|
+
│ └── api/
|
|
25
|
+
│ └── endpoints.spec.ts
|
|
26
|
+
├── fixtures/
|
|
27
|
+
│ ├── auth.ts
|
|
28
|
+
│ └── data.ts
|
|
29
|
+
└── playwright.config.ts
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Page Object Model (POM)
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { Page, Locator } from '@playwright/test'
|
|
36
|
+
|
|
37
|
+
export class ItemsPage {
|
|
38
|
+
readonly page: Page
|
|
39
|
+
readonly searchInput: Locator
|
|
40
|
+
readonly itemCards: Locator
|
|
41
|
+
readonly createButton: Locator
|
|
42
|
+
|
|
43
|
+
constructor(page: Page) {
|
|
44
|
+
this.page = page
|
|
45
|
+
this.searchInput = page.locator('[data-testid="search-input"]')
|
|
46
|
+
this.itemCards = page.locator('[data-testid="item-card"]')
|
|
47
|
+
this.createButton = page.locator('[data-testid="create-btn"]')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async goto() {
|
|
51
|
+
await this.page.goto('/items')
|
|
52
|
+
await this.page.waitForLoadState('networkidle')
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async search(query: string) {
|
|
56
|
+
await this.searchInput.fill(query)
|
|
57
|
+
await this.page.waitForResponse(resp => resp.url().includes('/api/search'))
|
|
58
|
+
await this.page.waitForLoadState('networkidle')
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async getItemCount() {
|
|
62
|
+
return await this.itemCards.count()
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Test Structure
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
import { test, expect } from '@playwright/test'
|
|
71
|
+
import { ItemsPage } from '../../pages/ItemsPage'
|
|
72
|
+
|
|
73
|
+
test.describe('Item Search', () => {
|
|
74
|
+
let itemsPage: ItemsPage
|
|
75
|
+
|
|
76
|
+
test.beforeEach(async ({ page }) => {
|
|
77
|
+
itemsPage = new ItemsPage(page)
|
|
78
|
+
await itemsPage.goto()
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('should search by keyword', async ({ page }) => {
|
|
82
|
+
await itemsPage.search('test')
|
|
83
|
+
|
|
84
|
+
const count = await itemsPage.getItemCount()
|
|
85
|
+
expect(count).toBeGreaterThan(0)
|
|
86
|
+
|
|
87
|
+
await expect(itemsPage.itemCards.first()).toContainText(/test/i)
|
|
88
|
+
await page.screenshot({ path: 'artifacts/search-results.png' })
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('should handle no results', async ({ page }) => {
|
|
92
|
+
await itemsPage.search('xyznonexistent123')
|
|
93
|
+
|
|
94
|
+
await expect(page.locator('[data-testid="no-results"]')).toBeVisible()
|
|
95
|
+
expect(await itemsPage.getItemCount()).toBe(0)
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Playwright Configuration
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { defineConfig, devices } from '@playwright/test'
|
|
104
|
+
|
|
105
|
+
export default defineConfig({
|
|
106
|
+
testDir: './tests/e2e',
|
|
107
|
+
fullyParallel: true,
|
|
108
|
+
forbidOnly: !!process.env.CI,
|
|
109
|
+
retries: process.env.CI ? 2 : 0,
|
|
110
|
+
workers: process.env.CI ? 1 : undefined,
|
|
111
|
+
reporter: [
|
|
112
|
+
['html', { outputFolder: 'playwright-report' }],
|
|
113
|
+
['junit', { outputFile: 'playwright-results.xml' }],
|
|
114
|
+
['json', { outputFile: 'playwright-results.json' }]
|
|
115
|
+
],
|
|
116
|
+
use: {
|
|
117
|
+
baseURL: process.env.BASE_URL || 'http://localhost:3000',
|
|
118
|
+
trace: 'on-first-retry',
|
|
119
|
+
screenshot: 'only-on-failure',
|
|
120
|
+
video: 'retain-on-failure',
|
|
121
|
+
actionTimeout: 10000,
|
|
122
|
+
navigationTimeout: 30000,
|
|
123
|
+
},
|
|
124
|
+
projects: [
|
|
125
|
+
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
|
|
126
|
+
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
|
|
127
|
+
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
|
|
128
|
+
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
|
|
129
|
+
],
|
|
130
|
+
webServer: {
|
|
131
|
+
command: 'npm run dev',
|
|
132
|
+
url: 'http://localhost:3000',
|
|
133
|
+
reuseExistingServer: !process.env.CI,
|
|
134
|
+
timeout: 120000,
|
|
135
|
+
},
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## Flaky Test Patterns
|
|
140
|
+
|
|
141
|
+
### Quarantine
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
test('flaky: complex search', async ({ page }) => {
|
|
145
|
+
test.fixme(true, 'Flaky - Issue #123')
|
|
146
|
+
// test code...
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('conditional skip', async ({ page }) => {
|
|
150
|
+
test.skip(process.env.CI, 'Flaky in CI - Issue #123')
|
|
151
|
+
// test code...
|
|
152
|
+
})
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Identify Flakiness
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
npx playwright test tests/search.spec.ts --repeat-each=10
|
|
159
|
+
npx playwright test tests/search.spec.ts --retries=3
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Common Causes & Fixes
|
|
163
|
+
|
|
164
|
+
**Race conditions:**
|
|
165
|
+
```typescript
|
|
166
|
+
// Bad: assumes element is ready
|
|
167
|
+
await page.click('[data-testid="button"]')
|
|
168
|
+
|
|
169
|
+
// Good: auto-wait locator
|
|
170
|
+
await page.locator('[data-testid="button"]').click()
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Network timing:**
|
|
174
|
+
```typescript
|
|
175
|
+
// Bad: arbitrary timeout
|
|
176
|
+
await page.waitForTimeout(5000)
|
|
177
|
+
|
|
178
|
+
// Good: wait for specific condition
|
|
179
|
+
await page.waitForResponse(resp => resp.url().includes('/api/data'))
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
**Animation timing:**
|
|
183
|
+
```typescript
|
|
184
|
+
// Bad: click during animation
|
|
185
|
+
await page.click('[data-testid="menu-item"]')
|
|
186
|
+
|
|
187
|
+
// Good: wait for stability
|
|
188
|
+
await page.locator('[data-testid="menu-item"]').waitFor({ state: 'visible' })
|
|
189
|
+
await page.waitForLoadState('networkidle')
|
|
190
|
+
await page.locator('[data-testid="menu-item"]').click()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Artifact Management
|
|
194
|
+
|
|
195
|
+
### Screenshots
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
await page.screenshot({ path: 'artifacts/after-login.png' })
|
|
199
|
+
await page.screenshot({ path: 'artifacts/full-page.png', fullPage: true })
|
|
200
|
+
await page.locator('[data-testid="chart"]').screenshot({ path: 'artifacts/chart.png' })
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Traces
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
await browser.startTracing(page, {
|
|
207
|
+
path: 'artifacts/trace.json',
|
|
208
|
+
screenshots: true,
|
|
209
|
+
snapshots: true,
|
|
210
|
+
})
|
|
211
|
+
// ... test actions ...
|
|
212
|
+
await browser.stopTracing()
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Video
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// In playwright.config.ts
|
|
219
|
+
use: {
|
|
220
|
+
video: 'retain-on-failure',
|
|
221
|
+
videosPath: 'artifacts/videos/'
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## CI/CD Integration
|
|
226
|
+
|
|
227
|
+
```yaml
|
|
228
|
+
# .github/workflows/e2e.yml
|
|
229
|
+
name: E2E Tests
|
|
230
|
+
on: [push, pull_request]
|
|
231
|
+
|
|
232
|
+
jobs:
|
|
233
|
+
test:
|
|
234
|
+
runs-on: ubuntu-latest
|
|
235
|
+
steps:
|
|
236
|
+
- uses: actions/checkout@v4
|
|
237
|
+
- uses: actions/setup-node@v4
|
|
238
|
+
with:
|
|
239
|
+
node-version: 20
|
|
240
|
+
- run: npm ci
|
|
241
|
+
- run: npx playwright install --with-deps
|
|
242
|
+
- run: npx playwright test
|
|
243
|
+
env:
|
|
244
|
+
BASE_URL: ${{ vars.STAGING_URL }}
|
|
245
|
+
- uses: actions/upload-artifact@v4
|
|
246
|
+
if: always()
|
|
247
|
+
with:
|
|
248
|
+
name: playwright-report
|
|
249
|
+
path: playwright-report/
|
|
250
|
+
retention-days: 30
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Test Report Template
|
|
254
|
+
|
|
255
|
+
```markdown
|
|
256
|
+
# E2E Test Report
|
|
257
|
+
|
|
258
|
+
**Date:** YYYY-MM-DD HH:MM
|
|
259
|
+
**Duration:** Xm Ys
|
|
260
|
+
**Status:** PASSING / FAILING
|
|
261
|
+
|
|
262
|
+
## Summary
|
|
263
|
+
- Total: X | Passed: Y (Z%) | Failed: A | Flaky: B | Skipped: C
|
|
264
|
+
|
|
265
|
+
## Failed Tests
|
|
266
|
+
|
|
267
|
+
### test-name
|
|
268
|
+
**File:** `tests/e2e/feature.spec.ts:45`
|
|
269
|
+
**Error:** Expected element to be visible
|
|
270
|
+
**Screenshot:** artifacts/failed.png
|
|
271
|
+
**Recommended Fix:** [description]
|
|
272
|
+
|
|
273
|
+
## Artifacts
|
|
274
|
+
- HTML Report: playwright-report/index.html
|
|
275
|
+
- Screenshots: artifacts/*.png
|
|
276
|
+
- Videos: artifacts/videos/*.webm
|
|
277
|
+
- Traces: artifacts/*.zip
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Wallet / Web3 Testing
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
test('wallet connection', async ({ page, context }) => {
|
|
284
|
+
// Mock wallet provider
|
|
285
|
+
await context.addInitScript(() => {
|
|
286
|
+
window.ethereum = {
|
|
287
|
+
isMetaMask: true,
|
|
288
|
+
request: async ({ method }) => {
|
|
289
|
+
if (method === 'eth_requestAccounts')
|
|
290
|
+
return ['0x1234567890123456789012345678901234567890']
|
|
291
|
+
if (method === 'eth_chainId') return '0x1'
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
await page.goto('/')
|
|
297
|
+
await page.locator('[data-testid="connect-wallet"]').click()
|
|
298
|
+
await expect(page.locator('[data-testid="wallet-address"]')).toContainText('0x1234')
|
|
299
|
+
})
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
## Financial / Critical Flow Testing
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
test('trade execution', async ({ page }) => {
|
|
306
|
+
// Skip on production — real money
|
|
307
|
+
test.skip(process.env.NODE_ENV === 'production', 'Skip on production')
|
|
308
|
+
|
|
309
|
+
await page.goto('/markets/test-market')
|
|
310
|
+
await page.locator('[data-testid="position-yes"]').click()
|
|
311
|
+
await page.locator('[data-testid="trade-amount"]').fill('1.0')
|
|
312
|
+
|
|
313
|
+
// Verify preview
|
|
314
|
+
const preview = page.locator('[data-testid="trade-preview"]')
|
|
315
|
+
await expect(preview).toContainText('1.0')
|
|
316
|
+
|
|
317
|
+
// Confirm and wait for blockchain
|
|
318
|
+
await page.locator('[data-testid="confirm-trade"]').click()
|
|
319
|
+
await page.waitForResponse(
|
|
320
|
+
resp => resp.url().includes('/api/trade') && resp.status() === 200,
|
|
321
|
+
{ timeout: 30000 }
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
await expect(page.locator('[data-testid="trade-success"]')).toBeVisible()
|
|
325
|
+
})
|
|
326
|
+
```
|
|
327
|
+
|