Files
playwright-gitcode/utils/login.js
2026-07-06 09:34:39 +08:00

186 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 登录模块
* 核心函数 ensureLogin(page, context)
*
* 流程:
* 1. 访问 积分中心页面
* 2. 检测 .g-user-avatar 是否存在
* 3. 如果已登录 → 返回 true
* 4. 如果未登录 → 执行账号密码登录 → 保存 state.json
* 5. 登录失败 → 抛出异常,阻止后续任务
*
* 【日志规范】每个 await 前后都有日志,定位卡顿时精确到行
*/
const config = require('../config');
const logger = require('./logger');
const { saveState } = require('./browser');
const { safeGoto } = require('./page');
/**
* 确保已登录,若未登录则执行登录流程
* @param {import('playwright').Page} page
* @param {import('playwright').BrowserContext} context
* @returns {Promise<boolean>} 登录是否成功
* @throws {Error} 登录失败时抛出异常
*/
async function ensureLogin(page, context) {
logger.divider();
logger.info('===== 开始检查登录状态 =====');
// ── 步骤1: 访问积分中心页面 ──
logger.info('[step 1/8] 正在访问积分中心页面...');
const pageReady = await safeGoto(page, config.urls.points, {
label: '积分中心',
timeout: config.navigationTimeout,
waitFor: [
'.g-user-avatar',
'text=密码登录',
],
});
if (!pageReady) {
logger.warn('[step 1/8] ⚠️ 关键元素未全部出现,尝试继续...');
} else {
logger.info('[step 1/8] ✅ 积分中心页面加载完成');
}
// ── 步骤2: 打印当前页面信息 ──
logger.info('[step 2/8] 获取当前页面信息...');
try {
const currentUrl = page.url();
const pageTitle = await page.title();
logger.info(` → 当前URL: ${currentUrl}`);
logger.info(` → 页面标题: ${pageTitle}`);
} catch (err) {
logger.warn(` → 获取页面信息失败: ${err.message}`);
}
// ── 步骤3: 检测登录状态 ──
logger.info('[step 3/8] 检测登录状态(查找 .g-user-avatar...');
let isLoggedIn = false;
try {
const avatarLocator = page.locator('.g-user-avatar');
logger.info(' → 调用 isVisible()...');
isLoggedIn = await avatarLocator.isVisible();
logger.info(` → isVisible() 返回: ${isLoggedIn}`);
} catch (err) {
logger.warn(` → 检测登录状态时出错: ${err.message}`);
isLoggedIn = false;
}
if (isLoggedIn) {
logger.info('✅ 已登录,无需执行登录流程');
return true;
}
// ── 步骤4: 未登录,开始登录 ──
logger.info('❌ 未登录,开始执行登录流程');
logger.info('[step 4/8] 等待页面稳定 (waitForTimeout 1000ms)...');
await page.waitForTimeout(1000);
try {
// ── 步骤4a: 点击密码登录 ──
logger.info('[step 4a] 点击"密码登录"标签...');
const pwdLoginBtn = page.getByText('密码登录');
logger.info(' → 等待密码登录按钮可见...');
await pwdLoginBtn.waitFor({ state: 'visible', timeout: 10000 });
logger.info(' → 点击密码登录按钮...');
await pwdLoginBtn.click();
logger.info('✅ 已切换到密码登录方式');
// ── 步骤4b: 填写用户名 ──
logger.info('[step 4b] 填写用户名...');
const usernameInput = page.getByPlaceholder('请填写手机号/用户名/邮箱');
logger.info(' → 等待输入框可见...');
await usernameInput.waitFor({ state: 'visible', timeout: 10000 });
logger.info(` → 输入用户名: ${config.login.username}`);
await usernameInput.fill(config.login.username);
logger.info('✅ 用户名已填写');
// ── 步骤4c: 填写密码 ──
logger.info('[step 4c] 填写密码...');
const passwordInput = page.getByPlaceholder('请填写密码');
logger.info(' → 等待密码输入框可见...');
await passwordInput.waitFor({ state: 'visible', timeout: 10000 });
logger.info(' → 输入密码 (已隐藏)');
await passwordInput.fill(config.login.password);
logger.info('✅ 密码已填写');
// ── 步骤4d: 勾选协议 ──
logger.info('[step 4d] 勾选同意协议...');
const agree1 = page.getByText('登录或注册完成代表你同意');
logger.info(' → 点击第一个勾选项...');
await agree1.click();
const agree2 = page.getByText('我同意将账号、组织、仓库等信息提供给AtomGit进行 ');
logger.info(' → 点击第二个勾选项...');
await agree2.click();
logger.info('✅ 协议已勾选');
// ── 步骤4e: 点击登录按钮 ──
logger.info('[step 4e] 点击登录按钮...');
const loginBtn = page.locator('button:has-text("登 录")');
logger.info(' → 等待登录按钮可见...');
await loginBtn.waitFor({ state: 'visible', timeout: 10000 });
logger.info(' → 点击登录按钮...');
await loginBtn.click();
logger.info('✅ 登录按钮已点击,等待跳转...');
// ── 步骤4f: 等待跳转 ──
logger.info('[step 4f] 等待页面跳转到积分中心...');
logger.info(` → waitForURL('**/setting/points'), timeout: ${config.navigationTimeout}ms`);
await page.waitForURL('**/setting/points', {
timeout: config.navigationTimeout,
});
logger.info('✅ 页面已跳转到积分中心');
// ── 步骤4g: 验证登录成功 ──
logger.info('[step 4g] 验证登录成功(查找 .g-user-avatar...');
logger.info(` → waitForSelector('.g-user-avatar'), timeout: ${config.navigationTimeout}ms`);
await page.waitForSelector('.g-user-avatar', {
timeout: config.navigationTimeout,
});
logger.info('✅ 登录成功,用户头像已出现');
// ── 步骤5: 保存状态 ──
logger.info('[step 5/8] 保存登录状态到 state.json...');
await saveState(context);
logger.info('✅ state.json 已保存');
return true;
} catch (err) {
// 登录失败:截图并抛异常
logger.error(`❌ 登录流程异常: ${err.message}`);
const screenshotPath = await takeScreenshot(page, 'login-failed');
logger.info(`截图已保存: ${screenshotPath}`);
throw new Error(`登录失败,无法继续执行任务: ${err.message}`);
}
}
/**
* 页面截图辅助
* @param {import('playwright').Page} page
* @param {string} label 截图标签
* @returns {Promise<string>} 截图文件路径
*/
async function takeScreenshot(page, label) {
const fs = require('fs');
const path = require('path');
const config = require('../config');
const dir = path.resolve(config.dirs.screenshots);
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
const filename = `error-${label}-${Date.now()}.png`;
const filepath = path.join(dir, filename);
logger.info(' → 正在截图...');
await page.screenshot({ path: filepath, fullPage: true }).catch(() => { });
return filepath;
}
module.exports = {
ensureLogin,
};