Compare commits

7 Commits
Author SHA1 Message Date
pei 94138ba7f4 test(e2e): mobile 视口测试 (375x812) + 扩展登录态路由
完整测试覆盖 (49 路由, 0 崩溃):
- 公开 desktop: 18/18 ✓
- 登录态 desktop: 17/17 ✓ (含 orderdetail/paynow/view-ticket/ocr-scan/renew-*)
- mobile 视口: 14/14 ✓ (仅 /me 4px RenderFlex 溢出, 无害布局警告)

修复汇总 (本轮自动化发现+修复):
1. download Scrollbar 缺 ScrollPosition -> primary:true
2. contact-stores initState dispose 后 setState -> mounted 保护
3. buy-service onGenerateRoute pathElements 越界 RangeError -> 长度检查
4. add_remove_button/shopping_cart_* cartInfo 误加 ! (nullfix 脚本)
5. checkout table_token(tableNumber!) 无判空
2026-08-03 02:01:17 +08:00
pei 33b54fe454 feat(e2e): autologin patch (dev) + 登录态路由测试脚本
- util_web devAutologin: localStorage[_test_token]=user|pass -> 登录API写Hive+dispatch currentUser
- 延迟3s执行等 Flutter/Hive ready (否则 httpPost hang)
- main.dart 调用 devAutologin
- run_auth.js: 注入token遍历登录态路由
- 已验证: autologin API 200 OK
2026-08-03 01:50:12 +08:00
pei 901e42a41b fix(runtime): onGenerateRoute pathElements 越界 RangeError
- buy-service/renew-minioffice 加长度检查 (pathElements.length >= 4/3)
- 根因: Flutter web 深链接渐进式路径解析, onGenerateRoute 被调用多次,
  pathElements 递增 ([,buy-service] -> [,buy-service,1] -> 完整),
  前几次路径短但 pathElements[1]已匹配 -> int.parse([2])/[3] 越界
- 直接访问/刷新 /buy-service/x/y, /renew-minioffice/x 时崩溃
- 恢复 buy_service 实验代码 (data 校验 + mounted 保留)
2026-08-02 00:13:33 +08:00
pei af8934fe7f diag: FlutterError.onError 加 CK_CTX (widget 上下文) 定位 RangeError 来源 2026-08-01 23:35:34 +08:00
pei 048c5a9ff4 fix(runtime): contact-stores initState httpGet.then setState 加 mounted 保护 (dispose 后 setState) 2026-08-01 23:29:08 +08:00
pei 1d4028f486 fix(compile): buy_service value cast Map<String,dynamic>.from; gitignore e2e node_modules 2026-08-01 23:26:14 +08:00
pei 944493a39b fix(runtime): e2e 发现的 3 个 bug 修复
- download: desktop_download_apps SingleChildScrollView 加 primary:true (Scrollbar 缺 ScrollPosition)
- contact-stores: checkDomainAvailability async 后 setState 加 mounted 保护 (dispose 后 setState)
- buy-service: _loadData 加 data 结构校验 + mounted 保护 (API 400 无效参数时 data 为错误结构 -> RangeError)
2026-08-01 23:20:25 +08:00
44 changed files with 1084 additions and 7 deletions
+4 -2
View File
@@ -36,12 +36,14 @@ void main() {
FlutterError.onError = (details) {
FlutterError.presentError(details);
print('CK_ERROR: ${details.exceptionAsString()}');
print('CK_CTX: ${details.context}');
print('CK_STACK: ${details.stack}');
};
// Enable configureApp will cause route problem.
// configureApp();
setPathUrlStrategy();
runApp(MyApp());
Future.delayed(Duration(seconds: 3), () => Util.devAutologin()); // DEV e2e: 延迟 3s 等 Flutter/Hive ready
}
class MyApp extends StatelessWidget {
@@ -170,12 +172,12 @@ class MyApp extends StatelessWidget {
),
);
}
if (pathElements[1] == 'renew-minioffice') {
if (pathElements[1] == 'renew-minioffice' && pathElements.length >= 3) {
return MaterialPageRoute(builder: (BuildContext context) =>
RenewMiniOffice(int.parse(pathElements[2]))
);
}
if (pathElements[1] == 'buy-service') {
if (pathElements[1] == 'buy-service' && pathElements.length >= 4) {
return MaterialPageRoute(builder: (BuildContext context) =>
BuyService(int.parse(pathElements[2]), pathElements[3])
);
+7 -3
View File
@@ -72,12 +72,16 @@ class BuyServiceState extends State<BuyService> {
'store_id': '${widget.sid}',
}
).then((value) {
data = value;
if (!mounted) return;
if (value == null || value is! Map || value['service_selections'] == null) {
Utils.showMessageDialog(context, 'Service info unavailable');
return;
}
data = Map<String, dynamic>.from(value);
data!['domain'] = widget.domain;
print('data: $data');
setState(() {});
}).onError((error, stackTrace) {
Utils.showMessageDialog(context, error);
if (mounted) Utils.showMessageDialog(context, error);
});
}
}
+3
View File
@@ -199,6 +199,9 @@ class Util {
return box;
}
/// DEV ONLY stub (原生不自动登录)
static void devAutologin() {}
static Widget showImage(String imageUrl, {double? width, double? height,
BoxFit? fit, Widget Function(BuildContext, String, dynamic)? errorWidget}) {
if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
+22
View File
@@ -41,6 +41,28 @@ class Util {
return box;
}
/// DEV ONLY (e2e tests): 读 localStorage['_test_token'] = 'username|password',
/// 调登录 API 写 Hive + dispatch currentUser。测试后移除。
static Future<void> devAutologin() async {
try {
final t = window.localStorage['_test_token'];
if (t == null || t.isEmpty) return;
final parts = t.split('|');
if (parts.length < 2) return;
print('CK_AUTOLOGIN_CALL');
await HttpUtil.httpPost('v1/oauth-wisetronic/access_token', (response) async {
final box = await getBox();
await box.put(Constants.KEY_ACCESS_TOKEN, response.data['access_token']);
await box.put(Constants.KEY_USER_ID, response.data['user_id']);
store.dispatch(UpdateCurrentUser(User.fromJson(response.data['user'])));
print('CK_AUTOLOGIN_OK');
},
queryParameters: {'client_id': Utils.getPlatformName(), 'grant_type': 'password'},
body: {'username': parts[0], 'password': parts[1], 'fcm_token': ''},
isFormData: true, businessId: Constants.BUSINESS_ID);
} catch (e) { print('CK_AUTOLOGIN_EX: ${e.toString().substring(0, 150)}'); }
}
static Widget showImage(String imageUrl, {double? width, double? height,
BoxFit? fit, Widget Function(BuildContext, String?, dynamic)? errorWidget}) {
return Image.network(imageUrl,
@@ -383,13 +383,14 @@ class CreateOnlineStore1State extends State<CreateOnlineStore1> {
'service': Constants.WEB_MINISTORE_SERVICE
}
).then((value) {
if (!mounted) return;
setState(() {
stores = value['stores'];
service = value['service'];
group = Group.fromJson(value['group']);
print('stores: ${value}');
});
}).onError((error, stackTrace) {
if (!mounted) return;
Utils.showMessageDialog(context, error, onOk: () {
Routes.router.navigateTo(context, '/');
});
@@ -411,7 +412,7 @@ class CreateOnlineStore1State extends State<CreateOnlineStore1> {
print('value: $value');
if (value['status'] == 'OK') {
domainResult = (value['domain_check_result'] as List);
setState(() {});
if (mounted) setState(() {});
} else if (value['status'] == 'ERROR') {
String errors = '';
for (int i = 0; i < (value['errors'] as List).length; i++) {
@@ -55,6 +55,7 @@ class DesktopDownloadAppsState extends State<DesktopDownloadApps> {
}
col.children.add(wrap);
Widget view = SingleChildScrollView(
primary: true,
child: Row(
children: [
Container(
+2
View File
@@ -0,0 +1,2 @@
node_modules/
*.png
+13
View File
@@ -0,0 +1,13 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext()).newPage();
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load' });
await page.waitForTimeout(3000);
const data = await page.evaluate(async () => {
const r = await fetch('https://api.minipos.us/v1/get-buy-service-info/pos/1?store_id=0');
return JSON.stringify(await r.json());
}).catch(e => 'ERR: ' + e.message);
console.log('buy-service API:', data.slice(0, 600));
await browser.close();
})();
+22
View File
@@ -0,0 +1,22 @@
// 拦截 buy-service 的 API 请求/响应,看实际数据结构
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
page.on('response', async (resp) => {
const url = resp.url();
if (url.includes('get-buy-service-info')) {
console.log('=== buy-service API 响应 ===');
console.log('状态:', resp.status());
try {
const body = await resp.text();
console.log('body:', body.slice(0, 700));
} catch(e) { console.log('读 body 失败'); }
}
});
page.on('pageerror', e => console.log('PAGEERR:', e.message.slice(0,150)));
page.on('console', m => { if (m.text().startsWith('data:') || m.text().includes('RangeError')) console.log('CONSOLE:', m.text().slice(0,200)); });
await page.goto('http://127.0.0.1:8099/buy-service/1/pos', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(8000);
await browser.close();
})();
+12
View File
@@ -0,0 +1,12 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/pos', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(6000);
const dataLog = logs.filter(l => l.startsWith('data: {') || l.startsWith('data:{'));
console.log('应用 data:', dataLog[0] ? dataLog[0].slice(0, 500) : '(未捕获)');
await browser.close();
})();
+21
View File
@@ -0,0 +1,21 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
page.on('pageerror', e => logs.push('PAGEERR: ' + e.message));
await page.goto('http://127.0.0.1:8099/buy-service/1/pos', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(7000);
// 找 data 打印 + RangeError
const dataLog = logs.find(l => l.startsWith('data:'));
console.log('=== data ===', dataLog ? dataLog.slice(0,400) : '(无)');
// 看 service_selections / options 部分
if (dataLog) {
const m = dataLog.match(/service_selections:[^,}]+/);
if (m) console.log('service_selections:', m[0].slice(0,200));
const o = dataLog.match(/options:[^]]*\]/);
if (o) console.log('options:', o[0].slice(0,200));
}
await browser.close();
})();
+33
View File
@@ -0,0 +1,33 @@
// 诊断:对失败路由打印完整错误 + CK_STACK
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
const ROUTES = ['/contact-stores', '/download', '/buy-service/1/pos'];
(async () => {
const browser = await chromium.launch({ headless: true });
for (const route of ROUTES) {
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const page = await ctx.newPage();
const allLogs = [];
page.on('pageerror', e => allLogs.push(`PAGEERROR: ${e.message}`));
page.on('console', m => allLogs.push(m.text()));
await page.goto(BASE + route, { waitUntil: 'load', timeout: 30000 }).catch(e => allLogs.push('GOTO: ' + e.message.slice(0,80)));
await page.waitForTimeout(5000);
console.log(`\n########## ${route} ##########`);
// 只打印 CK_ERROR + CK_STACK + PAGEERROR
for (let i = 0; i < allLogs.length; i++) {
const l = allLogs[i];
if (l.startsWith('CK_ERROR:') || l.startsWith('PAGEERROR')) {
console.log(l.slice(0, 300));
// 找紧跟的 CK_STACK
if (allLogs[i+1] && allLogs[i+1].startsWith('CK_STACK:')) {
const proj = allLogs[i+1].match(/flutter_wisetronic\/[^\s]+:\d+/g);
console.log(' → 项目位置:', proj ? [...new Set(proj)].slice(0,4) : '(无项目帧)');
}
}
}
await ctx.close();
}
await browser.close();
})();
+32
View File
@@ -0,0 +1,32 @@
// 诊断 v2:打印完整 CK_STACK(定位 framework 抛的错误的源头)
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
const ROUTES = ['/buy-service/1/pos', '/download'];
(async () => {
const browser = await chromium.launch({ headless: true });
for (const route of ROUTES) {
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const page = await ctx.newPage();
const allLogs = [];
page.on('pageerror', e => allLogs.push(`PAGEERROR: ${e.stack || e.message}`));
page.on('console', m => allLogs.push(m.text()));
await page.goto(BASE + route, { waitUntil: 'load', timeout: 30000 }).catch(()=>{});
await page.waitForTimeout(5000);
console.log(`\n########## ${route} ##########`);
for (let i = 0; i < allLogs.length; i++) {
const l = allLogs[i];
if (l.startsWith('CK_ERROR:') || l.startsWith('PAGEERROR')) {
console.log('--- ' + l.slice(0, 150));
if (allLogs[i+1] && allLogs[i+1].startsWith('CK_STACK:')) {
// 打印堆栈里所有 flutter_wisetronic 帧
const proj = allLogs[i+1].match(/flutter_wisetronic\/[^\s]+/g) || [];
console.log(' 项目帧: ' + (proj.length ? [...new Set(proj)].join('\n ') : '(无)'));
}
}
}
await ctx.close();
}
await browser.close();
})();
+14
View File
@@ -0,0 +1,14 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
page.on('pageerror', e => logs.push('PAGEERR: ' + e.message.slice(0,150)));
page.on('response', async r => { if (r.url().includes('get-buy-service')) console.log('API:', r.status()); });
await page.goto('http://127.0.0.1:8099/buy-service/1/pos', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(9000);
console.log('--- RangeError 相关 console ---');
logs.filter(l => l.includes('RangeError') || l.includes('CK_ERROR') || l.startsWith('PAGEERR')).slice(0,5).forEach(l => console.log(l.slice(0,180)));
await browser.close();
})();
+16
View File
@@ -0,0 +1,16 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(8000);
for (let i=0;i<logs.length;i++){
if(logs[i].startsWith('CK_ERROR')){console.log(logs[i].slice(0,120));
const ctx=logs.slice(i+1,i+4).find(l=>l.startsWith('CK_CTX'));
if(ctx) console.log(' CTX:', ctx.slice(0,150));
}
}
await browser.close();
})();
+13
View File
@@ -0,0 +1,13 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(7000);
const ctx = logs.filter(l => l.startsWith('CK_CTX'));
if (ctx.length) console.log(ctx[0].slice(0, 600));
else console.log('无 CK_CTX');
await browser.close();
})();
+14
View File
@@ -0,0 +1,14 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(7000);
const range = logs.filter(l => l.includes('RangeError'));
const err = logs.filter(l => l.includes('CK_BUY_ERR'));
console.log('RangeError 数:', range.length, err.length ? '| CK_BUY_ERR:'+err[0].slice(0,100) : '');
if (range.length) console.log(range[0].slice(0,120));
await browser.close();
})();
+21
View File
@@ -0,0 +1,21 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 375, height: 812 } });
await ctx.addInitScript(([u,p]) => { localStorage['_test_token'] = u+'|'+p; }, ['6477108200','8818']);
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load' });
await page.waitForSelector('canvas', { timeout: 90000 });
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(8000);
await page.goto('http://127.0.0.1:8099/me', { waitUntil: 'load' });
await page.waitForTimeout(6000);
const stacks = logs.filter(l => l.startsWith('CK_STACK:'));
for (const s of stacks) {
const proj = s.match(/flutter_wisetronic\/[^\s]+:\d+/g);
if (proj) console.log('RenderFlex 项目位置:', [...new Set(proj)].slice(0,3));
}
await browser.close();
})();
+15
View File
@@ -0,0 +1,15 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(7000);
console.log('=== path elements 打印(看是否有短路径)===');
logs.filter(l => l.includes('path elements')).forEach(l => console.log(' ' + l.slice(0,80)));
console.log('=== RangeError 之前的最后几条 console ===');
const idx = logs.findIndex(l => l.includes('RangeError'));
if (idx >= 0) logs.slice(Math.max(0,idx-4), idx).forEach(l => console.log(' ' + l.slice(0,90)));
await browser.close();
})();
+14
View File
@@ -0,0 +1,14 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
for (const route of ['/download', '/contact-stores', '/me', '/orders', '/buy-service/1/ministore']) {
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
let range = 0;
page.on('console', m => { if (m.text().includes('RangeError')) range++; });
await page.goto('http://127.0.0.1:8099'+route, { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(6000);
console.log(`${range>0?'✗ '+range+' RangeError':'✓ OK'} ${route}`);
await page.close();
}
await browser.close();
})();
+13
View File
@@ -0,0 +1,13 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(7000);
const seq = logs.filter(l => l.startsWith('CK_BS_') || l.includes('RangeError'));
console.log('时序:');
seq.slice(0,10).forEach(l => console.log(' ' + l.slice(0,90)));
await browser.close();
})();
+19
View File
@@ -0,0 +1,19 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(7000);
const stack = logs.filter(l => l.startsWith('CK_STACK'));
if (stack.length) {
// 找 .dart 源文件帧(非 main.dart.js)
const dartFrames = stack[0].split('\n').filter(l => l.includes('.dart') && !l.includes('main.dart.js'));
console.log('源文件帧:', dartFrames.length ? dartFrames.slice(0,5).join('\n ') : '(无,全是 main.dart.js)');
// 也看前 3 个 main.dart.js 帧
const jsFrames = stack[0].split('\n').filter(l => l.includes('main.dart.js')).slice(0,4);
console.log('main.dart.js 帧:', jsFrames.join('\n '));
}
await browser.close();
})();
+11
View File
@@ -0,0 +1,11 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(8000);
logs.filter(l => l.includes('CK_BUY_THEN') || l.includes('RangeError') || l.includes('CK_ERROR')).slice(0,5).forEach(l => console.log(l.slice(0,160)));
await browser.close();
})();
+47
View File
@@ -0,0 +1,47 @@
// dump IndexedDB 结构 + Hive entry 格式
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext()).newPage();
await page.goto(BASE + '/', { waitUntil: 'load', timeout: 60000 });
await page.waitForTimeout(7000);
const result = await page.evaluate(async () => {
const out = {};
const dbs = await indexedDB.databases();
out.databases = dbs.map(d => d.name);
for (const db of dbs) {
out[db.name] = [];
await new Promise((resolve) => {
const req = indexedDB.open(db.name);
req.onsuccess = () => {
const d = req.result;
const stores = [...d.objectStoreNames];
out[db.name + '_stores'] = stores;
if (stores.length === 0) { d.close(); resolve(); return; }
const tx = d.transaction(stores[0], 'readonly');
const store = tx.objectStore(stores[0]);
const allReq = store.getAll();
const keysReq = store.getAllKeys();
allReq.onsuccess = () => {
out[db.name] = allReq.result.map((v, i) => {
let hex = '';
if (v instanceof ArrayBuffer) {
const u = new Uint8Array(v);
hex = Array.from(u.slice(0, 40)).map(b => b.toString(16).padStart(2,'0')).join(' ');
return { key: keysReq.result[i], valueBytes: v.byteLength, hexFirst40: hex };
}
return { key: keysReq.result[i], value: typeof v === 'string' ? v.slice(0,80) : v };
});
};
tx.oncomplete = () => { d.close(); resolve(); };
};
req.onerror = () => resolve();
});
}
return out;
});
console.log(JSON.stringify(result, null, 2));
await browser.close();
})().catch(e => { console.error('失败:', e.message); process.exit(1); });
Binary file not shown.

After

Width:  |  Height:  |  Size: 813 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

+46
View File
@@ -0,0 +1,46 @@
"use strict";
// A map containing the URLs for the bootstrap scripts in debug.
let _scriptUrls = {
"mapper": "stack_trace_mapper.js",
"requireJs": "require.js"
};
// Create a TrustedTypes policy so we can attach Scripts...
let _ttPolicy;
if (window.trustedTypes) {
_ttPolicy = trustedTypes.createPolicy("flutter-tools-bootstrap", {
createScriptURL: (url) => {
let scriptUrl = _scriptUrls[url];
if (!scriptUrl) {
console.error("Unknown Flutter Web bootstrap resource!", url);
}
return scriptUrl;
}
});
}
// Creates a TrustedScriptURL for a given `scriptName`.
// See `_scriptUrls` and `_ttPolicy` above.
function getTTScriptUrl(scriptName) {
let defaultUrl = _scriptUrls[scriptName];
return _ttPolicy ? _ttPolicy.createScriptURL(scriptName) : defaultUrl;
}
// Attach source mapping.
var mapperEl = document.createElement("script");
mapperEl.defer = true;
mapperEl.async = false;
mapperEl.src = getTTScriptUrl("mapper");
document.head.appendChild(mapperEl);
// Attach require JS.
var requireEl = document.createElement("script");
requireEl.defer = true;
requireEl.async = false;
requireEl.src = getTTScriptUrl("requireJs");
// This attribute tells require JS what to load as main (defined below).
requireEl.setAttribute("data-main", "main_module.bootstrap");
document.head.appendChild(requireEl);
+70
View File
@@ -0,0 +1,70 @@
{
"name": "e2e",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "e2e",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"playwright": "^1.62.1",
"source-map": "^0.8.0"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.62.1.tgz",
"integrity": "sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==",
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.62.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=20"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.62.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.62.1.tgz",
"integrity": "sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==",
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=20"
}
},
"node_modules/source-map": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz",
"integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 12"
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
{
"name": "e2e",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"playwright": "^1.62.1",
"source-map": "^0.8.0"
}
}
+15
View File
@@ -0,0 +1,15 @@
const fs = require('fs');
const { SourceMapConsumer } = require('source-map');
(async () => {
const mapJson = await (await fetch('http://127.0.0.1:8099/main.dart.js.map')).text().catch(()=>null);
if (!mapJson) { console.log('无 sourcemap'); return; }
const smc = await new SourceMapConsumer(mapJson);
// 解析 299:254 (line:col)
const pos = smc.originalPositionFor({ line: 299, column: 254 });
console.log('299:254 →', pos.source, pos.line + ':' + pos.column, '|', pos.name);
// 也试 299 的前后几行
for (const c of [200, 100, 50]) {
const p = smc.originalPositionFor({ line: 299, column: c });
if (p.source) console.log(`299:${c} →`, p.source.split('/').slice(-2).join('/') + ':' + p.line);
}
})();
+68
View File
@@ -0,0 +1,68 @@
// 探测脚本:检查 Flutter renderer + 登录页 DOM + Hive 存储后端
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext();
const page = await ctx.newPage();
// 收集 console
const logs = [];
page.on('console', m => logs.push(`[${m.type()}] ${m.text()}`));
page.on('pageerror', e => logs.push(`[PAGEERROR] ${e.message}`));
console.log('=== 1. 访问首页,检查 renderer ===');
await page.goto(BASE + '/', { waitUntil: 'networkidle', timeout: 60000 });
await page.waitForTimeout(3000);
// 检查 renderer:canvaskit 有 <canvas> + flt-glass-pane;html 有 flutter-view 含 div
const canvasCount = await page.locator('canvas').count();
const fltGlass = await page.locator('flt-glass-pane').count();
const htmlView = await page.locator('flutter-view, .flutter-view').count();
console.log(`canvas 元素数: ${canvasCount}`);
console.log(`flt-glass-pane (canvaskit 标志): ${fltGlass}`);
console.log(`flutter-view: ${htmlView}`);
// body HTML 片段(判断 renderer)
const bodySnippet = await page.evaluate(() => document.body.innerHTML.slice(0, 400));
console.log(`body 片段: ${bodySnippet.slice(0, 200)}`);
console.log('\n=== 2. 访问登录页,检查输入框 DOM ===');
await page.goto(BASE + '/login', { waitUntil: 'networkidle', timeout: 60000 });
await page.waitForTimeout(3000);
// Flutter web 输入框:canvaskit 会在聚焦时创建 <input>;检查有无 input
const inputCount = await page.locator('input').count();
console.log(`input 元素数: ${inputCount}`);
const inputs = await page.locator('input').evaluateAll(els =>
els.map(e => ({ type: e.type, placeholder: e.placeholder, ariaLabel: e.getAttribute('aria-label'), cls: e.className.slice(0, 40) }))
);
console.log('input 详情:', JSON.stringify(inputs).slice(0, 300));
// 检查 semantics(辅助功能树)
const semanticsRoles = await page.locator('[role="textbox"], [role="button"]').count();
console.log(`semantics (role=textbox/button): ${semanticsRoles}`);
console.log('\n=== 3. 检查存储(localStorage + IndexedDB)===');
const ls = await page.evaluate(() => {
return JSON.stringify(Object.keys(localStorage).reduce((o, k) => { o[k] = localStorage.getItem(k).slice(0, 100); return o; }, {}));
});
console.log(`localStorage: ${ls}`);
const idb = await page.evaluate(async () => {
const dbs = indexedDB.databases ? await indexedDB.databases() : [];
const result = [];
for (const db of dbs) {
result.push({ name: db.name, version: db.version });
}
return JSON.stringify(result);
});
console.log(`IndexedDB databases: ${idb}`);
console.log('\n=== 4. console 日志(前 10 条)===');
console.log(logs.slice(0, 10).join('\n'));
await browser.close();
})().catch(e => { console.error('探测失败:', e.message); process.exit(1); });
+51
View File
@@ -0,0 +1,51 @@
// 探测 v2:正确加载 + 截图 + 检测 canvaskit 隐藏 input
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const page = await ctx.newPage();
const logs = [];
page.on('pageerror', e => logs.push(`[PAGEERROR] ${e.message.slice(0, 120)}`));
console.log('=== 访问 /login (load 模式) ===');
await page.goto(BASE + '/login', { waitUntil: 'load', timeout: 60000 });
await page.waitForTimeout(6000); // 等 Flutter 渲染
await page.screenshot({ path: 'login.png', fullPage: true });
console.log('截图: login.png');
// 检查所有 input(含 display:none)
const inputs = await page.evaluate(() => {
return [...document.querySelectorAll('input')].map(e => ({
type: e.type,
hidden: e.hidden || e.style.display === 'none' || e.offsetParent === null,
cls: e.className.slice(0, 50),
pos: e.getBoundingClientRect ? (() => { const r = e.getBoundingClientRect(); return {x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width), h: Math.round(r.height)}; })() : null,
}));
});
console.log(`所有 input (${inputs.length}):`, JSON.stringify(inputs));
// 检查 flt-glass-pane 内的 semantics
const semTextboxes = await page.locator('[role="textbox"]').count();
console.log(`role=textbox: ${semTextboxes}`);
console.log('\n=== 尝试点击用户名输入框区域,看是否触发 input ===');
// canvaskit 点击中间偏上区域(假设用户名框)
await page.mouse.click(640, 400);
await page.waitForTimeout(1500);
const inputsAfterClick = await page.evaluate(() => {
return [...document.querySelectorAll('input')].map(e => ({
hidden: e.hidden || e.style.display === 'none',
pos: (() => { const r = e.getBoundingClientRect(); return {x: Math.round(r.x), y: Math.round(r.y), w: Math.round(r.width)}; })(),
}));
});
console.log('点击后 input:', JSON.stringify(inputsAfterClick));
console.log('是否有 activeElement 是 input:', await page.evaluate(() => document.activeElement && document.activeElement.tagName));
console.log('\n=== pageerror 日志 ===');
console.log(logs.slice(0, 5).join('\n'));
await browser.close();
})().catch(e => { console.error('失败:', e.message); process.exit(1); });
+40
View File
@@ -0,0 +1,40 @@
// probe3:开启 Flutter web semantics(Enable accessibility),看能否得到 DOM 输入框
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({ viewport: { width: 1280, height: 900 } })).newPage();
await page.goto(BASE + '/login', { waitUntil: 'load', timeout: 60000 });
await page.waitForTimeout(5000);
// 方法1:点击 flt-semantics-placeholder("Enable accessibility")
const ph = await page.locator('flt-semantics-placeholder').count();
console.log('placeholder 数:', ph);
if (ph > 0) {
await page.locator('flt-semantics-placeholder').click({ force: true }).catch(e => console.log('点击 placeholder 失败:', e.message.slice(0,60)));
await page.waitForTimeout(3000);
}
// 方法2:尝试 JS 开启(多种已知方式)
await page.evaluate(() => {
// 部分版本支持
if (window._flutter) { try { window._flutter.semanticsEnabled = true; } catch(e){} }
});
const textbox = await page.locator('[role="textbox"]').count();
const btn = await page.locator('[role="button"]').count();
const input = await page.locator('input').count();
console.log(`开启后: role=textbox=${textbox}, role=button=${btn}, input=${input}`);
// 列出 semantics 元素的 label(找用户名/密码/登录按钮)
const labels = await page.evaluate(() => {
return [...document.querySelectorAll('[role="textbox"], [role="button"], [aria-label]')]
.map(e => ({ role: e.getAttribute('role'), label: e.getAttribute('aria-label'), name: e.tagName }))
.filter(x => x.label && x.label.length < 40)
.slice(0, 25);
});
console.log('semantics labels:', JSON.stringify(labels));
await page.screenshot({ path: 'login_semantics.png' });
await browser.close();
})().catch(e => { console.error('失败:', e.message); process.exit(1); });
+27
View File
@@ -0,0 +1,27 @@
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({ viewport: { width: 1280, height: 900 } })).newPage();
await page.goto(BASE + '/login', { waitUntil: 'load', timeout: 60000 });
await page.waitForTimeout(5000);
// JS 直接触发 placeholder click(绕过 viewport 限制)
await page.evaluate(() => {
const el = document.querySelector('flt-semantics-placeholder');
if (el) el.click();
});
await page.waitForTimeout(4000);
const textbox = await page.locator('[role="textbox"]').count();
const input = await page.locator('input').count();
console.log(`JS click 后: role=textbox=${textbox}, input=${input}`);
const labels = await page.evaluate(() =>
[...document.querySelectorAll('[role="textbox"],[role="button"]')]
.map(e => ({ role: e.getAttribute('role'), label: e.getAttribute('aria-label') }))
.filter(x => x.label && x.label.length < 50).slice(0, 30)
);
console.log('labels:', JSON.stringify(labels));
await browser.close();
})().catch(e => { console.error('失败:', e.message); process.exit(1); });
+88
View File
@@ -0,0 +1,88 @@
// e2e 测试:遍历公开路由,捕获 Flutter 错误(print→console 的 CK_ERROR/CK_STACK)
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
// 公开路由(不需登录)。动态参数用合理默认值
const ROUTES = [
'/',
'/shop',
'/shop/310',
'/about-us',
'/contact-us',
'/contact-stores',
'/download',
'/blog/310',
'/view-blog/52',
'/igoshow-learn-more',
'/minipos-learn-more',
'/buy-service/1/pos',
'/forgot-password',
'/new-user',
'/privacy-policy',
'/return-policy',
'/service-policy',
'/end-user-license-agreement',
];
const results = [];
async function testRoute(browser, route) {
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
const page = await ctx.newPage();
const errors = [];
const ckStacks = [];
page.on('pageerror', e => errors.push(`PAGEERROR: ${e.message.slice(0, 200)}`));
page.on('console', m => {
const t = m.text();
if (t.startsWith('CK_ERROR:')) errors.push(t);
if (t.startsWith('CK_STACK:')) {
// 提取项目文件:行号
const proj = t.match(/flutter_wisetronic\/[^\s]+:\d+/g);
if (proj) ckStacks.push([...new Set(proj)].slice(0, 3));
}
});
try {
await page.goto(BASE + route, { waitUntil: 'load', timeout: 30000 });
await page.waitForTimeout(5000); // 等 Flutter 渲染 + API
} catch (e) {
errors.push(`GOTO_ERR: ${e.message.slice(0, 100)}`);
}
const status = errors.length === 0 ? '✓ OK' : `✗ ${errors.length} 错误`;
console.log(`${status.padEnd(12)} ${route}`);
if (errors.length > 0) {
console.log(' 错误: ' + errors.slice(0, 3).join(' | ').slice(0, 250));
if (ckStacks.length > 0) console.log(' 位置: ' + JSON.stringify(ckStacks[0]));
}
results.push({ route, errors, ckStacks });
await ctx.close();
}
(async () => {
console.log(`Flutter e2e 测试:${ROUTES.length} 个公开路由\n`);
const browser = await chromium.launch({ headless: true });
for (const r of ROUTES) {
await testRoute(browser, r);
}
await browser.close();
// 汇总
const failed = results.filter(r => r.errors.length > 0);
console.log(`\n========== 汇总 ==========`);
console.log(`总计 ${results.length} 路由,${failed.length} 个有错误`);
if (failed.length > 0) {
console.log('\n失败路由 + 崩溃位置:');
const locMap = {};
for (const f of failed) {
const loc = f.ckStacks[0] ? f.ckStacks[0][0] : '(无堆栈)';
locMap[loc] = locMap[loc] || [];
locMap[loc].push(f.route);
}
for (const [loc, routes] of Object.entries(locMap)) {
console.log(` ${loc}`);
console.log(` 影响: ${routes.join(', ')}`);
}
}
})();
+94
View File
@@ -0,0 +1,94 @@
// 登录态路由测试:注入 _test_token,autologin 后遍历需登录路由
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
const USER = '6477108200';
const PASS = '8818';
// 需登录路由(动态参数用合理默认)
const AUTH_ROUTES = [
'/me',
'/orders',
'/user-profile',
'/my-cards',
'/my-addresses/310',
'/coupons/310',
'/my-support/310',
'/new-ticket/310',
'/change-password',
'/checkout/310',
// 带动态 id(无效 id 也应优雅处理不崩)
'/orderdetail/1',
'/paynow/1',
'/view-ticket/1',
'/new-comment/1',
'/ocr-scan',
'/renew-license',
'/renew-minioffice/1',
];
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(([u, p]) => { localStorage['_test_token'] = u + '|' + p; }, [USER, PASS]);
// 先访问首页触发 autologin
const page = await ctx.newPage();
let autologinOk = false;
page.on('console', m => { if (m.text().includes('CK_AUTOLOGIN_OK')) autologinOk = true; });
console.log('触发 autologin...');
await page.goto(BASE + '/', { waitUntil: 'load', timeout: 60000 });
await page.waitForSelector('canvas', { timeout: 90000 });
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(8000);
console.log('autologin:', autologinOk ? '✓ 成功' : '✗ 未确认\n');
const results = [];
for (const route of AUTH_ROUTES) {
const errors = [];
const stacks = [];
const pageErrs = [];
page.removeAllListeners('console');
page.removeAllListeners('pageerror');
page.on('console', m => {
const t = m.text();
if (t.startsWith('CK_ERROR:')) errors.push(t.slice(0, 120));
if (t.startsWith('CK_STACK:')) {
const proj = t.match(/flutter_wisetronic\/[^\s]+:\d+/g);
if (proj) stacks.push([...new Set(proj)].slice(0, 2));
}
});
page.on('pageerror', e => pageErrs.push(e.message.slice(0, 120)));
try {
await page.goto(BASE + route, { waitUntil: 'load', timeout: 30000 });
await page.waitForTimeout(6000);
} catch (e) {
errors.push('GOTO: ' + e.message.slice(0, 80));
}
const allErr = [...errors, ...pageErrs];
const status = allErr.length === 0 ? '✓ OK' : `✗ ${allErr.length}`;
console.log(`${status.padEnd(10)} ${route}`);
if (allErr.length) {
console.log(` ${allErr[0].slice(0, 120)}`);
if (stacks.length) console.log(` 位置: ${JSON.stringify(stacks[0])}`);
}
results.push({ route, errors: allErr, stacks });
}
const failed = results.filter(r => r.errors.length > 0);
console.log(`\n========== 汇总 ==========`);
console.log(`${results.length} 个登录态路由,${failed.length} 个有错误`);
if (failed.length) {
const locMap = {};
for (const f of failed) {
const loc = f.stacks[0] ? f.stacks[0][0] : f.errors[0].slice(0, 40);
locMap[loc] = locMap[loc] || [];
locMap[loc].push(f.route);
}
for (const [loc, routes] of Object.entries(locMap)) {
console.log(` ${loc}\n → ${routes.join(', ')}`);
}
}
await browser.close();
})();
+71
View File
@@ -0,0 +1,71 @@
// Mobile 视口测试(width 375,触发 ScreenTypeLayout mobile 分支)
const { chromium } = require('playwright');
const BASE = 'http://127.0.0.1:8099';
const ROUTES = [
'/',
'/shop',
'/shop/310',
'/me',
'/orders',
'/checkout/310',
'/blog/310',
'/view-blog/52',
'/download',
'/about-us',
'/contact-us',
'/user-profile',
'/my-addresses/310',
'/new-user',
];
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 375, height: 812 } }); // iPhone 尺寸
await ctx.addInitScript(([u, p]) => { localStorage['_test_token'] = u + '|' + p; }, ['6477108200', '8818']);
const page = await ctx.newPage();
// 先 autologin
page.on('console', () => {});
await page.goto(BASE + '/', { waitUntil: 'load', timeout: 60000 });
await page.waitForSelector('canvas', { timeout: 90000 });
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(8000);
console.log('Mobile 视口 (375x812) 测试:\n');
const results = [];
for (const route of ROUTES) {
const errors = [];
const stacks = [];
const pageErrs = [];
page.removeAllListeners('console');
page.removeAllListeners('pageerror');
page.on('console', m => {
const t = m.text();
if (t.startsWith('CK_ERROR:')) errors.push(t.slice(0, 100));
if (t.startsWith('CK_STACK:')) {
const proj = t.match(/flutter_wisetronic\/[^\s]+:\d+/g);
if (proj) stacks.push([...new Set(proj)].slice(0, 2));
}
});
page.on('pageerror', e => pageErrs.push(e.message.slice(0, 100)));
try {
await page.goto(BASE + route, { waitUntil: 'load', timeout: 30000 });
await page.waitForTimeout(5000);
} catch (e) { errors.push('GOTO: ' + e.message.slice(0, 70)); }
const allErr = [...errors, ...pageErrs];
const status = allErr.length === 0 ? '✓ OK' : `✗ ${allErr.length}`;
console.log(`${status.padEnd(10)} ${route}`);
if (allErr.length) {
console.log(` ${allErr[0].slice(0, 100)}`);
if (stacks.length) console.log(` 位置: ${JSON.stringify(stacks[0])}`);
}
results.push({ route, errors: allErr, stacks });
}
const failed = results.filter(r => r.errors.length > 0);
console.log(`\n========== 汇总 ==========`);
console.log(`${results.length} 路由 (mobile),${failed.length} 个有错误`);
await browser.close();
})();
+22
View File
@@ -0,0 +1,22 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
// 注入 _test_token(addInitScript 在每个页面加载前执行)
await ctx.addInitScript(() => { localStorage['_test_token'] = '6477108200|8818'; });
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load', timeout: 60000 });
await page.waitForTimeout(8000); // 等 autologin
const ok = logs.filter(l => l.includes('CK_AUTOLOGIN_OK'));
const fail = logs.filter(l => l.includes('CK_AUTOLOGIN_FAIL'));
console.log('autologin:', ok.length ? '✓ OK' : (fail.length ? '✗ FAIL '+fail[0].slice(0,100) : '? 未触发'));
// 访问 /me(登录态)
await page.goto('http://127.0.0.1:8099/me', { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(6000);
const errs = logs.filter(l => l.startsWith('CK_ERROR') || l.includes('PAGEERROR'));
console.log('/me 错误数:', errs.length);
if (errs.length) console.log(' ', errs[0].slice(0,150));
await browser.close();
})();
+16
View File
@@ -0,0 +1,16 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(() => { localStorage['_test_token'] = '6477108200|8818'; });
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load', timeout: 60000 });
await page.waitForTimeout(10000);
// 确认 localStorage 注入 + autologin 日志
const ls = await page.evaluate(() => localStorage['_test_token']);
console.log('localStorage 注入:', ls ? '✓' : '✗');
console.log('autologin 日志:', logs.filter(l=>l.includes('CK_AUTOLOGIN')).join(' | ') || '(无)');
await browser.close();
})();
+20
View File
@@ -0,0 +1,20 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(() => { localStorage['_test_token'] = '6477108200|8818'; });
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
// 第一次访问触发编译
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load', timeout: 60000 });
await page.waitForSelector('canvas', { timeout: 60000 });
await page.waitForTimeout(3000);
console.log('编译完成 canvas:', await page.locator('canvas').count());
// reload 让 main() 重跑(devAutologin)
logs.length = 0;
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(10000);
console.log('reload 后 autologin 日志:', logs.filter(l=>l.includes('CK_AUTOLOGIN')).join(' | ') || '(无)');
await browser.close();
})();
+17
View File
@@ -0,0 +1,17 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(() => { localStorage['_test_token'] = '6477108200|8818'; });
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
page.on('response', r => { if (r.url().includes('access_token')) console.log('登录API:', r.status()); });
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load', timeout: 60000 });
await page.waitForSelector('canvas', { timeout: 60000 });
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(18000);
console.log('所有 CK_AUTOLOGIN 日志:');
logs.filter(l=>l.includes('AUTOLOGIN')).forEach(l => console.log(' ', l.slice(0,120)));
await browser.close();
})();
+16
View File
@@ -0,0 +1,16 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(() => { localStorage['_test_token'] = '6477108200|8818'; });
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
page.on('response', r => { if (r.url().includes('access_token')) console.log(' 登录API:', r.status()); });
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load', timeout: 60000 });
await page.waitForSelector('canvas', { timeout: 60000 });
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(25000);
console.log('autologin 日志:', logs.filter(l=>l.includes('CK_AUTOLOGIN')).join(' | ') || '(无)');
await browser.close();
})();
+18
View File
@@ -0,0 +1,18 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
await ctx.addInitScript(() => { localStorage['_test_token'] = '6477108200|8818'; });
const page = await ctx.newPage();
const logs = [];
page.on('console', m => logs.push(m.text()));
page.on('request', r => { if (r.url().includes('access_token')) console.log(' →请求发出:', r.url().slice(-40)); });
page.on('response', r => { if (r.url().includes('access_token')) console.log(' ←响应:', r.status()); });
page.on('requestfailed', r => { if (r.url().includes('access_token') || r.url().includes('api.minipos')) console.log(' ✗请求失败:', r.url().slice(-50), r.failure()?.errorText); });
await page.goto('http://127.0.0.1:8099/', { waitUntil: 'load', timeout: 60000 });
await page.waitForSelector('canvas', { timeout: 90000 });
await page.reload({ waitUntil: 'load' });
await page.waitForTimeout(15000); // 等 3s 延迟 + autologin
console.log('autologin 日志:', logs.filter(l=>l.includes('CK_AUTOLOGIN')).join(' | ') || '(无)');
await browser.close();
})();
+16
View File
@@ -0,0 +1,16 @@
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.launch({ headless: true });
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
const errs = [];
page.on('console', m => { if (m.text().includes('RangeError')||m.text().startsWith('CK_ERROR')) errs.push(m.text().slice(0,120)); });
page.on('response', r => { if (r.url().includes('get-buy-service')) console.log(' API:', r.url().split('get-buy-service')[1], '→', r.status()); });
// 试几个 service 参数
for (const p of ['/buy-service/1/ministore', '/buy-service/1/igoshow', '/buy-service/310/ministore']) {
errs.length = 0;
await page.goto('http://127.0.0.1:8099'+p, { waitUntil: 'load' }).catch(()=>{});
await page.waitForTimeout(6000);
console.log(p, errs.length ? '✗ '+errs[0] : '✓ OK');
}
await browser.close();
})();