248 lines
7.1 KiB
JavaScript
248 lines
7.1 KiB
JavaScript
/**
|
|
* 任务:查看热门
|
|
*
|
|
* 逻辑:
|
|
* 1. 找到"查看热门"任务卡片
|
|
* 2. 点击 toComplete 打开热门页面
|
|
* 3. 点击第一个热门项目卡片
|
|
* 4. 在新页面停留后关闭
|
|
*/
|
|
|
|
const path = require('path');
|
|
const logger = require(path.join(__dirname, '..', 'utils', 'logger'));
|
|
const {
|
|
openNewPage,
|
|
openLinkPage,
|
|
safeClose,
|
|
waitPageReady,
|
|
waitForElements,
|
|
getPageInfo,
|
|
} = require(path.join(__dirname, '..', 'utils', 'page'));
|
|
const { runDebugSteps } = require(path.join(__dirname, '..', 'utils', 'debugSteps'));
|
|
|
|
const PROJECT_CARD_SELECTORS = [
|
|
'.dynamics-grid a.home-repo-card__link',
|
|
'.dynamic-card-project a.home-repo-card__link',
|
|
'a.home-repo-card__link[href*="source_module=dynamics"]',
|
|
'a[href*="source_module=dynamics"]',
|
|
];
|
|
|
|
/**
|
|
* 执行查看热门任务
|
|
* @param {import('playwright').Page} page - 主页面
|
|
*/
|
|
async function dailyTrending(page, options = {}) {
|
|
logger.info('👉 执行查看热门');
|
|
|
|
const ctx = {
|
|
page,
|
|
trendingTask: null,
|
|
trendingPage: null,
|
|
cards: [],
|
|
detailPage: null,
|
|
};
|
|
|
|
const steps = [
|
|
{
|
|
name: '查找查看热门任务卡片',
|
|
page: (state) => state.page,
|
|
run: async (state) => {
|
|
const tasks = await state.page.locator('.novice-task-item-box').all();
|
|
state.trendingTask = await findTaskByTitle(tasks, '查看热门');
|
|
if (!state.trendingTask) {
|
|
throw new Error('未找到"查看热门"任务卡片');
|
|
}
|
|
},
|
|
inspect: async (state) => ({
|
|
taskCardCount: await state.page.locator('.novice-task-item-box').count(),
|
|
titleLabelCount: await state.page.locator('.task-card-title__label-text').count(),
|
|
toCompleteCount: await state.page.locator('.toComplete').count(),
|
|
}),
|
|
},
|
|
{
|
|
name: '点击去完成并打开热门页',
|
|
page: (state) => state.trendingPage || state.page,
|
|
run: async (state) => {
|
|
state.trendingPage = await openNewPage(
|
|
state.page.context(),
|
|
() => state.trendingTask.locator('.toComplete').click()
|
|
);
|
|
state.currentPage = state.trendingPage;
|
|
},
|
|
inspect: async (state) => ({
|
|
taskToCompleteVisible: await state.trendingTask.locator('.toComplete').isVisible().catch(() => false),
|
|
}),
|
|
},
|
|
{
|
|
name: '查找热门项目卡片',
|
|
page: (state) => state.trendingPage,
|
|
run: async (state) => {
|
|
const targetPage = state.trendingPage;
|
|
|
|
if (!targetPage || targetPage.isClosed()) {
|
|
throw new Error('热门页面不存在或已经关闭');
|
|
}
|
|
|
|
state.currentPage = targetPage;
|
|
|
|
logger.info(`查找热门项目卡片,当前页面: ${targetPage.url()}`);
|
|
|
|
// 等待热门项目区域出现
|
|
await targetPage
|
|
.locator('.dynamics-grid')
|
|
.waitFor({
|
|
state: 'visible',
|
|
timeout: 15000,
|
|
});
|
|
|
|
state.cards = await findProjectCards(targetPage);
|
|
|
|
if (state.cards.length === 0) {
|
|
throw new Error('未找到热门项目卡片');
|
|
}
|
|
|
|
logger.info(`找到热门项目卡片: ${state.cards.length} 个`);
|
|
},
|
|
inspect: async (state) => ({
|
|
currentUrl: state.trendingPage?.url(),
|
|
dynamicsGridCount: await state.trendingPage
|
|
.locator('.dynamics-grid')
|
|
.count(),
|
|
dynamicCardCount: await state.trendingPage
|
|
.locator('.dynamics-grid .dynamic-card')
|
|
.count(),
|
|
repoCardCount: await state.trendingPage
|
|
.locator('.dynamics-grid .home-repo-card')
|
|
.count(),
|
|
repoLinkCount: await state.trendingPage
|
|
.locator('.dynamics-grid a.home-repo-card__link')
|
|
.count(),
|
|
dynamicsSourceLinkCount: await state.trendingPage
|
|
.locator('a[href*="source_module=dynamics"]')
|
|
.count(),
|
|
}),
|
|
},
|
|
{
|
|
name: '打开第一个热门项目详情页',
|
|
page: (state) => state.trendingPage,
|
|
run: async (state) => {
|
|
const firstCard = state.cards[0];
|
|
|
|
if (!firstCard) {
|
|
throw new Error('没有找到可点击的热门项目卡片');
|
|
}
|
|
|
|
await firstCard.scrollIntoViewIfNeeded();
|
|
|
|
const href = await firstCard.getAttribute('href');
|
|
logger.info(`准备点击热门项目: ${href}`);
|
|
|
|
const context = state.trendingPage.context();
|
|
|
|
const [detailPage] = await Promise.all([
|
|
context.waitForEvent('page', {
|
|
timeout: 15000,
|
|
}),
|
|
firstCard.click({
|
|
timeout: 15000,
|
|
}),
|
|
]);
|
|
|
|
state.detailPage = detailPage;
|
|
state.currentPage = detailPage;
|
|
|
|
await waitPageReady(detailPage);
|
|
|
|
logger.info(`热门项目详情页已打开: ${detailPage.url()}`);
|
|
},
|
|
inspect: async (state) => ({
|
|
cardCount: state.cards.length,
|
|
firstCardHref: state.cards[0]
|
|
? await state.cards[0].getAttribute('href')
|
|
: null,
|
|
}),
|
|
},
|
|
{
|
|
name: '停留热门项目详情页',
|
|
page: (state) => state.detailPage,
|
|
run: async (state) => {
|
|
if (!state.detailPage || state.detailPage.isClosed()) {
|
|
throw new Error('热门项目详情页不存在或已关闭');
|
|
}
|
|
|
|
await waitPageReady(state.detailPage);
|
|
|
|
// 给页面曝光统计、埋点和任务接口留出时间
|
|
await state.detailPage.waitForTimeout(10000);
|
|
|
|
logger.info(
|
|
`已在热门项目详情页停留10秒: ${state.detailPage.url()}`
|
|
);
|
|
},
|
|
inspect: async (state) => ({
|
|
detailPageUrl: state.detailPage?.url(),
|
|
detailPageTitle: state.detailPage
|
|
? await state.detailPage.title().catch(() => '')
|
|
: '',
|
|
}),
|
|
},
|
|
];
|
|
|
|
try {
|
|
await runDebugSteps('查看热门', steps, ctx, options);
|
|
await ctx.page.waitForTimeout(5000);
|
|
} finally {
|
|
await safeClose(ctx.detailPage);
|
|
await safeClose(ctx.trendingPage);
|
|
}
|
|
}
|
|
|
|
async function findProjectCards(page) {
|
|
logger.info(`findProjectCards 实际页面: ${page.url()}`);
|
|
for (const selector of PROJECT_CARD_SELECTORS) {
|
|
logger.info(`尝试选择器: "${selector}"`);
|
|
const locator = page.locator(selector);
|
|
try {
|
|
await locator.first().waitFor({
|
|
state: 'visible',
|
|
timeout: 5000,
|
|
});
|
|
const count = await locator.count();
|
|
if (count > 0) {
|
|
logger.info(`选择器 "${selector}" 找到 ${count} 个热门项目链接`);
|
|
|
|
return Array.from(
|
|
{ length: count },
|
|
(_, index) => locator.nth(index)
|
|
);
|
|
}
|
|
} catch (_) {
|
|
logger.info(`选择器 "${selector}" 未找到元素`);
|
|
}
|
|
}
|
|
const info = await getPageInfo(page);
|
|
throw new Error(
|
|
`未找到热门项目卡片,页面结构可能已变化。` +
|
|
`页面标题: ${info.title}, 页面URL: ${info.url}`
|
|
);
|
|
}
|
|
|
|
/**
|
|
* 根据标题查找任务卡片
|
|
*/
|
|
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 = dailyTrending;
|