# 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-inactive-author
- Location: visual/mfc-corporate-de.spec.ts:306:9

# Error details

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

```
Error: page.waitForLoadState: 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 "Zur Startseite":
        - /url: /
      - generic [ref=e6]:
        - link "Logo Marketing Factory Digital GmbH" [ref=e7] [cursor=pointer]:
          - /url: /
          - img "Logo Marketing Factory Digital GmbH" [ref=e8]
        - navigation [ref=e27]:
          - list [ref=e28]:
            - listitem [ref=e29]:
              - link "Basics" [ref=e30] [cursor=pointer]:
                - /url: /design-basiseigenschaften/
            - listitem [ref=e31]:
              - link "Einblendmenü umschalten mit Leertaste" [ref=e32] [cursor=pointer]:
                - /url: /inhaltselemente/
                - text: Inhaltselemente
            - listitem [ref=e33]:
              - link "Einblendmenü umschalten mit Leertaste" [ref=e34] [cursor=pointer]:
                - /url: /richtext-editor/
                - text: RTE
            - listitem [ref=e35]:
              - link "Einblendmenü umschalten mit Leertaste" [ref=e36] [cursor=pointer]:
                - /url: /seitentypen/
                - text: Seitentypen
            - listitem [ref=e37]:
              - link "Einblendmenü umschalten mit Leertaste" [ref=e38] [cursor=pointer]:
                - /url: /plugins/
                - text: Plugins
            - listitem [ref=e39]:
              - link "Blog" [ref=e40] [cursor=pointer]:
                - /url: /blog/
            - listitem [ref=e41]:
              - link "Einblendmenü umschalten mit Leertaste" [ref=e42] [cursor=pointer]:
                - /url: /formulare/
                - text: Formulare
        - generic [ref=e44]:
          - button "Suche einblenden" [ref=e46] [cursor=pointer]:
            - img [ref=e48]
          - button "Farbschema wechseln" [ref=e50] [cursor=pointer]:
            - img [ref=e51]
          - list "Sprache der Website" [ref=e53]:
            - listitem [ref=e54]:
              - link "Deutsch" [ref=e55] [cursor=pointer]:
                - /url: /blog/drei-autoren-einer-inaktiv/
                - text: De
            - listitem [ref=e56]:
              - generic "An english version of this page is not available." [ref=e57]: En
  - navigation "Brotkrumen-Navigation" [ref=e58]:
    - generic [ref=e59]:
      - paragraph [ref=e60]: "Sie sind here:"
      - list [ref=e61]:
        - listitem [ref=e62]:
          - link "Startseite" [ref=e63] [cursor=pointer]:
            - /url: /
            - img [ref=e64]
        - listitem [ref=e66]:
          - text: /
          - link "Blog" [ref=e67] [cursor=pointer]:
            - /url: /blog/
            - generic [ref=e68]: Blog
        - listitem [ref=e69]:
          - text: /
          - generic [ref=e70]: Drei Autoren, einer inaktiv
  - main [ref=e71]:
    - article [ref=e72]:
      - generic [ref=e74]:
        - generic [ref=e75]:
          - generic [ref=e76]:
            - figure [ref=e77]
            - list [ref=e82]:
              - listitem [ref=e83]:
                - link "Kekse" [ref=e84] [cursor=pointer]:
                  - /url: /blog/kategorien/kekse/
              - listitem [ref=e85]:
                - link "Schokolade" [ref=e86] [cursor=pointer]:
                  - /url: /blog/kategorien/schokolade/
            - time [ref=e90]: 09.03.2022
            - heading "Drei Autoren, einer inaktiv" [level=1] [ref=e91]
          - separator [ref=e92]
          - paragraph [ref=e96]: 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=e97]:
          - generic [ref=e98]:
            - paragraph [ref=e99]: Autoren
            - generic [ref=e100]:
              - group [ref=e101]:
                - generic "Michael Mustermann" [ref=e102] [cursor=pointer]:
                  - img [ref=e103]
                  - generic [ref=e105]: Michael Mustermann
                  - img [ref=e106]
              - group [ref=e108]:
                - generic "Max Powers" [ref=e109] [cursor=pointer]:
                  - generic [ref=e110]: Max Powers
                  - img [ref=e111]
              - generic [ref=e115]: Ehemaliger Kollege
          - link "Blog als RSS-Feed abonnieren" [ref=e116] [cursor=pointer]:
            - /url: /blog/blog.recent.xml
            - img [ref=e117]
            - text: Blog als RSS-Feed abonnieren
          - generic [ref=e120]:
            - paragraph [ref=e121]: Verwandte Beiträge
            - list [ref=e122]:
              - listitem [ref=e123]:
                - link "Dritter Teil einer Serie" [ref=e124] [cursor=pointer]:
                  - /url: /blog/dritter-teil-einer-serie/
              - listitem [ref=e125]:
                - link "Zweiter Teil einer Serie" [ref=e126] [cursor=pointer]:
                  - /url: /blog/zweiter-teil-einer-serie/
              - listitem [ref=e127]:
                - link "Erster Teil einer Serie" [ref=e128] [cursor=pointer]:
                  - /url: /blog/erster-teil-einer-serie/
              - listitem [ref=e129]:
                - link "Mit Kommentaren" [ref=e130] [cursor=pointer]:
                  - /url: /blog/mit-kommentaren/
      - generic [ref=e131]:
        - paragraph [ref=e132]: Wir freuen uns, wenn Ihr diesen Beitrag teilt.
        - list [ref=e133]:
          - listitem [ref=e134]:
            - button "teilen" [ref=e135] [cursor=pointer]:
              - img [ref=e136]
              - generic [ref=e138]: teilen
          - listitem [ref=e139]:
            - button "teilen" [ref=e140] [cursor=pointer]:
              - img [ref=e141]
              - generic [ref=e143]: teilen
          - listitem [ref=e144]:
            - button "teilen" [ref=e145] [cursor=pointer]:
              - img [ref=e146]
              - generic [ref=e148]: teilen
  - generic [ref=e150]:
    - separator [ref=e151]
    - paragraph [ref=e152]: Kommentare
    - paragraph [ref=e153]: Keine Kommentare gefunden.
  - contentinfo [ref=e154]:
    - generic [ref=e158]:
      - paragraph [ref=e160]: Kommentar verfassen.
      - generic [ref=e162]:
        - generic [ref=e163]:
          - generic [ref=e164]: Name *
          - textbox "Name *" [ref=e165]
        - generic [ref=e166]:
          - generic [ref=e167]: E-Mail *
          - textbox "E-Mail *" [ref=e168]
        - generic [ref=e169]:
          - generic [ref=e170]: Kommentar *
          - textbox "Kommentar *" [ref=e171]
        - generic [ref=e172]:
          - generic [ref=e173]:
            - checkbox "Ich habe die Datenschutzerklärung gelesen und willige in die Verarbeitung meiner personenbezogenen Daten entsprechend dieser Erklärung ein. *" [ref=e174]
            - generic [ref=e175]:
              - text: Ich habe die
              - link "Datenschutzerklärung" [ref=e176] [cursor=pointer]:
                - /url: https://www.marketing-factory.de.renovate-develop-lock-file-maintenance.review.mfc.gmbh/datenschutzerklaerung/
              - text: gelesen und willige in die Verarbeitung meiner personenbezogenen Daten entsprechend dieser Erklärung ein. *
          - generic [ref=e177]:
            - paragraph [ref=e178]: 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=e179]: 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=e180]:
          - button "Absenden" [ref=e181] [cursor=pointer]
    - generic [ref=e183]:
      - navigation [ref=e184]:
        - list [ref=e185]:
          - listitem [ref=e186]:
            - button "Cookie-Einstellungen" [ref=e187] [cursor=pointer]
          - listitem [ref=e188]:
            - link "Datenschutzerklärung" [ref=e189] [cursor=pointer]:
              - /url: https://www.marketing-factory.de.renovate-develop-lock-file-maintenance.review.mfc.gmbh/datenschutzerklaerung/
          - listitem [ref=e190]:
            - link "Impressum" [ref=e191] [cursor=pointer]:
              - /url: https://www.marketing-factory.de.renovate-develop-lock-file-maintenance.review.mfc.gmbh/impressum/
      - generic [ref=e192]:
        - paragraph [ref=e193]: © Marketing Factory Digital GmbH
        - paragraph [ref=e194]:
          - text: Alternativtexte für Bilder sind teilweise
          - link "KI-generiert" [ref=e195] [cursor=pointer]:
            - /url: https://www.marketing-factory.de.renovate-develop-lock-file-maintenance.review.mfc.gmbh/technologie/typo3/unsere-typo3-extensions/ai-filemetadata/
    - generic [ref=e198]:
      - strong [ref=e199]: 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 });
  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");
      |                ^ Error: page.waitForLoadState: Test timeout of 30000ms exceeded.
  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 | 
```