Commit ada61b98d1 for woocommerce

commit ada61b98d16786c4dc188f6479c12e58a3a7f5a9
Author: theAverageDev (Luca Tumedei) <luca@theaveragedev.com>
Date:   Fri Feb 6 11:38:27 2026 +0100

    Convert e2e-utils-playwright package to TypeScript (#63102)

    * Convert e2e-utils-playwright package to TypeScript

    Add TypeScript configuration, type definitions, and convert source files
    to use TypeScript syntax (type annotations, interfaces, generics).

diff --git a/packages/js/e2e-utils-playwright/babel.config.js b/packages/js/e2e-utils-playwright/babel.config.js
deleted file mode 100644
index 7036bf87d8..0000000000
--- a/packages/js/e2e-utils-playwright/babel.config.js
+++ /dev/null
@@ -1,11 +0,0 @@
-module.exports = {
-	"presets": [
-		"@wordpress/babel-preset-default",
-		[
-			"@babel/preset-env",
-			{
-				"modules": "commonjs"
-			}
-		]
-	]
-};
diff --git a/packages/js/e2e-utils-playwright/changelog/e2e-utils-playwright-ts-conversion b/packages/js/e2e-utils-playwright/changelog/e2e-utils-playwright-ts-conversion
new file mode 100644
index 0000000000..ccc34f3bf1
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/changelog/e2e-utils-playwright-ts-conversion
@@ -0,0 +1,4 @@
+Significance: minor
+Type: dev
+
+Converted the package source code to TypeScript.
diff --git a/packages/js/e2e-utils-playwright/jest.config.json b/packages/js/e2e-utils-playwright/jest.config.json
new file mode 100644
index 0000000000..51e3213829
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/jest.config.json
@@ -0,0 +1,5 @@
+{
+	"rootDir": "./",
+	"roots": [ "<rootDir>/src" ],
+	"preset": "./node_modules/@woocommerce/internal-js-tests/jest-preset.js"
+}
diff --git a/packages/js/e2e-utils-playwright/package.json b/packages/js/e2e-utils-playwright/package.json
index d0eb6af79f..865611ed07 100644
--- a/packages/js/e2e-utils-playwright/package.json
+++ b/packages/js/e2e-utils-playwright/package.json
@@ -12,6 +12,20 @@
 		"node": "^20.11.1"
 	},
 	"main": "build/index.js",
+	"module": "build-module/index.js",
+	"types": "build-types/index.d.ts",
+	"exports": {
+		".": {
+			"types": "./build-types/index.d.ts",
+			"import": "./build-module/index.js",
+			"require": "./build/index.js"
+		}
+	},
+	"files": [
+		"build",
+		"build-module",
+		"build-types"
+	],
 	"scripts": {
 		"changelog": "XDEBUG_MODE=off composer install --quiet && composer exec -- changelogger",
 		"update:php": "XDEBUG_MODE=off composer update --quiet",
@@ -21,21 +35,47 @@
 		"prepack": "pnpm build",
 		"build": "pnpm --if-present --workspace-concurrency=Infinity --stream --filter=\"$npm_package_name...\" '/^build:project:.*$/'",
 		"build:project": "pnpm --if-present '/^build:project:.*$/'",
-		"build:project:esm": "wireit"
+		"build:project:cjs": "wireit",
+		"build:project:esm": "wireit",
+		"watch:build": "pnpm --if-present --workspace-concurrency=Infinity --filter=\"$npm_package_name...\" --parallel '/^watch:build:project:.*$/'",
+		"watch:build:project": "pnpm --if-present run '/^watch:build:project:.*$/'",
+		"watch:build:project:cjs": "wireit",
+		"watch:build:project:esm": "wireit"
 	},
 	"config": {
 		"ci": {
 			"lint": {
 				"command": "lint",
 				"changes": "src/**/*.{js,ts,tsx}"
-			}
+			},
+			"tests": [
+				{
+					"name": "JavaScript",
+					"command": "test",
+					"changes": [
+						"jest.config.json",
+						"tsconfig.json",
+						"src/**/*.{js,jsx,ts,tsx}"
+					],
+					"events": [
+						"pull_request",
+						"push"
+					]
+				}
+			]
 		}
 	},
 	"devDependencies": {
-		"@babel/cli": "7.25.7",
-		"@babel/core": "7.25.7",
-		"@wordpress/babel-preset-default": "next",
+		"@playwright/test": "^1.57.0",
+		"@types/jest": "29.5.x",
+		"@types/node": "20.x.x",
+		"@woocommerce/eslint-plugin": "workspace:*",
+		"@woocommerce/internal-js-tests": "workspace:*",
+		"eslint": "^8.55.0",
 		"jest": "29.5.x",
+		"jest-cli": "29.5.x",
+		"ts-jest": "29.1.x",
+		"typescript": "5.7.x",
 		"wireit": "0.14.12"
 	},
 	"dependencies": {
@@ -46,18 +86,54 @@
 		"access": "public"
 	},
 	"wireit": {
-		"build:project:esm": {
-			"command": "babel src --out-dir build --copy-files --no-copy-ignored --ignore '**/test/**'",
+		"build:project:cjs": {
+			"command": "tsc --project tsconfig-cjs.json --noCheck",
 			"clean": "if-file-deleted",
 			"files": [
-				"babel.config.js",
-				"src/**/*.{js,jsx,ts,tsx}",
-				"typings/**/*.ts"
+				"tsconfig-cjs.json",
+				"src/**/*.{js,jsx,ts,tsx}"
 			],
 			"output": [
 				"build"
 			],
-			"dependencies": []
+			"dependencies": [
+				"dependencyOutputs"
+			]
+		},
+		"watch:build:project:cjs": {
+			"command": "tsc --project tsconfig-cjs.json --watch --noCheck",
+			"service": true
+		},
+		"build:project:esm": {
+			"command": "tsc --project tsconfig.json",
+			"clean": "if-file-deleted",
+			"files": [
+				"tsconfig.json",
+				"src/**/*.{js,jsx,ts,tsx}"
+			],
+			"output": [
+				"build-module",
+				"build-types"
+			],
+			"dependencies": [
+				"dependencyOutputs"
+			]
+		},
+		"watch:build:project:esm": {
+			"command": "tsc --project tsconfig.json --watch",
+			"service": true
+		},
+		"dependencyOutputs": {
+			"allowUsuallyExcludedPaths": true,
+			"files": [
+				"package.json",
+				"node_modules/@woocommerce/eslint-plugin/configs",
+				"node_modules/@woocommerce/eslint-plugin/rules",
+				"node_modules/@woocommerce/eslint-plugin/index.js",
+				"node_modules/@woocommerce/internal-js-tests/build",
+				"node_modules/@woocommerce/internal-js-tests/build-module",
+				"node_modules/@woocommerce/internal-js-tests/jest-preset.js"
+			]
 		}
 	}
 }
diff --git a/packages/js/e2e-utils-playwright/src/api-client.js b/packages/js/e2e-utils-playwright/src/api-client.js
deleted file mode 100644
index ab8c6e5c55..0000000000
--- a/packages/js/e2e-utils-playwright/src/api-client.js
+++ /dev/null
@@ -1,276 +0,0 @@
-/* eslint-disable jsdoc/check-property-names */
-// @ts-check
-/**
- * @typedef {Object} BasicAuth
- * @property {'basic'}  type           Type of authentication ('basic')
- * @property {string}   username       Username for basic authentication
- * @property {string}   password       Password for basic authentication
- *
- * @typedef {Object} OAuth1Auth
- * @property {'oauth1'} type           Type of authentication ('oauth1')
- * @property {string}   consumerKey    OAuth1 consumer key
- * @property {string}   consumerSecret OAuth1 consumer secret
- *
- * @typedef {BasicAuth|OAuth1Auth} Auth
- */
-/**
- * External dependencies
- */
-import axios from 'axios';
-import OAuth from 'oauth-1.0a';
-import { createHmac } from 'crypto';
-
-/**
- * Create an API client instance with the given configuration
- *
- * @param {string} baseURL Base URL for the API
- * @param {Object} auth    Auth object: { type: 'basic', username, password } or { type: 'oauth1', consumerKey, consumerSecret }
- * @return {Object} API client instance with HTTP methods
- */
-export function createClient( baseURL, auth ) {
-	if ( ! auth || typeof auth !== 'object' ) {
-		throw new Error( 'auth parameter is required and must be an object' );
-	}
-	if ( auth.type === 'basic' ) {
-		if ( ! auth.username || ! auth.password ) {
-			throw new Error( 'Basic auth requires username and password' );
-		}
-	} else if ( auth.type === 'oauth1' ) {
-		if ( ! auth.consumerKey || ! auth.consumerSecret ) {
-			throw new Error(
-				'OAuth1 auth requires consumerKey and consumerSecret'
-			);
-		}
-	} else {
-		throw new Error( 'auth.type must be either "basic" or "oauth1"' );
-	}
-
-	// Ensure baseURL ends with '/'
-	if ( ! baseURL.endsWith( '/' ) ) {
-		baseURL += '/';
-	}
-
-	// Only append 'wp-json/' if not already present
-	if ( ! baseURL.endsWith( 'wp-json/' ) ) {
-		baseURL += 'wp-json/';
-	}
-
-	const axiosConfig = {
-		baseURL,
-		headers: {
-			'Content-Type': 'application/json',
-		},
-	};
-
-	let oauth;
-	if ( auth.type === 'basic' ) {
-		axiosConfig.auth = {
-			username: auth.username,
-			password: auth.password,
-		};
-
-		// Warn if Basic Auth is used over HTTP, except for localhost
-		const isHttp = baseURL.startsWith( 'http' );
-		const isLocalhost =
-			baseURL.startsWith( 'http://localhost' ) ||
-			baseURL.startsWith( 'http://127.0.0.1' );
-		if ( isHttp && ! isLocalhost ) {
-			console.warn(
-				'Warning: Using Basic Auth over HTTP exposes credentials in plaintext!'
-			);
-		}
-	} else if ( auth.type === 'oauth1' ) {
-		oauth = new OAuth( {
-			consumer: {
-				key: auth.consumerKey,
-				secret: auth.consumerSecret,
-			},
-			signature_method: 'HMAC-SHA256',
-			hash_function: ( base, key ) => {
-				return createHmac( 'sha256', key )
-					.update( base )
-					.digest( 'base64' );
-			},
-		} );
-	}
-
-	const axiosInstance = axios.create( axiosConfig );
-
-	// Utility to redact sensitive fields from logs
-	function redact(
-		obj,
-		keys = [ 'password', 'token', 'authorization', 'cookie', 'secret' ]
-	) {
-		const shouldRedact = process.env.CI === 'true';
-		if ( ! shouldRedact ) return obj;
-		if ( ! obj || typeof obj !== 'object' ) return obj;
-		return Object.fromEntries(
-			Object.entries( obj ).map( ( [ k, v ] ) =>
-				keys.includes( k.toLowerCase() )
-					? [ k, '********' ]
-					: [ k, typeof v === 'object' ? redact( v, keys ) : v ]
-			)
-		);
-	}
-
-	// Centralized logging for requests, with redaction and formatting
-	function logRequest( label, details ) {
-		const redacted = Object.fromEntries(
-			Object.entries( details ).map( ( [ k, v ] ) => [ k, redact( v ) ] )
-		);
-		console.log( `[${ new Date().toISOString() }] ${ label }`, redacted );
-	}
-
-	function oauthRequest(
-		method,
-		path,
-		{ params = {}, data = {}, debug = false } = {}
-	) {
-		let url = baseURL + path.replace( /^\//, '' );
-		let requestConfig = { method };
-		let oauthParams, headers;
-
-		if ( method === 'GET' ) {
-			// For GET, sign the query params and append both params and OAuth params to the URL
-			oauthParams = oauth.authorize( {
-				url,
-				method,
-				data: params,
-			} );
-			const urlObj = new URL( url );
-			Object.entries( { ...params, ...oauthParams } ).forEach(
-				( [ key, value ] ) => {
-					urlObj.searchParams.append( key, value );
-				}
-			);
-			url = urlObj.toString();
-			requestConfig = { ...requestConfig, url };
-		} else {
-			// For POST/PUT/DELETE, sign the body if form-encoded, otherwise sign as if body is empty (for JSON)
-			const isJson = (
-				axiosConfig.headers[ 'Content-Type' ] || ''
-			).includes( 'application/json' );
-			oauthParams = oauth.authorize( {
-				url,
-				method,
-				data: isJson ? {} : data,
-			} );
-			headers = {
-				...axiosConfig.headers,
-				...oauth.toHeader( oauthParams ),
-			};
-			requestConfig = { ...requestConfig, url, headers, data };
-		}
-
-		if ( debug ) {
-			logRequest( 'oauthRequest', {
-				method,
-				url,
-				params,
-				data,
-				headers,
-			} );
-		}
-		return axios( requestConfig );
-	}
-
-	return {
-		/**
-		 * Make a GET request
-		 *
-		 * @param {string} path   API endpoint path
-		 * @param {Object} params Query parameters
-		 * @return {Promise} Promise that resolves to response object
-		 */
-		async get( path, params = {}, debug = false ) {
-			if ( auth.type === 'oauth1' ) {
-				return oauthRequest( 'GET', path, { params, debug } );
-			}
-			const response = await axiosInstance.get( path, { params } );
-			if ( debug ) {
-				logRequest( 'get', {
-					path,
-					params,
-					status: response?.status,
-					data: response?.data,
-				} );
-			}
-			return response;
-		},
-
-		/**
-		 * Make a POST request
-		 *
-		 * @param {string} path API endpoint path
-		 * @param {Object} data Request body data
-		 * @return {Promise} Promise that resolves to response object
-		 */
-		async post( path, data = {}, debug = false ) {
-			if ( auth.type === 'oauth1' ) {
-				return oauthRequest( 'POST', path, { data, debug } );
-			}
-			const response = await axiosInstance.post( path, data );
-			if ( debug ) {
-				logRequest( 'post', {
-					path,
-					data,
-					status: response?.status,
-					response: response?.data,
-				} );
-			}
-			return response;
-		},
-
-		/**
-		 * Make a PUT request
-		 *
-		 * @param {string} path API endpoint path
-		 * @param {Object} data Request body data
-		 * @return {Promise} Promise that resolves to response object
-		 */
-		async put( path, data = {}, debug = false ) {
-			if ( auth.type === 'oauth1' ) {
-				return oauthRequest( 'PUT', path, { data, debug } );
-			}
-			const response = await axiosInstance.put( path, data );
-			if ( debug ) {
-				logRequest( 'put', {
-					path,
-					data,
-					status: response?.status,
-					response: response?.data,
-				} );
-			}
-			return response;
-		},
-
-		/**
-		 * Make a DELETE request
-		 *
-		 * @param {string} path   API endpoint path
-		 * @param {Object} params Query parameters or request body
-		 * @return {Promise} Promise that resolves to response object
-		 */
-		async delete( path, params = {}, debug = false ) {
-			if ( auth.type === 'oauth1' ) {
-				return oauthRequest( 'DELETE', path, { data: params, debug } );
-			}
-			const response = await axiosInstance.delete( path, {
-				data: params,
-			} );
-			if ( debug ) {
-				logRequest( 'delete', {
-					path,
-					params,
-					status: response?.status,
-					response: response?.data,
-				} );
-			}
-			return response;
-		},
-	};
-}
-
-export const WC_API_PATH = 'wc/v3';
-export const WC_ADMIN_API_PATH = 'wc-admin';
-export const WP_API_PATH = 'wp/v2';
diff --git a/packages/js/e2e-utils-playwright/src/api-client.ts b/packages/js/e2e-utils-playwright/src/api-client.ts
new file mode 100644
index 0000000000..00e2a1b675
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/api-client.ts
@@ -0,0 +1,351 @@
+/**
+ * External dependencies
+ */
+import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
+import OAuth from 'oauth-1.0a';
+import { createHmac } from 'crypto';
+
+/**
+ * Internal dependencies
+ */
+import type { Auth, ApiClient } from './types';
+
+// Re-export types for consumers
+export type { BasicAuth, OAuth1Auth, Auth } from './types';
+
+interface OAuthRequestOptions {
+	params?: Record< string, unknown >;
+	data?: Record< string, unknown >;
+	debug?: boolean;
+}
+
+/**
+ * Create an API client instance with the given configuration.
+ *
+ * @param baseURL - Base URL for the API
+ * @param auth    - Auth object: { type: 'basic', username, password } or { type: 'oauth1', consumerKey, consumerSecret }
+ * @return API client instance with HTTP methods
+ */
+export function createClient( baseURL: string, auth: Auth ): ApiClient {
+	if ( ! auth || typeof auth !== 'object' ) {
+		throw new Error( 'auth parameter is required and must be an object' );
+	}
+	if ( auth.type === 'basic' ) {
+		if ( ! auth.username || ! auth.password ) {
+			throw new Error( 'Basic auth requires username and password' );
+		}
+	} else if ( auth.type === 'oauth1' ) {
+		if ( ! auth.consumerKey || ! auth.consumerSecret ) {
+			throw new Error(
+				'OAuth1 auth requires consumerKey and consumerSecret'
+			);
+		}
+	} else {
+		throw new Error( 'auth.type must be either "basic" or "oauth1"' );
+	}
+
+	// Ensure baseURL ends with '/'
+	let normalizedBaseURL = baseURL;
+	if ( ! normalizedBaseURL.endsWith( '/' ) ) {
+		normalizedBaseURL += '/';
+	}
+
+	// Only append 'wp-json/' if not already present
+	if ( ! normalizedBaseURL.endsWith( 'wp-json/' ) ) {
+		normalizedBaseURL += 'wp-json/';
+	}
+
+	const axiosConfig: AxiosRequestConfig = {
+		baseURL: normalizedBaseURL,
+		headers: {
+			'Content-Type': 'application/json',
+		},
+	};
+
+	let oauth: OAuth | undefined;
+	if ( auth.type === 'basic' ) {
+		axiosConfig.auth = {
+			username: auth.username,
+			password: auth.password,
+		};
+
+		// Warn if Basic Auth is used over HTTP, except for localhost
+		const isHttp = normalizedBaseURL.startsWith( 'http://' );
+		const isLocalhost =
+			normalizedBaseURL.startsWith( 'http://localhost' ) ||
+			normalizedBaseURL.startsWith( 'http://127.0.0.1' );
+		if ( isHttp && ! isLocalhost ) {
+			console.warn(
+				'Warning: Using Basic Auth over HTTP exposes credentials in plaintext!'
+			);
+		}
+	} else if ( auth.type === 'oauth1' ) {
+		oauth = new OAuth( {
+			consumer: {
+				key: auth.consumerKey,
+				secret: auth.consumerSecret,
+			},
+			signature_method: 'HMAC-SHA256',
+			hash_function: ( base: string, key: string ) => {
+				return createHmac( 'sha256', key )
+					.update( base )
+					.digest( 'base64' );
+			},
+		} );
+	}
+
+	const axiosInstance = axios.create( axiosConfig );
+
+	/**
+	 * Utility to redact sensitive fields from logs.
+	 *
+	 * @param obj  - Object to redact
+	 * @param keys - Keys to redact
+	 * @return Redacted object
+	 */
+	function redact(
+		obj: Record< string, unknown > | null | undefined,
+		keys: string[] = [
+			'password',
+			'token',
+			'authorization',
+			'cookie',
+			'secret',
+		]
+	): Record< string, unknown > | null | undefined {
+		const shouldRedact = process.env.CI === 'true';
+		if ( ! shouldRedact ) return obj;
+		if ( ! obj || typeof obj !== 'object' ) return obj;
+		return Object.fromEntries(
+			Object.entries( obj ).map( ( [ k, v ] ) =>
+				keys.includes( k.toLowerCase() )
+					? [ k, '********' ]
+					: [
+							k,
+							typeof v === 'object'
+								? redact( v as Record< string, unknown >, keys )
+								: v,
+					  ]
+			)
+		);
+	}
+
+	/**
+	 * Centralized logging for requests, with redaction and formatting.
+	 *
+	 * @param label   - Log label
+	 * @param details - Details to log
+	 */
+	function logRequest( label: string, details: Record< string, unknown > ) {
+		const redacted = Object.fromEntries(
+			Object.entries( details ).map( ( [ k, v ] ) => [
+				k,
+				redact( v as Record< string, unknown > ),
+			] )
+		);
+		console.log( `[${ new Date().toISOString() }] ${ label }`, redacted );
+	}
+
+	/**
+	 * Make an OAuth-authenticated request.
+	 *
+	 * @param method         - HTTP method
+	 * @param path           - API endpoint path
+	 * @param options        - Request options
+	 * @param options.params - Query parameters
+	 * @param options.data   - Request body data
+	 * @param options.debug  - Enable debug logging
+	 * @return Promise resolving to the response
+	 */
+	function oauthRequest(
+		method: string,
+		path: string,
+		{ params = {}, data = {}, debug = false }: OAuthRequestOptions = {}
+	): Promise< AxiosResponse > {
+		if ( ! oauth ) {
+			throw new Error( 'OAuth not initialized' );
+		}
+
+		let url = normalizedBaseURL + path.replace( /^\//, '' );
+		let requestConfig: AxiosRequestConfig = { method };
+		let oauthParams: OAuth.Authorization;
+		let headers: Record< string, string > | undefined;
+
+		if ( method === 'GET' ) {
+			// For GET, sign the query params and append both params and OAuth params to the URL
+			oauthParams = oauth.authorize( {
+				url,
+				method,
+				data: params,
+			} );
+			const urlObj = new URL( url );
+			Object.entries( { ...params, ...oauthParams } ).forEach(
+				( [ key, value ] ) => {
+					urlObj.searchParams.append( key, String( value ) );
+				}
+			);
+			url = urlObj.toString();
+			requestConfig = { ...requestConfig, url };
+		} else {
+			// For POST/PUT/DELETE, sign the body if form-encoded, otherwise sign as if body is empty (for JSON)
+			const contentType =
+				(
+					axiosConfig.headers as Record< string, string > | undefined
+				 )?.[ 'Content-Type' ] || '';
+			const isJson = contentType.includes( 'application/json' );
+			oauthParams = oauth.authorize( {
+				url,
+				method,
+				data: isJson ? {} : data,
+			} );
+			headers = {
+				...( axiosConfig.headers as Record< string, string > ),
+				...oauth.toHeader( oauthParams ),
+			};
+			requestConfig = { ...requestConfig, url, headers, data };
+		}
+
+		if ( debug ) {
+			logRequest( 'oauthRequest', {
+				method,
+				url,
+				params,
+				data,
+				headers,
+			} );
+		}
+		return axios( requestConfig );
+	}
+
+	return {
+		/**
+		 * Make a GET request.
+		 *
+		 * @param path   - API endpoint path
+		 * @param params - Query parameters
+		 * @param debug  - Enable debug logging
+		 * @return Promise that resolves to response object
+		 */
+		async get< T = unknown >(
+			path: string,
+			params: Record< string, unknown > = {},
+			debug = false
+		): Promise< AxiosResponse< T > > {
+			if ( auth.type === 'oauth1' ) {
+				return oauthRequest( 'GET', path, {
+					params,
+					debug,
+				} );
+			}
+			const response = await axiosInstance.get< T >( path, { params } );
+			if ( debug ) {
+				logRequest( 'get', {
+					path,
+					params,
+					status: response?.status,
+					data: response?.data,
+				} );
+			}
+			return response;
+		},
+
+		/**
+		 * Make a POST request.
+		 *
+		 * @param path  - API endpoint path
+		 * @param data  - Request body data
+		 * @param debug - Enable debug logging
+		 * @return Promise that resolves to response object
+		 */
+		async post< T = unknown >(
+			path: string,
+			data: Record< string, unknown > = {},
+			debug = false
+		): Promise< AxiosResponse< T > > {
+			if ( auth.type === 'oauth1' ) {
+				return oauthRequest( 'POST', path, {
+					data,
+					debug,
+				} );
+			}
+			const response = await axiosInstance.post< T >( path, data );
+			if ( debug ) {
+				logRequest( 'post', {
+					path,
+					data,
+					status: response?.status,
+					response: response?.data,
+				} );
+			}
+			return response;
+		},
+
+		/**
+		 * Make a PUT request.
+		 *
+		 * @param path  - API endpoint path
+		 * @param data  - Request body data
+		 * @param debug - Enable debug logging
+		 * @return Promise that resolves to response object
+		 */
+		async put< T = unknown >(
+			path: string,
+			data: Record< string, unknown > = {},
+			debug = false
+		): Promise< AxiosResponse< T > > {
+			if ( auth.type === 'oauth1' ) {
+				return oauthRequest( 'PUT', path, {
+					data,
+					debug,
+				} );
+			}
+			const response = await axiosInstance.put< T >( path, data );
+			if ( debug ) {
+				logRequest( 'put', {
+					path,
+					data,
+					status: response?.status,
+					response: response?.data,
+				} );
+			}
+			return response;
+		},
+
+		/**
+		 * Make a DELETE request.
+		 *
+		 * @param path   - API endpoint path
+		 * @param params - Query parameters or request body
+		 * @param debug  - Enable debug logging
+		 * @return Promise that resolves to response object
+		 */
+		async delete< T = unknown >(
+			path: string,
+			params: Record< string, unknown > = {},
+			debug = false
+		): Promise< AxiosResponse< T > > {
+			if ( auth.type === 'oauth1' ) {
+				return oauthRequest( 'DELETE', path, {
+					data: params,
+					debug,
+				} );
+			}
+			const response = await axiosInstance.delete< T >( path, {
+				data: params,
+			} );
+			if ( debug ) {
+				logRequest( 'delete', {
+					path,
+					params,
+					status: response?.status,
+					response: response?.data,
+				} );
+			}
+			return response;
+		},
+	};
+}
+
+export const WC_API_PATH = 'wc/v3';
+export const WC_ADMIN_API_PATH = 'wc-admin';
+export const WP_API_PATH = 'wp/v2';
diff --git a/packages/js/e2e-utils-playwright/src/cart.js b/packages/js/e2e-utils-playwright/src/cart.ts
similarity index 52%
rename from packages/js/e2e-utils-playwright/src/cart.js
rename to packages/js/e2e-utils-playwright/src/cart.ts
index f9e1d4a8e8..63551930d1 100644
--- a/packages/js/e2e-utils-playwright/src/cart.js
+++ b/packages/js/e2e-utils-playwright/src/cart.ts
@@ -1,11 +1,20 @@
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+
 /**
  * Adds a specified quantity of a product by ID to the WooCommerce cart.
  *
- * @param {import('playwright').Page} page
- * @param {string}                    productId
- * @param {number}                    [quantity=1]
+ * @param page      - Playwright page object
+ * @param productId - The product ID to add
+ * @param quantity  - Number of items to add (default: 1)
  */
-export const addAProductToCart = async ( page, productId, quantity = 1 ) => {
+export const addAProductToCart = async (
+	page: Page,
+	productId: string,
+	quantity = 1
+): Promise< void > => {
 	for ( let i = 0; i < quantity; i++ ) {
 		const responsePromise = page.waitForResponse(
 			'**/wp-json/wc/store/v1/cart**'
@@ -17,17 +26,17 @@ export const addAProductToCart = async ( page, productId, quantity = 1 ) => {
 };

 /**
- * Util helper made for adding multiple same products to cart
+ * Util helper made for adding multiple same products to cart.
  *
- * @param {import('playwright').Page} page
- * @param {string}                    productName
- * @param {number}                    quantityCount
+ * @param page          - Playwright page object
+ * @param productName   - Name of the product to add
+ * @param quantityCount - Number of items to add (default: 1)
  */
 export async function addOneOrMoreProductToCart(
-	page,
-	productName,
+	page: Page,
+	productName: string,
 	quantityCount = 1
-) {
+): Promise< void > {
 	await page.goto(
 		`product/${ productName.replace( / /gi, '-' ).toLowerCase() }`
 	);
diff --git a/packages/js/e2e-utils-playwright/src/checkout.js b/packages/js/e2e-utils-playwright/src/checkout.js
deleted file mode 100644
index 19dbc02b1c..0000000000
--- a/packages/js/e2e-utils-playwright/src/checkout.js
+++ /dev/null
@@ -1,207 +0,0 @@
-/**
- * Util helper made to fill the Checkout details in the block-based checkout.
- *
- * @param {Object}  page
- * @param {Object}  [details={}]                     - The shipping details object.
- * @param {string}  [details.country='US']           - The first name.
- * @param {string}  [details.firstName='Mister']     - The first name.
- * @param {string}  [details.lastName='Burns']       - The last name.
- * @param {string}  [details.address='156th Street'] - The address.
- * @param {string}  [details.zip='']                 - The ZIP code.
- * @param {string}  [details.city='']                - The city.
- * @param {string}  [details.state='']               - The State.
- * @param {string}  [details.suburb='']              - The Suburb.
- * @param {string}  [details.province='']            - The Province.
- * @param {string}  [details.district='']            - The District.
- * @param {string}  [details.department='']          - The Department.
- * @param {string}  [details.region='']              - The Region.
- * @param {string}  [details.parish='']              - The Parish.
- * @param {string}  [details.county='']              - The Country.
- * @param {string}  [details.prefecture='']          - The Prefecture.
- * @param {string}  [details.municipality='']        - The Municipality.
- * @param {boolean} [details.isPostalCode=false]     - If true, search by 'Postal code' instead of 'Zip Code'.
- */
-async function fillCheckoutBlocks( page, details = {}, type = 'shipping' ) {
-	const {
-		country = '',
-		firstName = '',
-		lastName = '',
-		address = '',
-		zip = '',
-		city = '',
-		state = '',
-		suburb = '',
-		province = '',
-		district = '',
-		department = '',
-		region = '',
-		parish = '',
-		county = '',
-		prefecture = '',
-		municipality = '',
-		phone = '',
-		isPostalCode = false,
-	} = details;
-
-	const label = {
-		shipping: 'Shipping address',
-		billing: 'Billing address',
-	};
-
-	async function setDynamicFieldType( field, addressElement ) {
-		const tagName = await field.evaluate( ( el ) =>
-			el.tagName.toLowerCase()
-		);
-
-		if ( tagName === 'select' ) {
-			await field.selectOption( addressElement );
-		} else {
-			await field.fill( addressElement );
-		}
-	}
-
-	await page
-		.getByRole( 'group', { name: label[ type ] } )
-		.getByLabel( 'First name' )
-		.fill( firstName );
-
-	await page
-		.getByRole( 'group', { name: label[ type ] } )
-		.getByLabel( 'Last name' )
-		.fill( lastName );
-
-	await page
-		.getByRole( 'group', { name: label[ type ] } )
-		.getByLabel( 'Address', { exact: true } )
-		.fill( address );
-
-	if ( country ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'Country' )
-			.selectOption( country );
-	}
-
-	if ( city ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'City' )
-			.fill( city );
-	}
-
-	if ( suburb ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'Suburb' )
-			.fill( suburb );
-	}
-
-	if ( province ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'Province' )
-			.selectOption( province );
-	}
-
-	if ( district ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'District' )
-			.selectOption( district );
-	}
-
-	if ( department ) {
-		await setDynamicFieldType(
-			await page
-				.getByRole( 'group', { name: label[ type ] } )
-				.getByLabel( 'Department' ),
-			department
-		);
-	}
-
-	if ( region ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'Region', { exact: true } )
-			.selectOption( region );
-	}
-
-	if ( parish ) {
-		await setDynamicFieldType(
-			await page
-				.getByRole( 'group', { name: label[ type ] } )
-				.getByLabel( 'Parish', { exact: false } ),
-			parish
-		);
-	}
-
-	if ( county ) {
-		await setDynamicFieldType(
-			await page
-				.getByRole( 'group', { name: label[ type ] } )
-				.getByLabel( 'County' ),
-			county
-		);
-	}
-
-	if ( prefecture ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'Prefecture' )
-			.selectOption( prefecture );
-	}
-
-	if ( municipality ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'Municipality' )
-			.fill( municipality );
-	}
-
-	if ( state ) {
-		const stateField = await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( 'State/County', { exact: false } )
-			.or(
-				await page
-					.getByRole( 'group', { name: label[ type ] } )
-					.getByLabel( 'State' )
-			);
-
-		await setDynamicFieldType( stateField, state );
-	}
-
-	if ( zip ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByLabel( isPostalCode ? 'Postal code' : 'ZIP Code' )
-			.fill( zip );
-	}
-
-	if ( phone ) {
-		await page
-			.getByRole( 'group', { name: label[ type ] } )
-			.getByRole( 'textbox', { name: 'Phone' } )
-			.fill( phone );
-	}
-}
-
-/**
- * Convenience function to fill Shipping Address fields.
- *
- * @param {Object} page
- * @param {*}      shippingDetails See arguments description for `fillCheckoutBlocks`.
- */
-export async function fillShippingCheckoutBlocks( page, shippingDetails = {} ) {
-	await fillCheckoutBlocks( page, shippingDetails, 'shipping' );
-}
-
-/**
- * Convenience function to fill Billing Address fields.
- *
- * @param {Object} page
- * @param {*}      billingDetails See arguments description for `fillCheckoutBlocks`.
- */
-export async function fillBillingCheckoutBlocks( page, billingDetails = {} ) {
-	await fillCheckoutBlocks( page, billingDetails, 'billing' );
-}
diff --git a/packages/js/e2e-utils-playwright/src/checkout.ts b/packages/js/e2e-utils-playwright/src/checkout.ts
new file mode 100644
index 0000000000..87ea4b791e
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/checkout.ts
@@ -0,0 +1,216 @@
+/**
+ * External dependencies
+ */
+import type { Page, Locator } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import type { CheckoutDetails, AddressType } from './types';
+
+// Re-export types for consumers
+export type { CheckoutDetails } from './types';
+
+const addressLabels: Record< AddressType, string > = {
+	shipping: 'Shipping address',
+	billing: 'Billing address',
+};
+
+/**
+ * Sets a field value based on its element type (select or input).
+ *
+ * @param field - The field locator
+ * @param value - The value to set
+ */
+async function setDynamicFieldType( field: Locator, value: string ) {
+	const tagName = await field.evaluate( ( el ) => el.tagName.toLowerCase() );
+
+	if ( tagName === 'select' ) {
+		await field.selectOption( value );
+	} else {
+		await field.fill( value );
+	}
+}
+
+/**
+ * Util helper made to fill the Checkout details in the block-based checkout.
+ *
+ * @param page    - Playwright page object
+ * @param details - The checkout details object
+ * @param type    - The address type ('shipping' or 'billing')
+ */
+async function fillCheckoutBlocks(
+	page: Page,
+	details: CheckoutDetails = {},
+	type: AddressType = 'shipping'
+) {
+	const {
+		country = '',
+		firstName = '',
+		lastName = '',
+		address = '',
+		zip = '',
+		city = '',
+		state = '',
+		suburb = '',
+		province = '',
+		district = '',
+		department = '',
+		region = '',
+		parish = '',
+		county = '',
+		prefecture = '',
+		municipality = '',
+		phone = '',
+		isPostalCode = false,
+	} = details;
+
+	const label = addressLabels[ type ];
+
+	await page
+		.getByRole( 'group', { name: label } )
+		.getByLabel( 'First name' )
+		.fill( firstName );
+
+	await page
+		.getByRole( 'group', { name: label } )
+		.getByLabel( 'Last name' )
+		.fill( lastName );
+
+	await page
+		.getByRole( 'group', { name: label } )
+		.getByLabel( 'Address', { exact: true } )
+		.fill( address );
+
+	if ( country ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'Country' )
+			.selectOption( country );
+	}
+
+	if ( city ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'City' )
+			.fill( city );
+	}
+
+	if ( suburb ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'Suburb' )
+			.fill( suburb );
+	}
+
+	if ( province ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'Province' )
+			.selectOption( province );
+	}
+
+	if ( district ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'District' )
+			.selectOption( district );
+	}
+
+	if ( department ) {
+		await setDynamicFieldType(
+			page
+				.getByRole( 'group', { name: label } )
+				.getByLabel( 'Department' ),
+			department
+		);
+	}
+
+	if ( region ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'Region', { exact: true } )
+			.selectOption( region );
+	}
+
+	if ( parish ) {
+		await setDynamicFieldType(
+			page
+				.getByRole( 'group', { name: label } )
+				.getByLabel( 'Parish', { exact: false } ),
+			parish
+		);
+	}
+
+	if ( county ) {
+		await setDynamicFieldType(
+			page.getByRole( 'group', { name: label } ).getByLabel( 'County' ),
+			county
+		);
+	}
+
+	if ( prefecture ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'Prefecture' )
+			.selectOption( prefecture );
+	}
+
+	if ( municipality ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'Municipality' )
+			.fill( municipality );
+	}
+
+	if ( state ) {
+		const stateField = page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( 'State/County', { exact: false } )
+			.or(
+				page.getByRole( 'group', { name: label } ).getByLabel( 'State' )
+			);
+
+		await setDynamicFieldType( stateField, state );
+	}
+
+	if ( zip ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByLabel( isPostalCode ? 'Postal code' : 'ZIP Code' )
+			.fill( zip );
+	}
+
+	if ( phone ) {
+		await page
+			.getByRole( 'group', { name: label } )
+			.getByRole( 'textbox', { name: 'Phone' } )
+			.fill( phone );
+	}
+}
+
+/**
+ * Convenience function to fill Shipping Address fields.
+ *
+ * @param page            - Playwright page object
+ * @param shippingDetails - See CheckoutDetails type for available fields
+ */
+export async function fillShippingCheckoutBlocks(
+	page: Page,
+	shippingDetails: CheckoutDetails = {}
+): Promise< void > {
+	await fillCheckoutBlocks( page, shippingDetails, 'shipping' );
+}
+
+/**
+ * Convenience function to fill Billing Address fields.
+ *
+ * @param page           - Playwright page object
+ * @param billingDetails - See CheckoutDetails type for available fields
+ */
+export async function fillBillingCheckoutBlocks(
+	page: Page,
+	billingDetails: CheckoutDetails = {}
+): Promise< void > {
+	await fillCheckoutBlocks( page, billingDetails, 'billing' );
+}
diff --git a/packages/js/e2e-utils-playwright/src/editor.js b/packages/js/e2e-utils-playwright/src/editor.js
deleted file mode 100644
index e3cf88d0aa..0000000000
--- a/packages/js/e2e-utils-playwright/src/editor.js
+++ /dev/null
@@ -1,142 +0,0 @@
-export const closeChoosePatternModal = async ( { page } ) => {
-	const closeModal = page
-		.locator( 'div' )
-		.filter( { hasText: 'Choose a pattern' } )
-		.getByLabel( 'Close' );
-	await page.addLocatorHandler( closeModal, async () => {
-		await closeModal.click();
-	} );
-};
-
-export const disableWelcomeModal = async ( { page } ) => {
-	// Further info: https://github.com/woocommerce/woocommerce/pull/45856/
-	await page.waitForLoadState( 'domcontentloaded' );
-
-	const isWelcomeGuideActive = await page.evaluate( () =>
-		window.wp.data
-			.select( 'core/edit-post' )
-			.isFeatureActive( 'welcomeGuide' )
-	);
-
-	if ( isWelcomeGuideActive ) {
-		await page.evaluate( () =>
-			window.wp.data
-				.dispatch( 'core/edit-post' )
-				.toggleFeature( 'welcomeGuide' )
-		);
-	}
-};
-
-export const openEditorSettings = async ( { page } ) => {
-	// Open Settings sidebar if closed
-	if ( await page.getByLabel( 'Editor Settings' ).isVisible() ) {
-		console.log( 'Editor Settings is open, skipping action.' );
-	} else {
-		await page.getByLabel( 'Settings', { exact: true } ).click();
-	}
-};
-
-/**
- * Returns the editor canvas frame for Gutenberg interactions.
- *
- * The Gutenberg editor content can be contained within an iframe in some contexts.
- * This helper function returns the content frame of the editor canvas iframe if it exists,
- * or falls back to the main page if the iframe isn't present.
- *
- * @param {import('@playwright/test').Page} page - The Playwright page object
- * @return {Promise<import('@playwright/test').FrameLocator|import('@playwright/test').Page>} The editor canvas frame or the original page
- */
-export const getCanvas = async ( page ) => {
-	return (
-		page.locator( 'iframe[name="editor-canvas"]' ).contentFrame() || page
-	);
-};
-
-export const goToPageEditor = async ( { page } ) => {
-	await page.goto( 'wp-admin/post-new.php?post_type=page' );
-	await disableWelcomeModal( { page } );
-	await closeChoosePatternModal( { page } );
-};
-
-export const goToPostEditor = async ( { page } ) => {
-	await page.goto( 'wp-admin/post-new.php' );
-	await disableWelcomeModal( { page } );
-};
-
-export const insertBlock = async ( page, blockName ) => {
-	// Focus on "Empty block" element before inserting a new block.
-	// Otherwise, Gutenberg nightly (v19.9-nightly) would display "{Block name} can't be inserted."
-	const emptyBlock = ( await getCanvas( page ) ).getByLabel( 'Empty block' );
-	if ( await emptyBlock.isVisible() ) {
-		await emptyBlock.click();
-	}
-
-	// With Gutenberg active we have Block Inserter name
-	await page
-		.getByRole( 'button', {
-			name: /Toggle block inserter|Block Inserter/,
-			expanded: false,
-		} )
-		.click();
-
-	await page.getByPlaceholder( 'Search', { exact: true } ).fill( blockName );
-	await page.getByRole( 'option', { name: blockName, exact: true } ).click();
-
-	await page
-		.getByRole( 'button', {
-			name: 'Close block inserter',
-		} )
-		.click();
-};
-
-export const insertBlockByShortcut = async ( page, blockName ) => {
-	const canvas = await getCanvas( page );
-	const emptyBlockField = canvas.getByText( 'Type / to choose a block' ).or(
-		canvas.getByRole( 'document', {
-			name: 'Empty block; start writing or type forward slash to choose a block',
-		} )
-	);
-	await emptyBlockField.click();
-	await emptyBlockField.pressSequentially( `/${ blockName }` );
-	await page.getByRole( 'option', { name: blockName, exact: true } ).click();
-};
-
-export const transformIntoBlocks = async ( page ) => {
-	const canvas = await getCanvas( page );
-
-	await canvas
-		.getByRole( 'button' )
-		.filter( { hasText: 'Transform into blocks' } )
-		.click();
-};
-
-export const publishPage = async ( page, pageTitle, isPost = false ) => {
-	await page
-		.getByRole( 'button', { name: 'Publish', exact: true } )
-		.dispatchEvent( 'click' );
-
-	const createPageResponse = page.waitForResponse( ( response ) => {
-		return (
-			response.url().includes( isPost ? '/posts/' : '/pages/' ) &&
-			response.ok() &&
-			response.request().method() === 'POST' &&
-			response
-				.json()
-				.then(
-					( json ) =>
-						json.title.rendered === pageTitle &&
-						json.status === 'publish'
-				)
-		);
-	} );
-
-	await page
-		.getByRole( 'region', { name: 'Editor publish' } )
-		.getByRole( 'button', { name: 'Publish', exact: true } )
-		.click();
-
-	// Validating that page was published via UI elements is not reliable,
-	// installed plugins (e.g. WooCommerce PayPal Payments) can interfere and add flakiness to the flow.
-	// In WC context, checking the API response is possibly the most reliable way to ensure the page was published.
-	await createPageResponse;
-};
diff --git a/packages/js/e2e-utils-playwright/src/editor.ts b/packages/js/e2e-utils-playwright/src/editor.ts
new file mode 100644
index 0000000000..e970b9f2d8
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/editor.ts
@@ -0,0 +1,256 @@
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import type { PageContext, EditorCanvas } from './types';
+
+// Re-export types for consumers
+export type { PageContext, EditorCanvas } from './types';
+
+/**
+ * WordPress data store interface for type checking.
+ */
+interface WpData {
+	select: ( store: string ) => {
+		isFeatureActive: ( feature: string ) => boolean;
+	};
+	dispatch: ( store: string ) => {
+		toggleFeature: ( feature: string ) => void;
+	};
+}
+
+type WindowWithWp = Window & { wp?: { data?: WpData } };
+
+/**
+ * Closes the "Choose a pattern" modal if present.
+ *
+ * @param context      - Object containing the Playwright page
+ * @param context.page - The Playwright page object
+ */
+export const closeChoosePatternModal = async ( {
+	page,
+}: PageContext ): Promise< void > => {
+	const closeModal = page
+		.locator( 'div' )
+		.filter( { hasText: 'Choose a pattern' } )
+		.getByLabel( 'Close' );
+	await page.addLocatorHandler( closeModal, async () => {
+		await closeModal.click();
+	} );
+};
+
+/**
+ * Disables the Gutenberg welcome modal.
+ *
+ * @param context      - Object containing the Playwright page
+ * @param context.page - The Playwright page object
+ */
+export const disableWelcomeModal = async ( {
+	page,
+}: PageContext ): Promise< void > => {
+	// Further info: https://github.com/woocommerce/woocommerce/pull/45856/
+	await page.waitForLoadState( 'domcontentloaded' );
+
+	const isWelcomeGuideActive = await page.evaluate( () =>
+		( window as unknown as WindowWithWp ).wp?.data
+			?.select( 'core/edit-post' )
+			?.isFeatureActive( 'welcomeGuide' )
+	);
+
+	if ( isWelcomeGuideActive ) {
+		await page.evaluate( () =>
+			( window as unknown as WindowWithWp ).wp?.data
+				?.dispatch( 'core/edit-post' )
+				?.toggleFeature( 'welcomeGuide' )
+		);
+	}
+};
+
+/**
+ * Opens the editor settings sidebar if closed.
+ *
+ * @param context      - Object containing the Playwright page
+ * @param context.page - The Playwright page object
+ */
+export const openEditorSettings = async ( {
+	page,
+}: PageContext ): Promise< void > => {
+	// Open Settings sidebar if closed
+	if ( await page.getByLabel( 'Editor Settings' ).isVisible() ) {
+		console.log( 'Editor Settings is open, skipping action.' );
+	} else {
+		await page.getByLabel( 'Settings', { exact: true } ).click();
+	}
+};
+
+/**
+ * Returns the editor canvas frame for Gutenberg interactions.
+ *
+ * The Gutenberg editor content can be contained within an iframe in some contexts.
+ * This helper function returns the content frame of the editor canvas iframe if it exists,
+ * or falls back to the main page if the iframe isn't present.
+ *
+ * @param page - The Playwright page object
+ * @return The editor canvas frame or the original page
+ */
+export const getCanvas = async ( page: Page ): Promise< EditorCanvas > => {
+	const iframeLocator = page.locator( 'iframe[name="editor-canvas"]' );
+	if ( ( await iframeLocator.count() ) > 0 ) {
+		return iframeLocator.contentFrame();
+	}
+	return page;
+};
+
+/**
+ * Navigates to the WordPress page editor.
+ *
+ * @param context      - Object containing the Playwright page
+ * @param context.page - The Playwright page object
+ */
+export const goToPageEditor = async ( {
+	page,
+}: PageContext ): Promise< void > => {
+	await page.goto( 'wp-admin/post-new.php?post_type=page' );
+	await disableWelcomeModal( { page } );
+	await closeChoosePatternModal( { page } );
+};
+
+/**
+ * Navigates to the WordPress post editor.
+ *
+ * @param context      - Object containing the Playwright page
+ * @param context.page - The Playwright page object
+ */
+export const goToPostEditor = async ( {
+	page,
+}: PageContext ): Promise< void > => {
+	await page.goto( 'wp-admin/post-new.php' );
+	await disableWelcomeModal( { page } );
+};
+
+/**
+ * Inserts a block using the block inserter.
+ *
+ * @param page      - The Playwright page object
+ * @param blockName - The name of the block to insert
+ */
+export const insertBlock = async (
+	page: Page,
+	blockName: string
+): Promise< void > => {
+	// Focus on "Empty block" element before inserting a new block.
+	// Otherwise, Gutenberg nightly (v19.9-nightly) would display "{Block name} can't be inserted."
+	const canvas = await getCanvas( page );
+	const emptyBlock = canvas.getByLabel( 'Empty block' );
+	if ( await emptyBlock.isVisible() ) {
+		await emptyBlock.click();
+	}
+
+	// With Gutenberg active we have Block Inserter name
+	await page
+		.getByRole( 'button', {
+			name: /Toggle block inserter|Block Inserter/,
+			expanded: false,
+		} )
+		.click();
+
+	await page.getByPlaceholder( 'Search', { exact: true } ).fill( blockName );
+	await page.getByRole( 'option', { name: blockName, exact: true } ).click();
+
+	await page
+		.getByRole( 'button', {
+			name: 'Close block inserter',
+		} )
+		.click();
+};
+
+/**
+ * Inserts a block using the slash command shortcut.
+ *
+ * @param page      - The Playwright page object
+ * @param blockName - The name of the block to insert
+ */
+export const insertBlockByShortcut = async (
+	page: Page,
+	blockName: string
+): Promise< void > => {
+	const canvas = await getCanvas( page );
+	const emptyBlockField = canvas.getByText( 'Type / to choose a block' ).or(
+		canvas.getByRole( 'document', {
+			name: 'Empty block; start writing or type forward slash to choose a block',
+		} )
+	);
+	await emptyBlockField.click();
+	await emptyBlockField.pressSequentially( `/${ blockName }` );
+	await page.getByRole( 'option', { name: blockName, exact: true } ).click();
+};
+
+/**
+ * Transforms classic content into blocks.
+ *
+ * @param page - The Playwright page object
+ */
+export const transformIntoBlocks = async ( page: Page ): Promise< void > => {
+	const canvas = await getCanvas( page );
+
+	await canvas
+		.getByRole( 'button' )
+		.filter( { hasText: 'Transform into blocks' } )
+		.click();
+};
+
+/**
+ * Response JSON structure for published pages/posts.
+ */
+interface PublishResponse {
+	title: {
+		rendered: string;
+	};
+	status: string;
+}
+
+/**
+ * Publishes a page or post.
+ *
+ * @param page      - The Playwright page object
+ * @param pageTitle - The title of the page/post being published
+ * @param isPost    - Whether this is a post (true) or page (false)
+ */
+export const publishPage = async (
+	page: Page,
+	pageTitle: string,
+	isPost = false
+): Promise< void > => {
+	await page
+		.getByRole( 'button', { name: 'Publish', exact: true } )
+		.dispatchEvent( 'click' );
+
+	const createPageResponse = page.waitForResponse( ( response ) => {
+		return (
+			response.url().includes( isPost ? '/posts/' : '/pages/' ) &&
+			response.ok() &&
+			response.request().method() === 'POST' &&
+			response
+				.json()
+				.then(
+					( json: PublishResponse ) =>
+						json.title.rendered === pageTitle &&
+						json.status === 'publish'
+				)
+		);
+	} );
+
+	await page
+		.getByRole( 'region', { name: 'Editor publish' } )
+		.getByRole( 'button', { name: 'Publish', exact: true } )
+		.click();
+
+	// Validating that page was published via UI elements is not reliable,
+	// installed plugins (e.g. WooCommerce PayPal Payments) can interfere and add flakiness to the flow.
+	// In WC context, checking the API response is possibly the most reliable way to ensure the page was published.
+	await createPageResponse;
+};
diff --git a/packages/js/e2e-utils-playwright/src/index.js b/packages/js/e2e-utils-playwright/src/index.js
deleted file mode 100644
index 1e258355e8..0000000000
--- a/packages/js/e2e-utils-playwright/src/index.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export * from './cart';
-export * from './checkout';
-export * from './editor';
-export * from './order';
-export * from './api-client';
diff --git a/packages/js/e2e-utils-playwright/src/index.ts b/packages/js/e2e-utils-playwright/src/index.ts
new file mode 100644
index 0000000000..c14224dd58
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/index.ts
@@ -0,0 +1,9 @@
+// Re-export all modules
+export * from './cart';
+export * from './checkout';
+export * from './editor';
+export * from './order';
+export * from './api-client';
+
+// Re-export types not already covered by module re-exports above
+export type { ApiClient, AddressType } from './types';
diff --git a/packages/js/e2e-utils-playwright/src/order.js b/packages/js/e2e-utils-playwright/src/order.js
deleted file mode 100644
index 9a99f48791..0000000000
--- a/packages/js/e2e-utils-playwright/src/order.js
+++ /dev/null
@@ -1,8 +0,0 @@
-export function getOrderIdFromUrl( page ) {
-	const regex = /order-received\/(\d+)/;
-	try {
-		return page.url().match( regex )[ 1 ];
-	} catch ( error ) {
-		return undefined;
-	}
-}
diff --git a/packages/js/e2e-utils-playwright/src/order.ts b/packages/js/e2e-utils-playwright/src/order.ts
new file mode 100644
index 0000000000..4153e0448f
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/order.ts
@@ -0,0 +1,16 @@
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+
+/**
+ * Extracts the order ID from the current page URL.
+ *
+ * @param page - Playwright page object
+ * @return The order ID or undefined if not found
+ */
+export function getOrderIdFromUrl( page: Page ): string | undefined {
+	const regex = /order-received\/(\d+)/;
+	const match = page.url().match( regex );
+	return match?.[ 1 ];
+}
diff --git a/packages/js/e2e-utils-playwright/src/test/order.test.js b/packages/js/e2e-utils-playwright/src/test/order.test.js
deleted file mode 100644
index 57423bdf8b..0000000000
--- a/packages/js/e2e-utils-playwright/src/test/order.test.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
- * Internal dependencies
- */
-import { getOrderIdFromUrl } from '../order';
-
-describe( 'getOrderIdFromUrl', () => {
-	it( 'should extract the order ID from a valid URL', () => {
-		const page = { url: () => 'https://example.com/order-received/12345/' };
-		const orderId = getOrderIdFromUrl( page );
-		expect( orderId ).toBe( '12345' );
-	} );
-
-	it( 'should return undefined if the URL does not contain an order ID', () => {
-		const page = { url: () => 'https://example.com/order-received/' };
-		const orderId = getOrderIdFromUrl( page );
-		expect( orderId ).toBeUndefined();
-	} );
-
-	it( 'should return undefined if the URL is not in the expected format', () => {
-		const page = { url: () => 'https://example.com/other-page/12345/' };
-		const orderId = getOrderIdFromUrl( page );
-		expect( orderId ).toBeUndefined();
-	} );
-} );
diff --git a/packages/js/e2e-utils-playwright/src/test/order.test.ts b/packages/js/e2e-utils-playwright/src/test/order.test.ts
new file mode 100644
index 0000000000..0a11e31199
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/test/order.test.ts
@@ -0,0 +1,32 @@
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import { getOrderIdFromUrl } from '../order';
+
+describe( 'getOrderIdFromUrl', () => {
+	it( 'should extract the order ID from a valid URL', () => {
+		const page = {
+			url: () => 'https://example.com/order-received/12345/',
+		} as unknown as Page;
+		expect( getOrderIdFromUrl( page ) ).toBe( '12345' );
+	} );
+
+	it( 'should return undefined if the URL does not contain an order ID', () => {
+		const page = {
+			url: () => 'https://example.com/order-received/',
+		} as unknown as Page;
+		expect( getOrderIdFromUrl( page ) ).toBeUndefined();
+	} );
+
+	it( 'should return undefined if the URL is not in the expected format', () => {
+		const page = {
+			url: () => 'https://example.com/other-page/12345/',
+		} as unknown as Page;
+		expect( getOrderIdFromUrl( page ) ).toBeUndefined();
+	} );
+} );
diff --git a/packages/js/e2e-utils-playwright/src/types.ts b/packages/js/e2e-utils-playwright/src/types.ts
new file mode 100644
index 0000000000..42d6e575be
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/src/types.ts
@@ -0,0 +1,161 @@
+/**
+ * External dependencies
+ */
+import type { Page, FrameLocator } from '@playwright/test';
+import type { AxiosResponse } from 'axios';
+
+/**
+ * Authentication configuration for Basic Auth.
+ *
+ * @property {'basic'} type     - Type of authentication ('basic')
+ * @property {string}  username - Username for basic authentication
+ * @property {string}  password - Password for basic authentication
+ */
+export interface BasicAuth {
+	type: 'basic';
+	username: string;
+	password: string;
+}
+
+/**
+ * Authentication configuration for OAuth 1.0a.
+ *
+ * @property {'oauth1'} type           - Type of authentication ('oauth1')
+ * @property {string}   consumerKey    - OAuth1 consumer key
+ * @property {string}   consumerSecret - OAuth1 consumer secret
+ */
+export interface OAuth1Auth {
+	type: 'oauth1';
+	consumerKey: string;
+	consumerSecret: string;
+}
+
+/**
+ * Union type for all supported authentication methods.
+ */
+export type Auth = BasicAuth | OAuth1Auth;
+
+/**
+ * API Client interface returned by createClient.
+ */
+export interface ApiClient {
+	/**
+	 * Make a GET request.
+	 *
+	 * @template T - The expected response data type
+	 * @param    path   - API endpoint path
+	 * @param    params - Query parameters
+	 * @param    debug  - Enable debug logging
+	 * @return Promise resolving to the response
+	 */
+	get< T = unknown >(
+		path: string,
+		params?: Record< string, unknown >,
+		debug?: boolean
+	): Promise< AxiosResponse< T > >;
+
+	/**
+	 * Make a POST request.
+	 *
+	 * @template T - The expected response data type
+	 * @param    path  - API endpoint path
+	 * @param    data  - Request body data
+	 * @param    debug - Enable debug logging
+	 * @return Promise resolving to the response
+	 */
+	post< T = unknown >(
+		path: string,
+		data?: Record< string, unknown >,
+		debug?: boolean
+	): Promise< AxiosResponse< T > >;
+
+	/**
+	 * Make a PUT request.
+	 *
+	 * @template T - The expected response data type
+	 * @param    path  - API endpoint path
+	 * @param    data  - Request body data
+	 * @param    debug - Enable debug logging
+	 * @return Promise resolving to the response
+	 */
+	put< T = unknown >(
+		path: string,
+		data?: Record< string, unknown >,
+		debug?: boolean
+	): Promise< AxiosResponse< T > >;
+
+	/**
+	 * Make a DELETE request.
+	 *
+	 * @template T - The expected response data type
+	 * @param    path   - API endpoint path
+	 * @param    params - Query parameters or request body
+	 * @param    debug  - Enable debug logging
+	 * @return Promise resolving to the response
+	 */
+	delete< T = unknown >(
+		path: string,
+		params?: Record< string, unknown >,
+		debug?: boolean
+	): Promise< AxiosResponse< T > >;
+}
+
+/**
+ * Checkout details for block-based checkout forms.
+ *
+ * @property {string}  [country]      - The country code (e.g., 'US')
+ * @property {string}  [firstName]    - The first name
+ * @property {string}  [lastName]     - The last name
+ * @property {string}  [address]      - The street address
+ * @property {string}  [zip]          - The ZIP or postal code
+ * @property {string}  [city]         - The city
+ * @property {string}  [state]        - The state
+ * @property {string}  [suburb]       - The suburb (for applicable countries)
+ * @property {string}  [province]     - The province (for applicable countries)
+ * @property {string}  [district]     - The district (for applicable countries)
+ * @property {string}  [department]   - The department (for applicable countries)
+ * @property {string}  [region]       - The region (for applicable countries)
+ * @property {string}  [parish]       - The parish (for applicable countries)
+ * @property {string}  [county]       - The county (for applicable countries)
+ * @property {string}  [prefecture]   - The prefecture (for applicable countries)
+ * @property {string}  [municipality] - The municipality (for applicable countries)
+ * @property {string}  [phone]        - The phone number
+ * @property {boolean} [isPostalCode] - If true, search by 'Postal code' instead of 'ZIP Code'
+ */
+export interface CheckoutDetails {
+	country?: string;
+	firstName?: string;
+	lastName?: string;
+	address?: string;
+	zip?: string;
+	city?: string;
+	state?: string;
+	suburb?: string;
+	province?: string;
+	district?: string;
+	department?: string;
+	region?: string;
+	parish?: string;
+	county?: string;
+	prefecture?: string;
+	municipality?: string;
+	phone?: string;
+	isPostalCode?: boolean;
+}
+
+/**
+ * Address type for checkout forms.
+ */
+export type AddressType = 'shipping' | 'billing';
+
+/**
+ * Page context for editor functions that receive a page object.
+ */
+export interface PageContext {
+	page: Page;
+}
+
+/**
+ * Canvas type - either FrameLocator for iframe-based editor or Page.
+ */
+export type EditorCanvas = FrameLocator | Page;
diff --git a/packages/js/e2e-utils-playwright/tsconfig-cjs.json b/packages/js/e2e-utils-playwright/tsconfig-cjs.json
new file mode 100644
index 0000000000..f9d6e810b3
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/tsconfig-cjs.json
@@ -0,0 +1,10 @@
+{
+	"extends": "../tsconfig-cjs",
+	"compilerOptions": {
+		"rootDir": "src",
+		"outDir": "build",
+		"typeRoots": ["./node_modules/@types"]
+	},
+	"include": ["src/**/*"],
+	"exclude": ["**/test/**"]
+}
diff --git a/packages/js/e2e-utils-playwright/tsconfig.json b/packages/js/e2e-utils-playwright/tsconfig.json
new file mode 100644
index 0000000000..5bb869c49e
--- /dev/null
+++ b/packages/js/e2e-utils-playwright/tsconfig.json
@@ -0,0 +1,13 @@
+{
+	"extends": "../tsconfig",
+	"compilerOptions": {
+		"rootDir": "src",
+		"outDir": "build-module",
+		"declaration": true,
+		"declarationMap": true,
+		"declarationDir": "./build-types",
+		"typeRoots": ["./node_modules/@types"]
+	},
+	"include": ["src/**/*"],
+	"exclude": ["**/test/**"]
+}
diff --git a/plugins/woocommerce/changelog/e2e-utils-playwright-ts-conversion b/plugins/woocommerce/changelog/e2e-utils-playwright-ts-conversion
new file mode 100644
index 0000000000..46f1a9c277
--- /dev/null
+++ b/plugins/woocommerce/changelog/e2e-utils-playwright-ts-conversion
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Convert e2e-utils-playwright package to TypeScript.
diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json
index 15d21fb460..edc851058a 100644
--- a/plugins/woocommerce/package.json
+++ b/plugins/woocommerce/package.json
@@ -959,6 +959,9 @@
 				"node_modules/@woocommerce/admin-library/build",
 				"node_modules/@woocommerce/block-library/build",
 				"node_modules/@woocommerce/classic-assets/build",
+				"node_modules/@woocommerce/e2e-utils-playwright/build",
+				"node_modules/@woocommerce/e2e-utils-playwright/build-module",
+				"node_modules/@woocommerce/e2e-utils-playwright/build-types",
 				"node_modules/@woocommerce/eslint-plugin/configs",
 				"node_modules/@woocommerce/eslint-plugin/rules",
 				"node_modules/@woocommerce/eslint-plugin/index.js"
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/editor.js b/plugins/woocommerce/tests/e2e-pw/utils/editor.js
index 25d548e634..6dbd19fec9 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/editor.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/editor.js
@@ -1,4 +1,4 @@
-const { getCanvas } = require( '@woocommerce/e2e-utils-playwright/src' );
+const { getCanvas } = require( '@woocommerce/e2e-utils-playwright' );

 const fillPageTitle = async ( page, title ) => {
 	// Close the Block Inserter if it's open.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 908b3a21aa..9c4be92a62 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -98,7 +98,7 @@ importers:
         version: 1.15.0
       postcss-loader:
         specifier: 4.3.x
-        version: 4.3.0(postcss@8.4.49)(webpack@5.97.1(@swc/core@1.3.100))
+        version: 4.3.0(postcss@8.4.49)(webpack@5.97.1)
       prettier:
         specifier: npm:wp-prettier@^2.8.5
         version: wp-prettier@2.8.5
@@ -177,7 +177,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.26.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -259,7 +259,7 @@ importers:
         version: 6.36.1-next.8b30e05b0.0
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       css-loader:
         specifier: 6.11.x
         version: 6.11.0(webpack@5.97.1(@swc/core@1.3.100))
@@ -292,7 +292,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -602,7 +602,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -845,7 +845,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -1108,18 +1108,36 @@ importers:
         specifier: ^2.2.6
         version: 2.2.6
     devDependencies:
-      '@babel/cli':
-        specifier: 7.25.7
-        version: 7.25.7(@babel/core@7.25.7)
-      '@babel/core':
-        specifier: 7.25.7
-        version: 7.25.7
-      '@wordpress/babel-preset-default':
-        specifier: next
-        version: 8.36.1-next.8b30e05b0.0
+      '@playwright/test':
+        specifier: ^1.57.0
+        version: 1.57.0
+      '@types/jest':
+        specifier: 29.5.x
+        version: 29.5.14
+      '@types/node':
+        specifier: 20.x.x
+        version: 20.17.8
+      '@woocommerce/eslint-plugin':
+        specifier: workspace:*
+        version: link:../eslint-plugin
+      '@woocommerce/internal-js-tests':
+        specifier: workspace:*
+        version: link:../internal-js-tests
+      eslint:
+        specifier: ^8.55.0
+        version: 8.55.0
       jest:
         specifier: 29.5.x
         version: 29.5.0(@types/node@20.17.8)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@20.17.8)(typescript@5.7.2))
+      jest-cli:
+        specifier: 29.5.x
+        version: 29.5.0(@types/node@20.17.8)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@20.17.8)(typescript@5.7.2))
+      ts-jest:
+        specifier: 29.1.x
+        version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@20.17.8)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@20.17.8)(typescript@5.7.2)))(typescript@5.7.2)
+      typescript:
+        specifier: 5.7.x
+        version: 5.7.2
       wireit:
         specifier: 0.14.12
         version: 0.14.12
@@ -1500,7 +1518,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -1701,7 +1719,7 @@ importers:
         version: 6.36.1-next.8b30e05b0.0
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       css-loader:
         specifier: 6.11.x
         version: 6.11.0(webpack@5.97.1(@swc/core@1.3.100))
@@ -1828,7 +1846,7 @@ importers:
         version: 2.9.2(webpack@5.97.1(@swc/core@1.3.100))
       postcss-loader:
         specifier: 4.3.x
-        version: 4.3.0(postcss@8.4.49)(webpack@5.97.1(@swc/core@1.3.100))
+        version: 4.3.0(postcss@8.4.49)(webpack@5.97.1)
       sass-loader:
         specifier: 10.5.x
         version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
@@ -2148,7 +2166,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -2377,7 +2395,7 @@ importers:
         version: 6.36.1-next.8b30e05b0.0
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       css-loader:
         specifier: 6.11.x
         version: 6.11.0(webpack@5.97.1(@swc/core@1.3.100))
@@ -2416,7 +2434,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -2737,7 +2755,7 @@ importers:
         version: 6.36.1-next.8b30e05b0.0
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       css-loader:
         specifier: 6.11.x
         version: 6.11.0(webpack@5.97.1(@swc/core@1.3.100))
@@ -2773,7 +2791,7 @@ importers:
         version: 5.0.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       ts-jest:
         specifier: 29.1.x
         version: 29.1.1(@babel/core@7.25.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.25.7))(jest@29.5.0(@types/node@22.9.1)(babel-plugin-macros@3.1.0)(node-notifier@8.0.2)(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)))(typescript@5.7.2)
@@ -3515,7 +3533,7 @@ importers:
         version: 3.3.7
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       css-loader:
         specifier: 6.11.x
         version: 6.11.0(webpack@5.97.1(@swc/core@1.3.100))
@@ -3614,7 +3632,7 @@ importers:
         version: 1.69.5
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       stylelint:
         specifier: ^14.16.1
         version: 14.16.1
@@ -4072,7 +4090,7 @@ importers:
         version: 5.2.2(webpack@5.97.1)
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       core-js:
         specifier: 3.25.0
         version: 3.25.0
@@ -4201,7 +4219,7 @@ importers:
         version: 4.1.1
       sass-loader:
         specifier: 10.5.x
-        version: 10.5.0(sass@1.69.5)(webpack@5.97.1)
+        version: 10.5.0(sass@1.69.5)(webpack@5.97.1(@swc/core@1.3.100))
       storybook:
         specifier: ^7.6.4
         version: 7.6.4(encoding@0.1.13)
@@ -4547,7 +4565,7 @@ importers:
         version: link:../../packages/js/eslint-plugin
       copy-webpack-plugin:
         specifier: 13.0.x
-        version: 13.0.0(webpack@5.97.1)
+        version: 13.0.0(webpack@5.97.1(@swc/core@1.3.100))
       eslint:
         specifier: ^8.55.0
         version: 8.55.0
@@ -26577,7 +26595,7 @@ snapshots:
       '@types/istanbul-lib-coverage': 2.0.6
       collect-v8-coverage: 1.0.2

-  '@jest/test-sequencer@26.6.3':
+  '@jest/test-sequencer@26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2))':
     dependencies:
       '@jest/test-result': 26.6.2
       graceful-fs: 4.2.11
@@ -26585,7 +26603,11 @@ snapshots:
       jest-runner: 26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2))
       jest-runtime: 26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2))
     transitivePeerDependencies:
+      - bufferutil
+      - canvas
       - supports-color
+      - ts-node
+      - utf-8-validate

   '@jest/test-sequencer@29.7.0':
     dependencies:
@@ -31159,7 +31181,7 @@ snapshots:
   '@tufjs/models@1.0.4':
     dependencies:
       '@tufjs/canonical-json': 1.0.0
-      minimatch: 9.0.3
+      minimatch: 9.0.5

   '@tybys/wasm-util@0.9.0':
     dependencies:
@@ -32204,7 +32226,7 @@ snapshots:

   '@typescript-eslint/utils@5.56.0(eslint@8.55.0)(typescript@5.7.2)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
+      '@eslint-community/eslint-utils': 4.7.0(eslint@8.55.0)
       '@types/json-schema': 7.0.15
       '@types/semver': 7.5.6
       '@typescript-eslint/scope-manager': 5.56.0
@@ -32234,7 +32256,7 @@ snapshots:

   '@typescript-eslint/utils@6.21.0(eslint@8.55.0)(typescript@5.7.2)':
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
+      '@eslint-community/eslint-utils': 4.7.0(eslint@8.55.0)
       '@types/json-schema': 7.0.15
       '@types/semver': 7.5.6
       '@typescript-eslint/scope-manager': 6.21.0
@@ -32548,7 +32570,7 @@ snapshots:

   '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.97.1)':
     dependencies:
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
+      webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)
       webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.1)(webpack-dev-server@4.15.1)(webpack@5.97.1)

   '@webpack-cli/info@1.5.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.7.0)(webpack@5.89.0))':
@@ -32558,7 +32580,7 @@ snapshots:

   '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.97.1)':
     dependencies:
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
+      webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)
       webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.1)(webpack-dev-server@4.15.1)(webpack@5.97.1)

   '@webpack-cli/serve@1.7.0(webpack-cli@4.10.0(webpack-bundle-analyzer@4.7.0)(webpack@5.89.0))':
@@ -32567,10 +32589,10 @@ snapshots:

   '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.1)(webpack@5.97.1)':
     dependencies:
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
+      webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)
       webpack-cli: 5.1.4(webpack-bundle-analyzer@4.9.1)(webpack-dev-server@4.15.1)(webpack@5.97.1)
     optionalDependencies:
-      webpack-dev-server: 4.15.1(webpack-cli@5.1.4)(webpack@5.97.1)
+      webpack-dev-server: 4.15.1(debug@4.3.4)(webpack-cli@5.1.4)(webpack@5.97.1)

   '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.97.1)':
     dependencies:
@@ -36825,7 +36847,7 @@ snapshots:
       expect-puppeteer: 4.4.0
       filenamify: 4.3.0
       jest: 26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2))
-      jest-circus: 26.6.3
+      jest-circus: 26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2))
       jest-dev-server: 5.0.3
       jest-environment-node: 26.6.2
       markdownlint: 0.23.1
@@ -39853,15 +39875,6 @@ snapshots:
       tinyglobby: 0.2.12
       webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)

-  copy-webpack-plugin@13.0.0(webpack@5.97.1):
-    dependencies:
-      glob-parent: 6.0.2
-      normalize-path: 3.0.0
-      schema-utils: 4.3.0
-      serialize-javascript: 6.0.2
-      tinyglobby: 0.2.12
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
-
   core-js-compat@3.39.0:
     dependencies:
       browserslist: 4.24.4
@@ -42010,7 +42023,7 @@ snapshots:

   eslint@8.55.0:
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0)
+      '@eslint-community/eslint-utils': 4.7.0(eslint@8.55.0)
       '@eslint-community/regexpp': 4.10.0
       '@eslint/eslintrc': 2.1.4
       '@eslint/js': 8.55.0
@@ -43991,7 +44004,7 @@ snapshots:

   ignore-walk@6.0.4:
     dependencies:
-      minimatch: 9.0.3
+      minimatch: 9.0.5

   ignore@4.0.6: {}

@@ -44601,7 +44614,7 @@ snapshots:
       jest-util: 29.7.0
       p-limit: 3.1.0

-  jest-circus@26.6.3:
+  jest-circus@26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)):
     dependencies:
       '@babel/traverse': 7.25.9
       '@jest/environment': 26.6.2
@@ -44625,7 +44638,11 @@ snapshots:
       stack-utils: 2.0.6
       throat: 5.0.0
     transitivePeerDependencies:
+      - bufferutil
+      - canvas
       - supports-color
+      - ts-node
+      - utf-8-validate

   jest-circus@29.5.0:
     dependencies:
@@ -44789,7 +44806,7 @@ snapshots:
   jest-config@26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2)):
     dependencies:
       '@babel/core': 7.25.7
-      '@jest/test-sequencer': 26.6.3
+      '@jest/test-sequencer': 26.6.3(ts-node@10.9.2(@swc/core@1.3.100)(@types/node@22.9.1)(typescript@5.7.2))
       '@jest/types': 26.6.2
       babel-jest: 26.6.3(@babel/core@7.25.7)
       chalk: 4.1.2
@@ -48472,16 +48489,6 @@ snapshots:
       semver: 7.6.3
       webpack: 4.47.0(webpack-cli@5.1.4)

-  postcss-loader@4.3.0(postcss@8.4.49)(webpack@5.97.1(@swc/core@1.3.100)):
-    dependencies:
-      cosmiconfig: 7.1.0
-      klona: 2.0.6
-      loader-utils: 2.0.4
-      postcss: 8.4.49
-      schema-utils: 3.3.0
-      semver: 7.6.3
-      webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)
-
   postcss-loader@4.3.0(postcss@8.4.49)(webpack@5.97.1):
     dependencies:
       cosmiconfig: 7.1.0
@@ -48490,7 +48497,7 @@ snapshots:
       postcss: 8.4.49
       schema-utils: 3.3.0
       semver: 7.6.3
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
+      webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)

   postcss-loader@6.2.1(postcss@8.4.32)(webpack@5.89.0):
     dependencies:
@@ -50677,17 +50684,6 @@ snapshots:
     optionalDependencies:
       sass: 1.69.5

-  sass-loader@10.5.0(sass@1.69.5)(webpack@5.97.1):
-    dependencies:
-      klona: 2.0.6
-      loader-utils: 2.0.4
-      neo-async: 2.6.2
-      schema-utils: 3.3.0
-      semver: 7.6.3
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
-    optionalDependencies:
-      sass: 1.69.5
-
   sass-loader@12.6.0(sass@1.69.5)(webpack@5.89.0):
     dependencies:
       klona: 2.0.6
@@ -53388,11 +53384,11 @@ snapshots:
       import-local: 3.1.0
       interpret: 3.1.1
       rechoir: 0.8.0
-      webpack: 5.97.1(@swc/core@1.3.100)(esbuild@0.18.20)(webpack-cli@5.1.4)
+      webpack: 5.97.1(@swc/core@1.3.100)(webpack-cli@5.1.4)
       webpack-merge: 5.10.0
     optionalDependencies:
       webpack-bundle-analyzer: 4.9.1
-      webpack-dev-server: 4.15.1(webpack-cli@5.1.4)(webpack@5.97.1)
+      webpack-dev-server: 4.15.1(debug@4.3.4)(webpack-cli@5.1.4)(webpack@5.97.1)

   webpack-cli@5.1.4(webpack@5.97.1):
     dependencies: