Added new tests to wp-admin-order-edit.test.js for covering merchant management actions over downloadable products. (#31650)

* Added new tests to wp-admin-order-edit.test.js for covering merchant management actions over downloadable products.

* Updated changelogs from e2e-utils and e2e-core packages.

* - Fixed minor issue from wp-admin-order-edit.test.js when running tests in browser mode.
- Changed those tests so that products and orders are deleted individually after every test run.
- Added deleteOrder() and deleteProduct() functions to withRestApi.

Co-authored-by: rodelgc <rodel.calasagsag@automattic.com>
This commit is contained in:
Alejandro López Ariza 2022-02-28 08:24:33 +01:00 committed by GitHub
parent d68b6d7777
commit 5af5be5100
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 383 additions and 10 deletions

View File

@ -2,16 +2,20 @@
## Fixed
- Updated assertion in the block `can update order details` from the e2e test `order-edit.test.js` that wasn't checking properly the date value when editing an order, allowing the test to return a false positive.
- Moved `merchant.login()` out of `beforeAll()` block and into test body for retried runs.
## Added
- Additional Merchant Order Edit tests to increase the downloadable products coverage.
- A `specs/data` folder to store page element data.
- Tests to verify that different top-level menus and their associated sub-menus load successfully.
- Test scaffolding via `npx wc-e2e install @woocommerce/e2e-core-tests`
## Changed
- The e2e test `update-product-settings.test.js` now covers setting and unsetting the `X-Accel-Redirect/X-Sendfile` download method and `Append a unique string to filename for security` flag.
- The e2e test `order-edit.test.js` now uses the API to create orders.
- New coupon test deletes the coupon instead of trashing it.
- A copy of sample_data.csv is included in the package.

View File

@ -3,22 +3,30 @@
*/
const {
merchant,
createSimpleOrder,
withRestApi,
utils,
createSimpleDownloadableProduct,
createOrder,
verifyValueOfInputField,
orderPageSaveChanges,
} = require( '@woocommerce/e2e-utils' );
let orderId;
const orderStatus = {
processing: 'processing',
completed: 'completed'
};
const runEditOrderTest = () => {
describe('WooCommerce Orders > Edit order', () => {
beforeAll(async () => {
orderId = await createOrder( { status: orderStatus.processing } );
await merchant.login();
orderId = await createSimpleOrder('Processing');
});
afterAll( async () => {
await withRestApi.deleteAllOrders();
await withRestApi.deleteOrder( orderId );
});
it('can view single order', async () => {
@ -69,12 +77,156 @@ const runEditOrderTest = () => {
await utils.waitForTimeout( 2000 );
// Save the order changes
await expect( page ).toClick( 'button.save_order' );
await page.waitForSelector( '#message' );
await orderPageSaveChanges();
// Verify
await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } );
await expect( page ).toMatchElement( 'input[name=order_date]', { value: '2018-12-14' } );
await verifyValueOfInputField( 'input[name=order_date]' , '2018-12-14' );
});
});
describe( 'WooCommerce Orders > Edit order > Downloadable product permissions', () => {
const productName = 'TDP 001';
const customerBilling = {
email: 'john.doe@example.com',
};
let productId;
beforeAll( async () => {
await merchant.login();
} );
beforeEach(async () => {
productId = await createSimpleDownloadableProduct( productName );
orderId = await createOrder( {
productId,
customerBilling ,
status: orderStatus.processing
} );
} );
afterEach( async () => {
await withRestApi.deleteOrder( orderId );
await withRestApi.deleteProduct( productId );
} );
it( 'can add downloadable product permissions to order without product', async () => {
// Create order without product
const newOrderId = await createOrder( {
customerBilling,
status: orderStatus.processing
} );
// Open order we created
await merchant.goToOrder( newOrderId );
// Add permission
await merchant.addDownloadableProductPermission( productName );
// Verify new downloadable product permission details
await merchant.verifyDownloadableProductPermission( productName )
// Remove order
await withRestApi.deleteOrder( newOrderId );
} );
it( 'can add downloadable product permissions to order with product', async () => {
// Create new downloadable product
const newProductName = 'TDP 002';
const newProductId = await createSimpleDownloadableProduct( newProductName );
// Open order we created
await merchant.goToOrder( orderId );
// Add permission
await merchant.addDownloadableProductPermission( newProductName );
// Verify new downloadable product permission details
await merchant.verifyDownloadableProductPermission( newProductName )
// Remove product
await withRestApi.deleteProduct( newProductId );
} );
it( 'can edit downloadable product permissions', async () => {
// Define expected downloadable product attributes
const expectedDownloadsRemaining = '10';
const expectedDownloadsExpirationDate = '2050-01-01';
// Open order we created
await merchant.goToOrder( orderId );
// Update permission
await merchant.updateDownloadableProductPermission(
productName,
expectedDownloadsExpirationDate,
expectedDownloadsRemaining
);
// Verify new downloadable product permission details
await merchant.verifyDownloadableProductPermission(
productName,
expectedDownloadsExpirationDate,
expectedDownloadsRemaining
);
} );
it( 'can revoke downloadable product permissions', async () => {
// Open order we created
await merchant.goToOrder( orderId );
// Revoke permission
await merchant.revokeDownloadableProductPermission( productName );
// Verify
await expect( page ).not.toMatchElement( 'div.order_download_permissions', {
text: productName
} );
} );
it( 'should not allow downloading a product if download attempts are exceeded', async () => {
// Define expected download error reason
const expectedReason = 'Sorry, you have reached your download limit for this file';
// Create order with product without any available download attempt
const newProductId = await createSimpleDownloadableProduct( productName, 0 );
const newOrderId = await createOrder( {
productId: newProductId,
customerBilling,
status: orderStatus.processing
} );
// Open order we created
await merchant.goToOrder( newOrderId );
// Open download page
const downloadPage = await merchant.openDownloadLink();
// Verify file download cannot start
await merchant.verifyCannotDownloadFromBecause( downloadPage, expectedReason );
// Remove data
await withRestApi.deleteOrder( newOrderId );
await withRestApi.deleteProduct( newProductId );
} );
it( 'should not allow downloading a product if expiration date is exceeded', async () => {
// Define expected download error reason
const expectedReason = 'Sorry, this download has expired';
// Open order we created
await merchant.goToOrder( orderId );
// Update permission so that the expiration date has already passed
// Note: Seems this operation can't be performed through the API
await merchant.updateDownloadableProductPermission( productName, '2018-12-14' );
// Open download page
const downloadPage = await merchant.openDownloadLink();
// Verify file download cannot start
await merchant.verifyCannotDownloadFromBecause( downloadPage, expectedReason );
} );
} );
}

View File

@ -29,6 +29,7 @@ const runProductSettingsTest = () => {
await setCheckbox('#woocommerce_downloads_require_login');
await setCheckbox('#woocommerce_downloads_grant_access_after_payment');
await setCheckbox('#woocommerce_downloads_redirect_fallback_allowed');
await unsetCheckbox('#woocommerce_downloads_add_hash_to_filename');
await settingsPageSaveChanges();
// Verify that settings have been saved
@ -38,22 +39,35 @@ const runProductSettingsTest = () => {
verifyCheckboxIsSet('#woocommerce_downloads_require_login'),
verifyCheckboxIsSet('#woocommerce_downloads_grant_access_after_payment'),
verifyCheckboxIsSet('#woocommerce_downloads_redirect_fallback_allowed'),
verifyCheckboxIsUnset('#woocommerce_downloads_add_hash_to_filename')
]);
await page.reload();
await expect(page).toSelect('#woocommerce_file_download_method', 'Force downloads');
await expect(page).toSelect('#woocommerce_file_download_method', 'X-Accel-Redirect/X-Sendfile');
await unsetCheckbox('#woocommerce_downloads_require_login');
await unsetCheckbox('#woocommerce_downloads_grant_access_after_payment');
await unsetCheckbox('#woocommerce_downloads_redirect_fallback_allowed');
await setCheckbox('#woocommerce_downloads_add_hash_to_filename');
await settingsPageSaveChanges();
// Verify that settings have been saved
await Promise.all([
expect(page).toMatchElement('#message', {text: 'Your settings have been saved.'}),
expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'Force downloads'}),
expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'X-Accel-Redirect/X-Sendfile'}),
verifyCheckboxIsUnset('#woocommerce_downloads_require_login'),
verifyCheckboxIsUnset('#woocommerce_downloads_grant_access_after_payment'),
verifyCheckboxIsUnset('#woocommerce_downloads_redirect_fallback_allowed'),
verifyCheckboxIsSet('#woocommerce_downloads_add_hash_to_filename')
]);
await page.reload();
await expect(page).toSelect('#woocommerce_file_download_method', 'Force downloads');
await settingsPageSaveChanges();
// Verify that settings have been saved
await Promise.all([
expect(page).toMatchElement('#message', {text: 'Your settings have been saved.'}),
expect(page).toMatchElement('#woocommerce_file_download_method', {text: 'Force downloads'})
]);
});
});

View File

@ -1,5 +1,22 @@
# Unreleased
## Added
- `createSimpleDownloadableProduct` component which creates a simple downloadable product, containing four parameters for title, price, download name and download limit.
- `orderPageSaveChanges()` to save changes in the order page.
- `getSelectorAttribute( selector, attribute )` to retrieve the desired HTML attribute from an element.
- `verifyValueOfElementAttribute( selector, attribute, expectedValue )` to check that a specific HTML attribute from an element matches the expected value.
- `withRestApi.deleteProduct()` that deletes a single product.
- `withRestApi.deleteOrder()` that deletes a single order.
- `merchant.addDownloadableProductPermission()` to add a downloadable product permission to an order.
- `merchant.updateDownloadableProductPermission()` to update the attributes of an existing downloadable product permission.
- `merchant.revokeDownloadableProductPermission()` to remove the existing downloadable product permission from an order.
- `merchant.verifyDownloadableProductPermission()` to check that the attributes of an existing downloadable product permission are correct.
- `merchant.openDownloadLink()` to open the url of a download in a new tab.
- `merchant.verifyCannotDownloadFromBecause()` to check that a download cannot happen for a specific reason.
# 0.1.7
## Fixed
- Identified the default product category using `slug == 'uncategorized'` in `deleteAllProductCategories`

View File

@ -84,6 +84,7 @@ This package provides support for enabling retries in tests:
| Function | Parameters | Description |
|----------|-------------|------------|
| `addDownloadableProductPermission` | `productName` | Add a downloadable permission for product in order |
| `collapseAdminMenu` | `collapse` | Collapse or expand the WP admin menu |
| `dismissOnboardingWizard` | | Dismiss the onboarding wizard if present |
| `goToOrder` | `orderId` | Go to view a single order |
@ -107,11 +108,16 @@ This package provides support for enabling retries in tests:
| `openImportProducts` | | Open the Import Products page |
| `openExtensions` | | Go to WooCommerce -> Extensions |
| `openWordPressUpdatesPage` | | Go to Dashboard -> Updates |
| `revokeDownloadableProductPermission` | `productName` | Remove a downloadable product permission from order |
| `installAllUpdates` | | Install all pending updates on Dashboard -> Updates|
| `updateDownloadableProductPermission` | `productName, expirationDate, downloadsRemaining` | Update the attributes of a downloadable product permission in order |
| `updateWordPress` | | Install pending WordPress updates on Dashboard -> Updates|
| `updatePlugins` | | Install all pending plugin updates on Dashboard -> Updates|
| `updateThemes` | | Install all pending theme updates on Dashboard -> Updates|
| `verifyCannotDownloadFromBecause` | `page, reason` | Verify that cannot download a product from `page` because of `reason` |
| `verifyDownloadableProductPermission` | `productName, expirationDate, downloadsRemaining` | Verify the attributes of a downloadable product permission in order |
| `runDatabaseUpdate` || Runs the database update if needed |
| `openDownloadLink` | | Open the download link of a product |
### Shopper `shopper`
@ -160,6 +166,8 @@ Please note: if you're using a non-SSL environment (such as a Docker container f
| `deleteAllShippingZones` | `testResponse` | Permanently delete all shipping zones except the default |
| `deleteCoupon` | `couponId` | Permanently delete a coupon |
| `deleteCustomerByEmail` | `emailAddress` | Delete customer user account. Posts are reassigned to user ID 1 |
| `deleteOrder` | `orderId` | Permanently delete an order |
| `deleteProduct` | `productId` | Permanently delete a simple product |
| `getSystemEnvironment` | | Get the current environment from the WooCommerce system status API. |
| `resetOnboarding` | | Reset onboarding settings |
| `resetSettingsGroupToDefault` | `settingsGroup`, `testResponse` | Reset settings in settings group to default except `select` fields |
@ -203,13 +211,16 @@ There is a general utilities object `utils` with the following functions:
| `completeOnboardingWizard` | | completes the onboarding wizard with some default settings |
| `createCoupon` | `couponAmount`, `couponType` | creates a basic coupon. Default amount is 5. Default coupon type is fixed discount. Returns the generated coupon code. |
| `createGroupedProduct` | | creates a grouped product for the grouped product tests. Returns the product id. |
| `createSimpleDownloadableProduct` | `name, downloadLimit, downloadName, price` | Create a simple downloadable product |
| `createSimpleOrder` | `status` | creates a basic order with the provided status string |
| `createSimpleProduct` | | creates the simple product configured in default.json. Returns the product id. |
| `createSimpleProductWithCategory` | `name`, `price`,`categoryName` | creates a simple product used passed values. Returns the product id. |
| `createVariableProduct` | | creates a variable product for the variable product tests. Returns the product id. |
| `deleteAllEmailLogs` | | deletes the emails generated by WP Mail Logging plugin |
| `evalAndClick` | `selector` | helper method that clicks an element inserted in the DOM by a script |
| `getSelectorAttribute` | `selector, attribute` | Retrieve the desired HTML attribute from a selector |
| `moveAllItemsToTrash` | | helper method that checks every item in a list page and moves them to the trash |
| `orderPageSaveChanges` | | Save the current order page |
| `permalinkSettingsPageSaveChanges` | | Save the current Permalink settings |
| `removeCoupon` | | helper method that removes a single coupon within cart or checkout |
| `selectOptionInSelect2` | `selector, value` | helper method that searchs for select2 type fields and select plus insert value inside |
@ -222,6 +233,7 @@ There is a general utilities object `utils` with the following functions:
| `verifyCheckboxIsSet` | `selector` | Verify that a checkbox is checked |
| `verifyCheckboxIsUnset` | `selector` | Verify that a checkbox is unchecked |
| `verifyPublishAndTrash` | `button, publishNotice, publishVerification, trashVerification` | Verify that an item can be published and trashed |
| `verifyValueOfElementAttribute` | `selector, attribute, expectedValue` | Assert the value of the desired HTML attribute of a selector |
| `verifyValueOfInputField` | `selector, value` | Verify an input contains the passed value |
| `clickFilter` | `selector` | Click on a list page filter |
| `moveAllItemsToTrash` | | Moves all items in a list view to the Trash |

View File

@ -27,6 +27,8 @@ const simpleProductPrice = config.has('products.simple.price') ? config.get('pro
const defaultVariableProduct = config.get('products.variable');
const defaultGroupedProduct = config.get('products.grouped');
const uuid = require( 'uuid' );
/**
* Verify and publish
*
@ -233,6 +235,28 @@ const createSimpleProductWithCategory = async ( productName, productPrice, categ
return product.id;
};
/**
* Create simple downloadable product
*
* @param name Product's name. Defaults to 'Simple Product' (see createSimpleProduct definition).
* @param downloadLimit Product's download limit. Defaults to '-1' (unlimited).
* @param downloadName Product's download name. Defaults to 'Single'.
* @param price Product's price. Defaults to '$9.99' (see createSimpleProduct definition).
*/
const createSimpleDownloadableProduct = async ( name, downloadLimit = -1, downloadName = 'Single', price ) => {
const productDownloadDetails = {
downloadable: true,
downloads: [ {
id: uuid.v4(),
name: downloadName,
file: 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg'
} ],
download_limit: downloadLimit
};
return await createSimpleProduct( name, price, productDownloadDetails );
};
/**
* Create variable product.
* Also, create variations for all attributes.
@ -594,6 +618,7 @@ export {
createCoupon,
addShippingZoneAndMethod,
createSimpleProductWithCategory,
createSimpleDownloadableProduct,
clickUpdateOrder,
deleteAllEmailLogs,
deleteAllShippingZones,

View File

@ -6,7 +6,16 @@ const config = require( 'config' );
/**
* Internal dependencies
*/
const { clearAndFillInput, setCheckbox } = require( '../page-utils' );
const {
clearAndFillInput,
selectOptionInSelect2,
setCheckbox,
verifyValueOfInputField,
getSelectorAttribute,
orderPageSaveChanges,
verifyValueOfElementAttribute,
} = require( '../page-utils' );
const {
WP_ADMIN_ALL_ORDERS_VIEW,
WP_ADMIN_ALL_PRODUCTS_VIEW,
@ -36,6 +45,12 @@ const { getSlug, waitForTimeout } = require('./utils');
const baseUrl = config.get( 'url' );
const WP_ADMIN_SINGLE_CPT_VIEW = ( postId ) => baseUrl + `wp-admin/post.php?post=${ postId }&action=edit`;
// Reusable selectors
const INPUT_DOWNLOADS_REMAINING = 'input[name="downloads_remaining[0]"]';
const INPUT_EXPIRATION_DATE = 'input[name="access_expires[0]"]';
const ORDER_DOWNLOADS = '#woocommerce-order-downloads';
const BTN_COPY_DOWNLOAD_LINK = '#copy-download-link';
const merchant = {
login: async () => {
await page.goto( WP_ADMIN_LOGIN, {
@ -149,7 +164,6 @@ const merchant = {
} );
},
goToOrder: async ( orderId ) => {
await page.goto( WP_ADMIN_SINGLE_CPT_VIEW( orderId ), {
waitUntil: 'networkidle0',
@ -198,6 +212,91 @@ const merchant = {
}
},
addDownloadableProductPermission: async ( productName ) => {
// Add downloadable product permission
await selectOptionInSelect2( productName );
await expect( page ).toClick( 'button.grant_access' );
// Save the order changes
await orderPageSaveChanges();
},
updateDownloadableProductPermission: async ( productName, expirationDate, downloadsRemaining ) => {
// Update downloadable product permission
await expect(page).toClick( ORDER_DOWNLOADS, { text: productName } );
if ( downloadsRemaining ) {
await clearAndFillInput( INPUT_DOWNLOADS_REMAINING, downloadsRemaining );
}
if ( expirationDate ) {
await clearAndFillInput( INPUT_EXPIRATION_DATE, expirationDate );
}
// Save the order changes
await orderPageSaveChanges();
},
revokeDownloadableProductPermission: async ( productName ) => {
// Revoke downloadable product permission
const permission = await expect(page).toMatchElement( 'div.wc-metabox > h3', { text: productName } );
await expect( permission ).toClick('button.revoke_access');
// Wait for auto save
await waitForTimeout( 2000 );
// Save the order changes
await orderPageSaveChanges();
},
verifyDownloadableProductPermission: async ( productName, expirationDate = '', downloadsRemaining = '' ) => {
// Open downloadable product permission details
await expect(page).toClick( ORDER_DOWNLOADS, { text: productName } );
// Verify downloads remaining
await verifyValueOfElementAttribute( INPUT_DOWNLOADS_REMAINING, 'placeholder', 'Unlimited' );
await verifyValueOfInputField( INPUT_DOWNLOADS_REMAINING, downloadsRemaining );
// Verify downloads expiration date
await verifyValueOfElementAttribute( INPUT_EXPIRATION_DATE, 'placeholder', 'Never' );
await verifyValueOfInputField( INPUT_EXPIRATION_DATE, expirationDate );
// Verify 'Copy link' and 'View report' buttons are available
await expect( page ).toMatchElement( BTN_COPY_DOWNLOAD_LINK, { text: 'Copy link'} );
await expect( page ).toMatchElement( '.button', { text: 'View report' } );
},
openDownloadLink: async () => {
// Open downloadable product permission details
await expect( page ).toClick( '#woocommerce-order-downloads > div.inside > div > div.wc-metaboxes > div' );
// Get download link
const downloadLink = await getSelectorAttribute( BTN_COPY_DOWNLOAD_LINK, 'href' );
const newPage = await browser.newPage();
// Open download link in new tab
await newPage.goto( downloadLink , {
waitUntil: 'networkidle0',
} );
return newPage;
},
verifyCannotDownloadFromBecause: async ( page, reason ) => {
// Select download page tab
await page.bringToFront();
// Verify error in download page
await expect( page.title() ).resolves.toMatch( 'WordPress Error' );
await expect( page ).toMatchElement( 'div.wp-die-message', {
text: reason
} );
// Close tab
await page.close();
},
openNewShipping: async () => {
await page.goto( WP_ADMIN_NEW_SHIPPING_ZONE, {
waitUntil: 'networkidle0',

View File

@ -108,6 +108,16 @@ export const withRestApi = {
const repository = SimpleProduct.restRepository( client );
await deleteAllRepositoryObjects( repository );
},
/**
* Use api package to delete a product.
*
* @param {number} productId Product ID.
* @return {Promise} Promise resolving once the product has been deleted.
*/
deleteProduct: async ( productId ) => {
const repository = SimpleProduct.restRepository( client );
await repository.delete( productId );
},
/**
* Use the API to delete all product attributes.
*
@ -177,6 +187,16 @@ export const withRestApi = {
const repository = Order.restRepository( client );
await deleteAllRepositoryObjects( repository, null, orderStatuses );
},
/**
* Use api package to delete an order.
*
* @param {number} orderId Order ID.
* @return {Promise} Promise resolving once the order has been deleted.
*/
deleteOrder: async ( orderId ) => {
const repository = Order.restRepository( client );
await repository.delete( orderId );
},
/**
* Adds a shipping zone along with a shipping method using the API.
*

View File

@ -53,6 +53,14 @@ export const permalinkSettingsPageSaveChanges = async () => {
] );
};
/**
* Save changes on Order page.
*/
export const orderPageSaveChanges = async () => {
await expect( page ).toClick( 'button.save_order' );
await page.waitForSelector( '#message' );
};
/**
* Set checkbox.
*
@ -335,3 +343,25 @@ export async function waitForSelector( page, selector, options = {} ) {
const element = await page.waitForSelector( selector, options );
return element;
}
/**
* Retrieves the desired HTML attribute from a selector.
* For example, the 'value' attribute of an input element.
* @param {string} selector Selector of the element you want to get the attribute from.
* @param {string} attribute The desired HTML attribute.
* @returns {Promise<string>}
*/
export async function getSelectorAttribute ( selector, attribute ) {
return await page.$eval( selector, ( element, attribute ) => element.getAttribute( attribute ), attribute );
}
/**
* Asserts the value of the desired HTML attribute of a selector.
* @param {string} selector Selector of the element you want to verify.
* @param {string} attribute The desired HTML attribute.
* @param {string} expectedValue The expected value.
*/
export async function verifyValueOfElementAttribute ( selector, attribute, expectedValue ) {
const actualValue = await getSelectorAttribute ( selector, attribute );
expect( actualValue ).toBe( expectedValue );
}