refactor(storybook): hide page headers and preserve accessibility
StoryPageHeader no longer shows the page title, kicker, description or the SW group notice; it renders only a visually hidden H1 with the Story title, so the preview keeps a page heading for screen readers while the first section starts at the top gutter (unchanged position). - OrgChart verify-p8 / p9 / p9-1 / p9-2-composition wait for the first visible StoryPage content block and read the page H1 instead of .sb-page-title; existing expectations are unchanged. - SW overview browser test clicks the page gutter as its outside-click target. - Drop the unused .sb-page-description from the SW Storybook hide rules. - Update the Storybook page frame rule in the project guideline.
This commit is contained in:
1 parent
f22428548a
commit
63b3ab010b
11 files changed
+59
-105
No files matched your search
@@ -46,8 +46,11 @@ page.setDefaultTimeout(20000);
|
||||
async function settle(target = page) {
|
||||
await target.evaluate(async () => { await document.fonts.ready; for (let i = 0; i < 5; i++) await new Promise(resolve => requestAnimationFrame(resolve)); });
|
||||
}
|
||||
// The Storybook page title is a visually hidden H1 (2026-11-11), so readiness waits for the first visible content block
|
||||
// of the StoryPage frame, and page identity reads the H1 text (the Story's accessible page heading).
|
||||
const pageContent = '.sb-page-inner > :not(h1)', pageHeading = '.sb-page-inner > h1';
|
||||
async function ready(expected) {
|
||||
await page.locator('.sb-page-title').waitFor();
|
||||
await page.locator(pageContent).first().waitFor();
|
||||
await page.waitForFunction(expected => {
|
||||
const hosts = [...document.querySelectorAll('.hm-orgchart-host,.hm-orgchart-component-host')];
|
||||
if (!hosts.length) return false;
|
||||
@@ -76,10 +79,8 @@ async function commonStyle() {
|
||||
const properties = {
|
||||
'.sb-page': ['fontFamily', 'color', 'paddingTop', 'paddingBottom', 'boxSizing', 'width'],
|
||||
'.sb-page-inner': ['width', 'boxSizing'],
|
||||
'.sb-page-header': ['paddingBottom', 'borderBottomWidth', 'borderBottomColor', 'fontSize', 'lineHeight'],
|
||||
'.sb-page-kicker': ['fontSize', 'fontWeight', 'color', 'marginBottom'],
|
||||
'.sb-page-title': ['fontSize', 'fontWeight', 'lineHeight', 'marginTop', 'marginBottom'],
|
||||
'.sb-page-description': ['fontSize', 'fontWeight', 'lineHeight', 'marginBottom'],
|
||||
// The visible header (kicker, title, description) is no longer rendered; the page H1 stays visually hidden.
|
||||
'.sb-page-inner > h1': ['position', 'width', 'height', 'overflow', 'clipPath'],
|
||||
'.sb-page-section': ['marginTop', 'scrollMarginTop'],
|
||||
'.sb-page-section-header': ['paddingBottom', 'marginBottom', 'borderBottomWidth', 'borderBottomColor'],
|
||||
'.sb-page-section-title': ['fontSize', 'lineHeight', 'fontWeight', 'marginTop', 'marginBottom'],
|
||||
@@ -105,7 +106,7 @@ async function pageGeometry() {
|
||||
return page.evaluate(() => {
|
||||
const rect = element => { if (!element) return null; const r = element.getBoundingClientRect(); return { x: r.x, y: r.y, width: r.width, height: r.height, right: r.right, bottom: r.bottom }; };
|
||||
const documentGeometry = { scrollWidth: document.documentElement.scrollWidth, clientWidth: document.documentElement.clientWidth, innerWidth, scrollHeight: document.documentElement.scrollHeight, clientHeight: document.documentElement.clientHeight };
|
||||
const frame = { page: rect(document.querySelector('.sb-page')), inner: rect(document.querySelector('.sb-page-inner')), title: document.querySelector('.sb-page-title')?.textContent };
|
||||
const frame = { page: rect(document.querySelector('.sb-page')), inner: rect(document.querySelector('.sb-page-inner')), title: document.querySelector('.sb-page-inner > h1')?.textContent };
|
||||
const shadow = document.querySelector('.hm-orgchart-host')?.shadowRoot;
|
||||
const wrapper = shadow?.querySelector('.hm-orgchart-composition');
|
||||
const stage = shadow?.querySelector('.hm-orgchart-stage');
|
||||
@@ -159,10 +160,10 @@ try {
|
||||
await manager.waitForFunction(() => {
|
||||
const preview = document.querySelector('#storybook-preview-iframe')?.contentDocument;
|
||||
const hosts = [...preview?.querySelectorAll('.hm-orgchart-component-host') ?? []];
|
||||
return preview?.querySelector('.sb-page-title')?.textContent === 'Overview' && hosts.length > 0
|
||||
return preview?.querySelector('.sb-page-inner > h1')?.textContent === 'Overview' && hosts.length > 0
|
||||
&& hosts.every(host => host.shadowRoot && [...host.shadowRoot.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet));
|
||||
});
|
||||
await manager.frameLocator('#storybook-preview-iframe').locator('.sb-page-title').evaluate(async element => {
|
||||
await manager.frameLocator('#storybook-preview-iframe').locator(pageHeading).evaluate(async element => {
|
||||
const document = element.ownerDocument, view = document.defaultView;
|
||||
await document.fonts.ready;
|
||||
for (let i = 0; i < 5; i++) await new Promise(resolve => view.requestAnimationFrame(resolve));
|
||||
@@ -198,7 +199,7 @@ try {
|
||||
await gotoStory(story);
|
||||
const rendered = await page.evaluate(({ selectors }) => {
|
||||
const hosts = [...document.querySelectorAll('.hm-orgchart-host,.hm-orgchart-component-host')];
|
||||
return { title: document.querySelector('.sb-page-title')?.textContent, hosts: hosts.length,
|
||||
return { title: document.querySelector('.sb-page-inner > h1')?.textContent, hosts: hosts.length,
|
||||
specimens: Object.fromEntries(selectors.map(selector => [selector, hosts.reduce((total, host) => total + host.shadowRoot.querySelectorAll(selector).length, 0)])),
|
||||
employees: hosts.reduce((total, host) => total + host.shadowRoot.querySelectorAll('.hm-orgchart-employee').length, 0),
|
||||
autoLayout: hosts.find(host => host.classList.contains('hm-orgchart-host'))?.shadowRoot.querySelector('.hm-orgchart-expanded')?.dataset.autoLayout };
|
||||
@@ -214,7 +215,7 @@ try {
|
||||
for (const id of patterns) {
|
||||
await page.setViewportSize({ width: 1440, height: 1100 });
|
||||
await page.goto(`${origin}/iframe.html?id=${id}&viewMode=story`);
|
||||
await page.locator('.sb-page-title').waitFor(); await settle();
|
||||
await page.locator(pageContent).first().waitFor(); await settle();
|
||||
references.push({ id, style: await commonStyle() });
|
||||
}
|
||||
const reference = references[0].style;
|
||||
@@ -268,7 +269,7 @@ try {
|
||||
await section('full-repair', async () => {
|
||||
await page.setViewportSize({ width: 1440, height: 1100 });
|
||||
await page.goto(`${origin}/iframe.html?id=${patterns[0]}&viewMode=story`);
|
||||
await page.locator('.sb-page-title').waitFor(); await settle();
|
||||
await page.locator(pageContent).first().waitFor(); await settle();
|
||||
const reference = await commonStyle();
|
||||
await gotoStory(stories[2]);
|
||||
const style = await commonStyle();
|
||||
|
||||
@@ -39,7 +39,8 @@ async function settle() {
|
||||
async function goto(name, size) {
|
||||
await page.setViewportSize(size);
|
||||
await page.goto(`${origin}/iframe.html?id=${stories[name]}&viewMode=story`);
|
||||
await page.locator('.sb-page-title').waitFor();
|
||||
// The page title is a visually hidden H1 (2026-11-11): wait for the first visible StoryPage content block instead.
|
||||
await page.locator('.sb-page-inner > :not(h1)').first().waitFor();
|
||||
await page.waitForFunction(() => {
|
||||
const hosts = [...document.querySelectorAll('.hm-orgchart-host,.hm-orgchart-component-host')];
|
||||
return hosts.length === 1 && hosts.every(host => host.shadowRoot && !host.shadowRoot.querySelector('[aria-busy="true"]') && [...host.shadowRoot.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet));
|
||||
@@ -86,7 +87,7 @@ async function snapshot() {
|
||||
const css = getComputedStyle(element);
|
||||
return { scrollWidth: element.scrollWidth, clientWidth: element.clientWidth, scrollHeight: element.scrollHeight, clientHeight: element.clientHeight, left: element.scrollLeft, top: element.scrollTop, overflowX: css.overflowX, overflowY: css.overflowY };
|
||||
};
|
||||
const common = Object.fromEntries(['.sb-page', '.sb-page-inner', '.sb-page-title', '.sb-page-section-title', '.sb-page-section-header', '.sb-page-section-body'].map(selector => [selector, style(document.querySelector(selector))]));
|
||||
const common = Object.fromEntries(['.sb-page', '.sb-page-inner', '.sb-page-inner > h1', '.sb-page-section-title', '.sb-page-section-header', '.sb-page-section-body'].map(selector => [selector, style(document.querySelector(selector))]));
|
||||
// Page/section height is content-dependent; typography, borders and gutters are invariant.
|
||||
for (const value of Object.values(common)) if (value) for (const key of ['height', 'minHeight']) delete value[key];
|
||||
const documentScroll = { viewportWidth: innerWidth, viewportHeight: innerHeight, root: scroll(document.documentElement), body: scroll(document.body), scrollingElement: scroll(document.scrollingElement) };
|
||||
@@ -145,7 +146,7 @@ async function snapshot() {
|
||||
};
|
||||
}
|
||||
return {
|
||||
title: document.querySelector('.sb-page-title')?.textContent, common, documentScroll, departments, unwanted,
|
||||
title: document.querySelector('.sb-page-inner > h1')?.textContent, common, documentScroll, departments, unwanted,
|
||||
optionsPresent: [...document.querySelectorAll('button')].filter(visible).some(button => /긴 이름 검증/.test(button.textContent)),
|
||||
sectionTitles: [...document.querySelectorAll('.sb-page-section-title')].map(element => element.textContent), fixed,
|
||||
};
|
||||
|
||||
@@ -33,7 +33,8 @@ async function settle() {
|
||||
async function open(size) {
|
||||
await page.setViewportSize(size);
|
||||
await page.goto(`${origin}/iframe.html?id=orgchart--full-composition&viewMode=story`);
|
||||
await page.locator('.sb-page-title').waitFor();
|
||||
// The page title is a visually hidden H1 (2026-11-11): wait for the first visible StoryPage content block instead.
|
||||
await page.locator('.sb-page-inner > :not(h1)').first().waitFor();
|
||||
await page.waitForFunction(() => {
|
||||
const shadow = document.querySelector('.hm-orgchart-host')?.shadowRoot;
|
||||
return shadow?.querySelector('.hm-orgchart-stage[data-workdesk-artwork]') && !shadow.querySelector('[aria-busy="true"]') && [...shadow.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet);
|
||||
|
||||
@@ -39,7 +39,8 @@ page.setDefaultTimeout(20000);
|
||||
let expectedHosts = 1;
|
||||
async function settle() { await page.evaluate(async () => { await document.fonts.ready; for (let i = 0; i < 5; i++) await new Promise(resolve => requestAnimationFrame(resolve)); }); }
|
||||
async function ready(requirePage = true) {
|
||||
if (requirePage) await page.locator('.sb-page-title').waitFor();
|
||||
// The page title is a visually hidden H1 (2026-11-11): wait for the first visible StoryPage content block instead.
|
||||
if (requirePage) await page.locator('.sb-page-inner > :not(h1)').first().waitFor();
|
||||
await page.waitForFunction(expected => {
|
||||
const hosts = [...document.querySelectorAll('.hm-orgchart-host,.hm-orgchart-component-host')];
|
||||
return hosts.length === expected && hosts.every(host => host.shadowRoot && [...host.shadowRoot.querySelectorAll('link[rel="stylesheet"]')].every(link => link.sheet) && !host.shadowRoot.querySelector('[aria-busy="true"]') && [...host.shadowRoot.querySelectorAll('img')].every(image => image.complete));
|
||||
@@ -111,8 +112,8 @@ async function snapshot() {
|
||||
cards: [...expanded.querySelectorAll('.hm-orgchart-employee')].map(card => { const css = getComputedStyle(card), direction = card.dataset.connectionDirection; return { company: card.dataset.companyId, direction, width: card.offsetWidth, height: card.offsetHeight, border: hex(css[direction === 'left' ? 'borderLeftColor' : direction === 'right' ? 'borderRightColor' : 'borderTopColor']), image: Boolean(card.querySelector('img')) }; }),
|
||||
connectors: expanded.querySelectorAll('svg,.edge,.lines').length,
|
||||
} : null;
|
||||
const common = Object.fromEntries(['.sb-page-title', '.sb-page-description', '.sb-page-section-title', '.sb-page-section-header'].map(selector => [selector, style(document.querySelector(selector))]));
|
||||
return { title: document.querySelector('.sb-page-title')?.textContent, titles: [...document.querySelectorAll('.sb-page-section-title')].map(element => element.textContent), document: { width: innerWidth, scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight, clientHeight: document.documentElement.clientHeight }, common, summary, affiliation, logos, departments, fixed, auto };
|
||||
const common = Object.fromEntries(['.sb-page-inner > h1', '.sb-page-section-title', '.sb-page-section-header'].map(selector => [selector, style(document.querySelector(selector))]));
|
||||
return { title: document.querySelector('.sb-page-inner > h1')?.textContent, titles: [...document.querySelectorAll('.sb-page-section-title')].map(element => element.textContent), document: { width: innerWidth, scrollWidth: document.documentElement.scrollWidth, scrollHeight: document.documentElement.scrollHeight, clientHeight: document.documentElement.clientHeight }, common, summary, affiliation, logos, departments, fixed, auto };
|
||||
});
|
||||
}
|
||||
function noHorizontalOverflow(state) { return state.document.scrollWidth <= state.document.width + 1; }
|
||||
|
||||
Reference in new issue
Block a user