wordpress-live-validation
WordPress Live Validation Skill
Overview
This skill loads a real WordPress post in a Playwright headless browser and verifies that what readers see matches what was uploaded. The browser is the source of truth -- REST API success does not guarantee correct rendering.
Browser backend selection: Playwright MCP (default) is used for automated validation and CI/CD. Use Chrome DevTools MCP when the user explicitly asks to "check in my browser" or "debug live", or when the task involves Lighthouse audits or performance profiling.
Instructions
Constraints (Always Applied)
-
Read-Only Observation Only: Never click, type, fill forms, or modify anything on the WordPress site. This is observation-only validation—any write action risks mutating published content.
-
Evidence-Based Reporting: Every check result must reference a concrete artifact (DOM value, network response, screenshot path). "Looks fine" is not acceptable. Report what the browser shows, not assumptions.
-
Non-Blocking Reports: Failed validation produces a report but does not revert the upload or block the pipeline. The user decides how to act on findings.
-
Severity Classification (enforce strictly):
- BLOCKER: Readers see broken content (missing title, broken images, placeholder text, wrong H1)
- WARNING: Degraded quality but functional (missing OG tags, JS errors, responsive overflow)
- INFO: Informational only (rendered values without comparison baseline)
- Never inflate or deflate—alert fatigue and hidden problems are equal harms.
-
Browser Availability: Requires either Playwright MCP or Chrome DevTools MCP. If neither is available, exit in Phase 1 with a skip report. Do not retry.
-
Default Behaviors (ON):
- Run all check categories (content integrity, SEO/social, responsive)
- Test three breakpoints: mobile (375px), tablet (768px), desktop (1440px)
- Save screenshots at each breakpoint as evidence
- Exclude known benign console patterns: ad networks (doubleclick, googlesyndication), analytics (gtag, fbevents), consent managers (cookiebot, onetrust)
- Try content selectors in order:
article→.entry-content→.post-content→main
-
Optional Behaviors (OFF unless enabled):
- Draft preview mode (requires authenticated WordPress session)
- Custom content selector override
- Strict mode (treat all WARNINGs as BLOCKERs)
- OG image fetch verification (navigates to og:image URL to check 200 response)
-
Input Requirements:
- WordPress post URL (from wordpress-uploader, direct user input, or
{WORDPRESS_SITE}/?p={post_id}&preview=true) - Optional: expected title (for title match comparison)
- Optional: expected H2 count (for structure comparison)
- Optional: custom content selector
- WordPress post URL (from wordpress-uploader, direct user input, or
Phase 1: NAVIGATE
Goal: Load the WordPress post and confirm the content area is present.
Step 1: Verify browser MCP availability
Before any browser operation, test Playwright tools are accessible. If unavailable, exit with skip report immediately rather than failing later.
Step 2: Navigate to the post URL
Use browser_navigate with a full HTTPS URL.
Step 3: Wait for content area
Use browser_wait_for with the content selector. Try selectors in order:
article(most WordPress themes).entry-content(classic themes).post-content(premium themes)main(fallback)
Use custom selector if provided.
Step 4: Remove cookie/consent banners (if present)
Use browser_evaluate to remove visual overlays (DOM removal only—does not interact with tracking):
// Common cookie banner selectors
const banners = document.querySelectorAll(
'[class*="cookie"], [class*="consent"], [id*="cookie"], [id*="consent"], .gdpr-banner'
);
banners.forEach(b => b.remove());
GATE: Page loaded with HTTP 200 (or 30x redirect to 200), content selector found. If 4xx/5xx or no selector found: capture screenshot, report FAIL with HTTP status, STOP. Do not proceed to Phase 2.
Phase 2: VALIDATE
Goal: Inspect rendered DOM and network activity for content integrity and SEO completeness. Run all checks without stopping on individual failures.
Check 1: Title Match (Severity: BLOCKER)
Extract the rendered title:
const titleEl = document.querySelector('h1, .entry-title, .post-title');
titleEl ? titleEl.textContent.trim() : null;
If expected title provided: compare (case-insensitive, trimmed). PASS if match, BLOCKER if differ or no title found. If no expected title: report rendered title as INFO.
Check 2: H2 Structure (Severity: WARNING)
Extract all H2s:
const h2s = Array.from(document.querySelectorAll('h2')).map(h => h.textContent.trim());
JSON.stringify(h2s);
If expected count provided: compare. PASS if match, WARNING if differ. Always report rendered H2 texts for inspection.
Check 3: Image Loading (Severity: BLOCKER)
Use browser_network_requests. Filter image URLs (common extensions or image MIME types). Check response status:
- 2xx: loaded successfully
- 4xx/5xx: BLOCKER (broken for readers)
Report total, loaded, and failed counts with URLs of failures.
Check 4: JavaScript Console Errors (Severity: WARNING)
Use browser_console_messages. Filter to error level. Exclude patterns:
- Ad networks: doubleclick, googlesyndication, adsbygoogle
- Analytics: gtag, analytics, fbevents
- Consent: cookiebot, onetrust, quantcast
- Browser extensions
Report count of genuine errors and their messages.
Check 5: OG Tags (Severity: WARNING)
Extract OG and social meta tags:
const getMeta = (sel) => {
const el = document.querySelector(sel);
return el ? el.getAttribute('content') : null;
};
JSON.stringify({
'og:title': getMeta('meta[property="og:title"]'),
'og:description': getMeta('meta[property="og:description"]'),
'og:image': getMeta('meta[property="og:image"]'),
'og:url': getMeta('meta[property="og:url"]'),
'twitter:card': getMeta('meta[name="twitter:card"]')
});
Mark WARNING for missing tags. Report each tag's value and character count.
Check 6: Meta Description (Severity: WARNING)
const desc = document.querySelector('meta[name="description"]');
desc ? desc.getAttribute('content') : null;
PASS if present and non-empty, WARNING if missing or empty. Report value and character count.
Check 7: Placeholder/Draft Text (Severity: BLOCKER)
Search visible text for patterns:
const body = document.body.innerText;
const patterns = ['[TBD]', '[TODO]', 'PLACEHOLDER', 'Lorem ipsum', '[insert', '[FIXME]'];
const found = patterns.filter(p => body.toLowerCase().includes(p.toLowerCase()));
JSON.stringify(found);
Mark BLOCKER if any found, PASS if none.
GATE: All 7 checks executed, each with severity and evidence. Proceed to Phase 3.
Phase 3: RESPONSIVE CHECK
Goal: Verify rendering at three standard breakpoints. Capture visual evidence.
Test each viewport in sequence:
| Viewport | Width | Height | Represents |
|---|---|---|---|
| Mobile | 375 | 812 | iPhone-class |
| Tablet | 768 | 1024 | iPad-class |
| Desktop | 1440 | 900 | Standard laptop |
For each viewport:
Step 1: Use browser_resize to set dimensions.
Step 2: Use browser_take_screenshot to capture. Save to known path.
Step 3: Check for horizontal overflow:
document.documentElement.scrollWidth > document.documentElement.clientWidth;
Mark WARNING if overflow detected—content extends beyond viewport (usually tables, images, or code blocks not responsive).
Step 4: Verify content container visibility:
const content = document.querySelector('article, .entry-content, .post-content, main');
if (content) {
const rect = content.getBoundingClientRect();
JSON.stringify({ visible: rect.width > 0 && rect.height > 0, width: rect.width, height: rect.height });
} else {
JSON.stringify({ visible: false });
}
Mark WARNING if container not visible or zero dimensions at any breakpoint.
GATE: Screenshots captured at all three viewports. Overflow and visibility recorded. Proceed to Phase 4.
Phase 4: REPORT
Goal: Produce structured pass/fail report with severity counts and evidence artifacts.
Step 1: Classify results from Phase 2 and 3. Count BLOCKERs, WARNINGs, INFOs.
Step 2: Generate report:
===============================================================
LIVE VALIDATION: {post_url}
===============================================================
CONTENT INTEGRITY:
[{status}] Title match: "{rendered_title}" vs "{uploaded_title}"
[{status}] H2 structure: {rendered_count} rendered / {source_count} expected
[{status}] Images: {loaded}/{total} loaded, {failed} failed
[{status}] JS errors: {count} errors detected
[{status}] Placeholder text: {found_patterns or "none"}
SEO / SOCIAL:
[{status}] og:title: "{value}" ({chars} chars)
[{status}] og:description: "{value}" ({chars} chars)
[{status}] og:image: {url}
[{status}] og:url: {url}
[{status}] twitter:card: {value}
[{status}] meta description: "{value}" ({chars} chars)
RESPONSIVE:
[{status}] Mobile (375px): overflow: {yes/no} — screenshot: {path}
[{status}] Tablet (768px): overflow: {yes/no} — screenshot: {path}
[{status}] Desktop (1440px): overflow: {yes/no} — screenshot: {path}
===============================================================
RESULT: {PASS | FAIL - N blockers, M warnings}
===============================================================
Screenshots:
- {mobile_screenshot_path}
- {tablet_screenshot_path}
- {desktop_screenshot_path}
Status markers:
[PASS]= check passed[FAIL]= BLOCKER severity[WARN]= WARNING severity[INFO]= informational[SKIP]= check could not be performed
Result classification:
- PASS: Zero BLOCKERs. WARNINGs may be present but do not constitute failure.
- FAIL: One or more BLOCKERs. List all blockers after result line.
GATE: Report generated with accurate severity counts. Screenshots saved. Result matches blocker tally.
Integration with wordpress-uploader
When invoked after wordpress-uploader, this skill acts as an optional Phase 5: POST-PUBLISH VALIDATION. The wordpress-uploader output provides:
post_url– the navigation targetpost_id– for constructing draft preview URLs ({WORDPRESS_SITE}/?p={post_id}&preview=true)- The
--titlevalue or extracted H1 – the expected title for comparison
The validation is non-blocking by default: a FAIL result produces a report for the user but does not revert the upload. The user decides whether to act on findings.
Example integration flow:
wordpress-uploader Phase 4 completes
-> post_url = "https://example.com/my-post/"
-> post_title = "My Post Title"
-> invoke wordpress-live-validation with post_url and expected title
-> report generated
-> user reviews and decides next action
Examples
Example 1: Post-Upload Validation
User says: "Upload this article and check if it looks right"
- wordpress-uploader creates the post, returns post_url
- NAVIGATE: Load post_url, wait for content area
- VALIDATE: Run all 7 checks
- RESPONSIVE: Screenshots at 375/768/1440px
- REPORT: Structured output with pass/fail and screenshots
Example 2: Standalone Live Check
User says: "Check if https://your-blog.com/posts/my-latest/ looks right"
- NAVIGATE: Load the URL
- VALIDATE: All checks run without expected title/H2 (reports rendered values as INFO)
- RESPONSIVE: Screenshots at all breakpoints
- REPORT: Structured output
Example 3: OG Tag Verification
User says: "Check the OG tags on my latest post"
- NAVIGATE: Load the URL
- VALIDATE: Full check suite (user focuses on SEO/SOCIAL section)
- RESPONSIVE: Run for completeness
- REPORT: Full report
Error Handling
Error: Playwright MCP Not Available
Cause: Playwright MCP server not running or not configured Solution:
- Detect in Phase 1 when first browser tool call fails
- Exit with skip report: "Playwright MCP not available. Skipping live validation."
- Do not retry—browser validation requires Playwright
Error: Page Returns 4xx/5xx
Cause: Wrong URL, post deleted, or WordPress down Solution:
- Capture screenshot of what browser shows
- Report HTTP status code
- STOP at Phase 1 gate—do not proceed to validation
- If draft preview URL, suggest checking that post exists and is accessible
Error: Content Selector Not Found
Cause: Theme uses non-standard content container, or page loaded but content empty Solution:
- Selector chain (article → .entry-content → .post-content → main) covers most themes
- If none match: capture screenshot and DOM snapshot
- Report FAIL with suggestion: "Content area not found. Try specifying a custom selector."
- Still attempt Phase 2 checks against full page (OG tags work without content selector)
Error: Network Timeout on Image Checks
Cause: CDN slow, images resolve but download slowly, intermittent network issues Solution:
- browser_network_requests reports what browser observed—timeout images appear as failed
- If all images fail: likely network issue rather than broken content
- Report failure with pattern note: "All {N} images failed—possible network/CDN issue rather than broken content"
Error: Cookie Banner Blocks Content
Cause: GDPR/consent overlay covers content Solution:
- Phase 1 Step 4 attempts DOM removal of common banners
- If banner persists (non-standard selector): screenshots may show overlay
- DOM-level checks (title, H2s, OG tags) still work—they query elements directly
- Note in report if consent overlay detected but not dismissed
References
For detailed check specifications and Playwright tool usage:
- Validation Checks: references/validation-checks.md – severity rationale, edge cases, and extended check specifications
- Playwright Tools: references/playwright-tools.md – MCP tool signatures, usage patterns, and common pitfalls
Complementary Skills
- pre-publish-checker – validates source markdown before upload (complementary, not overlapping)
- wordpress-uploader – uploads content to WordPress (upstream integration)
- seo-optimizer – validates SEO properties against source (data consumer of this skill's output)
- endpoint-validator – similar validation pattern for API endpoints
More from notque/claude-code-toolkit
generate-claudemd
Generate project-specific CLAUDE.md from repo analysis.
12fish-shell-config
Fish shell configuration and PATH management.
12pptx-generator
PPTX presentation generation with visual QA: slides, pitch decks.
12codebase-overview
Systematic codebase exploration and architecture mapping.
10image-to-video
FFmpeg-based video creation from image and audio.
9data-analysis
Decision-first data analysis with statistical rigor gates.
9