import random import re from typing import Dict, List from playwright.sync_api import Page, TimeoutError as PlaywrightTimeoutError from dulizhan.test_case.Resource.UI.base_page import BasePage class NewsPage(BasePage): """Joyhub News 相关页面对象。""" COOKIE_ACCEPT_TEXT_PATTERN = re.compile(r"^(Accepet|Accept|I Accept|Agree|Got it)$", re.I) NEWS_LINK_TEXT_PATTERN = re.compile(r"^News$", re.I) NEWS_ROUTE = "/news" def __init__(self, page: Page, base_url: str): super().__init__(page) self.base_url = base_url.rstrip("/") def open_home(self) -> None: self.goto(self.base_url) def accept_cookie_if_present(self) -> None: accept_button = self.page.get_by_role("button", name=self.COOKIE_ACCEPT_TEXT_PATTERN) self.click_if_visible(accept_button, timeout=3000) def enter_news_page(self) -> None: news_link = self.page.get_by_role("link", name=self.NEWS_LINK_TEXT_PATTERN) try: if news_link.first.is_visible(timeout=5000): news_link.first.click() self.wait_for_network_idle() return except Exception: pass self.goto(f"{self.base_url}{self.NEWS_ROUTE}") def is_news_context(self) -> bool: url = self.page.url.lower() title = self.page.title().lower() if "news" in url: return True if "news" in title: return True if self.visible_text_contains("News"): return True return False def _collect_clickable_news_candidates(self) -> List[Dict[str, str]]: script = """ () => { const navTexts = new Set([ 'home', 'download the app', 'rewards', 'support', 'about us', 'discover', 'following', 'partnerships', 'faqs', 'login' ]); const currentUrl = new URL(window.location.href); const anchors = Array.from(document.querySelectorAll('a[href]')); function isVisible(el) { const style = window.getComputedStyle(el); const rect = el.getBoundingClientRect(); return style && style.visibility !== 'hidden' && style.display !== 'none' && rect.width > 0 && rect.height > 0; } function hasContentContainer(el) { return !!el.closest( 'article, [class*="article" i], [class*="card" i], [class*="news" i], [class*="post" i], [class*="blog" i], [class*="item" i]' ); } function isBadProtocol(url) { return ['javascript:', 'mailto:', 'tel:'].includes(url.protocol); } function isLikelySocial(url) { const host = url.hostname.toLowerCase(); return [ 'facebook.com', 'instagram.com', 'twitter.com', 'x.com', 'youtube.com', 'tiktok.com', 'linkedin.com' ].some(domain => host.includes(domain)); } const candidates = []; for (const a of anchors) { if (!isVisible(a)) continue; let url; try { url = new URL(a.href, window.location.origin); } catch (e) { continue; } if (isBadProtocol(url)) continue; if (isLikelySocial(url)) continue; if (url.href === currentUrl.href) continue; const text = (a.innerText || a.textContent || '').trim().replace(/\\s+/g, ' '); const textLower = text.toLowerCase(); const pathLower = url.pathname.toLowerCase(); if (navTexts.has(textLower)) continue; if (url.pathname === '/' || url.pathname === '') continue; const hrefLooksLikeContent = /news|article|blog|post|detail|story/i.test(pathLower) && pathLower !== '/news'; const containerLooksLikeContent = hasContentContainer(a); if (hrefLooksLikeContent || containerLooksLikeContent) { candidates.push({ href: url.href, text: text, path: url.pathname, reason: hrefLooksLikeContent ? 'href_content_pattern' : 'content_container' }); } } const seen = new Set(); return candidates.filter(item => { if (seen.has(item.href)) return false; seen.add(item.href); return true; }); } """ return self.page.evaluate(script) def click_random_news_item(self) -> Dict[str, str]: self.wait_for_network_idle() candidates = self._collect_clickable_news_candidates() if not candidates: raise AssertionError( "未发现可点击的 news 内容候选。" "请检查 News 页面是否加载成功,或为 news 卡片补充稳定 selector/data-testid。" ) selected = random.choice(candidates) href = selected["href"] old_url = self.page.url click_script = """ (targetHref) => { const anchors = Array.from(document.querySelectorAll('a[href]')); const target = anchors.find(a => { try { return new URL(a.href, window.location.origin).href === targetHref; } catch (e) { return false; } }); if (!target) { throw new Error('Target news link not found: ' + targetHref); } target.scrollIntoView({block: 'center', inline: 'center'}); target.click(); } """ self.page.evaluate(click_script, href) try: self.page.wait_for_url(lambda url: str(url) != old_url, timeout=15000) except PlaywrightTimeoutError: pass self.wait_for_network_idle() return selected def screenshot_news_content(self, screenshot_dir: str, file_name: str) -> str: return self.screenshot(screenshot_dir=screenshot_dir, file_name=file_name, full_page=True)