# 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 >> plugins >> picturecredits
- Location: visual/mfc-corporate-de.spec.ts:240:9

# Error details

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

```
Error: page.goto: Test timeout of 30000ms exceeded.
Call log:
  - navigating to "https://www.marketing-factory.de.styleguide.renovate-develop-lock-file-maintenance.review.mfc.gmbh/plugins/picturecredits/", waiting until "load"

```

# 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]:
          - generic [ref=e45]:
            - button "Suche einblenden" [ref=e46] [cursor=pointer]:
              - img [ref=e48]
            - generic:
              - generic: Suchbegriff
              - textbox "Suchbegriff"
              - button "Suchergebnisse anzeigen"
          - 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: /plugins/picturecredits/
                - text: De
            - listitem [ref=e56]:
              - link "English" [ref=e57] [cursor=pointer]:
                - /url: https://www.marketing-factory.com.styleguide.renovate-develop-lock-file-maintenance.review.mfc.gmbh/plugins/picturecredits/
                - text: En
  - img "Stormtrooper mit Lightning Kabel" [ref=e60]
  - navigation "Brotkrumen-Navigation" [ref=e61]:
    - generic [ref=e62]:
      - paragraph [ref=e63]: "Sie sind here:"
      - list [ref=e64]:
        - listitem [ref=e65]:
          - link "Startseite" [ref=e66] [cursor=pointer]:
            - /url: /
            - img [ref=e67]
        - listitem [ref=e69]:
          - text: /
          - link "Plugins" [ref=e70] [cursor=pointer]:
            - /url: /plugins/
            - generic [ref=e71]: Plugins
        - listitem [ref=e72]:
          - text: /
          - generic [ref=e73]: picturecredits
  - main [ref=e74]
  - contentinfo [ref=e75]:
    - generic [ref=e78]:
      - navigation [ref=e79]:
        - list [ref=e80]:
          - listitem [ref=e81]:
            - button "Cookie-Einstellungen" [ref=e82] [cursor=pointer]
          - listitem [ref=e83]:
            - link "Datenschutzerklärung" [ref=e84] [cursor=pointer]:
              - /url: https://www.marketing-factory.de.renovate-develop-lock-file-maintenance.review.mfc.gmbh/datenschutzerklaerung/
          - listitem [ref=e85]:
            - link "Impressum" [ref=e86] [cursor=pointer]:
              - /url: https://www.marketing-factory.de.renovate-develop-lock-file-maintenance.review.mfc.gmbh/impressum/
      - generic [ref=e87]:
        - paragraph [ref=e88]: © Marketing Factory Digital GmbH
        - paragraph [ref=e89]:
          - text: Alternativtexte für Bilder sind teilweise
          - link "KI-generiert" [ref=e90] [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=e93]:
      - strong [ref=e94]: Bildnachweise
      - list [ref=e95]:
        - listitem [ref=e96]:
          - link "\"Os Stormtroopers são a tropa de base do Império Galáctico no universo Star Wars. Devido à extensão do Império existem variações para diferentes terrenos e situações - nesse caso em lego\"" [ref=e97] [cursor=pointer]:
            - /url: https://unsplash.com/photos/rRTqyHuZHXQ
          - text: ": © danilo.alvesd -"
          - link "Fotolia" [ref=e98] [cursor=pointer]:
            - /url: https://www.fotolia.com
```

# 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");
  94  | }
  95  | 
  96  | export async function setup(page: Page, url: string): Promise<void> {
  97  |     await blockDomains(page);
> 98  |     await page.goto(url, { waitUntil: 'load' });
      |                ^ Error: page.goto: Test timeout of 30000ms exceeded.
  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 | 
```