chore: upgrade Playwright (#15089)
authorElliott Johnson <elliott.johnson@vercel.com>
Wed, 24 Dec 2025 19:17:22 +0000 (12:17 -0700)
committerGitHub <noreply@github.com>
Wed, 24 Dec 2025 19:17:22 +0000 (12:17 -0700)
* chore: Upgrade Playwright

* checkpoint, not sure what else is going wrong

* i have never been so happy to see a test failure

* fix lockfile maybe

* fix: remove playwright

* more flaky tests

* fix clicknav

* fix another flaky test

* improve further

* another clicknav usage

packages/kit/test/apps/basics/test/client.test.js
packages/kit/test/apps/basics/test/cross-platform/client.test.js
packages/kit/test/types.d.ts
packages/kit/test/utils.js
pnpm-lock.yaml
pnpm-workspace.yaml

index a6730dd5c1c4194af9b9ab43e6f235716753bebf..4e72140dc50524248a21cfce897834037a371522 100644 (file)
@@ -1104,23 +1104,23 @@ test.describe('data-sveltekit attributes', () => {
        });
 
        test('data-sveltekit-reload', async ({ baseURL, page, clicknav }) => {
-               /** @type {string[]} */
-               const requests = [];
-               page.on('request', (r) => requests.push(r.url()));
-
                await page.goto('/data-sveltekit/reload');
+               let request_promise = page.waitForRequest(`${baseURL}/data-sveltekit/reload/target`);
                await clicknav('#one');
-               expect(requests).toContain(`${baseURL}/data-sveltekit/reload/target`);
+               await request_promise;
 
-               requests.length = 0;
                await page.goto('/data-sveltekit/reload');
+               request_promise = page.waitForRequest(`${baseURL}/data-sveltekit/reload/target`);
                await clicknav('#two');
-               expect(requests).toContain(`${baseURL}/data-sveltekit/reload/target`);
+               await request_promise;
 
-               requests.length = 0;
                await page.goto('/data-sveltekit/reload');
+               request_promise = page.waitForRequest(`${baseURL}/data-sveltekit/reload/target`, {
+                       timeout: 1000
+               });
+               request_promise.catch(() => {});
                await clicknav('#three');
-               expect(requests).not.toContain(`${baseURL}/data-sveltekit/reload/target`);
+               await expect(request_promise).rejects.toThrow();
        });
 
        test('data-sveltekit-noscroll', async ({ page, clicknav }) => {
@@ -1649,9 +1649,9 @@ test.describe('Shallow routing', () => {
 });
 
 test.describe('reroute', () => {
-       test('Apply reroute during client side navigation', async ({ page }) => {
+       test('Apply reroute during client side navigation', async ({ page, clicknav }) => {
                await page.goto('/reroute/basic');
-               await page.click("a[href='/reroute/basic/a']");
+               await clicknav('a[href="/reroute/basic/a"]', { waitForURL: '/reroute/basic/a' });
                expect(await page.textContent('h1')).toContain(
                        'Successfully rewritten, URL should still show a: /reroute/basic/a'
                );
index 36b84c22eeb668fb2c30065e74556c45a5fbcb99..ad791f44c7f1fd242369d2d4ca569bab7edda921 100644 (file)
@@ -14,15 +14,14 @@ test.describe('a11y', () => {
 
                await page.goto('/accessibility/a');
 
-               await clicknav('[href="/accessibility/b"]');
-               expect(await page.innerHTML('h1')).toBe('b');
+               await clicknav('[href="/accessibility/b"]', { waitForURL: '/accessibility/b' });
                expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BODY');
                await page.keyboard.press(tab);
 
                expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BUTTON');
                expect(await page.evaluate(() => (document.activeElement || {}).textContent)).toBe('focus me');
 
-               await clicknav('[href="/accessibility/a"]');
+               await clicknav('[href="/accessibility/a"]', { waitForURL: '/accessibility/a' });
                expect(await page.innerHTML('h1')).toBe('a');
                expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('BODY');
 
@@ -36,7 +35,9 @@ test.describe('a11y', () => {
        test('applies autofocus after a navigation', async ({ page, clicknav }) => {
                await page.goto('/accessibility/autofocus/a');
 
-               await clicknav('[href="/accessibility/autofocus/b"]');
+               await clicknav('[href="/accessibility/autofocus/b"]', {
+                       waitForURL: '/accessibility/autofocus/b'
+               });
                expect(await page.innerHTML('h1')).toBe('b');
                expect(await page.evaluate(() => (document.activeElement || {}).nodeName)).toBe('INPUT');
        });
@@ -388,14 +389,14 @@ test.describe('Scrolling', () => {
                await page.goto('/anchor');
                await page.locator('#scroll-anchor').click();
                const originalScrollY = /** @type {number} */ (await page.evaluate(() => scrollY));
-               await clicknav('#routing-page');
-               await page.goBack();
+               await clicknav('#routing-page', { waitForURL: '/routing/hashes/target' });
 
-               await expect(page).toHaveURL('/anchor#last-anchor-2');
+               await page.goBack();
+               await page.waitForURL('/anchor#last-anchor-2');
                expect(await page.evaluate(() => scrollY)).toEqual(originalScrollY);
 
                await page.goBack();
-               await expect(page).toHaveURL('/anchor');
+               await page.waitForURL('/anchor');
                expect(await page.evaluate(() => scrollY)).toEqual(0);
        });
 
@@ -734,14 +735,10 @@ test.describe('Prefetching', () => {
        test('same route hash links work more than once', async ({ page, clicknav, baseURL }) => {
                await page.goto('/routing/hashes/a');
 
-               await clicknav('[href="#preload"]');
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a#preload`);
-
-               await clicknav('[href="/routing/hashes/a"]');
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a`);
+               await clicknav('[href="#preload"]', { waitForURL: `${baseURL}/routing/hashes/a#preload` });
 
-               await clicknav('[href="#preload"]');
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a#preload`);
+               await clicknav('[href="/routing/hashes/a"]', { waitForURL: `${baseURL}/routing/hashes/a` });
+               await clicknav('[href="#preload"]', { waitForURL: `${baseURL}/routing/hashes/a#preload` });
        });
 
        test('does not rerun load on calls to duplicate preload hash route', async ({ app, page }) => {
@@ -802,19 +799,19 @@ test.describe('Routing', () => {
                expect(await page.textContent('#page-url-hash')).toBe('#target');
        });
 
-       test('page.url.hash is correctly set on navigation', async ({ page }) => {
+       test('page.url.hash is correctly set on navigation', async ({ page, clicknav }) => {
                await page.goto('/routing/hashes/pagestate');
-               expect(await page.textContent('#window-hash')).toBe('');
-               expect(await page.textContent('#page-url-hash')).toBe('');
-               await page.locator('[href="#target"]').click();
-               expect(await page.textContent('#window-hash')).toBe('#target');
-               expect(await page.textContent('#page-url-hash')).toBe('#target');
-               await page.locator('[href="/routing/hashes/pagestate"]').click();
+               await expect(page.locator('#window-hash')).toHaveText('');
+               await expect(page.locator('#page-url-hash')).toHaveText('');
+               await clicknav('[href="#target"]');
+               await expect(page.locator('#window-hash')).toHaveText('#target');
+               await expect(page.locator('#page-url-hash')).toHaveText('#target');
+               await clicknav('[href="/routing/hashes/pagestate"]');
                await expect(page.locator('#window-hash')).toHaveText('#target'); // hashchange doesn't fire for these
                await expect(page.locator('#page-url-hash')).toHaveText('');
                await page.goBack();
-               expect(await page.textContent('#window-hash')).toBe('#target');
-               expect(await page.textContent('#page-url-hash')).toBe('#target');
+               await expect(page.locator('#window-hash')).toHaveText('#target');
+               await expect(page.locator('#page-url-hash')).toHaveText('#target');
        });
 
        test('clicking on a hash link focuses the associated element', async ({ page }) => {
@@ -832,12 +829,14 @@ test.describe('Routing', () => {
                baseURL
        }) => {
                await page.goto('/data-sveltekit/reload/hash');
-               await page.locator('a[href="#example"]').click();
-               expect(page.url()).toBe(`${baseURL}/data-sveltekit/reload/hash#example`);
-               await clicknav('a[href="/data-sveltekit/reload/hash/new"]');
-               expect(page.url()).toBe(`${baseURL}/data-sveltekit/reload/hash/new`);
+               await clicknav('a[href="#example"]', {
+                       waitForURL: `${baseURL}/data-sveltekit/reload/hash#example`
+               });
+               await clicknav('a[href="/data-sveltekit/reload/hash/new"]', {
+                       waitForURL: `${baseURL}/data-sveltekit/reload/hash/new`
+               });
                await page.goBack();
-               expect(page.url()).toBe(`${baseURL}/data-sveltekit/reload/hash#example`);
+               await page.waitForURL(`${baseURL}/data-sveltekit/reload/hash#example`);
                await expect(page.getByRole('textbox')).toBeVisible();
        });
 
@@ -862,11 +861,9 @@ test.describe('Routing', () => {
                await page.goto('/routing/hashes/a');
 
                await page.locator('[href="#hash-target"]').click();
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a#hash-target`);
+               await page.waitForURL(`${baseURL}/routing/hashes/a#hash-target`);
 
                await clicknav('[href="/routing/hashes/b"]');
-               expect(await page.textContent('h1')).toBe('b');
-
                await expect(page.locator('h1')).toHaveText('b');
                await page.goBack();
                await expect(page.locator('h1')).toHaveText('a');
@@ -879,14 +876,16 @@ test.describe('Routing', () => {
        }) => {
                await page.goto('/routing/hashes/a');
 
-               await clicknav('[href="#hash-target"]');
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a#hash-target`);
+               await clicknav('[href="#hash-target"]', {
+                       waitForURL: `${baseURL}/routing/hashes/a#hash-target`
+               });
 
-               await clicknav('[href="#replace-state"]');
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a#replace-state`);
+               await clicknav('[href="#replace-state"]', {
+                       waitForURL: `${baseURL}/routing/hashes/a#replace-state`
+               });
 
                await page.goBack();
-               expect(page.url()).toBe(`${baseURL}/routing/hashes/a`);
+               await page.waitForURL(`${baseURL}/routing/hashes/a`);
        });
 
        test('does not normalize external path', async ({ page, start_server }) => {
index 7e51a44b2f2e375e1e3de37fa7c86b743619fc86..e407b11a390fb84811bc04fa4c7184ecee0e537d 100644 (file)
@@ -21,7 +21,10 @@ export const test: TestType<
                                preloadCode(pathname: string): Promise<void>;
                                preloadData(url: string): Promise<void>;
                        };
-                       clicknav(selector: string, options?: Parameters<Page['waitForNavigation']>[0]): Promise<void>;
+                       clicknav(
+                               selector: string,
+                               options?: { timeout?: number; waitForURL?: string }
+                       ): Promise<void>;
                        scroll_to(x: number, y: number): Promise<void>;
                        in_view(selector: string): Promise<boolean>;
                        get_computed_style(selector: string, prop: string): Promise<string>;
index 0153bf6de689b0fa4f4a157f6533b5d4fa803d5d..1c806ee2c84f684c7671424e59d27ac256f34e5c 100644 (file)
@@ -28,12 +28,16 @@ export const test = base.extend({
        clicknav: async ({ page, javaScriptEnabled }, use) => {
                /**
                 * @param {string} selector
-                * @param {{ timeout: number }} options
+                * @param {{ timeout?: number, waitForURL?: string }} [options]
                 */
-               async function clicknav(selector, options) {
+               async function clicknav(selector, { timeout, waitForURL } = {}) {
                        const element = page.locator(selector);
                        if (javaScriptEnabled) {
-                               await Promise.all([page.waitForNavigation(options), element.click()]);
+                               const promises = [page.waitForNavigation({ timeout }), element.click()];
+                               if (waitForURL) {
+                                       promises.push(page.waitForURL(waitForURL, { timeout }));
+                               }
+                               await Promise.all(promises);
                        } else {
                                await element.click();
                        }
index 53780fb1c00630c6e702d4a98273ec4852f17e1e..6b26082770d43d7afd2912bd92773b64028a03ca 100644 (file)
@@ -25,8 +25,8 @@ catalogs:
       specifier: ^2.0.1
       version: 2.2.0
     '@playwright/test':
-      specifier: ^1.51.1
-      version: 1.51.1
+      specifier: 1.56.0
+      version: 1.56.0
     '@polka/url':
       specifier: ^1.0.0-next.28
       version: 1.0.0-next.28
@@ -124,7 +124,7 @@ importers:
         version: 2.29.6(@types/node@18.19.119)
       '@playwright/test':
         specifier: 'catalog:'
-        version: 1.51.1
+        version: 1.56.0
       '@sveltejs/eslint-config':
         specifier: 'catalog:'
         version: 8.2.0(@stylistic/eslint-plugin-js@2.1.0(eslint@9.34.0(jiti@2.4.2)))(eslint-config-prettier@9.1.0(eslint@9.34.0(jiti@2.4.2)))(eslint-plugin-n@17.16.1(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(eslint-plugin-svelte@3.9.3(eslint@9.34.0(jiti@2.4.2))(svelte@5.42.2)(ts-node@10.9.2(@types/node@18.19.119)(typescript@5.8.3)))(eslint@9.34.0(jiti@2.4.2))(typescript-eslint@8.43.0(eslint@9.34.0(jiti@2.4.2))(typescript@5.8.3))(typescript@5.8.3)
@@ -176,7 +176,7 @@ importers:
     devDependencies:
       '@playwright/test':
         specifier: 'catalog:'
-        version: 1.51.1
+        version: 1.56.0
       '@sveltejs/kit':
         specifier: workspace:^
         version: link:../kit
@@ -370,7 +370,7 @@ importers:
     devDependencies:
       '@playwright/test':
         specifier: 'catalog:'
-        version: 1.51.1
+        version: 1.56.0
       '@sveltejs/kit':
         specifier: workspace:^
         version: link:../kit
@@ -571,7 +571,7 @@ importers:
         version: 1.9.0
       '@playwright/test':
         specifier: 'catalog:'
-        version: 1.51.1
+        version: 1.56.0
       '@sveltejs/vite-plugin-svelte':
         specifier: 'catalog:'
         version: 6.0.0-next.3(svelte@5.42.2)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
@@ -676,7 +676,7 @@ importers:
         version: 6.0.0-next.3(svelte@5.42.2)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
       '@vitest/browser':
         specifier: 'catalog:'
-        version: 3.2.4(playwright@1.51.1)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)
+        version: 3.2.4(playwright@1.56.0)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)
       svelte:
         specifier: 'catalog:'
         version: 5.42.2
@@ -2856,8 +2856,8 @@ packages:
     resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
     engines: {node: '>=14'}
 
-  '@playwright/test@1.51.1':
-    resolution: {integrity: sha512-nM+kEaTSAoVlXmMPH10017vn3FSiFqr/bh4fKg9vmAdMfd9SDqRZNvPSiAHADc/itWak+qPvMPZQOPwCBW7k7Q==}
+  '@playwright/test@1.56.0':
+    resolution: {integrity: sha512-Tzh95Twig7hUwwNe381/K3PggZBZblKUe2wv25oIpzWLr6Z0m4KgV1ZVIjnR6GM9ANEqjZD7XsZEa6JL/7YEgg==}
     engines: {node: '>=18'}
     hasBin: true
 
@@ -5997,13 +5997,13 @@ packages:
   pkg-types@1.3.1:
     resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
 
-  playwright-core@1.51.1:
-    resolution: {integrity: sha512-/crRMj8+j/Nq5s8QcvegseuyeZPxpQCZb6HNk3Sos3BlZyAknRjoyJPFWkpNn8v0+P3WiwqFF8P+zQo4eqiNuw==}
+  playwright-core@1.56.0:
+    resolution: {integrity: sha512-1SXl7pMfemAMSDn5rkPeZljxOCYAmQnYLBTExuh6E8USHXGSX3dx6lYZN/xPpTz1vimXmPA9CDnILvmJaB8aSQ==}
     engines: {node: '>=18'}
     hasBin: true
 
-  playwright@1.51.1:
-    resolution: {integrity: sha512-kkx+MB2KQRkyxjYPc3a0wLZZoDczmppyGJIvQ43l+aZihkaVvmu/21kiyaHeHjiFxjxNNFnUncKmcGIyOojsaw==}
+  playwright@1.56.0:
+    resolution: {integrity: sha512-X5Q1b8lOdWIE4KAoHpW3SE8HvUB+ZZsUoN64ZhjnN8dOb1UpujxBtENGiZFE+9F/yhzJwYa+ca3u43FeLbboHA==}
     engines: {node: '>=18'}
     hasBin: true
 
@@ -8841,9 +8841,9 @@ snapshots:
   '@pkgjs/parseargs@0.11.0':
     optional: true
 
-  '@playwright/test@1.51.1':
+  '@playwright/test@1.56.0':
     dependencies:
-      playwright: 1.51.1
+      playwright: 1.56.0
 
   '@pnpm/config.env-replace@1.1.0': {}
 
@@ -9326,7 +9326,7 @@ snapshots:
       - rollup
       - supports-color
 
-  '@vitest/browser@3.2.4(playwright@1.51.1)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)':
+  '@vitest/browser@3.2.4(playwright@1.56.0)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)':
     dependencies:
       '@testing-library/dom': 10.4.1
       '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
@@ -9338,7 +9338,7 @@ snapshots:
       vitest: 3.2.4(@types/node@18.19.119)(@vitest/browser@3.2.4)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
       ws: 8.18.3
     optionalDependencies:
-      playwright: 1.51.1
+      playwright: 1.56.0
     transitivePeerDependencies:
       - bufferutil
       - msw
@@ -12192,11 +12192,11 @@ snapshots:
       mlly: 1.7.4
       pathe: 2.0.3
 
-  playwright-core@1.51.1: {}
+  playwright-core@1.56.0: {}
 
-  playwright@1.51.1:
+  playwright@1.56.0:
     dependencies:
-      playwright-core: 1.51.1
+      playwright-core: 1.56.0
     optionalDependencies:
       fsevents: 2.3.2
 
@@ -13334,7 +13334,7 @@ snapshots:
       why-is-node-running: 2.3.0
     optionalDependencies:
       '@types/node': 18.19.119
-      '@vitest/browser': 3.2.4(playwright@1.51.1)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)
+      '@vitest/browser': 3.2.4(playwright@1.56.0)(vite@6.3.6(@types/node@18.19.119)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))(vitest@3.2.4)
     transitivePeerDependencies:
       - jiti
       - less
index 52556b76f569d174da95a77271519c93ab118533..434ef17f0ca344eadd5c500f27b7b7b17ef1a8ff 100644 (file)
@@ -17,7 +17,7 @@ catalog:
   '@netlify/functions': ^5.0.0
   '@opentelemetry/sdk-node': ^0.208.0
   '@opentelemetry/sdk-trace-node': ^2.0.1
-  '@playwright/test': ^1.51.1
+  '@playwright/test': 1.56.0
   '@polka/url': ^1.0.0-next.28
   '@rollup/plugin-commonjs': ^28.0.1
   '@rollup/plugin-json': ^6.1.0