[harness eval #34749] Docs: Add ariaLabel support to ActionItem interface#29
[harness eval #34749] Docs: Add ariaLabel support to ActionItem interface#29valentinpalkovic wants to merge 2 commits into
Conversation
|
Verify HarnessVerdict: Replay: Screenshots
|
fe2f521 to
e537022
Compare
Verify HarnessVerdict: Reason: Replay: Screenshots
|
Verify HarnessVerdict: Reason: Replay: Screenshots
|
Verify HarnessVerdict: Reason: Compile output (last 4KB)Replay: |
a11176d to
9de9d5b
Compare
Verify HarnessNo verdict produced — the workflow failed before the harness ran (likely recipe-author dispatch, deny-regex, or lint). See run log for details. |
Verify HarnessVerdict: Reason: PR-added unit tests: ✅ passed — 6712 passed, 0 failed across 2118 suite(s) Files: How Playwright validated thistest('addon-docs Canvas ActionBar renders without runtime errors', async ({ page }, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(`${baseURL}/?path=/docs/example-button--docs`);
const recipe = new RecipePage(page, expect);
await recipe.waitUntilLoaded();
const errorDisplay = page.locator('#sb-errordisplay');
await expect(errorDisplay).toBeHidden();
const canvas = recipe.previewRoot().locator('.docs-story, [class*="docs-story"]').first();
await expect(canvas).toBeAttached();
await canvas.scrollIntoViewIfNeeded();
await canvas.hover();
const toolbar = canvas.getByRole('toolbar');
await expect(toolbar).toBeVisible();
const showCode = toolbar.getByRole('button', { name: /show code/i });
await expect(showCode).toBeVisible();
await canvas.screenshot({
path: testInfo.outputPath('docs-canvas-actionbar.png'),
});
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
});Replay: Screenshots
|
ad75ba9 to
099b6f7
Compare
Verify HarnessVerdict: Reason: PR-added unit tests: ✅ passed — 6712 passed, 0 failed across 2118 suite(s) Files: How Playwright validated thistest('sidebar Tree renders with merged status button after status-icon refactor', async ({ page }, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(
`${baseURL}/?path=/story/sidebar-tree-status--with-new`,
);
const recipe = new RecipePage(page, expect);
await recipe.waitUntilLoaded();
await expect(page.locator('.sidebar-container')).toBeVisible();
await expect(page.locator('#sb-errordisplay')).toBeHidden();
const previewIframe = recipe.previewIframe();
const statusButton = previewIframe.locator(
'[data-testid="tree-change-status-button"], [data-testid="tree-status-button"]',
);
await expect(statusButton.first()).toBeVisible({ timeout: 15000 });
await previewIframe.locator('body').screenshot({
path: testInfo.outputPath('tree-with-status.png'),
});
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
});Replay: Screenshots
|
67053af to
2320302
Compare
Verify HarnessVerdict: Reason: How Playwright validated thistest('ActionBar ActionButton forwards ariaLabel prop as aria-label', async ({ page }, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(`${baseURL}/?path=/docs/example-button--docs`);
const sb = new RecipePage(page, expect);
await sb.waitUntilLoaded();
const errorDisplay = page.locator('#sb-errordisplay');
await expect(errorDisplay).toBeHidden();
const previewIframe = sb.previewIframe();
const previewRoot = sb.previewRoot();
await expect(previewRoot).toBeVisible();
const canvas = previewIframe
.locator('.docs-story, [class*="docs-story"]')
.first();
await canvas.scrollIntoViewIfNeeded();
await canvas.hover();
const actionBar = canvas.locator('xpath=..').locator('button').filter({ hasText: /show code/i }).first();
await expect(actionBar).toBeVisible({ timeout: 15000 });
const ariaLabelValue = await actionBar.getAttribute('aria-label');
expect(ariaLabelValue).toBeNull();
const accessibleName = await actionBar.evaluate((el) => el.textContent?.trim() ?? '');
expect(accessibleName.toLowerCase()).toContain('show code');
await expect(actionBar).toBeEnabled();
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
});Replay: Screenshots
|
Verify HarnessVerdict: Reason: How Playwright validated thistest('ActionBar ActionButton receives aria-label from ariaLabel prop', async ({
page,
}, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(`${baseURL}/?path=/docs/example-button--docs`);
const recipe = new RecipePage(page, expect);
await recipe.waitUntilLoaded();
const errorDisplay = page.locator('#sb-errordisplay');
await expect(errorDisplay).toBeHidden();
const previewIframe = recipe.previewIframe();
const previewRoot = recipe.previewRoot();
await expect(previewRoot).toBeVisible();
//
const canvas = previewIframe.locator('.docs-story, [class*="docs-story"]').first();
await canvas.scrollIntoViewIfNeeded();
await canvas.hover();
const showCodeButton = canvas
.locator('button.docblock-code-toggle, button')
.filter({ hasText: /show code/i })
.first();
await expect(showCodeButton).toBeVisible({ timeout: 15000 });
await expect(showCodeButton).toBeEnabled();
await showCodeButton.click();
const codeBlock = previewIframe.locator('pre.prismjs').first();
await expect(codeBlock).toBeVisible({ timeout: 10000 });
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
});Replay: Screenshots
|
Verify HarnessVerdict: Reason: How Playwright validated thistest('Preview additionalActionItems forward ariaLabel to ActionBar buttons', async ({ page }, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(`${baseURL}/?path=/docs/example-button--docs`);
const sb = new RecipePage(page, expect);
await sb.waitUntilLoaded();
const errorDisplay = page.locator('#sb-errordisplay');
await expect(errorDisplay).toBeHidden();
const previewIframe = sb.previewIframe();
const previewRoot = sb.previewRoot();
await expect(previewRoot).toBeVisible();
const canvas = previewIframe.locator('.docs-story, [class*="docs-story"]').first();
await canvas.scrollIntoViewIfNeeded();
await canvas.hover();
//
const showCode = canvas.getByRole('button', { name: /show code/i }).first();
await expect(showCode).toBeVisible({ timeout: 15000 });
await expect(showCode).toBeEnabled();
await canvas.screenshot({
path: testInfo.outputPath('canvas-actionbar.png'),
});
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
});Replay: Screenshots
|
…ActionBar scope Wave findings (#28/#29/#31 stuck at regression despite passing PR unit tests — recipe-author mis-targeted the DOM): - ActionBar/Canvas rule was conflating the docs-Canvas Zoom/Show-code toolbar with the generic `ActionBar` component. Scope-tagged it to the docs-Canvas surface only. - New HARD GATE "additive-only API changes with no story/consumer" — the #1 false-regression cause. #28/#29 add `ActionItem.ariaLabel` but no story or in-diff consumer passes it, so the attribute is never in the DOM; asserting it always fails. Rule: detect additive-no-consumer, fall back to `@verify-mode: visual` smoke on the component's existing story (`components-actionbar--many-items`), never `getByRole('toolbar')` (the component renders plain <button>s) nor `.docs-story`. - New HARD GATE for `Brand` / `theme.brand.title`: the sanitized dangerouslySetInnerHTML path runs ONLY when `theme.brand.image === null`. Target the existing `manager-sidebar-heading--only-text` / `--link-and-text` stories (already `{title, image:null}`); never runtime `api.setOptions({theme})` (#31 false regression — never reaches the path). XSS-inert proof is the PR's unit test; recipe is a render/boot smoke. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verify HarnessVerdict: How Playwright validated thistest('ActionBar stories render cleanly with new optional ariaLabel prop', async ({ page }, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(`${baseURL}/?path=/story/components-actionbar--many-items`);
const sb = new RecipePage(page, expect);
await sb.waitUntilLoaded();
const errorDisplay = page.locator('#sb-errordisplay');
await expect(errorDisplay).toBeHidden();
const previewIframe = page.frameLocator('#storybook-preview-iframe');
const previewRoot = previewIframe.locator('#storybook-root, #root');
await expect(previewRoot).toBeVisible();
const buttons = previewRoot.locator('button');
const count = await buttons.count();
expect(count).toBeGreaterThanOrEqual(2);
await page.goto(`${baseURL}/?path=/story/components-actionbar--single-item`);
await sb.waitUntilLoaded();
await expect(errorDisplay).toBeHidden();
await expect(previewRoot).toBeVisible();
const singleButtons = previewRoot.locator('button');
expect(await singleButtons.count()).toBeGreaterThanOrEqual(1);
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
});Replay: Screenshots
|
- Add ariaLabel?: string to ActionItem in ActionBar.tsx - Sync the duplicate inline type in addons/docs/src/types.ts - Pass ariaLabel through to Button in Preview.tsx (defaults to false to suppress the mandatory-prop warning when title is visible) Closes storybookjs#34746
Addresses CodeRabbit review feedback. ActionBar.tsx now destructures ariaLabel and passes it as aria-label HTML attribute, so ActionItem.ariaLabel is honored when ActionBar is used directly (not only via Preview.tsx).
2320302 to
5f2c069
Compare
Verify HarnessVerdict: Reason: How Playwright validated thistest('ActionBar renders cleanly after ariaLabel additive prop', async ({ page }, testInfo) => {
const pageErrors: string[] = [];
const consoleErrors: string[] = [];
page.on('pageerror', (err) => {
pageErrors.push(err.stack ?? err.message ?? String(err));
});
page.on('console', (msg) => {
if (msg.type() === 'error') consoleErrors.push(msg.text());
});
const baseURL =
process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';
try {
await page.goto(`${baseURL}/?path=/story/components-actionbar--many-items`);
const sb = new RecipePage(page, expect);
await sb.waitUntilLoaded();
const errorDisplay = page.locator('#sb-errordisplay');
await expect(errorDisplay).toBeHidden();
const previewIframe = sb.previewIframe();
const changeView = previewIframe.getByRole('button', { name: 'Change view' });
await expect(changeView).toBeVisible();
await expect(changeView).not.toHaveAttribute('aria-label', /.+/);
const useFoo = previewIframe.getByRole('button', { name: 'Use Foo' });
await expect(useFoo).toBeVisible();
await expect(useFoo).not.toHaveAttribute('aria-label', /.+/);
const useBar = previewIframe.getByRole('button', { name: 'Use Bar' });
await expect(useBar).toBeVisible();
await expect(useBar).not.toHaveAttribute('aria-label', /.+/);
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await testInfo.attach('consoleErrors', {
body: JSON.stringify(consoleErrors),
contentType: 'application/json',
});
}
expect(filterPageErrors(pageErrors)).toEqual([]);
expect(filterConsoleErrors(consoleErrors)).toEqual([]);
});Replay: Screenshots
|









Synthetic fork PR for agentic harness eval against storybookjs#34749.