Add UI automation test cases and webapp-testing skill
This commit is contained in:
200
dulizhan/test_case/Resource/UI/news_page.py
Normal file
200
dulizhan/test_case/Resource/UI/news_page.py
Normal file
@@ -0,0 +1,200 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user