|
@@ -114,7 +114,7 @@ export class EnterpriseMiniPage {
|
|
|
* 填写手机号
|
|
* 填写手机号
|
|
|
* @param phone 手机号(11位数字)
|
|
* @param phone 手机号(11位数字)
|
|
|
*
|
|
*
|
|
|
- * 注意:使用 click + type 方法触发自然的用户输入事件
|
|
|
|
|
|
|
+ * 注意:使用 fill() 方法并添加验证步骤确保密码输入完整
|
|
|
* Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
|
|
* Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
|
|
|
*/
|
|
*/
|
|
|
async fillPhone(phone: string): Promise<void> {
|
|
async fillPhone(phone: string): Promise<void> {
|
|
@@ -138,16 +138,24 @@ export class EnterpriseMiniPage {
|
|
|
* Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
|
|
* Taro Input 组件需要完整的事件流才能正确更新 react-hook-form 状态
|
|
|
*/
|
|
*/
|
|
|
async fillPassword(password: string): Promise<void> {
|
|
async fillPassword(password: string): Promise<void> {
|
|
|
- // 先移除覆盖层,确保输入可操作
|
|
|
|
|
await this.removeDevOverlays();
|
|
await this.removeDevOverlays();
|
|
|
- // 点击聚焦
|
|
|
|
|
await this.passwordInput.click();
|
|
await this.passwordInput.click();
|
|
|
- // 等待元素聚焦
|
|
|
|
|
await this.page.waitForTimeout(100);
|
|
await this.page.waitForTimeout(100);
|
|
|
- // 使用 type 方法输入
|
|
|
|
|
- await this.passwordInput.type(password, { delay: 50 });
|
|
|
|
|
- // 等待表单验证更新
|
|
|
|
|
- await this.page.waitForTimeout(200);
|
|
|
|
|
|
|
+ // 使用 fill 方法一次性填充
|
|
|
|
|
+ await this.passwordInput.fill(password);
|
|
|
|
|
+ await this.page.waitForTimeout(300);
|
|
|
|
|
+ // 验证输入值
|
|
|
|
|
+ const actualValue = await this.passwordInput.inputValue();
|
|
|
|
|
+ if (actualValue !== password) {
|
|
|
|
|
+ // 使用 JS 直接设置
|
|
|
|
|
+ await this.passwordInput.evaluate((el, val) => {
|
|
|
|
|
+ const input = el as HTMLInputElement;
|
|
|
|
|
+ input.value = val;
|
|
|
|
|
+ input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
|
|
|
+ input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
|
|
|
+ }, password);
|
|
|
|
|
+ await this.page.waitForTimeout(200);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -179,14 +187,18 @@ export class EnterpriseMiniPage {
|
|
|
// 小程序登录成功后会跳转到 dashboard 页面
|
|
// 小程序登录成功后会跳转到 dashboard 页面
|
|
|
|
|
|
|
|
// 等待 URL 变化,使用 Promise.race 实现超时
|
|
// 等待 URL 变化,使用 Promise.race 实现超时
|
|
|
- await this.page.waitForURL(
|
|
|
|
|
|
|
+ const urlChanged = await this.page.waitForURL(
|
|
|
url => url.pathname.includes('/dashboard') || url.pathname.includes('/pages/yongren/dashboard'),
|
|
url => url.pathname.includes('/dashboard') || url.pathname.includes('/pages/yongren/dashboard'),
|
|
|
{ timeout: TIMEOUTS.PAGE_LOAD }
|
|
{ timeout: TIMEOUTS.PAGE_LOAD }
|
|
|
- ).catch(() => {
|
|
|
|
|
- // 如果没有跳转,检查是否显示用户信息
|
|
|
|
|
- // 注意:此验证将在 Story 12.5 E2E 测试中完全实现
|
|
|
|
|
- // 当前仅提供基础结构
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ ).then(() => true).catch(() => false);
|
|
|
|
|
+
|
|
|
|
|
+ // 如果 URL 没有变化,检查 token 是否被存储
|
|
|
|
|
+ if (!urlChanged) {
|
|
|
|
|
+ const token = await this.getToken();
|
|
|
|
|
+ if (!token) {
|
|
|
|
|
+ throw new Error('登录失败:URL 未跳转且 token 未存储');
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -220,7 +232,8 @@ export class EnterpriseMiniPage {
|
|
|
* @returns token 字符串,如果不存在则返回 null
|
|
* @returns token 字符串,如果不存在则返回 null
|
|
|
*
|
|
*
|
|
|
* 注意:Taro.getStorageSync 在 H5 环境下映射到 localStorage
|
|
* 注意:Taro.getStorageSync 在 H5 环境下映射到 localStorage
|
|
|
- * token 直接存储为字符串,不是 JSON 格式
|
|
|
|
|
|
|
+ * Taro.setStorageSync 会将数据包装为 JSON 格式:{"data":"VALUE"}
|
|
|
|
|
+ * 因此需要解析 JSON 并提取 data 字段
|
|
|
*
|
|
*
|
|
|
* Taro H5 可能使用以下键名格式:
|
|
* Taro H5 可能使用以下键名格式:
|
|
|
* - 直接键名: 'enterprise_token'
|
|
* - 直接键名: 'enterprise_token'
|
|
@@ -229,33 +242,63 @@ export class EnterpriseMiniPage {
|
|
|
*/
|
|
*/
|
|
|
async getToken(): Promise<string | null> {
|
|
async getToken(): Promise<string | null> {
|
|
|
const result = await this.page.evaluate(() => {
|
|
const result = await this.page.evaluate(() => {
|
|
|
- // 获取所有 localStorage 键
|
|
|
|
|
- const keys = Object.keys(localStorage);
|
|
|
|
|
- const storage: Record<string, string> = {};
|
|
|
|
|
- keys.forEach(k => storage[k] = localStorage.getItem(k) || '');
|
|
|
|
|
-
|
|
|
|
|
// 尝试各种可能的键名
|
|
// 尝试各种可能的键名
|
|
|
- // 1. 直接键名
|
|
|
|
|
|
|
+ // 1. 直接键名 - Taro 的 setStorageSync 将数据包装为 {"data":"VALUE"}
|
|
|
const token = localStorage.getItem('enterprise_token');
|
|
const token = localStorage.getItem('enterprise_token');
|
|
|
- if (token) return token;
|
|
|
|
|
|
|
+ if (token) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Taro 格式: {"data":"JWT_TOKEN"}
|
|
|
|
|
+ const parsed = JSON.parse(token);
|
|
|
|
|
+ if (parsed.data) {
|
|
|
|
|
+ return parsed.data;
|
|
|
|
|
+ }
|
|
|
|
|
+ return token;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ return token;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 2. 带前缀的键名(Taro 可能使用前缀)
|
|
|
|
|
|
|
+ // 2. 获取所有 localStorage 键,查找可能的 token
|
|
|
|
|
+ const keys = Object.keys(localStorage);
|
|
|
const prefixedKeys = keys.filter(k => k.includes('token') || k.includes('auth'));
|
|
const prefixedKeys = keys.filter(k => k.includes('token') || k.includes('auth'));
|
|
|
for (const key of prefixedKeys) {
|
|
for (const key of prefixedKeys) {
|
|
|
const value = localStorage.getItem(key);
|
|
const value = localStorage.getItem(key);
|
|
|
- if (value && value.length > 20) { // JWT token 通常很长
|
|
|
|
|
- return value;
|
|
|
|
|
|
|
+ if (value) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 尝试解析 Taro 格式
|
|
|
|
|
+ const parsed = JSON.parse(value);
|
|
|
|
|
+ if (parsed.data && parsed.data.length > 20) { // JWT token 通常很长
|
|
|
|
|
+ return parsed.data;
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ // 不是 JSON 格式,直接使用
|
|
|
|
|
+ if (value.length > 20) {
|
|
|
|
|
+ return value;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 3. 其他常见键名
|
|
// 3. 其他常见键名
|
|
|
- return (
|
|
|
|
|
- localStorage.getItem('token') ||
|
|
|
|
|
- localStorage.getItem('auth_token') ||
|
|
|
|
|
- sessionStorage.getItem('token') ||
|
|
|
|
|
- sessionStorage.getItem('auth_token') ||
|
|
|
|
|
- null
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ const otherTokens = [
|
|
|
|
|
+ localStorage.getItem('token'),
|
|
|
|
|
+ localStorage.getItem('auth_token'),
|
|
|
|
|
+ sessionStorage.getItem('token'),
|
|
|
|
|
+ sessionStorage.getItem('auth_token')
|
|
|
|
|
+ ].filter(Boolean);
|
|
|
|
|
+
|
|
|
|
|
+ for (const t of otherTokens) {
|
|
|
|
|
+ if (t) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ const parsed = JSON.parse(t);
|
|
|
|
|
+ if (parsed.data) return parsed.data;
|
|
|
|
|
+ } catch {
|
|
|
|
|
+ if (t.length > 20) return t;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return null;
|
|
|
});
|
|
});
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|