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 };