Commit b39c11e758 for woocommerce

commit b39c11e7588eff4211aec47d2899b4eaa2721b55
Author: theAverageDev (Luca Tumedei) <luca@theaveragedev.com>
Date:   Thu Feb 19 12:21:04 2026 +0100

    Convert e2e-pw CJS utils and barrel index to TypeScript (#63318)

diff --git a/plugins/woocommerce/changelog/e2e-pw-ts-conversion-09 b/plugins/woocommerce/changelog/e2e-pw-ts-conversion-09
new file mode 100644
index 0000000000..8a03f1e6c0
--- /dev/null
+++ b/plugins/woocommerce/changelog/e2e-pw-ts-conversion-09
@@ -0,0 +1,4 @@
+Significance: patch
+Type: dev
+
+Convert e2e-pw utils to TypeScript.
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/cli.js b/plugins/woocommerce/tests/e2e-pw/utils/cli.js
deleted file mode 100644
index 4f27730efc..0000000000
--- a/plugins/woocommerce/tests/e2e-pw/utils/cli.js
+++ /dev/null
@@ -1,15 +0,0 @@
-const { promisify } = require( 'util' );
-
-const execAsync = promisify( require( 'child_process' ).exec );
-
-const wpCLI = async ( command ) => {
-	const { stdout, stderr } = await execAsync(
-		`pnpm exec wp-env run tests-cli -- ${ command }`
-	);
-
-	return { stdout, stderr };
-};
-
-module.exports = {
-	wpCLI,
-};
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/cli.ts b/plugins/woocommerce/tests/e2e-pw/utils/cli.ts
new file mode 100644
index 0000000000..1ca90ba025
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e-pw/utils/cli.ts
@@ -0,0 +1,17 @@
+/**
+ * External dependencies
+ */
+import { promisify } from 'util';
+import { exec } from 'child_process';
+
+const execAsync = promisify( exec );
+
+const wpCLI = async ( command: string ) => {
+	const { stdout, stderr } = await execAsync(
+		`pnpm exec wp-env run tests-cli -- ${ command }`
+	);
+
+	return { stdout, stderr };
+};
+
+export { wpCLI };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/coming-soon.js b/plugins/woocommerce/tests/e2e-pw/utils/coming-soon.js
deleted file mode 100644
index b82e195cc8..0000000000
--- a/plugins/woocommerce/tests/e2e-pw/utils/coming-soon.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const { request } = require( '@playwright/test' );
-const { setOption } = require( './options' );
-
-const setComingSoon = async ( { baseURL, enabled } ) => {
-	try {
-		await setOption( request, baseURL, 'woocommerce_coming_soon', enabled );
-	} catch ( error ) {
-		console.log( error );
-	}
-};
-
-module.exports = {
-	setComingSoon,
-};
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/coming-soon.ts b/plugins/woocommerce/tests/e2e-pw/utils/coming-soon.ts
new file mode 100644
index 0000000000..5caaa52e5a
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e-pw/utils/coming-soon.ts
@@ -0,0 +1,25 @@
+/**
+ * External dependencies
+ */
+import { request } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import { setOption } from './options';
+
+const setComingSoon = async ( {
+	baseURL,
+	enabled,
+}: {
+	baseURL: string;
+	enabled: string;
+} ) => {
+	try {
+		await setOption( request, baseURL, 'woocommerce_coming_soon', enabled );
+	} catch ( error ) {
+		console.error( error );
+	}
+};
+
+export { setComingSoon };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/editor.js b/plugins/woocommerce/tests/e2e-pw/utils/editor.ts
similarity index 69%
rename from plugins/woocommerce/tests/e2e-pw/utils/editor.js
rename to plugins/woocommerce/tests/e2e-pw/utils/editor.ts
index 6dbd19fec9..8ea2860907 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/editor.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/editor.ts
@@ -1,6 +1,10 @@
-const { getCanvas } = require( '@woocommerce/e2e-utils-playwright' );
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+import { getCanvas } from '@woocommerce/e2e-utils-playwright';

-const fillPageTitle = async ( page, title ) => {
+const fillPageTitle = async ( page: Page, title: string ) => {
 	// Close the Block Inserter if it's open.
 	// Since Gutenberg 19.9 it is expanded by default.
 	if (
@@ -21,6 +25,4 @@ const fillPageTitle = async ( page, title ) => {
 	await block_title.fill( title );
 };

-module.exports = {
-	fillPageTitle,
-};
+export { fillPageTitle };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/features.js b/plugins/woocommerce/tests/e2e-pw/utils/features.ts
similarity index 58%
rename from plugins/woocommerce/tests/e2e-pw/utils/features.js
rename to plugins/woocommerce/tests/e2e-pw/utils/features.ts
index 75f365f4c4..28bb4001d1 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/features.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/features.ts
@@ -1,7 +1,20 @@
-const { encodeCredentials } = require( './plugin-utils' );
-const { admin } = require( '../test-data/data' );
+/**
+ * External dependencies
+ */
+import type { APIRequest } from '@playwright/test';

-const setFeatureFlag = async ( request, baseURL, flagName, enable ) => {
+/**
+ * Internal dependencies
+ */
+import { encodeCredentials } from './plugin-utils';
+import { admin } from '../test-data/data';
+
+const setFeatureFlag = async (
+	request: APIRequest,
+	baseURL: string,
+	flagName: string,
+	enable: boolean
+) => {
 	const apiContext = await request.newContext( {
 		baseURL,
 		extraHTTPHeaders: {
@@ -19,7 +32,7 @@ const setFeatureFlag = async ( request, baseURL, flagName, enable ) => {
 	} );
 };

-const resetFeatureFlags = async ( request, baseURL ) => {
+const resetFeatureFlags = async ( request: APIRequest, baseURL: string ) => {
 	const apiContext = await request.newContext( {
 		baseURL,
 		extraHTTPHeaders: {
@@ -36,7 +49,4 @@ const resetFeatureFlags = async ( request, baseURL ) => {
 	} );
 };

-module.exports = {
-	setFeatureFlag,
-	resetFeatureFlags,
-};
+export { setFeatureFlag, resetFeatureFlags };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/helpers.js b/plugins/woocommerce/tests/e2e-pw/utils/helpers.ts
similarity index 53%
rename from plugins/woocommerce/tests/e2e-pw/utils/helpers.js
rename to plugins/woocommerce/tests/e2e-pw/utils/helpers.ts
index 721aa50640..4aed00d925 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/helpers.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/helpers.ts
@@ -1,9 +1,10 @@
-const crypto = require( 'crypto' );
+/**
+ * External dependencies
+ */
+import crypto from 'crypto';

 const random = ( size = 4 ) => {
 	return crypto.randomBytes( size ).toString( 'hex' );
 };

-module.exports = {
-	random,
-};
+export { random };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/index.js b/plugins/woocommerce/tests/e2e-pw/utils/index.js
deleted file mode 100644
index 5ab4a9cbf0..0000000000
--- a/plugins/woocommerce/tests/e2e-pw/utils/index.js
+++ /dev/null
@@ -1,21 +0,0 @@
-const api = require( './api' );
-const variableProducts = require( './variable-products' );
-const features = require( './features' );
-const tours = require( './tours' );
-const login = require( './login' );
-const editor = require( './editor' );
-const helpers = require( './helpers' );
-const comingSoon = require( './coming-soon' );
-const cli = require( './cli' );
-
-module.exports = {
-	api,
-	variableProducts,
-	features,
-	tours,
-	login,
-	editor,
-	helpers,
-	comingSoon,
-	cli,
-};
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/index.ts b/plugins/woocommerce/tests/e2e-pw/utils/index.ts
new file mode 100644
index 0000000000..636bf90558
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e-pw/utils/index.ts
@@ -0,0 +1,24 @@
+/**
+ * Internal dependencies
+ */
+import * as api from './api';
+import * as variableProducts from './variable-products';
+import * as features from './features';
+import * as tours from './tours';
+import * as login from './login';
+import * as editor from './editor';
+import * as helpers from './helpers';
+import * as comingSoon from './coming-soon';
+import * as cli from './cli';
+
+export {
+	api,
+	variableProducts,
+	features,
+	tours,
+	login,
+	editor,
+	helpers,
+	comingSoon,
+	cli,
+};
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/login.js b/plugins/woocommerce/tests/e2e-pw/utils/login.ts
similarity index 72%
rename from plugins/woocommerce/tests/e2e-pw/utils/login.js
rename to plugins/woocommerce/tests/e2e-pw/utils/login.ts
index ae27b203e0..f0231001ab 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/login.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/login.ts
@@ -1,5 +1,19 @@
-const { expect } = require( '../fixtures/fixtures' );
-const logIn = async ( page, username, password, assertSuccess = true ) => {
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import { expect } from '../fixtures/fixtures';
+
+const logIn = async (
+	page: Page,
+	username: string,
+	password: string,
+	assertSuccess = true
+) => {
 	await page
 		.getByLabel( 'Username or Email Address' )
 		.click( { delay: 100 } );
@@ -16,9 +30,9 @@ const logIn = async ( page, username, password, assertSuccess = true ) => {
 };

 const logInFromMyAccount = async (
-	page,
-	username,
-	password,
+	page: Page,
+	username: string,
+	password: string,
 	assertSuccess = true
 ) => {
 	await page.locator( '#username' ).fill( username );
@@ -37,4 +51,4 @@ const logInFromMyAccount = async (
 	}
 };

-module.exports = { logIn, logInFromMyAccount };
+export { logIn, logInFromMyAccount };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/payments-settings.js b/plugins/woocommerce/tests/e2e-pw/utils/payments-settings.js
deleted file mode 100644
index 6c82c87564..0000000000
--- a/plugins/woocommerce/tests/e2e-pw/utils/payments-settings.js
+++ /dev/null
@@ -1,14 +0,0 @@
-const { request } = require( '@playwright/test' );
-const { deleteOption } = require( './options' );
-
-const resetGatewayOrder = async ( baseURL ) => {
-	try {
-		await deleteOption( request, baseURL, 'woocommerce_gateway_order' );
-	} catch ( error ) {
-		console.log( error );
-	}
-};
-
-module.exports = {
-	resetGatewayOrder,
-};
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/payments-settings.ts b/plugins/woocommerce/tests/e2e-pw/utils/payments-settings.ts
new file mode 100644
index 0000000000..c454522aec
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e-pw/utils/payments-settings.ts
@@ -0,0 +1,19 @@
+/**
+ * External dependencies
+ */
+import { request } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import { deleteOption } from './options';
+
+const resetGatewayOrder = async ( baseURL: string ) => {
+	try {
+		await deleteOption( request, baseURL, 'woocommerce_gateway_order' );
+	} catch ( error ) {
+		console.error( error );
+	}
+};
+
+export { resetGatewayOrder };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/product-block-editor.js b/plugins/woocommerce/tests/e2e-pw/utils/product-block-editor.ts
similarity index 75%
rename from plugins/woocommerce/tests/e2e-pw/utils/product-block-editor.js
rename to plugins/woocommerce/tests/e2e-pw/utils/product-block-editor.ts
index 9cc9850cfb..616753bd0d 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/product-block-editor.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/product-block-editor.ts
@@ -1,4 +1,15 @@
-const updateProduct = async ( { page, expect } ) => {
+/**
+ * External dependencies
+ */
+import type { Expect, Page } from '@playwright/test';
+
+const updateProduct = async ( {
+	page,
+	expect,
+}: {
+	page: Page;
+	expect: Expect;
+} ) => {
 	await page.getByRole( 'button', { name: 'Update' } ).click();
 	// Verify product was updated
 	await expect( page.getByLabel( 'Dismiss this notice' ) ).toContainText(
@@ -6,7 +17,7 @@ const updateProduct = async ( { page, expect } ) => {
 	);
 };

-const disableVariableProductBlockTour = async ( { page } ) => {
+const disableVariableProductBlockTour = async ( { page }: { page: Page } ) => {
 	// Further info: https://github.com/woocommerce/woocommerce/pull/45856/
 	await page.waitForLoadState( 'domcontentloaded' );

@@ -36,7 +47,4 @@ const disableVariableProductBlockTour = async ( { page } ) => {
 	await page.reload();
 };

-module.exports = {
-	updateProduct,
-	disableVariableProductBlockTour,
-};
+export { updateProduct, disableVariableProductBlockTour };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/simple-products.js b/plugins/woocommerce/tests/e2e-pw/utils/simple-products.ts
similarity index 75%
rename from plugins/woocommerce/tests/e2e-pw/utils/simple-products.js
rename to plugins/woocommerce/tests/e2e-pw/utils/simple-products.ts
index 8876e2fd3e..9641a16f12 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/simple-products.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/simple-products.ts
@@ -1,11 +1,15 @@
-const { expect } = require( '@playwright/test' );
+/**
+ * External dependencies
+ */
+import type { Page } from '@playwright/test';
+import { expect } from '@playwright/test';

 /**
  * This function simulates the clicking of the "Add New" link under the "product" section in the menu.
  *
  * @param {import('@playwright/test').Page} page
  */
-async function clickAddNewMenuItem( page ) {
+async function clickAddNewMenuItem( page: Page ) {
 	await page
 		.locator( '#menu-posts-product' )
 		.getByRole( 'link', { name: 'Add New' } )
@@ -17,7 +21,7 @@ async function clickAddNewMenuItem( page ) {
  *
  * @param {import('@playwright/test').Page} page
  */
-async function expectOldProductEditor( page ) {
+async function expectOldProductEditor( page: Page ) {
 	await expect(
 		page.getByRole( 'heading', { name: 'Product data' } )
 	).toBeVisible();
@@ -28,7 +32,7 @@ async function expectOldProductEditor( page ) {
  *
  * @param {import('@playwright/test').Page} page
  */
-async function expectBlockProductEditor( page ) {
+async function expectBlockProductEditor( page: Page ) {
 	await expect(
 		page.locator( '.woocommerce-product-header__inner h1' )
 	).toContainText( 'Add new product' );
@@ -40,14 +44,14 @@ async function expectBlockProductEditor( page ) {
  * @param {string}                          tabName
  * @param {import('@playwright/test').Page} page
  */
-async function clickOnTab( tabName, page ) {
+async function clickOnTab( tabName: string, page: Page ) {
 	await page
 		.locator( '.woocommerce-product-tabs' )
 		.getByRole( 'tab', { name: tabName } )
 		.click();
 }

-module.exports = {
+export {
 	expectBlockProductEditor,
 	expectOldProductEditor,
 	clickAddNewMenuItem,
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/tours.js b/plugins/woocommerce/tests/e2e-pw/utils/tours.ts
similarity index 78%
rename from plugins/woocommerce/tests/e2e-pw/utils/tours.js
rename to plugins/woocommerce/tests/e2e-pw/utils/tours.ts
index 137fa0e10a..2f81b3105e 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/tours.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/tours.ts
@@ -1,4 +1,12 @@
-const { admin } = require( '../test-data/data' );
+/**
+ * External dependencies
+ */
+import type { Page, APIRequestContext } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import { admin } from '../test-data/data';

 const base64String = Buffer.from(
 	`${ admin.username }:${ admin.password }`
@@ -14,7 +22,10 @@ const headers = {
  * @param {import('@playwright/test').APIRequestContext} request Request context from calling function.
  * @param {boolean}                                      enable  Set to `true` if you want to enable the block product tour. `false` if otherwise.
  */
-const toggleBlockProductTour = async ( request, enable ) => {
+const toggleBlockProductTour = async (
+	request: APIRequestContext,
+	enable: boolean
+) => {
 	const url = './wp-json/wc-admin/options';
 	const params = { _locale: 'user' };
 	const toggleValue = enable ? 'no' : 'yes';
@@ -27,7 +38,7 @@ const toggleBlockProductTour = async ( request, enable ) => {
 	} );
 };

-const toggleVariableProductTour = async ( page, enable ) => {
+const toggleVariableProductTour = async ( page: Page, enable: boolean ) => {
 	await page.waitForLoadState( 'domcontentloaded' );

 	// Get the current user data
@@ -56,4 +67,4 @@ const toggleVariableProductTour = async ( page, enable ) => {
 	await page.reload();
 };

-module.exports = { toggleBlockProductTour, toggleVariableProductTour };
+export { toggleBlockProductTour, toggleVariableProductTour };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/variable-products.js b/plugins/woocommerce/tests/e2e-pw/utils/variable-products.ts
similarity index 72%
rename from plugins/woocommerce/tests/e2e-pw/utils/variable-products.js
rename to plugins/woocommerce/tests/e2e-pw/utils/variable-products.ts
index 3a71d7dc44..63966d13b9 100644
--- a/plugins/woocommerce/tests/e2e-pw/utils/variable-products.js
+++ b/plugins/woocommerce/tests/e2e-pw/utils/variable-products.ts
@@ -1,9 +1,29 @@
-const api = require( './api' );
+/**
+ * External dependencies
+ */
+import type { Browser } from '@playwright/test';
+
+/**
+ * Internal dependencies
+ */
+import * as api from './api';
+
+export interface ProductAttribute {
+	name: string;
+	visible: boolean;
+	variation: boolean;
+	options: string[];
+}
+
+export interface Variation {
+	regular_price: string;
+	attributes: { name: string; option: string }[];
+}

 /**
  * Array to hold all product ID's to be deleted after test.
  */
-const productIds = [];
+const productIds: number[] = [];

 /**
  * The `attributes` property to be used in the request payload for creating a variable product through the REST API.
@@ -94,9 +114,9 @@ const sampleVariations = [
  * Create a variable product using the WooCommerce REST API.
  *
  * @param {{ name: string, visible: boolean, variation: boolean, options: string[] }[]} attributes List of attributes. See [Product - Attributes properties](https://woocommerce.github.io/woocommerce-rest-api-docs/#product-attributes-properties).
- * @returns {Promise<number>} ID of the created variable product
+ * @return {Promise<number>} ID of the created variable product
  */
-async function createVariableProduct( attributes = [] ) {
+async function createVariableProduct( attributes: ProductAttribute[] = [] ) {
 	const randomNum = Math.floor( Math.random() * 1000 );
 	const payload = {
 		name: `Unbranded Granite Shirt ${ randomNum }`,
@@ -122,9 +142,9 @@ async function deleteProductsAddedByTests() {
  * Enable or disable the variable product tour through JavaScript.
  *
  * @param {import('@playwright/test').Browser} browser
- * @param {boolean} show Whether to show the variable product tour or not.
+ * @param {boolean}                            show    Whether to show the variable product tour or not.
  */
-async function showVariableProductTour( browser, show ) {
+async function showVariableProductTour( browser: Browser, show: boolean ) {
 	const productPageURL = 'wp-admin/post-new.php?post_type=product';
 	const addProductPage = await browser.newPage();

@@ -146,6 +166,7 @@ async function showVariableProductTour( browser, show ) {

 	// Save the updated user preferences
 	await addProductPage.evaluate(
+		// eslint-disable-next-line @typescript-eslint/no-shadow
 		async ( { userId, updatedWooCommerceMeta } ) => {
 			await window.wp.data.dispatch( 'core' ).saveUser( {
 				id: userId,
@@ -163,19 +184,22 @@ async function showVariableProductTour( browser, show ) {
  * Generate all possible variations from the given attributes.
  *
  * @param {{ name: string, visible: boolean, variation: boolean, options: string[] }[]} attributes
- * @returns All possible variations from the given attributes
+ * @return All possible variations from the given attributes
  */
-function generateVariationsFromAttributes( attributes ) {
-	const combine = ( runningList, nextAttribute ) => {
-		const variations = [];
-		let newVar;
-
-		if ( ! Array.isArray( runningList[ 0 ] ) ) {
-			runningList = [ runningList ];
-		}
-
-		for ( const partialVariation of runningList ) {
-			if ( runningList.length === 1 ) {
+function generateVariationsFromAttributes( attributes: ProductAttribute[] ) {
+	const combine = (
+		runningList: string[] | string[][],
+		nextAttribute: string[]
+	): string[][] => {
+		const variations: string[][] = [];
+		let newVar: string[];
+
+		const normalized: string[][] = Array.isArray( runningList[ 0 ] )
+			? ( runningList as string[][] )
+			: [ runningList as string[] ];
+
+		for ( const partialVariation of normalized ) {
+			if ( normalized.length === 1 ) {
 				for ( const startingAttribute of partialVariation ) {
 					for ( const nextAttrValue of nextAttribute ) {
 						newVar = [ startingAttribute, nextAttrValue ];
@@ -193,7 +217,7 @@ function generateVariationsFromAttributes( attributes ) {
 		return variations;
 	};

-	let allVariations = attributes[ 0 ].options;
+	let allVariations: string[] | string[][] = attributes[ 0 ].options;

 	for ( let i = 1; i < attributes.length; i++ ) {
 		const nextAttribute = attributes[ i ].options;
@@ -207,15 +231,15 @@ function generateVariationsFromAttributes( attributes ) {
 /**
  * Create variations through the WooCommerce REST API.
  *
- * @param {number} productId Product ID to add variations to.
+ * @param {number}                                                                  productId  Product ID to add variations to.
  * @param {{regular_price: string, attributes: {name: string, option: string}[]}[]} variations List of variations to create.
- * @returns {Promise<number[]>} Array of variation ID's created.
+ * @return {Promise<number[]>} Array of variation ID's created.
  */
-async function createVariations( productId, variations ) {
+async function createVariations( productId: number, variations: Variation[] ) {
 	return await api.create.productVariations( productId, variations );
 }

-module.exports = {
+export {
 	createVariableProduct,
 	deleteProductsAddedByTests,
 	generateVariationsFromAttributes,
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js b/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js
deleted file mode 100644
index c36a21cd25..0000000000
--- a/plugins/woocommerce/tests/e2e-pw/utils/wordpress.js
+++ /dev/null
@@ -1,41 +0,0 @@
-const { promisify } = require( 'util' );
-const execAsync = promisify( require( 'child_process' ).exec );
-
-const getVersionWPLatestMinusOne = async ( { core, github } ) => {
-	const URL_WP_STABLE_VERSION_CHECK =
-		'https://api.wordpress.org/core/stable-check/1.0/';
-
-	const response = await github.request( URL_WP_STABLE_VERSION_CHECK );
-
-	const body = response.data;
-	const allVersions = Object.keys( body );
-	const previousStableVersions = allVersions
-		.filter( ( version ) => body[ version ] === 'outdated' )
-		.sort()
-		.reverse();
-	const latestMajorAndMinorNumbers = allVersions
-		.find( ( version ) => body[ version ] === 'latest' )
-		.match( /^\d+.\d+/ )[ 0 ];
-
-	const latestMinus1 = previousStableVersions.find(
-		( version ) => ! version.startsWith( latestMajorAndMinorNumbers )
-	);
-
-	core.setOutput( 'version', latestMinus1 );
-};
-
-const getInstalledWordPressVersion = async () => {
-	try {
-		const { stdout } = await execAsync(
-			`pnpm exec wp-env run tests-cli -- wp core version`
-		);
-
-		return Number.parseFloat( stdout.trim() );
-	} catch ( error ) {
-		throw new Error(
-			`Error getting WordPress version: ${ error.message }`
-		);
-	}
-};
-
-module.exports = { getVersionWPLatestMinusOne, getInstalledWordPressVersion };
diff --git a/plugins/woocommerce/tests/e2e-pw/utils/wordpress.ts b/plugins/woocommerce/tests/e2e-pw/utils/wordpress.ts
new file mode 100644
index 0000000000..cd86614b01
--- /dev/null
+++ b/plugins/woocommerce/tests/e2e-pw/utils/wordpress.ts
@@ -0,0 +1,70 @@
+/**
+ * External dependencies
+ */
+import { promisify } from 'util';
+import { exec } from 'child_process';
+
+const execAsync = promisify( exec );
+
+const getVersionWPLatestMinusOne = async ( {
+	core,
+	github,
+}: {
+	core: { setOutput: ( name: string, value: string ) => void };
+	github: {
+		request: (
+			url: string
+		) => Promise< { data: Record< string, string > } >;
+	};
+} ) => {
+	const URL_WP_STABLE_VERSION_CHECK =
+		'https://api.wordpress.org/core/stable-check/1.0/';
+
+	const response = await github.request( URL_WP_STABLE_VERSION_CHECK );
+
+	const body = response.data;
+	const allVersions = Object.keys( body );
+	const previousStableVersions = allVersions
+		.filter( ( version ) => body[ version ] === 'outdated' )
+		.sort()
+		.reverse();
+	const latestVersion = allVersions.find(
+		( version ) => body[ version ] === 'latest'
+	);
+	if ( ! latestVersion ) {
+		throw new Error( 'No latest WordPress version found in API response' );
+	}
+	const match = latestVersion.match( /^\d+\.\d+/ );
+	if ( ! match ) {
+		throw new Error( `Unexpected version format: ${ latestVersion }` );
+	}
+	const latestMajorAndMinorNumbers = match[ 0 ];
+
+	const latestMinus1 = previousStableVersions.find(
+		( version ) => ! version.startsWith( latestMajorAndMinorNumbers )
+	);
+
+	if ( ! latestMinus1 ) {
+		throw new Error(
+			'Unable to find the previous stable WordPress version'
+		);
+	}
+
+	core.setOutput( 'version', latestMinus1 );
+};
+
+const getInstalledWordPressVersion = async () => {
+	try {
+		const { stdout } = await execAsync(
+			`pnpm exec wp-env run tests-cli -- wp core version`
+		);
+
+		return Number.parseFloat( stdout.trim() );
+	} catch ( error ) {
+		throw new Error(
+			`Error getting WordPress version: ${ error.message }`
+		);
+	}
+};
+
+export { getVersionWPLatestMinusOne, getInstalledWordPressVersion };