201 lines
8.1 KiB
TypeScript
201 lines
8.1 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
|
|
test.describe('모든 페이지 종합 검증 (AGENTS.md v16.0)', () => {
|
|
// 1. HomePage 검증
|
|
test('1. HomePage - 국내 기준 상용 수준 검증', async ({ page }) => {
|
|
await page.goto('/', { waitUntil: 'networkidle' })
|
|
await page.screenshot({ path: 'D:\\Temp\\home-validation.png', fullPage: true })
|
|
|
|
const analysis = await page.evaluate(() => {
|
|
const h1 = document.querySelector('h1')?.textContent || ''
|
|
const sections = document.querySelectorAll('section').length
|
|
const ctas = document.querySelectorAll('a[href*="/model-ops"], a[href*="/governance"]').length
|
|
const trustedElements = document.querySelectorAll('[class*="trust"], [class*="stat"]').length
|
|
|
|
return {
|
|
heroTitle: h1.substring(0, 50),
|
|
sections,
|
|
ctas,
|
|
trustElements: trustedElements,
|
|
visibleText: document.body.innerText.length,
|
|
}
|
|
})
|
|
|
|
console.log('\n✅ HomePage 분석:')
|
|
console.log(` Hero Title: "${analysis.heroTitle}"`)
|
|
console.log(` Sections: ${analysis.sections}`)
|
|
console.log(` CTAs: ${analysis.ctas}`)
|
|
console.log(` Trust Elements: ${analysis.trustElements}`)
|
|
console.log(` Text Length: ${analysis.visibleText}`)
|
|
|
|
expect(analysis.sections).toBeGreaterThan(5)
|
|
expect(analysis.ctas).toBeGreaterThan(2)
|
|
})
|
|
|
|
// 2. ModelList 검증
|
|
test('2. ModelList - 마스터 디테일 레이아웃 검증', async ({ page }) => {
|
|
await page.goto('/model-ops/models-master', { waitUntil: 'networkidle' })
|
|
await page.screenshot({ path: 'D:\\Temp\\models-validation.png', fullPage: true })
|
|
|
|
const analysis = await page.evaluate(() => {
|
|
const title = document.querySelector('h1')?.textContent || ''
|
|
const masterItems = document.querySelectorAll('[class*="list"], [class*="item"]').length
|
|
const detailPanel = document.querySelector('[class*="detail"]')?.textContent?.length || 0
|
|
const metrics = document.querySelectorAll('[class*="metric"], [class*="stat"]').length
|
|
const actions = document.querySelectorAll('button, a[class*="btn"], a[class*="action"]').length
|
|
|
|
return {
|
|
title: title.substring(0, 40),
|
|
masterItems,
|
|
detailPanelTextLength: detailPanel,
|
|
metrics,
|
|
actions,
|
|
}
|
|
})
|
|
|
|
console.log('\n✅ ModelList 분석:')
|
|
console.log(` Title: "${analysis.title}"`)
|
|
console.log(` Master Items: ${analysis.masterItems}`)
|
|
console.log(` Detail Panel Content: ${analysis.detailPanelTextLength} chars`)
|
|
console.log(` Metrics Displayed: ${analysis.metrics}`)
|
|
console.log(` Action Buttons: ${analysis.actions}`)
|
|
|
|
expect(analysis.masterItems).toBeGreaterThan(0)
|
|
expect(analysis.actions).toBeGreaterThan(0)
|
|
})
|
|
|
|
// 3. ShadowRunQueue 검증
|
|
test('3. ShadowRunQueue - 작업 모니터링 검증', async ({ page }) => {
|
|
await page.goto('/model-ops/shadow-run-jobs', { waitUntil: 'networkidle' })
|
|
await page.screenshot({ path: 'D:\\Temp\\shadowrun-validation.png', fullPage: true })
|
|
|
|
const analysis = await page.evaluate(() => {
|
|
const title = document.querySelector('h1')?.textContent || ''
|
|
const statCards = document.querySelectorAll('[class*="stat"]').length
|
|
const jobCards = document.querySelectorAll('[class*="card"], [class*="job"], article').length
|
|
const progressBars = document.querySelectorAll('progress, [class*="progress"], [class*="bar"]').length
|
|
const statusBadges = document.querySelectorAll('[class*="badge"], [class*="status"]').length
|
|
const filters = document.querySelectorAll('input, select, [class*="filter"]').length
|
|
|
|
return {
|
|
title: title.substring(0, 40),
|
|
stats: statCards,
|
|
jobs: jobCards,
|
|
progress: progressBars,
|
|
statusBadges,
|
|
filters,
|
|
}
|
|
})
|
|
|
|
console.log('\n✅ ShadowRunQueue 분석:')
|
|
console.log(` Title: "${analysis.title}"`)
|
|
console.log(` Status Cards: ${analysis.stats}`)
|
|
console.log(` Job Cards: ${analysis.jobs}`)
|
|
console.log(` Progress Bars: ${analysis.progress}`)
|
|
console.log(` Status Badges: ${analysis.statusBadges}`)
|
|
console.log(` Filters: ${analysis.filters}`)
|
|
|
|
expect(analysis.jobs).toBeGreaterThan(0)
|
|
expect(analysis.statusBadges).toBeGreaterThan(0)
|
|
})
|
|
|
|
// 4. ApprovalQueue 검증
|
|
test('4. ApprovalQueue - 승인 워크플로우 검증', async ({ page }) => {
|
|
await page.goto('/governance/approvals', { waitUntil: 'networkidle' })
|
|
await page.screenshot({ path: 'D:\\Temp\\approval-validation.png', fullPage: true })
|
|
|
|
const analysis = await page.evaluate(() => {
|
|
const title = document.querySelector('h1')?.textContent || ''
|
|
const statCards = document.querySelectorAll('[class*="stat"]').length
|
|
const requestItems = document.querySelectorAll('[class*="request"], [class*="item"], article').length
|
|
const approveButtons = Array.from(document.querySelectorAll('button, a')).filter(el =>
|
|
el.textContent?.includes('승인') || el.textContent?.includes('Approve')
|
|
).length
|
|
const rejectButtons = Array.from(document.querySelectorAll('button, a')).filter(el =>
|
|
el.textContent?.includes('거부') || el.textContent?.includes('Reject')
|
|
).length
|
|
const metrics = document.querySelectorAll('[class*="metric"], [class*="validation"]').length
|
|
|
|
return {
|
|
title: title.substring(0, 40),
|
|
stats: statCards,
|
|
requests: requestItems,
|
|
approveActions: approveButtons,
|
|
rejectActions: rejectButtons,
|
|
metrics,
|
|
}
|
|
})
|
|
|
|
console.log('\n✅ ApprovalQueue 분석:')
|
|
console.log(` Title: "${analysis.title}"`)
|
|
console.log(` Status Cards: ${analysis.stats}`)
|
|
console.log(` Request Items: ${analysis.requests}`)
|
|
console.log(` Approve Actions: ${analysis.approveActions}`)
|
|
console.log(` Reject Actions: ${analysis.rejectActions}`)
|
|
console.log(` Validation Metrics: ${analysis.metrics}`)
|
|
|
|
expect(analysis.requests).toBeGreaterThan(0)
|
|
})
|
|
|
|
// 5. 종합 평가
|
|
test('5. 종합 평가 - AGENTS.md v16.0 준수도', async ({ page }) => {
|
|
const pages = [
|
|
{ name: 'HomePage', url: '/' },
|
|
{ name: 'ModelList', url: '/model-ops/models-master' },
|
|
{ name: 'ShadowRunQueue', url: '/model-ops/shadow-run-jobs' },
|
|
{ name: 'ApprovalQueue', url: '/governance/approvals' },
|
|
]
|
|
|
|
const results = []
|
|
|
|
for (const pageInfo of pages) {
|
|
await page.goto(pageInfo.url, { waitUntil: 'networkidle' })
|
|
|
|
const metrics = await page.evaluate(() => {
|
|
// SOLID: 컴포넌트 책임 분리 확인
|
|
const uniqueClasses = new Set(
|
|
Array.from(document.querySelectorAll('*')).map(el => el.className)
|
|
)
|
|
|
|
// 필요성 주도: 불필요한 요소 확인
|
|
const allElements = document.querySelectorAll('*').length
|
|
const visibleElements = Array.from(document.querySelectorAll('*')).filter(el => {
|
|
const style = window.getComputedStyle(el)
|
|
return style.display !== 'none' && style.visibility !== 'hidden'
|
|
}).length
|
|
|
|
// 접근성: ARIA 속성
|
|
const ariaElements = document.querySelectorAll('[aria-label], [role], [aria-describedby]').length
|
|
|
|
// 반응형: 미디어 쿼리 감지
|
|
const hasResponsiveDesign = window.innerWidth < 1024
|
|
? document.querySelectorAll('[class*="mobile"], [class*="responsive"]').length > 0
|
|
: true
|
|
|
|
return {
|
|
solidScore: Math.min(uniqueClasses.size / 20, 1) * 100,
|
|
necessityScore: Math.min(visibleElements / allElements, 1) * 100,
|
|
accessibilityScore: Math.min(ariaElements / 20, 1) * 100,
|
|
responsiveReady: hasResponsiveDesign,
|
|
}
|
|
})
|
|
|
|
results.push({
|
|
page: pageInfo.name,
|
|
...metrics,
|
|
})
|
|
}
|
|
|
|
console.log('\n📊 종합 평가:')
|
|
results.forEach(r => {
|
|
console.log(`\n${r.page}:`)
|
|
console.log(` SOLID: ${r.solidScore.toFixed(1)}%`)
|
|
console.log(` 필요성 주도: ${r.necessityScore.toFixed(1)}%`)
|
|
console.log(` 접근성: ${r.accessibilityScore.toFixed(1)}%`)
|
|
console.log(` 반응형: ${r.responsiveReady ? '✅' : '⚠️'}`)
|
|
})
|
|
|
|
expect(results.length).toBe(4)
|
|
})
|
|
})
|