# 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-com.spec.ts >> blog >> post-with-comments
- Location: visual/mfc-corporate-com.spec.ts:77:9

# Error details

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

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

# Page snapshot

```yaml
- generic [ref=e2]:
  - link "Skip to content" [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 "Contact form" [ref=e26] [cursor=pointer]:
        - /url: /forms/contact-form/
      - button "Navigation" [expanded] [ref=e27] [cursor=pointer]
  - navigation "Breadcrumb" [ref=e29]:
    - generic [ref=e30]:
      - paragraph [ref=e31]: "You are here:"
      - list [ref=e32]:
        - listitem [ref=e33]:
          - link "Homepage" [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]: With comments
  - 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 "Ice cream" [ref=e55] [cursor=pointer]:
                  - /url: /blog/categories/ice-cream/
              - listitem [ref=e56]:
                - link "Chocolate" [ref=e57] [cursor=pointer]:
                  - /url: /blog/categories/chocolate/
              - listitem [ref=e58]:
                - link "Cookies" [ref=e59] [cursor=pointer]:
                  - /url: /blog/categories/cookies/
            - time [ref=e63]: 15.06.2022
            - heading "With comments" [level=1] [ref=e64]
          - separator [ref=e65]
        - complementary [ref=e66]:
          - generic [ref=e68]:
            - generic [ref=e70]: John Doe
            - paragraph [ref=e71]:
              - link "More posts by this author" [ref=e72] [cursor=pointer]:
                - /url: /blog/authors/john-doe/
            - list [ref=e74]:
              - listitem [ref=e75]:
                - link "The author's website" [ref=e76] [cursor=pointer]:
                  - /url: https://www.example.org
                  - img [ref=e77]
              - listitem [ref=e79]:
                - link "The author on Twitter" [ref=e80] [cursor=pointer]:
                  - /url: https://twitter.com/realjohndoe
                  - img [ref=e81]
          - link "Get blog posts as RSS feed" [ref=e83] [cursor=pointer]:
            - /url: /blog/blog.recent.xml
            - img [ref=e84]
            - text: Get blog posts as RSS feed
          - paragraph [ref=e86]: All parts of this blog series
          - list [ref=e87]:
            - listitem [ref=e88]:
              - link "With comments" [ref=e89] [cursor=pointer]:
                - /url: /blog/with-comments/
            - listitem [ref=e90]:
              - link "Localized Featured Image" [ref=e91] [cursor=pointer]:
                - /url: /blog/localized-featured-image/
      - generic [ref=e92]:
        - paragraph [ref=e93]: Please feel free to share this article.
        - list [ref=e94]:
          - listitem [ref=e95]:
            - button "share" [ref=e96] [cursor=pointer]:
              - img [ref=e97]
              - generic [ref=e99]: share
          - listitem [ref=e100]:
            - button "share" [ref=e101] [cursor=pointer]:
              - img [ref=e102]
              - generic [ref=e104]: share
          - listitem [ref=e105]:
            - button "share" [ref=e106] [cursor=pointer]:
              - img [ref=e107]
              - generic [ref=e109]: share
  - generic [ref=e111]:
    - separator [ref=e112]
    - paragraph [ref=e113]: Comments
    - paragraph [ref=e114]: No comments yet.
  - contentinfo [ref=e115]:
    - generic [ref=e119]:
      - paragraph [ref=e121]: Write a comment.
      - generic [ref=e123]:
        - generic [ref=e124]:
          - generic [ref=e125]: Name *
          - textbox "Name *" [ref=e126]
        - generic [ref=e127]:
          - generic [ref=e128]: E-mail *
          - textbox "E-mail *" [ref=e129]
        - generic [ref=e130]:
          - generic [ref=e131]: Comment *
          - textbox "Comment *" [ref=e132]
        - generic [ref=e133]:
          - generic [ref=e134]:
            - checkbox "I have read the data privacy statement and consent to my personal data being processed according to this statement. *" [ref=e135]
            - generic [ref=e136]:
              - text: I have read the
              - link "data privacy statement" [ref=e137] [cursor=pointer]:
                - /url: https://www.marketing-factory.com.renovate-develop-lock-file-maintenance.review.mfc.gmbh/data-privacy-policy/
              - text: and consent to my personal data being processed according to this statement. *
          - generic [ref=e138]:
            - paragraph [ref=e139]: I have been informed that the processing of my data is on a voluntary basis and that I can refuse my consent without detrimental consequences for me or withdraw my consent at any time to Marketing Factory Digital GmbH by mail (Erkrather Straße 401, D-40231 Düsseldorf) or e-mail (info@marketing-factory.de).
            - paragraph [ref=e140]: I understand that the above data will be stored for as long as I wish to be contacted by Marketing Factory. After my revocation my data will be deleted. Further storage may take place in individual cases if this is required by law.
        - navigation [ref=e141]:
          - button "Submit" [ref=e142] [cursor=pointer]
    - generic [ref=e144]:
      - navigation [ref=e145]:
        - list [ref=e146]:
          - listitem [ref=e147]:
            - button "Cookie settings" [ref=e148] [cursor=pointer]
          - listitem [ref=e149]:
            - link "Data privacy policy" [ref=e150] [cursor=pointer]:
              - /url: https://www.marketing-factory.com.renovate-develop-lock-file-maintenance.review.mfc.gmbh/data-privacy-policy/
          - listitem [ref=e151]:
            - link "Legal notice" [ref=e152] [cursor=pointer]:
              - /url: https://www.marketing-factory.com.renovate-develop-lock-file-maintenance.review.mfc.gmbh/legal-notice/
      - generic [ref=e153]:
        - paragraph [ref=e154]: © Marketing Factory Digital GmbH
        - paragraph [ref=e155]:
          - text: Alternative text for images is partially
          - link "generated by AI" [ref=e156] [cursor=pointer]:
            - /url: https://www.marketing-factory.com.renovate-develop-lock-file-maintenance.review.mfc.gmbh/technology/typo3/our-typo3-extensions/ai-filemetadata/
    - generic [ref=e159]:
      - strong [ref=e160]: Picture Credits
      - 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 | 
```