Skip to content

[harness eval #34712] Fix XSS in Brand: sanitize dangerouslySetInnerHTML with DOMPurify#31

Open
valentinpalkovic wants to merge 2 commits into
nextfrom
try-pr-34712
Open

[harness eval #34712] Fix XSS in Brand: sanitize dangerouslySetInnerHTML with DOMPurify#31
valentinpalkovic wants to merge 2 commits into
nextfrom
try-pr-34712

Conversation

@valentinpalkovic
Copy link
Copy Markdown
Owner

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

@valentinpalkovic valentinpalkovic added the ci:verify Trigger PR Verification Harness label May 14, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 14, 2026

Fails
🚫

PR is not labeled with one of: ["cleanup","BREAKING CHANGE","feature request","bug","documentation","maintenance","build","dependencies"]

🚫

PR is not labeled with one of: ["ci:normal","ci:merged","ci:daily","ci:docs"]

🚫 PR title must be in the format of "Area: Summary", With both Area and Summary starting with a capital letter Good examples: - "Docs: Describe Canvas Doc Block" - "Svelte: Support Svelte v4" Bad examples: - "add new api docs" - "fix: Svelte 4 support" - "Vue: improve docs"

Generated by 🚫 dangerJS against ab83e49

github-actions Bot pushed a commit that referenced this pull request May 14, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

PR-added unit tests: ❌ failed — vitest exited 1 without writing a JSON report (likely setup error); see Action log

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

vitest output (last 4KB)
 /tmp/claude/eval-sync-baselines-nested-no-legacy-4L3QdC/remotes/excalidraw.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-staging-UoHkvb'...
warning: You appear to have cloned an empty repository.
done.
To /tmp/claude/eval-sync-baselines-auto-clone-Shj00Y/remotes/mealdrop.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-staging-8eDDq5'...
warning: You appear to have cloned an empty repository.
done.
To /tmp/claude/eval-sync-baselines-auto-clone-Shj00Y/remotes/wikitok.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-nAkeOe/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-nAkeOe/remotes/wikitok.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-noop-H7eNOe/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-baselines-target-behind-ppgrc7/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-baselines-target-behind-ppgrc7/remotes/edgy.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-baselines-target-behind-ppgrc7/edgy-remote-worktree'...
done.
To /tmp/claude/eval-sync-baselines-target-behind-ppgrc7/remotes/edgy.git
   bca0f70..7783448  main -> main
To /tmp/claude/eval-sync-storybook-version-dirty-h0W7OL/remotes/mealdrop.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-storybook-version-staging-eHqEbY'...
warning: You appear to have cloned an empty repository.
done.
To /tmp/claude/eval-sync-storybook-version-auto-clone-V4KlJl/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-skip-push-ymADFJ/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
To /tmp/claude/eval-sync-storybook-version-resume-push-6eWBrg/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

������ Unhandled Error �������
Error: EROFS: read-only file system, open '/home/runner/work/_temp/unit-tests-report.json'
 � open node:internal/fs/promises:636:25
 � Object.writeFile node:internal/fs/promises:1205:14
 � JsonReporter.writeReport node_modules/vitest/dist/chunks/index.UpGiHP7g.js:3626:4
 � JsonReporter.onTestRunEnd node_modules/vitest/dist/chunks/index.UpGiHP7g.js:3613:3
 � Vitest.report node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13968:3
 � TestRun.end node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:12591:3
 � node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13591:6
 � node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13601:11
 � node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13463:19

������������������������������
Serialized Error: { errno: -30, code: 'EROFS', syscall: 'open', path: '/home/runner/work/_temp/unit-tests-report.json' }




Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-14T13-53-41.344Z/pr-31-Brand-sanitizes-XSS--31f15-m-brand-title-via-DOMPurify-chromium/sidebar-brand-sanitized.png

2026-05-14T13-53-41.344Z/pr-31-Brand-sanitizes-XSS--31f15-m-brand-title-via-DOMPurify-chromium/sidebar-brand-sanitized.png

2026-05-14T13-53-41.344Z/pr-31-Brand-sanitizes-XSS--31f15-m-brand-title-via-DOMPurify-chromium/test-failed-1.png

2026-05-14T13-53-41.344Z/pr-31-Brand-sanitizes-XSS--31f15-m-brand-title-via-DOMPurify-chromium/test-failed-1.png

@valentinpalkovic valentinpalkovic force-pushed the next branch 3 times, most recently from fe2f521 to e537022 Compare May 14, 2026 14:09
@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 14, 2026
github-actions Bot pushed a commit that referenced this pull request May 14, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: PR-added unit tests failed (see unitTests.details)

Evidence (vision-check, claude-haiku-4-5-20251001): undetermined

Vision reasoning

The diff contains user-visible changes to the Brand component (DOMPurify sanitization and rel attribute addition), but the Playwright recipe is designed to verify module resolution and XSS prevention rather than visible UI changes. The screenshots show the sidebar renders without errors, which evidences the DOMPurify dependency loads correctly, but the sanitization and rel attribute changes are not visually observable—they are runtime contract validations that don't produce detectable UI differences from the screenshots.

PR-added unit tests: ❌ failed — vitest exited 1 without writing a JSON report (likely setup error); see Action log

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

vitest output (last 4KB)
 /tmp/claude/eval-sync-baselines-nested-no-legacy-w7RMa5/remotes/excalidraw.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-staging-WiGXXL'...
warning: You appear to have cloned an empty repository.
done.
To /tmp/claude/eval-sync-baselines-auto-clone-OafUfU/remotes/mealdrop.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-staging-aoTzjf'...
warning: You appear to have cloned an empty repository.
done.
To /tmp/claude/eval-sync-baselines-auto-clone-OafUfU/remotes/wikitok.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-loF3Bm/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-loF3Bm/remotes/wikitok.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-noop-XVqMgI/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-baselines-target-behind-bbGQmv/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-baselines-target-behind-bbGQmv/remotes/edgy.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-baselines-target-behind-bbGQmv/edgy-remote-worktree'...
done.
To /tmp/claude/eval-sync-baselines-target-behind-bbGQmv/remotes/edgy.git
   8d15f14..0ef4799  main -> main
To /tmp/claude/eval-sync-storybook-version-dirty-9tykAG/remotes/mealdrop.git
 * [new branch]      main -> main
Cloning into '/tmp/claude/eval-sync-storybook-version-staging-u2rIds'...
warning: You appear to have cloned an empty repository.
done.
To /tmp/claude/eval-sync-storybook-version-auto-clone-MspjmE/remotes/mealdrop.git
 * [new branch]      main -> main
To /tmp/claude/eval-sync-storybook-version-skip-push-8sg2HK/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
To /tmp/claude/eval-sync-storybook-version-resume-push-G1Kxxn/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)

������ Unhandled Error �������
Error: EROFS: read-only file system, open '/home/runner/work/_temp/unit-tests-report.json'
 � open node:internal/fs/promises:636:25
 � Object.writeFile node:internal/fs/promises:1205:14
 � JsonReporter.writeReport node_modules/vitest/dist/chunks/index.UpGiHP7g.js:3626:4
 � JsonReporter.onTestRunEnd node_modules/vitest/dist/chunks/index.UpGiHP7g.js:3613:3
 � Vitest.report node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13968:3
 � TestRun.end node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:12591:3
 � node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13591:6
 � node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13601:11
 � node_modules/vitest/dist/chunks/cli-api.Cjt90eJu.js:13463:19

������������������������������
Serialized Error: { errno: -30, code: 'EROFS', syscall: 'open', path: '/home/runner/work/_temp/unit-tests-report.json' }




Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-14T14-24-42.965Z/pr-31-sidebar-Brand-render-c8493-itle-without-runtime-errors-chromium/sidebar-brand.png

2026-05-14T14-24-42.965Z/pr-31-sidebar-Brand-render-c8493-itle-without-runtime-errors-chromium/sidebar-brand.png

2026-05-14T14-24-42.965Z/pr-31-sidebar-Brand-render-c8493-itle-without-runtime-errors-chromium/test-finished-1.png

2026-05-14T14-24-42.965Z/pr-31-sidebar-Brand-render-c8493-itle-without-runtime-errors-chromium/test-finished-1.png

@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 14, 2026
github-actions Bot pushed a commit that referenced this pull request May 14, 2026
@github-actions github-actions Bot added the verified-by-harness Verified by PR Verify Harness label May 14, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: verified (target internal-ui)

Evidence (vision-check, claude-haiku-4-5-20251001): undetermined

Vision reasoning

The diff adds DOMPurify sanitization to the Brand component and security-related attributes (rel='noopener noreferrer'), but these are primarily defensive security changes that are not visually observable in screenshots. The sanitization prevents XSS execution but doesn't produce a visible UI difference—the safe text renders identically to unsanitized safe HTML. The screenshots show the sidebar rendering without errors, confirming the code works, but cannot demonstrate the sanitization itself visually.

PR-added unit tests: ✅ passed — 6716 passed, 0 failed across 2120 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-14T14-40-20.947Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/sidebar-brand.png

2026-05-14T14-40-20.947Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/sidebar-brand.png

2026-05-14T14-40-20.947Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/test-finished-1.png

2026-05-14T14-40-20.947Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/test-finished-1.png

@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 15, 2026
@valentinpalkovic valentinpalkovic force-pushed the next branch 2 times, most recently from 80ccd7d to 745162d Compare May 15, 2026 16:58
github-actions Bot pushed a commit that referenced this pull request May 15, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: verified (target internal-ui)

Evidence (vision-check, claude-haiku-4-5-20251001): undetermined

Vision reasoning

The diff modifies Brand.tsx to add DOMPurify sanitization of the theme.brand.title, but the screenshots show the sidebar with the default Storybook logo (image branch), not the dangerouslySetInnerHTML branch that would visibly render the sanitized title. The recipe exercises the Brand component's mount path and verifies no errors occurred, but the visual change (sanitized HTML rendering) is not observable in these screenshots since the default brand uses an image, not HTML content.

PR-added unit tests: ✅ passed — 6716 passed, 0 failed across 2120 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-15T16-55-27.741Z/pr-31-Brand-sanitizes-theme-brand-title-via-DOMPurify-chromium/sidebar-brand.png

2026-05-15T16-55-27.741Z/pr-31-Brand-sanitizes-theme-brand-title-via-DOMPurify-chromium/sidebar-brand.png

2026-05-15T16-55-27.741Z/pr-31-Brand-sanitizes-theme-brand-title-via-DOMPurify-chromium/test-finished-1.png

2026-05-15T16-55-27.741Z/pr-31-Brand-sanitizes-theme-brand-title-via-DOMPurify-chromium/test-finished-1.png

@valentinpalkovic valentinpalkovic force-pushed the next branch 3 times, most recently from a11176d to 9de9d5b Compare May 15, 2026 17:25
@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 15, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

No verdict produced — the workflow failed before the harness ran (likely recipe-author dispatch, deny-regex, or lint). See run log for details.

github-actions Bot pushed a commit that referenced this pull request May 18, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: Playwright assertion failed in: ? — Error: expect(locator).toBeVisible() failed Locator: locator('.sidebar-container a').filter({ hasText: 'BrandXSS' }).first() Expected: visible Timeout: 10000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 10000ms - waiting for locator('.sidebar-container a').filter({ hasText: 'BrandXSS' }).first() 76 | // Wait for the sidebar brand link to reflect the inj

PR-added unit tests: ✅ passed — 6764 passed, 0 failed across 2130 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

How Playwright validated this
//
//
//

test('Brand title sanitization via DOMPurify strips script and inline handlers', 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 dialogs: string[] = [];
  page.on('dialog', async (dialog) => {
    dialogs.push(`${dialog.type()}:${dialog.message()}`);
    await dialog.dismiss().catch(() => {});
  });

  const baseURL =
    process.env.STORYBOOK_URL ?? testInfo.project.use.baseURL ?? 'http://localhost:6006';

  try {
    await page.goto(`${baseURL}/?path=/story/example-button--primary`);
    const sb = new RecipePage(page, expect);
    await sb.waitUntilLoaded();

    const brandLink = page.locator('.sidebar-container a[href="./"]').first();
    await expect(brandLink).toBeVisible({ timeout: 10000 });

    const brandImg = brandLink.locator('img').first();
    await expect(brandImg).toBeVisible();

    const sanitizerProbe = await page.evaluate(async () => {
      const payloads = [
        'Hello<script>window.__xss1=true;</script>',
        '<img src=x onerror="window.__xss2=true">',
        '<strong>Safe</strong>',
      ];

      const w = window as unknown as Record<string, unknown>;
      const candidates: unknown[] = [
        w.DOMPurify,
        (w.dompurify as Record<string, unknown> | undefined)?.default,
      ];
      let purify: { sanitize: (s: string) => string } | undefined;
      for (const c of candidates) {
        if (c && typeof (c as { sanitize?: unknown }).sanitize === 'function') {
          purify = c as { sanitize: (s: string) => string };
          break;
        }
      }

      const baselineDiv = document.createElement('div');
      baselineDiv.innerHTML = payloads[0] + payloads[1] + payloads[2];
      const baselineHtml = baselineDiv.innerHTML;

      let saniti

Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-18T06-41-40.339Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/test-failed-1.png

2026-05-18T06-41-40.339Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/test-failed-1.png

@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 18, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

No verdict produced — the workflow failed before the harness ran (likely recipe-author dispatch, deny-regex, or lint). See run log for details.

valentinpalkovic added a commit that referenced this pull request May 18, 2026
…rity no-verdict cause

Wave finding (#31 try-pr-34712 XSS): recipe-author correctly chose behavioral
mode and drove the change via the public manager-api `setOptions` path (no
module import), but needed `(window as any).__STORYBOOK_ADDONS_MANAGER` to
reach the runtime singleton. `@typescript-eslint/recommended` makes
`no-explicit-any` an error, so the scoped lint gate failed twice → no verdict.

- .verify-recipes/.eslintrc.cjs: `@typescript-eslint/no-explicit-any: 'off'`.
  Code-quality rule, NOT a security control — deny-regex and
  no-restricted-{globals,imports,syntax} remain the load-bearing gates.
  `as any` for window/manager-api globals is correct and unavoidable.
- _recipe-authoring-guide.md §12.5: note that `as any` for runtime globals
  is allowed; don't waste retries trying to type them.

Verified: behavioral recipe using `(window as any).__STORYBOOK_ADDONS_MANAGER`
now lints clean (exit 0).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 18, 2026
github-actions Bot pushed a commit that referenced this pull request May 18, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: Playwright assertion failed in: ? — Error: expect(locator).toBeVisible() failed Locator: locator('.sidebar-container').first().getByText('BrandText') Expected: visible Timeout: 10000ms Error: element(s) not found Call log: - Expect "toBeVisible" with timeout 10000ms - waiting for locator('.sidebar-container').first().getByText('BrandText') 87 | const brandHost = page.locator('.sidebar-container').first(); 88 | aw

PR-added unit tests: ✅ passed — 6764 passed, 0 failed across 2130 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

How Playwright validated this
//
//

test('Brand renders cleanly after DOMPurify wiring; no boot/runtime errors from sanitizer change', 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 sb = new RecipePage(page, expect);
    await sb.waitUntilLoaded();

    const sidebar = page.locator('.sidebar-container');
    await expect(sidebar).toBeVisible();

    const brandLink = page.getByRole('banner').getByRole('link', { name: 'Storybook' });
    await expect(brandLink).toBeVisible();
    await expect(brandLink).toHaveAttribute('href', './');

    const errorDisplay = page.locator('#sb-errordisplay');
    await expect(errorDisplay).toBeHidden();

    const previewRoot = sb.previewIframe().locator('#storybook-root, #root');
    await expect(previewRoot).toBeVisible();
  } 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(consoleErrors).toEqual([]);
});

Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-18T11-35-03.080Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/test-failed-1.png

2026-05-18T11-35-03.080Z/pr-31-Brand-sanitizes-dang-c64d6-nerHTML-title-via-DOMPurify-chromium/test-failed-1.png

valentinpalkovic added a commit that referenced this pull request May 18, 2026
…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>
@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 18, 2026
github-actions Bot pushed a commit that referenced this pull request May 18, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: Playwright assertion failed in: ? — TimeoutError: locator.waitFor: Timeout 10000ms exceeded. Call log: - waiting for locator('#storybook-preview-iframe').contentFrame().locator('#storybook-root:visible, #storybook-docs:visible').locator(':scope > *').first() at _util.ts:66 64 | await this.page.waitForURL((url) => url.search.includes('path')); 65 | const root = this.previewRoot(); > 66 | await root.locator(':s

PR-added unit tests: ✅ passed — 6764 passed, 0 failed across 2130 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

How Playwright validated this
test('Brand sanitizes theme.brand.title via DOMPurify (image===null path)', 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/manager-sidebar-heading--only-text&viewMode=story`
    );

    const sb = new RecipePage(page, expect);
    await page.locator('.sb-preparing-story').waitFor({ state: 'hidden', timeout: 30_000 });
    await page.locator('.sb-preparing-docs').waitFor({ state: 'hidden', timeout: 30_000 });

    const errorDisplay = page.locator('#sb-errordisplay');
    await expect(errorDisplay).toBeHidden();

    const previewIframe = sb.previewIframe();
    const brand = previewIframe.locator('body');
    await expect(brand).toContainText(/Only text/i);

    const scriptCount = await previewIframe.locator('script:not([src])').count();
    expect(scriptCount).toBe(0);

    await page.goto(
      `${baseURL}/?path=/story/manager-sidebar-heading--link-and-text&viewMode=story`
    );
    await page.locator('.sb-preparing-story').waitFor({ state: 'hidden', timeout: 30_000 });
    await page.locator('.sb-preparing-docs').waitFor({ state: 'hidden', timeout: 30_000 });

    const linkBrand = sb.previewIframe().locator('body');
    await expect(linkBrand.locator('a').first()).toBeVisible();
  } 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: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-18T11-58-37.004Z/pr-31-Brand-renders-saniti-91d69-le-without-script-injection-chromium/test-failed-1.png

2026-05-18T11-58-37.004Z/pr-31-Brand-renders-saniti-91d69-le-without-script-injection-chromium/test-failed-1.png

valentinpalkovic added a commit that referenced this pull request May 18, 2026
… TMPDIR pinned

Two distinct wave-#31/#36 root causes, both false regressions:

(a) _util.ts previewRoot() filtered `#storybook-root:visible`. Stories with
    `parameters.layout:'fullscreen'` + the internal-ui side-by-side/stacked
    theme decorator wrap the story so #storybook-root has a zero-size
    (Playwright-"not visible") box though it rendered — locator matched
    nothing, waitForStoryLoaded timed out (#31 manager-sidebar-heading--*).
    Use `:has(> *)` instead: selects whichever container actually has
    children, keeps story-vs-docs disambiguation, drops the bounding-box
    requirement.

(b) verify-pr.yml unit-test step runs `env -i … srt … yarn vitest`. `env -i`
    strips TMPDIR, so Yarn's run-temp realpaths a nonexistent srt path
    (`lstat '/tmp/claude'` ENOENT) and aborts before vitest starts →
    false "vitest exited without JSON report" regression (#36 a11yRunner).
    Pin TMPDIR to an existing allowWrite dir
    ($PR_HEAD_DIR/.verify-output/vitest-tmp), same rationale REPORT/LOG
    already live there.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 18, 2026
github-actions Bot pushed a commit that referenced this pull request May 18, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: Playwright assertion failed in: ? — Error: expect(locator).toBeVisible() failed Locator: locator('#storybook-preview-iframe').contentFrame().locator('#storybook-root, #root') Expected: visible Received: hidden Timeout: 10000ms Call log: - Expect "toBeVisible" with timeout 10000ms - waiting for locator('#storybook-preview-iframe').contentFrame().locator('#storybook-root, #root') 14 × locator resolved to <div id="storybook

PR-added unit tests: ✅ passed — 6764 passed, 0 failed across 2130 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

How Playwright validated this
test('Brand sanitizes dangerouslySetInnerHTML title on image:null branch', 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/manager-sidebar-heading--link-and-text`);

    const sb = new RecipePage(page, expect);
    await sb.waitUntilLoaded();

    const previewIframe = sb.previewIframe();

    const brandLink = previewIframe.locator('a[href]').first();
    await expect(brandLink).toBeVisible({ timeout: 15000 });
    const linkText = await brandLink.innerText();
    expect(linkText.trim().length).toBeGreaterThan(0);

    const scriptCount = await previewIframe
      .locator('script:not([src])')
      .evaluateAll((nodes) =>
        nodes.filter((n) => /alert\s*\(/i.test(n.textContent ?? '')).length
      );
    expect(scriptCount).toBe(0);

    await page.goto(`${baseURL}/?path=/story/manager-sidebar-heading--only-text`);
    await sb.waitUntilLoaded();

    const previewIframe2 = sb.previewIframe();
    const previewRoot = previewIframe2.locator('#storybook-root');
    await expect(previewRoot).toBeAttached({ timeout: 15000 });
    const renderedText = await previewRoot.innerText();
    expect(renderedText.trim().length).toBeGreaterThan(0);

    const errorDisplay = page.locator('#sb-errordisplay');
    await expect(errorDisplay).toBeHidden();
  } 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(p

Replay: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-18T21-07-06.979Z/pr-31-Brand-renders-saniti-de583-erHTML-on-image-null-branch-chromium/test-failed-1.png

2026-05-18T21-07-06.979Z/pr-31-Brand-renders-saniti-de583-erHTML-on-image-null-branch-chromium/test-failed-1.png

valentinpalkovic added a commit that referenced this pull request May 18, 2026
…bans root-visible assert

Re-run of #36/#31 showed both prior fixes missed the real cause:

- #36: TMPDIR pin had zero effect — Yarn's mktempPromise still ENOENT
  `/tmp/claude`. Root cause: srt derives its sandbox tmp from
  CLAUDE_CODE_TMPDIR, NOT TMPDIR. The main recipe run inherits it via
  $GITHUB_ENV ($SANDBOX_TMPDIR); the unit-test step's `env -i` strips it,
  so srt falls back to its hardcoded `/tmp/claude` (never created). Pass
  CLAUDE_CODE_TMPDIR=$VITEST_TMPDIR (existing allowWrite dir) in env -i.

- #31: previewRoot `:has(> *)` fix removed the _util.ts:66 timeout, but the
  recipe-author hand-rolled `expect('#storybook-root').toBeVisible()` which
  is "hidden" for `Sidebar/Heading` (layout:fullscreen + side-by-side =
  zero-box root). Brand triage rule now explicitly bans root-visibility
  asserts and prescribes a child `toBeAttached()` content assertion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@valentinpalkovic valentinpalkovic added ci:verify Trigger PR Verification Harness and removed ci:verify Trigger PR Verification Harness labels May 18, 2026
github-actions Bot pushed a commit that referenced this pull request May 18, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: PR-added unit tests failed (see unitTests.details)

PR-added unit tests: ❌ failed — 6763 passed, 1 failed across 2130 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

vitest output (last 4KB)
      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-dirty-zC7JqU/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-dirty-zC7JqU/remotes/edgy.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-nested-no-legacy-lHJh8Y/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-nested-no-legacy-lHJh8Y/remotes/excalidraw.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-HE7WqU/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-HE7WqU/remotes/wikitok.git
 * [new branch]      main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-staging-liySn4'...
warning: You appear to have cloned an empty repository.
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-auto-clone-EmBump/remotes/mealdrop.git
 * [new branch]      main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-staging-4ZzhxS'...
warning: You appear to have cloned an empty repository.
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-auto-clone-EmBump/remotes/wikitok.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-noop-d5QDri/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-dirty-3qNFkZ/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-8qlIX8/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-8qlIX8/remotes/edgy.git
 * [new branch]      main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-8qlIX8/edgy-remote-worktree'...
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-8qlIX8/remotes/edgy.git
   048fc34..1e8b1f8  main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-staging-KHzavy'...
warning: You appear to have cloned an empty repository.
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-auto-clone-fk2G18/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-skip-push-W64Qv8/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-resume-push-hayAlv/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
JSON report written to /home/runner/work/_temp/pr-head/.verify-output/unit-tests-report.json

How Playwright validated this
test('Brand sanitizes title HTML on image:null branch without breaking render', 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/manager-sidebar-heading--only-text`);

    const sb = new RecipePage(page, expect);
    await sb.waitUntilLoaded();

    await expect(page.locator('#sb-errordisplay')).toBeHidden();

    const previewIframe = sb.previewIframe();

    const brandTitle = previewIframe
      .locator('#storybook-root')
      .locator('div, a')
      .filter({ hasText: /My title/i })
      .first();
    await expect(brandTitle).toBeAttached();

    const scriptCount = await previewIframe
      .locator('#storybook-root script')
      .count();
    expect(scriptCount).toBe(0);

    await page.goto(`${baseURL}/?path=/story/manager-sidebar-heading--link-and-text`);
    await sb.waitUntilLoaded();
    await expect(page.locator('#sb-errordisplay')).toBeHidden();

    const linkBrand = previewIframe
      .locator('#storybook-root a')
      .filter({ hasText: /./ })
      .first();
    await expect(linkBrand).toBeAttached();

    const linkScriptCount = await previewIframe
      .locator('#storybook-root script')
      .count();
    expect(linkScriptCount).toBe(0);
  } 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: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-18T21-20-51.464Z/pr-31-Brand-sanitizes-titl-7413c-nch-without-breaking-render-chromium/test-finished-1.png

2026-05-18T21-20-51.464Z/pr-31-Brand-sanitizes-titl-7413c-nch-without-breaking-render-chromium/test-finished-1.png

github-actions Bot pushed a commit that referenced this pull request May 19, 2026
@github-actions
Copy link
Copy Markdown

Verify Harness

Verdict: regression (target internal-ui)

Reason: Playwright assertion failed in: ? — TimeoutError: locator.waitFor: Timeout 10000ms exceeded. Call log: - waiting for locator('#storybook-preview-iframe').contentFrame().locator('#storybook-root:has(> *), #storybook-docs:has(> *)').locator(':scope > *').first() at _util.ts:75 73 | await this.page.waitForURL((url) => url.search.includes('path')); 74 | const root = this.previewRoot(); > 75 | await root.locator('

PR-added unit tests: ❌ failed — 6944 passed, 1 failed across 2180 suite(s)

Files: code/core/src/manager/components/sidebar/__tests__/Brand.test.tsx

vitest output (last 4KB)
      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-dirty-Bm08iP/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-dirty-Bm08iP/remotes/edgy.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-nested-no-legacy-e1pe17/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-nested-no-legacy-e1pe17/remotes/excalidraw.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-YyWSL9/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-YyWSL9/remotes/wikitok.git
 * [new branch]      main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-staging-wKNdWX'...
warning: You appear to have cloned an empty repository.
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-auto-clone-UevYL7/remotes/mealdrop.git
 * [new branch]      main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-staging-9bzEr7'...
warning: You appear to have cloned an empty repository.
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-auto-clone-UevYL7/remotes/wikitok.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-noop-PYJozC/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-dirty-6Af4gR/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-rKbhyH/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-rKbhyH/remotes/edgy.git
 * [new branch]      main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-rKbhyH/edgy-remote-worktree'...
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-baselines-target-behind-rKbhyH/remotes/edgy.git
   520935f..0b736db  main -> main
Cloning into '/home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-staging-T4s6w0'...
warning: You appear to have cloned an empty repository.
done.
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-auto-clone-9lucfh/remotes/mealdrop.git
 * [new branch]      main -> main
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-skip-push-VwRZgw/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) ExperimentalWarning: SQLite is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
To /home/runner/work/_temp/pr-head/.verify-output/vitest-tmp/eval-sync-storybook-version-resume-push-Mrv338/remotes/mealdrop.git
 * [new branch]      main -> main
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
(node:21) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
JSON report written to /home/runner/work/_temp/pr-head/.verify-output/unit-tests-report.json

How Playwright validated this
test('Brand sanitizes title innerHTML on the image:null branch', 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/manager-sidebar-heading--only-text`);

    const sb = new RecipePage(page, expect);
    await sb.waitUntilLoaded();

    await expect(page.locator('#sb-errordisplay')).toBeHidden();

    const previewIframe = sb.previewIframe();

    const brandTitle = previewIframe
      .locator('#storybook-root, #storybook-docs')
      .locator('a, div')
      .filter({ hasText: /My title|Storybook/i })
      .first();
    await expect(brandTitle).toBeAttached();

    const scriptCount = await previewIframe
      .locator('#storybook-root script, #storybook-docs script')
      .count();
    expect(scriptCount).toBe(0);
  } 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: npx playwright show-trace on the trace.zip listed under "Artifacts" on the run summary page.

Screenshots

2026-05-19T14-00-07.999Z/pr-31-Brand-custom-HTML-ti-85680-tion-without-runtime-errors-chromium/test-failed-1.png

2026-05-19T14-00-07.999Z/pr-31-Brand-custom-HTML-ti-85680-tion-without-runtime-errors-chromium/test-failed-1.png

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:verify Trigger PR Verification Harness

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants