# 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: plugins/international-press-zone/tests/e2e/journeys/UJ-021-generate-all-content.spec.js >> UJ-021 Generate All admits one manifest, replays an ambiguous request, and restores multiple manifests after navigation
- Location: plugins/international-press-zone/tests/e2e/journeys/UJ-021-generate-all-content.spec.js:163:1

# Error details

```
Error: page.goto: Protocol error (Page.navigate): Cannot navigate to invalid URL
Call log:
  - navigating to "/wp-admin/admin.php?page=international-press-zone&ipz_e2e=1787599563844#/translations/posts", waiting until "domcontentloaded"

```

# Test source

```ts
  68  |     }
  69  | 
  70  |     const url = path.startsWith('/wp-json/') ? path : `${API_ROOT}${path}`;
  71  | 
  72  |     // The plugin rate-limits an authenticated admin to a few hundred requests per
  73  |     // rolling minute, and every browser project shares that one bucket. A journey
  74  |     // that builds a large fixture can therefore be throttled by traffic the
  75  |     // PREVIOUS journey generated -- a property of the suite's pacing, not of the
  76  |     // behaviour under test. Waiting the window out and retrying keeps that
  77  |     // scheduling artefact from being reported as a product failure. No journey
  78  |     // asserts on a 429, so nothing here is masking an expected one.
  79  |     let response = await page.request.fetch(url, options);
  80  |     for (let attempt = 0; attempt < 2 && response.status() === 429; attempt += 1) {
  81  |         const retryAfter = Number.parseInt(response.headers()['retry-after'] ?? '', 10);
  82  |         const waitSeconds = Number.isFinite(retryAfter) ? Math.min(retryAfter + 1, 61) : 61;
  83  |         await page.waitForTimeout(waitSeconds * 1000);
  84  |         response = await page.request.fetch(url, options);
  85  |     }
  86  | 
  87  |     return response;
  88  | }
  89  | 
  90  | async function wpRequest(page, nonce, method, path, data) {
  91  |     return request(page, nonce, method, path.startsWith(WP_API_ROOT) ? path : `${WP_API_ROOT}${path}`, data);
  92  | }
  93  | 
  94  | async function json(response) {
  95  |     const text = await response.text();
  96  |     if (!text) {
  97  |         return null;
  98  |     }
  99  | 
  100 |     try {
  101 |         return JSON.parse(text);
  102 |     } catch (error) {
  103 |         throw new Error(`Expected JSON response from ${response.url()}: ${error.message}`);
  104 |     }
  105 | }
  106 | 
  107 | async function getAcfFields(page, nonce) {
  108 |     const response = await request(page, nonce, 'GET', '/translations/fields');
  109 |     const text = await response.text();
  110 |     let body;
  111 | 
  112 |     try {
  113 |         body = JSON.parse(text);
  114 |     } catch (error) {
  115 |         throw new Error(`Malformed ACF fields response: status ${response.status()}, body ${text}`);
  116 |     }
  117 |     if (!response.ok()) {
  118 |         throw new Error(`ACF fields request failed: status ${response.status()}, body ${text}`);
  119 |     }
  120 |     if (!body || typeof body !== 'object' || typeof body.available !== 'boolean' || !Array.isArray(body.groups)) {
  121 |         throw new Error(`Malformed ACF fields response: status ${response.status()}, body ${text}`);
  122 |     }
  123 | 
  124 |     return body;
  125 | }
  126 | 
  127 | async function waitForAdminApp(page) {
  128 |     await page.waitForFunction(adminAppIsReady, undefined, { timeout: 30_000 });
  129 |     const root = page.locator('#ipz-admin-root');
  130 |     await expect(root).toBeVisible();
  131 |     await expect(root.locator('[data-ipz-app-frame="true"]')).toBeVisible();
  132 |     await expect(root.locator('#presszone-international-content > div[id]').first()).toBeVisible();
  133 | }
  134 | 
  135 | /**
  136 |  * Drops the admin app's persisted SWR caches.
  137 |  *
  138 |  * `AdminDataPolicy` gives `languages:list` (and its siblings) `browserTtlMs: 300000`
  139 |  * with `persist: true`, and `DataStore` keeps those entries in `localStorage` under the
  140 |  * `ipz_swr_` prefix. The store is only invalidated when the *app* performs a declared
  141 |  * mutation, so a fixture created over REST from outside the page is invisible to the
  142 |  * rendered list for a full five minutes -- across reloads and fresh navigations alike.
  143 |  * Journeys that seed state over REST and then assert on the UI must drop the cache
  144 |  * first; the page must already be on the admin origin for this to reach the right
  145 |  * storage.
  146 |  *
  147 |  * @param {import('@playwright/test').Page} page Page to clear.
  148 |  */
  149 | async function clearAdminDataCache(page) {
  150 |     await page.evaluate(() => {
  151 |         try {
  152 |             const doomed = [];
  153 |             for (let index = 0; index < window.localStorage.length; index += 1) {
  154 |                 const key = window.localStorage.key(index);
  155 |                 if (key && key.startsWith('ipz_swr_')) {
  156 |                     doomed.push(key);
  157 |                 }
  158 |             }
  159 |             doomed.forEach((key) => window.localStorage.removeItem(key));
  160 |         } catch (error) {
  161 |             // Storage unavailable means nothing was persisted to begin with.
  162 |         }
  163 |     });
  164 | }
  165 | 
  166 | async function openAdmin(page, hash) {
  167 |     const normalizedHash = hash.replace(/^#\/?/, '').replace(/^\//, '');
> 168 |     const response = await page.goto(`/wp-admin/admin.php?page=international-press-zone&ipz_e2e=${Date.now()}#/${normalizedHash}`, {
      |                                 ^ Error: page.goto: Protocol error (Page.navigate): Cannot navigate to invalid URL
  169 |         waitUntil: 'domcontentloaded',
  170 |     });
  171 |     if (!response?.ok()) {
  172 |         throw new Error(`Admin page request failed with status ${response?.status() ?? 'unknown'}`);
  173 |     }
  174 |     await waitForAdminApp(page);
  175 | }
  176 | 
  177 | async function filterContent(page, query) {
  178 |     const input = page.locator('#ipz-ct-search-post');
  179 |     await expect(input).toBeVisible();
  180 |     const responsePromise = page.waitForResponse((response) => response.request().method() === 'GET'
  181 |         && response.url().includes(`${API_ROOT}/translations/content`)
  182 |         && new URL(response.url()).searchParams.get('search') === query);
  183 |     await input.fill(query);
  184 |     await responsePromise;
  185 | }
  186 | 
  187 | const ABORTED_REQUEST_ERRORS = [/NS_BINDING_ABORTED/, /net::ERR_ABORTED/];
  188 | 
  189 | // Firefox's generic performance advisory for any position:sticky (or other scroll-linked)
  190 | // element on the page. Not app-caused, not actionable: the plugin's sticky modal headers
  191 | // are correct, standard CSS.
  192 | // Firefox's OTS font-sanitizer diagnostics ("downloadable font: glyf: Glyph bbox was
  193 | // incorrect...") fire once per glyph run for the bundled twentytwentyfour theme's Cardo
  194 | // webfont. Browser-owned noise about a third-party theme asset, not product JS — scoped
  195 | // to fonts served from wp-content/themes/ so a broken font shipped by THIS plugin
  196 | // (wp-content/plugins/...) would still fail the run.
  197 | const ENGINE_ADVISORIES = [
  198 |     /This site appears to use a scroll-linked positioning effect/,
  199 |     /downloadable font: [^\n]* source: \S*\/wp-content\/themes\//,
  200 | ];
  201 | 
  202 | function isAbortedRequest(failure) {
  203 |     return failure.type === 'request'
  204 |         && ABORTED_REQUEST_ERRORS.some((aborted) => aborted.test(failure.error));
  205 | }
  206 | 
  207 | function runtimeFailures(page) {
  208 |     const failures = [];
  209 | 
  210 |     page.on('pageerror', (error) => failures.push(`pageerror: ${error.message}`));
  211 |     page.on('console', (message) => {
  212 |         if ((message.type() === 'error' || message.type() === 'warning')
  213 |             && !ENGINE_ADVISORIES.some((advisory) => advisory.test(message.text()))) {
  214 |             failures.push({
  215 |                 type: 'console',
  216 |                 messageType: message.type(),
  217 |                 text: message.text(),
  218 |                 url: message.location().url,
  219 |             });
  220 |         }
  221 |     });
  222 |     page.on('requestfailed', (requestValue) => {
  223 |         const url = requestValue.url();
  224 |         if (url.includes('/wp-json/') || url.includes('admin.php?page=international-press-zone')) {
  225 |             failures.push({
  226 |                 type: 'request',
  227 |                 method: requestValue.method(),
  228 |                 url,
  229 |                 error: requestValue.failure()?.errorText || '',
  230 |             });
  231 |         }
  232 |     });
  233 | 
  234 |     return failures;
  235 | }
  236 | 
  237 | async function assertNoRuntimeFailures(page, failures, expectedResponses = [], expectedAbortedUrls = []) {
  238 |     await page.evaluate(() => new Promise((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve))));
  239 |     const expectedHttpFailures = await Promise.all(expectedResponses.map(async (response) => ({
  240 |         url: response.url(),
  241 |         status: response.status(),
  242 |     })));
  243 |     const unexpected = failures.filter((failure) => !expectedHttpFailures.some(({ url, status }) => failure.type === 'console'
  244 |         && failure.url === url
  245 |         && new RegExp(`\\b${status}\\b`).test(failure.text))
  246 |         && !(isAbortedRequest(failure) && expectedAbortedUrls.some((url) => failure.url.includes(url))));
  247 |     expect(unexpected, 'journey-owned runtime failures').toEqual([]);
  248 | }
  249 | 
  250 | async function captureResponse(page, predicate, action) {
  251 |     const responsePromise = page.waitForResponse(predicate);
  252 |     await action();
  253 |     const response = await responsePromise;
  254 |     return { response, body: await json(response) };
  255 | }
  256 | 
  257 | async function createLanguage(page, nonce, testInfo, overrides = {}) {
  258 |     const token = uniqueToken(testInfo, 'e2e');
  259 |     for (let attempt = 0; attempt < 12; attempt += 1) {
  260 |         const code = alphaToken(testInfo, 2, `${token}:${attempt}`);
  261 |         const locale = `${code}_${alphaToken(testInfo, 2, `${token}:locale:${attempt}`).toUpperCase()}`;
  262 |         const payload = {
  263 |             code,
  264 |             locale,
  265 |             name: `E2E ${token}`,
  266 |             nativeName: `E2E ${token}`,
  267 |             flagCode: 'US',
  268 |             textDirection: 'ltr',
```