speccore 8.3.149 → 8.3.151
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/package.json +1 -1
- package/templates/html/speccore-ask-guide.html +1 -1
- package/templates/html/speccore-ask-pipeline.html +1 -1
- package/templates/html/speccore-ask-result.html +1 -1
- package/templates/html/speccore-dev.html +1 -1
- package/templates/html/speccore-help.html +1 -1
- package/templates/html/speccore-retro-T-001.html +1 -1
- package/templates/html/speccore-welcome.html +1 -1
- package/templates/verify-spec-example.yaml +348 -272
|
@@ -1,76 +1,104 @@
|
|
|
1
|
-
# VERIFY_SPEC.yaml —
|
|
1
|
+
# VERIFY_SPEC.yaml — UI 冒烟测试规格(真实值示例版)
|
|
2
2
|
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
3
3
|
# 格式类型: VERIFY_SPEC 格式(scenarios + actions + assertions 结构)
|
|
4
|
-
# 适用场景: 精细 UI 交互测试(填写表单、点击按钮、断言元素状态)
|
|
5
4
|
# 对应命令:
|
|
6
|
-
# • speccore verify --ui --spec ./tests/smoke.yaml ←
|
|
7
|
-
# • speccore verify --ui --config ./tests/smoke.yaml ←
|
|
8
|
-
# 按模块过滤: speccore verify --ui --spec ./tests/smoke.yaml --module=
|
|
5
|
+
# • speccore verify --ui --spec ./tests/smoke.yaml ← 推荐
|
|
6
|
+
# • speccore verify --ui --config ./tests/smoke.yaml ← 兼容
|
|
7
|
+
# 按模块过滤: speccore verify --ui --spec ./tests/smoke.yaml --module=login
|
|
9
8
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# TestConfig 格式 = tests + routes + type(用于批量页面/API测试,见 pr.yaml/release.yaml)
|
|
13
|
-
# 两种格式 speccore verify --config 均可自动识别。
|
|
9
|
+
# 本文件所有 value / selector 均为真实示例值,使用时替换为你的实际值即可。
|
|
10
|
+
# 选择器建议优先使用 data-testid(最稳定),其次 name / id,避免随机 class。
|
|
14
11
|
#
|
|
15
|
-
# 本示例覆盖:
|
|
16
|
-
# • 全部 ActionType: fill, click, select, check, uncheck, hover, focus, blur, press,
|
|
17
|
-
# wait, navigate, screenshot, cookie, localStorage, script, scroll, upload, iframe,
|
|
18
|
-
# waitForRequest, waitForResponse, drag
|
|
19
|
-
# • 全部 AssertionType: visible, hidden, text, value, url, count, attribute, style, visual
|
|
20
|
-
# • 业务模块: 登录认证 / 用户中心 / 商品 / 购物车 / 订单 / 消息通知 / 后台管理
|
|
21
12
|
#
|
|
22
|
-
#
|
|
13
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
14
|
+
# 【重要】关于"需要登录的场景"怎么处理?
|
|
15
|
+
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
#
|
|
17
|
+
# 本文件场景1-9 是测试"登录功能本身"的,必须走完整的登录流程。
|
|
18
|
+
# 场景10-42 是测试"登录后的业务功能"(如个人资料、订单、购物车),这些场景默认已登录。
|
|
19
|
+
#
|
|
20
|
+
# 你有两种方式让"业务场景"拿到登录态:
|
|
21
|
+
#
|
|
22
|
+
# 方式A: Cookie/Token 注入(推荐,最简洁)
|
|
23
|
+
# 把「场景13: Token注入免登录」或「场景33: Cookie注入免登录」复制到业务场景前面,
|
|
24
|
+
# 作为第一个场景执行。注入后同一浏览器会话内后续场景共享登录态。
|
|
25
|
+
# 示例顺序:
|
|
26
|
+
# 1. Token注入免登录(注入 accessToken + refreshToken)
|
|
27
|
+
# 2. 个人资料修改
|
|
28
|
+
# 3. 商品列表浏览
|
|
29
|
+
# 4. 创建订单...
|
|
30
|
+
#
|
|
31
|
+
# 方式B: 每个业务场景自己注入(最独立,适合并行执行)
|
|
32
|
+
# 在每个需要登录的场景的 actions 开头加 cookie 或 localStorage 注入:
|
|
33
|
+
# actions:
|
|
34
|
+
# - type: localStorage
|
|
35
|
+
# selector: accessToken
|
|
36
|
+
# value: 'Bearer eyJhbGci...'
|
|
37
|
+
# - type: navigate
|
|
38
|
+
# value: /profile
|
|
39
|
+
#
|
|
40
|
+
# 方式C: 先执行登录场景,再执行业务场景(顺序依赖)
|
|
41
|
+
# 把「场景1: 账号密码登录」放第一个,登录成功后同一浏览器会话内后续场景共享登录态。
|
|
42
|
+
# 注意: 如果登录有验证码/短信,这种方式不稳定,建议用方式A。
|
|
43
|
+
#
|
|
44
|
+
# ⚠️ 每个 scenario 独立执行,但 cookie/localStorage 在同一浏览器会话内共享。
|
|
23
45
|
|
|
24
|
-
name:
|
|
46
|
+
name: 示例项目冒烟测试
|
|
25
47
|
platform: h5-mobile
|
|
26
48
|
url: /
|
|
27
49
|
|
|
28
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
29
|
-
# 一、登录认证模块 (auth)
|
|
30
|
-
# ─────────────────────────────────────────────────────────────────────────────
|
|
31
50
|
scenarios:
|
|
51
|
+
|
|
52
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
53
|
+
# 一、登录认证 (auth)
|
|
54
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
55
|
+
|
|
32
56
|
# ── 场景1: 账号密码登录 ──
|
|
57
|
+
# 说明: 最基础的登录方式,填写用户名密码后点击登录
|
|
58
|
+
# 如何替换: 把 selector 换成你页面上的真实选择器,value 换成你的测试账号
|
|
33
59
|
- name: 登录-账号密码登录
|
|
34
60
|
description: 模块:auth 页面:/login
|
|
35
61
|
actions:
|
|
36
62
|
- type: navigate
|
|
37
|
-
value: /login
|
|
63
|
+
value: /login # 登录页路由
|
|
38
64
|
- type: fill
|
|
39
|
-
selector: 'input[name="username"]'
|
|
40
|
-
value: test_user
|
|
65
|
+
selector: 'input[name="username"]' # 用户名输入框(换成你实际的,如 '#username' / '[data-testid="user-input"]')
|
|
66
|
+
value: test_user # 测试账号用户名
|
|
41
67
|
- type: fill
|
|
42
|
-
selector: 'input[name="password"]'
|
|
43
|
-
value:
|
|
68
|
+
selector: 'input[name="password"]' # 密码输入框
|
|
69
|
+
value: Test@123456 # 测试账号密码
|
|
44
70
|
- type: click
|
|
45
|
-
selector: 'button[type="submit"]'
|
|
46
|
-
waitFor: '.dashboard-container'
|
|
71
|
+
selector: 'button[type="submit"]' # 登录按钮
|
|
72
|
+
waitFor: '.dashboard-container' # 登录成功后出现的元素(用于等待跳转完成)
|
|
47
73
|
assertions:
|
|
48
74
|
- type: url
|
|
49
|
-
contains: /dashboard
|
|
75
|
+
contains: /dashboard # 断言 URL 跳转到控制台
|
|
50
76
|
- type: visible
|
|
51
|
-
selector: '.dashboard-container'
|
|
77
|
+
selector: '.dashboard-container' # 断言控制台容器可见
|
|
52
78
|
- type: text
|
|
53
|
-
selector: '.user-name'
|
|
54
|
-
contains: test_user
|
|
79
|
+
selector: '.user-name' # 断言页面显示用户名
|
|
80
|
+
contains: test_user # 预期显示的用户名
|
|
55
81
|
|
|
56
82
|
# ── 场景2: 手机号+验证码登录 ──
|
|
83
|
+
# 说明: 输入手机号 → 点击获取验证码 → 输入验证码 → 登录
|
|
84
|
+
# 如何替换: 测试环境通常有固定验证码(如123456),或需要 mock 验证码接口
|
|
57
85
|
- name: 登录-手机号验证码
|
|
58
86
|
description: 模块:auth 页面:/login/phone
|
|
59
87
|
actions:
|
|
60
88
|
- type: navigate
|
|
61
|
-
value: /login/phone
|
|
89
|
+
value: /login/phone # 手机号登录页路由
|
|
62
90
|
- type: fill
|
|
63
|
-
selector: 'input[name="phone"]'
|
|
64
|
-
value: '13800138000'
|
|
91
|
+
selector: 'input[name="phone"]' # 手机号输入框
|
|
92
|
+
value: '13800138000' # 测试手机号
|
|
65
93
|
- type: click
|
|
66
|
-
selector: '.send-code-btn'
|
|
94
|
+
selector: '.send-code-btn' # 获取验证码按钮
|
|
67
95
|
- type: wait
|
|
68
|
-
delay: 2000
|
|
96
|
+
delay: 2000 # 等待短信发送(测试环境可缩短)
|
|
69
97
|
- type: fill
|
|
70
|
-
selector: 'input[name="verifyCode"]'
|
|
71
|
-
value: '123456'
|
|
98
|
+
selector: 'input[name="verifyCode"]' # 验证码输入框
|
|
99
|
+
value: '123456' # 测试环境固定验证码
|
|
72
100
|
- type: click
|
|
73
|
-
selector: 'button[type="submit"]'
|
|
101
|
+
selector: 'button[type="submit"]' # 登录按钮
|
|
74
102
|
waitFor: '.dashboard-container'
|
|
75
103
|
assertions:
|
|
76
104
|
- type: url
|
|
@@ -79,35 +107,39 @@ scenarios:
|
|
|
79
107
|
selector: '.dashboard-container'
|
|
80
108
|
|
|
81
109
|
# ── 场景3: 微信 OAuth 登录 ──
|
|
110
|
+
# 说明: 点击微信登录按钮 → 跳转微信授权页 → 授权后回调
|
|
111
|
+
# 如何替换: 需配置微信开放平台测试账号,回调域名需与测试环境一致
|
|
82
112
|
- name: 登录-微信OAuth
|
|
83
113
|
description: 模块:auth 页面:/login 第三方登录
|
|
84
114
|
actions:
|
|
85
115
|
- type: navigate
|
|
86
|
-
value: /login
|
|
116
|
+
value: /login # 登录页
|
|
87
117
|
- type: click
|
|
88
|
-
selector: '.wechat-login-btn'
|
|
118
|
+
selector: '.wechat-login-btn' # 微信登录按钮(如 [data-testid="wechat-login"])
|
|
89
119
|
- type: waitForRequest
|
|
90
|
-
selector: 'https://open.weixin.qq.com/connect/oauth2/authorize'
|
|
120
|
+
selector: 'https://open.weixin.qq.com/connect/oauth2/authorize' # 等待跳转微信授权页
|
|
91
121
|
timeout: 5000
|
|
92
122
|
- type: wait
|
|
93
|
-
delay: 3000
|
|
123
|
+
delay: 3000 # 等待授权回调
|
|
94
124
|
assertions:
|
|
95
125
|
- type: url
|
|
96
|
-
contains: wechat
|
|
126
|
+
contains: wechat # 断言 URL 包含微信域名
|
|
97
127
|
|
|
98
128
|
# ── 场景4: 企业微信 SSO 登录 ──
|
|
129
|
+
# 说明: 页面内嵌 iframe 展示企业微信登录 → 填写企业 ID → 授权
|
|
130
|
+
# 如何替换: 企业 ID 从企业微信管理后台获取(格式: wwxxxxxxxxxxxxxxxx)
|
|
99
131
|
- name: 登录-企业微信SSO
|
|
100
132
|
description: 模块:auth 页面:/login/sso
|
|
101
133
|
actions:
|
|
102
134
|
- type: navigate
|
|
103
|
-
value: /login/sso
|
|
135
|
+
value: /login/sso # SSO 登录页
|
|
104
136
|
- type: iframe
|
|
105
|
-
selector: '#sso-frame'
|
|
137
|
+
selector: '#sso-frame' # iframe 选择器(切换上下文到 iframe 内部)
|
|
106
138
|
- type: fill
|
|
107
|
-
selector: 'input[name="corpId"]'
|
|
108
|
-
value: 'ww1234567890abcdef'
|
|
139
|
+
selector: 'input[name="corpId"]' # 企业 ID 输入框(在 iframe 内)
|
|
140
|
+
value: 'ww1234567890abcdef' # 企业微信企业 ID
|
|
109
141
|
- type: click
|
|
110
|
-
selector: '.sso-submit-btn'
|
|
142
|
+
selector: '.sso-submit-btn' # 确认按钮(在 iframe 内)
|
|
111
143
|
waitFor: '.dashboard-container'
|
|
112
144
|
assertions:
|
|
113
145
|
- type: url
|
|
@@ -116,24 +148,28 @@ scenarios:
|
|
|
116
148
|
selector: '.dashboard-container'
|
|
117
149
|
|
|
118
150
|
# ── 场景5: 扫码登录 ──
|
|
151
|
+
# 说明: 进入扫码页 → 二维码渲染 → 等待扫码 → 登录成功
|
|
152
|
+
# 如何替换: 测试环境可用 mock 接口模拟扫码成功,避免人工操作
|
|
119
153
|
- name: 登录-扫码登录
|
|
120
154
|
description: 模块:auth 页面:/login/qrcode
|
|
121
155
|
actions:
|
|
122
156
|
- type: navigate
|
|
123
|
-
value: /login/qrcode
|
|
157
|
+
value: /login/qrcode # 扫码登录页
|
|
124
158
|
- type: visible
|
|
125
|
-
selector: '.qrcode-img'
|
|
159
|
+
selector: '.qrcode-img' # 断言二维码图片已渲染
|
|
126
160
|
- type: wait
|
|
127
|
-
delay: 5000
|
|
161
|
+
delay: 5000 # 等待扫码(测试环境可 mock 缩短)
|
|
128
162
|
assertions:
|
|
129
163
|
- type: visible
|
|
130
164
|
selector: '.qrcode-img'
|
|
131
165
|
- type: attribute
|
|
132
|
-
selector: '.qrcode-img'
|
|
133
|
-
attribute: src
|
|
134
|
-
contains: data:image
|
|
166
|
+
selector: '.qrcode-img' # 断言二维码图片
|
|
167
|
+
attribute: src # 检查 src 属性
|
|
168
|
+
contains: data:image # 包含 base64 图片数据
|
|
135
169
|
|
|
136
170
|
# ── 场景6: 登录失败-密码错误 ──
|
|
171
|
+
# 说明: 故意输错密码,验证错误提示是否正确
|
|
172
|
+
# 如何替换: value 填存在的账号 + 错误的密码
|
|
137
173
|
- name: 登录-密码错误提示
|
|
138
174
|
description: 模块:auth 页面:/login 异常场景
|
|
139
175
|
actions:
|
|
@@ -141,22 +177,23 @@ scenarios:
|
|
|
141
177
|
value: /login
|
|
142
178
|
- type: fill
|
|
143
179
|
selector: 'input[name="username"]'
|
|
144
|
-
value: test_user
|
|
180
|
+
value: test_user # 存在的用户名
|
|
145
181
|
- type: fill
|
|
146
182
|
selector: 'input[name="password"]'
|
|
147
|
-
value: wrong_password
|
|
183
|
+
value: wrong_password # 故意填错密码
|
|
148
184
|
- type: click
|
|
149
185
|
selector: 'button[type="submit"]'
|
|
150
186
|
- type: wait
|
|
151
|
-
delay: 1000
|
|
187
|
+
delay: 1000 # 等待错误提示出现
|
|
152
188
|
assertions:
|
|
153
189
|
- type: visible
|
|
154
|
-
selector: '.error-message'
|
|
190
|
+
selector: '.error-message' # 错误提示元素
|
|
155
191
|
- type: text
|
|
156
192
|
selector: '.error-message'
|
|
157
|
-
contains: 密码错误
|
|
193
|
+
contains: 密码错误 # 根据实际文案调整(如"用户名或密码错误")
|
|
158
194
|
|
|
159
195
|
# ── 场景7: 登录失败-账号锁定 ──
|
|
196
|
+
# 说明: 使用被锁定的账号登录,验证锁定提示
|
|
160
197
|
- name: 登录-账号锁定提示
|
|
161
198
|
description: 模块:auth 页面:/login 异常场景
|
|
162
199
|
actions:
|
|
@@ -164,10 +201,10 @@ scenarios:
|
|
|
164
201
|
value: /login
|
|
165
202
|
- type: fill
|
|
166
203
|
selector: 'input[name="username"]'
|
|
167
|
-
value: locked_user
|
|
204
|
+
value: locked_user # 被锁定的测试账号
|
|
168
205
|
- type: fill
|
|
169
206
|
selector: 'input[name="password"]'
|
|
170
|
-
value: any_password
|
|
207
|
+
value: any_password # 任意密码
|
|
171
208
|
- type: click
|
|
172
209
|
selector: 'button[type="submit"]'
|
|
173
210
|
- type: wait
|
|
@@ -177,114 +214,122 @@ scenarios:
|
|
|
177
214
|
selector: '.error-message'
|
|
178
215
|
- type: text
|
|
179
216
|
selector: '.error-message'
|
|
180
|
-
contains: 账号已被锁定
|
|
217
|
+
contains: 账号已被锁定 # 根据实际文案调整
|
|
181
218
|
|
|
182
|
-
# ── 场景8:
|
|
219
|
+
# ── 场景8: 记住密码 ──
|
|
220
|
+
# 说明: 勾选记住密码 → 登录 → 验证 Cookie 中写入记住密码标记
|
|
183
221
|
- name: 登录-记住密码
|
|
184
222
|
description: 模块:auth 页面:/login 记住密码勾选
|
|
185
223
|
actions:
|
|
186
224
|
- type: navigate
|
|
187
225
|
value: /login
|
|
188
226
|
- type: check
|
|
189
|
-
selector: 'input[name="rememberMe"]'
|
|
227
|
+
selector: 'input[name="rememberMe"]' # 记住密码复选框
|
|
190
228
|
- type: fill
|
|
191
229
|
selector: 'input[name="username"]'
|
|
192
230
|
value: test_user
|
|
193
231
|
- type: fill
|
|
194
232
|
selector: 'input[name="password"]'
|
|
195
|
-
value:
|
|
233
|
+
value: Test@123456
|
|
196
234
|
- type: click
|
|
197
235
|
selector: 'button[type="submit"]'
|
|
198
236
|
waitFor: '.dashboard-container'
|
|
199
237
|
assertions:
|
|
200
238
|
- type: cookie
|
|
201
|
-
selector: remember_me
|
|
202
|
-
contains: 'true'
|
|
239
|
+
selector: remember_me # Cookie 名称
|
|
240
|
+
contains: 'true' # Cookie 值包含 true
|
|
203
241
|
|
|
204
242
|
# ── 场景9: 退出登录 ──
|
|
243
|
+
# 说明: 点击用户头像展开菜单 → 点击退出 → 验证跳回登录页
|
|
205
244
|
- name: 登录-退出登录
|
|
206
245
|
description: 模块:auth 页面:/dashboard
|
|
207
246
|
actions:
|
|
208
247
|
- type: navigate
|
|
209
|
-
value: /dashboard
|
|
248
|
+
value: /dashboard # 已登录后的首页
|
|
210
249
|
- type: click
|
|
211
|
-
selector: '.user-avatar'
|
|
250
|
+
selector: '.user-avatar' # 用户头像/菜单触发器
|
|
212
251
|
- type: click
|
|
213
|
-
selector: '.logout-btn'
|
|
252
|
+
selector: '.logout-btn' # 退出按钮
|
|
214
253
|
- type: wait
|
|
215
|
-
delay: 1000
|
|
254
|
+
delay: 1000 # 等待跳转
|
|
216
255
|
assertions:
|
|
217
256
|
- type: url
|
|
218
|
-
contains: /login
|
|
257
|
+
contains: /login # 断言回到登录页
|
|
219
258
|
- type: visible
|
|
220
|
-
selector: '.login-form'
|
|
259
|
+
selector: '.login-form' # 断言登录表单可见
|
|
221
260
|
|
|
222
|
-
#
|
|
223
|
-
#
|
|
224
|
-
#
|
|
261
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
262
|
+
# 二、用户中心 (user)
|
|
263
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
225
264
|
|
|
226
|
-
# ── 场景10:
|
|
265
|
+
# ── 场景10: 修改个人资料 ──
|
|
266
|
+
# 说明: 修改昵称、性别、邮箱,点击保存
|
|
267
|
+
# ⚠️ 前置条件: 用户已登录。如需登录态,参考文件开头「方式A」把「场景13: Token注入」放第一个
|
|
268
|
+
# select 说明: value 填 option 的 value 属性(不是显示文本)
|
|
227
269
|
- name: 用户-修改个人资料
|
|
228
270
|
description: 模块:user 页面:/profile/edit
|
|
229
271
|
actions:
|
|
230
272
|
- type: navigate
|
|
231
|
-
value: /profile/edit
|
|
273
|
+
value: /profile/edit # 个人资料编辑页
|
|
232
274
|
- type: fill
|
|
233
|
-
selector: 'input[name="nickname"]'
|
|
234
|
-
value: 新昵称测试
|
|
275
|
+
selector: 'input[name="nickname"]' # 昵称输入框
|
|
276
|
+
value: 新昵称测试 # 新昵称
|
|
235
277
|
- type: select
|
|
236
|
-
selector: 'select[name="gender"]'
|
|
237
|
-
value: female
|
|
278
|
+
selector: 'select[name="gender"]' # 性别下拉框
|
|
279
|
+
value: female # option 的 value 值(如 female/male/unknown)
|
|
238
280
|
- type: fill
|
|
239
|
-
selector: 'input[name="email"]'
|
|
240
|
-
value: 'newemail@example.com'
|
|
281
|
+
selector: 'input[name="email"]' # 邮箱输入框
|
|
282
|
+
value: 'newemail@example.com' # 新邮箱
|
|
241
283
|
- type: click
|
|
242
|
-
selector: '.save-profile-btn'
|
|
284
|
+
selector: '.save-profile-btn' # 保存按钮
|
|
243
285
|
- type: wait
|
|
244
|
-
delay: 1500
|
|
286
|
+
delay: 1500 # 等待保存接口返回
|
|
245
287
|
assertions:
|
|
246
288
|
- type: visible
|
|
247
|
-
selector: '.toast-success'
|
|
289
|
+
selector: '.toast-success' # 成功提示元素
|
|
248
290
|
- type: text
|
|
249
291
|
selector: '.toast-success'
|
|
250
|
-
contains: 保存成功
|
|
292
|
+
contains: 保存成功 # 预期提示文案
|
|
251
293
|
|
|
252
|
-
# ── 场景11:
|
|
294
|
+
# ── 场景11: 上传头像 ──
|
|
295
|
+
# 说明: 选择本地图片文件上传,验证预览图更新
|
|
296
|
+
# upload 说明: value 为本地文件路径(相对项目根目录或绝对路径)
|
|
253
297
|
- name: 用户-上传头像
|
|
254
298
|
description: 模块:user 页面:/profile/edit 头像上传
|
|
255
299
|
actions:
|
|
256
300
|
- type: navigate
|
|
257
301
|
value: /profile/edit
|
|
258
302
|
- type: upload
|
|
259
|
-
selector: 'input[type="file"].avatar-upload'
|
|
260
|
-
value: ./fixtures/test-avatar.png
|
|
303
|
+
selector: 'input[type="file"].avatar-upload' # 文件输入框
|
|
304
|
+
value: ./fixtures/test-avatar.png # 本地图片路径(确保文件存在)
|
|
261
305
|
- type: wait
|
|
262
|
-
delay: 2000
|
|
306
|
+
delay: 2000 # 等待上传完成
|
|
263
307
|
assertions:
|
|
264
308
|
- type: visible
|
|
265
|
-
selector: '.avatar-preview img'
|
|
309
|
+
selector: '.avatar-preview img' # 头像预览图
|
|
266
310
|
- type: attribute
|
|
267
311
|
selector: '.avatar-preview img'
|
|
268
|
-
attribute: src
|
|
269
|
-
contains: avatar
|
|
312
|
+
attribute: src # 断言 src 属性已更新
|
|
313
|
+
contains: avatar # 包含 avatar 路径
|
|
270
314
|
|
|
271
315
|
# ── 场景12: 修改密码 ──
|
|
316
|
+
# 说明: 填写旧密码 + 新密码 + 确认密码,点击修改
|
|
272
317
|
- name: 用户-修改密码
|
|
273
318
|
description: 模块:user 页面:/profile/security
|
|
274
319
|
actions:
|
|
275
320
|
- type: navigate
|
|
276
|
-
value: /profile/security
|
|
321
|
+
value: /profile/security # 安全设置页
|
|
277
322
|
- type: fill
|
|
278
|
-
selector: 'input[name="oldPassword"]'
|
|
279
|
-
value:
|
|
323
|
+
selector: 'input[name="oldPassword"]' # 旧密码
|
|
324
|
+
value: OldPass123 # 当前密码
|
|
280
325
|
- type: fill
|
|
281
|
-
selector: 'input[name="newPassword"]'
|
|
282
|
-
value:
|
|
326
|
+
selector: 'input[name="newPassword"]' # 新密码
|
|
327
|
+
value: NewPass456 # 新密码
|
|
283
328
|
- type: fill
|
|
284
|
-
selector: 'input[name="confirmPassword"]'
|
|
285
|
-
value:
|
|
329
|
+
selector: 'input[name="confirmPassword"]' # 确认密码
|
|
330
|
+
value: NewPass456 # 与新密码保持一致
|
|
286
331
|
- type: click
|
|
287
|
-
selector: '.change-password-btn'
|
|
332
|
+
selector: '.change-password-btn' # 确认修改按钮
|
|
288
333
|
- type: wait
|
|
289
334
|
delay: 1500
|
|
290
335
|
assertions:
|
|
@@ -294,100 +339,107 @@ scenarios:
|
|
|
294
339
|
selector: '.toast-success'
|
|
295
340
|
contains: 密码修改成功
|
|
296
341
|
|
|
297
|
-
# ── 场景13: Token
|
|
342
|
+
# ── 场景13: Token 注入免登录 ──
|
|
343
|
+
# 说明: 直接通过 localStorage 注入 JWT,跳过登录流程
|
|
344
|
+
# ⚠️ 核心用途: 作为「前置场景」放在业务场景(场景10-42)之前,为后续场景提供登录态
|
|
345
|
+
# 适用场景: 测试环境已有固定账号,不需重复测试登录。把这个场景放第一个,后面所有场景共享登录态
|
|
298
346
|
- name: 用户-Token注入免登录
|
|
299
347
|
description: 模块:user 页面:/profile Token注入方式
|
|
300
348
|
actions:
|
|
301
349
|
- type: localStorage
|
|
302
|
-
selector: accessToken
|
|
303
|
-
value: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
|
350
|
+
selector: accessToken # localStorage key 名
|
|
351
|
+
value: 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwMDg2fQ.test' # JWT Token
|
|
304
352
|
- type: localStorage
|
|
305
|
-
selector: refreshToken
|
|
306
|
-
value: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
|
353
|
+
selector: refreshToken # 同时注入 refreshToken
|
|
354
|
+
value: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoicmVmcmVzaCJ9.test'
|
|
307
355
|
- type: navigate
|
|
308
|
-
value: /profile
|
|
356
|
+
value: /profile # 直接访问需要登录的页面
|
|
309
357
|
- type: wait
|
|
310
358
|
delay: 800
|
|
311
359
|
assertions:
|
|
312
360
|
- type: visible
|
|
313
|
-
selector: '.profile-header'
|
|
361
|
+
selector: '.profile-header' # 断言个人中心头部可见
|
|
314
362
|
- type: text
|
|
315
|
-
selector: '.username'
|
|
363
|
+
selector: '.username' # 断言显示用户名
|
|
316
364
|
contains: test_user
|
|
317
365
|
|
|
318
|
-
#
|
|
319
|
-
# 三、商品模块 (product)
|
|
320
|
-
#
|
|
366
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
367
|
+
# 三、商品模块 (product)
|
|
368
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
321
369
|
|
|
322
370
|
# ── 场景14: 商品列表浏览 ──
|
|
371
|
+
# 说明: 进入商品列表 → 滚动到底部 → 验证商品数量
|
|
323
372
|
- name: 商品-列表浏览
|
|
324
373
|
description: 模块:product 页面:/products
|
|
325
374
|
actions:
|
|
326
375
|
- type: navigate
|
|
327
|
-
value: /products
|
|
376
|
+
value: /products # 商品列表页
|
|
328
377
|
- type: scroll
|
|
329
|
-
selector: bottom
|
|
378
|
+
selector: bottom # 滚动到页面底部(触发懒加载)
|
|
330
379
|
- type: wait
|
|
331
380
|
delay: 1000
|
|
332
381
|
assertions:
|
|
333
382
|
- type: visible
|
|
334
|
-
selector: '.product-list'
|
|
383
|
+
selector: '.product-list' # 商品列表容器
|
|
335
384
|
- type: count
|
|
336
|
-
selector: '.product-item'
|
|
337
|
-
count: 10
|
|
385
|
+
selector: '.product-item' # 商品卡片
|
|
386
|
+
count: 10 # 预期至少 10 个商品
|
|
338
387
|
|
|
339
388
|
# ── 场景15: 商品搜索 ──
|
|
389
|
+
# 说明: 输入关键词 → 按回车 → 验证搜索结果
|
|
340
390
|
- name: 商品-搜索
|
|
341
391
|
description: 模块:product 页面:/products 搜索功能
|
|
342
392
|
actions:
|
|
343
393
|
- type: navigate
|
|
344
394
|
value: /products
|
|
345
395
|
- type: fill
|
|
346
|
-
selector: 'input[name="keyword"]'
|
|
347
|
-
value: iPhone
|
|
396
|
+
selector: 'input[name="keyword"]' # 搜索框
|
|
397
|
+
value: iPhone # 搜索关键词
|
|
348
398
|
- type: press
|
|
349
|
-
key: Enter
|
|
399
|
+
key: Enter # 按回车触发搜索
|
|
350
400
|
- type: wait
|
|
351
|
-
delay: 1500
|
|
401
|
+
delay: 1500 # 等待搜索结果返回
|
|
352
402
|
assertions:
|
|
353
403
|
- type: visible
|
|
354
|
-
selector: '.search-result-list'
|
|
404
|
+
selector: '.search-result-list' # 搜索结果列表
|
|
355
405
|
- type: count
|
|
356
406
|
selector: '.product-item'
|
|
357
|
-
count: 3
|
|
407
|
+
count: 3 # 预期至少 3 个结果
|
|
358
408
|
|
|
359
409
|
# ── 场景16: 商品详情 ──
|
|
410
|
+
# 说明: 点击商品标题进入详情页,验证详情信息展示
|
|
360
411
|
- name: 商品-详情页
|
|
361
412
|
description: 模块:product 页面:/products/:id
|
|
362
413
|
actions:
|
|
363
414
|
- type: navigate
|
|
364
415
|
value: /products
|
|
365
416
|
- type: click
|
|
366
|
-
selector: '.product-item:first-child .product-title'
|
|
417
|
+
selector: '.product-item:first-child .product-title' # 点击第一个商品的标题
|
|
367
418
|
- type: wait
|
|
368
419
|
delay: 1000
|
|
369
420
|
assertions:
|
|
370
421
|
- type: visible
|
|
371
|
-
selector: '.product-detail'
|
|
422
|
+
selector: '.product-detail' # 详情页容器
|
|
372
423
|
- type: text
|
|
373
|
-
selector: '.product-name'
|
|
374
|
-
contains: iPhone
|
|
424
|
+
selector: '.product-name' # 商品名称
|
|
425
|
+
contains: iPhone # 包含搜索关键词
|
|
375
426
|
- type: visible
|
|
376
|
-
selector: '.product-price'
|
|
427
|
+
selector: '.product-price' # 价格元素
|
|
377
428
|
|
|
378
|
-
# ── 场景17:
|
|
429
|
+
# ── 场景17: 商品多条件筛选 ──
|
|
430
|
+
# 说明: 同时选择分类 + 库存状态,验证筛选结果
|
|
379
431
|
- name: 商品-多条件筛选
|
|
380
432
|
description: 模块:product 页面:/products 筛选功能
|
|
381
433
|
actions:
|
|
382
434
|
- type: navigate
|
|
383
435
|
value: /products
|
|
384
436
|
- type: select
|
|
385
|
-
selector: 'select[name="category"]'
|
|
386
|
-
value: electronics
|
|
437
|
+
selector: 'select[name="category"]' # 分类下拉框
|
|
438
|
+
value: electronics # option value(如 electronics/clothing/food)
|
|
387
439
|
- type: check
|
|
388
|
-
selector: 'input[value="inStock"]'
|
|
440
|
+
selector: 'input[value="inStock"]' # 仅看有库存(复选框)
|
|
389
441
|
- type: click
|
|
390
|
-
selector: '.filter-submit-btn'
|
|
442
|
+
selector: '.filter-submit-btn' # 确认筛选按钮
|
|
391
443
|
- type: wait
|
|
392
444
|
delay: 1200
|
|
393
445
|
assertions:
|
|
@@ -395,155 +447,162 @@ scenarios:
|
|
|
395
447
|
selector: '.product-list'
|
|
396
448
|
- type: count
|
|
397
449
|
selector: '.product-item'
|
|
398
|
-
count: 5
|
|
450
|
+
count: 5 # 筛选后预期 5 个商品
|
|
399
451
|
|
|
400
452
|
# ── 场景18: 商品收藏 ──
|
|
453
|
+
# 说明: 点击收藏按钮,验证按钮颜色变为红色(已收藏状态)
|
|
401
454
|
- name: 商品-收藏/取消收藏
|
|
402
455
|
description: 模块:product 页面:/products/:id 收藏功能
|
|
403
456
|
actions:
|
|
404
457
|
- type: navigate
|
|
405
|
-
value: /products/123
|
|
458
|
+
value: /products/123 # 某个商品详情页(123 为商品 ID)
|
|
406
459
|
- type: click
|
|
407
|
-
selector: '.favorite-btn'
|
|
460
|
+
selector: '.favorite-btn' # 收藏按钮(空心)
|
|
408
461
|
- type: wait
|
|
409
462
|
delay: 500
|
|
410
463
|
assertions:
|
|
411
464
|
- type: style
|
|
412
|
-
selector: '.favorite-btn'
|
|
413
|
-
style: color
|
|
414
|
-
value: rgb(255, 77, 79)
|
|
465
|
+
selector: '.favorite-btn' # 断言按钮样式变化
|
|
466
|
+
style: color # CSS 属性: 颜色
|
|
467
|
+
value: rgb(255, 77, 79) # 预期变为红色(根据实际样式调整)
|
|
415
468
|
|
|
416
|
-
#
|
|
417
|
-
#
|
|
418
|
-
#
|
|
469
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
470
|
+
# 四、购物车 (cart)
|
|
471
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
419
472
|
|
|
420
473
|
# ── 场景19: 添加商品到购物车 ──
|
|
474
|
+
# 说明: 商品详情页点击加购,验证购物车角标数字更新
|
|
421
475
|
- name: 购物车-添加商品
|
|
422
476
|
description: 模块:cart 页面:/products/:id
|
|
423
477
|
actions:
|
|
424
478
|
- type: navigate
|
|
425
|
-
value: /products/123
|
|
479
|
+
value: /products/123 # 商品详情页
|
|
426
480
|
- type: click
|
|
427
|
-
selector: '.add-to-cart-btn'
|
|
481
|
+
selector: '.add-to-cart-btn' # 加入购物车按钮
|
|
428
482
|
- type: wait
|
|
429
|
-
delay: 800
|
|
483
|
+
delay: 800 # 等待加购动画/接口
|
|
430
484
|
assertions:
|
|
431
485
|
- type: visible
|
|
432
|
-
selector: '.cart-badge'
|
|
486
|
+
selector: '.cart-badge' # 购物车角标
|
|
433
487
|
- type: text
|
|
434
488
|
selector: '.cart-badge'
|
|
435
|
-
contains: '1'
|
|
489
|
+
contains: '1' # 角标显示 1
|
|
436
490
|
|
|
437
|
-
# ── 场景20:
|
|
491
|
+
# ── 场景20: 修改购物车商品数量 ──
|
|
492
|
+
# 说明: 修改数量输入框 → 失去焦点触发计算 → 验证小计金额更新
|
|
438
493
|
- name: 购物车-修改数量
|
|
439
494
|
description: 模块:cart 页面:/cart
|
|
440
495
|
actions:
|
|
441
496
|
- type: navigate
|
|
442
|
-
value: /cart
|
|
497
|
+
value: /cart # 购物车页面
|
|
443
498
|
- type: fill
|
|
444
|
-
selector: '.cart-item:first-child .quantity-input'
|
|
445
|
-
value: '3'
|
|
499
|
+
selector: '.cart-item:first-child .quantity-input' # 第一个商品的数量输入框
|
|
500
|
+
value: '3' # 修改为 3 件
|
|
446
501
|
- type: blur
|
|
447
|
-
selector: '.cart-item:first-child .quantity-input'
|
|
502
|
+
selector: '.cart-item:first-child .quantity-input' # 触发 blur 事件(触发价格重算)
|
|
448
503
|
- type: wait
|
|
449
504
|
delay: 800
|
|
450
505
|
assertions:
|
|
451
506
|
- type: text
|
|
452
|
-
selector: '.cart-item:first-child .subtotal'
|
|
453
|
-
contains: '¥'
|
|
507
|
+
selector: '.cart-item:first-child .subtotal' # 小计金额
|
|
508
|
+
contains: '¥' # 包含人民币符号(断言金额已更新)
|
|
454
509
|
|
|
455
|
-
# ── 场景21:
|
|
510
|
+
# ── 场景21: 购物车去结算 ──
|
|
511
|
+
# 说明: 勾选商品 → 点击结算 → 验证跳转到结算页
|
|
456
512
|
- name: 购物车-去结算
|
|
457
513
|
description: 模块:cart 页面:/cart
|
|
458
514
|
actions:
|
|
459
515
|
- type: navigate
|
|
460
516
|
value: /cart
|
|
461
517
|
- type: check
|
|
462
|
-
selector: '.cart-item:first-child .item-checkbox'
|
|
518
|
+
selector: '.cart-item:first-child .item-checkbox' # 勾选第一个商品
|
|
463
519
|
- type: click
|
|
464
|
-
selector: '.checkout-btn'
|
|
520
|
+
selector: '.checkout-btn' # 去结算按钮
|
|
465
521
|
- type: wait
|
|
466
522
|
delay: 1500
|
|
467
523
|
assertions:
|
|
468
524
|
- type: url
|
|
469
|
-
contains: /checkout
|
|
525
|
+
contains: /checkout # 断言跳转到结算页
|
|
470
526
|
- type: visible
|
|
471
|
-
selector: '.checkout-form'
|
|
527
|
+
selector: '.checkout-form' # 结算表单
|
|
472
528
|
|
|
473
|
-
# ── 场景22:
|
|
529
|
+
# ── 场景22: 删除购物车商品 ──
|
|
530
|
+
# 说明: 点击删除按钮,验证商品被移除
|
|
474
531
|
- name: 购物车-删除商品
|
|
475
532
|
description: 模块:cart 页面:/cart
|
|
476
533
|
actions:
|
|
477
534
|
- type: navigate
|
|
478
535
|
value: /cart
|
|
479
536
|
- type: click
|
|
480
|
-
selector: '.cart-item:first-child .delete-btn'
|
|
537
|
+
selector: '.cart-item:first-child .delete-btn' # 删除按钮
|
|
481
538
|
- type: wait
|
|
482
539
|
delay: 800
|
|
483
540
|
assertions:
|
|
484
541
|
- type: count
|
|
485
|
-
selector: '.cart-item'
|
|
486
|
-
count: 0
|
|
542
|
+
selector: '.cart-item' # 断言购物车商品数量
|
|
543
|
+
count: 0 # 预期为 0(空购物车)
|
|
487
544
|
|
|
488
|
-
#
|
|
489
|
-
# 五、订单模块 (order)
|
|
490
|
-
#
|
|
545
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
546
|
+
# 五、订单模块 (order)
|
|
547
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
491
548
|
|
|
492
|
-
# ── 场景23:
|
|
549
|
+
# ── 场景23: 创建订单 ──
|
|
550
|
+
# 说明: 填写收货信息 → 提交订单 → 验证订单创建成功
|
|
493
551
|
- name: 订单-创建订单
|
|
494
552
|
description: 模块:order 页面:/checkout
|
|
495
553
|
actions:
|
|
496
554
|
- type: navigate
|
|
497
|
-
value: /checkout
|
|
555
|
+
value: /checkout # 结算页
|
|
498
556
|
- type: fill
|
|
499
|
-
selector: 'input[name="receiverName"]'
|
|
500
|
-
value: 张三
|
|
557
|
+
selector: 'input[name="receiverName"]' # 收货人姓名
|
|
558
|
+
value: 张三 # 真实姓名或测试姓名
|
|
501
559
|
- type: fill
|
|
502
|
-
selector: 'input[name="phone"]'
|
|
560
|
+
selector: 'input[name="phone"]' # 手机号
|
|
503
561
|
value: '13800138000'
|
|
504
562
|
- type: fill
|
|
505
|
-
selector: 'input[name="address"]'
|
|
506
|
-
value: 北京市朝阳区测试街道123号
|
|
563
|
+
selector: 'input[name="address"]' # 详细地址
|
|
564
|
+
value: 北京市朝阳区测试街道123号 # 测试地址
|
|
507
565
|
- type: click
|
|
508
|
-
selector: '.submit-order-btn'
|
|
566
|
+
selector: '.submit-order-btn' # 提交订单按钮
|
|
509
567
|
- type: wait
|
|
510
|
-
delay: 2000
|
|
568
|
+
delay: 2000 # 等待订单创建接口
|
|
511
569
|
assertions:
|
|
512
570
|
- type: url
|
|
513
|
-
contains: /order/success
|
|
571
|
+
contains: /order/success # 断言跳转到成功页
|
|
514
572
|
- type: visible
|
|
515
|
-
selector: '.order-success'
|
|
573
|
+
selector: '.order-success' # 成功提示
|
|
516
574
|
- type: text
|
|
517
575
|
selector: '.order-success'
|
|
518
576
|
contains: 订单提交成功
|
|
519
577
|
|
|
520
578
|
# ── 场景24: 订单支付 ──
|
|
579
|
+
# 说明: 选择支付方式 → 确认支付 → 等待支付接口返回 → 验证成功
|
|
521
580
|
- name: 订单-支付订单
|
|
522
581
|
description: 模块:order 页面:/pay/:orderId
|
|
523
582
|
actions:
|
|
524
583
|
- type: navigate
|
|
525
|
-
value: /pay/ORD202409110001
|
|
584
|
+
value: /pay/ORD202409110001 # 支付页(ORD202409110001 为订单号)
|
|
526
585
|
- type: click
|
|
527
|
-
selector: '.pay-method-wechat'
|
|
586
|
+
selector: '.pay-method-wechat' # 选择微信支付
|
|
528
587
|
- type: click
|
|
529
|
-
selector: '.confirm-pay-btn'
|
|
588
|
+
selector: '.confirm-pay-btn' # 确认支付按钮
|
|
530
589
|
- type: waitForResponse
|
|
531
|
-
selector: '/api/pay/create'
|
|
532
|
-
timeout: 10000
|
|
590
|
+
selector: '/api/pay/create' # 等待支付创建接口返回
|
|
591
|
+
timeout: 10000 # 支付接口较慢,超时设 10 秒
|
|
533
592
|
- type: wait
|
|
534
593
|
delay: 2000
|
|
535
594
|
assertions:
|
|
536
595
|
- type: url
|
|
537
|
-
contains: /pay/success
|
|
596
|
+
contains: /pay/success # 断言跳转到支付成功页
|
|
538
597
|
- type: visible
|
|
539
598
|
selector: '.pay-success'
|
|
540
599
|
|
|
541
|
-
# ── 场景25:
|
|
600
|
+
# ── 场景25: 订单列表查询 ──
|
|
542
601
|
- name: 订单-列表查询
|
|
543
602
|
description: 模块:order 页面:/orders
|
|
544
603
|
actions:
|
|
545
604
|
- type: navigate
|
|
546
|
-
value: /orders
|
|
605
|
+
value: /orders # 订单列表页
|
|
547
606
|
- type: wait
|
|
548
607
|
delay: 1000
|
|
549
608
|
assertions:
|
|
@@ -551,54 +610,55 @@ scenarios:
|
|
|
551
610
|
selector: '.order-list'
|
|
552
611
|
- type: count
|
|
553
612
|
selector: '.order-item'
|
|
554
|
-
count: 5
|
|
613
|
+
count: 5 # 预期至少 5 个订单
|
|
555
614
|
|
|
556
|
-
# ── 场景26:
|
|
615
|
+
# ── 场景26: 订单详情查看 ──
|
|
557
616
|
- name: 订单-详情查看
|
|
558
617
|
description: 模块:order 页面:/orders/:id
|
|
559
618
|
actions:
|
|
560
619
|
- type: navigate
|
|
561
620
|
value: /orders
|
|
562
621
|
- type: click
|
|
563
|
-
selector: '.order-item:first-child .view-detail-btn'
|
|
622
|
+
selector: '.order-item:first-child .view-detail-btn' # 查看第一个订单详情
|
|
564
623
|
- type: wait
|
|
565
624
|
delay: 800
|
|
566
625
|
assertions:
|
|
567
626
|
- type: visible
|
|
568
627
|
selector: '.order-detail'
|
|
569
628
|
- type: text
|
|
570
|
-
selector: '.order-status'
|
|
571
|
-
contains: 待付款
|
|
629
|
+
selector: '.order-status' # 订单状态
|
|
630
|
+
contains: 待付款 # 预期状态文案
|
|
572
631
|
|
|
573
|
-
# ── 场景27:
|
|
632
|
+
# ── 场景27: 取消订单 ──
|
|
633
|
+
# 说明: 点击取消 → 确认取消 → 验证状态变为已取消
|
|
574
634
|
- name: 订单-取消订单
|
|
575
635
|
description: 模块:order 页面:/orders/:id
|
|
576
636
|
actions:
|
|
577
637
|
- type: navigate
|
|
578
|
-
value: /orders/ORD202409110001
|
|
638
|
+
value: /orders/ORD202409110001 # 某个待付款订单
|
|
579
639
|
- type: click
|
|
580
|
-
selector: '.cancel-order-btn'
|
|
640
|
+
selector: '.cancel-order-btn' # 取消订单按钮
|
|
581
641
|
- type: wait
|
|
582
|
-
delay: 500
|
|
642
|
+
delay: 500 # 等待确认弹窗
|
|
583
643
|
- type: click
|
|
584
|
-
selector: '.confirm-cancel-btn'
|
|
644
|
+
selector: '.confirm-cancel-btn' # 确认取消按钮
|
|
585
645
|
- type: wait
|
|
586
|
-
delay: 1500
|
|
646
|
+
delay: 1500 # 等待取消接口返回
|
|
587
647
|
assertions:
|
|
588
648
|
- type: text
|
|
589
649
|
selector: '.order-status'
|
|
590
|
-
contains: 已取消
|
|
650
|
+
contains: 已取消 # 状态变为已取消
|
|
591
651
|
|
|
592
|
-
#
|
|
593
|
-
#
|
|
594
|
-
#
|
|
652
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
653
|
+
# 六、消息通知 (notification)
|
|
654
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
595
655
|
|
|
596
|
-
# ── 场景28:
|
|
656
|
+
# ── 场景28: 消息列表查看 ──
|
|
597
657
|
- name: 消息-列表查看
|
|
598
658
|
description: 模块:notification 页面:/notifications
|
|
599
659
|
actions:
|
|
600
660
|
- type: navigate
|
|
601
|
-
value: /notifications
|
|
661
|
+
value: /notifications # 消息中心
|
|
602
662
|
- type: wait
|
|
603
663
|
delay: 1000
|
|
604
664
|
assertions:
|
|
@@ -606,98 +666,104 @@ scenarios:
|
|
|
606
666
|
selector: '.notification-list'
|
|
607
667
|
- type: count
|
|
608
668
|
selector: '.notification-item'
|
|
609
|
-
count: 3
|
|
669
|
+
count: 3 # 预期 3 条未读消息
|
|
610
670
|
|
|
611
671
|
# ── 场景29: 消息标记已读 ──
|
|
672
|
+
# 说明: 点击消息 → 验证字体从粗体变为正常(已读状态)
|
|
612
673
|
- name: 消息-标记已读
|
|
613
674
|
description: 模块:notification 页面:/notifications
|
|
614
675
|
actions:
|
|
615
676
|
- type: navigate
|
|
616
677
|
value: /notifications
|
|
617
678
|
- type: click
|
|
618
|
-
selector: '.notification-item:first-child'
|
|
679
|
+
selector: '.notification-item:first-child' # 点击第一条消息
|
|
619
680
|
- type: wait
|
|
620
681
|
delay: 500
|
|
621
682
|
assertions:
|
|
622
683
|
- type: style
|
|
623
684
|
selector: '.notification-item:first-child'
|
|
624
|
-
style: font-weight
|
|
625
|
-
value: '400'
|
|
685
|
+
style: font-weight # CSS 属性: 字重
|
|
686
|
+
value: '400' # 400 = normal(已读),700 = bold(未读)
|
|
626
687
|
|
|
627
|
-
#
|
|
628
|
-
#
|
|
629
|
-
#
|
|
688
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
689
|
+
# 七、后台管理 (admin)
|
|
690
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
630
691
|
|
|
631
692
|
# ── 场景30: 用户管理列表 ──
|
|
632
693
|
- name: 后台-用户管理
|
|
633
694
|
description: 模块:admin 页面:/admin/users
|
|
634
695
|
actions:
|
|
635
696
|
- type: navigate
|
|
636
|
-
value: /admin/users
|
|
697
|
+
value: /admin/users # 后台用户管理页
|
|
637
698
|
- type: wait
|
|
638
|
-
delay: 1500
|
|
699
|
+
delay: 1500 # 等待表格数据加载
|
|
639
700
|
assertions:
|
|
640
701
|
- type: visible
|
|
641
|
-
selector: '.user-table'
|
|
702
|
+
selector: '.user-table' # 用户表格
|
|
642
703
|
- type: count
|
|
643
|
-
selector: '.user-table tbody tr'
|
|
644
|
-
count: 10
|
|
704
|
+
selector: '.user-table tbody tr' # 表格行数
|
|
705
|
+
count: 10 # 预期 10 条用户数据
|
|
645
706
|
|
|
646
707
|
# ── 场景31: 数据报表(iframe 嵌入) ──
|
|
708
|
+
# 说明: 后台报表通常用 iframe 嵌入第三方 BI 工具(如帆软、Tableau)
|
|
647
709
|
- name: 后台-数据报表iframe
|
|
648
710
|
description: 模块:admin 页面:/admin/reports iframe嵌入
|
|
649
711
|
actions:
|
|
650
712
|
- type: navigate
|
|
651
|
-
value: /admin/reports
|
|
713
|
+
value: /admin/reports # 报表页面
|
|
652
714
|
- type: iframe
|
|
653
|
-
selector: '#report-frame'
|
|
715
|
+
selector: '#report-frame' # 报表 iframe
|
|
654
716
|
- type: wait
|
|
655
|
-
delay: 2000
|
|
717
|
+
delay: 2000 # 等待 iframe 内报表加载
|
|
656
718
|
assertions:
|
|
657
719
|
- type: visible
|
|
658
|
-
selector: '.chart-container'
|
|
720
|
+
selector: '.chart-container' # 图表容器(在 iframe 内)
|
|
659
721
|
- type: count
|
|
660
|
-
selector: '.chart-item'
|
|
661
|
-
count: 4
|
|
722
|
+
selector: '.chart-item' # 图表数量
|
|
723
|
+
count: 4 # 预期 4 个图表
|
|
662
724
|
|
|
663
725
|
# ── 场景32: 拖拽排序 ──
|
|
726
|
+
# 说明: 拖拽分类/菜单项调整顺序
|
|
664
727
|
- name: 后台-拖拽排序
|
|
665
728
|
description: 模块:admin 页面:/admin/categories 拖拽排序
|
|
666
729
|
actions:
|
|
667
730
|
- type: navigate
|
|
668
|
-
value: /admin/categories
|
|
731
|
+
value: /admin/categories # 分类管理页
|
|
669
732
|
- type: drag
|
|
670
|
-
selector: '.category-item:first-child'
|
|
671
|
-
toSelector: '.category-item:last-child'
|
|
733
|
+
selector: '.category-item:first-child' # 拖拽第一个分类
|
|
734
|
+
toSelector: '.category-item:last-child' # 放到最后一个位置
|
|
672
735
|
- type: wait
|
|
673
|
-
delay: 1000
|
|
736
|
+
delay: 1000 # 等待保存接口
|
|
674
737
|
assertions:
|
|
675
738
|
- type: text
|
|
676
|
-
selector: '.category-item:last-child .category-name'
|
|
677
|
-
contains: 原第一个分类
|
|
739
|
+
selector: '.category-item:last-child .category-name' # 断言最后一个分类的名称
|
|
740
|
+
contains: 原第一个分类 # 确认拖拽成功
|
|
678
741
|
|
|
679
|
-
#
|
|
680
|
-
#
|
|
681
|
-
#
|
|
742
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
743
|
+
# 八、通用能力
|
|
744
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
682
745
|
|
|
683
746
|
# ── 场景33: Cookie 注入免登录 ──
|
|
747
|
+
# 说明: 直接注入 session Cookie,跳过登录页,适合批量测试业务模块
|
|
748
|
+
# ⚠️ 核心用途: 作为「前置场景」放在业务场景(场景10-42)之前,为后续场景提供登录态
|
|
749
|
+
# 如何获取 Cookie: 在浏览器开发者工具 → Application → Cookies 中复制
|
|
684
750
|
- name: 通用-Cookie注入免登录
|
|
685
751
|
description: 模块:common Cookie注入跳过登录
|
|
686
752
|
actions:
|
|
687
753
|
- type: cookie
|
|
688
|
-
selector: session_token
|
|
689
|
-
value: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
|
|
690
|
-
domain: example.com
|
|
691
|
-
path: /
|
|
692
|
-
secure: true
|
|
693
|
-
httpOnly: true
|
|
754
|
+
selector: session_token # Cookie 名称
|
|
755
|
+
value: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjEwMDg2fQ.test' # Cookie 值
|
|
756
|
+
domain: example.com # Cookie 域名(必须与测试站点一致)
|
|
757
|
+
path: / # Cookie 路径
|
|
758
|
+
secure: true # 仅 HTTPS 传输
|
|
759
|
+
httpOnly: true # 禁止 JS 读取
|
|
694
760
|
- type: cookie
|
|
695
|
-
selector: user_id
|
|
761
|
+
selector: user_id # 同时注入 user_id
|
|
696
762
|
value: '10086'
|
|
697
763
|
domain: example.com
|
|
698
764
|
path: /
|
|
699
765
|
- type: navigate
|
|
700
|
-
value: /dashboard
|
|
766
|
+
value: /dashboard # 直接访问需登录页面
|
|
701
767
|
- type: wait
|
|
702
768
|
delay: 1000
|
|
703
769
|
assertions:
|
|
@@ -714,21 +780,22 @@ scenarios:
|
|
|
714
780
|
- type: navigate
|
|
715
781
|
value: /
|
|
716
782
|
- type: scroll
|
|
717
|
-
selector: bottom
|
|
783
|
+
selector: bottom # 滚动到底部
|
|
718
784
|
- type: wait
|
|
719
785
|
delay: 1500
|
|
720
786
|
- type: scroll
|
|
721
|
-
selector: bottom
|
|
787
|
+
selector: bottom # 再次滚动(触发第二轮加载)
|
|
722
788
|
- type: wait
|
|
723
789
|
delay: 1500
|
|
724
790
|
assertions:
|
|
725
791
|
- type: visible
|
|
726
|
-
selector: '.load-more-btn'
|
|
792
|
+
selector: '.load-more-btn' # 加载更多按钮
|
|
727
793
|
- type: count
|
|
728
|
-
selector: '.product-card'
|
|
729
|
-
count: 20
|
|
794
|
+
selector: '.product-card' # 商品卡片总数
|
|
795
|
+
count: 20 # 预期加载了 20 个商品
|
|
730
796
|
|
|
731
797
|
# ── 场景35: 样式断言 ──
|
|
798
|
+
# 说明: 验证按钮颜色、圆角等样式是否符合设计稿
|
|
732
799
|
- name: 通用-按钮样式检查
|
|
733
800
|
description: 模块:ui 页面:/
|
|
734
801
|
actions:
|
|
@@ -736,39 +803,42 @@ scenarios:
|
|
|
736
803
|
value: /
|
|
737
804
|
assertions:
|
|
738
805
|
- type: style
|
|
739
|
-
selector: '.primary-btn'
|
|
740
|
-
style: background-color
|
|
741
|
-
value: rgb(24, 144, 255)
|
|
806
|
+
selector: '.primary-btn' # 主按钮
|
|
807
|
+
style: background-color # CSS 属性: 背景色
|
|
808
|
+
value: rgb(24, 144, 255) # 预期颜色(根据设计稿调整)
|
|
742
809
|
- type: style
|
|
743
810
|
selector: '.primary-btn'
|
|
744
|
-
style: border-radius
|
|
745
|
-
value: 4px
|
|
811
|
+
style: border-radius # CSS 属性: 圆角
|
|
812
|
+
value: 4px # 预期 4px 圆角
|
|
746
813
|
|
|
747
814
|
# ── 场景36: 视觉对比测试 ──
|
|
815
|
+
# 说明: 截图后与基线图片对比,检测 UI 回归
|
|
816
|
+
# 前置条件: 先执行一次生成基线图片,后续对比以此为准
|
|
748
817
|
- name: 通用-视觉对比测试
|
|
749
818
|
description: 模块:ui 页面:/ 视觉回归
|
|
750
819
|
actions:
|
|
751
820
|
- type: navigate
|
|
752
821
|
value: /
|
|
753
|
-
- type: screenshot
|
|
822
|
+
- type: screenshot # 页面截图
|
|
754
823
|
assertions:
|
|
755
824
|
- type: visual
|
|
756
|
-
selector: 'body'
|
|
757
|
-
threshold: normal
|
|
825
|
+
selector: 'body' # 对比整个页面
|
|
826
|
+
threshold: normal # 阈值: strict/normal/loose
|
|
758
827
|
|
|
759
828
|
# ── 场景37: 键盘操作 ──
|
|
829
|
+
# 说明: 聚焦搜索框 → 输入关键词 → 按回车触发搜索
|
|
760
830
|
- name: 通用-键盘操作
|
|
761
831
|
description: 模块:common 页面:/products 键盘导航
|
|
762
832
|
actions:
|
|
763
833
|
- type: navigate
|
|
764
834
|
value: /products
|
|
765
835
|
- type: focus
|
|
766
|
-
selector: 'input[name="keyword"]'
|
|
836
|
+
selector: 'input[name="keyword"]' # 聚焦搜索框
|
|
767
837
|
- type: fill
|
|
768
838
|
selector: 'input[name="keyword"]'
|
|
769
839
|
value: MacBook
|
|
770
840
|
- type: press
|
|
771
|
-
key: Enter
|
|
841
|
+
key: Enter # 按回车
|
|
772
842
|
- type: wait
|
|
773
843
|
delay: 1500
|
|
774
844
|
assertions:
|
|
@@ -776,37 +846,40 @@ scenarios:
|
|
|
776
846
|
selector: '.search-result-list'
|
|
777
847
|
- type: count
|
|
778
848
|
selector: '.product-item'
|
|
779
|
-
count: 2
|
|
849
|
+
count: 2 # 预期 2 个结果
|
|
780
850
|
|
|
781
851
|
# ── 场景38: hover 效果 ──
|
|
852
|
+
# 说明: 鼠标悬停在商品卡片上,验证阴影/边框等 hover 样式
|
|
782
853
|
- name: 通用-hover效果
|
|
783
854
|
description: 模块:ui 页面:/products hover交互
|
|
784
855
|
actions:
|
|
785
856
|
- type: navigate
|
|
786
857
|
value: /products
|
|
787
858
|
- type: hover
|
|
788
|
-
selector: '.product-item:first-child'
|
|
859
|
+
selector: '.product-item:first-child' # 悬停在第一个商品
|
|
789
860
|
- type: wait
|
|
790
|
-
delay: 500
|
|
861
|
+
delay: 500 # 等待 hover 动画
|
|
791
862
|
assertions:
|
|
792
863
|
- type: style
|
|
793
864
|
selector: '.product-item:first-child'
|
|
794
|
-
style: box-shadow
|
|
795
|
-
contains: rgba
|
|
865
|
+
style: box-shadow # CSS 属性: 阴影
|
|
866
|
+
contains: rgba # 包含 rgba(有阴影)
|
|
796
867
|
|
|
797
868
|
# ── 场景39: 元素隐藏检查 ──
|
|
869
|
+
# 说明: 验证未登录状态下,用户菜单和退出按钮不显示
|
|
798
870
|
- name: 通用-元素隐藏
|
|
799
871
|
description: 模块:ui 页面:/login 未登录状态
|
|
800
872
|
actions:
|
|
801
873
|
- type: navigate
|
|
802
|
-
value: /login
|
|
874
|
+
value: /login # 未登录页面
|
|
803
875
|
assertions:
|
|
804
876
|
- type: hidden
|
|
805
|
-
selector: '.user-menu'
|
|
877
|
+
selector: '.user-menu' # 用户菜单应隐藏
|
|
806
878
|
- type: hidden
|
|
807
|
-
selector: '.logout-btn'
|
|
879
|
+
selector: '.logout-btn' # 退出按钮应隐藏
|
|
808
880
|
|
|
809
881
|
# ── 场景40: 表单值断言 ──
|
|
882
|
+
# 说明: 验证表单字段的默认值是否正确
|
|
810
883
|
- name: 通用-表单值检查
|
|
811
884
|
description: 模块:user 页面:/profile/edit 表单默认值
|
|
812
885
|
actions:
|
|
@@ -814,30 +887,33 @@ scenarios:
|
|
|
814
887
|
value: /profile/edit
|
|
815
888
|
assertions:
|
|
816
889
|
- type: value
|
|
817
|
-
selector: 'input[name="username"]'
|
|
818
|
-
equals: test_user
|
|
890
|
+
selector: 'input[name="username"]' # 断言表单值
|
|
891
|
+
equals: test_user # 精确匹配
|
|
819
892
|
- type: value
|
|
820
893
|
selector: 'input[name="email"]'
|
|
821
|
-
contains: '@'
|
|
894
|
+
contains: '@' # 包含 @ 符号
|
|
822
895
|
|
|
823
896
|
# ── 场景41: JavaScript 注入执行 ──
|
|
897
|
+
# 说明: 执行自定义 JS,如修改全局变量、调用前端方法
|
|
824
898
|
- name: 通用-JS脚本注入
|
|
825
899
|
description: 模块:common 页面:/ JS注入测试
|
|
826
900
|
actions:
|
|
827
901
|
- type: navigate
|
|
828
902
|
value: /
|
|
829
903
|
- type: script
|
|
830
|
-
value: 'window.testFlag = "injected";'
|
|
904
|
+
value: 'window.testFlag = "injected";' # 设置全局变量
|
|
831
905
|
- type: wait
|
|
832
906
|
delay: 500
|
|
833
907
|
- type: script
|
|
834
|
-
value: 'return window.testFlag;'
|
|
908
|
+
value: 'return window.testFlag;' # 读取全局变量(返回给引擎)
|
|
835
909
|
assertions:
|
|
836
910
|
- type: text
|
|
837
911
|
selector: 'body'
|
|
838
|
-
contains: injected
|
|
912
|
+
contains: injected # 断言页面包含 injected 文本
|
|
839
913
|
|
|
840
|
-
# ── 场景42:
|
|
914
|
+
# ── 场景42: 响应式布局测试 ──
|
|
915
|
+
# 说明: 验证移动端下显示移动端导航,隐藏桌面端侧边栏
|
|
916
|
+
# 注意: Playwright 可通过 viewport 配置模拟不同设备尺寸
|
|
841
917
|
- name: 通用-响应式布局
|
|
842
918
|
description: 模块:ui 页面:/ 移动端适配
|
|
843
919
|
actions:
|
|
@@ -847,6 +923,6 @@ scenarios:
|
|
|
847
923
|
delay: 800
|
|
848
924
|
assertions:
|
|
849
925
|
- type: visible
|
|
850
|
-
selector: '.mobile-nav'
|
|
926
|
+
selector: '.mobile-nav' # 移动端导航应可见
|
|
851
927
|
- type: hidden
|
|
852
|
-
selector: '.desktop-sidebar'
|
|
928
|
+
selector: '.desktop-sidebar' # 桌面端侧边栏应隐藏
|