# Instructions

- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.

# Test info

- Name: mfc-corporate-de.spec.ts >> blog >> post-blogseries
- Location: visual/mfc-corporate-de.spec.ts:291:9

# Error details

```
Test timeout of 30000ms exceeded.
```

```
Error: page.addStyleTag: Test timeout of 30000ms exceeded.
```

# Page snapshot

```yaml
- generic [ref=e2]:
  - link "Zum Inhalt springen" [ref=e3] [cursor=pointer]:
    - /url: "#page-content"
  - banner [ref=e4]:
    - generic [ref=e5]:
      - link "Marketing Factory Digital GmbH" [ref=e6] [cursor=pointer]:
        - /url: /
        - img "Marketing Factory Digital GmbH" [ref=e7]
      - link "Kontaktformular" [ref=e26] [cursor=pointer]:
        - /url: /formulare/kontaktformular/
      - button "Navigation" [expanded] [ref=e27] [cursor=pointer]
  - navigation "Brotkrumen-Navigation" [ref=e29]:
    - generic [ref=e30]:
      - paragraph [ref=e31]: "Sie sind here:"
      - list [ref=e32]:
        - listitem [ref=e33]:
          - link "Startseite" [ref=e34] [cursor=pointer]:
            - /url: /
            - img [ref=e35]
        - listitem [ref=e37]:
          - text: /
          - link "Blog" [ref=e38] [cursor=pointer]:
            - /url: /blog/
            - generic [ref=e39]: Blog
        - listitem [ref=e40]:
          - text: /
          - generic [ref=e41]: Erster Teil einer Serie
  - main [ref=e42]:
    - article [ref=e43]:
      - generic [ref=e45]:
        - generic [ref=e46]:
          - generic [ref=e47]:
            - figure [ref=e48]
            - list [ref=e53]:
              - listitem [ref=e54]:
                - link "Schokolade" [ref=e55] [cursor=pointer]:
                  - /url: /blog/kategorien/schokolade/
              - listitem [ref=e56]:
                - link "Kekse" [ref=e57] [cursor=pointer]:
                  - /url: /blog/kategorien/kekse/
            - time [ref=e61]: 15.02.2023
            - heading "Erster Teil einer Serie" [level=1] [ref=e62]
          - separator [ref=e63]
          - paragraph [ref=e67]: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
        - complementary [ref=e68]:
          - generic [ref=e70]:
            - generic [ref=e72]: Max Powers
            - paragraph [ref=e74]: Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
            - paragraph [ref=e75]:
              - link "Weitere Beiträge dieses Autors" [ref=e76] [cursor=pointer]:
                - /url: /blog/autoren/max-powers/
            - list [ref=e78]:
              - listitem [ref=e79]:
                - link "Der Autor auf Twitter" [ref=e80] [cursor=pointer]:
                  - /url: https://twitter.com/maxpowers1
                  - img [ref=e81]
          - link "Blog als RSS-Feed abonnieren" [ref=e83] [cursor=pointer]:
            - /url: /blog/blog.recent.xml
            - img [ref=e84]
            - text: Blog als RSS-Feed abonnieren
          - paragraph [ref=e86]: Alle Teile der Blogreihe
          - list [ref=e87]:
            - listitem [ref=e88]:
              - link "Erster Teil einer Serie" [ref=e89] [cursor=pointer]:
                - /url: /blog/erster-teil-einer-serie/
            - listitem [ref=e90]:
              - link "Zweiter Teil einer Serie" [ref=e91] [cursor=pointer]:
                - /url: /blog/zweiter-teil-einer-serie/
            - listitem [ref=e92]:
              - link "Dritter Teil einer Serie" [ref=e93] [cursor=pointer]:
                - /url: /blog/dritter-teil-einer-serie/
      - generic [ref=e94]:
        - paragraph [ref=e95]: Wir freuen uns, wenn Ihr diesen Beitrag teilt.
        - list [ref=e96]:
          - listitem [ref=e97]:
            - button "teilen" [ref=e98] [cursor=pointer]:
              - img [ref=e99]
              - generic [ref=e101]: teilen
          - listitem [ref=e102]:
            - button "teilen" [ref=e103] [cursor=pointer]:
              - img [ref=e104]
              - generic [ref=e106]: teilen
          - listitem [ref=e107]:
            - button "teilen" [ref=e108] [cursor=pointer]:
              - img [ref=e109]
              - generic [ref=e111]: teilen
  - generic [ref=e113]:
    - separator [ref=e114]
    - paragraph [ref=e115]: Kommentare
    - paragraph [ref=e116]: Keine Kommentare gefunden.
  - contentinfo [ref=e117]:
    - generic [ref=e121]:
      - paragraph [ref=e123]: Kommentar verfassen.
      - generic [ref=e125]:
        - generic [ref=e126]:
          - generic [ref=e127]: Name *
          - textbox "Name *" [ref=e128]
        - generic [ref=e129]:
          - generic [ref=e130]: E-Mail *
          - textbox "E-Mail *" [ref=e131]
        - generic [ref=e132]:
          - generic [ref=e133]: Kommentar *
          - textbox "Kommentar *" [ref=e134]
        - generic [ref=e135]:
          - generic [ref=e136]:
            - checkbox "Ich habe die Datenschutzerklärung gelesen und willige in die Verarbeitung meiner personenbezogenen Daten entsprechend dieser Erklärung ein. *" [ref=e137]
            - generic [ref=e138]:
              - text: Ich habe die
              - link "Datenschutzerklärung" [ref=e139] [cursor=pointer]:
                - /url: https://www.marketing-factory.de.renovate-develop-pnpm-10-x.review.mfc.gmbh/datenschutzerklaerung/
              - text: gelesen und willige in die Verarbeitung meiner personenbezogenen Daten entsprechend dieser Erklärung ein. *
          - generic [ref=e140]:
            - paragraph [ref=e141]: Ich bin darauf hingewiesen worden, dass die Verarbeitung meiner Daten auf freiwilliger Basis erfolgt und dass ich mein Einverständnis ohne für mich nachteilige Folgen verweigern bzw. jederzeit gegenüber der Marketing Factory Digital GmbH per Post (Erkrather Straße 401, D-40231 Düsseldorf) oder E-Mail (info@marketing-factory.de) widerrufen kann.
            - paragraph [ref=e142]: Mir ist bekannt, dass die oben angegebenen Daten so lange gespeichert werden, wie ich die Kontaktaufnahme durch Marketing Factory wünsche. Nach meinem Widerruf werden meine Daten gelöscht. Eine weitergehende Speicherung kann im Einzelfall erfolgen, wenn dies gesetzlich vorgeschrieben ist.
        - navigation [ref=e143]:
          - button "Absenden" [ref=e144] [cursor=pointer]
    - generic [ref=e146]:
      - navigation [ref=e147]:
        - list [ref=e148]:
          - listitem [ref=e149]:
            - button "Cookie-Einstellungen" [ref=e150] [cursor=pointer]
          - listitem [ref=e151]:
            - link "Datenschutzerklärung" [ref=e152] [cursor=pointer]:
              - /url: https://www.marketing-factory.de.renovate-develop-pnpm-10-x.review.mfc.gmbh/datenschutzerklaerung/
          - listitem [ref=e153]:
            - link "Impressum" [ref=e154] [cursor=pointer]:
              - /url: https://www.marketing-factory.de.renovate-develop-pnpm-10-x.review.mfc.gmbh/impressum/
      - generic [ref=e155]:
        - paragraph [ref=e156]: © Marketing Factory Digital GmbH
        - paragraph [ref=e157]:
          - text: Alternativtexte für Bilder sind teilweise
          - link "KI-generiert" [ref=e158] [cursor=pointer]:
            - /url: https://www.marketing-factory.de.renovate-develop-pnpm-10-x.review.mfc.gmbh/technologie/typo3/unsere-typo3-extensions/ai-filemetadata/
    - generic [ref=e161]:
      - strong [ref=e162]: Bildnachweise
      - list
```

# Test source

```ts
  1   | import { Page } from '@playwright/test';
  2   | 
  3   | const BLOCKED_DOMAINS = [
  4   |     'google-analytics.com',
  5   |     'googletagmanager.com',
  6   |     'analytics.google.com',
  7   |     'stats.g.doubleclick.net',
  8   |     'googleads.g.doubleclick.net',
  9   |     'sentry.marketing-factory.de',
  10  | ];
  11  | 
  12  | const BLOCKED_PATH_PARTS = [
  13  |     '/sentry-init',
  14  |     '/ga4Integration'
  15  | ];
  16  | 
  17  | export async function blockDomains(page: Page, domains: string[] = BLOCKED_DOMAINS, pathParts: string[] = BLOCKED_PATH_PARTS): Promise<void> {
  18  |     await page.route('**', route => {
  19  |         const url = new URL(route.request().url());
  20  |         const hostname = url.hostname;
  21  |         const path = url.pathname;
  22  | 
  23  |         if (pathParts.some(p => path.includes(p))) {
  24  |             route.abort();
  25  |             return;
  26  |         }
  27  | 
  28  |         if (domains.some(d => hostname === d || hostname.endsWith(`.${d}`))) {
  29  |             route.abort();
  30  |         } else {
  31  |             route.continue();
  32  |         }
  33  |     });
  34  | }
  35  | 
  36  | const CSS_OVERRIDES = [
  37  |     '.c-header-content { transition: unset; }',
  38  |     '.c-header-search__form { display: none; transition: unset; }',
  39  |     '.c-captcha__img { filter: brightness(0); }',
  40  |     '.o-radio-switch__label:first-of-type::before, .c-comparison__slide-bg, .c-comparison__content { transition: unset; }',
  41  |     '.c-btn { transition: unset !important; }',
  42  |     '.c-header-content { transition: none !important; }',
  43  |     '.c-comparison__switch { position: initial !important; }',
  44  |     'figure.video { visibility: hidden !important; }',
  45  |     '::file-selector-button { transition: none !important; }',
  46  | ].join(' ');
  47  | 
  48  | export async function overrideCSS(page: Page): Promise<void> {
> 49  |     await page.addStyleTag({ content: CSS_OVERRIDES });
      |                ^ Error: page.addStyleTag: Test timeout of 30000ms exceeded.
  50  | }
  51  | 
  52  | export async function loadLazyImages(page: Page): Promise<void> {
  53  |     await page.evaluate(() => {
  54  |         document.querySelectorAll<HTMLImageElement>('[loading="lazy"]').forEach(el => { el.loading = 'eager'; });
  55  |         document.querySelectorAll<HTMLImageElement>('[decoding="async"]').forEach(el => { el.decoding = 'sync'; });
  56  |     });
  57  | }
  58  | 
  59  | export async function waitForFontsAndImages(page: Page): Promise<void> {
  60  |     await page.evaluate(async () => {
  61  |         await document.fonts.ready;
  62  |         await Promise.all(Array.from(document.images).map(img => img.decode().catch(() => undefined)));
  63  |     });
  64  | }
  65  | 
  66  | export async function scrollToBottom(page: Page): Promise<void> {
  67  |     await page.evaluate(async () => {
  68  |         await new Promise<void>(resolve => {
  69  |             let totalHeight = 0;
  70  |             const distance = 500;
  71  |             const timer = setInterval(() => {
  72  |                 const scrollHeight = document.body.scrollHeight;
  73  |                 window.scrollBy(0, distance);
  74  |                 totalHeight += distance;
  75  |                 if (totalHeight >= scrollHeight) {
  76  |                     clearInterval(timer);
  77  |                     resolve();
  78  |                 }
  79  |             }, 100);
  80  |         });
  81  |     });
  82  |     await page.waitForLoadState('networkidle');
  83  |     await waitForFontsAndImages(page);
  84  | }
  85  | 
  86  | export async function setDarkTheme(page: Page): Promise<void> {
  87  |     await page.evaluate(() => {
  88  |         document.documentElement.dataset.colorMode = 'dark';
  89  |     });
  90  | }
  91  | 
  92  | export async function waitUntilLoaded(page: Page): Promise<void> {
  93  |     await page.waitForLoadState("networkidle");
  94  | }
  95  | 
  96  | export async function setup(page: Page, url: string): Promise<void> {
  97  |     await blockDomains(page);
  98  |     await page.goto(url, { waitUntil: 'load' });
  99  |     await overrideCSS(page);
  100 |     await loadLazyImages(page);
  101 |     await waitForFontsAndImages(page);
  102 |     await waitUntilLoaded(page);
  103 | }
  104 | 
  105 | export async function hideElements(page: Page, selectors: string[]): Promise<void> {
  106 |     if (selectors.length === 0) return;
  107 |     const css = selectors.map(s => `${s} { display: none !important; }`).join(' ');
  108 |     await page.addStyleTag({ content: css });
  109 | }
  110 | 
```