// ── Shadow DOM isolation tests ────────────────────────────────── // // The #1 historical pain point with the WP plugin is that WordPress themes // consistently override the payment fields' CSS, making every merchant install // look different. The new SDK puts the entire UI inside a Shadow DOM via // ``. These tests assert that the encapsulation actually // holds against hostile theme stylesheets. // // Run: `npx playwright test tests/theme-regression/shadow-isolation.spec.ts` // // Setup: depends on the web-component bundle being built — run `npm run build` // in this package first. Tests load `tests/theme-regression/fixture.html` as a // file:// URL; no WP install is needed. import { test, expect, type Page } from '@playwright/test'; import { HOSTILE_THEMES } from './themes'; // ESM-safe path resolution (package has `"type": "module"`). const FIXTURE = new URL('./fixture.html', import.meta.url).href; interface StyleSnapshot { fontFamily: string; fontSize: string; color: string; background: string; borderRadius: string; width: number; height: number; } async function snapshotTrustText(page: Page): Promise { return page.evaluate(() => { const el = document.querySelector('wcp-payment-fields'); if (!el?.shadowRoot) throw new Error('shadow root not present'); const trustText = el.shadowRoot.querySelector('.wcp-pf__trust-text'); if (!trustText) throw new Error('.wcp-pf__trust-text missing'); const cs = getComputedStyle(trustText); const rect = trustText.getBoundingClientRect(); return { fontFamily: cs.fontFamily, fontSize: cs.fontSize, color: cs.color, background: cs.backgroundColor, borderRadius: cs.borderRadius, width: rect.width, height: rect.height, }; }); } async function snapshotFirstField(page: Page): Promise { return page.evaluate(() => { const el = document.querySelector('wcp-payment-fields'); if (!el?.shadowRoot) throw new Error('shadow root not present'); const field = el.shadowRoot.querySelector('.wcp-pf__field'); if (!field) throw new Error('.wcp-pf__field missing'); const cs = getComputedStyle(field); const rect = field.getBoundingClientRect(); return { fontFamily: cs.fontFamily, fontSize: cs.fontSize, color: cs.color, background: cs.backgroundColor, borderRadius: cs.borderRadius, width: rect.width, height: rect.height, }; }); } test.describe('Shadow DOM isolation', () => { test.beforeEach(async ({ page }) => { await page.goto(FIXTURE); // Give the SDK a moment to register + render (Clover loader fails, but the // UI shell renders regardless of Clover SDK load status — that's what we test). await page.waitForSelector('wcp-payment-fields'); await page.waitForFunction(() => { const el = document.querySelector('wcp-payment-fields'); return Boolean(el?.shadowRoot && el.shadowRoot.querySelector('.wcp-pf')); }); }); test('shadowRoot mounts and renders the widget chrome', async ({ page }) => { const snapshot = await snapshotTrustText(page); expect(snapshot.fontSize).toBeTruthy(); expect(snapshot.width).toBeGreaterThan(0); }); for (const theme of HOSTILE_THEMES) { test(`hostile theme "${theme.name}" does not alter shadowRoot styles`, async ({ page }) => { const baselineTrust = await snapshotTrustText(page); const baselineField = await snapshotFirstField(page); // Inject the hostile theme CSS into the document. await page.evaluate((css) => { const mount = document.getElementById('hostile-theme-mount'); if (mount) mount.textContent = css; }, theme.css); // Wait a tick for any potential restyle. await page.waitForTimeout(100); const afterTrust = await snapshotTrustText(page); const afterField = await snapshotFirstField(page); // All shadow-internal styles must remain unchanged. expect(afterTrust).toEqual(baselineTrust); expect(afterField).toEqual(baselineField); }); } test('field positions remain stable across all hostile themes applied in sequence', async ({ page }) => { const baseline = await snapshotFirstField(page); for (const theme of HOSTILE_THEMES) { await page.evaluate((css) => { const mount = document.getElementById('hostile-theme-mount'); if (mount) mount.textContent = css; }, theme.css); await page.waitForTimeout(50); const snapshot = await snapshotFirstField(page); expect(snapshot, `theme "${theme.name}" altered field`).toEqual(baseline); } }); });