@casoon/auditmysite
Version:
A comprehensive command-line tool for automated accessibility, security, performance, and SEO testing using Playwright and pa11y, based on sitemap URLs
132 lines (108 loc) • 5.92 kB
text/typescript
import { BaseAccessibilityTest, TestContext, TestResult } from '../base-test';
export class SeoContentTest extends BaseAccessibilityTest {
name = 'SEO Content Test';
description = 'Checks content quality, headings, and text optimization';
category = 'seo';
priority = 'medium';
standards = ['SEO Best Practices'];
async run(context: TestContext): Promise<TestResult> {
const { page } = context;
try {
const result = await this.evaluateOnPage(page, () => {
const issues: string[] = [];
const warnings: string[] = [];
// Check for main content
const mainContent = document.querySelector('main') || document.querySelector('article') || document.querySelector('.content') || document.querySelector('#content');
if (!mainContent) {
warnings.push('No main content area identified (consider using <main> or <article> tags)');
}
// Check heading hierarchy
const headings = document.querySelectorAll('h1, h2, h3, h4, h5, h6');
const h1Elements = document.querySelectorAll('h1');
const h2Elements = document.querySelectorAll('h2');
const h3Elements = document.querySelectorAll('h3');
// Check for H1
if (h1Elements.length === 0) {
issues.push('Missing H1 heading (required for SEO)');
} else if (h1Elements.length > 1) {
warnings.push(`Multiple H1 headings found (${h1Elements.length}), should be only one per page`);
}
// Check heading hierarchy
if (h1Elements.length > 0 && h2Elements.length === 0 && h3Elements.length > 0) {
warnings.push('Heading hierarchy skipped: H1 followed by H3 (should be H1 → H2 → H3)');
}
// Check content length
const bodyText = document.body.innerText || '';
const wordCount = bodyText.trim().split(/\s+/).length;
if (wordCount < 300) {
warnings.push(`Content is too short (${wordCount} words, recommended minimum 300 words)`);
} else if (wordCount > 2000) {
warnings.push(`Content is very long (${wordCount} words, consider breaking into multiple pages)`);
}
// Check for images with alt text (SEO aspect)
const images = document.querySelectorAll('img');
const imagesWithoutAlt = Array.from(images).filter(img => !img.hasAttribute('alt'));
if (imagesWithoutAlt.length > 0) {
warnings.push(`${imagesWithoutAlt.length} images missing alt text (important for SEO and accessibility)`);
}
// Check for internal links
const internalLinks = Array.from(document.querySelectorAll('a[href]')).filter(link => {
const href = link.getAttribute('href') || '';
return href.startsWith('/') || href.startsWith(window.location.origin);
});
if (internalLinks.length === 0) {
warnings.push('No internal links found (recommended for SEO and user experience)');
}
// Check for external links
const externalLinks = Array.from(document.querySelectorAll('a[href]')).filter(link => {
const href = link.getAttribute('href') || '';
return href.startsWith('http') && !href.startsWith(window.location.origin);
});
// Check if external links have rel="noopener"
const externalLinksWithoutNoopener = externalLinks.filter(link => !link.hasAttribute('rel') || !link.getAttribute('rel')?.includes('noopener'));
if (externalLinksWithoutNoopener.length > 0) {
warnings.push(`${externalLinksWithoutNoopener.length} external links missing rel="noopener" (security best practice)`);
}
// Check for structured data (JSON-LD)
const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
if (jsonLdScripts.length === 0) {
warnings.push('No structured data (JSON-LD) found (recommended for rich snippets)');
}
// Check for schema markup
const schemaElements = document.querySelectorAll('[itemtype]');
if (schemaElements.length === 0) {
warnings.push('No schema markup found (recommended for better search engine understanding)');
}
// Check for breadcrumbs
const breadcrumbs = document.querySelector('[class*="breadcrumb"], [id*="breadcrumb"], nav[aria-label*="breadcrumb"]');
if (!breadcrumbs) {
warnings.push('No breadcrumb navigation found (recommended for SEO and user experience)');
}
// Check for pagination
const pagination = document.querySelector('[class*="pagination"], [class*="pager"], nav[aria-label*="pagination"]');
if (!pagination && wordCount > 1500) {
warnings.push('Long content without pagination (consider pagination for better user experience)');
}
// Check for language attribute
const htmlLang = document.documentElement.getAttribute('lang');
if (!htmlLang) {
issues.push('Missing language attribute on <html> tag (required for SEO and accessibility)');
}
// Check for reading time estimation
const readingTimeMinutes = Math.ceil(wordCount / 200); // Average reading speed
if (readingTimeMinutes > 10) {
warnings.push(`Estimated reading time: ${readingTimeMinutes} minutes (consider breaking into shorter sections)`);
}
return { issues, warnings };
});
return this.createResult(
result.issues.length === 0,
result.issues.length + result.warnings.length,
result.issues,
result.warnings
);
} catch (error) {
return this.createResult(false, 1, [`SEO Content Test failed: ${error}`], []);
}
}
}