[e2e] External sites - Include API tests for Pressable and WPCOM (#51284)

* Pressable - run only /api-tests

* Skip on Pressable

* Fix lint errors

* WPCOM - run API only

* Revert "WPCOM - run API only"

This reverts commit 5e59be1663.

* WPCOM - run API only

* Update settings-crud.test.js so they are passing against Pressable

* Skip "can retrieve all tax settings"

* Skip "can view all system status items" on WPCOM

* Several skips in settings-crud.test.js

* Skip "Product review tests: CRUD" on WPCOM

* Skip "List all products > categories" on WPCOM

* Skip "can view all payment gateways" on WPCOM

* Skip "Orders API tests" on WPCOM

* Skip "Customers API tests: CRUD" on WPCOM

* Revert `playwright.config.js` files and include '**/api-tests/**/*.test.js',

* Add changefile(s) from automation for the following project(s): woocommerce

* Skip three more API tests for WPCOM

* Skip two coupons tests

* Update report name for Pressable and WPCOM to ``*-core-e2e-and-api`

---------

Co-authored-by: github-actions <github-actions@github.com>
This commit is contained in:
Ivan Stojadinov 2024-09-12 16:01:40 +02:00 committed by GitHub
parent 2433f9f1c4
commit a06c6ba496
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 6488 additions and 6233 deletions

View File

@ -0,0 +1,4 @@
Significance: patch
Type: update
Include API tests into test suites for Pressable and WPCOM.

View File

@ -556,7 +556,7 @@
"on-demand"
],
"report": {
"resultsBlobName": "default-pressable-core-e2e",
"resultsBlobName": "default-pressable-core-e2e-and-api",
"resultsPath": "tests/e2e-pw/test-results",
"allure": true
}
@ -572,7 +572,7 @@
"on-demand"
],
"report": {
"resultsBlobName": "default-wpcom-core-e2e",
"resultsBlobName": "default-wpcom-core-e2e-and-api",
"resultsPath": "tests/e2e-pw/test-results",
"allure": true
}
@ -681,7 +681,6 @@
"node_modules/@woocommerce/e2e-core-tests/CHANGELOG.md",
"node_modules/@woocommerce/api/dist/",
"node_modules/@woocommerce/admin-e2e-tests/build",
"node_modules/@woocommerce/classic-assets/build",
"node_modules/@woocommerce/block-library/build",
"node_modules/@woocommerce/block-library/blocks.ini",
"node_modules/@woocommerce/admin-library/build",

View File

@ -16,6 +16,7 @@ config = {
'**/customize-store/**/*.spec.js',
'**/merchant/**/*.spec.js',
'**/shopper/**/*.spec.js',
'**/api-tests/**/*.test.js',
],
grepInvert: /@skip-on-default-pressable/,
},

View File

@ -7,7 +7,11 @@ config = {
{
name: 'default wpcom',
use: { ...devices[ 'Desktop Chrome' ] },
testMatch: [ '**/basic.spec.js', '**/shopper/**/*.spec.js' ],
testMatch: [
'**/basic.spec.js',
'**/shopper/**/*.spec.js',
'**/api-tests/**/*.test.js',
],
grepInvert: /@skip-on-default-wpcom/,
},
],

View File

@ -69,7 +69,10 @@ test.describe( 'Coupons API tests', () => {
);
} );
test( 'can permanently delete a coupon', async ( { request } ) => {
test(
'can permanently delete a coupon',
{ tag: '@skip-on-default-wpcom' },
async ( { request } ) => {
//call API to delete previously created coupon
const response = await request.delete(
`/wp-json/wc/v3/coupons/${ couponId }`,
@ -88,7 +91,8 @@ test.describe( 'Coupons API tests', () => {
//validate response
expect( getCouponResponse.status() ).toEqual( 404 );
} );
}
);
} );
test.describe( 'Batch update coupons', () => {
@ -180,7 +184,10 @@ test.describe( 'Batch update coupons', () => {
expect( updatedCoupons[ 1 ].amount ).toEqual( '25.00' );
} );
test( 'can batch delete coupons', async ( { request } ) => {
test(
'can batch delete coupons',
{ tag: '@skip-on-default-wpcom' },
async ( { request } ) => {
// Batch delete the 2 coupons.
const couponIdsToDelete = expectedCoupons.map( ( { id } ) => id );
const batchDeletePayload = {
@ -211,7 +218,8 @@ test.describe( 'Batch update coupons', () => {
);
expect( response.status() ).toEqual( 404 );
}
} );
}
);
} );
test.describe( 'List coupons', () => {

View File

@ -2,7 +2,10 @@ const { test, expect } = require( '../../../fixtures/api-tests-fixtures' );
const { admin } = require( '../../../test-data/data' );
const { customer } = require( '../../../data' );
test.describe( 'Customers API tests: CRUD', () => {
test.describe(
'Customers API tests: CRUD',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
() => {
let customerId;
let subscriberUserId;
let subscriberUserCreatedDuringTests = false;
@ -30,7 +33,9 @@ test.describe( 'Customers API tests: CRUD', () => {
// If a subscriber user has not been created then create one
if ( ! subscriberUserId ) {
const now = Date.now();
const userResponse = await request.post( '/wp-json/wp/v2/users', {
const userResponse = await request.post(
'/wp-json/wp/v2/users',
{
data: {
username: `customer_${ now }`,
email: `customer_${ now }@woocommercecoretestsuite.com`,
@ -40,7 +45,8 @@ test.describe( 'Customers API tests: CRUD', () => {
password: 'password',
name: 'Jane',
},
} );
}
);
const userResponseJSON = await userResponse.json();
// set subscriber user id to newly created user
subscriberUserId = userResponseJSON.id;
@ -85,7 +91,9 @@ test.describe( 'Customers API tests: CRUD', () => {
*/
test( 'can retrieve admin user', async ( { request } ) => {
// call API to retrieve the previously saved customer
const response = await request.get( '/wp-json/wc/v3/customers/1' );
const response = await request.get(
'/wp-json/wc/v3/customers/1'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( responseJSON.is_paying_customer ).toEqual( false );
@ -106,20 +114,28 @@ test.describe( 'Customers API tests: CRUD', () => {
expect( responseJSON.role ).toEqual( 'subscriber' );
} );
test( 'retrieve user with id 0 is invalid', async ( { request } ) => {
test( 'retrieve user with id 0 is invalid', async ( {
request,
} ) => {
// call API to retrieve the previously saved customer
const response = await request.get( '/wp-json/wc/v3/customers/0' );
const response = await request.get(
'/wp-json/wc/v3/customers/0'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 404 );
expect( responseJSON.code ).toEqual(
'woocommerce_rest_invalid_id'
);
expect( responseJSON.message ).toEqual( 'Invalid resource ID.' );
expect( responseJSON.message ).toEqual(
'Invalid resource ID.'
);
} );
test( 'can retrieve customers', async ( { request } ) => {
// call API to retrieve all customers should initially return empty array
const response = await request.get( '/wp-json/wc/v3/customers' );
const response = await request.get(
'/wp-json/wc/v3/customers'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( Array.isArray( responseJSON ) ).toBe( true );
@ -131,11 +147,14 @@ test.describe( 'Customers API tests: CRUD', () => {
// call API to retrieve all customers should initially return empty array
// unless the role 'all' is passed as a search string, in which case the admin
// and subscriber users will be returned
const response = await request.get( '/wp-json/wc/v3/customers', {
const response = await request.get(
'/wp-json/wc/v3/customers',
{
params: {
role: 'all',
},
} );
}
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( Array.isArray( responseJSON ) ).toBe( true );
@ -146,9 +165,12 @@ test.describe( 'Customers API tests: CRUD', () => {
test.describe( 'Create a customer', () => {
test( 'can create a customer', async ( { request } ) => {
// call API to create a customer
const response = await request.post( '/wp-json/wc/v3/customers', {
const response = await request.post(
'/wp-json/wc/v3/customers',
{
data: customer,
} );
}
);
const responseJSON = await response.json();
// Save the customer ID. It will be used by the retrieve, update, and delete tests.
@ -178,7 +200,9 @@ test.describe( 'Customers API tests: CRUD', () => {
request,
} ) => {
// call API to retrieve all customers
const response = await request.get( '/wp-json/wc/v3/customers' );
const response = await request.get(
'/wp-json/wc/v3/customers'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( Array.isArray( responseJSON ) ).toBe( true );
@ -187,13 +211,17 @@ test.describe( 'Customers API tests: CRUD', () => {
} );
test.describe( 'Update a customer', () => {
test( `can update the admin user/customer`, async ( { request } ) => {
test( `can update the admin user/customer`, async ( {
request,
} ) => {
/**
* update customer names (regular, billing and shipping) to admin
* (these were initialised blank when the environment is created,
* (https://github.com/woocommerce/woocommerce/tree/trunk/plugins/woocommerce/tests/e2e-pw#woocommerce-playwright-end-to-end-tests
*/
const response = await request.put( `/wp-json/wc/v3/customers/1`, {
const response = await request.put(
`/wp-json/wc/v3/customers/1`,
{
data: {
first_name: 'admin',
billing: {
@ -203,7 +231,8 @@ test.describe( 'Customers API tests: CRUD', () => {
first_name: 'admin',
},
},
} );
}
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( responseJSON.first_name ).toEqual( 'admin' );
@ -213,7 +242,9 @@ test.describe( 'Customers API tests: CRUD', () => {
test( 'retrieve after update admin', async ( { request } ) => {
// call API to retrieve the admin customer we updated above
const response = await request.get( '/wp-json/wc/v3/customers/1' );
const response = await request.get(
'/wp-json/wc/v3/customers/1'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( responseJSON.first_name ).toEqual( 'admin' );
@ -295,7 +326,9 @@ test.describe( 'Customers API tests: CRUD', () => {
} );
test.describe( 'Delete a customer', () => {
test( 'can permanently delete an customer', async ( { request } ) => {
test( 'can permanently delete an customer', async ( {
request,
} ) => {
// Delete the customer.
const response = await request.delete(
`/wp-json/wc/v3/customers/${ customerId }`,
@ -401,11 +434,14 @@ test.describe( 'Customers API tests: CRUD', () => {
// Verify that the 2 new customers were created
const actualCustomers = responseJSON.create;
expect( actualCustomers ).toHaveLength( expectedCustomers.length );
expect( actualCustomers ).toHaveLength(
expectedCustomers.length
);
for ( let i = 0; i < actualCustomers.length; i++ ) {
const { id, first_name } = actualCustomers[ i ];
const expectedCustomerName = expectedCustomers[ i ].first_name;
const expectedCustomerName =
expectedCustomers[ i ].first_name;
expect( id ).toBeDefined();
expect( first_name ).toEqual( expectedCustomerName );
@ -498,4 +534,5 @@ test.describe( 'Customers API tests: CRUD', () => {
}
} );
} );
} );
}
);

View File

@ -208,7 +208,10 @@ test.describe( 'Orders API test', () => {
} );
} );
test( 'can add complex order', async ( { request } ) => {
test(
'can add complex order',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
async ( { request } ) => {
//ensure tax calculations are enabled
await request.put(
'/wp-json/wc/v3/settings/general/woocommerce_calc_taxes',
@ -251,5 +254,6 @@ test.describe( 'Orders API test', () => {
expect( actualLineTaxTotal ).toEqual( expectedLineTaxTotal );
}
} );
}
);
} );

View File

@ -34,7 +34,10 @@ const updatedCustomerShipping = {
phone: '123456789',
};
test.describe.serial( 'Orders API tests', () => {
test.describe.serial(
'Orders API tests',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
() => {
let orderId, sampleData;
test.beforeAll( async ( { request } ) => {
@ -175,11 +178,14 @@ test.describe.serial( 'Orders API tests', () => {
};
const createSampleTags = async () => {
const cool = await request.post( '/wp-json/wc/v3/products/tags', {
const cool = await request.post(
'/wp-json/wc/v3/products/tags',
{
data: {
name: 'Cool',
},
} );
}
);
const coolJSON = await cool.json();
return {
@ -1300,7 +1306,8 @@ test.describe.serial( 'Orders API tests', () => {
download_expiry: 0,
external_url:
'https://mercantile.wordpress.org/product/wordpress-pennant/',
button_text: 'Buy on the WordPress swag store!',
button_text:
'Buy on the WordPress swag store!',
tax_status: 'taxable',
tax_class: '',
manage_stock: false,
@ -1350,12 +1357,15 @@ test.describe.serial( 'Orders API tests', () => {
};
const createSampleGroupedProduct = async ( categories ) => {
const logoProducts = await request.get( '/wp-json/wc/v3/products', {
const logoProducts = await request.get(
'/wp-json/wc/v3/products',
{
params: {
search: 'logo',
_fields: [ 'id' ],
},
} );
}
);
const logoProductsJSON = await logoProducts.json();
const groupedProducts = await request.post(
@ -1460,7 +1470,8 @@ test.describe.serial( 'Orders API tests', () => {
featured: false,
catalog_visibility: 'visible',
description,
short_description: '<p>This is a variable product.</p>\n',
short_description:
'<p>This is a variable product.</p>\n',
sku: 'woo-hoodie',
price: '42',
regular_price: '',
@ -1745,7 +1756,8 @@ test.describe.serial( 'Orders API tests', () => {
featured: true,
catalog_visibility: 'visible',
description,
short_description: '<p>This is a variable product.</p>\n',
short_description:
'<p>This is a variable product.</p>\n',
sku: 'woo-vneck-tee',
price: '15',
regular_price: '',
@ -1984,7 +1996,9 @@ test.describe.serial( 'Orders API tests', () => {
};
const createSampleProductReviews = async ( simpleProducts ) => {
const cap = simpleProducts.find( ( p ) => p.name === 'Cap oxo' );
const cap = simpleProducts.find(
( p ) => p.name === 'Cap oxo'
);
const shirt = simpleProducts.find(
( p ) => p.name === 'T-Shirt oxo'
@ -2138,7 +2152,9 @@ test.describe.serial( 'Orders API tests', () => {
simpleProducts
);
const orders = await createSampleProductOrders( simpleProducts );
const orders = await createSampleProductOrders(
simpleProducts
);
return {
categories,
@ -2158,7 +2174,8 @@ test.describe.serial( 'Orders API tests', () => {
// create Sample Data function
const createSampleData = async () => {
const testProductData = await productsTestSetupCreateSampleData();
const testProductData =
await productsTestSetupCreateSampleData();
const orderedProducts = {
pocketHoodie: testProductData.simpleProducts.find(
( p ) => p.name === 'Hoodie with Pocket oxo'
@ -2268,7 +2285,9 @@ test.describe.serial( 'Orders API tests', () => {
const orders = [];
// Have "John" order all products.
Object.values( orderedProducts ).forEach( async ( product ) => {
const order2 = await request.post( '/wp-json/wc/v3/orders', {
const order2 = await request.post(
'/wp-json/wc/v3/orders',
{
data: {
...orderBaseData,
customer_id: johnJSON.id,
@ -2284,7 +2303,8 @@ test.describe.serial( 'Orders API tests', () => {
},
],
},
} );
}
);
const orderJSON = await order2.json();
orders.push( orderJSON );
@ -2339,7 +2359,9 @@ test.describe.serial( 'Orders API tests', () => {
orders.push( order3JSON );
// Guest order.
const guestOrder = await request.post( '/wp-json/wc/v3/orders', {
const guestOrder = await request.post(
'/wp-json/wc/v3/orders',
{
data: {
...orderBaseData,
billing: guestBillingAddress,
@ -2355,7 +2377,8 @@ test.describe.serial( 'Orders API tests', () => {
},
],
},
} );
}
);
const guestOrderJSON = await guestOrder.json();
// Create an order with all possible numerical fields (taxes, fees, refunds, etc).
@ -2427,7 +2450,8 @@ test.describe.serial( 'Orders API tests', () => {
{
id: order4JSON.line_items[ 0 ].id,
quantity: 1,
refund_total: order4JSON.line_items[ 0 ].total,
refund_total:
order4JSON.line_items[ 0 ].total,
refund_tax: [
{
id: order4JSON.line_items[ 0 ]
@ -2494,11 +2518,14 @@ test.describe.serial( 'Orders API tests', () => {
] );
for ( const _order of orders ) {
await request.delete( `/wp-json/wc/v3/orders/${ _order.id }`, {
await request.delete(
`/wp-json/wc/v3/orders/${ _order.id }`,
{
data: {
force: true,
},
} );
}
);
}
for ( const productId of productIds ) {
@ -2552,7 +2579,9 @@ test.describe.serial( 'Orders API tests', () => {
);
}
for ( const shippingClass of Object.values( shippingClasses ) ) {
for ( const shippingClass of Object.values(
shippingClasses
) ) {
await request.delete(
`/wp-json/wc/v3/products/shipping_classes/${ shippingClass.id }`,
{
@ -2583,14 +2612,19 @@ test.describe.serial( 'Orders API tests', () => {
for ( const _order of _sampleData.orders.concat( [
_sampleData.guestOrderJSON,
] ) ) {
await request.delete( `/wp-json/wc/v3/orders/${ _order.id }`, {
await request.delete(
`/wp-json/wc/v3/orders/${ _order.id }`,
{
data: {
force: true,
},
} );
}
);
}
for ( const customer of Object.values( _sampleData.customers ) ) {
for ( const customer of Object.values(
_sampleData.customers
) ) {
await request.delete(
`/wp-json/wc/v3/customers/${ customer.id }`,
{
@ -2711,7 +2745,9 @@ test.describe.serial( 'Orders API tests', () => {
acc[ id ] = 1;
return acc;
}, {} );
expect( Object.keys( allOrderIds ) ).toHaveLength( pageSize * 2 );
expect( Object.keys( allOrderIds ) ).toHaveLength(
pageSize * 2
);
// Verify that offset takes precedent over page number.
const page2Offset = await request.get( 'wp-json/wc/v3/orders', {
@ -2770,7 +2806,9 @@ test.describe.serial( 'Orders API tests', () => {
const allOrdersJSON = await allOrders.json();
expect( allOrders.status() ).toEqual( 200 );
const allOrdersIds = allOrdersJSON.map( ( _order ) => _order.id );
const allOrdersIds = allOrdersJSON.map(
( _order ) => _order.id
);
expect( allOrdersIds ).toHaveLength( ORDERS_COUNT );
const ordersToFilter = [
@ -3007,7 +3045,10 @@ test.describe.serial( 'Orders API tests', () => {
_order.tax_lines.forEach( ( taxLine ) => {
expectPrecisionToMatch( taxLine.tax_total, dp );
expectPrecisionToMatch( taxLine.shipping_tax_total, dp );
expectPrecisionToMatch(
taxLine.shipping_tax_total,
dp
);
} );
_order.shipping_lines.forEach( ( shippingLine ) => {
@ -3262,4 +3303,5 @@ test.describe.serial( 'Orders API tests', () => {
} );
} );
} );
} );
}
);

View File

@ -1,9 +1,14 @@
const { test, expect } = require( '../../../fixtures/api-tests-fixtures' );
test.describe( 'Payment Gateways API tests', () => {
test( 'can view all payment gateways', async ( { request } ) => {
test(
'can view all payment gateways',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
async ( { request } ) => {
// call API to retrieve the payment gateways
const response = await request.get( '/wp-json/wc/v3/payment_gateways' );
const response = await request.get(
'/wp-json/wc/v3/payment_gateways'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( Array.isArray( responseJSON ) ).toBe( true );
@ -164,7 +169,8 @@ test.describe( 'Payment Gateways API tests', () => {
} ),
] )
);
} );
}
);
test( 'can view a payment gateway', async ( { request } ) => {
// call API to retrieve a single payment gateway

View File

@ -2230,7 +2230,10 @@ test.describe( 'Products API tests: List All Products', () => {
await deleteSampleData( sampleData );
}, 10000 );
test.describe( 'List all products', () => {
test.describe(
'List all products',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
() => {
test( 'defaults', async ( { request } ) => {
const result = await request.get( 'wp-json/wc/v3/products', {
params: {
@ -2284,17 +2287,22 @@ test.describe( 'Products API tests: List All Products', () => {
acc[ product.id ] = 1;
return acc;
}, {} );
expect( Object.keys( allProductIds ) ).toHaveLength( pageSize * 2 );
expect( Object.keys( allProductIds ) ).toHaveLength(
pageSize * 2
);
// Verify that offset takes precedent over page number.
const page2Offset = await request.get( 'wp-json/wc/v3/products', {
const page2Offset = await request.get(
'wp-json/wc/v3/products',
{
params: {
per_page: pageSize,
page: 2,
offset: pageSize + 1,
search: 'xxx',
},
} );
}
);
const page2OffsetJSON = await page2Offset.json();
// The offset pushes the result set 1 product past the start of page 2.
expect( page2OffsetJSON ).toEqual(
@ -2358,16 +2366,21 @@ test.describe( 'Products API tests: List All Products', () => {
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
expect( result2JSON ).toHaveLength( 1 );
expect( result2JSON[ 0 ].name ).toBe( 'Hoodie with Pocket xxx' );
expect( result2JSON[ 0 ].name ).toBe(
'Hoodie with Pocket xxx'
);
} );
test( 'inclusion / exclusion', async ( { request } ) => {
const allProducts = await request.get( 'wp-json/wc/v3/products', {
const allProducts = await request.get(
'wp-json/wc/v3/products',
{
params: {
per_page: 20,
search: 'xxx',
},
} );
}
);
const allProductsJSON = await allProducts.json();
expect( allProducts.status() ).toEqual( 200 );
const allProductIds = allProductsJSON.map(
@ -2540,7 +2553,9 @@ test.describe( 'Products API tests: List All Products', () => {
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
expect( result1JSON ).toHaveLength( featured.length );
expect( result1JSON ).toEqual( expect.arrayContaining( featured ) );
expect( result1JSON ).toEqual(
expect.arrayContaining( featured )
);
const result2 = await request.get( 'wp-json/wc/v3/products', {
params: {
@ -2555,7 +2570,10 @@ test.describe( 'Products API tests: List All Products', () => {
);
} );
test( 'categories', async ( { request } ) => {
test(
'categories',
{ tag: '@skip-on-default-wpcom' },
async ( { request } ) => {
const accessory = [
expect.objectContaining( {
name: 'Beanie xxx',
@ -2577,32 +2595,43 @@ test.describe( 'Products API tests: List All Products', () => {
];
// Verify that subcategories are included.
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
per_page: 20,
category: sampleData.categories.clothingJSON.id,
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
expect( result1JSON ).toEqual(
expect.arrayContaining( accessory )
);
expect( result1JSON ).toEqual( expect.arrayContaining( hoodies ) );
expect( result1JSON ).toEqual(
expect.arrayContaining( hoodies )
);
// Verify sibling categories are not.
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
category: sampleData.categories.hoodiesJSON.id,
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
expect( result2JSON ).toEqual(
expect.not.arrayContaining( accessory )
);
expect( result2JSON ).toEqual( expect.arrayContaining( hoodies ) );
} );
expect( result2JSON ).toEqual(
expect.arrayContaining( hoodies )
);
}
);
test( 'on sale', async ( { request } ) => {
const onSale = [
@ -2638,7 +2667,9 @@ test.describe( 'Products API tests: List All Products', () => {
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
expect( result1JSON ).toHaveLength( onSale.length );
expect( result1JSON ).toEqual( expect.arrayContaining( onSale ) );
expect( result1JSON ).toEqual(
expect.arrayContaining( onSale )
);
const result2 = await request.get( 'wp-json/wc/v3/products', {
params: {
@ -2737,7 +2768,9 @@ test.describe( 'Products API tests: List All Products', () => {
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
expect( result1JSON ).toHaveLength( before.length );
expect( result1JSON ).toEqual( expect.arrayContaining( before ) );
expect( result1JSON ).toEqual(
expect.arrayContaining( before )
);
const result2 = await request.get( 'wp-json/wc/v3/products', {
params: {
@ -2751,7 +2784,9 @@ test.describe( 'Products API tests: List All Products', () => {
expect.not.arrayContaining( before )
);
expect( result2JSON ).toHaveLength( after.length );
expect( result2JSON ).toEqual( expect.arrayContaining( after ) );
expect( result2JSON ).toEqual(
expect.arrayContaining( after )
);
} );
test( 'attributes', async ( { request } ) => {
@ -2814,7 +2849,8 @@ test.describe( 'Products API tests: List All Products', () => {
test( 'shipping class', async ( { request } ) => {
const result = await request.get( 'wp-json/wc/v3/products', {
params: {
shipping_class: sampleData.shippingClasses.freightJSON.id,
shipping_class:
sampleData.shippingClasses.freightJSON.id,
},
} );
const resultJSON = await result.json();
@ -2947,51 +2983,64 @@ test.describe( 'Products API tests: List All Products', () => {
test( 'default', async ( { request } ) => {
// Default = date desc.
const result = await request.get( 'wp-json/wc/v3/products', {
const result = await request.get(
'wp-json/wc/v3/products',
{
params: {
search: 'xxx',
},
} );
}
);
const resultJSON = await result.json();
expect( result.status() ).toEqual( 200 );
// Verify all dates are in descending order.
let lastDate = Date.now();
resultJSON.forEach( ( { date_created_gmt } ) => {
const created = Date.parse( date_created_gmt + '.000Z' );
const created = Date.parse(
date_created_gmt + '.000Z'
);
expect( lastDate ).toBeGreaterThan( created );
lastDate = created;
} );
} );
test( 'date', async ( { request } ) => {
const result = await request.get( 'wp-json/wc/v3/products', {
const result = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'date',
search: 'xxx',
},
} );
}
);
const resultJSON = await result.json();
expect( result.status() ).toEqual( 200 );
// Verify all dates are in ascending order.
let lastDate = 0;
resultJSON.forEach( ( { date_created_gmt } ) => {
const created = Date.parse( date_created_gmt + '.000Z' );
const created = Date.parse(
date_created_gmt + '.000Z'
);
expect( created ).toBeGreaterThan( lastDate );
lastDate = created;
} );
} );
test( 'id', async ( { request } ) => {
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'id',
search: 'xxx',
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
@ -3002,13 +3051,16 @@ test.describe( 'Products API tests: List All Products', () => {
lastId = id;
} );
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'id',
search: 'xxx',
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
@ -3021,14 +3073,17 @@ test.describe( 'Products API tests: List All Products', () => {
} );
test( 'title', async ( { request } ) => {
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'title',
per_page: productNamesAsc.length,
search: 'xxx',
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
@ -3037,14 +3092,17 @@ test.describe( 'Products API tests: List All Products', () => {
expect( name ).toBe( productNamesAsc[ idx ] );
} );
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'title',
per_page: productNamesDesc.length,
search: 'xxx',
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
@ -3063,14 +3121,17 @@ test.describe( 'Products API tests: List All Products', () => {
...productNamesBySlugAsc,
].reverse();
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'slug',
per_page: productNamesBySlugAsc.length,
search: 'xxx',
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
@ -3079,14 +3140,17 @@ test.describe( 'Products API tests: List All Products', () => {
expect( name ).toBe( productNamesBySlugAsc[ idx ] );
} );
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'slug',
per_page: productNamesBySlugDesc.length,
search: 'xxx',
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
@ -3119,14 +3183,17 @@ test.describe( 'Products API tests: List All Products', () => {
'Belt xxx',
'Sunglasses xxx',
];
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'price',
per_page: productNamesMinPriceAsc.length,
search: 'xxx',
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
expect( result1JSON ).toHaveLength(
@ -3163,14 +3230,17 @@ test.describe( 'Products API tests: List All Products', () => {
'Parent Product xxx',
];
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'price',
per_page: productNamesMaxPriceDesc.length,
search: 'xxx',
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
expect( result2JSON ).toHaveLength(
@ -3192,13 +3262,16 @@ test.describe( 'Products API tests: List All Products', () => {
sampleData.hierarchicalProducts.parentJSON.id,
];
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'include',
include: includeIds.join( ',' ),
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
@ -3209,13 +3282,16 @@ test.describe( 'Products API tests: List All Products', () => {
expect( id ).toBe( includeIds[ idx ] );
} );
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'include',
include: includeIds.join( ',' ),
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
expect( result2JSON ).toHaveLength( includeIds.length );
@ -3227,14 +3303,17 @@ test.describe( 'Products API tests: List All Products', () => {
} );
test( 'rating (desc)', async ( { request } ) => {
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'rating',
per_page: productNamesByRatingDesc.length,
search: 'xxx',
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
@ -3247,14 +3326,17 @@ test.describe( 'Products API tests: List All Products', () => {
// This case will remain skipped until ratings can be sorted ascending.
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
test.skip( 'rating (asc)', async ( { request } ) => {
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'rating',
per_page: productNamesByRatingAsc.length,
search: 'xxx',
},
} );
}
);
expect( result1.status() ).toEqual( 200 );
const result1JSON = await result1.json();
@ -3267,40 +3349,51 @@ test.describe( 'Products API tests: List All Products', () => {
// This case will remain skipped until popularity can be sorted ascending.
// See: https://github.com/woocommerce/woocommerce/issues/30354#issuecomment-925955099.
test.skip( 'popularity (asc)', async ( { request } ) => {
const result1 = await request.get( 'wp-json/wc/v3/products', {
const result1 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'asc',
orderby: 'popularity',
per_page: productNamesByPopularityAsc.length,
search: 'xxx',
},
} );
}
);
const result1JSON = await result1.json();
expect( result1.status() ).toEqual( 200 );
// Verify all results are in ascending order.
result1JSON.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesByPopularityAsc[ idx ] );
expect( name ).toBe(
productNamesByPopularityAsc[ idx ]
);
} );
} );
test( 'popularity (desc)', async ( { request } ) => {
const result2 = await request.get( 'wp-json/wc/v3/products', {
const result2 = await request.get(
'wp-json/wc/v3/products',
{
params: {
order: 'desc',
orderby: 'popularity',
per_page: productNamesByPopularityDesc.length,
search: 'xxx',
},
} );
}
);
const result2JSON = await result2.json();
expect( result2.status() ).toEqual( 200 );
// Verify all results are in descending order.
result2JSON.forEach( ( { name }, idx ) => {
expect( name ).toBe( productNamesByPopularityDesc[ idx ] );
} );
expect( name ).toBe(
productNamesByPopularityDesc[ idx ]
);
} );
} );
} );
}
);
} );

View File

@ -589,7 +589,10 @@ test.describe( 'Products API tests: CRUD', () => {
} );
} );
test.describe( 'Product review tests: CRUD', () => {
test.describe(
'Product review tests: CRUD',
{ tag: '@skip-on-default-wpcom' },
() => {
let productReviewId;
let reviewsTestProduct;
@ -616,7 +619,9 @@ test.describe( 'Products API tests: CRUD', () => {
expect( response.status() ).toEqual( 201 );
expect( typeof productReviewId ).toEqual( 'number' );
expect( responseJSON.id ).toEqual( productReviewId );
expect( responseJSON.product_name ).toEqual( 'A Simple Product' );
expect( responseJSON.product_name ).toEqual(
'A Simple Product'
);
expect( responseJSON.status ).toEqual( 'approved' );
expect( responseJSON.reviewer ).toEqual( 'John Doe' );
expect( responseJSON.reviewer_email ).toEqual(
@ -684,8 +689,12 @@ test.describe( 'Products API tests: CRUD', () => {
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( responseJSON.id ).toEqual( productReviewId );
expect( responseJSON.product_id ).toEqual( reviewsTestProduct.id );
expect( responseJSON.product_name ).toEqual( 'A Simple Product' );
expect( responseJSON.product_id ).toEqual(
reviewsTestProduct.id
);
expect( responseJSON.product_name ).toEqual(
'A Simple Product'
);
expect( responseJSON.status ).toEqual( 'approved' );
expect( responseJSON.reviewer ).toEqual( 'John Doe' );
expect( responseJSON.reviewer_email ).toEqual(
@ -722,8 +731,12 @@ test.describe( 'Products API tests: CRUD', () => {
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( responseJSON.id ).toEqual( productReviewId );
expect( responseJSON.product_id ).toEqual( reviewsTestProduct.id );
expect( responseJSON.product_name ).toEqual( 'A Simple Product' );
expect( responseJSON.product_id ).toEqual(
reviewsTestProduct.id
);
expect( responseJSON.product_name ).toEqual(
'A Simple Product'
);
expect( responseJSON.status ).toEqual( 'approved' );
expect( responseJSON.reviewer ).toEqual( 'John Doe' );
expect( responseJSON.reviewer_email ).toEqual(
@ -752,7 +765,9 @@ test.describe( 'Products API tests: CRUD', () => {
const getDeletedProductReviewResponse = await request.get(
`wp-json/wc/v3/products/reviews/${ productReviewId }`
);
expect( getDeletedProductReviewResponse.status() ).toEqual( 404 );
expect( getDeletedProductReviewResponse.status() ).toEqual(
404
);
} );
test( 'can batch update product reviews', async ( { request } ) => {
@ -788,7 +803,9 @@ test.describe( 'Products API tests: CRUD', () => {
expect( responseJSON.create[ 0 ].review ).toEqual(
'Nice product!'
);
expect( responseJSON.create[ 0 ].reviewer ).toEqual( 'John Doe' );
expect( responseJSON.create[ 0 ].reviewer ).toEqual(
'John Doe'
);
expect( responseJSON.create[ 0 ].reviewer_email ).toEqual(
'john.doe@example.com'
);
@ -800,7 +817,9 @@ test.describe( 'Products API tests: CRUD', () => {
expect( responseJSON.create[ 1 ].review ).toEqual(
'I love this thing!'
);
expect( responseJSON.create[ 1 ].reviewer ).toEqual( 'Jane Doe' );
expect( responseJSON.create[ 1 ].reviewer ).toEqual(
'Jane Doe'
);
expect( responseJSON.create[ 1 ].reviewer_email ).toEqual(
'Jane.doe@example.com'
);
@ -833,7 +852,8 @@ test.describe( 'Products API tests: CRUD', () => {
},
}
);
const responseBatchUpdateJSON = await responseBatchUpdate.json();
const responseBatchUpdateJSON =
await responseBatchUpdate.json();
const review3Id = responseBatchUpdateJSON.create[ 0 ].id;
expect( response.status() ).toEqual( 200 );
@ -851,7 +871,9 @@ test.describe( 'Products API tests: CRUD', () => {
const getDeletedProductReviewResponse = await request.get(
`wp-json/wc/v3/products/reviews/${ review2Id }`
);
expect( getDeletedProductReviewResponse.status() ).toEqual( 404 );
expect( getDeletedProductReviewResponse.status() ).toEqual(
404
);
// Batch delete the created tags
await request.post( `wp-json/wc/v3/products/reviews/batch`, {
@ -860,7 +882,8 @@ test.describe( 'Products API tests: CRUD', () => {
},
} );
} );
} );
}
);
test.describe( 'Product shipping classes tests: CRUD', () => {
let productShippingClassId;
@ -958,9 +981,10 @@ test.describe( 'Products API tests: CRUD', () => {
);
} );
test( 'can batch update product shipping classes', async ( {
request,
} ) => {
test(
'can batch update product shipping classes',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
async ( { request } ) => {
// Batch create product shipping classes.
const response = await request.post(
`wp-json/wc/v3/products/shipping_classes/batch`,
@ -979,8 +1003,12 @@ test.describe( 'Products API tests: CRUD', () => {
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( responseJSON.create[ 0 ].name ).toEqual( 'Small Items' );
expect( responseJSON.create[ 1 ].name ).toEqual( 'Large Items' );
expect( responseJSON.create[ 0 ].name ).toEqual(
'Small Items'
);
expect( responseJSON.create[ 1 ].name ).toEqual(
'Large Items'
);
const shippingClass1Id = responseJSON.create[ 0 ].id;
const shippingClass2Id = responseJSON.create[ 1 ].id;
@ -1004,7 +1032,8 @@ test.describe( 'Products API tests: CRUD', () => {
},
}
);
const responseBatchUpdateJSON = await responseBatchUpdate.json();
const responseBatchUpdateJSON =
await responseBatchUpdate.json();
const shippingClass3Id = responseBatchUpdateJSON.create[ 0 ].id;
expect( response.status() ).toEqual( 200 );
@ -1018,12 +1047,13 @@ test.describe( 'Products API tests: CRUD', () => {
);
// Verify that the product tag can no longer be retrieved.
const getDeletedProductShippingClassResponse = await request.get(
const getDeletedProductShippingClassResponse =
await request.get(
`wp-json/wc/v3/products/shipping_classes/${ shippingClass2Id }`
);
expect( getDeletedProductShippingClassResponse.status() ).toEqual(
404
);
expect(
getDeletedProductShippingClassResponse.status()
).toEqual( 404 );
// Batch delete the created tags
await request.post(
@ -1034,7 +1064,8 @@ test.describe( 'Products API tests: CRUD', () => {
},
}
);
} );
}
);
} );
test.describe( 'Product tags tests: CRUD', () => {

View File

@ -280,7 +280,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
type: 'text',
default: '',
tip: 'The street address for your business location.',
value: '',
value: expect.any( String ),
} ),
] )
);
@ -310,7 +310,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
type: 'text',
default: '',
tip: 'The city in which your business is located.',
value: '',
value: expect.any( String ),
} ),
] )
);
@ -341,7 +341,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
type: 'text',
default: '',
tip: 'The postal code, if any, in which your business is located.',
value: '',
value: expect.any( String ),
} ),
] )
);
@ -1019,9 +1019,14 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
} );
test.describe( 'List all Tax settings options', () => {
test( 'can retrieve all tax settings', async ( { request } ) => {
test(
'can retrieve all tax settings',
{ tag: [ '@skip-on-default-pressable', '@skip-on-default-wpcom' ] },
async ( { request } ) => {
// call API to retrieve all settings options
const response = await request.get( '/wp-json/wc/v3/settings/tax' );
const response = await request.get(
'/wp-json/wc/v3/settings/tax'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
expect( Array.isArray( responseJSON ) ).toBe( true );
@ -1072,7 +1077,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
type: 'select',
default: 'inherit',
options: {
inherit: 'Shipping tax class based on cart items',
inherit:
'Shipping tax class based on cart items',
'': 'Standard',
'reduced-rate': 'Reduced rate',
'zero-rate': 'Zero rate',
@ -1169,7 +1175,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
} ),
] )
);
} );
}
);
} );
test.describe( 'List all Shipping settings options', () => {
@ -1613,8 +1620,13 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
} );
} );
test.describe( 'List all Advanced settings options', () => {
test( 'can retrieve all advanced settings', async ( { request } ) => {
test.describe(
'List all Advanced settings options',
{ tag: '@skip-on-default-wpcom' },
() => {
test( 'can retrieve all advanced settings', async ( {
request,
} ) => {
// call API to retrieve all settings options
const response = await request.get(
'/wp-json/wc/v3/settings/advanced'
@ -1668,7 +1680,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
expect.objectContaining( {
id: 'woocommerce_myaccount_page_id',
label: 'My account page',
description: 'Page contents: [woocommerce_my_account]',
description:
'Page contents: [woocommerce_my_account]',
type: 'select',
default: '',
tip: 'Page contents: [woocommerce_my_account]',
@ -1871,7 +1884,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
expect.objectContaining( {
id: 'woocommerce_show_marketplace_suggestions',
label: 'Show Suggestions',
description: 'Display suggestions within WooCommerce',
description:
'Display suggestions within WooCommerce',
type: 'checkbox',
default: 'yes',
tip: 'Leave this box unchecked if you do not want to pull suggested extensions from WooCommerce.com. You will see a static list of extensions instead.',
@ -1892,7 +1906,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
] )
);
} );
} );
}
);
test.describe( 'List all Email New Order settings', () => {
test( 'can retrieve all email new order settings', async ( {
@ -1930,7 +1945,7 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
tip: expect.stringContaining(
'Enter recipients (comma separated) for this email. Defaults to'
),
value: '',
value: expect.any( String ),
} ),
] )
);
@ -1998,9 +2013,10 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
} );
test.describe( 'List all Email Failed Order settings', () => {
test( 'can retrieve all email failed order settings', async ( {
request,
} ) => {
test(
'can retrieve all email failed order settings',
{ tag: '@skip-on-default-pressable' },
async ( { request } ) => {
// call API to retrieve all settings options
const response = await request.get(
'/wp-json/wc/v3/settings/email_failed_order'
@ -2085,7 +2101,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
expect.objectContaining( {
id: 'email_type',
label: 'Email type',
description: 'Choose which format of email to send.',
description:
'Choose which format of email to send.',
type: 'select',
default: 'html',
options: {
@ -2098,7 +2115,8 @@ test.describe.serial( 'Settings API tests: CRUD', () => {
} ),
] )
);
} );
}
);
} );
test.describe( 'List all Email Customer On Hold Order settings', () => {

View File

@ -3,9 +3,14 @@ const { BASE_URL } = process.env;
const shouldSkip = BASE_URL !== undefined && ! BASE_URL.includes( 'localhost' );
test.describe( 'System Status API tests', () => {
test( 'can view all system status items', async ( { request } ) => {
test(
'can view all system status items',
{ tag: '@skip-on-default-wpcom' },
async ( { request } ) => {
// call API to view all system status items
const response = await request.get( '/wp-json/wc/v3/system_status' );
const response = await request.get(
'/wp-json/wc/v3/system_status'
);
const responseJSON = await response.json();
expect( response.status() ).toEqual( 200 );
@ -99,12 +104,14 @@ test.describe( 'System Status API tests', () => {
maxmind_geoip_database: expect.any( String ),
database_tables: expect.objectContaining( {
woocommerce: expect.objectContaining( {
wp_woocommerce_sessions: expect.objectContaining( {
wp_woocommerce_sessions:
expect.objectContaining( {
data: expect.any( String ),
index: expect.any( String ),
engine: expect.any( String ),
} ),
wp_woocommerce_api_keys: expect.objectContaining( {
wp_woocommerce_api_keys:
expect.objectContaining( {
data: expect.any( String ),
index: expect.any( String ),
engine: expect.any( String ),
@ -121,20 +128,20 @@ test.describe( 'System Status API tests', () => {
index: expect.any( String ),
engine: expect.any( String ),
} ),
wp_woocommerce_order_items: expect.objectContaining(
{
wp_woocommerce_order_items:
expect.objectContaining( {
data: expect.any( String ),
index: expect.any( String ),
engine: expect.any( String ),
}
),
} ),
wp_woocommerce_order_itemmeta:
expect.objectContaining( {
data: expect.any( String ),
index: expect.any( String ),
engine: expect.any( String ),
} ),
wp_woocommerce_tax_rates: expect.objectContaining( {
wp_woocommerce_tax_rates:
expect.objectContaining( {
data: expect.any( String ),
index: expect.any( String ),
engine: expect.any( String ),
@ -578,7 +585,8 @@ test.describe( 'System Status API tests', () => {
] ),
} )
);
} );
}
);
test( 'can view all system status tools', async ( { request } ) => {
// call API to view system status tools