195 lines
7.0 KiB
TypeScript
195 lines
7.0 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
test('HomePage - Full DOM & Visual Analysis', async ({ page }) => {
|
|
// Navigate to home
|
|
await page.goto('/', { waitUntil: 'networkidle' })
|
|
|
|
// Take screenshot
|
|
await page.screenshot({ path: 'D:\\Temp\\homepage-full.png', fullPage: true })
|
|
console.log('✅ Screenshot saved: D:\\Temp\\homepage-full.png')
|
|
|
|
// DOM Analysis
|
|
const domData = await page.evaluate(() => {
|
|
const totalElements = document.querySelectorAll('*').length
|
|
const visibleElements = Array.from(document.querySelectorAll('*')).filter(el => {
|
|
const style = window.getComputedStyle(el)
|
|
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0'
|
|
}).length
|
|
|
|
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6')
|
|
const buttons = document.querySelectorAll('button')
|
|
const links = document.querySelectorAll('a')
|
|
const images = document.querySelectorAll('img')
|
|
const cards = document.querySelectorAll('[class*="card"], [class*="Card"]')
|
|
|
|
// Text content
|
|
const textContent = document.body.innerText
|
|
const wordCount = textContent.split(/\s+/).filter(w => w.length > 0).length
|
|
|
|
// Extract heading texts
|
|
const headingTexts = Array.from(headings).map(h => ({
|
|
tag: h.tagName,
|
|
text: h.textContent?.trim().substring(0, 50),
|
|
}))
|
|
|
|
// Extract button texts
|
|
const buttonTexts = Array.from(buttons).map(b => ({
|
|
text: b.textContent?.trim(),
|
|
type: b.getAttribute('type') || 'button',
|
|
}))
|
|
|
|
// Extract link texts
|
|
const linkTexts = Array.from(links).map(l => ({
|
|
text: l.textContent?.trim().substring(0, 30),
|
|
href: l.getAttribute('href'),
|
|
}))
|
|
|
|
return {
|
|
totalElements,
|
|
visibleElements,
|
|
headingCount: headings.length,
|
|
headingTexts,
|
|
buttonCount: buttons.length,
|
|
buttonTexts,
|
|
linkCount: links.length,
|
|
linkTexts: linkTexts.slice(0, 10),
|
|
imageCount: images.length,
|
|
cardCount: cards.length,
|
|
wordCount,
|
|
}
|
|
})
|
|
|
|
console.log('\n=== HomePage DOM Analysis ===')
|
|
console.log(`Total Elements: ${domData.totalElements}`)
|
|
console.log(`Visible Elements: ${domData.visibleElements}`)
|
|
console.log(`Headings: ${domData.headingCount}`)
|
|
console.log(
|
|
'Heading Texts:',
|
|
domData.headingTexts.map(h => `${h.tag}: "${h.text}"`).join(' | ')
|
|
)
|
|
console.log(`\nButtons: ${domData.buttonCount}`)
|
|
domData.buttonTexts.forEach((b, i) => {
|
|
console.log(` ${i + 1}. "${b.text}" (type: ${b.type})`)
|
|
})
|
|
console.log(`\nLinks: ${domData.linkCount}`)
|
|
domData.linkTexts.forEach((l, i) => {
|
|
console.log(` ${i + 1}. "${l.text}" → ${l.href}`)
|
|
})
|
|
console.log(`\nImages: ${domData.imageCount}`)
|
|
console.log(`Cards/Components: ${domData.cardCount}`)
|
|
console.log(`Word Count: ${domData.wordCount}`)
|
|
|
|
// Accessibility Check
|
|
const a11yData = await page.evaluate(() => {
|
|
const elementsWithAria = document.querySelectorAll('[aria-label], [aria-describedby], [role]')
|
|
const imagesWithAlt = document.querySelectorAll('img[alt]')
|
|
const buttonsWithAriaOrText = Array.from(document.querySelectorAll('button')).filter(
|
|
b => b.getAttribute('aria-label') || b.textContent?.trim().length > 0
|
|
)
|
|
const linksWithAriaOrText = Array.from(document.querySelectorAll('a')).filter(
|
|
l => l.getAttribute('aria-label') || l.textContent?.trim().length > 0
|
|
)
|
|
const hasLandmarks = !!document.querySelector('main, nav[aria-label], aside[aria-label]')
|
|
const skipLink = document.querySelector('a.ks-skip')
|
|
|
|
return {
|
|
ariaElements: elementsWithAria.length,
|
|
imagesWithAlt: imagesWithAlt.length,
|
|
totalImages: document.querySelectorAll('img').length,
|
|
accessibleButtons: buttonsWithAriaOrText.length,
|
|
totalButtons: document.querySelectorAll('button').length,
|
|
accessibleLinks: linksWithAriaOrText.length,
|
|
totalLinks: document.querySelectorAll('a').length,
|
|
hasLandmarks,
|
|
hasSkipLink: !!skipLink,
|
|
}
|
|
})
|
|
|
|
console.log('\n=== Accessibility Assessment ===')
|
|
console.log(`ARIA/Role Elements: ${a11yData.ariaElements}`)
|
|
console.log(`Images with Alt Text: ${a11yData.imagesWithAlt}/${a11yData.totalImages}`)
|
|
console.log(`Accessible Buttons: ${a11yData.accessibleButtons}/${a11yData.totalButtons}`)
|
|
console.log(`Accessible Links: ${a11yData.accessibleLinks}/${a11yData.totalLinks}`)
|
|
console.log(`Has Landmarks: ${a11yData.hasLandmarks}`)
|
|
console.log(`Has Skip Link: ${a11yData.hasSkipLink}`)
|
|
|
|
// Color & Typography Check
|
|
const styleData = await page.evaluate(() => {
|
|
const h1 = document.querySelector('h1')
|
|
const buttons = document.querySelectorAll('button')
|
|
const cards = document.querySelectorAll('[class*="card"], [class*="Card"]')
|
|
|
|
const h1Style = h1
|
|
? {
|
|
fontSize: window.getComputedStyle(h1).fontSize,
|
|
fontWeight: window.getComputedStyle(h1).fontWeight,
|
|
color: window.getComputedStyle(h1).color,
|
|
}
|
|
: null
|
|
|
|
const buttonStyles = Array.from(buttons)
|
|
.slice(0, 3)
|
|
.map(b => ({
|
|
text: b.textContent?.trim().substring(0, 20),
|
|
backgroundColor: window.getComputedStyle(b).backgroundColor,
|
|
color: window.getComputedStyle(b).color,
|
|
padding: window.getComputedStyle(b).padding,
|
|
minHeight: window.getComputedStyle(b).minHeight,
|
|
}))
|
|
|
|
return {
|
|
h1Style,
|
|
buttonStyles,
|
|
cardCount: cards.length,
|
|
}
|
|
})
|
|
|
|
console.log('\n=== Typography & Styling ===')
|
|
console.log('H1:', styleData.h1Style)
|
|
console.log('Button Samples:')
|
|
styleData.buttonStyles.forEach((b, i) => {
|
|
console.log(` ${i + 1}. "${b.text}" | bg: ${b.backgroundColor} | color: ${b.color} | padding: ${b.padding}`)
|
|
})
|
|
|
|
// Completeness Score Calculation
|
|
const completenessScore = (() => {
|
|
let score = 0
|
|
|
|
// DOM structure (max 25)
|
|
score += Math.min((domData.visibleElements / 150) * 25, 25)
|
|
|
|
// Content (max 20)
|
|
score += Math.min((domData.wordCount / 300) * 20, 20)
|
|
|
|
// Interactivity (max 20)
|
|
score += Math.min(((domData.buttonCount + domData.linkCount) / 15) * 20, 20)
|
|
|
|
// Accessibility (max 20)
|
|
const a11yScore =
|
|
(a11yData.imagesWithAlt / Math.max(a11yData.totalImages, 1)) * 5 +
|
|
(a11yData.accessibleButtons / Math.max(a11yData.totalButtons, 1)) * 5 +
|
|
(a11yData.accessibleLinks / Math.max(a11yData.totalLinks, 1)) * 5 +
|
|
(a11yData.hasLandmarks ? 3 : 0) +
|
|
(a11yData.hasSkipLink ? 2 : 0)
|
|
score += Math.min(a11yScore, 20)
|
|
|
|
// Visual hierarchy (max 15)
|
|
score += styleData.h1Style ? 8 : 0
|
|
score += styleData.cardCount > 0 ? 7 : 0
|
|
|
|
return Math.min(score, 100)
|
|
})()
|
|
|
|
console.log(`\n=== Completeness Score ===`)
|
|
console.log(`Overall: ${completenessScore.toFixed(1)}%`)
|
|
console.log(
|
|
`Status: ${completenessScore >= 90 ? '✅ COMMERCIAL-GRADE' : completenessScore >= 80 ? '⚠️ GOOD' : '❌ NEEDS WORK'}`
|
|
)
|
|
|
|
// Expectations
|
|
expect(domData.visibleElements).toBeGreaterThan(80)
|
|
expect(domData.headingCount).toBeGreaterThan(0)
|
|
expect(domData.buttonCount).toBeGreaterThan(0)
|
|
expect(a11yData.hasLandmarks).toBe(true)
|
|
})
|