hubeiqiao/skills · Archived

playwright-whileinview-screenshots

Fix invisible/blank sections in Playwright full-page screenshots when using Framer Motion whileInView animations. Use when: (1) Playwright fullPage screenshots show blank areas where content should be, (2) sections render with opacity:0 because IntersectionObserver never fires, (3) motion elements with initial="hidden" appear missing in automated screenshots. Solution: scroll through the page programmatically before capturing. Covers Framer Motion, GSAP ScrollTrigger, and any IntersectionObserv…

First seen Apr 16, 2026

Installation

$ npx skills add hubeiqiao/skills --skill playwright-whileinview-screenshots

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from hubeiqiao/skills · top by installs.

npx skills add hubeiqiao/skills

Browse all from hubeiqiao/skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Default branch main
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Version1.0.0
Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 4,566 B
  • docs SUMMARY.md 568 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Playwright Screenshots with Scroll-Triggered Animations

Problem

When taking full-page screenshots with Playwright, sections using Framer Motion's whileInView (or any IntersectionObserver-based animation) appear blank/invisible. Elements start with initial="hidden" (opacity: 0, y: 40, etc.) and never animate to visible because Playwright's fullPage: true screenshot captures the DOM without triggering scroll-based intersection events.

Context / Trigger Conditions

  • Playwright page.screenshot({ fullPage: true }) produces images with blank sections
  • Elements use Framer Motion whileInView, initial="hidden", or variants with viewport triggers
  • Content exists in the DOM (verified via page.$()) but isVisible() may return true while opacity is 0
  • Sections appear fine in a real browser when scrolled manually
  • Also affects GSAP ScrollTrigger, vanilla IntersectionObserver, and CSS scroll-timeline animations

Solution

Before taking screenshots, scroll through the entire page to trigger all animations:

// Step 1: Wait for page load
await page.goto(url, { waitUntil: 'networkidle' });
await page.waitForTimeout(2000);

// Step 2: Scroll through entire page to trigger IntersectionObserver
const totalHeight = await page.evaluate(() => document.body.scrollHeight);
for (let y = 0; y < totalHeight; y += 300) {
  await page.evaluate((scrollY) => window.scrollTo(0, scrollY), y);
  await page.waitForTimeout(100); // Allow observer callbacks to fire
}

// Step 3: Wait for animations to complete
await page.waitForTimeout(1500);

// Step 4: Now take screenshots (per-section is more reliable than fullPage)
const section = await page.$('section[aria-labelledby="my-heading"]');
if (section) {
  await section.scrollIntoViewIfNeeded();
  await page.waitForTimeout(800); // Wait for entrance animation
  await page.screenshot({ path: 'section.png' });
}

For per-section screenshots (more reliable):

// Scroll to each section individually and capture
const sections = ['hero', 'features', 'footer'];
for (const id of sections) {
  const el = await page.$(`#${id}`);
  if (el) {
    await el.scrollIntoViewIfNeeded();
    await page.waitForTimeout(1000);
    await page.screenshot({ path: `${id}.png` });
  }
}

Verification

  • Sections that were blank now show content in screenshots
  • Compare against page.$eval to confirm elements have non-zero opacity after scrolling
  • Console should show no Framer Motion warnings about missing containers

Example

Before (broken):

await page.goto('http://localhost:3000');
await page.screenshot({ path: 'full.png', fullPage: true });
// Result: Most sections blank (opacity: 0)

After (working):

await page.goto('http://localhost:3000', { waitUntil: 'networkidle' });
await page.waitForTimeout(2000);

// Trigger all scroll animations
const height = await page.evaluate(() => document.body.scrollHeight);
for (let y = 0; y < height; y += 300) {
  await page.evaluate((sy) => window.scrollTo(0, sy), y);
  await page.waitForTimeout(100);
}
await page.waitForTimeout(1500);

// Capture individual sections
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(500);
await page.screenshot({ path: 'hero.png' });

Notes

  • Scroll step size of 300px works well; smaller values are slower but more thorough
  • The 100ms wait per scroll step is necessary for IntersectionObserver callbacks to fire
  • fullPage: true still may not work perfectly after scrolling — per-section screenshots are more reliable
  • For sticky/pinned sections (e.g., scroll-driven animations), you need to scroll to specific positions within the section's scroll runway
  • This also applies when using page.pdf() for PDF generation of animated pages
  • Consider adding data-testid attributes to sections for reliable selector targeting in tests