[harness eval #34752] fix: add ariaLabel support to ActionItem interface#28
[harness eval #34752] fix: add ariaLabel support to ActionItem interface#28valentinpalkovic wants to merge 1 commit into
Conversation
|
Verify HarnessVerdict: Replay: Screenshots
|
fe2f521 to
e537022
Compare
Verify HarnessVerdict: Reason: Replay: Screenshots
|
Verify HarnessVerdict: Reason: Replay: Screenshots
|
Verify HarnessVerdict: Reason: Replay: Screenshots
|
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 this//
test('sidebar Tree renders modified/new status icons without runtime errors after status-merge 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/core-sidebar-tree--with-modified`);
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 modifiedStatusButton = previewIframe.locator(
'[data-testid="tree-change-status-button"], [data-testid="tree-status-button"]'
);
await expect(modifiedStatusButton.first()).toBeVisible({ timeout: 15000 });
await previewIframe.locator('body').screenshot({
path: testInfo.outputPath('tree-with-modified.png'),
});
await page.goto(`${baseURL}/?path=/story/core-sidebar-tree--with-new`);
await recipe.waitUntilLoaded();
await expect(errorDisplay).toBeHidden();
await expect(previewRoot).toBeVisible();
const newStatusButton = previewIframe.locator(
'[data-testid="tree-change-status-button"], [data-testid="tree-status-button"]'
);
await expect(newStatusButton.first()).toBeVisible({ timeout: 15000 });
await previewIframe.locator('body').screenshot({
path: testInfo.outputPath('tree-with-new.png'),
});
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentType: 'application/json',
});
await tesReplay: |
ad75ba9 to
099b6f7
Compare
Verify HarnessVerdict: Evidence (vision-check, Vision reasoningThe diff is predominantly non-UI changes (CI/GitHub Actions YAML, package.json versions, test file deletions, and documentation removals). The one UI change mentioned in the Playwright recipe (CloseIcon→SweepIcon swap in ReviewChangesButton) is not visible in the provided screenshots because they show the sidebar region but the clear button's icon detail is too small/unclear to definitively confirm the icon swap at this resolution. PR-added unit tests: ✅ passed — 6712 passed, 0 failed across 2118 suite(s) Files: How Playwright validated thistest('ReviewChangesButton clear button renders SweepIcon after Save from Controls', 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/example-button--primary`);
const recipe = new RecipePage(page, expect);
await recipe.waitUntilLoaded();
await expect(page.locator('#sb-errordisplay')).toBeHidden();
const controlsTab = page.getByRole('tab', { name: /controls/i });
await controlsTab.click();
const labelInput = page.locator('input[name="label"], textarea[name="label"]').first();
await expect(labelInput).toBeVisible({ timeout: 10000 });
await labelInput.fill('Verify harness saved this');
const saveButton = page.getByRole('button', {
name: /save changes to story|update story/i,
});
await expect(saveButton).toBeVisible({ timeout: 10000 });
await saveButton.click();
const reviewToggle = page.getByRole('switch', { name: /review.+stories/i });
await expect(reviewToggle).toBeVisible({ timeout: 20000 });
await reviewToggle.click();
const clearButton = page.getByRole('button', { name: /^clear$/i });
await expect(clearButton).toBeVisible({ timeout: 10000 });
const svg = clearButton.locator('svg');
await expect(svg).toBeVisible();
await clearButton.screenshot({
path: testInfo.outputPath('clear-button-sweep-icon.png'),
});
await page.locator('.sidebar-container').screenshot({
path: testInfo.outputPath('sidebar-with-clear-button.png'),
});
} finally {
await testInfo.attach('pageErrors', {
body: JSON.stringify(pageErrors),
contentTyReplay: Screenshots
|
5435bad to
28661b5
Compare
Verify HarnessVerdict: Reason: How Playwright validated thistest('ActionBar wires aria-label from ariaLabel prop without breaking existing 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 sourceBlock = previewIframe.locator('.docblock-source, pre.prismjs').first();
await sourceBlock.scrollIntoViewIfNeeded();
const showCodeButtons = previewIframe.getByRole('button', { name: /show code/i });
await expect(showCodeButtons.first()).toBeAttached();
const firstShowCode = showCodeButtons.first();
const ariaLabelValue = await firstShowCode.evaluate((el) => el.getAttribute('aria-label'));
expect(ariaLabelValue === null || typeof ariaLabelValue === 'string').toBe(true);
expect(ariaLabelValue).not.toBe('undefined');
expect(ariaLabelValue).not.toBe('false');
await firstShowCode.click();
} 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: |
Verify HarnessVerdict: Reason: How Playwright validated thistest('ActionBar renders action buttons with accessible names on docs Canvas', 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 previewRoot = recipe.previewRoot();
await expect(previewRoot).toBeVisible();
const canvas = previewRoot.locator('.docs-story, [class*="docs-story"]').first();
await expect(canvas).toBeVisible();
await canvas.scrollIntoViewIfNeeded();
await canvas.hover();
const showCode = canvas.getByRole('button', { name: /show code/i });
await expect(showCode).toBeVisible({ timeout: 15000 });
const ariaLabel = await showCode.getAttribute('aria-label');
if (ariaLabel !== null) {
expect(ariaLabel.length).toBeGreaterThan(0);
expect(ariaLabel).not.toBe('false');
}
const accessibleName = await showCode.evaluate((el) => {
return (el.getAttribute('aria-label') ?? el.textContent ?? '').trim();
});
expect(accessibleName.length).toBeGreaterThan(0);
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)).toEquReplay: Screenshots
|
Verify HarnessVerdict: Reason: How Playwright validated thistest('ActionBar renders without aria-label when ariaLabel is undefined and boots clean', 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').first();
await canvas.scrollIntoViewIfNeeded();
await canvas.hover();
const showCodeButton = previewIframe.getByRole('button', { name: /show code/i }).first();
await expect(showCodeButton).toBeVisible({ timeout: 15000 });
const ariaLabelAttr = await showCodeButton.getAttribute('aria-label');
expect(ariaLabelAttr).toBeNull();
await expect(showCodeButton).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
|
…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: Evidence (vision-check, Vision reasoningThe diff adds an optional How Playwright validated thistest('ActionBar renders cleanly after ariaLabel prop addition', 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();
await expect(page.locator('#sb-errordisplay')).toBeHidden();
const previewIframe = sb.previewIframe();
const previewRoot = sb.previewRoot();
await expect(previewRoot).toBeVisible();
const buttons = previewIframe.locator('#storybook-root button, #root button');
const count = await buttons.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i += 1) {
const btn = buttons.nth(i);
await expect(btn).not.toHaveAttribute('aria-label', /.+/);
}
await previewIframe.locator('body').screenshot({
path: testInfo.outputPath('actionbar-many-items.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
|
Fixes storybookjs#34746 Problem: The ActionItem interface used by canvas.additionalActions in the docs addon doesn't include an ariaLabel property. Storybook 11 will require ariaLabel on Button components, causing console warnings when users add custom actions via additionalActions with no way to provide ariaLabel. Solution: - Added ariaLabel?: string | false to ActionItem interface - Updated ActionBar component to pass ariaLabel to button elements - Added ariaLabel to additionalActions type in docs addon - ariaLabel=false allows opt-out for buttons with text content Changes: - code/core/src/components/components/ActionBar/ActionBar.tsx - Added ariaLabel property to ActionItem interface - Updated ActionBar to destructure and pass ariaLabel to buttons - code/addons/docs/src/types.ts - Added ariaLabel property to additionalActions type This allows users to provide meaningful aria-label values for accessibility or set ariaLabel={false} for buttons with text content.
28661b5 to
0f8221b
Compare
Verify HarnessVerdict: Evidence (vision-check, Vision reasoningThe diff adds an optional How Playwright validated thistest('ActionBar forwards ariaLabel from ActionItem to the rendered button', 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 previewIframe = sb.previewIframe();
const previewRoot = sb.previewRoot();
await expect(previewRoot).toBeAttached();
const buttons = previewIframe.locator('button');
const count = await buttons.count();
expect(count).toBeGreaterThan(0);
for (let i = 0; i < count; i++) {
const btn = buttons.nth(i);
const aria = await btn.getAttribute('aria-label');
expect(aria).toBeNull();
}
await page.goto(`${baseURL}/?path=/docs/components-actionbar--docs`);
await sb.waitUntilLoaded();
const docsRoot = sb.previewRoot();
await expect(docsRoot).toBeAttached();
const docsButtons = previewIframe.locator('button');
await expect(docsButtons.first()).toBeVisible({ timeout: 15000 });
} 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#34752.