OTTODesign System

Code

Interactive

Storybook group: Tests · Sidebar path: Tests/Interactive · Extracted 28.09.2026

Interaction test stories (automated tests, not usage patterns).

Story: tests-interactive--deprecated-links · tags: play-fn

<h2 class="oc-headline-200">oc-button-v1 (Deprecated)</h2>
<oc-button-v1 href="/foo" rel="noreferrer" target="_blank">oc-button-v1</oc-button-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-card-v2 (Deprecated)</h2>
<oc-card-v2 href="/foo" rel="noreferrer" target="_blank">oc-card-v2</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-energy-label-v1 (Deprecated)</h2>
<oc-energy-label-v1 href="/foo" rel="noreferrer" target="_blank"></oc-energy-label-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v2 (Deprecated)</h2>
<oc-icon-button-v2
  href="/foo"
  rel="noreferrer"
  target="_blank"
  icon="almond"
  oc-aria-label="oc-icon-button-v2 (screen reader only)"
></oc-icon-button-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-link-v2 (Deprecated)</h2>
<oc-link-v2 href="/foo" rel="noreferrer" target="_blank">oc-link-v2</oc-link-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-logo-v1 (Deprecated)</h2>
<oc-logo-v1
  href="/foo"
  rel="noreferrer"
  target="_blank"
  logo="otto"
  oc-aria-label="oc-logo-v1 (screen reader only)"
></oc-logo-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1 (Deprecated)</h2>
<oc-promo-card-v1 href="/foo" rel="noreferrer" target="_blank"
  >oc-promo-card-v1
</oc-promo-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-row-v2 (Deprecated)</h2>
<oc-row-v2 href="/foo" rel="noreferrer" target="_blank">oc-row-v2 </oc-row-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <h2 class="oc-headline-200">oc-button-v1 (Deprecated)</h2>
      <oc-button-v1 href="/foo" rel="noreferrer" target="_blank">oc-button-v1</oc-button-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-card-v2 (Deprecated)</h2>
      <oc-card-v2 href="/foo" rel="noreferrer" target="_blank">oc-card-v2</oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-energy-label-v1 (Deprecated)</h2>
      <oc-energy-label-v1 href="/foo" rel="noreferrer" target="_blank"></oc-energy-label-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v2 (Deprecated)</h2>
      <oc-icon-button-v2
        href="/foo"
        rel="noreferrer"
        target="_blank"
        icon="almond"
        oc-aria-label="oc-icon-button-v2 (screen reader only)"
      ></oc-icon-button-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-link-v2 (Deprecated)</h2>
      <oc-link-v2 href="/foo" rel="noreferrer" target="_blank">oc-link-v2</oc-link-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-logo-v1 (Deprecated)</h2>
      <oc-logo-v1
        href="/foo"
        rel="noreferrer"
        target="_blank"
        logo="otto"
        oc-aria-label="oc-logo-v1 (screen reader only)"
      ></oc-logo-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1 (Deprecated)</h2>
      <oc-promo-card-v1 href="/foo" rel="noreferrer" target="_blank"
        >oc-promo-card-v1
      </oc-promo-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-row-v2 (Deprecated)</h2>
      <oc-row-v2 href="/foo" rel="noreferrer" target="_blank">oc-row-v2 </oc-row-v2>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(8);
    await waitFor(function hrefAttribute() {
      return components.map(c => expect(c.getAttribute("href"), `${c.tagName}.getAttribute("href")`).toBe("/foo"));
    });
    await waitFor(function hrefProperty() {
      return components.map(c => expect(c.href, `${c.tagName}.href`).toBe(`${window.location.origin}/foo`));
    });
    await waitFor(function aTagInShadowDom() {
      return components.flatMap(c => [expect(c.shadowRoot!.querySelector("a"), `${c.tagName} > a`).toBeTruthy(), expect(c.shadowRoot!.querySelector("a")?.href, `${c.tagName} > a.href`).toBe(`${window.location.origin}/foo`), expect(c.shadowRoot!.querySelector("a")?.rel, `${c.tagName} > a.rel`).toBe(`noreferrer`), expect(c.shadowRoot!.querySelector("a")?.target, `${c.tagName} > a.target`).toBe(`_blank`)]);
    });
    components.forEach(c => {
      c.href = "/bar";
    });
    await tick();
    await waitFor(function hrefPropertyBar() {
      return components.map(c => expect(c.href, `${c.tagName}.href`).toBe(`${window.location.origin}/bar`));
    });
  }
}

Interactive (MultipleInteractions.interactions.stories.ts)

Interaction test stories (automated tests, not usage patterns).

Multiple Interactions

Story: tests-interactive--multiple-interactions · tags: play-fn

<h2 class="oc-headline-200">oc-card-v2</h2>
<oc-card-v2>
  <a
    slot="primary-action"
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-card-v2 (screen reader only)"
  ></a>
  <div class="oc-m-100">oc-card-v2</div>
  <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
<oc-promo-card-v1>
  <a
    slot="primary-action"
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-promo-card-v1 (screen reader only)"
  ></a>
  <div class="oc-m-100">oc-promo-card-v1</div>
  <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
</oc-promo-card-v1>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <h2 class="oc-headline-200">oc-card-v2</h2>
      <oc-card-v2>
        <a
          slot="primary-action"
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-card-v2 (screen reader only)"
        ></a>
        <div class="oc-m-100">oc-card-v2</div>
        <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
      </oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
      <oc-promo-card-v1>
        <a
          slot="primary-action"
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-promo-card-v1 (screen reader only)"
        ></a>
        <div class="oc-m-100">oc-promo-card-v1</div>
        <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
      </oc-promo-card-v1>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(2);
    await expectNoInteractiveElementsInShadowDom(components);
  }
}

Interactive (MultipleInteractionsWithButtons.interactions.stories.ts)

Interaction test stories (automated tests, not usage patterns).

Multiple Interactions With Buttons

Story: tests-interactive--multiple-interactions-with-buttons · tags: play-fn

<h2 class="oc-headline-200">oc-card-v2</h2>
<oc-card-v2>
  <button slot="primary-action" aria-label="oc-card-v2 (screen reader only)"></button>
  <div class="oc-m-100">oc-card-v2</div>
  <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
<oc-promo-card-v1>
  <button slot="primary-action" aria-label="oc-promo-card-v1 (screen reader only)"></button>
  <div class="oc-m-100">oc-promo-card-v1</div>
  <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
</oc-promo-card-v1>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <h2 class="oc-headline-200">oc-card-v2</h2>
      <oc-card-v2>
        <button slot="primary-action" aria-label="oc-card-v2 (screen reader only)"></button>
        <div class="oc-m-100">oc-card-v2</div>
        <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
      </oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
      <oc-promo-card-v1>
        <button slot="primary-action" aria-label="oc-promo-card-v1 (screen reader only)"></button>
        <div class="oc-m-100">oc-promo-card-v1</div>
        <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
      </oc-promo-card-v1>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(2);
    await expectNoInteractiveElementsInShadowDom(components);
  }
}

Interaction test stories (automated tests, not usage patterns).

Story: tests-interactive--multiple-interactions-with-masked-links · tags: play-fn

<h2 class="oc-headline-200">oc-card-v2</h2>
<oc-card-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">
  <span slot="primary-action" aria-label="oc-card-v2 (screen reader only)"></span>
  <div class="oc-m-100">oc-card-v2</div>
  <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
<oc-promo-card-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">
  <span slot="primary-action" aria-label="oc-promo-card-v1 (screen reader only)"></span>
  <div class="oc-m-100">oc-promo-card-v1</div>
  <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
</oc-promo-card-v1>
Story source (TypeScript, verbatim from Storybook)
{
  name: "Multiple interactions with masked links",
  render() {
    return html`
      <h2 class="oc-headline-200">oc-card-v2</h2>
      <oc-card-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">
        <span slot="primary-action" aria-label="oc-card-v2 (screen reader only)"></span>
        <div class="oc-m-100">oc-card-v2</div>
        <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
      </oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
      <oc-promo-card-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">
        <span slot="primary-action" aria-label="oc-promo-card-v1 (screen reader only)"></span>
        <div class="oc-m-100">oc-promo-card-v1</div>
        <button class="oc-secondary-action oc-m-100 oc-p-100">secondary action</button>
      </oc-promo-card-v1>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(2);
    await expectNoInteractiveElementsInShadowDom(components);
    await waitFor(function unmaskAndCheckFocus() {
      return components.map(c => {
        (c.querySelector('[slot="primary-action"]') as HTMLElement).focus();
        return expect(document.activeElement, `${c.tagName} > a`).toBe(c.querySelector("a"));
      });
    });
    await waitFor(function aTagHrefShouldBeFoo() {
      return components.flatMap(c => [expect(c.querySelector("a")!.href, `${c.tagName} > a`).toBe(`${window.location.origin}/foo`)]);
    });
    await expectNoInteractiveElementsInShadowDom(components);
  }
}

Interaction test stories (automated tests, not usage patterns).

Story: tests-interactive--seo-masked-links · tags: play-fn

<h2 class="oc-headline-200">oc-button-v1</h2>
<oc-button-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank"
  >oc-button-v1</oc-button-v1
>

<h2 class="oc-headline-200 oc-mt-100">oc-card-v2</h2>
<oc-card-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">oc-card-v2</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-energy-label-v1 (Deprecated)</h2>
<oc-energy-label-v1
  base64-href="L2Zvbw=="
  rel="noreferrer"
  target="_blank"
></oc-energy-label-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v3</h2>
<oc-icon-button-v3
  base64-href="L2Zvbw=="
  rel="noreferrer"
  target="_blank"
  icon="almond"
  oc-aria-label="oc-icon-button-v3 (screen reader only)"
></oc-icon-button-v3>

<h2 class="oc-headline-200 oc-mt-100">oc-interactive-overlay-v1</h2>
<oc-interactive-overlay-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank"
  >oc-interactive-overlay-v1
</oc-interactive-overlay-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-link-v2</h2>
<oc-link-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">oc-link-v2</oc-link-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-logo-v1 (Deprecated)</h2>
<oc-logo-v1
  base64-href="L2Zvbw=="
  rel="noreferrer"
  target="_blank"
  logo="otto"
  oc-aria-label="oc-logo-v1 (screen reader only)"
></oc-logo-v1>

<h2 class="oc-headline-200">oc-media-object-card-v1</h2>
<oc-media-object-card-v1
  variant="ambient-image"
  base64-href="L2Zvbw=="
  rel="noreferrer"
  target="_blank"
>
  <img slot="img" src="${placeholderImage}" alt="blue checkered background" />
  <div>Headline</div>
  <div slot="subline">Subline</div>
</oc-media-object-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
<oc-promo-card-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank"
  >oc-promo-card-v1
</oc-promo-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-row-v2</h2>
<oc-row-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">oc-row-v2 </oc-row-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <h2 class="oc-headline-200">oc-button-v1</h2>
      <oc-button-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank"
        >oc-button-v1</oc-button-v1
      >

      <h2 class="oc-headline-200 oc-mt-100">oc-card-v2</h2>
      <oc-card-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">oc-card-v2</oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-energy-label-v1 (Deprecated)</h2>
      <oc-energy-label-v1
        base64-href="L2Zvbw=="
        rel="noreferrer"
        target="_blank"
      ></oc-energy-label-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v3</h2>
      <oc-icon-button-v3
        base64-href="L2Zvbw=="
        rel="noreferrer"
        target="_blank"
        icon="almond"
        oc-aria-label="oc-icon-button-v3 (screen reader only)"
      ></oc-icon-button-v3>

      <h2 class="oc-headline-200 oc-mt-100">oc-interactive-overlay-v1</h2>
      <oc-interactive-overlay-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank"
        >oc-interactive-overlay-v1
      </oc-interactive-overlay-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-link-v2</h2>
      <oc-link-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">oc-link-v2</oc-link-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-logo-v1 (Deprecated)</h2>
      <oc-logo-v1
        base64-href="L2Zvbw=="
        rel="noreferrer"
        target="_blank"
        logo="otto"
        oc-aria-label="oc-logo-v1 (screen reader only)"
      ></oc-logo-v1>

      <h2 class="oc-headline-200">oc-media-object-card-v1</h2>
      <oc-media-object-card-v1
        variant="ambient-image"
        base64-href="L2Zvbw=="
        rel="noreferrer"
        target="_blank"
      >
        <img slot="img" src="${placeholderImage}" alt="blue checkered background" />
        <div>Headline</div>
        <div slot="subline">Subline</div>
      </oc-media-object-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
      <oc-promo-card-v1 base64-href="L2Zvbw==" rel="noreferrer" target="_blank"
        >oc-promo-card-v1
      </oc-promo-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-row-v2</h2>
      <oc-row-v2 base64-href="L2Zvbw==" rel="noreferrer" target="_blank">oc-row-v2 </oc-row-v2>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(10);
    await waitFor(function hrefProperty() {
      return components.map(c => expect(c.href, `${c.tagName}.href`).toBe(`${window.location.origin}/foo`));
    });
    await expectNoInteractiveElementsInShadowDom(components);
    await waitFor(function unmaskAndCheckFocus() {
      return components.map(c => {
        (c.shadowRoot!.querySelector(".-interactive") as HTMLElement).focus();
        return expect(document.activeElement!.shadowRoot!.activeElement, `${c.tagName} > a`).toBe(c.shadowRoot!.querySelector("a"));
      });
    });
    await waitFor(function hrefProperty() {
      return components.map(c => expect(c.href, `${c.tagName}.href`).toBe(`${window.location.origin}/foo`));
    });
    await waitFor(function aTagInShadowDom() {
      return components.flatMap(c => [expect(c.shadowRoot!.querySelector("a"), `${c.tagName} > a`).toBeTruthy(), expect(c.shadowRoot!.querySelector("a")?.href, `${c.tagName} > a.href`).toBe(`${window.location.origin}/foo`), expect(c.shadowRoot!.querySelector("a")?.rel, `${c.tagName} > a.rel`).toBe(`noreferrer`), expect(c.shadowRoot!.querySelector("a")?.target, `${c.tagName} > a.target`).toBe(`_blank`)]);
    });
    components.forEach(c => {
      c.href = "/bar";
    });
    await tick();
    await waitFor(function hrefPropertyBar() {
      return components.map(c => expect(c.href, `${c.tagName}.href`).toBe(`${window.location.origin}/bar`));
    });
  }
}

Interaction test stories (automated tests, not usage patterns).

Story: tests-interactive--seo-relevant-links · tags: play-fn

<h2 class="oc-headline-200">oc-button-v1</h2>
<oc-button-v1><a href="/foo" rel="noreferrer" target="_blank">oc-button-v1</a></oc-button-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-card-v2</h2>
<oc-card-v2>
  <a
    slot="primary-action"
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-card-v2 (screen reader only)"
  ></a>
  <div>oc-card-v2</div>
</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v3</h2>
<oc-icon-button-v3 icon="almond">
  <a
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-icon-button-v3 (screen reader only)"
  ></a>
</oc-icon-button-v3>

<h2 class="oc-headline-200 oc-mt-100">oc-interactive-overlay-v1</h2>
<oc-interactive-overlay-v1
  ><a href="/foo" rel="noreferrer" target="_blank"
    >oc-interactive-overlay-v1</a
  ></oc-interactive-overlay-v1
>

<h2 class="oc-headline-200 oc-mt-100">oc-link-v2</h2>
<oc-link-v2><a href="/foo" rel="noreferrer" target="_blank">oc-link-v2</a></oc-link-v2>

<h2 class="oc-headline-200">oc-media-object-card-v1</h2>
<oc-media-object-card-v1 variant="ambient-image">
  <a
    slot="primary-action"
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-media-object-card-v1  (screen reader only)"
  ></a>
  <img slot="img" src="${placeholderImage}" alt="blue checkered background" />
  <div>Headline</div>
  <div slot="subline">Subline</div>
</oc-media-object-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
<oc-promo-card-v1>
  <a
    slot="primary-action"
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-promo-card-v1 (screen reader only)"
  ></a>
  <div>oc-promo-card-v1</div>
</oc-promo-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-row-v2</h2>
<oc-row-v2>
  <a
    slot="primary-action"
    href="/foo"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-row-v2 (screen reader only)"
  ></a>
  <div>oc-row-v2</div>
</oc-row-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <h2 class="oc-headline-200">oc-button-v1</h2>
      <oc-button-v1><a href="/foo" rel="noreferrer" target="_blank">oc-button-v1</a></oc-button-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-card-v2</h2>
      <oc-card-v2>
        <a
          slot="primary-action"
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-card-v2 (screen reader only)"
        ></a>
        <div>oc-card-v2</div>
      </oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v3</h2>
      <oc-icon-button-v3 icon="almond">
        <a
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-icon-button-v3 (screen reader only)"
        ></a>
      </oc-icon-button-v3>

      <h2 class="oc-headline-200 oc-mt-100">oc-interactive-overlay-v1</h2>
      <oc-interactive-overlay-v1
        ><a href="/foo" rel="noreferrer" target="_blank"
          >oc-interactive-overlay-v1</a
        ></oc-interactive-overlay-v1
      >

      <h2 class="oc-headline-200 oc-mt-100">oc-link-v2</h2>
      <oc-link-v2><a href="/foo" rel="noreferrer" target="_blank">oc-link-v2</a></oc-link-v2>

      <h2 class="oc-headline-200">oc-media-object-card-v1</h2>
      <oc-media-object-card-v1 variant="ambient-image">
        <a
          slot="primary-action"
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-media-object-card-v1  (screen reader only)"
        ></a>
        <img slot="img" src="${placeholderImage}" alt="blue checkered background" />
        <div>Headline</div>
        <div slot="subline">Subline</div>
      </oc-media-object-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
      <oc-promo-card-v1>
        <a
          slot="primary-action"
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-promo-card-v1 (screen reader only)"
        ></a>
        <div>oc-promo-card-v1</div>
      </oc-promo-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-row-v2</h2>
      <oc-row-v2>
        <a
          slot="primary-action"
          href="/foo"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-row-v2 (screen reader only)"
        ></a>
        <div>oc-row-v2</div>
      </oc-row-v2>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(8);
    await expectNoInteractiveElementsInShadowDom(components);
  }
}

Interaction test stories (automated tests, not usage patterns).

Story: tests-interactive--seo-switching-links · tags: play-fn

<h2 class="oc-headline-200">oc-button-v1</h2>
<oc-button-v1 base64-href="L2Zvbw=="
  ><a href="/bar" rel="noreferrer" target="_blank">oc-button-v1</a></oc-button-v1
>

<h2 class="oc-headline-200 oc-mt-100">oc-card-v2</h2>
<oc-card-v2 base64-href="L2Zvbw==">
  <a
    slot="primary-action"
    href="/bar"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-card-v2 (screen reader only)"
  ></a>
  <div>oc-card-v2</div>
</oc-card-v2>

<h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v3</h2>
<oc-icon-button-v3 base64-href="L2Zvbw==" icon="almond">
  <a
    href="/bar"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-icon-button-v3 (screen reader only)"
  ></a>
</oc-icon-button-v3>

<h2 class="oc-headline-200 oc-mt-100">oc-interactive-overlay-v1</h2>
<oc-interactive-overlay-v1 base64-href="L2Zvbw=="
  ><a href="/bar" rel="noreferrer" target="_blank"
    >oc-interactive-overlay-v1</a
  ></oc-interactive-overlay-v1
>

<h2 class="oc-headline-200 oc-mt-100">oc-link-v2</h2>
<oc-link-v2 base64-href="L2Zvbw=="
  ><a href="/bar" rel="noreferrer" target="_blank">oc-link-v2</a></oc-link-v2
>

<h2 class="oc-headline-200">oc-media-object-card-v1</h2>
<oc-media-object-card-v1 variant="ambient-image" base64-href="L2Zvbw==">
  <a
    slot="primary-action"
    href="/bar"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-media-object-card-v1  (screen reader only)"
  ></a>
  <img slot="img" src="${placeholderImage}" alt="blue checkered background" />
  <div>Headline</div>
  <div slot="subline">Subline</div>
</oc-media-object-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
<oc-promo-card-v1 base64-href="L2Zvbw==">
  <a
    slot="primary-action"
    href="/bar"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-promo-card-v1 (screen reader only)"
  ></a>
  <div>oc-promo-card-v1</div>
</oc-promo-card-v1>

<h2 class="oc-headline-200 oc-mt-100">oc-row-v2</h2>
<oc-row-v2 base64-href="L2Zvbw==">
  <a
    slot="primary-action"
    href="/bar"
    rel="noreferrer"
    target="_blank"
    aria-label="oc-row-v2 (screen reader only)"
  ></a>
  <div>oc-row-v2</div>
</oc-row-v2>
Story source (TypeScript, verbatim from Storybook)
{
  render() {
    return html`
      <h2 class="oc-headline-200">oc-button-v1</h2>
      <oc-button-v1 base64-href="L2Zvbw=="
        ><a href="/bar" rel="noreferrer" target="_blank">oc-button-v1</a></oc-button-v1
      >

      <h2 class="oc-headline-200 oc-mt-100">oc-card-v2</h2>
      <oc-card-v2 base64-href="L2Zvbw==">
        <a
          slot="primary-action"
          href="/bar"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-card-v2 (screen reader only)"
        ></a>
        <div>oc-card-v2</div>
      </oc-card-v2>

      <h2 class="oc-headline-200 oc-mt-100">oc-icon-button-v3</h2>
      <oc-icon-button-v3 base64-href="L2Zvbw==" icon="almond">
        <a
          href="/bar"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-icon-button-v3 (screen reader only)"
        ></a>
      </oc-icon-button-v3>

      <h2 class="oc-headline-200 oc-mt-100">oc-interactive-overlay-v1</h2>
      <oc-interactive-overlay-v1 base64-href="L2Zvbw=="
        ><a href="/bar" rel="noreferrer" target="_blank"
          >oc-interactive-overlay-v1</a
        ></oc-interactive-overlay-v1
      >

      <h2 class="oc-headline-200 oc-mt-100">oc-link-v2</h2>
      <oc-link-v2 base64-href="L2Zvbw=="
        ><a href="/bar" rel="noreferrer" target="_blank">oc-link-v2</a></oc-link-v2
      >

      <h2 class="oc-headline-200">oc-media-object-card-v1</h2>
      <oc-media-object-card-v1 variant="ambient-image" base64-href="L2Zvbw==">
        <a
          slot="primary-action"
          href="/bar"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-media-object-card-v1  (screen reader only)"
        ></a>
        <img slot="img" src="${placeholderImage}" alt="blue checkered background" />
        <div>Headline</div>
        <div slot="subline">Subline</div>
      </oc-media-object-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-promo-card-v1</h2>
      <oc-promo-card-v1 base64-href="L2Zvbw==">
        <a
          slot="primary-action"
          href="/bar"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-promo-card-v1 (screen reader only)"
        ></a>
        <div>oc-promo-card-v1</div>
      </oc-promo-card-v1>

      <h2 class="oc-headline-200 oc-mt-100">oc-row-v2</h2>
      <oc-row-v2 base64-href="L2Zvbw==">
        <a
          slot="primary-action"
          href="/bar"
          rel="noreferrer"
          target="_blank"
          aria-label="oc-row-v2 (screen reader only)"
        ></a>
        <div>oc-row-v2</div>
      </oc-row-v2>
    `;
  },
  async play({
    canvasElement
  }) {
    const components = Array.from(canvasElement.querySelectorAll("*")).filter(el => el.tagName.startsWith("OC-")) as HTMLAnchorElement[];
    await expect(components.length).toBe(8);
    await expectNoInteractiveElementsInShadowDom(components);
    await waitFor(function aTagHrefShouldBeBar() {
      return components.flatMap(c => [expect(c.querySelector("a")!.href, `${c.tagName} > a`).toBe(`${window.location.origin}/bar`)]);
    });
    await waitFor(function unmaskAndCheckFocus() {
      return components.map(async c => {
        (c.querySelector("a") as HTMLElement).focus();
        return expect(document.activeElement, `${c.tagName} > a`).toBe(c.querySelector("a"));
      });
    });
    await waitFor(function aTagHrefShouldBeFoo() {
      return components.flatMap(c => [expect(c.querySelector("a")!.href, `${c.tagName} > a`).toBe(`${window.location.origin}/foo`)]);
    });
    await expectNoInteractiveElementsInShadowDom(components);
  }
}