feat(e2e): 添加完整的E2E测试框架和测试用例

添加Playwright测试框架配置和基础页面对象
实现冒烟测试用例覆盖首页和联系页面核心功能
更新导航组件以支持滚动高亮功能
添加BackButton组件统一返回按钮行为
配置Woodpecker CI集成和测试报告生成
This commit is contained in:
张翔
2026-02-27 10:30:33 +08:00
parent 4a616fe96e
commit 5d5b7feb0a
50 changed files with 6765 additions and 46 deletions
+31 -5
View File
@@ -12,12 +12,29 @@ import { useFocusTrap } from '@/hooks/use-focus-trap';
export function Header() {
const [isOpen, setIsOpen] = useState(false);
const [isScrolled, setIsScrolled] = useState(false);
const [activeSection, setActiveSection] = useState('home');
const pathname = usePathname();
const focusTrapRef = useFocusTrap<HTMLDivElement>(isOpen);
useEffect(() => {
const handleScroll = () => {
setIsScrolled(window.scrollY > 20);
if (pathname === '/') {
const sections = ['home', 'services', 'products', 'cases', 'about', 'news', 'contact'];
const scrollPosition = window.scrollY + 100;
for (const sectionId of sections) {
const element = document.getElementById(sectionId);
if (element) {
const { offsetTop, offsetHeight } = element;
if (scrollPosition >= offsetTop && scrollPosition < offsetTop + offsetHeight) {
setActiveSection(sectionId);
break;
}
}
}
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
@@ -26,7 +43,7 @@ export function Header() {
return () => {
window.removeEventListener('scroll', handleScroll);
};
}, []);
}, [pathname]);
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
@@ -38,6 +55,15 @@ export function Header() {
}
}, [isOpen]);
const isActive = (item: typeof NAVIGATION[number]) => {
if (pathname === '/') {
return activeSection === item.id;
}
const navPath = item.href.split('#')[0];
return pathname === navPath || pathname.startsWith(navPath + '/');
};
return (
<>
<header
@@ -72,15 +98,15 @@ export function Header() {
relative px-3 py-1.5 text-sm font-medium
transition-all duration-300
focus:outline-none focus:ring-2 focus:ring-[#C41E3A] focus:ring-offset-2 rounded-sm
${pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href))
${isActive(item)
? 'text-[#1C1C1C]'
: 'text-[#3D3D3D] hover:text-[#1C1C1C]'
}
`}
aria-current={pathname === item.href ? 'page' : undefined}
aria-current={isActive(item) ? 'page' : undefined}
>
{item.label}
{(pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href))) && (
{isActive(item) && (
<motion.span
layoutId="activeNav"
className="absolute bottom-0 left-1/2 -translate-x-1/2 w-6 h-0.5 bg-[#C41E3A] rounded-full"
@@ -154,7 +180,7 @@ export function Header() {
transition-all duration-300
border-l-2
focus:outline-none focus:ring-2 focus:ring-[#C41E3A] focus:ring-inset
${pathname === item.href || (item.href !== '/' && pathname.startsWith(item.href))
${isActive(item)
? 'text-[#1C1C1C] border-[#C41E3A] bg-[#FEF2F4]'
: 'text-[#3D3D3D] border-transparent hover:text-[#1C1C1C] hover:bg-[#F5F5F5]'
}