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
This commit is contained in:
@@ -43,6 +43,7 @@ void main() {
|
|||||||
// configureApp();
|
// configureApp();
|
||||||
setPathUrlStrategy();
|
setPathUrlStrategy();
|
||||||
runApp(MyApp());
|
runApp(MyApp());
|
||||||
|
Future.delayed(Duration(seconds: 3), () => Util.devAutologin()); // DEV e2e: 延迟 3s 等 Flutter/Hive ready
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
|
|||||||
@@ -199,6 +199,9 @@ class Util {
|
|||||||
return box;
|
return box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// DEV ONLY stub (原生不自动登录)
|
||||||
|
static void devAutologin() {}
|
||||||
|
|
||||||
static Widget showImage(String imageUrl, {double? width, double? height,
|
static Widget showImage(String imageUrl, {double? width, double? height,
|
||||||
BoxFit? fit, Widget Function(BuildContext, String, dynamic)? errorWidget}) {
|
BoxFit? fit, Widget Function(BuildContext, String, dynamic)? errorWidget}) {
|
||||||
if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
|
if (imageUrl != null && imageUrl.isNotEmpty && imageUrl.startsWith('https:')) {
|
||||||
|
|||||||
@@ -41,6 +41,28 @@ class Util {
|
|||||||
return box;
|
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,
|
static Widget showImage(String imageUrl, {double? width, double? height,
|
||||||
BoxFit? fit, Widget Function(BuildContext, String?, dynamic)? errorWidget}) {
|
BoxFit? fit, Widget Function(BuildContext, String?, dynamic)? errorWidget}) {
|
||||||
return Image.network(imageUrl,
|
return Image.network(imageUrl,
|
||||||
|
|||||||
86
tools/e2e/run_auth.js
Normal file
86
tools/e2e/run_auth.js
Normal file
@@ -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();
|
||||||
|
})();
|
||||||
22
tools/e2e/test_autologin.js
Normal file
22
tools/e2e/test_autologin.js
Normal 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
tools/e2e/test_autologin2.js
Normal file
16
tools/e2e/test_autologin2.js
Normal 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
tools/e2e/test_autologin3.js
Normal file
20
tools/e2e/test_autologin3.js
Normal 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
tools/e2e/test_autologin4.js
Normal file
17
tools/e2e/test_autologin4.js
Normal 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
tools/e2e/test_autologin5.js
Normal file
16
tools/e2e/test_autologin5.js
Normal 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
tools/e2e/test_autologin6.js
Normal file
18
tools/e2e/test_autologin6.js
Normal 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();
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user