From 33b54fe45447fae73e00d9eed02a3df7544499bd Mon Sep 17 00:00:00 2001 From: peima Date: Mon, 3 Aug 2026 01:50:12 +0800 Subject: [PATCH] =?UTF-8?q?feat(e2e):=20autologin=20patch=20(dev)=20+=20?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=80=81=E8=B7=AF=E7=94=B1=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=20-=20util=5Fweb=20devAutologin:=20localStor?= =?UTF-8?q?age[=5Ftest=5Ftoken]=3Duser|pass=20->=20=E7=99=BB=E5=BD=95API?= =?UTF-8?q?=E5=86=99Hive+dispatch=20currentUser=20-=20=E5=BB=B6=E8=BF=9F3s?= =?UTF-8?q?=E6=89=A7=E8=A1=8C=E7=AD=89=20Flutter/Hive=20ready=20(=E5=90=A6?= =?UTF-8?q?=E5=88=99=20httpPost=20hang)=20-=20main.dart=20=E8=B0=83?= =?UTF-8?q?=E7=94=A8=20devAutologin=20-=20run=5Fauth.js:=20=E6=B3=A8?= =?UTF-8?q?=E5=85=A5token=E9=81=8D=E5=8E=86=E7=99=BB=E5=BD=95=E6=80=81?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=20-=20=E5=B7=B2=E9=AA=8C=E8=AF=81:=20autolog?= =?UTF-8?q?in=20API=20200=20OK?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 1 + lib/utils/util_io.dart | 3 ++ lib/utils/util_web.dart | 22 +++++++++ tools/e2e/run_auth.js | 86 ++++++++++++++++++++++++++++++++++++ tools/e2e/test_autologin.js | 22 +++++++++ tools/e2e/test_autologin2.js | 16 +++++++ tools/e2e/test_autologin3.js | 20 +++++++++ tools/e2e/test_autologin4.js | 17 +++++++ tools/e2e/test_autologin5.js | 16 +++++++ tools/e2e/test_autologin6.js | 18 ++++++++ 10 files changed, 221 insertions(+) create mode 100644 tools/e2e/run_auth.js create mode 100644 tools/e2e/test_autologin.js create mode 100644 tools/e2e/test_autologin2.js create mode 100644 tools/e2e/test_autologin3.js create mode 100644 tools/e2e/test_autologin4.js create mode 100644 tools/e2e/test_autologin5.js create mode 100644 tools/e2e/test_autologin6.js diff --git a/lib/main.dart b/lib/main.dart index b2b92cd..a658a53 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -43,6 +43,7 @@ void main() { // configureApp(); setPathUrlStrategy(); runApp(MyApp()); + Future.delayed(Duration(seconds: 3), () => Util.devAutologin()); // DEV e2e: 延迟 3s 等 Flutter/Hive ready } class MyApp extends StatelessWidget { diff --git a/lib/utils/util_io.dart b/lib/utils/util_io.dart index e56d007..760a8ac 100644 --- a/lib/utils/util_io.dart +++ b/lib/utils/util_io.dart @@ -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:')) { diff --git a/lib/utils/util_web.dart b/lib/utils/util_web.dart index 0593658..9b5bd02 100644 --- a/lib/utils/util_web.dart +++ b/lib/utils/util_web.dart @@ -41,6 +41,28 @@ class Util { return box; } + /// DEV ONLY (e2e tests): 读 localStorage['_test_token'] = 'username|password', + /// 调登录 API 写 Hive + dispatch currentUser。测试后移除。 + static Future 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, diff --git a/tools/e2e/run_auth.js b/tools/e2e/run_auth.js new file mode 100644 index 0000000..7a7aa65 --- /dev/null +++ b/tools/e2e/run_auth.js @@ -0,0 +1,86 @@ +// 登录态路由测试:注入 _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', +]; + +(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(); +})(); diff --git a/tools/e2e/test_autologin.js b/tools/e2e/test_autologin.js new file mode 100644 index 0000000..e89b37b --- /dev/null +++ b/tools/e2e/test_autologin.js @@ -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(); +})(); diff --git a/tools/e2e/test_autologin2.js b/tools/e2e/test_autologin2.js new file mode 100644 index 0000000..5093ea9 --- /dev/null +++ b/tools/e2e/test_autologin2.js @@ -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(); +})(); diff --git a/tools/e2e/test_autologin3.js b/tools/e2e/test_autologin3.js new file mode 100644 index 0000000..0539036 --- /dev/null +++ b/tools/e2e/test_autologin3.js @@ -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(); +})(); diff --git a/tools/e2e/test_autologin4.js b/tools/e2e/test_autologin4.js new file mode 100644 index 0000000..77039bc --- /dev/null +++ b/tools/e2e/test_autologin4.js @@ -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(); +})(); diff --git a/tools/e2e/test_autologin5.js b/tools/e2e/test_autologin5.js new file mode 100644 index 0000000..e9a692d --- /dev/null +++ b/tools/e2e/test_autologin5.js @@ -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(); +})(); diff --git a/tools/e2e/test_autologin6.js b/tools/e2e/test_autologin6.js new file mode 100644 index 0000000..10e2e79 --- /dev/null +++ b/tools/e2e/test_autologin6.js @@ -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(); +})();