Commit e5d7cac62f6 for woocommerce
commit e5d7cac62f6b8ed6ec89a67678a46967dc45dd52
Author: Vlad Olaru <vlad.olaru@automattic.com>
Date: Thu Sep 24 18:16:54 2026 +0300
Revert "[tests] Demote order admin coverage below E2E, keeping fifteen browser titles" (#69054)
revert: Revert "[tests] Demote order admin coverage below E2E, keeping fifteen browser titles" (#68637)
This reverts commit b917fdcb33b7ccc9a5b3f2355bc1f0cea7292a83.
Rubik asked for the E2E migration's test changes in its areas to be
reverted until the team can review them. #68637 is one of the 11 PRs on
the revert list Rubik agreed on 2026-09-24.
The revert brings back the browser tests the PR removed or cut down and
removes the lower-layer tests it added in their place. Only test code
and changelog entries change; nothing ships.
Two files stay as they are. The PR also hardened MetaDataUtilTest and
COTMigrationUtilTest so they can fail, which has nothing to do with the
order admin screens, and #68747 has since built on MetaDataUtilTest.
Reverting those hunks would weaken both tests and undo #68747's change.
Refs TESTOPS-288
Refs #68637
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
diff --git a/plugins/woocommerce/changelog/testops-288-orders-admin b/plugins/woocommerce/changelog/testops-288-orders-admin
deleted file mode 100644
index 447af902b20..00000000000
--- a/plugins/woocommerce/changelog/testops-288-orders-admin
+++ /dev/null
@@ -1,4 +0,0 @@
-Significance: patch
-Type: dev
-Comment: Move order admin coverage below E2E; twenty-two browser titles become fifteen.
-
diff --git a/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts b/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts
index c4c536330ac..752fc5c763b 100644
--- a/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/order/create-order.spec.ts
@@ -17,6 +17,14 @@ const taxClasses = [
name: 'Tax Class Simple',
slug: 'tax-class-simple',
},
+ {
+ name: 'Tax Class Variable',
+ slug: 'tax-class-variable',
+ },
+ {
+ name: 'Tax Class External',
+ slug: 'tax-class-external',
+ },
];
const taxRates = [
{
@@ -24,6 +32,16 @@ const taxRates = [
rate: '10.0000',
class: 'tax-class-simple',
},
+ {
+ name: 'Tax Rate Variable',
+ rate: '20.0000',
+ class: 'tax-class-variable',
+ },
+ {
+ name: 'Tax Rate External',
+ rate: '30.0000',
+ class: 'tax-class-external',
+ },
];
async function getOrderIdFromPage( page: Page ) {
// get order ID from the page
@@ -37,11 +55,7 @@ async function getOrderIdFromPage( page: Page ) {
async function addProductToOrder( page: Page, product, quantity: number ) {
await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
await page.getByRole( 'button', { name: 'Add product(s)' } ).click();
- const productSearch = page.locator(
- '.select2-container--open input.select2-search__field'
- );
- await expect( productSearch ).toBeVisible();
- await productSearch.fill( product.name );
+ await page.locator( 'span > .select2-search__field' ).fill( product.name );
await page.getByRole( 'option', { name: product.name } ).first().click();
const quantityField = page
@@ -147,20 +161,130 @@ const test = baseTest.extend( {
force: true,
} );
},
-} );
-// This describe carries `tags.HPOS`, and the `e2e-hpos-disabled` project greps on that tag
-// with `DISABLE_HPOS=1` set. So these titles also run against legacy post storage, where
-// `admin.php?page=wc-orders` is not registered and the editor lives under `post.php`.
-const newOrderUrl = () =>
- process.env.DISABLE_HPOS === '1'
- ? 'wp-admin/post-new.php?post_type=shop_order'
- : 'wp-admin/admin.php?page=wc-orders&action=new';
+ variableProduct: async ( { restApi }, use ) => {
+ let product = {};
+
+ const variations = [
+ {
+ regular_price: '100',
+ attributes: [
+ {
+ name: 'Size',
+ option: 'Small',
+ },
+ {
+ name: 'Colour',
+ option: 'Yellow',
+ },
+ ],
+ tax_class: 'Tax Class Variable',
+ },
+ {
+ regular_price: '100',
+ attributes: [
+ {
+ name: 'Size',
+ option: 'Medium',
+ },
+ {
+ name: 'Colour',
+ option: 'Magenta',
+ },
+ ],
+ tax_class: 'Tax Class Variable',
+ },
+ ];
+
+ await restApi
+ .post( `${ WC_API_PATH }/products`, {
+ name: `Product variable ${ random() }`,
+ type: 'variable',
+ tax_class: 'Tax Class Variable',
+ } )
+ .then( ( response ) => {
+ product = response.data;
+ } );
+
+ for ( const variation of variations ) {
+ await restApi.post(
+ `${ WC_API_PATH }/products/${ product.id }/variations`,
+ variation
+ );
+ }
+
+ await use( product );
-const editOrderUrl = ( orderId: number | string ) =>
- process.env.DISABLE_HPOS === '1'
- ? `wp-admin/post.php?post=${ orderId }&action=edit`
- : `wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }`;
+ // Cleanup
+ await restApi.delete( `${ WC_API_PATH }/products/${ product.id }`, {
+ force: true,
+ } );
+ },
+
+ externalProduct: async ( { restApi }, use ) => {
+ let product = {};
+
+ await restApi
+ .post( `${ WC_API_PATH }/products`, {
+ name: `Product external ${ random() }`,
+ regular_price: '800',
+ tax_class: 'Tax Class External',
+ external_url: 'https://wordpress.org/plugins/woocommerce',
+ type: 'external',
+ button_text: 'Buy now',
+ } )
+ .then( ( response ) => {
+ product = response.data;
+ } );
+
+ await use( product );
+
+ // Cleanup
+ await restApi.delete( `${ WC_API_PATH }/products/${ product.id }`, {
+ force: true,
+ } );
+ },
+
+ groupedProduct: async ( { restApi }, use ) => {
+ let product = {};
+ let subProductAId: number;
+ let subProductBId: number;
+
+ await restApi
+ .post( `${ WC_API_PATH }/products`, {
+ name: 'Add-on A',
+ regular_price: '11.95',
+ } )
+ .then( ( response: { data: { id: number } } ) => {
+ subProductAId = response.data.id;
+ } );
+ await restApi
+ .post( `${ WC_API_PATH }/products`, {
+ name: 'Add-on B',
+ regular_price: '18.97',
+ } )
+ .then( ( response: { data: { id: number } } ) => {
+ subProductBId = response.data.id;
+ } );
+ await restApi
+ .post( `${ WC_API_PATH }/products`, {
+ name: `Product grouped ${ random() }`,
+ regular_price: '29.99',
+ grouped_products: [ subProductAId, subProductBId ],
+ type: 'grouped',
+ } )
+ .then( ( response ) => {
+ product = response.data;
+ } );
+
+ await use( product );
+
+ // Cleanup
+ await restApi.delete( `${ WC_API_PATH }/products/${ product.id }`, {
+ force: true,
+ } );
+ },
+} );
test.describe(
'WooCommerce Orders > Add new order',
@@ -204,11 +328,103 @@ test.describe(
}
} );
+ test( 'can create a simple guest order', async ( {
+ page,
+ simpleProduct,
+ order,
+ } ) => {
+ await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
+ order.id = await getOrderIdFromPage( page );
+
+ await page
+ .locator( '#order_status' )
+ .selectOption( 'wc-processing' );
+
+ // Enter billing information
+ await page
+ .getByRole( 'heading', { name: 'Billing Edit' } )
+ .getByRole( 'link' )
+ .click();
+ await page
+ .getByRole( 'textbox', { name: 'First name' } )
+ .fill( 'Bart' );
+ await page
+ .getByRole( 'textbox', { name: 'Last name' } )
+ .fill( 'Simpson' );
+ await page
+ .getByRole( 'textbox', { name: 'Company' } )
+ .fill( 'Kwik-E-Mart' );
+ await page
+ .getByRole( 'textbox', { name: 'Address line 1' } )
+ .fill( '742 Evergreen Terrace' );
+ await page
+ .getByRole( 'textbox', { name: 'City' } )
+ .fill( 'Springfield' );
+ await page
+ .getByRole( 'textbox', { name: 'Postcode' } )
+ .fill( '12345' );
+ // eslint-disable-next-line playwright/no-conditional-in-test
+ if (
+ await page
+ .getByRole( 'textbox', { name: 'Select an option…' } )
+ .isVisible()
+ ) {
+ await page
+ .getByRole( 'textbox', { name: 'Select an option…' } )
+ .click();
+ await page.getByRole( 'option', { name: 'Florida' } ).click();
+ }
+ await page
+ .getByRole( 'textbox', { name: 'Email address' } )
+ .fill( 'elbarto@example.com' );
+ await page
+ .getByRole( 'textbox', { name: 'Phone' } )
+ .fill( '555-555-5555' );
+ await page
+ .getByRole( 'textbox', { name: 'Transaction ID' } )
+ .fill( '1234567890' );
+
+ // Enter shipping information
+ await page
+ .getByRole( 'heading', { name: 'Shipping Edit' } )
+ .getByRole( 'link' )
+ .click();
+ page.on( 'dialog', ( dialog ) => dialog.accept() );
+ await page
+ .getByRole( 'link', { name: 'Copy billing address' } )
+ .click();
+ await page
+ .getByPlaceholder( 'Customer notes about the order' )
+ .fill( 'Only asked for a slushie' );
+
+ // Add a product
+ await addProductToOrder( page, simpleProduct, 2 );
+
+ // Create the order
+ await page.getByRole( 'button', { name: 'Create' } ).click();
+ await expect( page.getByText( 'Order updated' ) ).toBeVisible();
+
+ // Confirm the details
+ await expect(
+ page.getByText(
+ 'Billing Edit Load billing address Bart SimpsonKwik-E-Mart742 Evergreen'
+ )
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ 'Shipping Edit Load shipping address Copy billing address Bart SimpsonKwik-E-'
+ )
+ ).toBeVisible();
+ await expect(
+ page.locator( 'table' ).filter( { hasText: 'Paid: $200.00' } )
+ ).toBeVisible();
+ } );
+
test( 'can add a product without an extra click or rogue search box', async ( {
page,
simpleProduct,
} ) => {
- await page.goto( newOrderUrl() );
+ await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
// Open the Add products modal.
await page.getByRole( 'button', { name: 'Add item(s)' } ).click();
@@ -272,12 +488,11 @@ test.describe(
test( 'can create an order for an existing customer', async ( {
page,
- restApi,
simpleProduct,
customer,
order,
} ) => {
- await page.goto( newOrderUrl() );
+ await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
order.id = await getOrderIdFromPage( page );
// Select customer
@@ -291,110 +506,147 @@ test.describe(
} )
.click();
- await expect( page.locator( '#_billing_first_name' ) ).toHaveValue(
- 'Sideshow'
- );
- await expect( page.locator( '#_billing_address_1' ) ).toHaveValue(
- '123 Fake St'
- );
- await expect( page.locator( '#_shipping_first_name' ) ).toHaveValue(
- 'Sideshow'
- );
- await expect( page.locator( '#_shipping_address_1' ) ).toHaveValue(
- '321 Fake St'
- );
+ // Add a product
+ await addProductToOrder( page, simpleProduct, 2 );
- await page.locator( '#_billing_address_1' ).fill( '124 Fake St' );
- page.on( 'dialog', ( dialog ) => dialog.accept() );
+ // Create the order
+ await page.getByRole( 'button', { name: 'Create' } ).click();
+ await expect( page.getByText( 'Order updated' ) ).toBeVisible();
+
+ // Confirm the details
+ await expect(
+ page.getByText(
+ 'Billing Edit Load billing address Sideshow BobDie Bart Die123 Fake'
+ )
+ ).toBeVisible();
+ await expect(
+ page.getByText(
+ 'Shipping Edit Load shipping address Copy billing address Sideshow BobDie Bart'
+ )
+ ).toBeVisible();
+
+ // View customer profile
+ await page.getByRole( 'link', { name: 'Profile →' } ).click();
+ await expect(
+ page.getByRole( 'heading', {
+ name: `Edit User ${ customer.username }`,
+ } )
+ ).toBeVisible();
+
+ // Go back to the order
+ await page.goto(
+ `wp-admin/admin.php?page=wc-orders&action=edit&id=${ order.id }`
+ );
await page
- .getByRole( 'link', { name: 'Copy billing address' } )
+ .getByRole( 'link', {
+ name: 'View other orders',
+ } )
.click();
- await expect( page.locator( '#_shipping_address_1' ) ).toHaveValue(
- '124 Fake St'
- );
+ await expect(
+ page.locator( 'h1.wp-heading-inline' )
+ ).toContainText( 'Orders' );
+ await expect( page.getByRole( 'row' ) ).toHaveCount( 3 ); // 1 order and header and footer rows
+ } );
+
+ test( 'can create new order', async ( { page, order } ) => {
+ await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
+ await expect(
+ page.locator( 'h1.wp-heading-inline' )
+ ).toContainText( 'Add new order' );
+ order.id = await getOrderIdFromPage( page );
await page
.locator( '#order_status' )
.selectOption( 'wc-processing' );
- await page
- .getByPlaceholder( 'Customer notes about the order' )
- .fill( 'Leave the order with the prison guard' );
-
- await addProductToOrder( page, simpleProduct, 2 );
- await page
- .getByRole( 'button', { name: 'Recalculate', exact: true } )
- .click();
- await expect( page.locator( 'th.line_tax' ) ).toHaveText(
- 'Tax Rate Simple'
- );
+ await page.locator( 'input[name=order_date]' ).fill( '2018-12-13' );
+ await page.locator( 'input[name=order_date_hour]' ).fill( '18' );
+ await page.locator( 'input[name=order_date_minute]' ).fill( '55' );
- await page.getByRole( 'button', { name: 'Create' } ).click();
- await expect( page.getByText( 'Order updated' ) ).toBeVisible();
+ await page.locator( 'button.save_order' ).click();
- await page.goto( editOrderUrl( order.id ) );
- // The order data meta box links the customer's profile and their other orders.
- await expect(
- page.getByRole( 'link', { name: 'Profile →' } )
- ).toHaveAttribute(
- 'href',
- new RegExp( `user-edit\\.php\\?user_id=${ customer.id }$` )
- );
await expect(
- page.getByRole( 'link', { name: 'View other orders →' } )
- ).toHaveAttribute(
- 'href',
- new RegExp(
- `edit\\.php\\?post_status=all&post_type=shop_order&_customer_user=${ customer.id }$`
+ page.locator(
+ 'div.updated.notice.notice-success.is-dismissible',
+ {
+ has: page.locator( 'p' ),
+ }
)
- );
- await expect( page.locator( '#_billing_address_1' ) ).toHaveValue(
- '124 Fake St'
- );
- await expect( page.locator( '#_shipping_address_1' ) ).toHaveValue(
- '124 Fake St'
- );
+ ).toContainText( 'Order updated.' );
await expect( page.locator( '#order_status' ) ).toHaveValue(
'wc-processing'
);
await expect(
- page.getByPlaceholder( 'Customer notes about the order' )
- ).toHaveValue( 'Leave the order with the prison guard' );
- await expect(
- page.locator( 'td.name > a' ).filter( {
- hasText: simpleProduct.name,
+ page.locator( 'div.note_content' ).filter( {
+ hasText:
+ 'Order status changed from Pending payment to Processing.',
} )
).toBeVisible();
- await expect( page.locator( 'th.line_tax' ) ).toHaveText(
- 'Tax Rate Simple'
- );
+ } );
- const response = await restApi.get(
- `${ WC_API_PATH }/orders/${ order.id }`
- );
- const persistedOrder = response.data;
- expect( persistedOrder.customer_id ).toBe( customer.id );
- expect( persistedOrder.status ).toBe( 'processing' );
- expect( persistedOrder.customer_note ).toBe(
- 'Leave the order with the prison guard'
- );
- expect( persistedOrder.billing.address_1 ).toBe( '124 Fake St' );
- expect( persistedOrder.shipping.address_1 ).toBe( '124 Fake St' );
- expect( persistedOrder.line_items ).toHaveLength( 1 );
- expect( persistedOrder.line_items[ 0 ].product_id ).toBe(
- simpleProduct.id
- );
- expect( persistedOrder.line_items[ 0 ].quantity ).toBe( 2 );
- expect( Number( persistedOrder.line_items[ 0 ].total ) ).toBe(
- 200
- );
- expect( persistedOrder.tax_lines ).toHaveLength( 1 );
- expect( persistedOrder.tax_lines[ 0 ].label ).toBe(
- 'Tax Rate Simple'
- );
- expect( Number( persistedOrder.tax_lines[ 0 ].tax_total ) ).toBe(
- 20
- );
- expect( Number( persistedOrder.total ) ).toBe( 220 );
+ test( 'can create new complex order with multiple product types & tax classes', async ( {
+ page,
+ simpleProduct,
+ variableProduct,
+ externalProduct,
+ groupedProduct,
+ order,
+ } ) => {
+ await page.goto( 'wp-admin/admin.php?page=wc-orders&action=new' );
+ order.id = await getOrderIdFromPage( page );
+
+ // open modal for adding line items
+ await page.locator( 'button.add-line-item' ).click();
+ await page.locator( 'button.add-order-item' ).click();
+
+ // search for each product to add
+ for ( const [ index, product ] of [
+ simpleProduct,
+ variableProduct,
+ groupedProduct,
+ externalProduct,
+ ].entries() ) {
+ if ( index > 0 ) {
+ await page.getByText( 'Search for a product…' ).click();
+ }
+ await page
+ .locator( 'span > .select2-search__field' )
+ .fill( product.name );
+ await page
+ .getByRole( 'option', { name: product.name } )
+ .first()
+ .click();
+ }
+
+ await page.locator( 'button#btn-ok' ).click();
+
+ // assert that products added
+ await expect(
+ page.locator( 'td.name > a >> nth=0' )
+ ).toContainText( simpleProduct.name );
+ await expect(
+ page.locator( 'td.name > a >> nth=1' )
+ ).toContainText( variableProduct.name );
+ await expect(
+ page.locator( 'td.name > a >> nth=2' )
+ ).toContainText( groupedProduct.name );
+ await expect(
+ page.locator( 'td.name > a >> nth=3' )
+ ).toContainText( externalProduct.name );
+
+ // Recalculate taxes
+ page.on( 'dialog', ( dialog ) => dialog.accept() );
+ await page
+ .getByRole( 'button', { name: 'Recalculate', exact: true } )
+ .click();
+
+ // verify tax names
+ let i = 0;
+ for ( const taxRate of taxRates ) {
+ await expect(
+ page.locator( `th.line_tax >> nth=${ i }` )
+ ).toHaveText( taxRate.name );
+ i++;
+ }
} );
}
);
diff --git a/plugins/woocommerce/tests/e2e/tests/order/order-coupon.spec.ts b/plugins/woocommerce/tests/e2e/tests/order/order-coupon.spec.ts
index 7bc8d21fb00..858f7fdd809 100644
--- a/plugins/woocommerce/tests/e2e/tests/order/order-coupon.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/order/order-coupon.spec.ts
@@ -118,7 +118,9 @@ test.describe(
exact: true,
} )
).toBeVisible();
+ } );
+ test( 'can remove a coupon', async ( { page } ) => {
await page.goto(
`wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }`
);
diff --git a/plugins/woocommerce/tests/e2e/tests/order/order-edit.spec.ts b/plugins/woocommerce/tests/e2e/tests/order/order-edit.spec.ts
index 90b6007f0c1..58d5dd90c28 100644
--- a/plugins/woocommerce/tests/e2e/tests/order/order-edit.spec.ts
+++ b/plugins/woocommerce/tests/e2e/tests/order/order-edit.spec.ts
@@ -14,17 +14,11 @@ import { wpCLI } from '../../utils/cli';
test.use( { storageState: ADMIN_STATE_PATH } );
-const orderEditUrl = ( id: number ) =>
- process.env.DISABLE_HPOS === '1'
- ? `wp-admin/post.php?post=${ id }&action=edit`
- : `wp-admin/admin.php?page=wc-orders&action=edit&id=${ id }`;
-const ordersListUrl = ( id: number ) =>
- process.env.DISABLE_HPOS === '1'
- ? `wp-admin/edit.php?post_type=shop_order&s=${ id }`
- : `wp-admin/admin.php?page=wc-orders&s=${ id }`;
-
test.describe( 'Edit order', { tag: [ tags.SERVICES, tags.HPOS ] }, () => {
- let orderId: number, secondOrderId: number, customerId: number;
+ let orderId: number,
+ secondOrderId: number,
+ orderToCancel: number,
+ customerId: number;
const username = `big.archie.${ Date.now() }`;
test.beforeAll( async ( { restApi } ) => {
@@ -42,6 +36,13 @@ test.describe( 'Edit order', { tag: [ tags.SERVICES, tags.HPOS ] }, () => {
.then( ( response: { data: { id: number } } ) => {
secondOrderId = response.data.id;
} );
+ await restApi
+ .post( `${ WC_API_PATH }/orders`, {
+ status: 'processing',
+ } )
+ .then( ( response: { data: { id: number } } ) => {
+ orderToCancel = response.data.id;
+ } );
await restApi
.post( `${ WC_API_PATH }/customers`, {
email: `${ username }@email.addr`,
@@ -85,29 +86,56 @@ test.describe( 'Edit order', { tag: [ tags.SERVICES, tags.HPOS ] }, () => {
await restApi.delete( `${ WC_API_PATH }/orders/${ secondOrderId }`, {
force: true,
} );
+ await restApi.delete( `${ WC_API_PATH }/orders/${ orderToCancel }`, {
+ force: true,
+ } );
await restApi.delete( `${ WC_API_PATH }/customers/${ customerId }`, {
force: true,
} );
} );
- test( 'can persist order status and date', async ( { page } ) => {
- await page.goto( orderEditUrl( orderId ) );
+ test( 'can view single order', async ( { page } ) => {
+ if ( process.env.DISABLE_HPOS === '1' ) {
+ await page.goto( 'wp-admin/edit.php?post_type=shop_order' );
+ } else {
+ await page.goto( 'wp-admin/admin.php?page=wc-orders' );
+ }
- await page.locator( 'input[name=order_date]' ).fill( '2018-12-14' );
+ // confirm we're on the orders page
+ await expect(
+ page.locator( 'h1.components-text, h1.wp-heading-inline' )
+ ).toContainText( 'Orders' );
+ // open order we created
+ await page.goto(
+ `wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }`
+ );
+
+ // make sure we're on the order details page
+ await expect( page.locator( 'h1.wp-heading-inline' ) ).toContainText(
+ /Edit [oO]rder/
+ );
+ } );
+
+ test( 'can update order status', async ( { page } ) => {
+ // open order we created
+ await page.goto(
+ `wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }`
+ );
+
+ // update order status to Completed
await page.locator( '#order_status' ).selectOption( 'wc-completed' );
await page.locator( 'button.save_order' ).click();
+ // verify order status changed and note added
await expect( page.locator( '#order_status' ) ).toHaveValue(
'wc-completed'
);
- await expect( page.locator( 'input[name=order_date]' ) ).toHaveValue(
- '2018-12-14'
- );
await expect(
page.locator( '#woocommerce-order-notes .note_content >> nth=0' )
).toContainText( 'Order status changed from Processing to Completed.' );
- await page.goto( ordersListUrl( orderId ) );
+ // load the orders listing and confirm order is completed
+ await page.goto( 'wp-admin/admin.php?page=wc-orders' );
await expect(
page
@@ -116,6 +144,59 @@ test.describe( 'Edit order', { tag: [ tags.SERVICES, tags.HPOS ] }, () => {
).toBeVisible();
} );
+ test( 'can update order status to cancelled', async ( { page } ) => {
+ // open order we created
+ await page.goto(
+ `wp-admin/post.php?post=${ orderToCancel }&action=edit`
+ );
+
+ // update order status to Completed
+ await page.locator( '#order_status' ).selectOption( 'Cancelled' );
+ await page.locator( 'button.save_order' ).click();
+
+ // verify order status changed and note added
+ await expect( page.locator( '#order_status' ) ).toHaveValue(
+ 'wc-cancelled'
+ );
+ await expect(
+ page.getByText(
+ 'Order status changed from Processing to Cancelled.'
+ )
+ ).toBeVisible();
+
+ // load the orders listing and confirm order is cancelled
+ await page.goto( 'wp-admin/admin.php?page=wc-orders' );
+
+ await expect(
+ page
+ .locator(
+ `:is(#order-${ orderToCancel }, #post-${ orderToCancel })`
+ )
+ .getByRole( 'cell', { name: 'Cancelled' } )
+ ).toBeVisible();
+ } );
+
+ test( 'can update order details', async ( { page } ) => {
+ // open order we created
+ await page.goto(
+ `wp-admin/admin.php?page=wc-orders&action=edit&id=${ orderId }`
+ );
+
+ // update order date
+ await page.locator( 'input[name=order_date]' ).fill( '2018-12-14' );
+ await page.locator( 'button.save_order' ).click();
+
+ // verify changes
+ await expect(
+ page
+ .locator( 'div.notice-success > p' )
+ .filter( { hasText: 'Order updated.' } )
+ ).toBeVisible();
+ await expect( page.locator( 'input[name=order_date]' ) ).toHaveValue(
+ '2018-12-14'
+ );
+ } );
+
test( 'saving an order does not trigger a false unsaved-changes warning', async ( {
page,
} ) => {
diff --git a/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-order-data-test.php b/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-order-data-test.php
index 753caceb029..70a36149527 100644
--- a/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-order-data-test.php
+++ b/plugins/woocommerce/tests/php/includes/admin/meta-boxes/class-wc-meta-box-order-data-test.php
@@ -604,90 +604,6 @@ class WC_Meta_Box_Order_Data_Test extends WC_Unit_Test_Case {
$this->assertStringContainsString( 'Extension contact reference: Extension reference 39300', $summary );
}
- /**
- * @testdox Saving the order data meta box persists the $status_label status, date, and transition note.
- *
- * @dataProvider provider_order_status_transitions
- *
- * @param string $status Status to persist.
- * @param string $status_label Human-readable status label.
- */
- public function test_save_persists_status_date_and_transition_note( string $status, string $status_label ): void {
- $order = wc_create_order( array( 'status' => 'processing' ) );
- if ( ! $order instanceof WC_Order ) {
- throw new RuntimeException( 'The order fixture could not be created.' );
- }
-
- $this->orders[] = $order;
-
- // phpcs:disable WordPress.Security.NonceVerification.Missing -- Simulate the nonce-verified meta-box save request.
- $_POST = array(
- 'order_status' => 'wc-' . $status,
- '_payment_method' => $order->get_payment_method(),
- 'customer_user' => 0,
- 'order_date' => '2018-12-14',
- 'order_date_hour' => '13',
- 'order_date_minute' => '14',
- 'order_date_second' => '15',
- // Billing fields on a guest order go through their own branch in save(),
- // guarded on the customer being a guest or a valid user. Posting them here
- // is what covers the deleted "create a simple guest order" E2E title; the
- // surviving browser test only asserts billing after picking a customer.
- '_billing_first_name' => 'Guest',
- '_billing_last_name' => 'Buyer',
- '_billing_company' => 'Guest Co',
- '_billing_address_1' => '1 Guest Street',
- '_billing_city' => 'Guestville',
- '_billing_postcode' => '90210',
- '_billing_email' => 'guest.buyer@example.com',
- '_billing_phone' => '555-0100',
- );
- // phpcs:enable WordPress.Security.NonceVerification.Missing
-
- WC_Meta_Box_Order_Data::save( $order->get_id() );
-
- $saved_order = wc_get_order( $order->get_id() );
- if ( ! $saved_order instanceof WC_Order ) {
- throw new RuntimeException( 'The saved order could not be reloaded.' );
- }
-
- $date_created = $saved_order->get_date_created();
- if ( ! $date_created instanceof WC_DateTime ) {
- throw new RuntimeException( 'The saved order date could not be reloaded.' );
- }
-
- $this->assertSame( $status, $saved_order->get_status() );
- $this->assertSame( '2018-12-14 13:14:15', $date_created->date( 'Y-m-d H:i:s' ) );
-
- $this->assertSame( 'Guest', $saved_order->get_billing_first_name( 'edit' ) );
- $this->assertSame( 'Buyer', $saved_order->get_billing_last_name( 'edit' ) );
- $this->assertSame( 'Guest Co', $saved_order->get_billing_company( 'edit' ) );
- $this->assertSame( '1 Guest Street', $saved_order->get_billing_address_1( 'edit' ) );
- $this->assertSame( 'Guestville', $saved_order->get_billing_city( 'edit' ) );
- $this->assertSame( '90210', $saved_order->get_billing_postcode( 'edit' ) );
- $this->assertSame( 'guest.buyer@example.com', $saved_order->get_billing_email( 'edit' ) );
- $this->assertSame( '555-0100', $saved_order->get_billing_phone( 'edit' ) );
-
- $notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) );
- $this->assertNotEmpty( $notes, 'The status transition should create an order note.' );
- $this->assertSame(
- "Order status changed from Processing to {$status_label}.",
- $notes[0]->content
- );
- }
-
- /**
- * Status transitions saved through the order data meta box.
- *
- * @return array<string, array{string, string}>
- */
- public function provider_order_status_transitions(): array {
- return array(
- 'completed' => array( 'completed', 'Completed' ),
- 'cancelled' => array( 'cancelled', 'Cancelled' ),
- );
- }
-
/**
* Create an order with billing-derived shipping data.
*
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
index d87556ea1ae..adf9e943429 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-ajax-test.php
@@ -8,7 +8,6 @@
declare( strict_types = 1 );
use Automattic\WooCommerce\Enums\OrderStatus;
-use Automattic\WooCommerce\Enums\ProductType;
use Automattic\WooCommerce\Internal\Orders\CouponsController;
use Automattic\WooCommerce\Internal\Orders\TaxesController;
use Automattic\WooCommerce\Proxies\LegacyProxy;
@@ -1489,133 +1488,6 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
$this->assertEquals( 45, $order->get_total() );
}
- /**
- * @testdox Calculating line taxes persists the tax class, rate, and totals for each supported product type.
- */
- public function test_calc_line_taxes_persists_multiple_tax_classes(): void {
- $suffix = strtolower( wp_generate_password( 8, false, false ) );
- $class_definitions = array(
- array( "Ajax order ten {$suffix}", "ajax-order-ten-{$suffix}", '10', "Ajax order Ten {$suffix}" ),
- array( "Ajax order twenty {$suffix}", "ajax-order-twenty-{$suffix}", '20', "Ajax order Twenty {$suffix}" ),
- array( "Ajax order thirty {$suffix}", "ajax-order-thirty-{$suffix}", '30', "Ajax order Thirty {$suffix}" ),
- );
- $tax_classes = array();
- $tax_rate_ids = array();
-
- update_option( 'woocommerce_calc_taxes', 'yes' );
- update_option( 'woocommerce_prices_include_tax', 'no' );
- update_option( 'woocommerce_tax_based_on', 'shipping' );
-
- foreach ( $class_definitions as $definition ) {
- $tax_class = WC_Tax::create_tax_class( $definition[0], $definition[1] );
- if ( is_wp_error( $tax_class ) ) {
- throw new RuntimeException( esc_html( $tax_class->get_error_message() ) );
- }
- $tax_classes[] = $tax_class['slug'];
- $tax_rate_id = WC_Tax::_insert_tax_rate(
- array(
- 'tax_rate_country' => 'US',
- 'tax_rate_state' => 'CA',
- 'tax_rate' => $definition[2],
- 'tax_rate_name' => $definition[3],
- 'tax_rate_priority' => 1,
- 'tax_rate_compound' => 0,
- 'tax_rate_shipping' => 0,
- 'tax_rate_order' => 1,
- 'tax_rate_class' => $tax_class['slug'],
- )
- );
- if ( ! $tax_rate_id ) {
- throw new RuntimeException( 'Could not create the tax-rate fixture.' );
- }
- $tax_rate_ids[] = $tax_rate_id;
- }
- WC_Cache_Helper::invalidate_cache_group( 'taxes' );
-
- $simple_product = WC_Helper_Product::create_simple_product();
- $simple_product->set_regular_price( '100' );
- $simple_product->set_tax_class( $tax_classes[0] );
- $simple_product->save();
-
- $variable_product = new WC_Product_Variable();
- $variable_product->set_name( 'Ajax order taxed variable parent' );
- $variable_product->save();
- $variation = new WC_Product_Variation();
- $variation->set_parent_id( $variable_product->get_id() );
- $variation->set_regular_price( '100' );
- $variation->set_tax_class( $tax_classes[1] );
- $variation->save();
-
- $external_product = WC_Helper_Product::create_external_product();
- $external_product->set_regular_price( '100' );
- $external_product->set_tax_class( $tax_classes[2] );
- $external_product->save();
-
- $order = wc_create_order();
- if ( is_wp_error( $order ) ) {
- throw new RuntimeException( 'Could not create the empty taxed order fixture.' );
- }
- $order->set_shipping_country( 'GB' );
- $order->add_product( $simple_product, 1 );
- $order->add_product( $variation, 1 );
- $order->add_product( $external_product, 1 );
- $order->save();
-
- $serialized_items = array(
- 'order_item_id' => array(),
- 'order_item_name' => array(),
- 'order_item_qty' => array(),
- 'order_item_tax_class' => array(),
- 'line_subtotal' => array(),
- 'line_total' => array(),
- );
- foreach ( $order->get_items( 'line_item' ) as $item_id => $item ) {
- $serialized_items['order_item_id'][] = $item_id;
- $serialized_items['order_item_name'][ $item_id ] = $item->get_name();
- $serialized_items['order_item_qty'][ $item_id ] = 1;
- $serialized_items['order_item_tax_class'][ $item_id ] = $item->get_tax_class();
- $serialized_items['line_subtotal'][ $item_id ] = '100';
- $serialized_items['line_total'][ $item_id ] = '100';
- }
-
- $taxes_controller = wc_get_container()->get( TaxesController::class );
- $taxes_controller->calc_line_taxes(
- array(
- 'order_id' => $order->get_id(),
- 'items' => http_build_query( $serialized_items ),
- 'country' => 'US',
- 'state' => 'CA',
- 'postcode' => '90210',
- 'city' => 'Beverly Hills',
- )
- );
-
- $fresh_order = wc_get_order( $order->get_id() );
- if ( ! $fresh_order instanceof WC_Order ) {
- throw new RuntimeException( 'Could not reload the taxed order fixture.' );
- }
- $fresh_items = array_values( $fresh_order->get_items( 'line_item' ) );
- $expected_tax = array( 10.0, 20.0, 30.0 );
- $this->assertCount( 3, $fresh_items );
-
- foreach ( $fresh_items as $index => $item ) {
- $taxes = $item->get_taxes();
- $this->assertSame( $tax_classes[ $index ], $item->get_tax_class() );
- $this->assertSame( array( $tax_rate_ids[ $index ] ), array_map( 'intval', array_keys( $taxes['total'] ) ) );
- $this->assertSame( $expected_tax[ $index ], (float) current( $taxes['total'] ) );
- }
-
- $tax_items = array_values( $fresh_order->get_items( 'tax' ) );
- $this->assertCount( 3, $tax_items );
- foreach ( $tax_items as $index => $tax_item ) {
- $this->assertSame( $tax_rate_ids[ $index ], $tax_item->get_rate_id() );
- $this->assertSame( $class_definitions[ $index ][3], $tax_item->get_label() );
- $this->assertSame( $expected_tax[ $index ], (float) $tax_item->get_tax_total() );
- }
- $this->assertSame( 60.0, (float) $fresh_order->get_total_tax() );
- $this->assertSame( 360.0, (float) $fresh_order->get_total() );
- }
-
/**
* @testdox Product search decodes URL-encoded characters before returning plain text names.
* @dataProvider product_search_name_provider
@@ -1732,54 +1604,18 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
*
* @throws Automattic\WooCommerce\Internal\DependencyManagement\ContainerException If the LegacyProxy cannot be retrieved.
*/
- public function test_get_customer_details_returns_exact_billing_and_shipping_payload(): void {
+ public function test_get_customer_details(): void {
// This class does not inherit from WC_Unit_Test_Case, so we're handling the legacy proxy mechanics ourselves.
$legacy_proxy = wc_get_container()->get( LegacyProxy::class );
$legacy_proxy->reset();
- $customer_id = 0;
- $is_member_of_blog = true;
- $is_multisite = false;
- $customer = WC_Helper_Customer::create_customer( 'ajaxordercustomer', 'pass2', 'ajaxorder@example.com' );
- $customer_id = $customer->get_id();
- $administrator_user = self::factory()->user->create( array( 'role' => 'administrator' ) );
- $expected_billing = array(
- 'first_name' => 'Sideshow',
- 'last_name' => 'Bob',
- 'company' => 'Die Bart Die',
- 'address_1' => '123 Fake St',
- 'address_2' => 'Suite 4',
- 'city' => 'Springfield',
- 'postcode' => '12345',
- 'country' => 'US',
- 'state' => 'FL',
- 'email' => 'billing-ajaxorder@example.com',
- 'phone' => '555-555-5556',
- );
- $expected_shipping = array(
- 'first_name' => 'Robert',
- 'last_name' => 'Terwilliger',
- 'company' => 'Springfield Penitentiary',
- 'address_1' => '321 Fake St',
- 'address_2' => 'Cell 8',
- 'city' => 'Springfield',
- 'postcode' => '54321',
- 'country' => 'US',
- 'state' => 'FL',
- 'phone' => '555-555-5557',
- );
-
- foreach ( $expected_billing as $field => $value ) {
- $customer->{"set_billing_{$field}"}( $value );
- }
- foreach ( $expected_shipping as $field => $value ) {
- $customer->{"set_shipping_{$field}"}( $value );
- }
- $customer->update_meta_data( 'ajaxorder_unrelated_meta', 'must-not-leak' );
- $customer->save();
+ $customer_id = 0;
+ $is_member_of_blog = true;
+ $is_multisite = true;
$legacy_proxy->register_function_mocks(
array(
+ 'check_ajax_referer' => fn () => true,
'is_multisite' => function () use ( &$is_multisite ) {
return $is_multisite;
},
@@ -1793,196 +1629,28 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
return filter_input( $method, $key, $filter, $options );
},
+ 'wp_die' => fn () => '',
)
);
- try {
- wp_set_current_user( $administrator_user );
- $nonce = wp_create_nonce( 'get-customer-details' );
- $_POST['user_id'] = $customer_id;
- $_POST['security'] = $nonce;
- $_REQUEST['user_id'] = $customer_id;
- $_REQUEST['security'] = $nonce;
-
- $response = $this->do_ajax( 'woocommerce_get_customer_details' );
-
- $this->assertIsArray( $response, 'The registered customer-details action should return JSON data.' );
- $this->assertSame( $customer_id, $response['id'] );
- $this->assertSame( $expected_billing, $response['billing'] );
- $this->assertSame( $expected_shipping, $response['shipping'] );
- $this->assertArrayNotHasKey( 'meta_data', $response, 'Unrelated customer metadata must not be exposed.' );
-
- $is_multisite = true;
- $is_member_of_blog = false;
- $this->_last_response = '';
- $response = $this->do_ajax( 'woocommerce_get_customer_details' );
- $this->assertNull( $response, 'Customers outside the current multisite blog must remain inaccessible.' );
- } finally {
- // The container keeps the mocked functions; nothing else here outlives
- // the transaction rollback and the hook restore in tear_down().
- $legacy_proxy->reset();
- }
- }
-
- /**
- * @testdox Registered Add Order Item AJAX persists every supported product type and its quantities.
- */
- public function test_add_order_item_via_ajax_persists_supported_product_types(): void {
- $order = wc_create_order();
- if ( is_wp_error( $order ) ) {
- throw new RuntimeException( 'Could not create the empty order fixture.' );
- }
- $simple_product = WC_Helper_Product::create_simple_product();
- $variable_product = new WC_Product_Variable();
- $grouped_product = new WC_Product_Grouped();
- $external_product = WC_Helper_Product::create_external_product();
-
- $variable_product->set_name( 'Ajax order variable parent' );
- $variable_product->save();
-
- $variation = new WC_Product_Variation();
- $variation->set_parent_id( $variable_product->get_id() );
- $variation->set_regular_price( '25' );
- $variation->save();
+ $customer_id = WC_Helper_Customer::create_customer( 'test2', 'pass2', 'test2@example.com' )->get_id();
+ $admin_id = self::factory()->user->create( array( 'role' => 'administrator' ) );
- $grouped_product->set_name( 'Ajax order grouped' );
- $grouped_product->save();
+ wp_set_current_user( $admin_id );
+ $_POST['user_id'] = $customer_id;
- $products = array(
- array( $simple_product, 2, ProductType::SIMPLE ),
- array( $variation, 3, ProductType::VARIATION ),
- array( $grouped_product, 4, ProductType::GROUPED ),
- array( $external_product, 5, ProductType::EXTERNAL ),
+ $response = $this->do_ajax( 'woocommerce_get_customer_details' );
+ $this->assertIsArray(
+ $response,
+ 'If the customer is part of the blog, an array of information is supplied.'
);
- $this->_setRole( 'administrator' );
- $request_data = array(
- 'security' => wp_create_nonce( 'order-item' ),
- 'order_id' => $order->get_id(),
- 'items' => '',
- 'data' => array_map(
- static fn ( array $row ): array => array(
- 'id' => $row[0]->get_id(),
- 'qty' => $row[1],
- ),
- $products
- ),
+ $is_member_of_blog = false;
+ $response = $this->do_ajax( 'woocommerce_get_customer_details' );
+ $this->assertNull(
+ $response,
+ 'If the customer is not part of the blog, we do not get back any customer information (in reality, the request was ended with wp_die).'
);
- $_POST = $request_data;
- $_REQUEST = $request_data;
-
- $response = $this->do_ajax( 'woocommerce_add_order_item' );
- $this->assertTrue( $response['success'] ?? false, 'The registered AJAX action should report success.' );
-
- $fresh_order = wc_get_order( $order->get_id() );
- if ( ! $fresh_order instanceof WC_Order ) {
- throw new RuntimeException( 'Could not reload the order-item fixture.' );
- }
- $items = array_values( $fresh_order->get_items( 'line_item' ) );
- $this->assertCount( 4, $items );
-
- foreach ( $products as $index => $expected ) {
- list( $product, $quantity, $product_type ) = $expected;
- $item = $items[ $index ];
-
- $this->assertSame( $quantity, $item->get_quantity() );
- $this->assertSame( $product_type, $item->get_product()->get_type() );
- if ( ProductType::VARIATION === $product_type ) {
- $this->assertSame( $variable_product->get_id(), $item->get_product_id() );
- $this->assertSame( $variation->get_id(), $item->get_variation_id() );
- } else {
- $this->assertSame( $product->get_id(), $item->get_product_id() );
- $this->assertSame( 0, $item->get_variation_id() );
- }
- }
-
- $notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) );
- $this->assertNotEmpty( $notes, 'Adding line items should create an order update note.' );
- $this->assertStringContainsString( 'Added line items:', $notes[0]->content );
- foreach ( $products as $expected ) {
- $this->assertStringContainsString( $expected[0]->get_name(), $notes[0]->content );
- }
- }
-
- /**
- * @testdox Registered Remove Order Coupon AJAX removes the coupon, recalculates totals, and records its internal note.
- */
- public function test_remove_order_coupon(): void {
- $output_buffering_level = ob_get_level();
- $coupon_code = 'remove-coupon-' . wp_rand( 1000, 9999 );
- $product_name = 'Coupon Removal Product';
- $expected_removal_note = sprintf( 'Coupon removed: "%s".', $coupon_code );
- $expected_product_total = '10.00';
- $expected_discounted_total = '5.00';
-
- try {
- $product = WC_Helper_Product::create_simple_product();
- $product->set_name( $product_name );
- $product->set_regular_price( $expected_product_total );
- $product->save();
-
- $coupon = WC_Helper_Coupon::create_coupon(
- $coupon_code,
- array(
- 'discount_type' => 'fixed_product',
- 'coupon_amount' => $expected_discounted_total,
- 'product_ids' => array( $product->get_id() ),
- )
- );
- $order = wc_create_order();
- if ( is_wp_error( $order ) ) {
- throw new RuntimeException( 'Could not create the coupon-removal order fixture.' );
- }
- $order->add_product( $product, 1 );
- $order->calculate_totals();
- $this->assertTrue( $order->apply_coupon( $coupon_code ), 'The fixture order should accept its fixed-product coupon.' );
- $order->calculate_totals();
- $order->save();
- $this->assertSame( $expected_discounted_total, $order->get_total(), 'The fixture order should start with its coupon discount applied.' );
-
- $this->_setRole( 'administrator' );
- $request_data = array(
- 'security' => wp_create_nonce( 'order-item' ),
- 'order_id' => $order->get_id(),
- 'coupon' => $coupon_code,
- 'country' => 'US',
- 'state' => 'CA',
- 'postcode' => '94105',
- 'city' => 'San Francisco',
- );
- $_POST = $request_data;
- $_REQUEST = $request_data;
-
- $response = $this->do_ajax( 'woocommerce_remove_order_coupon' );
- $this->assertTrue( $response['success'] ?? false, 'The registered AJAX action should report success.' );
- $this->assertStringContainsString( $product_name, $response['data']['html'] ?? '', 'The AJAX response should render the order items.' );
- $this->assertStringContainsString( esc_html( $expected_removal_note ), $response['data']['notes_html'] ?? '', 'The AJAX response should render the coupon-removal note.' );
-
- $fresh_order = wc_get_order( $order->get_id() );
- if ( ! $fresh_order instanceof WC_Order ) {
- throw new RuntimeException( 'Could not reload the coupon-removal order fixture.' );
- }
- $this->assertNotContains( $coupon_code, $fresh_order->get_coupon_codes(), 'The fresh order should no longer have the removed coupon.' );
- $this->assertSame( $expected_product_total, $fresh_order->get_total(), 'Removing the coupon should restore the product total.' );
-
- $removal_notes = array_values(
- array_filter(
- wc_get_order_notes( array( 'order_id' => $fresh_order->get_id() ) ),
- static fn ( $note ): bool => esc_html( $expected_removal_note ) === $note->content
- )
- );
- $this->assertCount( 1, $removal_notes, 'Removing the coupon should create one exact removal note.' );
- $this->assertSame( 0, (int) $removal_notes[0]->customer_note, 'The coupon-removal note should remain internal.' );
- } finally {
- // Output buffering is process state, so a die handler that unwinds mid-render
- // would otherwise leave the level where the next test inherits it.
- while ( ob_get_level() > $output_buffering_level ) {
- ob_end_clean();
- }
- while ( ob_get_level() < $output_buffering_level ) {
- ob_start();
- }
- }
}
/**
@@ -3097,22 +2765,9 @@ class WC_AJAX_Test extends \WP_Ajax_UnitTestCase {
while ( ob_get_level() > $output_buffering_level ) {
ob_end_clean();
}
- while ( ob_get_level() < $output_buffering_level ) {
- ob_start();
- }
}
- $raw_response = (string) $this->_last_response;
- $result = json_decode( $raw_response, true );
- // A handler can send two payloads under the test die handler: wp_send_json_*() throws
- // to stop the request, and the handler's own catch block catches that and sends an
- // error payload after it. Decode the first one, which is all a real request receives.
- if ( null === $result ) {
- $second_response_offset = strpos( $raw_response, '}{"success":false' );
- if ( false !== $second_response_offset ) {
- $result = json_decode( substr( $raw_response, 0, $second_response_offset + 1 ), true );
- }
- }
+ $result = json_decode( $this->_last_response, true );
$this->_last_response = false;
return $result;