Files
playwright-gitcode/tasks/dailyStar.js
2026-07-15 10:05:26 +08:00

224 lines
6.3 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.
/**
* 任务每日Star
*
* 逻辑:
* 1. 找到"每日Star"任务卡片
* 2. 点击 toComplete 打开热门列表
* 3. 随机选择一个热门项目
* 4. 进入项目详情页
* 5. 找到 Star/Starred 按钮
* 6. 如果已 Starred → 先取消再重新 Star
* 7. 如果未 Star → 直接 Star
*
* 定位器优化:
* - 优先文本匹配Star / Starred
* - 不依赖固定 nth 索引
* - 自动打印页面 title 和 URL
* - 找不到按钮抛异常
*/
const path = require('path');
const logger = require(path.join(__dirname, '..', 'utils', 'logger'));
const {
openNewPage,
openLinkPage,
safeClose,
getPageInfo,
waitForElements,
} = require(path.join(__dirname, '..', 'utils', 'page'));
const PROJECT_CARD_SELECTORS = [
'a[href*="/"][href*="?source_module="]',
];
/**
* 执行每日Star任务
* @param {import('playwright').Page} page - 主页面
*/
async function dailyStar(page) {
logger.info('👉 执行每日Star');
// 从主页面找到"每日Star"任务卡片
const tasks = await page.locator('.novice-task-item-box').all();
const starTask = await findTaskByTitle(tasks, '每日Star');
if (!starTask) {
throw new Error('未找到"每日Star"任务卡片');
}
// ── 第一步:打开热门项目列表页 ──
const listPage = await openNewPage(
page.context(),
() => starTask.locator('.toComplete').click()
);
try {
const cards = await findProjectCards(listPage);
const count = cards.length;
logger.info(`热门项目数量: ${count}`);
if (count === 0) {
throw new Error('没有找到热门项目');
}
// 随机选择一个项目
const index = Math.floor(Math.random() * count);
logger.info(`随机选择项目索引: ${index}`);
// ── 第二步:打开项目详情页 ──
const projectPage = await openLinkPage(
listPage.context(),
cards[index],
{ label: 'Star 项目详情页' }
);
try {
// 打印页面信息
const info = await getPageInfo(projectPage);
logger.info(`项目页面: ${info.title}`);
logger.info(`项目URL: ${info.url}`);
// ── 第三步:找到 Star/Starred 按钮 ──
await doStarOperation(projectPage);
} finally {
await safeClose(projectPage);
}
} finally {
await safeClose(listPage);
}
}
async function findProjectCards(page) {
const cards = await waitForElements(page, PROJECT_CARD_SELECTORS, {
interval: 1000,
maxRetries: 15,
minCount: 1,
});
if (cards.length > 0) {
return cards;
}
const info = await getPageInfo(page);
throw new Error(
`未找到热门项目卡片,页面结构可能已变化。页面标题: ${info.title}, 页面URL: ${info.url}`
);
}
/**
* 执行 Star/Unstar 操作
*
* 定位策略:
* 1. 在 #repo-header-tab 区域内查找包含 "Star" 或 "Starred" 文本的按钮
* 2. 优先匹配 .status-btn 类
* 3. 使用文本过滤而非 nth 索引
*
* @param {import('playwright').Page} page
*/
async function doStarOperation(page) {
// 等待页面加载
await page.waitForLoadState('domcontentloaded', { timeout: 30000 }).catch(() => {});
logger.info('项目详情页加载完成');
// 定位 Star 按钮区域
const headerTab = page.locator('#repo-header-tab');
await headerTab.waitFor({ state: 'attached', timeout: 15000 }).catch(() => {
// 如果 #repo-header-tab 不存在,尝试更通用的定位
logger.warn('#repo-header-tab 未找到,尝试更通用的定位方式');
});
// 查找 Star/Starred 按钮
// 策略1: #repo-header-tab 下的 .status-btn
let starBtn = page.locator('#repo-header-tab .status-btn').filter({
hasText: /Star/i,
});
let btnCount = await starBtn.count();
logger.info(`#repo-header-tab .status-btn 含 Star 文本的按钮数量: ${btnCount}`);
// 策略2: 如果策略1没找到全局搜索含 Star 的按钮
if (btnCount === 0) {
logger.warn('未在 #repo-header-tab 下找到 Star 按钮,全局搜索...');
starBtn = page.locator('button, a, .status-btn, .btn').filter({
hasText: /Star/i,
});
btnCount = await starBtn.count();
logger.info(`全局搜索含 Star 文本的按钮数量: ${btnCount}`);
}
// 策略3: 包含 ⭐ 或 svg star 图标
if (btnCount === 0) {
logger.warn('按文本未找到,尝试通过 aria-label 匹配...');
starBtn = page.locator('[aria-label*="star" i], [aria-label*="Star" i]');
btnCount = await starBtn.count();
logger.info(`通过 aria-label 找到的按钮数量: ${btnCount}`);
}
if (btnCount === 0) {
// 最终失败:截图并抛异常
const pageInfo = await getPageInfo(page);
throw new Error(
`Star按钮不存在\n` +
`页面标题: ${pageInfo.title}\n` +
`页面URL: ${pageInfo.url}\n` +
`共尝试了3种定位策略均未找到 Star/Starred 按钮`
);
}
// 取第一个匹配的按钮
const btn = starBtn.nth(btnCount > 1 ? 1 : 0);
const btnText = await btn.innerText().catch(() => '(获取文本失败)');
logger.info(`找到按钮,文本内容: "${btnText}"`);
// 判断当前状态
const isStarred = btnText.toLowerCase().includes('starred');
if (isStarred) {
// 已 Starred → 先取消
logger.info('当前状态: 已 Starred先取消');
await btn.click();
// 等待按钮文本变为 Star
await page.waitForTimeout(800);
const newText = await btn.innerText().catch(() => '');
logger.info(`取消后按钮文本: "${newText}"`);
}
// 点击 Star
logger.info('点击 Star 按钮');
await btn.click();
// 确认操作成功(等待按钮文本变化)
await page.waitForTimeout(1000);
const finalText = await btn.innerText().catch(() => '');
logger.info(`Star操作完成当前按钮文本: "${finalText}"`);
if (finalText.toLowerCase().includes('starred')) {
logger.info('✅ Star 操作成功确认');
} else {
logger.warn('Star 操作后按钮状态未确认,但操作已执行');
}
}
/**
* 根据标题查找任务卡片
*/
async function findTaskByTitle(tasks, title) {
for (const task of tasks) {
try {
const text = await task.locator('.task-card-title__label-text').innerText();
if (text.includes(title)) {
return task;
}
} catch (_) {
// 跳过
}
}
return null;
}
module.exports = dailyStar;