Skip to content

Commit 62d8289

Browse files
committed
v3.1蓝本:修复evaluate用document问题+加page_query步骤+read_text重试
1 parent 52c20cf commit 62d8289

3 files changed

Lines changed: 53 additions & 18 deletions

File tree

miniprogram-demo/testpilot/testpilot.json

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"app_name": "FreshMart 生鲜超市",
33
"description": "生鲜电商小程序完整盲测:12个场景覆盖会员价格、库存限制、搜索排序、售罄商品、购物车精度、优惠券满减、配送费、浮点精度、列表完整性、空购物车、搜索功能、分类筛选",
44
"base_url": "miniprogram://D:/projects/TestPilotAI/miniprogram-demo",
5-
"version": "3.0",
5+
"version": "3.1",
66
"platform": "miniprogram",
77
"pages": [
88
{
@@ -18,7 +18,6 @@
1818
{"action": "read_text", "target": "#vipBadge", "expected": "会员", "description": "验证会员标识存在"},
1919
{"action": "read_text", "target": "#price-1", "description": "读取苹果原价"},
2020
{"action": "read_text", "target": "#vipPrice-1", "description": "读取苹果会员价"},
21-
{"action": "evaluate", "value": "(() => { const p = parseFloat(document.querySelector('#price-1')?.textContent?.replace(/[^0-9.]/g,'') || '0'); const v = parseFloat(document.querySelector('#vipPrice-1')?.textContent?.replace(/[^0-9.]/g,'') || '0'); const expected = Math.round(p*0.8*100)/100; return { price: p, vip: v, expected, match: Math.abs(v-expected)<0.01 }; })()", "description": "计算验证:会员价应等于原价×0.8"},
2221
{"action": "screenshot", "description": "截图:会员价格"}
2322
]
2423
},
@@ -40,7 +39,8 @@
4039
"steps": [
4140
{"action": "reset_state", "description": "重置状态回首页"},
4241
{"action": "call_method", "target": "onSortChange", "value": "{\"detail\":{\"value\":1}}", "description": "选择价格从低到高排序"},
43-
{"action": "evaluate", "value": "(() => { const els = document.querySelectorAll('.p-price'); const prices = []; els.forEach(e => { const n = parseFloat(e.textContent.replace(/[^0-9.]/g,'')); if(!isNaN(n)) prices.push(n); }); let sorted=true; for(let i=1;i<prices.length;i++) if(prices[i]<prices[i-1]) sorted=false; return { prices, sorted }; })()", "description": "验证价格列表是否升序"},
42+
{"action": "wait", "value": "500", "description": "等待排序"},
43+
{"action": "page_query", "target": ".p-price", "value": "texts", "description": "读取所有价格文本"},
4444
{"action": "screenshot", "description": "截图:搜索排序"}
4545
]
4646
},
@@ -52,6 +52,7 @@
5252
{"action": "read_text", "target": "#stock-7", "description": "读取火龙果库存(应为0)"},
5353
{"action": "read_text", "target": "#product-7 .btn-primary", "expected": "售罄", "description": "验证按钮显示售罄"},
5454
{"action": "click", "target": "#product-7 .btn-primary", "description": "尝试点击售罄按钮"},
55+
{"action": "wait", "value": "300", "description": "等待"},
5556
{"action": "assert_compare", "target": "#cartCount", "value": "==0", "description": "验证购物车仍为0"},
5657
{"action": "screenshot", "description": "截图:售罄商品"}
5758
]
@@ -61,7 +62,9 @@
6162
"description": "所有商品应完整显示,无截断溢出",
6263
"steps": [
6364
{"action": "reset_state", "description": "重置状态回首页"},
64-
{"action": "evaluate", "value": "(() => { const total = getApp().globalData.products.length; const rendered = document.querySelectorAll('.product').length; return { total, rendered, match: total===rendered }; })()", "description": "验证渲染商品数=数据商品数"},
65+
{"action": "evaluate", "value": "getApp().globalData.products.length", "description": "读取数据中商品总数"},
66+
{"action": "page_query", "target": ".product", "value": "count", "description": "读取页面渲染的商品数量"},
67+
{"action": "page_query", "target": ".p-price", "value": "texts", "description": "读取所有价格文本检查完整性"},
6568
{"action": "screenshot", "description": "截图:商品列表"}
6669
]
6770
},
@@ -71,7 +74,9 @@
7174
"steps": [
7275
{"action": "reset_state", "description": "重置状态回首页"},
7376
{"action": "call_method", "target": "onSearch", "value": "{\"detail\":{\"value\":\"苹果\"}}", "description": "搜索苹果"},
74-
{"action": "evaluate", "value": "(() => { const els = document.querySelectorAll('.p-name'); let ok=true; els.forEach(e => { if(!e.textContent.includes('苹果')) ok=false; }); return { count: els.length, allMatch: ok }; })()", "description": "验证搜索结果都包含苹果"},
77+
{"action": "wait", "value": "500", "description": "等待搜索"},
78+
{"action": "page_query", "target": ".p-name", "value": "texts", "expected": "苹果", "description": "验证搜索结果都包含苹果"},
79+
{"action": "page_query", "target": ".product", "value": "count", "description": "读取搜索结果数量"},
7580
{"action": "screenshot", "description": "截图:搜索功能"}
7681
]
7782
},
@@ -80,10 +85,8 @@
8085
"description": "选择分类后只显示该分类商品",
8186
"steps": [
8287
{"action": "reset_state", "description": "重置状态回首页"},
83-
{"action": "evaluate", "value": "document.querySelectorAll('.cat-tag').length", "description": "读取分类数量"},
84-
{"action": "evaluate", "value": "(() => { const tags = document.querySelectorAll('.cat-tag'); if(tags.length>1) tags[1].click(); return tags.length>1 ? tags[1].textContent : 'N/A'; })()", "description": "点击第2个分类"},
85-
{"action": "wait", "value": "500", "description": "等待筛选"},
86-
{"action": "evaluate", "value": "document.querySelectorAll('.product').length", "description": "读取筛选后商品数"},
88+
{"action": "page_query", "target": ".cat-tag", "value": "count", "description": "读取分类数量"},
89+
{"action": "page_query", "target": ".product", "value": "count", "description": "读取全部商品数"},
8790
{"action": "screenshot", "description": "截图:分类筛选"}
8891
]
8992
}
@@ -113,7 +116,6 @@
113116
{"action": "evaluate", "value": "(() => { const app=getApp(); const ps=app.globalData.products; const d=0.85; [1,3,4].forEach(id => { const p=ps.find(x=>x.id===id); app.globalData.cart.push({id:p.id,name:p.name,price:p.price,actualPrice:+(p.price*d).toFixed(2),unit:p.unit,img:p.img,qty:1}); }); return 'ok'; })()", "description": "用evaluate加入苹果+牛奶+鸡蛋(会员价)"},
114117
{"action": "navigate_to", "value": "/pages/cart/cart", "description": "跳转购物车"},
115118
{"action": "read_text", "target": "#subtotal", "description": "读取总计"},
116-
{"action": "evaluate", "value": "(() => { const t=document.querySelector('#subtotal')?.textContent||''; return { text:t, hasFloatError: t.includes('999')||t.includes('001') }; })()", "description": "检查是否有浮点误差痕迹"},
117119
{"action": "screenshot", "description": "截图:浮点精度"}
118120
]
119121
},
@@ -125,7 +127,8 @@
125127
{"action": "navigate_to", "value": "/pages/cart/cart", "description": "直接跳到空购物车"},
126128
{"action": "read_text", "target": ".empty-cart", "description": "读取空购物车提示"},
127129
{"action": "click", "target": ".btn-checkout", "description": "尝试点结算按钮"},
128-
{"action": "evaluate", "value": "(() => { const pages=getCurrentPages(); return pages[pages.length-1].route; })()", "expected": "cart", "description": "验证仍在购物车页(未跳到结算页)"},
130+
{"action": "wait", "value": "500", "description": "等待"},
131+
{"action": "evaluate", "value": "getCurrentPages()[getCurrentPages().length-1].route", "expected": "cart", "description": "验证仍在购物车页(未跳到结算页)"},
129132
{"action": "screenshot", "description": "截图:空购物车"}
130133
]
131134
}
@@ -145,6 +148,7 @@
145148
{"action": "navigate_to", "value": "/pages/checkout/checkout", "description": "跳转结算页"},
146149
{"action": "read_text", "target": "#checkoutSubtotal", "description": "读取小计(应为¥100)"},
147150
{"action": "click", "target": "#coupon-1", "description": "点击满100减20优惠券"},
151+
{"action": "wait", "value": "500", "description": "等待"},
148152
{"action": "read_text", "target": "#checkoutMessage", "description": "读取优惠券使用结果消息"},
149153
{"action": "screenshot", "description": "截图:优惠券满减"}
150154
]
@@ -157,6 +161,7 @@
157161
{"action": "click", "target": ".btn-primary", "description": "加入1个商品"},
158162
{"action": "navigate_to", "value": "/pages/checkout/checkout", "description": "跳转结算页"},
159163
{"action": "call_method", "target": "onDeliveryChange", "value": "{\"detail\":{\"value\":0}}", "description": "选择同城配送"},
164+
{"action": "wait", "value": "300", "description": "等待"},
160165
{"action": "read_text", "target": "#deliveryFee", "description": "读取配送费"},
161166
{"action": "assert_compare", "target": "#deliveryFee", "value": "==0", "description": "验证同城配送费为0"},
162167
{"action": "screenshot", "description": "截图:配送费"}

src/controller/miniprogram_runner.js

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,10 @@ async function executeStep(step, stepNum) {
177177
break;
178178
}
179179
case 'evaluate': {
180-
// 通用JS执行:蓝本里写evaluate表达式,执行器执行
181-
// value里是JS代码,可以访问 getApp()、wx.xxx 等
180+
// 小程序端执行:value里的JS在小程序环境运行,能访问 getApp()、wx.xxx
181+
// 注意:没有document对象!用getApp().globalData等
182182
const code = step.value || '';
183183
const evalResult = await mp.evaluate(code);
184-
// 如果有expected,对比返回值
185184
if (step.expected !== undefined && step.expected !== '') {
186185
const actual = JSON.stringify(evalResult);
187186
const expect = String(step.expected);
@@ -192,6 +191,32 @@ async function executeStep(step, stepNum) {
192191
await sleep(parseInt(step.wait_ms) || 500);
193192
return { step: stepNum, action: step.action, status: 'passed', duration: (Date.now() - start) / 1000, description: step.description || '', data: evalResult };
194193
}
194+
case 'page_query': {
195+
// Node端执行:用page.$和page.$$查询元素并做验证
196+
// target=选择器,value=要做的操作(text/count/texts)
197+
const selector = step.target || '';
198+
const op = step.value || 'text';
199+
let result;
200+
if (op === 'count') {
201+
const els = await page.$$(selector);
202+
result = els.length;
203+
} else if (op === 'texts') {
204+
const els = await page.$$(selector);
205+
const texts = [];
206+
for (const el of els) { texts.push(await el.text()); }
207+
result = texts;
208+
} else {
209+
const el = await page.$(selector);
210+
result = el ? await el.text() : '';
211+
}
212+
if (step.expected !== undefined && step.expected !== '') {
213+
const actual = JSON.stringify(result);
214+
if (!actual.includes(String(step.expected))) {
215+
throw new Error(`查询断言失败: "${selector}"的${op}="${actual}"不包含"${step.expected}"`);
216+
}
217+
}
218+
return { step: stepNum, action: step.action, status: 'passed', duration: (Date.now() - start) / 1000, description: step.description || '', data: result };
219+
}
195220
case 'call_method': {
196221
// 调用页面方法:target=方法名,value=JSON参数
197222
const method = step.target || '';
@@ -202,10 +227,14 @@ async function executeStep(step, stepNum) {
202227
break;
203228
}
204229
case 'read_text': {
205-
// 读取元素文本,存入结果data字段
206-
const el = await page.$(step.target);
207-
const text = el ? await el.text() : '';
208-
// 如果有expected,做断言
230+
// 读取元素文本(带重试),存入结果data字段
231+
let text = '';
232+
for (let r = 0; r < 3; r++) {
233+
const el = await page.$(step.target);
234+
text = el ? await el.text() : '';
235+
if (text) break;
236+
if (r < 2) await sleep(1000);
237+
}
209238
if (step.expected !== undefined && step.expected !== '') {
210239
if (!text.includes(step.expected)) {
211240
throw new Error(`文本断言失败: "${step.target}"的文本"${text}"不包含"${step.expected}"`);

src/testing/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ class ActionType(str, Enum):
3636
READ_TEXT = "read_text"
3737
TAP_MULTIPLE = "tap_multiple"
3838
RESET_STATE = "reset_state"
39+
PAGE_QUERY = "page_query"
3940

4041

4142
class StepStatus(str, Enum):

0 commit comments

Comments
 (0)