pageqa 0.6.0 → 0.8.0
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 +77 -8
- package/dist/agent.d.ts +11 -1
- package/dist/agent.js +78 -102
- package/dist/agent.js.map +1 -1
- package/dist/bsk/tools.d.ts +61 -0
- package/dist/bsk/tools.js +239 -79
- package/dist/bsk/tools.js.map +1 -1
- package/dist/index.js +177 -10
- package/dist/index.js.map +1 -1
- package/dist/locator.d.ts +105 -0
- package/dist/locator.js +263 -0
- package/dist/locator.js.map +1 -0
- package/dist/record.d.ts +41 -0
- package/dist/record.js +166 -0
- package/dist/record.js.map +1 -0
- package/dist/replay.d.ts +190 -0
- package/dist/replay.js +550 -0
- package/dist/replay.js.map +1 -0
- package/dist/report.d.ts +41 -4
- package/dist/report.js +133 -5
- package/dist/report.js.map +1 -1
- package/dist/snapshot.d.ts +15 -0
- package/dist/snapshot.js +167 -0
- package/dist/snapshot.js.map +1 -0
- package/dist/vars.d.ts +25 -0
- package/dist/vars.js +32 -0
- package/dist/vars.js.map +1 -1
- package/examples/smoke-test-page.html +531 -0
- package/examples/smoke-test.md +43 -0
- package/examples/smoke-test.replay.json +389 -0
- package/package.json +5 -3
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>冒烟测试页面</title>
|
|
7
|
+
<style>
|
|
8
|
+
* {
|
|
9
|
+
margin: 0;
|
|
10
|
+
padding: 0;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
body {
|
|
15
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
+
background-color: #f5f5f5;
|
|
17
|
+
color: #333;
|
|
18
|
+
line-height: 1.6;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.navbar {
|
|
22
|
+
background-color: #fff;
|
|
23
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
24
|
+
padding: 1rem 2rem;
|
|
25
|
+
display: flex;
|
|
26
|
+
gap: 1rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.nav-link {
|
|
30
|
+
text-decoration: none;
|
|
31
|
+
color: #333;
|
|
32
|
+
padding: 0.5rem 1rem;
|
|
33
|
+
border-radius: 4px;
|
|
34
|
+
transition: background-color 0.3s;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.nav-link:hover {
|
|
38
|
+
background-color: #e8e8e8;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.nav-link.active {
|
|
42
|
+
background-color: #007bff;
|
|
43
|
+
color: #fff;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.content {
|
|
47
|
+
max-width: 800px;
|
|
48
|
+
margin: 2rem auto;
|
|
49
|
+
padding: 0 1rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.route-content {
|
|
53
|
+
background-color: #fff;
|
|
54
|
+
padding: 2rem;
|
|
55
|
+
border-radius: 8px;
|
|
56
|
+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
h1, h2 {
|
|
60
|
+
margin-bottom: 1rem;
|
|
61
|
+
color: #007bff;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.form-group {
|
|
65
|
+
margin-bottom: 1.5rem;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
label {
|
|
69
|
+
display: block;
|
|
70
|
+
margin-bottom: 0.5rem;
|
|
71
|
+
font-weight: 600;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
input[type="text"],
|
|
75
|
+
input[type="email"],
|
|
76
|
+
input[type="date"],
|
|
77
|
+
select {
|
|
78
|
+
width: 100%;
|
|
79
|
+
padding: 0.75rem;
|
|
80
|
+
border: 1px solid #ddd;
|
|
81
|
+
border-radius: 4px;
|
|
82
|
+
font-size: 1rem;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
input[type="text"]:focus,
|
|
86
|
+
input[type="email"]:focus,
|
|
87
|
+
input[type="date"]:focus,
|
|
88
|
+
select:focus {
|
|
89
|
+
outline: none;
|
|
90
|
+
border-color: #007bff;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.btn {
|
|
94
|
+
background-color: #007bff;
|
|
95
|
+
color: #fff;
|
|
96
|
+
padding: 0.75rem 1.5rem;
|
|
97
|
+
border: none;
|
|
98
|
+
border-radius: 4px;
|
|
99
|
+
font-size: 1rem;
|
|
100
|
+
cursor: pointer;
|
|
101
|
+
transition: background-color 0.3s;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.btn:hover {
|
|
105
|
+
background-color: #0056b3;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.btn:disabled {
|
|
109
|
+
background-color: #ccc;
|
|
110
|
+
cursor: not-allowed;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.error {
|
|
114
|
+
color: #dc3545;
|
|
115
|
+
font-size: 0.875rem;
|
|
116
|
+
margin-top: 0.25rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.success-message {
|
|
120
|
+
background-color: #d4edda;
|
|
121
|
+
color: #155724;
|
|
122
|
+
padding: 1rem;
|
|
123
|
+
border-radius: 4px;
|
|
124
|
+
margin-top: 1rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.file-input-wrapper {
|
|
128
|
+
margin-bottom: 1rem;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.file-list {
|
|
132
|
+
margin-top: 1rem;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.file-item {
|
|
136
|
+
background-color: #f8f9fa;
|
|
137
|
+
padding: 0.5rem;
|
|
138
|
+
border-radius: 4px;
|
|
139
|
+
margin-bottom: 0.5rem;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.checkbox-group,
|
|
143
|
+
.radio-group {
|
|
144
|
+
display: flex;
|
|
145
|
+
gap: 1rem;
|
|
146
|
+
flex-wrap: wrap;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.checkbox-group label,
|
|
150
|
+
.radio-group label {
|
|
151
|
+
display: inline-flex;
|
|
152
|
+
align-items: center;
|
|
153
|
+
gap: 0.25rem;
|
|
154
|
+
font-weight: normal;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.required {
|
|
158
|
+
color: #dc3545;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.dropdown {
|
|
162
|
+
position: relative;
|
|
163
|
+
width: 100%;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.dropdown-toggle {
|
|
167
|
+
width: 100%;
|
|
168
|
+
padding: 0.75rem;
|
|
169
|
+
border: 1px solid #ddd;
|
|
170
|
+
border-radius: 4px;
|
|
171
|
+
font-size: 1rem;
|
|
172
|
+
background-color: #fff;
|
|
173
|
+
cursor: pointer;
|
|
174
|
+
user-select: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.dropdown-toggle:focus {
|
|
178
|
+
outline: none;
|
|
179
|
+
border-color: #007bff;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.dropdown-menu {
|
|
183
|
+
position: absolute;
|
|
184
|
+
top: 100%;
|
|
185
|
+
left: 0;
|
|
186
|
+
right: 0;
|
|
187
|
+
margin-top: 4px;
|
|
188
|
+
background-color: #fff;
|
|
189
|
+
border: 1px solid #ddd;
|
|
190
|
+
border-radius: 4px;
|
|
191
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
|
|
192
|
+
max-height: 200px;
|
|
193
|
+
overflow-y: auto;
|
|
194
|
+
z-index: 1000;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.dropdown-item {
|
|
198
|
+
padding: 0.5rem 1rem;
|
|
199
|
+
cursor: pointer;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.dropdown-item:hover {
|
|
203
|
+
background-color: #f5f5f5;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.dropdown-item.selected {
|
|
207
|
+
background-color: #e8f0fe;
|
|
208
|
+
color: #007bff;
|
|
209
|
+
}
|
|
210
|
+
</style>
|
|
211
|
+
</head>
|
|
212
|
+
<body>
|
|
213
|
+
<nav class="navbar">
|
|
214
|
+
<a href="#/" class="nav-link" data-route="/">首页</a>
|
|
215
|
+
<a href="#/form1" class="nav-link" data-route="/form1">表单页面 1</a>
|
|
216
|
+
<a href="#/form2" class="nav-link" data-route="/form2">表单页面 2</a>
|
|
217
|
+
<a href="#/upload" class="nav-link" data-route="/upload">文件上传</a>
|
|
218
|
+
</nav>
|
|
219
|
+
|
|
220
|
+
<main class="content">
|
|
221
|
+
<section id="home" class="route-content" data-route="/">
|
|
222
|
+
<h1>欢迎来到冒烟测试页面</h1>
|
|
223
|
+
<p>请选择上方导航栏的页面开始测试。</p>
|
|
224
|
+
</section>
|
|
225
|
+
|
|
226
|
+
<section id="form1" class="route-content" data-route="/form1" style="display:none;">
|
|
227
|
+
<h1>表单页面 1</h1>
|
|
228
|
+
<form id="form1-form" class="test-form">
|
|
229
|
+
<div class="form-group">
|
|
230
|
+
<label for="name">姓名 <span class="required">*</span></label>
|
|
231
|
+
<input type="text" id="name" name="name" placeholder="请输入姓名" required>
|
|
232
|
+
<div class="error" id="name-error"></div>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
<div class="form-group">
|
|
236
|
+
<label for="city">城市 <span class="required">*</span></label>
|
|
237
|
+
<div class="dropdown" id="cityDropdown">
|
|
238
|
+
<div class="dropdown-toggle" onclick="toggleDropdown('cityDropdown')">请选择城市</div>
|
|
239
|
+
<div class="dropdown-menu" style="display:none;">
|
|
240
|
+
<div class="dropdown-item" data-value="北京" onclick="selectOption('cityDropdown', '北京')">北京</div>
|
|
241
|
+
<div class="dropdown-item" data-value="上海" onclick="selectOption('cityDropdown', '上海')">上海</div>
|
|
242
|
+
<div class="dropdown-item" data-value="广州" onclick="selectOption('cityDropdown', '广州')">广州</div>
|
|
243
|
+
<div class="dropdown-item" data-value="深圳" onclick="selectOption('cityDropdown', '深圳')">深圳</div>
|
|
244
|
+
</div>
|
|
245
|
+
</div>
|
|
246
|
+
<input type="hidden" id="city" name="city" required>
|
|
247
|
+
<div class="error" id="city-error"></div>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
<div class="form-group">
|
|
251
|
+
<label>兴趣爱好</label>
|
|
252
|
+
<div class="checkbox-group">
|
|
253
|
+
<label><input type="checkbox" name="hobbies" value="阅读"> 阅读</label>
|
|
254
|
+
<label><input type="checkbox" name="hobbies" value="运动"> 运动</label>
|
|
255
|
+
<label><input type="checkbox" name="hobbies" value="音乐"> 音乐</label>
|
|
256
|
+
<label><input type="checkbox" name="hobbies" value="游戏"> 游戏</label>
|
|
257
|
+
</div>
|
|
258
|
+
</div>
|
|
259
|
+
|
|
260
|
+
<div class="form-group">
|
|
261
|
+
<label>性别 <span class="required">*</span></label>
|
|
262
|
+
<div class="radio-group">
|
|
263
|
+
<label><input type="radio" name="gender" value="男" required> 男</label>
|
|
264
|
+
<label><input type="radio" name="gender" value="女"> 女</label>
|
|
265
|
+
</div>
|
|
266
|
+
<div class="error" id="gender-error"></div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div class="form-group">
|
|
270
|
+
<label for="birthday">出生日期 <span class="required">*</span></label>
|
|
271
|
+
<input type="text" id="birthday" name="birthday" placeholder="例如:1990-01-01" required>
|
|
272
|
+
<div class="error" id="birthday-error"></div>
|
|
273
|
+
</div>
|
|
274
|
+
|
|
275
|
+
<button type="submit" class="btn">提交表单</button>
|
|
276
|
+
<div class="success-message" id="form1-success" style="display:none;">
|
|
277
|
+
表单提交成功!
|
|
278
|
+
</div>
|
|
279
|
+
</form>
|
|
280
|
+
</section>
|
|
281
|
+
|
|
282
|
+
<section id="form2" class="route-content" data-route="/form2" style="display:none;">
|
|
283
|
+
<h1>表单页面 2</h1>
|
|
284
|
+
<form id="form2-form" class="test-form">
|
|
285
|
+
<div class="form-group">
|
|
286
|
+
<label for="email">邮箱 <span class="required">*</span></label>
|
|
287
|
+
<input type="email" id="email" name="email" placeholder="请输入邮箱" required>
|
|
288
|
+
<div class="error" id="email-error"></div>
|
|
289
|
+
</div>
|
|
290
|
+
|
|
291
|
+
<div class="form-group">
|
|
292
|
+
<label for="profession">职业 <span class="required">*</span></label>
|
|
293
|
+
<div class="dropdown" id="professionDropdown">
|
|
294
|
+
<div class="dropdown-toggle" onclick="toggleDropdown('professionDropdown')">请选择职业</div>
|
|
295
|
+
<div class="dropdown-menu" style="display:none;">
|
|
296
|
+
<div class="dropdown-item" data-value="学生" onclick="selectOption('professionDropdown', '学生')">学生</div>
|
|
297
|
+
<div class="dropdown-item" data-value="工程师" onclick="selectOption('professionDropdown', '工程师')">工程师</div>
|
|
298
|
+
<div class="dropdown-item" data-value="设计师" onclick="selectOption('professionDropdown', '设计师')">设计师</div>
|
|
299
|
+
<div class="dropdown-item" data-value="产品经理" onclick="selectOption('professionDropdown', '产品经理')">产品经理</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
<input type="hidden" id="profession" name="profession" required>
|
|
303
|
+
<div class="error" id="profession-error"></div>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<button type="submit" class="btn">提交表单</button>
|
|
307
|
+
<div class="success-message" id="form2-success" style="display:none;">
|
|
308
|
+
表单提交成功!
|
|
309
|
+
</div>
|
|
310
|
+
</form>
|
|
311
|
+
</section>
|
|
312
|
+
|
|
313
|
+
<section id="upload" class="route-content" data-route="/upload" style="display:none;">
|
|
314
|
+
<h1>文件上传</h1>
|
|
315
|
+
<div class="upload-section">
|
|
316
|
+
<div class="file-input-wrapper">
|
|
317
|
+
<label for="fileInput">选择文件</label>
|
|
318
|
+
<input type="file" id="fileInput" multiple accept="image/*,.pdf,.doc,.docx">
|
|
319
|
+
</div>
|
|
320
|
+
|
|
321
|
+
<div class="file-list" id="fileList">
|
|
322
|
+
<p>尚未选择文件</p>
|
|
323
|
+
</div>
|
|
324
|
+
|
|
325
|
+
<button type="button" id="uploadBtn" class="btn" disabled>上传文件</button>
|
|
326
|
+
<div class="upload-status" id="uploadStatus"></div>
|
|
327
|
+
</div>
|
|
328
|
+
</section>
|
|
329
|
+
</main>
|
|
330
|
+
|
|
331
|
+
<script>
|
|
332
|
+
function toggleDropdown(dropdownId) {
|
|
333
|
+
const dropdown = document.getElementById(dropdownId);
|
|
334
|
+
const menu = dropdown.querySelector('.dropdown-menu');
|
|
335
|
+
const isHidden = menu.style.display === 'none';
|
|
336
|
+
|
|
337
|
+
document.querySelectorAll('.dropdown-menu').forEach(menu => {
|
|
338
|
+
menu.style.display = 'none';
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
if (isHidden) {
|
|
342
|
+
menu.style.display = 'block';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function selectOption(dropdownId, value) {
|
|
347
|
+
const dropdown = document.getElementById(dropdownId);
|
|
348
|
+
const toggle = dropdown.querySelector('.dropdown-toggle');
|
|
349
|
+
|
|
350
|
+
const hiddenInput = document.getElementById(dropdownId.replace('Dropdown', ''));
|
|
351
|
+
|
|
352
|
+
toggle.textContent = value;
|
|
353
|
+
if (hiddenInput) hiddenInput.value = value;
|
|
354
|
+
|
|
355
|
+
dropdown.querySelectorAll('.dropdown-item').forEach(item => {
|
|
356
|
+
item.classList.remove('selected');
|
|
357
|
+
if (item.dataset.value === value) {
|
|
358
|
+
item.classList.add('selected');
|
|
359
|
+
}
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
dropdown.querySelector('.dropdown-menu').style.display = 'none';
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function switchRoute(route) {
|
|
366
|
+
const normalizedRoute = route.startsWith('/') ? route : '/' + route;
|
|
367
|
+
const routeId = normalizedRoute.replace(/^\//, '') || 'home';
|
|
368
|
+
|
|
369
|
+
document.querySelectorAll('.route-content').forEach(content => {
|
|
370
|
+
content.style.display = 'none';
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
const target = document.getElementById(routeId);
|
|
374
|
+
if (target) {
|
|
375
|
+
target.style.display = 'block';
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
document.querySelectorAll('.nav-link').forEach(link => {
|
|
379
|
+
link.classList.remove('active');
|
|
380
|
+
const href = link.getAttribute('href');
|
|
381
|
+
const linkRoute = href.substring(1);
|
|
382
|
+
if (linkRoute === normalizedRoute) {
|
|
383
|
+
link.classList.add('active');
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function initRouter() {
|
|
389
|
+
window.addEventListener('hashchange', () => {
|
|
390
|
+
const hash = window.location.hash || '#/';
|
|
391
|
+
const route = hash === '#' ? '/' : hash.substring(1);
|
|
392
|
+
switchRoute(route);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
397
|
+
initRouter();
|
|
398
|
+
if (!window.location.hash) {
|
|
399
|
+
window.location.hash = '#/';
|
|
400
|
+
}
|
|
401
|
+
const hash = window.location.hash;
|
|
402
|
+
const route = hash === '#' ? '/' : hash.substring(1);
|
|
403
|
+
switchRoute(route);
|
|
404
|
+
|
|
405
|
+
const form1 = document.getElementById('form1-form');
|
|
406
|
+
if (form1) {
|
|
407
|
+
form1.addEventListener('submit', (e) => {
|
|
408
|
+
e.preventDefault();
|
|
409
|
+
let isValid = true;
|
|
410
|
+
const name = document.getElementById('name');
|
|
411
|
+
const city = document.getElementById('city');
|
|
412
|
+
const gender = document.querySelector('input[name="gender"]:checked');
|
|
413
|
+
const birthday = document.getElementById('birthday');
|
|
414
|
+
|
|
415
|
+
document.querySelectorAll('.error').forEach(el => el.textContent = '');
|
|
416
|
+
|
|
417
|
+
if (!name.value.trim()) {
|
|
418
|
+
document.getElementById('name-error').textContent = '请输入姓名';
|
|
419
|
+
isValid = false;
|
|
420
|
+
}
|
|
421
|
+
if (!city.value) {
|
|
422
|
+
document.getElementById('city-error').textContent = '请选择城市';
|
|
423
|
+
isValid = false;
|
|
424
|
+
}
|
|
425
|
+
if (!gender) {
|
|
426
|
+
document.getElementById('gender-error').textContent = '请选择性别';
|
|
427
|
+
isValid = false;
|
|
428
|
+
}
|
|
429
|
+
if (!birthday.value) {
|
|
430
|
+
document.getElementById('birthday-error').textContent = '请选择出生日期';
|
|
431
|
+
isValid = false;
|
|
432
|
+
}
|
|
433
|
+
if (isValid) {
|
|
434
|
+
// 成功提示常驻、不自动消失:自动化 agent 从「点击提交」到「回头断言」之间
|
|
435
|
+
// 要经历若干次 LLM 往返(实测 2~6 秒),3 秒的 toast 会让断言必然看不到它。
|
|
436
|
+
document.getElementById('form1-success').style.display = 'block';
|
|
437
|
+
form1.reset();
|
|
438
|
+
document.getElementById('cityDropdown').querySelector('.dropdown-toggle').textContent = '请选择城市';
|
|
439
|
+
document.getElementById('cityDropdown').querySelector('input[type="hidden"]').value = '';
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const form2 = document.getElementById('form2-form');
|
|
445
|
+
if (form2) {
|
|
446
|
+
form2.addEventListener('submit', (e) => {
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
let isValid = true;
|
|
449
|
+
const email = document.getElementById('email');
|
|
450
|
+
const profession = document.getElementById('profession');
|
|
451
|
+
|
|
452
|
+
document.querySelectorAll('.error').forEach(el => el.textContent = '');
|
|
453
|
+
|
|
454
|
+
if (!email.value.trim()) {
|
|
455
|
+
document.getElementById('email-error').textContent = '请输入邮箱';
|
|
456
|
+
isValid = false;
|
|
457
|
+
} else if (!email.value.includes('@')) {
|
|
458
|
+
document.getElementById('email-error').textContent = '邮箱格式不正确';
|
|
459
|
+
isValid = false;
|
|
460
|
+
}
|
|
461
|
+
if (!profession.value) {
|
|
462
|
+
document.getElementById('profession-error').textContent = '请在列表中选择项目。';
|
|
463
|
+
isValid = false;
|
|
464
|
+
}
|
|
465
|
+
if (isValid) {
|
|
466
|
+
// 同表单页面 1:成功提示常驻,避免断言因 agent 往返耗时错过提示
|
|
467
|
+
document.getElementById('form2-success').style.display = 'block';
|
|
468
|
+
form2.reset();
|
|
469
|
+
document.getElementById('professionDropdown').querySelector('.dropdown-toggle').textContent = '请选择职业';
|
|
470
|
+
document.getElementById('professionDropdown').querySelector('input[type="hidden"]').value = '';
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
const fileInput = document.getElementById('fileInput');
|
|
476
|
+
const fileList = document.getElementById('fileList');
|
|
477
|
+
const uploadBtn = document.getElementById('uploadBtn');
|
|
478
|
+
const uploadStatus = document.getElementById('uploadStatus');
|
|
479
|
+
let selectedFiles = [];
|
|
480
|
+
|
|
481
|
+
if (fileInput) {
|
|
482
|
+
fileInput.addEventListener('change', (e) => {
|
|
483
|
+
selectedFiles = Array.from(e.target.files);
|
|
484
|
+
renderFileList();
|
|
485
|
+
if (uploadBtn) uploadBtn.disabled = selectedFiles.length === 0;
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function renderFileList() {
|
|
490
|
+
if (selectedFiles.length === 0) {
|
|
491
|
+
if (fileList) fileList.innerHTML = '<p>尚未选择文件</p>';
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
if (fileList) {
|
|
495
|
+
fileList.innerHTML = selectedFiles.map(file => `
|
|
496
|
+
<div class="file-item">${file.name} (${formatFileSize(file.size)})</div>
|
|
497
|
+
`).join('');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function formatFileSize(bytes) {
|
|
502
|
+
if (bytes < 1024) return bytes + ' B';
|
|
503
|
+
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
504
|
+
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
if (uploadBtn) {
|
|
508
|
+
uploadBtn.addEventListener('click', () => {
|
|
509
|
+
if (selectedFiles.length === 0) return;
|
|
510
|
+
uploadBtn.disabled = true;
|
|
511
|
+
if (uploadStatus) uploadStatus.innerHTML = '<p>上传中...</p>';
|
|
512
|
+
setTimeout(() => {
|
|
513
|
+
// 成功/失败提示都常驻显示:自动化 agent 从「点击上传」到「回头断言」
|
|
514
|
+
// 要经历若干次 LLM 往返(实测 2~6 秒),自动隐藏的 toast 会让断言必然看不到它。
|
|
515
|
+
const MAX_SIZE = 10 * 1024 * 1024;
|
|
516
|
+
const oversized = selectedFiles.filter(f => f.size > MAX_SIZE);
|
|
517
|
+
if (uploadStatus) {
|
|
518
|
+
uploadStatus.innerHTML = oversized.length > 0
|
|
519
|
+
? `<div class="error">上传失败:${oversized.map(f => f.name).join('、')} 超过 10MB 大小限制</div>`
|
|
520
|
+
: `<div class="success-message">
|
|
521
|
+
成功上传 ${selectedFiles.length} 个文件
|
|
522
|
+
</div>`;
|
|
523
|
+
}
|
|
524
|
+
uploadBtn.disabled = false;
|
|
525
|
+
}, 1500);
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
</script>
|
|
530
|
+
</body>
|
|
531
|
+
</html>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# 冒烟测试用例
|
|
2
|
+
|
|
3
|
+
用 `## ` 分隔多个测试场景,CLI 会逐个运行并汇总整体结论与退出码。
|
|
4
|
+
|
|
5
|
+
> 前置:先运行 `pnpm serve:smoke` 启动静态服务,访问地址为 `http://localhost:18888/smoke-test-page.html`
|
|
6
|
+
|
|
7
|
+
## 路由切换测试
|
|
8
|
+
|
|
9
|
+
打开 `http://localhost:18888/smoke-test-page.html` 并断言页面标题包含 冒烟测试页面
|
|
10
|
+
点击页面上的「表单页面 1」链接,等待页面路由切换
|
|
11
|
+
断言页面中已出现表单内容
|
|
12
|
+
点击页面上的「文件上传」链接,等待页面路由切换
|
|
13
|
+
断言页面中已出现文件上传相关内容
|
|
14
|
+
|
|
15
|
+
## 表单填写测试
|
|
16
|
+
|
|
17
|
+
打开 `http://localhost:18888/smoke-test-page.html`
|
|
18
|
+
点击「表单页面 1」链接
|
|
19
|
+
在「姓名」输入框填写 `张三`
|
|
20
|
+
点击「城市」下拉框,在展开的选项中点击选择 `北京`
|
|
21
|
+
在「性别」中选择 `男`
|
|
22
|
+
在「出生日期」输入框填写 `1990-01-01`
|
|
23
|
+
点击「提交表单」按钮
|
|
24
|
+
等待 1 秒,让表单提交完成
|
|
25
|
+
断言页面中已出现成功消息
|
|
26
|
+
|
|
27
|
+
## 表单页面 2 测试
|
|
28
|
+
|
|
29
|
+
打开 `http://localhost:18888/smoke-test-page.html`
|
|
30
|
+
点击「表单页面 2」链接
|
|
31
|
+
在「邮箱」输入框填写 `test@example.com`
|
|
32
|
+
点击「职业」下拉框,在展开的选项中点击选择 `工程师`
|
|
33
|
+
点击「提交表单」按钮
|
|
34
|
+
等待 1 秒,让表单提交完成
|
|
35
|
+
断言页面中已出现成功消息
|
|
36
|
+
|
|
37
|
+
## 文件上传测试
|
|
38
|
+
|
|
39
|
+
打开 `http://localhost:18888/smoke-test-page.html`
|
|
40
|
+
点击「文件上传」链接
|
|
41
|
+
点击「选择文件」按钮并上传本地文件 `D:\1project\page-test-agent\examples\smoke-test.md`
|
|
42
|
+
等待 1 秒,让文件列表完成渲染
|
|
43
|
+
断言页面中已出现上传的文件名
|