# 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 >> forms >> contact
- Location: visual/mfc-corporate-com.spec.ts:89: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 "Visit the homepage":
        - /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 "Toggle dropdown with space key" [ref=e32] [cursor=pointer]:
                - /url: /content-elements/
                - text: Content elements
            - listitem [ref=e33]:
              - link "Toggle dropdown with space key" [ref=e34] [cursor=pointer]:
                - /url: /richtext-editor/
                - text: RTE
            - listitem [ref=e35]:
              - link "Toggle dropdown with space key" [ref=e36] [cursor=pointer]:
                - /url: /page-types/
                - text: Page types
            - listitem [ref=e37]:
              - link "Toggle dropdown with space key" [ref=e38] [cursor=pointer]:
                - /url: /plugins/
                - text: Plugins
            - listitem [ref=e39]:
              - link "Blog" [ref=e40] [cursor=pointer]:
                - /url: /blog/
            - listitem [ref=e41]:
              - link "Toggle dropdown with space key" [ref=e42] [cursor=pointer]:
                - /url: /forms/
                - text: Forms
        - generic [ref=e44]:
          - button "Show search field" [ref=e46] [cursor=pointer]:
            - img [ref=e48]
          - button "Farbschema wechseln" [ref=e50] [cursor=pointer]:
            - img [ref=e51]
          - list "Website language" [ref=e53]:
            - listitem [ref=e54]:
              - link "Deutsch" [ref=e55] [cursor=pointer]:
                - /url: https://www.marketing-factory.de.styleguide.renovate-develop-pnpm-10-x.review.mfc.gmbh/formulare/kontaktformular/
                - text: De
            - listitem [ref=e56]:
              - link "English" [ref=e57] [cursor=pointer]:
                - /url: /forms/contact-form/
                - text: En
  - navigation "Breadcrumb" [ref=e58]:
    - generic [ref=e59]:
      - paragraph [ref=e60]: "You are here:"
      - list [ref=e61]:
        - listitem [ref=e62]:
          - link "Homepage" [ref=e63] [cursor=pointer]:
            - /url: /
            - img [ref=e64]
        - listitem [ref=e66]:
          - text: /
          - link "Forms" [ref=e67] [cursor=pointer]:
            - /url: /forms/
            - generic [ref=e68]: Forms
        - listitem [ref=e69]:
          - text: /
          - generic [ref=e70]: Contact form
  - main [ref=e71]:
    - generic [ref=e75]:
      - generic [ref=e76]:
        - generic [ref=e77]: Salutation *
        - combobox "Salutation *" [ref=e78]:
          - option "Please select …" [selected]
          - option "Mr"
          - option "Ms"
          - option "Mx"
      - generic [ref=e79]:
        - generic [ref=e80]: Name *
        - textbox "Name *" [ref=e81]
      - generic [ref=e82]:
        - generic [ref=e83]: E-mail *
        - textbox "E-mail *" [ref=e84]
      - textbox [ref=e85]
      - generic [ref=e86]:
        - generic [ref=e87]: Callback number
        - textbox "Callback number" [ref=e88]
      - generic [ref=e89]:
        - generic [ref=e90]: Message
        - textbox "Message" [ref=e91]:
          - /placeholder: ""
      - generic [ref=e92]:
        - generic [ref=e93]:
          - checkbox "I have read the data privacy statement and consent to my personal data being processed according to this statement. *" [ref=e94]
          - generic [ref=e95]:
            - text: I have read the
            - link "data privacy statement" [ref=e96] [cursor=pointer]:
              - /url: https://www.marketing-factory.com.renovate-develop-pnpm-10-x.review.mfc.gmbh/data-privacy-policy/
            - text: and consent to my personal data being processed according to this statement. *
        - generic [ref=e97]:
          - paragraph [ref=e98]: 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=e99]: 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 "Form Navigation" [ref=e101]:
        - button "Submit" [ref=e102] [cursor=pointer]
  - contentinfo [ref=e103]:
    - generic [ref=e105]:
      - navigation [ref=e106]:
        - list [ref=e107]:
          - listitem [ref=e108]:
            - button "Cookie settings" [ref=e109] [cursor=pointer]
          - listitem [ref=e110]:
            - link "Data privacy policy" [ref=e111] [cursor=pointer]:
              - /url: https://www.marketing-factory.com.renovate-develop-pnpm-10-x.review.mfc.gmbh/data-privacy-policy/
          - listitem [ref=e112]:
            - link "Legal notice" [ref=e113] [cursor=pointer]:
              - /url: https://www.marketing-factory.com.renovate-develop-pnpm-10-x.review.mfc.gmbh/legal-notice/
      - generic [ref=e114]:
        - paragraph [ref=e115]: © Marketing Factory Digital GmbH
        - paragraph [ref=e116]:
          - text: Alternative text for images is partially
          - link "generated by AI" [ref=e117] [cursor=pointer]:
            - /url: https://www.marketing-factory.com.renovate-develop-pnpm-10-x.review.mfc.gmbh/technology/typo3/our-typo3-extensions/ai-filemetadata/
```

# 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 | 
```