From 0cebdf5277d184a6a8557ea2e1196cd13fac1fc1 Mon Sep 17 00:00:00 2001 From: Jonathan Lane Date: Wed, 6 Jul 2022 13:28:00 -0700 Subject: [PATCH] Convert e2e tests to Playwright (#33462) * Final updates for Playwright * Update config * Add uuid dependency * Increase retries to 2 * Update selectors on shipping page * Use baseURL instead of hard-coded URL for API * Clarify comment * Use baseURL instead of hard-coded URL * Check to see if an order was created before attempting to delete it * Add changelog * Turn on Playwright tests in GitHub * Increase timeout for CI execution * Update configuration options (minor edit) * Fix for checkout flaky test * Parse orderId from URL * Check for substring * Streamline email tests * Remove .only * Only clear email logs of messages for test * Get orderId from page element * Fix for test not waiting for reset * Add in second wait for Performance section * Change significance from minor to patch Co-authored-by: Jon Lane --- .github/workflows/pr-build-and-e2e-tests.yml | 2 +- .../changelog/add-merge-playwright | 1 + plugins/woocommerce/e2e/global-setup.js | 8 +- plugins/woocommerce/e2e/playwright.config.js | 13 +- .../e2e/test-data/sample_products.csv | 50 +- .../test-data/sample_products_override.csv | 50 +- .../activate-and-setup/basic-setup.spec.js | 28 +- .../complete-onboarding-wizard.spec.js | 36 +- .../analytics-overview.spec.js | 134 ++--- .../tests/admin-analytics/analytics.spec.js | 2 +- .../e2e/tests/admin-tasks/payment.spec.js | 73 ++- plugins/woocommerce/e2e/tests/basic.spec.js | 49 +- .../e2e/tests/merchant/create-coupon.spec.js | 29 +- .../e2e/tests/merchant/create-order.spec.js | 123 +++-- .../merchant/create-shipping-classes.spec.js | 28 +- .../merchant/create-shipping-zones.spec.js | 251 ++++++---- .../merchant/create-simple-product.spec.js | 5 +- .../merchant/create-variable-product.spec.js | 6 +- .../e2e/tests/merchant/order-coupon.spec.js | 22 +- .../e2e/tests/merchant/order-edit.spec.js | 467 +++++++++--------- .../e2e/tests/merchant/order-emails.spec.js | 33 +- .../e2e/tests/merchant/order-refund.spec.js | 2 +- .../e2e/tests/merchant/order-search.spec.js | 17 +- .../merchant/order-status-filter.spec.js | 20 +- .../tests/merchant/product-import-csv.spec.js | 114 +++-- .../e2e/tests/merchant/product-search.spec.js | 3 +- .../tests/merchant/product-settings.spec.js | 6 +- .../tests/merchant/settings-general.spec.js | 6 +- .../e2e/tests/merchant/settings-tax.spec.js | 40 +- .../tests/shopper/calculate-shipping.spec.js | 220 +++++++++ .../e2e/tests/shopper/cart-coupons.spec.js | 177 +++++++ .../tests/shopper/cart-redirection.spec.js | 79 +++ .../e2e/tests/shopper/cart.spec.js | 152 ++++++ .../tests/shopper/checkout-coupons.spec.js | 179 +++++++ .../shopper/checkout-create-account.spec.js | 146 ++++++ .../e2e/tests/shopper/checkout-login.spec.js | 186 +++++++ .../e2e/tests/shopper/checkout.spec.js | 332 +++++++++++++ .../shopper/my-account-create-account.spec.js | 67 +++ .../shopper/my-account-pay-order.spec.js | 89 ++++ .../e2e/tests/shopper/my-account.spec.js | 54 ++ .../shopper/order-email-receiving.spec.js | 109 ++++ .../product-browse-search-sort.spec.js | 161 ++++++ .../e2e/tests/shopper/single-product.spec.js | 324 ++++++++++++ .../shopper/variable-product-updates.spec.js | 254 ++++++++++ plugins/woocommerce/package.json | 1 + pnpm-lock.yaml | 10 +- 46 files changed, 3437 insertions(+), 721 deletions(-) create mode 100644 plugins/woocommerce/changelog/add-merge-playwright create mode 100644 plugins/woocommerce/e2e/tests/shopper/calculate-shipping.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/cart-coupons.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/cart-redirection.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/cart.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/checkout-coupons.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/checkout-create-account.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/checkout-login.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/checkout.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/my-account-create-account.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/my-account-pay-order.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/my-account.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/order-email-receiving.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/product-browse-search-sort.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/single-product.spec.js create mode 100644 plugins/woocommerce/e2e/tests/shopper/variable-product-updates.spec.js diff --git a/.github/workflows/pr-build-and-e2e-tests.yml b/.github/workflows/pr-build-and-e2e-tests.yml index f69239236b1..381e8d14dba 100644 --- a/.github/workflows/pr-build-and-e2e-tests.yml +++ b/.github/workflows/pr-build-and-e2e-tests.yml @@ -8,7 +8,7 @@ concurrency: cancel-in-progress: true env: - E2E_PLAYWRIGHT: ${{ false }} + E2E_PLAYWRIGHT: ${{ true }} jobs: e2e-tests-run: diff --git a/plugins/woocommerce/changelog/add-merge-playwright b/plugins/woocommerce/changelog/add-merge-playwright new file mode 100644 index 00000000000..f8809fb0193 --- /dev/null +++ b/plugins/woocommerce/changelog/add-merge-playwright @@ -0,0 +1 @@ +Significance: patch diff --git a/plugins/woocommerce/e2e/global-setup.js b/plugins/woocommerce/e2e/global-setup.js index 85178273c25..8268184d1a5 100644 --- a/plugins/woocommerce/e2e/global-setup.js +++ b/plugins/woocommerce/e2e/global-setup.js @@ -28,9 +28,7 @@ module.exports = async ( config ) => { await adminPage.fill( 'input[name="log"]', 'admin' ); await adminPage.fill( 'input[name="pwd"]', 'password' ); await adminPage.click( 'text=Log In' ); - await adminPage - .context() - .storageState( { path: 'e2e/storage/adminState.json' } ); + await adminPage.context().storageState( { path: adminState } ); // While we're here, let's add a consumer token for API access await adminPage.goto( `${ baseURL }/wp-admin/admin.php?page=wc-settings&tab=advanced§ion=keys&create-key=1` @@ -51,8 +49,6 @@ module.exports = async ( config ) => { await customerPage.fill( 'input[name="log"]', 'customer' ); await customerPage.fill( 'input[name="pwd"]', 'password' ); await customerPage.click( 'text=Log In' ); - await customerPage - .context() - .storageState( { path: 'e2e/storage/customerState.json' } ); + await customerPage.context().storageState( { path: customerState } ); await browser.close(); }; diff --git a/plugins/woocommerce/e2e/playwright.config.js b/plugins/woocommerce/e2e/playwright.config.js index 23962b8be32..e8849be8bea 100644 --- a/plugins/woocommerce/e2e/playwright.config.js +++ b/plugins/woocommerce/e2e/playwright.config.js @@ -1,12 +1,13 @@ const { devices } = require( '@playwright/test' ); const config = { - timeout: 20000, + timeout: 60 * 1000, outputDir: './report', globalSetup: require.resolve( './global-setup' ), globalTeardown: require.resolve( './global-teardown' ), testDir: 'tests', - retries: 1, + retries: 2, + workers: 4, reporter: [ [ 'list' ], [ 'html', { outputFolder: 'output' } ], @@ -24,14 +25,6 @@ const config = { name: 'Chrome', use: { ...devices[ 'Desktop Chrome' ] }, }, - // { - // name: 'Firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - // { - // name: 'Webkit', - // use: { ...devices['Desktop Webkit'] }, - // }, ], }; diff --git a/plugins/woocommerce/e2e/test-data/sample_products.csv b/plugins/woocommerce/e2e/test-data/sample_products.csv index dfb25e8889a..e73f8d1a313 100644 --- a/plugins/woocommerce/e2e/test-data/sample_products.csv +++ b/plugins/woocommerce/e2e/test-data/sample_products.csv @@ -1,26 +1,26 @@ ID,Type,SKU,Name,Published,"Is featured?","Visibility in catalog","Short description",Description,"Date sale price starts","Date sale price ends","Tax status","Tax class","In stock?",Stock,"Backorders allowed?","Sold individually?","Weight (lbs)","Length (in)","Width (in)","Height (in)","Allow customer reviews?","Purchase note","Sale price","Regular price",Categories,Tags,"Shipping class",Images,"Download limit","Download expiry days",Parent,"Grouped products",Upsells,Cross-sells,"External URL","Button text",Position,"Attribute 1 name","Attribute 1 value(s)","Attribute 1 visible","Attribute 1 global","Attribute 2 name","Attribute 2 value(s)","Attribute 2 visible","Attribute 2 global","Meta: _wpcom_is_markdown","Download 1 name","Download 1 URL","Download 2 name","Download 2 URL" -44,variable,woo-vneck-tee,"V-Neck T-Shirt",1,1,visible,"This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.5,24,1,2,1,,,,"Clothing > Tshirts",,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg",,,,,,,,,0,Color,"Blue, Green, Red",1,1,Size,"Large, Medium, Small",1,1,1,,,, -45,variable,woo-hoodie,Hoodie,1,0,visible,"This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,1.5,10,8,3,1,,,,"Clothing > Hoodies",,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,,,,,,0,Color,"Blue, Green, Red",1,1,Logo,"Yes, No",1,0,1,,,, -46,simple,woo-hoodie-with-logo,"Hoodie with Logo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,2,10,6,3,1,,,45,"Clothing > Hoodies",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,,,,,,,0,Color,Blue,1,1,,,,,1,,,, -47,simple,woo-tshirt,T-Shirt,1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.8,8,6,1,1,,,18,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/tshirt-2.jpg,,,,,,,,,0,Color,Gray,1,1,,,,,1,,,, -48,simple,woo-beanie,Beanie,1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.2,4,5,.5,1,,18,20,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-2.jpg,,,,,,,,,0,Color,Red,1,1,,,,,1,,,, -58,simple,woo-belt,Belt,1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,1.2,12,2,1.5,1,,55,65,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/belt-2.jpg,,,,,,,,,0,,,,,,,,,1,,,, -60,simple,woo-cap,Cap,1,1,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,0.6,8,6.5,4,1,,16,18,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/cap-2.jpg,,,,,,,,,0,Color,Yellow,1,1,,,,,1,,,, -62,simple,woo-sunglasses,Sunglasses,1,1,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.2,4,1.4,1,1,,,90,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/sunglasses-2.jpg,,,,,,,,,0,,,,,,,,,1,,,, -64,simple,woo-hoodie-with-pocket,"Hoodie with Pocket",1,1,hidden,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,3,10,8,2,1,,35,45,"Clothing > Hoodies",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-pocket-2.jpg,,,,,,,,,0,Color,Gray,1,1,,,,,1,,,, -66,simple,woo-hoodie-with-zipper,"Hoodie with Zipper",1,1,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,2,8,6,2,1,,,45,"Clothing > Hoodies",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-zipper-2.jpg,,,,,,,,,0,,,,,,,,,1,,,, -68,simple,woo-long-sleeve-tee,"Long Sleeve Tee",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,1,7,5,1,1,,,25,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/long-sleeve-tee-2.jpg,,,,,,,,,0,Color,Green,1,1,,,,,1,,,, -70,simple,woo-polo,Polo,1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.8,6,5,1,1,,,20,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/polo-2.jpg,,,,,,,,,0,Color,Blue,1,1,,,,,1,,,, -73,"simple, downloadable, virtual",woo-album,Album,1,0,visible,"This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,1,,,15,Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/album-1.jpg,1,1,,,,,,,0,,,,,,,,,1,"Single 1",https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg,"Single 2",https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/album.jpg -75,"simple, downloadable, virtual",woo-single,Single,1,0,visible,"This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,1,,2,3,Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/single-1.jpg,1,1,,,,,,,0,,,,,,,,,1,Single,https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg,, -76,variation,woo-vneck-tee-red,"V-Neck T-Shirt - Red",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,20,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg,,,woo-vneck-tee,,,,,,0,Color,Red,,1,Size,,,1,,,,, -77,variation,woo-vneck-tee-green,"V-Neck T-Shirt - Green",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,20,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg,,,woo-vneck-tee,,,,,,0,Color,Green,,1,Size,,,1,,,,, -78,variation,woo-vneck-tee-blue,"V-Neck T-Shirt - Blue",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,15,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg,,,woo-vneck-tee,,,,,,0,Color,Blue,,1,Size,,,1,,,,, -79,variation,woo-hoodie-red,"Hoodie - Red, No",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,42,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg,,,woo-hoodie,,,,,,1,Color,Red,,1,Logo,No,,0,,,,, -80,variation,woo-hoodie-green,"Hoodie - Green, No",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg,,,woo-hoodie,,,,,,2,Color,Green,,1,Logo,No,,0,,,,, -81,variation,woo-hoodie-blue,"Hoodie - Blue, No",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg,,,woo-hoodie,,,,,,3,Color,Blue,,1,Logo,No,,0,,,,, -83,simple,Woo-tshirt-logo,"T-Shirt with Logo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.5,10,12,.5,1,,,18,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg,,,,,,,,,0,Color,Gray,1,1,,,,,1,,,, -85,simple,Woo-beanie-logo,"Beanie with Logo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.2,6,4,1,1,,18,20,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg,,,,,,,,,0,Color,Red,1,1,,,,,1,,,, -87,grouped,logo-collection,"Logo Collection",1,0,visible,"This is a grouped product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,,,,,1,,,,Clothing,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,"woo-hoodie-with-logo, woo-tshirt, woo-beanie",,,,,0,,,,,,,,,1,,,, -89,external,wp-pennant,"WordPress Pennant",1,0,visible,"This is an external product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,,,,,1,,,11.05,Decor,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/pennant-1.jpg,,,,,,,https://mercantile.wordpress.org/product/wordpress-pennant/,"Buy on the WordPress swag store!",0,,,,,,,,,1,,,, -90,variation,woo-hoodie-blue-logo,"Hoodie - Blue, Yes",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,woo-hoodie,,,,,,0,Color,Blue,,1,Logo,Yes,,0,,,,, +44,variable,woo-vneck-tee,"Imported V-Neck T-Shirt",1,1,visible,"This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.5,24,1,2,1,,,,"Clothing > Tshirts",,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg",,,,,,,,,0,Color,"Blue, Green, Red",1,1,Size,"Large, Medium, Small",1,1,1,,,, +45,variable,woo-hoodie,"Imported Hoodie",1,0,visible,"This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,1.5,10,8,3,1,,,,"Clothing > Hoodies",,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,,,,,,0,Color,"Blue, Green, Red",1,1,Logo,"Yes, No",1,0,1,,,, +46,simple,woo-hoodie-with-logo,"Imported Hoodie with Logo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,2,10,6,3,1,,,45,"Clothing > Hoodies",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,,,,,,,0,Color,Blue,1,1,,,,,1,,,, +47,simple,woo-tshirt,"Imported T-Shirt",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.8,8,6,1,1,,,18,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/tshirt-2.jpg,,,,,,,,,0,Color,Gray,1,1,,,,,1,,,, +48,simple,woo-beanie,"Imported Beanie",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.2,4,5,.5,1,,18,20,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-2.jpg,,,,,,,,,0,Color,Red,1,1,,,,,1,,,, +58,simple,woo-belt,"Imported Belt",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,1.2,12,2,1.5,1,,55,65,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/belt-2.jpg,,,,,,,,,0,,,,,,,,,1,,,, +60,simple,woo-cap,"Imported Cap",1,1,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,0.6,8,6.5,4,1,,16,18,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/cap-2.jpg,,,,,,,,,0,Color,Yellow,1,1,,,,,1,,,, +62,simple,woo-sunglasses,"Imported Sunglasses",1,1,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.2,4,1.4,1,1,,,90,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/sunglasses-2.jpg,,,,,,,,,0,,,,,,,,,1,,,, +64,simple,woo-hoodie-with-pocket,"Imported Hoodie with Pocket",1,1,hidden,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,3,10,8,2,1,,35,45,"Clothing > Hoodies",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-pocket-2.jpg,,,,,,,,,0,Color,Gray,1,1,,,,,1,,,, +66,simple,woo-hoodie-with-zipper,"Imported Hoodie with Zipper",1,1,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,2,8,6,2,1,,,45,"Clothing > Hoodies",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-zipper-2.jpg,,,,,,,,,0,,,,,,,,,1,,,, +68,simple,woo-long-sleeve-tee,"Imported Long Sleeve Tee",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,1,7,5,1,1,,,25,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/long-sleeve-tee-2.jpg,,,,,,,,,0,Color,Green,1,1,,,,,1,,,, +70,simple,woo-polo,"Imported Polo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.8,6,5,1,1,,,20,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/polo-2.jpg,,,,,,,,,0,Color,Blue,1,1,,,,,1,,,, +73,"simple, downloadable, virtual",woo-album,"Imported Album",1,0,visible,"This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,1,,,15,Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/album-1.jpg,1,1,,,,,,,0,,,,,,,,,1,"Single 1",https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg,"Single 2",https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/album.jpg +75,"simple, downloadable, virtual",woo-single,"Imported Single",1,0,visible,"This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,1,,2,3,Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/single-1.jpg,1,1,,,,,,,0,,,,,,,,,1,Single,https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg,, +76,variation,woo-vneck-tee-red,"Imported V-Neck T-Shirt - Red",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,20,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg,,,woo-vneck-tee,,,,,,0,Color,Red,,1,Size,,,1,,,,, +77,variation,woo-vneck-tee-green,"Imported V-Neck T-Shirt - Green",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,20,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg,,,woo-vneck-tee,,,,,,0,Color,Green,,1,Size,,,1,,,,, +78,variation,woo-vneck-tee-blue,"Imported V-Neck T-Shirt - Blue",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,15,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg,,,woo-vneck-tee,,,,,,0,Color,Blue,,1,Size,,,1,,,,, +79,variation,woo-hoodie-red,"Imported Hoodie - Red, No",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,42,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg,,,woo-hoodie,,,,,,1,Color,Red,,1,Logo,No,,0,,,,, +80,variation,woo-hoodie-green,"Imported Hoodie - Green, No",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg,,,woo-hoodie,,,,,,2,Color,Green,,1,Logo,No,,0,,,,, +81,variation,woo-hoodie-blue,"Imported Hoodie - Blue, No",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg,,,woo-hoodie,,,,,,3,Color,Blue,,1,Logo,No,,0,,,,, +83,simple,Woo-tshirt-logo,"Imported T-Shirt with Logo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.5,10,12,.5,1,,,18,"Clothing > Tshirts",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg,,,,,,,,,0,Color,Gray,1,1,,,,,1,,,, +85,simple,Woo-beanie-logo,"Imported Beanie with Logo",1,0,visible,"This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,.2,6,4,1,1,,18,20,"Clothing > Accessories",,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg,,,,,,,,,0,Color,Red,1,1,,,,,1,,,, +87,grouped,logo-collection,"Imported Logo Collection",1,0,visible,"This is a grouped product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,,,,,1,,,,Clothing,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,"woo-hoodie-with-logo, woo-tshirt, woo-beanie",,,,,0,,,,,,,,,1,,,, +89,external,wp-pennant,"Imported WordPress Pennant",1,0,visible,"This is an external product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,taxable,,1,,0,0,,,,,1,,,11.05,Decor,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/pennant-1.jpg,,,,,,,https://mercantile.wordpress.org/product/wordpress-pennant/,"Buy on the WordPress swag store!",0,,,,,,,,,1,,,, +90,variation,woo-hoodie-blue-logo,"Imported Hoodie - Blue, Yes",1,0,visible,,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,taxable,,1,,0,0,,,,,0,,,45,,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,woo-hoodie,,,,,,0,Color,Blue,,1,Logo,Yes,,0,,,,, diff --git a/plugins/woocommerce/e2e/test-data/sample_products_override.csv b/plugins/woocommerce/e2e/test-data/sample_products_override.csv index 6b880308a7a..295cc62dd05 100644 --- a/plugins/woocommerce/e2e/test-data/sample_products_override.csv +++ b/plugins/woocommerce/e2e/test-data/sample_products_override.csv @@ -1,26 +1,26 @@ ID,Type,SKU,Name,Published,"Is featured?","Visibility in catalog","Short description",Description,"Date sale price starts","Date sale price ends","Tax status","Tax class","In stock?",Stock,"Backorders allowed?","Sold individually?","Weight (lbs)","Length (in)","Width (in)","Height (in)","Allow customer reviews?","Purchase note","Sale price","Regular price",Categories,Tags,"Shipping class",Images,"Download limit","Download expiry days",Parent,"Grouped products",Upsells,Cross-sells,"External URL","Button text",Position,"Attribute 1 name","Attribute 1 value(s)","Attribute 1 visible","Attribute 1 global","Attribute 2 name","Attribute 2 value(s)","Attribute 2 visible","Attribute 2 global","Meta: _wpcom_is_markdown","Download 1 name","Download 1 URL","Download 2 name","Download 2 URL" -,variable,woo-vneck-tee,V-Neck T-Shirt Override,1,"1","visible","This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".5","24","1","2","1",,,,Clothing > Tshirts,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg",,,,,,,,,0,"Color","Blue, Green, Red","1","1","Size","Large, Medium, Small","1","1","1",,,, -,variable,woo-hoodie,Hoodie Override,1,"0","visible","This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","1.5","10","8","3","1",,,,Clothing > Hoodies,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,,,,,,0,"Color","Blue, Green, Red","1","1","Logo","Yes, No","1","0","1",,,, -,simple,woo-hoodie-with-logo,Hoodie with Logo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","2","10","6","3","1",,,"145",Clothing > Hoodies,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,,,,,,,0,"Color","Blue","1","1",,,,,"1",,,, -,simple,woo-tshirt,T-Shirt Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".8","8","6","1","1",,,"118",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/tshirt-2.jpg,,,,,,,,,0,"Color","Gray","1","1",,,,,"1",,,, -,simple,woo-beanie,Beanie Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".2","4","5",".5","1",,"118","120",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-2.jpg,,,,,,,,,0,"Color","Red","1","1",,,,,"1",,,, -,simple,woo-belt,Belt Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","1.2","12","2","1.5","1",,"155","165",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/belt-2.jpg,,,,,,,,,0,,,,,,,,,"1",,,, -,simple,woo-cap,Cap Override,1,"1","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","0.6","8","6.5","4","1",,"116","118",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/cap-2.jpg,,,,,,,,,0,"Color","Yellow","1","1",,,,,"1",,,, -,simple,woo-sunglasses,Sunglasses Override,1,"1","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".2","4","1.4","1","1",,,"190",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/sunglasses-2.jpg,,,,,,,,,0,,,,,,,,,"1",,,, -,simple,woo-hoodie-with-pocket,Hoodie with Pocket Override,1,"1","hidden","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","3","10","8","2","1",,"135","145",Clothing > Hoodies,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-pocket-2.jpg,,,,,,,,,0,"Color","Gray","1","1",,,,,"1",,,, -,simple,woo-hoodie-with-zipper,Hoodie with Zipper Override,1,"1","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","2","8","6","2","1",,,"145",Clothing > Hoodies,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-zipper-2.jpg,,,,,,,,,0,,,,,,,,,"1",,,, -,simple,woo-long-sleeve-tee,Long Sleeve Tee Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","1","7","5","1","1",,,"125",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/long-sleeve-tee-2.jpg,,,,,,,,,0,"Color","Green","1","1",,,,,"1",,,, -,simple,woo-polo,Polo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".8","6","5","1","1",,,"120",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/polo-2.jpg,,,,,,,,,0,"Color","Blue","1","1",,,,,"1",,,, -,"simple, downloadable, virtual",woo-album,Album Override,1,"0","visible","This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"1",,,"115",Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/album-1.jpg,"1","1",,,,,,,0,,,,,,,,,"1","Single 1","https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg","Single 2","https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/album.jpg" -,"simple, downloadable, virtual",woo-single,Single Override,1,"0","visible","This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"1",,"12","13",Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/single-1.jpg,"1","1",,,,,,,0,,,,,,,,,"1","Single","https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg",, -,variation,woo-vneck-tee-red,V-Neck T-Shirt - Red Override,1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"120",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg,,,woo-vneck-tee,,,,,,0,"Color","Red",,"1","Size",,,"1",,,,, -,variation,woo-vneck-tee-green,V-Neck T-Shirt - Green Override,1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"120",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg,,,woo-vneck-tee,,,,,,0,"Color","Green",,"1","Size",,,"1",,,,, -,variation,woo-vneck-tee-blue,V-Neck T-Shirt - Blue Override,1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"115",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg,,,woo-vneck-tee,,,,,,0,"Color","Blue",,"1","Size",,,"1",,,,, -,variation,woo-hoodie-red,"Hoodie - Red, No Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,"142","145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg,,,woo-hoodie,,,,,,1,"Color","Red",,"1","Logo","No",,"0",,,,, -,variation,woo-hoodie-green,"Hoodie - Green, No Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg,,,woo-hoodie,,,,,,2,"Color","Green",,"1","Logo","No",,"0",,,,, -,variation,woo-hoodie-blue,"Hoodie - Blue, No Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg,,,woo-hoodie,,,,,,3,"Color","Blue",,"1","Logo","No",,"0",,,,, -,simple,Woo-tshirt-logo,T-Shirt with Logo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".5","10","12",".5","1",,,"118",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg,,,,,,,,,0,"Color","Gray","1","1",,,,,"1",,,, -,simple,Woo-beanie-logo,Beanie with Logo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".2","6","4","1","1",,"118","120",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg,,,,,,,,,0,"Color","Red","1","1",,,,,"1",,,, -,grouped,logo-collection,Logo Collection Override,1,"0","visible","This is a grouped product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",,,,,"1",,,,Clothing,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,"woo-hoodie-with-logo, woo-tshirt, woo-beanie",,,,,0,,,,,,,,,"1",,,, -,external,wp-pennant,WordPress Pennant Override,1,"0","visible","This is an external product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",,,,,"1",,,"111.05",Decor,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/pennant-1.jpg,,,,,,,"https://mercantile.wordpress.org/product/wordpress-pennant/","Buy on the WordPress swag store!",0,,,,,,,,,"1",,,, -,variation,woo-hoodie-blue-logo,"Hoodie - Blue, Yes Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,woo-hoodie,,,,,,0,"Color","Blue",,"1","Logo","Yes",,"0",,,,, \ No newline at end of file +,variable,woo-vneck-tee,Imported V-Neck T-Shirt Override,1,"1","visible","This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".5","24","1","2","1",,,,Clothing > Tshirts,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg",,,,,,,,,0,"Color","Blue, Green, Red","1","1","Size","Large, Medium, Small","1","1","1",,,, +,variable,woo-hoodie,Imported Hoodie Override,1,"0","visible","This is a variable product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","1.5","10","8","3","1",,,,Clothing > Hoodies,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,,,,,,0,"Color","Blue, Green, Red","1","1","Logo","Yes, No","1","0","1",,,, +,simple,woo-hoodie-with-logo,Imported Hoodie with Logo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","2","10","6","3","1",,,"145",Clothing > Hoodies,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,,,,,,,0,"Color","Blue","1","1",,,,,"1",,,, +,simple,woo-tshirt,Imported T-Shirt Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".8","8","6","1","1",,,"118",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/tshirt-2.jpg,,,,,,,,,0,"Color","Gray","1","1",,,,,"1",,,, +,simple,woo-beanie,Imported Beanie Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".2","4","5",".5","1",,"118","120",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-2.jpg,,,,,,,,,0,"Color","Red","1","1",,,,,"1",,,, +,simple,woo-belt,Imported Belt Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","1.2","12","2","1.5","1",,"155","165",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/belt-2.jpg,,,,,,,,,0,,,,,,,,,"1",,,, +,simple,woo-cap,Imported Cap Override,1,"1","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","0.6","8","6.5","4","1",,"116","118",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/cap-2.jpg,,,,,,,,,0,"Color","Yellow","1","1",,,,,"1",,,, +,simple,woo-sunglasses,Imported Sunglasses Override,1,"1","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".2","4","1.4","1","1",,,"190",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/sunglasses-2.jpg,,,,,,,,,0,,,,,,,,,"1",,,, +,simple,woo-hoodie-with-pocket,Imported Hoodie with Pocket Override,1,"1","hidden","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","3","10","8","2","1",,"135","145",Clothing > Hoodies,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-pocket-2.jpg,,,,,,,,,0,"Color","Gray","1","1",,,,,"1",,,, +,simple,woo-hoodie-with-zipper,Imported Hoodie with Zipper Override,1,"1","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","2","8","6","2","1",,,"145",Clothing > Hoodies,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-zipper-2.jpg,,,,,,,,,0,,,,,,,,,"1",,,, +,simple,woo-long-sleeve-tee,Imported Long Sleeve Tee Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0","1","7","5","1","1",,,"125",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/long-sleeve-tee-2.jpg,,,,,,,,,0,"Color","Green","1","1",,,,,"1",,,, +,simple,woo-polo,Imported Polo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".8","6","5","1","1",,,"120",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/polo-2.jpg,,,,,,,,,0,"Color","Blue","1","1",,,,,"1",,,, +,"simple, downloadable, virtual",woo-album,Imported Album Override,1,"0","visible","This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"1",,,"115",Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/album-1.jpg,"1","1",,,,,,,0,,,,,,,,,"1","Single 1","https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg","Single 2","https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/album.jpg" +,"simple, downloadable, virtual",woo-single,Imported Single Override,1,"0","visible","This is a simple, virtual product.","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"1",,"12","13",Music,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/single-1.jpg,"1","1",,,,,,,0,,,,,,,,,"1","Single","https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg",, +,variation,woo-vneck-tee-red,Imported V-Neck T-Shirt - Red Override,1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"120",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vneck-tee-2.jpg,,,woo-vneck-tee,,,,,,0,"Color","Red",,"1","Size",,,"1",,,,, +,variation,woo-vneck-tee-green,Imported V-Neck T-Shirt - Green Override,1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"120",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-green-1.jpg,,,woo-vneck-tee,,,,,,0,"Color","Green",,"1","Size",,,"1",,,,, +,variation,woo-vneck-tee-blue,Imported V-Neck T-Shirt - Blue Override,1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"115",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/vnech-tee-blue-1.jpg,,,woo-vneck-tee,,,,,,0,"Color","Blue",,"1","Size",,,"1",,,,, +,variation,woo-hoodie-red,"Imported Hoodie - Red, No Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,"142","145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-2.jpg,,,woo-hoodie,,,,,,1,"Color","Red",,"1","Logo","No",,"0",,,,, +,variation,woo-hoodie-green,"Imported Hoodie - Green, No Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-green-1.jpg,,,woo-hoodie,,,,,,2,"Color","Green",,"1","Logo","No",,"0",,,,, +,variation,woo-hoodie-blue,"Imported Hoodie - Blue, No Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-blue-1.jpg,,,woo-hoodie,,,,,,3,"Color","Blue",,"1","Logo","No",,"0",,,,, +,simple,Woo-tshirt-logo,Imported T-Shirt with Logo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".5","10","12",".5","1",,,"118",Clothing > Tshirts,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg,,,,,,,,,0,"Color","Gray","1","1",,,,,"1",,,, +,simple,Woo-beanie-logo,Imported Beanie with Logo Override,1,"0","visible","This is a simple product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",".2","6","4","1","1",,"118","120",Clothing > Accessories,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg,,,,,,,,,0,"Color","Red","1","1",,,,,"1",,,, +,grouped,logo-collection,Imported Logo Collection Override,1,"0","visible","This is a grouped product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",,,,,"1",,,,Clothing,,,"https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/beanie-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/t-shirt-with-logo-1.jpg, https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg",,,,"woo-hoodie-with-logo, woo-tshirt, woo-beanie",,,,,0,,,,,,,,,"1",,,, +,external,wp-pennant,Imported WordPress Pennant Override,1,"0","visible","This is an external product.","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo.",,,"taxable",,"1",,"0","0",,,,,"1",,,"111.05",Decor,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/pennant-1.jpg,,,,,,,"https://mercantile.wordpress.org/product/wordpress-pennant/","Buy on the WordPress swag store!",0,,,,,,,,,"1",,,, +,variation,woo-hoodie-blue-logo,"Imported Hoodie - Blue, Yes Override",1,"0","visible",,"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum sagittis orci ac odio dictum tincidunt. Donec ut metus leo. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed luctus, dui eu sagittis sodales, nulla nibh sagittis augue, vel porttitor diam enim non metus. Vestibulum aliquam augue neque. Phasellus tincidunt odio eget ullamcorper efficitur. Cras placerat ut turpis pellentesque vulputate. Nam sed consequat tortor. Curabitur finibus sapien dolor. Ut eleifend tellus nec erat pulvinar dignissim. Nam non arcu purus. Vivamus et massa massa.",,,"taxable",,"1",,"0","0",,,,,"0",,,"145",,,,https://woocommercecore.mystagingwebsite.com/wp-content/uploads/2017/12/hoodie-with-logo-2.jpg,,,woo-hoodie,,,,,,0,"Color","Blue",,"1","Logo","Yes",,"0",,,,, diff --git a/plugins/woocommerce/e2e/tests/activate-and-setup/basic-setup.spec.js b/plugins/woocommerce/e2e/tests/activate-and-setup/basic-setup.spec.js index d70d3de9981..dbf9a1398b6 100644 --- a/plugins/woocommerce/e2e/tests/activate-and-setup/basic-setup.spec.js +++ b/plugins/woocommerce/e2e/tests/activate-and-setup/basic-setup.spec.js @@ -3,16 +3,16 @@ const { test, expect } = require( '@playwright/test' ); test.describe( 'Store owner can finish initial store setup', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); test( 'can enable tax rates and calculations', async ( { page } ) => { - await page.goto( '/wp-admin/admin.php?page=wc-settings' ); + await page.goto( 'wp-admin/admin.php?page=wc-settings' ); // Check the enable taxes checkbox await page.check( '#woocommerce_calc_taxes' ); await page.click( 'text=Save changes' ); // Verify changes have been saved - expect( page.isChecked( '#woocommerce_calc_taxes' ) ).toBeTruthy(); + await expect( page.locator( '#woocommerce_calc_taxes' ) ).toBeChecked(); } ); test( 'can configure permalink settings', async ( { page } ) => { - await page.goto( '/wp-admin/options-permalink.php' ); + await page.goto( 'wp-admin/options-permalink.php' ); // Select "Post name" option in common settings section await page.check( 'label >> text=Post name' ); // Select "Custom base" in product permalinks section @@ -21,20 +21,14 @@ test.describe( 'Store owner can finish initial store setup', () => { await page.fill( '#woocommerce_permalink_structure', '/product/' ); await page.click( '#submit' ); // Verify that settings have been saved - await page.waitForLoadState( 'networkidle' ); // not autowaiting for form submission - const notice = await page.textContent( - '#setting-error-settings_updated' + await expect( + page.locator( '#setting-error-settings_updated' ) + ).toContainText( 'Permalink structure updated.' ); + await expect( page.locator( '#permalink_structure' ) ).toHaveValue( + '/%postname%/' ); - expect( notice ).toContain( 'Permalink structure updated.' ); - const postSlug = await page.getAttribute( - '#permalink_structure', - 'value' - ); - expect( postSlug ).toBe( '/%postname%/' ); - const wcSlug = await page.getAttribute( - '#woocommerce_permalink_structure', - 'value' - ); - expect( wcSlug ).toBe( '/product/' ); + await expect( + page.locator( '#woocommerce_permalink_structure' ) + ).toHaveValue( '/product/' ); } ); } ); diff --git a/plugins/woocommerce/e2e/tests/activate-and-setup/complete-onboarding-wizard.spec.js b/plugins/woocommerce/e2e/tests/activate-and-setup/complete-onboarding-wizard.spec.js index 7c78a2a68f1..c53da01df96 100644 --- a/plugins/woocommerce/e2e/tests/activate-and-setup/complete-onboarding-wizard.spec.js +++ b/plugins/woocommerce/e2e/tests/activate-and-setup/complete-onboarding-wizard.spec.js @@ -156,8 +156,9 @@ test.describe( 'Store owner can complete onboarding wizard', () => { } ); } ); +// !Changed from Japanese to Malta store, as Japanese Yen does not use decimals test.describe( - 'A japanese store can complete the selective bundle install but does not include WCPay.', + 'A Malta store can complete the selective bundle install but does not include WCPay.', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); @@ -171,11 +172,11 @@ test.describe( await page.click( '#woocommerce-select-control-0__control-input' ); await page.fill( '#woocommerce-select-control-0__control-input', - 'Japan — Hokkaido' + 'Malta' ); - await page.click( 'button >> text=Japan — Hokkaido' ); - await page.fill( '#inspector-text-control-2', 'Sapporo' ); - await page.fill( '#inspector-text-control-3', '007-0852' ); + await page.click( 'button >> text=Malta' ); + await page.fill( '#inspector-text-control-2', 'Valletta' ); + await page.fill( '#inspector-text-control-3', 'VLT 1011' ); await page.fill( '#inspector-text-control-4', 'admin@woocommercecoree2etestsuite.com' @@ -294,29 +295,16 @@ test.describe( 'Store owner can go through setup Task List', () => { test( 'can setup shipping', async ( { page } ) => { await page.goto( '/wp-admin/admin.php?page=wc-admin' ); - // Close the welcome dialog if it's present - await page.waitForLoadState( 'networkidle' ); // explictly wait because the welcome dialog loads last - const welcomeDialog = await page.$( '.components-modal__header' ); - if ( welcomeDialog !== null ) { - await page.click( - 'div.components-modal__header >> button.components-button' - ); - } - await expect( welcomeDialog ).not.toBeVisible(); - await page - .locator( 'li[role="button"]:has-text("Set up shipping1 minute")' ) - .click(); + await page.click( 'text="Set up shipping"' ); - const shippingPage = await page.textContent( 'h1' ); - if ( shippingPage === 'Shipping' ) { + // check if this is the first time (or if the test is being retried) + const currPage = page.url(); + if ( currPage.indexOf( 'page=wc-settings&tab=shipping' ) > 0 ) { // click the Add shipping zone button on the shipping settings page await page.locator( '.page-title-action' ).click(); - await expect( - page.locator( 'h2', { - hasText: 'Shipping zones', - } ) - ).toBeVisible(); + page.locator( 'div.woocommerce > form > h2' ) + ).toContainText( 'Shipping zones' ); } else { await page.locator( 'button.components-button.is-primary' ).click(); } diff --git a/plugins/woocommerce/e2e/tests/admin-analytics/analytics-overview.spec.js b/plugins/woocommerce/e2e/tests/admin-analytics/analytics-overview.spec.js index 795ad4481d8..fc0bdc8338c 100644 --- a/plugins/woocommerce/e2e/tests/admin-analytics/analytics-overview.spec.js +++ b/plugins/woocommerce/e2e/tests/admin-analytics/analytics-overview.spec.js @@ -11,6 +11,9 @@ test.describe( 'Analytics pages', () => { await page.goto( 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview' ); + + await page.waitForLoadState( 'networkidle' ); + // Grab all of the section headings const sections = await page.$$( 'h2.woocommerce-section-header__title' @@ -25,51 +28,6 @@ test.describe( 'Analytics pages', () => { ); } ); - test( 'should allow a user to remove a section', async ( { page } ) => { - await page.goto( - 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview' - ); - // clicks the first button to the right of the Performance heading - await page - .locator( 'button:right-of(:text("Performance")) >> nth=0' ) - .click(); - await page.locator( 'text=Remove section' ).click(); - // Grab all of the section headings - const sections = await page.$$( - 'h2.woocommerce-section-header__title' - ); - await expect( sections.length ).toEqual( 2 ); - - // clean up - await page.locator( '//button[@title="Add more sections"]' ).click(); - await page - .locator( '//button[@title="Add Performance section"]' ) - .click(); - await page.waitForLoadState( 'networkidle' ); - } ); - - test( 'should allow a user to add a section back in', async ( { - page, - } ) => { - await page.goto( - 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview' - ); - // button only shows when not all sections visible, so remove a section - await page - .locator( 'button:right-of(:text("Performance")) >> nth=0' ) - .click(); - await page.locator( 'text=Remove section' ).click(); - - // add section - await page.locator( '//button[@title="Add more sections"]' ).click(); - await page - .locator( '//button[@title="Add Performance section"]' ) - .click(); - await expect( - page.locator( 'h2.woocommerce-section-header__title >> nth=2' ) - ).toContainText( 'Performance' ); - } ); - test.describe( 'moving sections', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); @@ -80,20 +38,16 @@ test.describe( 'Analytics pages', () => { 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview' ); // check the top section - await page - .locator( - 'button.components-button.woocommerce-ellipsis-menu__toggle >> nth=0' - ) - .click(); + await page.click( + '//button[@title="Choose which analytics to display and the section name"]' + ); await expect( page.locator( 'text=Move up' ) ).not.toBeVisible(); await expect( page.locator( 'text=Move down' ) ).toBeVisible(); // check the bottom section - await await page - .locator( - 'button.components-button.woocommerce-ellipsis-menu__toggle >> nth=2' - ) - .click(); + await page.click( + '//button[@title="Choose which leaderboards to display and other settings"]' + ); await expect( page.locator( 'text=Move down' ) ).not.toBeVisible(); await expect( page.locator( 'text=Move up' ) ).toBeVisible(); } ); @@ -111,12 +65,10 @@ test.describe( 'Analytics pages', () => { .locator( 'h2.woocommerce-section-header__title >> nth=1' ) .innerText(); - await page - .locator( - 'button.components-button.woocommerce-ellipsis-menu__toggle >> nth=0' - ) - .click(); - await page.locator( 'text=Move down' ).click(); + await page.click( + 'button.components-button.woocommerce-ellipsis-menu__toggle >> nth=0' + ); + await page.click( 'text=Move down' ); // second section becomes first section, first becomes second await expect( @@ -140,12 +92,10 @@ test.describe( 'Analytics pages', () => { .locator( 'h2.woocommerce-section-header__title >> nth=1' ) .innerText(); - await page - .locator( - 'button.components-button.woocommerce-ellipsis-menu__toggle >> nth=1' - ) - .click(); - await page.locator( 'text=Move up' ).click(); + await page.click( + 'button.components-button.woocommerce-ellipsis-menu__toggle >> nth=1' + ); + await page.click( 'text=Move up' ); // second section becomes first section, first becomes second await expect( @@ -156,4 +106,54 @@ test.describe( 'Analytics pages', () => { ).toHaveText( firstSection ); } ); } ); + + test( 'should allow a user to remove a section', async ( { page } ) => { + await page.goto( + 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview' + ); + // clicks the first button to the right of the Performance heading + await page.click( 'button:right-of(:text("Performance")) >> nth=0' ); + await page.click( 'text=Remove section' ); + // Grab all of the section headings + const sections = await page.$$( + 'h2.woocommerce-section-header__title' + ); + await expect( sections.length ).toEqual( 2 ); + + // clean up + await page.click( '//button[@title="Add more sections"]' ); + await page.click( '//button[@title="Add Performance section"]' ); + await page.waitForSelector( 'h2:has-text("Performance")', { + state: 'visible', + } ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test( 'should allow a user to add a section back in', async ( { + page, + } ) => { + await page.goto( + 'wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2Foverview' + ); + // button only shows when not all sections visible, so remove a section + await page.click( 'button:right-of(:text("Performance")) >> nth=0' ); + await page.click( 'text=Remove section' ); + + // add section + await page.click( '//button[@title="Add more sections"]' ); + await page.click( '//button[@title="Add Performance section"]' ); + await expect( + page.locator( 'h2.woocommerce-section-header__title >> nth=2' ) + ).toContainText( 'Performance' ); + + // clean up by moving performance section back to the top + await page.click( + '//button[@title="Choose which analytics to display and the section name"]' + ); + await page.click( 'text=Move up' ); + await page.click( + '//button[@title="Choose which analytics to display and the section name"]' + ); + await page.click( 'text=Move up' ); + } ); } ); diff --git a/plugins/woocommerce/e2e/tests/admin-analytics/analytics.spec.js b/plugins/woocommerce/e2e/tests/admin-analytics/analytics.spec.js index 2e2b2948917..44c283ed237 100644 --- a/plugins/woocommerce/e2e/tests/admin-analytics/analytics.spec.js +++ b/plugins/woocommerce/e2e/tests/admin-analytics/analytics.spec.js @@ -24,7 +24,7 @@ test.describe( 'Analytics pages', () => { `/wp-admin/admin.php?page=wc-admin&path=%2Fanalytics%2F${ urlTitle }` ); const pageTitle = page.locator( 'h1' ); - await expect( pageTitle ).toHaveText( aPages ); + await expect( pageTitle ).toContainText( aPages ); await expect( page.locator( '#woocommerce-layout__primary' ) ).toBeVisible(); diff --git a/plugins/woocommerce/e2e/tests/admin-tasks/payment.spec.js b/plugins/woocommerce/e2e/tests/admin-tasks/payment.spec.js index e59b432bb6b..860d2ebba84 100644 --- a/plugins/woocommerce/e2e/tests/admin-tasks/payment.spec.js +++ b/plugins/woocommerce/e2e/tests/admin-tasks/payment.spec.js @@ -1,4 +1,5 @@ const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; test.describe( 'Payment setup task', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); @@ -12,6 +13,21 @@ test.describe( 'Payment setup task', () => { await page.waitForLoadState( 'networkidle' ); } ); + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'payment_gateways/bacs', { + enabled: false, + } ); + await api.put( 'payment_gateways/cod', { + enabled: false, + } ); + } ); + test( 'Can visit the payment setup task from the homescreen if the setup wizard has been skipped', async ( { page, } ) => { @@ -23,13 +39,16 @@ test.describe( 'Payment setup task', () => { test( 'Saving valid bank account transfer details enables the payment method', async ( { page, } ) => { + // load the bank transfer page await page.goto( 'wp-admin/admin.php?page=wc-admin&task=payments&id=bacs' ); + // purposely no await -- close the help dialog if/when it appears page.locator( '.components-button.is-small.has-icon' ) .click() .catch( () => {} ); - // purposely no await -- close the help dialog if/when it appears + + // fill in bank transfer form await page.fill( '//input[@placeholder="Account name"]', 'Savings' ); await page.fill( '//input[@placeholder="Account number"]', '1234' ); await page.fill( '//input[@placeholder="Bank name"]', 'Test Bank' ); @@ -37,46 +56,48 @@ test.describe( 'Payment setup task', () => { await page.fill( '//input[@placeholder="IBAN"]', '12 3456 7890' ); await page.fill( '//input[@placeholder="BIC / Swift"]', 'ABBA' ); await page.click( 'text=Save' ); + + // check that bank transfers were set up await expect( page.locator( 'div.components-snackbar__content' ) - ).toHaveText( 'Direct bank transfer details added successfully' ); - await expect( page.locator( 'h1' ) ).toHaveText( 'Set up payments' ); + ).toContainText( 'Direct bank transfer details added successfully' ); + + await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=checkout' ); + await expect( page.locator( - 'a:right-of(h3:has-text("Direct bank transfer")) >> nth=0' + '//tr[@data-gateway_id="bacs"]/td[@class="status"]/a' ) - ).toHaveText( 'Manage' ); - - // clean up - await page.goto( - 'wp-admin/admin.php?page=wc-settings&tab=checkout§ion=bacs' - ); - await page.click( 'text="Enable bank transfer"' ); - await page.click( 'text="Save changes"' ); + ).toHaveClass( 'wc-payment-gateway-method-toggle-enabled' ); } ); test( 'Enabling cash on delivery enables the payment method', async ( { page, } ) => { await page.goto( 'wp-admin/admin.php?page=wc-admin&task=payments' ); + + // purposely no await -- close the help dialog if/when it appears page.locator( '.components-button.is-small.has-icon' ) .click() .catch( () => {} ); - // purposely no await -- close the help dialog if/when it appears - await page.click( 'button:has-text("Enable")' ); // enable COD payment option - await page.goto( 'wp-admin/admin.php?page=wc-admin&task=payments' ); - await expect( page.locator( 'h1' ) ).toHaveText( 'Set up payments' ); - await expect( - page.locator( - 'a:right-of(h3:has-text("Cash on delivery")) >> nth=0' - ) - ).toHaveText( 'Manage' ); - // clean up - await page.goto( - 'wp-admin/admin.php?page=wc-settings&tab=checkout§ion=cod' + if ( await page.isVisible( 'text=Offline payment methods' ) ) { + // other payment methods are already shown + } else { + // show other payment methods + await page.click( 'button.toggle-button' ); + } + + // enable COD payment option + await page.click( + 'div.woocommerce-task-payment-cod > div.woocommerce-task-payment__footer > button' ); - await page.click( 'text="Enable cash on delivery"' ); - await page.click( 'text="Save changes"' ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=checkout' ); + + await expect( + page.locator( '//tr[@data-gateway_id="cod"]/td[@class="status"]/a' ) + ).toHaveClass( 'wc-payment-gateway-method-toggle-enabled' ); } ); } ); diff --git a/plugins/woocommerce/e2e/tests/basic.spec.js b/plugins/woocommerce/e2e/tests/basic.spec.js index 41cee6f1926..b18fa3e107b 100644 --- a/plugins/woocommerce/e2e/tests/basic.spec.js +++ b/plugins/woocommerce/e2e/tests/basic.spec.js @@ -1,25 +1,32 @@ const { test, expect } = require( '@playwright/test' ); -test( 'Load the home page', async ( { page } ) => { - await page.goto( '/' ); - const title = page.locator( 'h1.site-title' ); - await expect( title ).toHaveText( 'WooCommerce Core E2E Test Suite' ); -} ); +test.describe( + 'A basic set of tests to ensure WP, wp-admin and my-account load', + () => { + test( 'Load the home page', async ( { page } ) => { + await page.goto( '/' ); + const title = page.locator( 'h1.site-title' ); + await expect( title ).toHaveText( + 'WooCommerce Core E2E Test Suite' + ); + } ); -test.describe( 'Sign in as admin', () => { - test.use( { storageState: 'e2e/storage/adminState.json' } ); - test( 'Load wp-admin', async ( { page } ) => { - await page.goto( '/wp-admin' ); - const title = page.locator( 'div.wrap > h1' ); - await expect( title ).toHaveText( 'Dashboard' ); - } ); -} ); + test.describe( 'Sign in as admin', () => { + test.use( { storageState: 'e2e/storage/adminState.json' } ); + test( 'Load wp-admin', async ( { page } ) => { + await page.goto( '/wp-admin' ); + const title = page.locator( 'div.wrap > h1' ); + await expect( title ).toHaveText( 'Dashboard' ); + } ); + } ); -test.describe( 'Sign in as customer', () => { - test.use( { storageState: 'e2e/storage/customerState.json' } ); - test( 'Load customer my account page', async ( { page } ) => { - await page.goto( '/my-account' ); - const title = page.locator( 'h1.entry-title' ); - await expect( title ).toHaveText( 'My account' ); - } ); -} ); + test.describe( 'Sign in as customer', () => { + test.use( { storageState: 'e2e/storage/customerState.json' } ); + test( 'Load customer my account page', async ( { page } ) => { + await page.goto( '/my-account' ); + const title = page.locator( 'h1.entry-title' ); + await expect( title ).toHaveText( 'My account' ); + } ); + } ); + } +); diff --git a/plugins/woocommerce/e2e/tests/merchant/create-coupon.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-coupon.spec.js index 63df4eb5fa0..889bbb44a90 100644 --- a/plugins/woocommerce/e2e/tests/merchant/create-coupon.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/create-coupon.spec.js @@ -1,14 +1,32 @@ const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const couponCode = `code-${ new Date().getTime().toString() }`; test.describe( 'Add New Coupon Page', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.get( 'coupons' ).then( ( response ) => { + for ( let i = 0; i < response.data.length; i++ ) { + if ( response.data[ i ].code === couponCode ) { + api.delete( `coupons/${ response.data[ i ].id }`, { + force: true, + } ); + } + } + } ); + } ); + test( 'can create new coupon', async ( { page } ) => { await page.goto( 'wp-admin/post-new.php?post_type=shop_coupon' ); - await page.fill( - '#title', - `code-${ new Date().getTime().toString() }` - ); + await page.fill( '#title', couponCode ); await page.fill( '#woocommerce-coupon-description', 'test coupon' ); await page.fill( '#coupon_amount', '100' ); @@ -18,8 +36,5 @@ test.describe( 'Add New Coupon Page', () => { await expect( page.locator( 'div.notice.notice-success' ) ).toHaveText( 'Coupon updated.Dismiss this notice.' ); - - // delete the coupon - await page.dispatchEvent( 'a.submitdelete', 'click' ); } ); } ); diff --git a/plugins/woocommerce/e2e/tests/merchant/create-order.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-order.spec.js index 451208dffc7..b8a9f1f67dd 100644 --- a/plugins/woocommerce/e2e/tests/merchant/create-order.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/create-order.spec.js @@ -1,6 +1,10 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; +const simpleProductName = 'Add new order simple product'; +const variableProductName = 'Add new order variable product'; +const externalProductName = 'Add new order external product'; +const groupedProductName = 'Add new order grouped product'; const taxClasses = [ { name: 'Tax Class Simple', @@ -30,13 +34,14 @@ const taxRates = [ }, ]; const taxClassSlugs = []; -const taxTotals = [ '$10.00', '$60.00', '$240.00' ]; +const taxTotals = [ '10.00', '20.00', '240.00' ]; let simpleProductId, variableProductId, externalProductId, subProductAId, subProductBId, - groupedProductId; + groupedProductId, + orderId; test.describe( 'WooCommerce Orders > Add new order', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); @@ -53,41 +58,32 @@ test.describe( 'WooCommerce Orders > Add new order', () => { value: 'yes', } ); // add tax classes - let a = 0; - for ( const tax in taxClasses ) { - api.post( 'taxes/classes', taxClasses[ tax ] ).then( - ( response ) => { - taxClassSlugs[ a ] = response.data.slug; - a++; - // add tax rates - for ( const rate in taxRates ) { - api.post( 'taxes', taxRates[ rate ] ); - } - } - ); + for ( let i = 0; i < taxClasses.length; i++ ) { + await api + .post( 'taxes/classes', taxClasses[ i ] ) + .then( ( response ) => { + taxClassSlugs[ i ] = response.data.slug; + } ); + } + // attach rates to the classes + for ( let i = 0; i < taxRates.length; i++ ) { + await api.post( 'taxes', taxRates[ i ] ); } - // make sure the taxes are all created before creating products - api.get( 'taxes/classes' ).then( ( response ) => { - while ( true ) { - if ( Object.keys( response.data ).length === 3 ) { - api.post( 'products', { - name: 'Simple Product 273722', - type: 'simple', - regular_price: '100', - tax_class: 'Tax Class Simple', - } ).then( ( resp ) => { - simpleProductId = resp.data.id; - } ); - break; - } - } - } ); // create simple product - + await api + .post( 'products', { + name: simpleProductName, + type: 'simple', + regular_price: '100', + tax_class: 'Tax Class Simple', + } ) + .then( ( resp ) => { + simpleProductId = resp.data.id; + } ); // create variable product const variations = [ { - regular_price: '200', + regular_price: '100', attributes: [ { name: 'Size', @@ -101,7 +97,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { tax_class: 'Tax Class Variable', }, { - regular_price: '300', + regular_price: '100', attributes: [ { name: 'Size', @@ -117,7 +113,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { ]; await api .post( 'products', { - name: 'Variable Product 024611', + name: variableProductName, type: 'variable', tax_class: 'Tax Class Variable', } ) @@ -133,7 +129,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { // create external product await api .post( 'products', { - name: 'External product 786794', + name: externalProductName, regular_price: '800', tax_class: 'Tax Class External', external_url: 'https://wordpress.org/plugins/woocommerce', @@ -156,7 +152,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { } ); await api .post( 'products', { - name: 'Grouped Product 858012', + name: groupedProductName, regular_price: '29.99', grouped_products: [ subProductAId, subProductBId ], type: 'grouped', @@ -167,22 +163,26 @@ test.describe( 'WooCommerce Orders > Add new order', () => { } ); test.afterAll( async ( { baseURL } ) => { - // cleans up all products after run const api = new wcApi( { url: baseURL, consumerKey: process.env.CONSUMER_KEY, consumerSecret: process.env.CONSUMER_SECRET, version: 'wc/v3', } ); - await api.delete( `products/${ simpleProductId }`, { force: true } ); - await api.delete( `products/${ variableProductId }`, { force: true } ); - await api.delete( `products/${ externalProductId }`, { force: true } ); - await api.delete( `products/${ subProductAId }`, { force: true } ); - await api.delete( `products/${ subProductBId }`, { force: true } ); - await api.delete( `products/${ groupedProductId }`, { force: true } ); + // cleans up all products after run + await api.post( 'products/batch', { + delete: [ + simpleProductId, + variableProductId, + externalProductId, + subProductAId, + subProductBId, + groupedProductId, + ], + } ); // clean up tax classes and rates - for ( const key in taxClassSlugs ) { - await api.delete( `taxes/classes/${ taxClassSlugs[ key ] }`, { + for ( let i = 0; i < taxClassSlugs.length; i++ ) { + await api.delete( `taxes/classes/${ taxClassSlugs[ i ] }`, { force: true, } ); } @@ -190,6 +190,11 @@ test.describe( 'WooCommerce Orders > Add new order', () => { await api.put( 'settings/general/woocommerce_calc_taxes', { value: 'no', } ); + + // if we're only running the second test, there's no orderId created + if ( orderId ) { + await api.delete( `orders/${ orderId }`, { force: true } ); + } } ); test( 'can create new order', async ( { page } ) => { @@ -198,6 +203,18 @@ test.describe( 'WooCommerce Orders > Add new order', () => { 'Add new order' ); + await page.waitForLoadState( 'networkidle' ); + // get order ID from the page + const orderHtmlElement = await page.$( + 'h2.woocommerce-order-data__heading' + ); + const orderText = await page.evaluate( + ( element ) => element.textContent, + orderHtmlElement + ); + orderId = orderText.match( /([0-9])\w+/ ); + orderId = orderId[ 0 ].toString(); + await page.selectOption( '#order_status', 'wc-processing' ); await page.fill( 'input[name=order_date]', '2018-12-13' ); await page.fill( 'input[name=order_date_hour]', '18' ); @@ -231,7 +248,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { await page.click( 'text=Search for a product…' ); await page.type( 'input:below(:text("Search for a product…"))', - 'Simple Product 273722' + simpleProductName ); await page.click( 'li.select2-results__option.select2-results__option--highlighted' @@ -240,7 +257,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { await page.click( 'text=Search for a product…' ); await page.type( 'input:below(:text("Search for a product…"))', - 'Variable Product 024611' + variableProductName ); await page.click( 'li.select2-results__option.select2-results__option--highlighted' @@ -249,7 +266,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { await page.click( 'text=Search for a product…' ); await page.type( 'input:below(:text("Search for a product…"))', - 'Grouped Product 858012' + groupedProductName ); await page.click( 'li.select2-results__option.select2-results__option--highlighted' @@ -258,7 +275,7 @@ test.describe( 'WooCommerce Orders > Add new order', () => { await page.click( 'text=Search for a product…' ); await page.type( 'input:below(:text("Search for a product…"))', - 'External product 786794' + externalProductName ); await page.click( 'li.select2-results__option.select2-results__option--highlighted' @@ -268,16 +285,16 @@ test.describe( 'WooCommerce Orders > Add new order', () => { // assert that products added await expect( page.locator( 'td.name > a >> nth=0' ) ).toContainText( - 'Simple Product 273722' + simpleProductName ); await expect( page.locator( 'td.name > a >> nth=1' ) ).toContainText( - 'Variable Product 024611' + variableProductName ); await expect( page.locator( 'td.name > a >> nth=2' ) ).toContainText( - 'Grouped Product 858012' + groupedProductName ); await expect( page.locator( 'td.name > a >> nth=3' ) ).toContainText( - 'External product 786794' + externalProductName ); // Recalculate taxes diff --git a/plugins/woocommerce/e2e/tests/merchant/create-shipping-classes.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-shipping-classes.spec.js index dfb83bcaa7c..f3ad9e2b599 100644 --- a/plugins/woocommerce/e2e/tests/merchant/create-shipping-classes.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/create-shipping-classes.spec.js @@ -3,6 +3,23 @@ const { test, expect } = require( '@playwright/test' ); test.describe( 'Merchant can add shipping classes', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); + test.afterEach( async ( { page } ) => { + // no api endpoints for shipping classes, so use the UI to cleanup + await page.goto( + 'wp-admin/admin.php?page=wc-settings&tab=shipping§ion=classes' + ); + + await page.dispatchEvent( + '.wc-shipping-class-delete >> nth=0', + 'click' + ); + await page.dispatchEvent( + '.wc-shipping-class-delete >> nth=0', + 'click' + ); + await page.dispatchEvent( 'text=Save shipping classes', 'click' ); + } ); + test( 'can add shipping classes', async ( { page } ) => { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping§ion=classes' @@ -54,16 +71,5 @@ test.describe( 'Merchant can add shipping classes', () => { ).toBeVisible(); } } - - // clean up - await page.dispatchEvent( - '.wc-shipping-class-delete >> nth=0', - 'click' - ); - await page.dispatchEvent( - '.wc-shipping-class-delete >> nth=0', - 'click' - ); - await page.dispatchEvent( 'text=Save shipping classes', 'click' ); } ); } ); diff --git a/plugins/woocommerce/e2e/tests/merchant/create-shipping-zones.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-shipping-zones.spec.js index 9260fad5df3..3ef0c998da6 100644 --- a/plugins/woocommerce/e2e/tests/merchant/create-shipping-zones.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/create-shipping-zones.spec.js @@ -1,38 +1,59 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; -const sanFranciscoZIP = '94107'; -const shippingZoneNameUS = 'US with Flat rate'; -const shippingZoneNameFL = 'CA with Free shipping'; -const shippingZoneNameSF = 'SF with Local pickup'; -let productId; +const maynePostal = 'V0N 2J0'; +const shippingZoneNameFlatRate = 'Canada with Flat rate'; +const shippingZoneNameFreeShip = 'BC with Free shipping'; +const shippingZoneNameLocalPickup = 'Mayne Island with Local pickup'; test.describe( 'WooCommerce Shipping Settings - Add new shipping zone', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); - test( 'add shipping zone for San Francisco with free Local pickup', async ( { + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.get( 'shipping/zones' ).then( ( response ) => { + for ( let i = 0; i < response.data.length; i++ ) { + if ( + response.data[ i ].name === shippingZoneNameFlatRate || + response.data[ i ].name === shippingZoneNameFreeShip || + response.data[ i ].name === shippingZoneNameLocalPickup + ) { + api.delete( `shipping/zones/${ response.data[ i ].id }`, { + force: true, + } ); + } + } + } ); + } ); + + test( 'add shipping zone for Mayne Island with free Local pickup', async ( { page, } ) => { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping' ); - if ( await page.isVisible( `text=${ shippingZoneNameSF }` ) ) { + if ( await page.isVisible( `text=${ shippingZoneNameLocalPickup }` ) ) { // this shipping zone already exists, don't create it } else { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping&zone_id=new' ); - await page.fill( '#zone_name', shippingZoneNameSF ); + await page.fill( '#zone_name', shippingZoneNameLocalPickup ); await page.click( '.select2-search__field' ); await page.type( '.select2-search__field', - 'California, United States' + 'British Columbia, Canada' ); await page.click( '.select2-results__option.select2-results__option--highlighted' ); await page.click( '.wc-shipping-zone-postcodes-toggle' ); - await page.fill( '#zone_postcodes', sanFranciscoZIP ); + await page.fill( '#zone_postcodes', maynePostal ); await page.click( 'text=Add shipping method' ); @@ -49,32 +70,32 @@ test.describe( 'WooCommerce Shipping Settings - Add new shipping zone', () => { } await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( - /SF with Local pickup.*/ + /Mayne Island with Local pickup.*/ ); await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( - /California, 94107.*/ + /British Columbia, V0N 2J0.*/ ); await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( /Local pickup.*/ ); } ); - test( 'add shipping zone for California with Free shipping', async ( { + test( 'add shipping zone for British Columbia with Free shipping', async ( { page, } ) => { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping' ); - if ( await page.isVisible( `text=${ shippingZoneNameFL }` ) ) { + if ( await page.isVisible( `text=${ shippingZoneNameFreeShip }` ) ) { // this shipping zone already exists, don't create it } else { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping&zone_id=new' ); - await page.fill( '#zone_name', shippingZoneNameFL ); + await page.fill( '#zone_name', shippingZoneNameFreeShip ); await page.click( '.select2-search__field' ); await page.type( '.select2-search__field', - 'California, United States' + 'British Columbia, Canada' ); await page.click( '.select2-results__option.select2-results__option--highlighted' @@ -94,28 +115,28 @@ test.describe( 'WooCommerce Shipping Settings - Add new shipping zone', () => { await page.reload(); // Playwright runs so fast, the location shows up as "Everywhere" at first } await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( - /CA with Free shipping.*/ + /BC with Free shipping.*/ ); await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( - /California.*/ + /British Columbia.*/ ); await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( /Free shipping.*/ ); } ); - test( 'add shipping zone for the US with Flat rate', async ( { page } ) => { + test( 'add shipping zone for Canada with Flat rate', async ( { page } ) => { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping' ); - if ( await page.isVisible( `text=${ shippingZoneNameUS }` ) ) { + if ( await page.isVisible( `text=${ shippingZoneNameFlatRate }` ) ) { // this shipping zone already exists, don't create it } else { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=shipping&zone_id=new' ); - await page.fill( '#zone_name', shippingZoneNameUS ); + await page.fill( '#zone_name', shippingZoneNameFlatRate ); await page.click( '.select2-search__field' ); - await page.type( '.select2-search__field', 'United States' ); + await page.type( '.select2-search__field', 'Canada' ); await page.click( '.select2-results__option.select2-results__option--highlighted' ); @@ -138,10 +159,10 @@ test.describe( 'WooCommerce Shipping Settings - Add new shipping zone', () => { await page.reload(); // Playwright runs so fast, the location shows up as "Everywhere" at first } await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( - /US with Flat rate*/ + /Canada with Flat rate*/ ); await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( - /United States \(US\).*/ + /Canada.*/ ); await expect( page.locator( '.wc-shipping-zones' ) ).toHaveText( /Flat rate.*/ @@ -151,109 +172,171 @@ test.describe( 'WooCommerce Shipping Settings - Add new shipping zone', () => { test.describe( 'Verifies shipping options from customer perspective', () => { // note: tests are being run in an unauthenticated state (not as admin) - test.beforeAll( async () => { + let productId, shippingFreeId, shippingFlatId, shippingLocalId; + + test.beforeAll( async ( { baseURL } ) => { // need to add a product to the store so that we can order it and check shipping options const api = new wcApi( { - url: 'http://localhost:8084', + url: baseURL, consumerKey: process.env.CONSUMER_KEY, consumerSecret: process.env.CONSUMER_SECRET, version: 'wc/v3', } ); - api.post( 'products', { - name: 'Shipping options are the best', - type: 'simple', - regular_price: '25.99', - } ).then( ( response ) => { - productId = response.data.id; + await api + .post( 'products', { + name: 'Shipping options are the best', + type: 'simple', + regular_price: '25.99', + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + // create shipping zones + await api + .post( 'shipping/zones', { + name: shippingZoneNameLocalPickup, + } ) + .then( ( response ) => { + shippingLocalId = response.data.id; + } ); + await api + .post( 'shipping/zones', { + name: shippingZoneNameFreeShip, + } ) + .then( ( response ) => { + shippingFreeId = response.data.id; + } ); + await api + .post( 'shipping/zones', { + name: shippingZoneNameFlatRate, + } ) + .then( ( response ) => { + shippingFlatId = response.data.id; + } ); + // set shipping zone locations + await api.put( `shipping/zones/${ shippingFlatId }/locations`, [ + { + code: 'CA', + }, + ] ); + await api.put( `shipping/zones/${ shippingFreeId }/locations`, [ + { + code: 'CA:BC', + type: 'state', + }, + ] ); + await api.put( `shipping/zones/${ shippingLocalId }/locations`, [ + { + code: 'V0N 2J0', + type: 'postcode', + }, + ] ); + // set shipping zone methods + await api.post( `shipping/zones/${ shippingFlatId }/methods`, { + method_id: 'flat_rate', + settings: { + cost: '10.00', + }, + } ); + await api.post( `shipping/zones/${ shippingFreeId }/methods`, { + method_id: 'free_shipping', + } ); + await api.post( `shipping/zones/${ shippingLocalId }/methods`, { + method_id: 'local_pickup', } ); } ); - test.afterAll( async () => { + test.beforeEach( async ( { context, page } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test.afterAll( async ( { baseURL } ) => { const api = new wcApi( { - url: 'http://localhost:8084', + url: baseURL, consumerKey: process.env.CONSUMER_KEY, consumerSecret: process.env.CONSUMER_SECRET, version: 'wc/v3', } ); - api.delete( `products/${ productId }`, { force: true } ); + await api.delete( `products/${ productId }`, { force: true } ); + await api.delete( `shipping/zones/${ shippingFlatId }`, { + force: true, + } ); + await api.delete( `shipping/zones/${ shippingFreeId }`, { + force: true, + } ); + await api.delete( `shipping/zones/${ shippingLocalId }`, { + force: true, + } ); } ); - test( 'allows customer to benefit from a free Local pickup if in SF', async ( { + test( 'allows customer to benefit from a free Local pickup if on Mayne Island', async ( { page, } ) => { - await page.goto( '/shop' ); - await page.click( 'text=Add to cart' ); - await page.click( 'text=View cart' ); - - await page.click( 'text=Change address' ); - await page.fill( '#calc_shipping_postcode', '94107' ); + await page.goto( 'cart/' ); + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', 'CA' ); + await page.selectOption( '#calc_shipping_state', 'BC' ); + await page.fill( '#calc_shipping_postcode', maynePostal ); await page.click( 'button[name=calc_shipping]' ); await page.waitForSelector( 'button[name=calc_shipping]', { state: 'hidden', } ); - expect( - await page.textContent( - '.shipping ul#shipping_method > li > label' - ) - ).toBe( 'Local pickup' ); - expect( - await page.textContent( - 'td[data-title="Total"] > strong > .amount > bdi' - ) - ).toBe( '$25.99' ); + await expect( + page.locator( '.shipping ul#shipping_method > li > label' ) + ).toContainText( 'Local pickup' ); + await expect( + page.locator( 'td[data-title="Total"] > strong > .amount > bdi' ) + ).toContainText( '25.99' ); } ); - test( 'allows customer to benefit from a free Free shipping if in CA', async ( { + test( 'allows customer to benefit from a free Free shipping if in BC', async ( { page, } ) => { - await page.goto( '/shop' ); - await page.click( 'text=Add to cart' ); - await page.click( 'text=View cart' ); + await page.goto( 'cart/' ); - await page.click( 'text=Change address' ); - await page.fill( '#calc_shipping_postcode', '94000' ); + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', 'CA' ); + await page.selectOption( '#calc_shipping_state', 'BC' ); await page.click( 'button[name=calc_shipping]' ); await page.waitForSelector( 'button[name=calc_shipping]', { state: 'hidden', } ); - expect( - await page.textContent( - '.shipping ul#shipping_method > li > label' - ) - ).toBe( 'Free shipping' ); - expect( - await page.textContent( - 'td[data-title="Total"] > strong > .amount > bdi' - ) - ).toBe( '$25.99' ); + await expect( + page.locator( '.shipping ul#shipping_method > li > label' ) + ).toContainText( 'Free shipping' ); + await expect( + page.locator( 'td[data-title="Total"] > strong > .amount > bdi' ) + ).toContainText( '25.99' ); } ); test( 'allows customer to pay for a Flat rate shipping method', async ( { page, } ) => { - await page.goto( '/shop' ); - await page.click( 'text=Add to cart' ); - await page.click( 'text=View cart' ); + await page.goto( 'cart/' ); - await page.click( 'text=Change address' ); - await page.selectOption( '#calc_shipping_state', 'NY' ); - await page.fill( '#calc_shipping_postcode', '10010' ); + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', 'CA' ); + await page.selectOption( '#calc_shipping_state', 'AB' ); + await page.fill( '#calc_shipping_postcode', 'T2T 1B3' ); await page.click( 'button[name=calc_shipping]' ); await page.waitForSelector( 'button[name=calc_shipping]', { state: 'hidden', } ); - expect( - await page.textContent( - '.shipping ul#shipping_method > li > label' - ) - ).toBe( 'Flat rate: $10.00' ); - expect( - await page.textContent( - 'td[data-title="Total"] > strong > .amount > bdi' - ) - ).toBe( '$35.99' ); + await expect( + page.locator( '.shipping ul#shipping_method > li > label' ) + ).toContainText( 'Flat rate:' ); + await expect( + page.locator( '.shipping ul#shipping_method > li > label' ) + ).toContainText( '10.00' ); + await expect( + page.locator( 'td[data-title="Total"] > strong > .amount > bdi' ) + ).toContainText( '35.99' ); } ); } ); diff --git a/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js index 607de7df939..c0be70730f1 100644 --- a/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/create-simple-product.spec.js @@ -20,10 +20,13 @@ test.describe( 'Add New Simple Product Page', () => { // and the flat rate shipping method to that zone await api .post( 'shipping/zones', { - name: 'Everywhere', + name: 'Somewhere', } ) .then( ( response ) => { shippingZoneId = response.data.id; + api.put( `shipping/zones/${ shippingZoneId }/locations`, [ + { code: 'CN' }, + ] ); api.post( `shipping/zones/${ shippingZoneId }/methods`, { method_id: 'flat_rate', } ); diff --git a/plugins/woocommerce/e2e/tests/merchant/create-variable-product.spec.js b/plugins/woocommerce/e2e/tests/merchant/create-variable-product.spec.js index 5ec97af7eea..54aeb12f277 100644 --- a/plugins/woocommerce/e2e/tests/merchant/create-variable-product.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/create-variable-product.spec.js @@ -34,8 +34,6 @@ test.describe( 'Add New Variable Product Page', () => { ) { api.delete( `products/${ product.id }`, { force: true, - } ).then( () => { - // nothing to do here. } ); } } @@ -290,8 +288,8 @@ test.describe( 'Add New Variable Product Page', () => { // remove a variation page.on( 'dialog', ( dialog ) => dialog.accept() ); - await page.click( '.remove_variation.delete', { force: true } ); - await page.click( '.remove_variation.delete' ); // have to do this twice to get the link to appear + await page.hover( '.woocommerce_variation' ); + await page.click( '.remove_variation.delete' ); await expect( page.locator( '.woocommerce_variation' ) ).toHaveCount( 0 ); diff --git a/plugins/woocommerce/e2e/tests/merchant/order-coupon.spec.js b/plugins/woocommerce/e2e/tests/merchant/order-coupon.spec.js index c51a50797e2..9e3239e2f15 100644 --- a/plugins/woocommerce/e2e/tests/merchant/order-coupon.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/order-coupon.spec.js @@ -4,6 +4,8 @@ const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; let productId, couponId, orderId; const productPrice = '9.99'; +const productName = 'Apply Coupon Product'; +const couponCode = '5off'; const couponAmount = '5'; const discountedPrice = ( productPrice - couponAmount ).toString(); @@ -20,7 +22,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { // create a simple product await api .post( 'products', { - name: 'Simple Product', + name: productName, type: 'simple', regular_price: productPrice, } ) @@ -30,7 +32,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { // create a $5 off coupon await api .post( 'coupons', { - code: '5off', + code: couponCode, discount_type: 'fixed_product', amount: couponAmount, } ) @@ -48,7 +50,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { ], coupon_lines: [ { - code: '5off', + code: couponCode, }, ], } ) @@ -81,7 +83,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { await page.click( 'text=Search for a product…' ); await page.type( 'input:below(:text("Search for a product…"))', - 'Simple Product' + productName ); await page.click( 'li.select2-results__option.select2-results__option--highlighted' @@ -90,10 +92,10 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { await page.click( 'button#btn-ok' ); // apply coupon - page.on( 'dialog', ( dialog ) => dialog.accept( '5off' ) ); + page.on( 'dialog', ( dialog ) => dialog.accept( couponCode ) ); await page.click( 'button.add-coupon' ); - await expect( page.locator( 'text=5off' ) ).toBeVisible(); + await expect( page.locator( `text=${ couponCode }` ) ).toBeVisible(); await expect( page.locator( '.wc-order-totals td.label >> nth=1' ) ).toContainText( 'Coupon(s)' ); @@ -111,7 +113,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { test( 'can remove a coupon', async ( { page } ) => { await page.goto( `/wp-admin/post.php?post=${ orderId }&action=edit` ); // assert that there is a coupon on the order - await expect( page.locator( 'text=5off' ) ).toBeVisible(); + await expect( page.locator( `text=${ couponCode }` ) ).toBeVisible(); await expect( page.locator( '.wc-order-totals td.label >> nth=1' ) ).toContainText( 'Coupon(s)' ); @@ -128,12 +130,14 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => { await page.dispatchEvent( 'a.remove-coupon', 'click' ); // have to use dispatchEvent because nothing visible to click on // make sure the coupon was removed - await expect( page.locator( 'text=5off' ) ).not.toBeVisible(); + await expect( + page.locator( `text=${ couponCode }` ) + ).not.toBeVisible(); await expect( page.locator( '.wc-order-totals td.label >> nth=1' ) ).toContainText( 'Order Total' ); await expect( page.locator( '.wc-order-totals td.total >> nth=1' ) - ).toContainText( '$9.99' ); + ).toContainText( productPrice ); } ); } ); diff --git a/plugins/woocommerce/e2e/tests/merchant/order-edit.spec.js b/plugins/woocommerce/e2e/tests/merchant/order-edit.spec.js index 3bbcc62636f..2c8b09094ca 100644 --- a/plugins/woocommerce/e2e/tests/merchant/order-edit.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/order-edit.spec.js @@ -2,11 +2,11 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; const uuid = require( 'uuid' ); -let orderId; - -test.describe( 'WooCommerce Orders > Edit order', () => { +test.describe( 'Edit order', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); + let orderId; + test.beforeAll( async ( { baseURL } ) => { const api = new wcApi( { url: baseURL, @@ -86,279 +86,256 @@ test.describe( 'WooCommerce Orders > Edit order', () => { } ); } ); -test.describe( - 'WooCommerce Orders > Edit order > Downloadable product permissions', - () => { - test.use( { storageState: 'e2e/storage/adminState.json' } ); +test.describe( 'Edit order > Downloadable product permissions', () => { + test.use( { storageState: 'e2e/storage/adminState.json' } ); - const productName = 'TDP 001'; - const product2Name = 'TDP 002'; - const customerBilling = { - email: 'john.doe@example.com', - }; + const productName = 'TDP 001'; + const product2Name = 'TDP 002'; + const customerBilling = { + email: 'john.doe@example.com', + }; - let productId, product2Id, noProductOrderId; + let orderId, productId, product2Id, noProductOrderId; - test.beforeEach( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', + test.beforeEach( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api + .post( 'products', { + name: productName, + downloadable: true, + download_limit: -1, + downloads: [ + { + id: uuid.v4(), + name: 'Single', + file: + 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', + }, + ], + } ) + .then( ( response ) => { + productId = response.data.id; } ); - await api - .post( 'products', { - name: productName, - downloadable: true, - download_limit: -1, - downloads: [ - { - id: uuid.v4(), - name: 'Single', - file: - 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', - }, - ], - } ) - .then( ( response ) => { - productId = response.data.id; - } ); - await api - .post( 'products', { - name: product2Name, - downloadable: true, - download_limit: -1, - downloads: [ - { - id: uuid.v4(), - name: 'Single', - file: - 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', - }, - ], - } ) - .then( ( response ) => { - product2Id = response.data.id; - } ); - await api - .post( 'orders', { - status: 'processing', - line_items: [ - { - product_id: productId, - quantity: 1, - }, - ], - billing: customerBilling, - } ) - .then( ( response ) => { - orderId = response.data.id; - } ); - await api - .post( 'orders', { - status: 'processing', - billing: customerBilling, - } ) - .then( ( response ) => { - noProductOrderId = response.data.id; - } ); - } ); - - test.afterEach( async ( { baseURL } ) => { - const api = new wcApi( { - url: baseURL, - consumerKey: process.env.CONSUMER_KEY, - consumerSecret: process.env.CONSUMER_SECRET, - version: 'wc/v3', + await api + .post( 'products', { + name: product2Name, + downloadable: true, + download_limit: -1, + downloads: [ + { + id: uuid.v4(), + name: 'Single', + file: + 'https://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2017/08/single.jpg', + }, + ], + } ) + .then( ( response ) => { + product2Id = response.data.id; } ); - await api.delete( `products/${ productId }`, { force: true } ); - await api.delete( `products/${ product2Id }`, { force: true } ); - await api.delete( `orders/${ orderId }`, { force: true } ); - await api.delete( `orders/${ noProductOrderId }`, { force: true } ); + await api + .post( 'orders', { + status: 'processing', + line_items: [ + { + product_id: productId, + quantity: 1, + }, + ], + billing: customerBilling, + } ) + .then( ( response ) => { + orderId = response.data.id; + } ); + await api + .post( 'orders', { + status: 'processing', + billing: customerBilling, + } ) + .then( ( response ) => { + noProductOrderId = response.data.id; + } ); + } ); + + test.afterEach( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', } ); + await api.delete( `products/${ productId }`, { force: true } ); + await api.delete( `products/${ product2Id }`, { force: true } ); + await api.delete( `orders/${ orderId }`, { force: true } ); + await api.delete( `orders/${ noProductOrderId }`, { force: true } ); + } ); - test( 'can add downloadable product permissions to order without product', async ( { - page, - } ) => { - // go to the order with no products - await page.goto( - `wp-admin/post.php?post=${ noProductOrderId }&action=edit` - ); + test( 'can add downloadable product permissions to order without product', async ( { + page, + } ) => { + // go to the order with no products + await page.goto( + `wp-admin/post.php?post=${ noProductOrderId }&action=edit` + ); - // add downloadable product permissions - await page.type( 'input.select2-search__field', productName ); - await page.click( - 'li.select2-results__option.select2-results__option--highlighted' - ); - await page.click( 'button.grant_access' ); + // add downloadable product permissions + await page.type( 'input.select2-search__field', productName ); + await page.click( + 'li.select2-results__option.select2-results__option--highlighted' + ); + await page.click( 'button.grant_access' ); - // verify new downloadable product permission details - await expect( page.locator( 'h3.fixed' ) ).toContainText( - productName - ); - await expect( - page.locator( 'input[name="downloads_remaining[1]"]' ) - ).toHaveAttribute( 'placeholder', 'Unlimited' ); - await expect( - page.locator( 'input[name="access_expires[1]"]' ) - ).toHaveAttribute( 'placeholder', 'Never' ); - await expect( - page.locator( 'button.revoke_access' ) - ).toBeVisible(); - await expect( - page.locator( 'a:has-text("Copy link")' ) - ).toBeVisible(); - await expect( - page.locator( 'a:has-text("View report")' ) - ).toBeVisible(); - } ); + // verify new downloadable product permission details + await expect( page.locator( 'h3.fixed' ) ).toContainText( productName ); + await expect( + page.locator( 'input[name="downloads_remaining[1]"]' ) + ).toHaveAttribute( 'placeholder', 'Unlimited' ); + await expect( + page.locator( 'input[name="access_expires[1]"]' ) + ).toHaveAttribute( 'placeholder', 'Never' ); + await expect( page.locator( 'button.revoke_access' ) ).toBeVisible(); + await expect( page.locator( 'a:has-text("Copy link")' ) ).toBeVisible(); + await expect( + page.locator( 'a:has-text("View report")' ) + ).toBeVisible(); + } ); - test( 'can add downloadable product permissions to order with product', async ( { - page, - } ) => { - // open the order that already has a product assigned - await page.goto( - `wp-admin/post.php?post=${ orderId }&action=edit` - ); + test( 'can add downloadable product permissions to order with product', async ( { + page, + } ) => { + // open the order that already has a product assigned + await page.goto( `wp-admin/post.php?post=${ orderId }&action=edit` ); - // add downloadable product permissions - await page.type( 'input.select2-search__field', product2Name ); - await page.click( - 'li.select2-results__option.select2-results__option--highlighted' - ); - await page.click( 'button.grant_access' ); + // add downloadable product permissions + await page.type( 'input.select2-search__field', product2Name ); + await page.click( + 'li.select2-results__option.select2-results__option--highlighted' + ); + await page.click( 'button.grant_access' ); - // verify new downloadable product permission details - await expect( page.locator( 'h3.fixed >> nth=1' ) ).toContainText( - product2Name - ); - await expect( - page.locator( 'input[name="downloads_remaining[2]"]' ) - ).toHaveAttribute( 'placeholder', 'Unlimited' ); - await expect( - page.locator( 'input[name="access_expires[2]"]' ) - ).toHaveAttribute( 'placeholder', 'Never' ); - } ); + // verify new downloadable product permission details + await expect( page.locator( 'h3.fixed >> nth=1' ) ).toContainText( + product2Name + ); + await expect( + page.locator( 'input[name="downloads_remaining[2]"]' ) + ).toHaveAttribute( 'placeholder', 'Unlimited' ); + await expect( + page.locator( 'input[name="access_expires[2]"]' ) + ).toHaveAttribute( 'placeholder', 'Never' ); + } ); - test( 'can edit downloadable product permissions', async ( { - page, - } ) => { - const expectedDownloadsRemaining = '10'; - const expectedDownloadsExpirationDate = '2050-01-01'; + test( 'can edit downloadable product permissions', async ( { page } ) => { + const expectedDownloadsRemaining = '10'; + const expectedDownloadsExpirationDate = '2050-01-01'; - // open the order that already has a product assigned - await page.goto( - `wp-admin/post.php?post=${ orderId }&action=edit` - ); + // open the order that already has a product assigned + await page.goto( `wp-admin/post.php?post=${ orderId }&action=edit` ); - // expand product download permissions - await page.click( 'h3.fixed' ); + // expand product download permissions + await page.click( 'h3.fixed' ); - // edit download permissions - await page.fill( - 'input[name="downloads_remaining[0]"]', - expectedDownloadsRemaining - ); - await page.fill( - 'input[name="access_expires[0]"]', - expectedDownloadsExpirationDate - ); - await page.click( 'button.save_order' ); + // edit download permissions + await page.fill( + 'input[name="downloads_remaining[0]"]', + expectedDownloadsRemaining + ); + await page.fill( + 'input[name="access_expires[0]"]', + expectedDownloadsExpirationDate + ); + await page.click( 'button.save_order' ); - // verify new downloadable product permissions - await page.click( 'h3.fixed' ); - await expect( - page.locator( 'input[name="downloads_remaining[0]"]' ) - ).toHaveValue( expectedDownloadsRemaining ); - await expect( - page.locator( 'input[name="access_expires[0]"]' ) - ).toHaveValue( expectedDownloadsExpirationDate ); - } ); + // verify new downloadable product permissions + await page.click( 'h3.fixed' ); + await expect( + page.locator( 'input[name="downloads_remaining[0]"]' ) + ).toHaveValue( expectedDownloadsRemaining ); + await expect( + page.locator( 'input[name="access_expires[0]"]' ) + ).toHaveValue( expectedDownloadsExpirationDate ); + } ); - test( 'can revoke downloadable product permissions', async ( { - page, - } ) => { - // open the order that already has a product assigned - await page.goto( - `wp-admin/post.php?post=${ orderId }&action=edit` - ); + test( 'can revoke downloadable product permissions', async ( { page } ) => { + // open the order that already has a product assigned + await page.goto( `wp-admin/post.php?post=${ orderId }&action=edit` ); - // expand product download permissions - await page.click( 'h3.fixed' ); + // expand product download permissions + await page.click( 'h3.fixed' ); - // verify prior state before revoking - await expect( page.locator( 'h3.fixed' ) ).toHaveCount( 1 ); + // verify prior state before revoking + await expect( page.locator( 'h3.fixed' ) ).toHaveCount( 1 ); - // click revoke access - page.on( 'dialog', ( dialog ) => dialog.accept() ); - await page.click( 'button.revoke_access' ); - await page.waitForLoadState( 'networkidle' ); + // click revoke access + page.on( 'dialog', ( dialog ) => dialog.accept() ); + await page.click( 'button.revoke_access' ); + await page.waitForLoadState( 'networkidle' ); - // verify permissions gone - await expect( page.locator( 'h3.fixed' ) ).toHaveCount( 0 ); - } ); + // verify permissions gone + await expect( page.locator( 'h3.fixed' ) ).toHaveCount( 0 ); + } ); - test( 'should not allow downloading a product if download attempts are exceeded', async ( { - page, - } ) => { - const expectedReason = - 'Sorry, you have reached your download limit for this file'; + test( 'should not allow downloading a product if download attempts are exceeded', async ( { + page, + } ) => { + const expectedReason = + 'Sorry, you have reached your download limit for this file'; - // open the order that already has a product assigned - await page.goto( - `wp-admin/post.php?post=${ orderId }&action=edit` - ); + // open the order that already has a product assigned + await page.goto( `wp-admin/post.php?post=${ orderId }&action=edit` ); - // set the download limit to 0 - // expand product download permissions - await page.click( 'h3.fixed' ); + // set the download limit to 0 + // expand product download permissions + await page.click( 'h3.fixed' ); - // edit download permissions - await page.fill( 'input[name="downloads_remaining[0]"]', '0' ); - await page.click( 'button.save_order' ); + // edit download permissions + await page.fill( 'input[name="downloads_remaining[0]"]', '0' ); + await page.click( 'button.save_order' ); - // get the download link - await page.click( 'h3.fixed' ); - const downloadPage = await page - .locator( 'a#copy-download-link' ) - .getAttribute( 'href' ); + // get the download link + await page.click( 'h3.fixed' ); + const downloadPage = await page + .locator( 'a#copy-download-link' ) + .getAttribute( 'href' ); - // open download page - await page.goto( downloadPage ); - await expect( page.locator( 'div.wp-die-message' ) ).toContainText( - expectedReason - ); - } ); + // open download page + await page.goto( downloadPage ); + await expect( page.locator( 'div.wp-die-message' ) ).toContainText( + expectedReason + ); + } ); - test( 'should not allow downloading a product if expiration date has passed', async ( { - page, - } ) => { - const expectedReason = 'Sorry, this download has expired'; + test( 'should not allow downloading a product if expiration date has passed', async ( { + page, + } ) => { + const expectedReason = 'Sorry, this download has expired'; - // open the order that already has a product assigned - await page.goto( - `wp-admin/post.php?post=${ orderId }&action=edit` - ); + // open the order that already has a product assigned + await page.goto( `wp-admin/post.php?post=${ orderId }&action=edit` ); - // set the download limit to 0 - // expand product download permissions - await page.click( 'h3.fixed' ); + // set the download limit to 0 + // expand product download permissions + await page.click( 'h3.fixed' ); - // edit download permissions - await page.fill( 'input[name="access_expires[0]"]', '2018-12-14' ); - await page.click( 'button.save_order' ); + // edit download permissions + await page.fill( 'input[name="access_expires[0]"]', '2018-12-14' ); + await page.click( 'button.save_order' ); - // get the download link - await page.click( 'h3.fixed' ); - const downloadPage = await page - .locator( 'a#copy-download-link' ) - .getAttribute( 'href' ); + // get the download link + await page.click( 'h3.fixed' ); + const downloadPage = await page + .locator( 'a#copy-download-link' ) + .getAttribute( 'href' ); - // open download page - await page.goto( downloadPage ); - await expect( page.locator( 'div.wp-die-message' ) ).toContainText( - expectedReason - ); - } ); - } -); + // open download page + await page.goto( downloadPage ); + await expect( page.locator( 'div.wp-die-message' ) ).toContainText( + expectedReason + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/merchant/order-emails.spec.js b/plugins/woocommerce/e2e/tests/merchant/order-emails.spec.js index 917f69dbfbb..eb4049a0a81 100644 --- a/plugins/woocommerce/e2e/tests/merchant/order-emails.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/order-emails.spec.js @@ -5,7 +5,7 @@ test.describe( 'Merchant > Order Action emails received', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); const customerBilling = { - email: 'john.doe@example.com', + email: 'john.doe.merchant.test@example.com', }; const adminEmail = 'admin@woocommercecoree2etestsuite.com'; const storeName = 'WooCommerce Core E2E Test Suite'; @@ -29,7 +29,11 @@ test.describe( 'Merchant > Order Action emails received', () => { } ); test.beforeEach( async ( { page } ) => { - await page.goto( 'wp-admin/tools.php?page=wpml_plugin_log' ); + await page.goto( + `wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerBilling.email + ) }` + ); // clear out the email logs before each test while ( ( await page.$( '#bulk-action-selector-top' ) ) !== null ) { await page.click( '#cb-select-all-1' ); @@ -46,6 +50,7 @@ test.describe( 'Merchant > Order Action emails received', () => { version: 'wc/v3', } ); await api.delete( `orders/${ orderId }`, { force: true } ); + await api.delete( `orders/${ newOrderId }`, { force: true } ); } ); test( 'can receive new order email', async ( { page, baseURL } ) => { @@ -65,16 +70,18 @@ test.describe( 'Merchant > Order Action emails received', () => { .then( ( response ) => { newOrderId = response.data.id; } ); - - await page.goto( 'wp-admin/tools.php?page=wpml_plugin_log' ); + // search to narrow it down to just the messages we want + await page.goto( + `wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerBilling.email + ) }` + ); await expect( page.locator( 'td.column-receiver >> nth=1' ) ).toContainText( adminEmail ); await expect( page.locator( 'td.column-subject >> nth=1' ) ).toContainText( `[${ storeName }]: New order #${ newOrderId }` ); - - await api.delete( `orders/${ newOrderId }`, { force: true } ); } ); test( 'can resend new order notification', async ( { page } ) => { @@ -87,8 +94,12 @@ test.describe( 'Merchant > Order Action emails received', () => { await page.click( 'button.wc-reload' ); await page.waitForLoadState( 'networkidle' ); - // confirm the message was delivered in the logs - await page.goto( 'wp-admin/tools.php?page=wpml_plugin_log' ); + // search to narrow it down to just the messages we want + await page.goto( + `wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerBilling.email + ) }` + ); await expect( page.locator( 'td.column-receiver' ) ).toContainText( adminEmail ); @@ -105,7 +116,11 @@ test.describe( 'Merchant > Order Action emails received', () => { await page.waitForLoadState( 'networkidle' ); // confirm the message was delivered in the logs - await page.goto( 'wp-admin/tools.php?page=wpml_plugin_log' ); + await page.goto( + `wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerBilling.email + ) }` + ); await expect( page.locator( 'td.column-receiver' ) ).toContainText( customerBilling.email ); diff --git a/plugins/woocommerce/e2e/tests/merchant/order-refund.spec.js b/plugins/woocommerce/e2e/tests/merchant/order-refund.spec.js index f3102073a2a..b36d8028c21 100644 --- a/plugins/woocommerce/e2e/tests/merchant/order-refund.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/order-refund.spec.js @@ -226,7 +226,7 @@ test.describe( 'WooCommerce Orders > Refund and restock an order item', () => { // Update the order await page.click( 'button.save_order' ); - await expect( page.locator( 'div.notice-success' ) ).toHaveText( + await expect( page.locator( 'div.notice-success' ) ).toContainText( 'Order updated.' ); diff --git a/plugins/woocommerce/e2e/tests/merchant/order-search.spec.js b/plugins/woocommerce/e2e/tests/merchant/order-search.spec.js index 7621d1baf7f..0f767e21383 100644 --- a/plugins/woocommerce/e2e/tests/merchant/order-search.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/order-search.spec.js @@ -1,11 +1,11 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; -const searchString = 'John Doe'; +const searchString = 'James Doe'; const itemName = 'Wanted Product'; const customerBilling = { - first_name: 'John', + first_name: 'James', last_name: 'Doe', company: 'Automattic', country: 'US', @@ -15,7 +15,7 @@ const customerBilling = { state: 'CA', postcode: '94107', phone: '123456789', - email: 'john.doe@example.com', + email: 'john.doe.ordersearch@example.com', }; const customerShipping = { first_name: 'Tim', @@ -28,7 +28,7 @@ const customerShipping = { state: 'NY', postcode: '14201', phone: '123456789', - email: 'john.doe@example.com', + email: 'john.doe.ordersearch@example.com', }; const queries = [ @@ -76,9 +76,9 @@ test.describe( 'WooCommerce Orders > Search orders', () => { // update customer info await api .post( 'customers', { - email: 'john.doe@example.com', - first_name: 'John', - last_name: 'Doe', + email: customerBilling.email, + first_name: customerBilling.first_name, + last_name: customerBilling.last_name, username: 'john.doe', billing: customerBilling, shipping: customerShipping, @@ -134,8 +134,9 @@ test.describe( 'WooCommerce Orders > Search orders', () => { await page.fill( '#post-search-input', queries[ i ][ 0 ] ); await page.click( '#search-submit' ); + // always check the last item, in case of multiples await expect( - page.locator( '.order_number > a.order-view' ) + page.locator( '.order_number > a.order-view >> nth=-1' ) ).toContainText( `#${ orderId } ${ searchString }` ); } ); } diff --git a/plugins/woocommerce/e2e/tests/merchant/order-status-filter.spec.js b/plugins/woocommerce/e2e/tests/merchant/order-status-filter.spec.js index 4a998e5fbe3..3a1169fc710 100644 --- a/plugins/woocommerce/e2e/tests/merchant/order-status-filter.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/order-status-filter.spec.js @@ -56,13 +56,12 @@ test.describe( 'WooCommerce Orders > Filter Order by Status', () => { await page.goto( 'wp-admin/edit.php?post_type=shop_order' ); await page.click( 'li.all > a' ); - await page.click( 'th#order_number > a' ); // ensure we're sorting in the right order - let i = 0; - for ( const [ statusText ] of orderStatus ) { - await expect( - page.locator( `${ statusColumnTextSelector } >> nth=${ i }` ) - ).toContainText( statusText ); - i++; + // because tests are running in parallel, we can't know how many orders there + // are beyond the ones we created here. + for ( let i = 0; i < orderStatus.length; i++ ) { + const statusTag = 'text=' + orderStatus[ i ][ 0 ]; + const countElements = await page.locator( statusTag ).count(); + await expect( countElements ).toBeGreaterThan( 0 ); } } ); @@ -73,9 +72,10 @@ test.describe( 'WooCommerce Orders > Filter Order by Status', () => { await page.goto( 'wp-admin/edit.php?post_type=shop_order' ); await page.click( `li.${ orderStatus[ i ][ 1 ] }` ); - await expect( - page.locator( statusColumnTextSelector ) - ).toContainText( orderStatus[ i ][ 0 ] ); + const countElements = await page + .locator( statusColumnTextSelector ) + .count(); + await expect( countElements ).toBeGreaterThan( 0 ); } ); } } ); diff --git a/plugins/woocommerce/e2e/tests/merchant/product-import-csv.spec.js b/plugins/woocommerce/e2e/tests/merchant/product-import-csv.spec.js index 9f2ace2653c..8dd49d50dcc 100644 --- a/plugins/woocommerce/e2e/tests/merchant/product-import-csv.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/product-import-csv.spec.js @@ -7,46 +7,47 @@ const filePathOverride = path.resolve( ); const productIds = []; +const categoryIds = []; const productNames = [ - 'V-Neck T-Shirt', - 'Hoodie', - 'Hoodie with Logo', - 'T-Shirt', - 'Beanie', - 'Belt', - 'Cap', - 'Sunglasses', - 'Hoodie with Pocket', - 'Hoodie with Zipper', - 'Long Sleeve Tee', - 'Polo', - 'Album', - 'Single', - 'T-Shirt with Logo', - 'Beanie with Logo', - 'Logo Collection', - 'WordPress Pennant', + 'Imported V-Neck T-Shirt', + 'Imported Hoodie', + 'Imported Hoodie with Logo', + 'Imported T-Shirt', + 'Imported Beanie', + 'Imported Belt', + 'Imported Cap', + 'Imported Sunglasses', + 'Imported Hoodie with Pocket', + 'Imported Hoodie with Zipper', + 'Imported Long Sleeve Tee', + 'Imported Polo', + 'Imported Album', + 'Imported Single', + 'Imported T-Shirt with Logo', + 'Imported Beanie with Logo', + 'Imported Logo Collection', + 'Imported WordPress Pennant', ]; const productNamesOverride = [ - 'V-Neck T-Shirt Override', - 'Hoodie Override', - 'Hoodie with Logo Override', - 'T-Shirt Override', - 'Beanie Override', - 'Belt Override', - 'Cap Override', - 'Sunglasses Override', - 'Hoodie with Pocket Override', - 'Hoodie with Zipper Override', - 'Long Sleeve Tee Override', - 'Polo Override', - 'Album Override', - 'Single Override', - 'T-Shirt with Logo Override', - 'Beanie with Logo Override', - 'Logo Collection Override', - 'WordPress Pennant Override', + 'Imported V-Neck T-Shirt Override', + 'Imported Hoodie Override', + 'Imported Hoodie with Logo Override', + 'Imported T-Shirt Override', + 'Imported Beanie Override', + 'Imported Belt Override', + 'Imported Cap Override', + 'Imported Sunglasses Override', + 'Imported Hoodie with Pocket Override', + 'Imported Hoodie with Zipper Override', + 'Imported Long Sleeve Tee Override', + 'Imported Polo Override', + 'Imported Album Override', + 'Imported Single Override', + 'Imported T-Shirt with Logo Override', + 'Imported Beanie with Logo Override', + 'Imported Logo Collection Override', + 'Imported WordPress Pennant Override', ]; const productPricesOverride = [ '$111.05', @@ -77,6 +78,15 @@ const productPricesOverride = [ '$115.00', '$120.00', ]; +const productCategories = [ + 'Clothing', + 'Hoodies', + 'Tshirts', + 'Accessories', + 'Music', + 'Decor', +]; + const errorMessage = 'Invalid file type. The importer supports CSV and TXT file formats.'; @@ -91,7 +101,7 @@ test.describe( 'Import Products from a CSV file', () => { version: 'wc/v3', } ); // get a list of all products - await api.get( 'products?per_page=20' ).then( ( response ) => { + await api.get( 'products?per_page=50' ).then( ( response ) => { for ( let i = 0; i < response.data.length; i++ ) { // if the product is one we imported, add it to the array for ( let j = 0; j < productNamesOverride.length; j++ ) { @@ -105,6 +115,21 @@ test.describe( 'Import Products from a CSV file', () => { } ); // batch delete all products in the array await api.post( 'products/batch', { delete: [ ...productIds ] } ); + // get a list of all product categories + await api.get( 'products/categories' ).then( ( response ) => { + for ( let i = 0; i < response.data.length; i++ ) { + // if the product category is one that was created, add it to the array + for ( let j = 0; j < productCategories.length; j++ ) { + if ( response.data[ i ].name === productCategories[ j ] ) { + categoryIds.push( response.data[ i ].id ); + } + } + } + } ); + // batch delete all categories in the array + await api.post( 'products/categories/batch', { + delete: [ ...categoryIds ], + } ); } ); test( 'should show error message if you go without providing CSV file', async ( { @@ -145,8 +170,15 @@ test.describe( 'Import Products from a CSV file', () => { // View the products await page.click( 'text=View products' ); + // Search for "import" to narrow the results to just the products we imported + await page.fill( '#post-search-input', 'Imported' ); + await page.click( '#search-submit' ); + // Compare imported products to what's expected - await page.waitForSelector( 'a.row-title' ); + await page.waitForSelector( 'a.row-title', { + state: 'visible', + timeout: 120000, // search can take a while + } ); const productTitles = await page.$$eval( 'a.row-title', ( elements ) => elements.map( ( item ) => item.innerHTML ) ); @@ -176,11 +208,15 @@ test.describe( 'Import Products from a CSV file', () => { // Confirm that the import is done await expect( page.locator( '.woocommerce-importer-done' ) - ).toContainText( 'Import complete!', { timeout: 120000 } ); + ).toContainText( 'Import complete!', { timeout: 120000 } ); // import can take a while // View the products await page.click( 'text=View products' ); + // Search for "import" to narrow the results to just the products we imported + await page.fill( '#post-search-input', 'Imported' ); + await page.click( '#search-submit' ); + // Compare imported products to what's expected await page.waitForSelector( 'a.row-title' ); const productTitles = await page.$$eval( 'a.row-title', ( elements ) => diff --git a/plugins/woocommerce/e2e/tests/merchant/product-search.spec.js b/plugins/woocommerce/e2e/tests/merchant/product-search.spec.js index bebabdc3e66..1a495b0e054 100644 --- a/plugins/woocommerce/e2e/tests/merchant/product-search.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/product-search.spec.js @@ -2,7 +2,7 @@ const { test, expect } = require( '@playwright/test' ); const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; let productId; -const productName = 'Simple product to search'; +const productName = 'Unique thing that we sell'; const productPrice = '9.99'; test.describe( 'Products > Search and View a product', () => { @@ -46,6 +46,7 @@ test.describe( 'Products > Search and View a product', () => { await page.fill( '#post-search-input', searchString ); await page.click( '#search-submit' ); + await page.waitForLoadState( 'networkidle' ); await expect( page.locator( '.row-title' ) ).toContainText( productName diff --git a/plugins/woocommerce/e2e/tests/merchant/product-settings.spec.js b/plugins/woocommerce/e2e/tests/merchant/product-settings.spec.js index 5d9623c3008..3ba8b53c4bc 100644 --- a/plugins/woocommerce/e2e/tests/merchant/product-settings.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/product-settings.spec.js @@ -28,7 +28,7 @@ test.describe( 'WooCommerce Products > Downloadable Product Settings', () => { await page.click( 'text=Save changes' ); // Verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( @@ -64,7 +64,7 @@ test.describe( 'WooCommerce Products > Downloadable Product Settings', () => { await page.click( 'text=Save changes' ); // Verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( @@ -89,7 +89,7 @@ test.describe( 'WooCommerce Products > Downloadable Product Settings', () => { await page.click( 'text=Save changes' ); // Verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( diff --git a/plugins/woocommerce/e2e/tests/merchant/settings-general.spec.js b/plugins/woocommerce/e2e/tests/merchant/settings-general.spec.js index 2d330b06a4d..46dd0d2a938 100644 --- a/plugins/woocommerce/e2e/tests/merchant/settings-general.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/settings-general.spec.js @@ -30,7 +30,7 @@ test.describe( 'WooCommerce General Settings', () => { await page.click( 'text=Save changes' ); // confirm setting saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( @@ -45,7 +45,7 @@ test.describe( 'WooCommerce General Settings', () => { await page.click( 'text=Save changes' ); // verify the settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( @@ -68,7 +68,7 @@ test.describe( 'WooCommerce General Settings', () => { await page.click( 'text=Save changes' ); // verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( diff --git a/plugins/woocommerce/e2e/tests/merchant/settings-tax.spec.js b/plugins/woocommerce/e2e/tests/merchant/settings-tax.spec.js index a4b929b3a12..c9311aea6a0 100644 --- a/plugins/woocommerce/e2e/tests/merchant/settings-tax.spec.js +++ b/plugins/woocommerce/e2e/tests/merchant/settings-tax.spec.js @@ -1,6 +1,7 @@ const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; -test.describe( 'WooCommerce Tax Settings', () => { +test.describe( 'WooCommerce Tax Settings > enable', () => { test.use( { storageState: 'e2e/storage/adminState.json' } ); test( 'can enable tax calculation', async ( { page } ) => { @@ -16,7 +17,7 @@ test.describe( 'WooCommerce Tax Settings', () => { await page.click( 'text=Save changes' ); // Verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( page.locator( '#woocommerce_calc_taxes' ) ).toBeChecked(); @@ -26,6 +27,33 @@ test.describe( 'WooCommerce Tax Settings', () => { page.locator( 'a.nav-tab:has-text("Tax")' ) ).toBeVisible(); } ); +} ); + +test.describe( 'WooCommerce Tax Settings', () => { + test.use( { storageState: 'e2e/storage/adminState.json' } ); + + test.beforeEach( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'yes', + } ); + } ); + test.afterEach( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( 'settings/general/woocommerce_calc_taxes', { + value: 'no', + } ); + } ); test( 'can set tax options', async ( { page } ) => { await page.goto( 'wp-admin/admin.php?page=wc-settings&tab=tax' ); @@ -53,7 +81,7 @@ test.describe( 'WooCommerce Tax Settings', () => { await page.click( 'text=Save changes' ); // Verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( @@ -88,7 +116,7 @@ test.describe( 'WooCommerce Tax Settings', () => { await page.click( 'text=Save changes' ); // Verify that the settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( page.locator( '#woocommerce_tax_classes' ) ).toHaveValue( @@ -100,7 +128,7 @@ test.describe( 'WooCommerce Tax Settings', () => { await page.click( 'text=Save changes' ); // Verify that the settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( @@ -178,7 +206,7 @@ test.describe( 'WooCommerce Tax Settings', () => { await page.click( 'text=Save changes' ); // Verify that settings have been saved - await expect( page.locator( 'div.inline' ) ).toContainText( + await expect( page.locator( 'div.updated.inline' ) ).toContainText( 'Your settings have been saved.' ); await expect( page.locator( '#woocommerce_tax_classes' ) ).toHaveValue( diff --git a/plugins/woocommerce/e2e/tests/shopper/calculate-shipping.spec.js b/plugins/woocommerce/e2e/tests/shopper/calculate-shipping.spec.js new file mode 100644 index 00000000000..dbbccb748b3 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/calculate-shipping.spec.js @@ -0,0 +1,220 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const firstProductName = 'First Product'; +const firstProductPrice = '9.99'; +const secondProductName = 'Second Product'; +const secondProductPrice = '4.99'; +const fourProductsTotal = +firstProductPrice * 4; +const twoProductsTotal = +firstProductPrice + +secondProductPrice; +const firstProductWithFlatRate = +firstProductPrice + 5; +const fourProductsWithFlatRate = +fourProductsTotal + 5; +const twoProductsWithFlatRate = +twoProductsTotal + 5; + +const shippingZoneNameDE = 'Germany Free Shipping'; +const shippingCountryDE = 'DE'; +const shippingZoneNameFR = 'France Flat Local'; +const shippingCountryFR = 'FR'; + +test.describe( 'Cart Calculate Shipping', () => { + let firstProductId, secondProductId, shippingZoneDEId, shippingZoneFRId; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add products + await api + .post( 'products', { + name: firstProductName, + type: 'simple', + regular_price: firstProductPrice, + } ) + .then( ( response ) => { + firstProductId = response.data.id; + } ); + await api + .post( 'products', { + name: secondProductName, + type: 'simple', + regular_price: secondProductPrice, + } ) + .then( ( response ) => { + secondProductId = response.data.id; + } ); + // create shipping zones + await api + .post( 'shipping/zones', { + name: shippingZoneNameDE, + } ) + .then( ( response ) => { + shippingZoneDEId = response.data.id; + } ); + await api + .post( 'shipping/zones', { + name: shippingZoneNameFR, + } ) + .then( ( response ) => { + shippingZoneFRId = response.data.id; + } ); + // set shipping zone locations + await api.put( `shipping/zones/${ shippingZoneDEId }/locations`, [ + { + code: shippingCountryDE, + }, + ] ); + await api.put( `shipping/zones/${ shippingZoneFRId }/locations`, [ + { + code: shippingCountryFR, + }, + ] ); + // set shipping zone methods + await api.post( `shipping/zones/${ shippingZoneDEId }/methods`, { + method_id: 'free_shipping', + } ); + await api.post( `shipping/zones/${ shippingZoneFRId }/methods`, { + method_id: 'flat_rate', + settings: { + cost: '5.00', + }, + } ); + await api.post( `shipping/zones/${ shippingZoneFRId }/methods`, { + method_id: 'local_pickup', + } ); + // confirm that we allow shipping to any country + api.put( 'settings/general/woocommerce_allowed_countries', { + value: 'all', + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ firstProductId }`, { + force: true, + } ); + await api.delete( `products/${ secondProductId }`, { + force: true, + } ); + await api.delete( `shipping/zones/${ shippingZoneDEId }`, { + force: true, + } ); + await api.delete( `shipping/zones/${ shippingZoneFRId }`, { + force: true, + } ); + } ); + + test( 'allows customer to calculate Free Shipping if in Germany', async ( { + page, + } ) => { + await page.goto( '/cart/' ); + // Set shipping country to Germany + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', shippingCountryDE ); + await page.click( 'button[name="calc_shipping"]' ); + + // Verify shipping costs + await expect( + page.locator( '.shipping ul#shipping_method > li' ) + ).toContainText( 'Free shipping' ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + firstProductPrice + ); + } ); + + test( 'allows customer to calculate Flat rate and Local pickup if in France', async ( { + page, + } ) => { + await page.goto( '/cart/' ); + // Set shipping country to France + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', shippingCountryFR ); + await page.click( 'button[name="calc_shipping"]' ); + + // Verify shipping costs + await expect( page.locator( '.shipping .amount' ) ).toContainText( + '$5.00' + ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ firstProductWithFlatRate }` + ); + + // Set shipping to local pickup instead of flat rate + await page.click( 'text=Local pickup' ); + + // Verify updated shipping costs + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ firstProductPrice }` + ); + } ); + + test( 'should show correct total cart price after updating quantity', async ( { + page, + } ) => { + await page.goto( '/cart/' ); + await page.fill( 'input.qty', '4' ); + await page.click( 'text=Update cart' ); + + // Set shipping country to France + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', shippingCountryFR ); + await page.click( 'button[name="calc_shipping"]' ); + + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ fourProductsWithFlatRate }` + ); + } ); + + test( 'should show correct total cart price with 2 products and flat rate', async ( { + page, + } ) => { + await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/cart/' ); + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', shippingCountryFR ); + await page.click( 'button[name="calc_shipping"]' ); + + await expect( page.locator( '.shipping .amount' ) ).toContainText( + '$5.00' + ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ twoProductsWithFlatRate }` + ); + } ); + + test( 'should show correct total cart price with 2 products without flat rate', async ( { + page, + } ) => { + await page.goto( `/shop/?add-to-cart=${ secondProductId }` ); + await page.waitForLoadState( 'networkidle' ); + + // Set shipping country to Spain + await page.goto( '/cart/' ); + await page.click( 'a.shipping-calculator-button' ); + await page.selectOption( '#calc_shipping_country', 'ES' ); + await page.click( 'button[name="calc_shipping"]' ); + + // Verify shipping costs + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ twoProductsTotal }` + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/cart-coupons.spec.js b/plugins/woocommerce/e2e/tests/shopper/cart-coupons.spec.js new file mode 100644 index 00000000000..e1863afcaa7 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/cart-coupons.spec.js @@ -0,0 +1,177 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const firstProductName = 'Coupon test product'; +const coupons = [ + { + code: 'fixed-cart-off', + discount_type: 'fixed_cart', + amount: '5.00', + }, + { + code: 'percent-off', + discount_type: 'percent', + amount: '50', + }, + { + code: 'fixed-product-off', + discount_type: 'fixed_product', + amount: '7.00', + }, +]; +const discounts = [ '$5.00', '$10.00', '$7.00' ]; +const totals = [ '$15.00', '$10.00', '$13.00' ]; + +test.describe( 'Cart applying coupons', () => { + let firstProductId; + const couponBatchId = new Array(); + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: firstProductName, + type: 'simple', + regular_price: '20.00', + } ) + .then( ( response ) => { + firstProductId = response.data.id; + } ); + // add coupons + await api + .post( 'coupons/batch', { + create: coupons, + } ) + .then( ( response ) => { + for ( let i = 0; i < response.data.create.length; i++ ) { + couponBatchId.push( response.data.create[ i ].id ); + } + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ firstProductId }`, { + force: true, + } ); + await api.post( 'coupons/batch', { delete: [ ...couponBatchId ] } ); + } ); + + for ( let i = 0; i < coupons.length; i++ ) { + test( `allows cart to apply coupon of type ${ coupons[ i ].discount_type }`, async ( { + page, + } ) => { + await page.goto( '/cart/' ); + await page.fill( '#coupon_code', coupons[ i ].code ); + await page.click( 'text=Apply coupon' ); + + await expect( + page.locator( '.woocommerce-message' ) + ).toContainText( 'Coupon code applied successfully.' ); + // Checks the coupon amount is credited properly + await expect( + page.locator( '.cart-discount .amount' ) + ).toContainText( discounts[ i ] ); + // Checks that the cart total is updated + await expect( + page.locator( '.order-total .amount' ) + ).toContainText( totals[ i ] ); + } ); + } + + test( 'prevents cart applying same coupon twice', async ( { page } ) => { + await page.goto( '/cart/' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + // successful first time + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'Coupon code applied successfully.' + ); + await page.waitForLoadState( 'networkidle' ); + // try to apply the same coupon + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + // error received + await expect( page.locator( '.woocommerce-error' ) ).toContainText( + 'Coupon code already applied!' + ); + // check cart total + await expect( page.locator( '.cart-discount .amount' ) ).toContainText( + discounts[ 0 ] + ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + totals[ 0 ] + ); + } ); + + test( 'allows cart to apply multiple coupons', async ( { page } ) => { + await page.goto( '/cart/' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + // successful + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'Coupon code applied successfully.' + ); + + await page.waitForLoadState( 'networkidle' ); + await page.fill( '#coupon_code', coupons[ 2 ].code ); + await page.click( 'text=Apply coupon' ); + // successful + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'Coupon code applied successfully.' + ); + // check cart total + await expect( + page.locator( '.cart-discount .amount >> nth=0' ) + ).toContainText( discounts[ 0 ] ); + await expect( + page.locator( '.cart-discount .amount >> nth=1' ) + ).toContainText( discounts[ 2 ] ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + '$8.00' + ); + } ); + + test( 'restores cart total when coupons are removed', async ( { + page, + } ) => { + await page.goto( '/cart/' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + + // confirm numbers + await expect( page.locator( '.cart-discount .amount' ) ).toContainText( + discounts[ 0 ] + ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + totals[ 0 ] + ); + + await page.click( 'a.woocommerce-remove-coupon' ); + await page.waitForLoadState( 'networkidle' ); + + await expect( page.locator( '.order-total .amount' ) ).toContainText( + '$20.00' + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/cart-redirection.spec.js b/plugins/woocommerce/e2e/tests/shopper/cart-redirection.spec.js new file mode 100644 index 00000000000..668a1864314 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/cart-redirection.spec.js @@ -0,0 +1,79 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +test.describe( 'Cart > Redirect to cart from shop', () => { + let productId; + const productName = 'A redirect product test'; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add products + await api + .post( 'products', { + name: productName, + type: 'simple', + regular_price: '17.99', + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.put( + 'settings/products/woocommerce_cart_redirect_after_add', + { + value: 'yes', + } + ); + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + await api.put( + 'settings/products/woocommerce_cart_redirect_after_add', + { + value: 'no', + } + ); + } ); + + test( 'can redirect user to cart from shop page', async ( { page } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + + await expect( page.url() ).toContain( '/cart/' ); + await expect( page.locator( 'td.product-name' ) ).toContainText( + productName + ); + } ); + + test( 'can redirect user to cart from detail page', async ( { page } ) => { + await page.goto( '/shop/' ); + await page.click( `text=${ productName }` ); + await page.waitForLoadState( 'networkidle' ); + + await page.click( 'text=Add to cart' ); + + await expect( page.url() ).toContain( '/cart/' ); + await expect( page.locator( 'td.product-name' ) ).toContainText( + productName + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/cart.spec.js b/plugins/woocommerce/e2e/tests/shopper/cart.spec.js new file mode 100644 index 00000000000..515b67bf5a0 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/cart.spec.js @@ -0,0 +1,152 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const productName = 'Cart product test'; +const productPrice = '13.99'; +const twoProductPrice = +productPrice * 2; + +test.describe( 'Cart page', () => { + let productId; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add products + await api + .post( 'products', { + name: productName, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + } ); + + test( 'should display no item in the cart', async ( { page } ) => { + await page.goto( '/cart/' ); + await expect( page.locator( '.cart-empty' ) ).toContainText( + 'Your cart is currently empty.' + ); + } ); + + test( 'should add the product to the cart from the shop page', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/cart/' ); + await expect( page.locator( 'td.product-name' ) ).toContainText( + productName + ); + } ); + + test( 'should increase item quantity when "Add to cart" of the same product is clicked', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + // Once the view cart link is visible, item has been added + await page.waitForLoadState( 'networkidle' ); + // Click add to cart a second time (load the shop in case redirection enabled) + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/cart/' ); + await expect( page.locator( 'input.qty' ) ).toHaveValue( '2' ); + } ); + + test( 'should update quantity when updated via quantity input', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/cart/' ); + await page.fill( 'input.qty', '2' ); + await page.click( 'text=Update cart' ); + + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ twoProductPrice }` + ); + } ); + + test( 'should remove the item from the cart when remove is clicked', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + await page.goto( '/cart/' ); + + // make sure that the product is in the cart + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ productPrice }` + ); + + await page.click( 'a.remove' ); + + await expect( page.locator( 'p.woocommerce-info' ) ).toContainText( + 'Your cart is currently empty.' + ); + } ); + + test( 'should update subtotal in cart totals when adding product to the cart', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/cart/' ); + await expect( page.locator( '.cart-subtotal .amount' ) ).toContainText( + `$${ productPrice }` + ); + + await page.fill( 'input.qty', '2' ); + await page.click( 'text=Update cart' ); + + await expect( page.locator( '.order-total .amount' ) ).toContainText( + `$${ twoProductPrice }` + ); + } ); + + test( 'should go to the checkout page when "Proceed to Checkout" is clicked', async ( { + page, + } ) => { + await page.goto( '/shop/' ); + await page.click( `a:below(:text("${ productName }"))` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/cart/' ); + + await page.click( '.checkout-button' ); + + await expect( page.locator( '#order_review' ) ).toBeVisible(); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/checkout-coupons.spec.js b/plugins/woocommerce/e2e/tests/shopper/checkout-coupons.spec.js new file mode 100644 index 00000000000..9455d6ee320 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/checkout-coupons.spec.js @@ -0,0 +1,179 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const firstProductName = 'Coupon checkout test product'; +const coupons = [ + { + code: 'fixed-cart-off-checkout', + discount_type: 'fixed_cart', + amount: '5.00', + }, + { + code: 'percent-off-checkout', + discount_type: 'percent', + amount: '50', + }, + { + code: 'fixed-product-off-checkout', + discount_type: 'fixed_product', + amount: '7.00', + }, +]; +const discounts = [ '$5.00', '$10.00', '$7.00' ]; +const totals = [ '$15.00', '$10.00', '$13.00' ]; + +test.describe( 'Checkout coupons', () => { + let firstProductId; + const couponBatchId = new Array(); + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: firstProductName, + type: 'simple', + regular_price: '20.00', + } ) + .then( ( response ) => { + firstProductId = response.data.id; + } ); + // add coupons + await api + .post( 'coupons/batch', { + create: coupons, + } ) + .then( ( response ) => { + for ( let i = 0; i < response.data.create.length; i++ ) { + couponBatchId.push( response.data.create[ i ].id ); + } + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ firstProductId }` ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ firstProductId }`, { + force: true, + } ); + await api.post( 'coupons/batch', { delete: [ ...couponBatchId ] } ); + } ); + + for ( let i = 0; i < coupons.length; i++ ) { + test( `allows checkout to apply coupon of type ${ coupons[ i ].discount_type }`, async ( { + page, + } ) => { + await page.goto( '/checkout/' ); + await page.click( 'text=Click here to enter your code' ); + await page.fill( '#coupon_code', coupons[ i ].code ); + await page.click( 'text=Apply coupon' ); + + await expect( + page.locator( '.woocommerce-message' ) + ).toContainText( 'Coupon code applied successfully.' ); + await expect( + page.locator( '.cart-discount .amount' ) + ).toContainText( discounts[ i ] ); + await expect( + page.locator( '.order-total .amount' ) + ).toContainText( totals[ i ] ); + } ); + } + + test( 'prevents checkout applying same coupon twice', async ( { + page, + } ) => { + await page.goto( '/checkout/' ); + await page.click( 'text=Click here to enter your code' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + // successful first time + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'Coupon code applied successfully.' + ); + // try to apply the same coupon + await page.click( 'text=Click here to enter your code' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + // error received + await expect( page.locator( '.woocommerce-error' ) ).toContainText( + 'Coupon code already applied!' + ); + // check cart total + await expect( page.locator( '.cart-discount .amount' ) ).toContainText( + discounts[ 0 ] + ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + totals[ 0 ] + ); + } ); + + test( 'allows checkout to apply multiple coupons', async ( { page } ) => { + await page.goto( '/checkout/' ); + await page.click( 'text=Click here to enter your code' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + // successful + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'Coupon code applied successfully.' + ); + await page.click( 'text=Click here to enter your code' ); + await page.fill( '#coupon_code', coupons[ 2 ].code ); + await page.click( 'text=Apply coupon' ); + // successful + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'Coupon code applied successfully.' + ); + // check cart total + await expect( + page.locator( '.cart-discount .amount >> nth=0' ) + ).toContainText( discounts[ 0 ] ); + await expect( + page.locator( '.cart-discount .amount >> nth=1' ) + ).toContainText( discounts[ 2 ] ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + '$8.00' + ); + } ); + + test( 'restores checkout total when coupons are removed', async ( { + page, + } ) => { + await page.goto( '/checkout/' ); + await page.click( 'text=Click here to enter your code' ); + await page.fill( '#coupon_code', coupons[ 0 ].code ); + await page.click( 'text=Apply coupon' ); + + // confirm numbers + await expect( page.locator( '.cart-discount .amount' ) ).toContainText( + discounts[ 0 ] + ); + await expect( page.locator( '.order-total .amount' ) ).toContainText( + totals[ 0 ] + ); + + await page.click( 'a.woocommerce-remove-coupon' ); + + await expect( page.locator( '.order-total .amount' ) ).toContainText( + '$20.00' + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/checkout-create-account.spec.js b/plugins/woocommerce/e2e/tests/shopper/checkout-create-account.spec.js new file mode 100644 index 00000000000..a82dac8d0c6 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/checkout-create-account.spec.js @@ -0,0 +1,146 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const billingEmail = 'marge-test-account@example.com'; + +test.describe( 'Shopper Checkout Create Account', () => { + let productId, orderId, shippingZoneId; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: 'Checkout Create Account', + type: 'simple', + regular_price: '19.99', + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.put( + 'settings/account/woocommerce_enable_signup_and_login_from_checkout', + { + value: 'yes', + } + ); + await api + .post( 'shipping/zones', { + name: 'Free Shipping CA', + } ) + .then( ( response ) => { + shippingZoneId = response.data.id; + } ); + await api.put( `shipping/zones/${ shippingZoneId }/locations`, [ + { + code: 'US:CA', + type: 'state', + }, + ] ); + await api.post( `shipping/zones/${ shippingZoneId }/methods`, { + method_id: 'free_shipping', + } ); + await api.put( 'payment_gateways/cod', { + enabled: true, + } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + if ( orderId ) { + await api.delete( `orders/${ orderId }`, { + force: true, + } ); + } + await api.put( + 'settings/account/woocommerce_enable_signup_and_login_from_checkout', + { + value: 'no', + } + ); + await api.delete( `shipping/zones/${ shippingZoneId }`, { + force: true, + } ); + await api.put( 'payment_gateways/cod', { + enabled: false, + } ); + // clear out the customer we create during the test + await api.get( 'customers' ).then( ( response ) => { + for ( let i = 0; i < response.data.length; i++ ) { + if ( response.data[ i ].billing.email === billingEmail ) { + api.delete( `customers/${ response.data[ i ].id }`, { + force: true, + } ); + } + } + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + + // all tests use the first product + await page.goto( `shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test( 'can create an account during checkout', async ( { page } ) => { + await page.goto( 'checkout/' ); + await page.fill( '#billing_first_name', 'Marge' ); + await page.fill( '#billing_last_name', 'Simpson' ); + await page.fill( '#billing_address_1', '742 Evergreen Terrace' ); + await page.fill( '#billing_address_2', 'c/o Maggie Simpson' ); + await page.fill( '#billing_city', 'Springfield' ); + await page.fill( '#billing_postcode', '97403' ); + await page.fill( '#billing_phone', '123456789' ); + await page.fill( '#billing_email', billingEmail ); + + await page.check( '#createaccount' ); + + await page.click( '#place_order' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'Order received' + ); + + // get order ID from the page + const orderReceivedHtmlElement = await page.$( + '.woocommerce-order-overview__order.order' + ); + const orderReceivedText = await page.evaluate( + ( element ) => element.textContent, + orderReceivedHtmlElement + ); + orderId = orderReceivedText.split( /(\s+)/ )[ 6 ].toString(); + + await page.goto( '/my-account/' ); + // confirms that an account was created + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'My account' + ); + await page.click( 'text=Logout' ); + // sign in as admin to confirm account creation + await page.fill( '#username', 'admin' ); + await page.fill( '#password', 'password' ); + await page.click( 'text=Log in' ); + + await page.goto( 'wp-admin/users.php' ); + await expect( page.locator( 'tbody#the-list' ) ).toContainText( + billingEmail + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/checkout-login.spec.js b/plugins/woocommerce/e2e/tests/shopper/checkout-login.spec.js new file mode 100644 index 00000000000..1bcadeedac9 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/checkout-login.spec.js @@ -0,0 +1,186 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const first_name = 'Jane'; +const last_name = 'Smith'; +const address_1 = '123 Anywhere St.'; +const address_2 = 'Apartment 42'; +const city = 'New York'; +const state = 'NY'; +const postcode = '10010'; +const country = 'US'; +const phone = '(555) 777-7777'; + +test.describe( 'Shopper Checkout Login Account', () => { + let productId, orderId, shippingZoneId; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: 'Checkout Login Account', + type: 'simple', + regular_price: '19.99', + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + await api.put( + 'settings/account/woocommerce_enable_checkout_login_reminder', + { + value: 'yes', + } + ); + // add a shipping zone and method + await api + .post( 'shipping/zones', { + name: 'Free Shipping New York', + } ) + .then( ( response ) => { + shippingZoneId = response.data.id; + } ); + await api.put( `shipping/zones/${ shippingZoneId }/locations`, [ + { + code: 'US:NY', + type: 'state', + }, + ] ); + await api.post( `shipping/zones/${ shippingZoneId }/methods`, { + method_id: 'free_shipping', + } ); + // update customer billing details. + await api.put( 'customers/2', { + billing: { + first_name, + last_name, + address_1, + address_2, + city, + state, + postcode, + country, + phone, + }, + } ); + // enable a payment method + await api.put( 'payment_gateways/cod', { + enabled: true, + } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + if ( orderId ) { + await api.delete( `orders/${ orderId }`, { force: true } ); + } + await api.put( + 'settings/account/woocommerce_enable_checkout_login_reminder', + { + value: 'no', + } + ); + // reset the customer account to how it was at the start + await api.put( 'customers/2', { + billing: { + address_1: '', + address_2: '', + city: '', + state: '', + postcode: '', + country: '', + phone: '', + }, + } ); + // disable payment method + await api.put( 'payment_gateways/cod', { + enabled: false, + } ); + // delete shipping + await api.delete( `shipping/zones/${ shippingZoneId }`, { + force: true, + } ); + } ); + + test.beforeEach( async ( { page, context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + + // all tests use the first product + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } ); + + test( 'can login to an existing account during checkout', async ( { + page, + } ) => { + await page.goto( '/checkout/' ); + await page.click( 'text=Click here to login' ); + + // fill in the customer account info + await page.fill( '#username', 'customer' ); + await page.fill( '#password', 'password' ); + await page.click( 'button[name="login"]' ); + + // billing form should pre-populate + await expect( page.locator( '#billing_first_name' ) ).toHaveValue( + first_name + ); + await expect( page.locator( '#billing_last_name' ) ).toHaveValue( + last_name + ); + await expect( page.locator( '#billing_address_1' ) ).toHaveValue( + address_1 + ); + await expect( page.locator( '#billing_address_2' ) ).toHaveValue( + address_2 + ); + await expect( page.locator( '#billing_city' ) ).toHaveValue( city ); + await expect( page.locator( '#billing_state' ) ).toHaveValue( state ); + await expect( page.locator( '#billing_postcode' ) ).toHaveValue( + postcode + ); + await expect( page.locator( '#billing_phone' ) ).toHaveValue( phone ); + + // place an order + await page.click( 'text=Place order' ); + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'Order received' + ); + + await page.waitForLoadState( 'networkidle' ); + // get order ID from the page + const orderReceivedHtmlElement = await page.$( + '.woocommerce-order-overview__order.order' + ); + const orderReceivedText = await page.evaluate( + ( element ) => element.textContent, + orderReceivedHtmlElement + ); + orderId = orderReceivedText.split( /(\s+)/ )[ 6 ].toString(); + + await expect( page.locator( 'ul > li.email' ) ).toContainText( + 'customer@woocommercecoree2etestsuite.com' + ); + + // check my account page + await page.goto( '/my-account/' ); + await expect( page.url() ).toContain( 'my-account/' ); + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'My account' + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/checkout.spec.js b/plugins/woocommerce/e2e/tests/shopper/checkout.spec.js new file mode 100644 index 00000000000..feb9a4bcf61 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/checkout.spec.js @@ -0,0 +1,332 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const guestEmail = 'checkout-guest@example.com'; +const customerEmail = 'checkout-customer@example.com'; + +test.describe( 'Checkout page', () => { + const singleProductPrice = '9.99'; + const simpleProductName = 'Checkout Page Product'; + const twoProductPrice = ( singleProductPrice * 2 ).toString(); + const threeProductPrice = ( singleProductPrice * 3 ).toString(); + + let guestOrderId, customerOrderId, productId, shippingZoneId; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: simpleProductName, + type: 'simple', + regular_price: singleProductPrice, + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + // add a shipping zone and method + await api + .post( 'shipping/zones', { + name: 'Free Shipping Oregon', + } ) + .then( ( response ) => { + shippingZoneId = response.data.id; + } ); + await api.put( `shipping/zones/${ shippingZoneId }/locations`, [ + { + code: 'US:OR', + type: 'state', + }, + ] ); + await api.post( `shipping/zones/${ shippingZoneId }/methods`, { + method_id: 'free_shipping', + } ); + // enable bank transfers and COD for payment + await api.put( 'payment_gateways/bacs', { + enabled: true, + } ); + await api.put( 'payment_gateways/cod', { + enabled: true, + } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + await api.delete( `shipping/zones/${ shippingZoneId }`, { + force: true, + } ); + await api.put( 'payment_gateways/bacs', { + enabled: false, + } ); + await api.put( 'payment_gateways/cod', { + enabled: false, + } ); + // delete the orders we created + if ( guestOrderId ) { + await api.delete( `orders/${ guestOrderId }`, { force: true } ); + } + if ( customerOrderId ) { + await api.delete( `orders/${ customerOrderId }`, { force: true } ); + } + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test( 'should display cart items in order review', async ( { page } ) => { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/checkout/' ); + + await expect( page.locator( 'td.product-name' ) ).toContainText( + simpleProductName + ); + await expect( page.locator( 'strong.product-quantity' ) ).toContainText( + '1' + ); + await expect( page.locator( 'td.product-total' ) ).toContainText( + singleProductPrice + ); + } ); + + test( 'allows customer to choose available payment methods', async ( { + page, + } ) => { + // this time we're going to add two products to the cart + for ( let i = 1; i < 3; i++ ) { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } + + await page.goto( '/checkout/' ); + await expect( page.locator( 'strong.product-quantity' ) ).toContainText( + '2' + ); + await expect( page.locator( 'td.product-total' ) ).toContainText( + twoProductPrice + ); + + // check the payment methods + await expect( page.locator( '#payment_method_bacs' ) ).toBeEnabled(); + await expect( page.locator( '#payment_method_cod' ) ).toBeEnabled(); + } ); + + test( 'allows customer to fill billing details', async ( { page } ) => { + // this time we're going to add three products to the cart + for ( let i = 1; i < 4; i++ ) { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } + + await page.goto( '/checkout/' ); + await expect( page.locator( 'strong.product-quantity' ) ).toContainText( + '3' + ); + await expect( page.locator( 'td.product-total' ) ).toContainText( + threeProductPrice + ); + + // asserting that you can fill in the billing details + await expect( page.locator( '#billing_first_name' ) ).toBeEditable(); + await expect( page.locator( '#billing_last_name' ) ).toBeEditable(); + await expect( page.locator( '#billing_company' ) ).toBeEditable(); + await expect( page.locator( '#billing_country' ) ).toBeEnabled(); + await expect( page.locator( '#billing_address_1' ) ).toBeEditable(); + await expect( page.locator( '#billing_address_2' ) ).toBeEditable(); + await expect( page.locator( '#billing_city' ) ).toBeEditable(); + await expect( page.locator( '#billing_state' ) ).toBeEnabled(); + await expect( page.locator( '#billing_postcode' ) ).toBeEditable(); + await expect( page.locator( '#billing_phone' ) ).toBeEditable(); + await expect( page.locator( '#billing_email' ) ).toBeEditable(); + } ); + + test( 'allows customer to fill shipping details', async ( { page } ) => { + for ( let i = 1; i < 3; i++ ) { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } + + await page.goto( '/checkout/' ); + await expect( page.locator( 'strong.product-quantity' ) ).toContainText( + '2' + ); + await expect( page.locator( 'td.product-total' ) ).toContainText( + twoProductPrice + ); + + await page.click( '#ship-to-different-address' ); + + // asserting that you can fill in the shipping details + await expect( page.locator( '#shipping_first_name' ) ).toBeEditable(); + await expect( page.locator( '#shipping_last_name' ) ).toBeEditable(); + await expect( page.locator( '#shipping_company' ) ).toBeEditable(); + await expect( page.locator( '#shipping_country' ) ).toBeEnabled(); + await expect( page.locator( '#shipping_address_1' ) ).toBeEditable(); + await expect( page.locator( '#shipping_address_2' ) ).toBeEditable(); + await expect( page.locator( '#shipping_city' ) ).toBeEditable(); + await expect( page.locator( '#shipping_state' ) ).toBeEnabled(); + await expect( page.locator( '#shipping_postcode' ) ).toBeEditable(); + } ); + + test( 'allows guest customer to place an order', async ( { page } ) => { + for ( let i = 1; i < 3; i++ ) { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } + + await page.goto( '/checkout/' ); + await expect( page.locator( 'strong.product-quantity' ) ).toContainText( + '2' + ); + await expect( page.locator( 'td.product-total' ) ).toContainText( + twoProductPrice + ); + + await page.fill( '#billing_first_name', 'Lisa' ); + await page.fill( '#billing_last_name', 'Simpson' ); + await page.fill( '#billing_address_1', '123 Evergreen Terrace' ); + await page.fill( '#billing_city', 'Springfield' ); + await page.selectOption( '#billing_state', 'OR' ); + await page.fill( '#billing_postcode', '97403' ); + await page.fill( '#billing_phone', '555 555-5555' ); + await page.fill( '#billing_email', guestEmail ); + + await page.click( 'text=Cash on delivery' ); + await expect( page.locator( 'div.payment_method_cod' ) ).toBeVisible(); + + await page.click( 'text=Place order' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'Order received' + ); + + // get order ID from the page + const orderReceivedHtmlElement = await page.$( + '.woocommerce-order-overview__order.order' + ); + const orderReceivedText = await page.evaluate( + ( element ) => element.textContent, + orderReceivedHtmlElement + ); + guestOrderId = await orderReceivedText.split( /(\s+)/ )[ 6 ].toString(); + + await page.goto( 'wp-login.php' ); + await page.fill( 'input[name="log"]', 'admin' ); + await page.fill( 'input[name="pwd"]', 'password' ); + await page.click( 'text=Log In' ); + + // load the order placed as a guest + await page.goto( + `wp-admin/post.php?post=${ guestOrderId }&action=edit` + ); + + await expect( + page.locator( 'h2.woocommerce-order-data__heading' ) + ).toContainText( `Order #${ guestOrderId } details` ); + await expect( page.locator( '.wc-order-item-name' ) ).toContainText( + simpleProductName + ); + await expect( page.locator( 'td.quantity >> nth=0' ) ).toContainText( + '2' + ); + await expect( page.locator( 'td.item_cost >> nth=0' ) ).toContainText( + singleProductPrice + ); + await expect( page.locator( 'td.line_cost >> nth=0' ) ).toContainText( + twoProductPrice + ); + } ); + + test( 'allows existing customer to place order', async ( { page } ) => { + await page.goto( 'wp-admin/' ); + await page.fill( 'input[name="log"]', 'customer' ); + await page.fill( 'input[name="pwd"]', 'password' ); + await page.click( 'text=Log In' ); + await page.waitForLoadState( 'networkidle' ); + for ( let i = 1; i < 3; i++ ) { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + } + + await page.goto( '/checkout/' ); + await expect( page.locator( 'strong.product-quantity' ) ).toContainText( + '2' + ); + await expect( page.locator( 'td.product-total' ) ).toContainText( + twoProductPrice + ); + + await page.fill( '#billing_first_name', 'Homer' ); + await page.fill( '#billing_last_name', 'Simpson' ); + await page.fill( '#billing_address_1', '123 Evergreen Terrace' ); + await page.fill( '#billing_city', 'Springfield' ); + await page.selectOption( '#billing_state', 'OR' ); + await page.fill( '#billing_postcode', '97403' ); + await page.fill( '#billing_phone', '555 555-5555' ); + await page.fill( '#billing_email', customerEmail ); + + await page.click( 'text=Cash on delivery' ); + await expect( page.locator( 'div.payment_method_cod' ) ).toBeVisible(); + + await page.click( 'text=Place order' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'Order received' + ); + + // get order ID from the page + const orderReceivedHtmlElement = await page.$( + '.woocommerce-order-overview__order.order' + ); + const orderReceivedText = await page.evaluate( + ( element ) => element.textContent, + orderReceivedHtmlElement + ); + customerOrderId = await orderReceivedText + .split( /(\s+)/ )[ 6 ] + .toString(); + + await page.goto( 'wp-login.php?loggedout=true' ); + await page.waitForLoadState( 'networkidle' ); + + await page.fill( 'input[name="log"]', 'admin' ); + await page.fill( 'input[name="pwd"]', 'password' ); + await page.click( 'text=Log In' ); + + // load the order placed as a customer + await page.goto( + `wp-admin/post.php?post=${ customerOrderId }&action=edit` + ); + await expect( + page.locator( 'h2.woocommerce-order-data__heading' ) + ).toContainText( `Order #${ customerOrderId } details` ); + await expect( page.locator( '.wc-order-item-name' ) ).toContainText( + simpleProductName + ); + await expect( page.locator( 'td.quantity >> nth=0' ) ).toContainText( + '2' + ); + await expect( page.locator( 'td.item_cost >> nth=0' ) ).toContainText( + singleProductPrice + ); + await expect( page.locator( 'td.line_cost >> nth=0' ) ).toContainText( + twoProductPrice + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/my-account-create-account.spec.js b/plugins/woocommerce/e2e/tests/shopper/my-account-create-account.spec.js new file mode 100644 index 00000000000..1e7309979b2 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/my-account-create-account.spec.js @@ -0,0 +1,67 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const customerEmailAddress = 'john.doe.test@example.com'; + +test.describe( 'Shopper My Account Create Account', () => { + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.put( + 'settings/account/woocommerce_enable_myaccount_registration', + { + value: 'yes', + } + ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // get a list of all customers + await api.get( 'customers' ).then( ( response ) => { + for ( let i = 0; i < response.data.length; i++ ) { + if ( response.data[ i ].email === customerEmailAddress ) { + api.delete( `customers/${ response.data[ i ].id }`, { + force: true, + } ); + } + } + } ); + await api.put( + 'settings/account/woocommerce_enable_myaccount_registration', + { + value: 'no', + } + ); + } ); + + test( 'can create a new account via my account', async ( { page } ) => { + await page.goto( 'my-account/' ); + + await expect( + page.locator( '.woocommerce-form-register' ) + ).toBeVisible(); + + await page.fill( 'input#reg_email', customerEmailAddress ); + await page.click( 'button[name="register"]' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'My account' + ); + await expect( page.locator( 'text=Logout' ) ).toBeVisible(); + + await page.goto( 'my-account/edit-account/' ); + await expect( page.locator( '#account_email' ) ).toHaveValue( + customerEmailAddress + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/my-account-pay-order.spec.js b/plugins/woocommerce/e2e/tests/shopper/my-account-pay-order.spec.js new file mode 100644 index 00000000000..84a11eea464 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/my-account-pay-order.spec.js @@ -0,0 +1,89 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +test.describe( 'Customer can pay for their order through My Account', () => { + let productId, orderId; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: 'Pay Order My Account', + type: 'simple', + regular_price: '15.77', + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + // create an order + await api + .post( 'orders', { + set_paid: false, + billing: { + first_name: 'Jane', + last_name: 'Smith', + email: 'customer@woocommercecoree2etestsuite.com', + }, + line_items: [ + { + product_id: productId, + quantity: 1, + }, + ], + } ) + .then( ( response ) => { + orderId = response.data.id; + } ); + // once the order is created, assign it to our existing customer user + await api.put( `orders/${ orderId }`, { + customer_id: 2, // should be safe to use this ID. Saves an API call to retrieve. + } ); + // enable COD payment + await api.put( 'payment_gateways/cod', { + enabled: true, + } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + await api.delete( `orders/${ orderId }`, { force: true } ); + await api.put( 'payment_gateways/cod', { + enabled: false, + } ); + } ); + + test( 'allows customer to pay for their order in My Account', async ( { + page, + } ) => { + await page.goto( 'my-account/orders/' ); + // sign in as the "customer" user + await page.fill( '#username', 'customer' ); + await page.fill( '#password', 'password' ); + await page.click( 'text=Log in' ); + + await page.click( 'a.pay' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'Pay for order' + ); + await page.click( '#place_order' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'Order received' + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/my-account.spec.js b/plugins/woocommerce/e2e/tests/shopper/my-account.spec.js new file mode 100644 index 00000000000..b04b0fb617c --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/my-account.spec.js @@ -0,0 +1,54 @@ +const { test, expect } = require( '@playwright/test' ); + +const pages = [ + [ 'Orders', 'my-account/orders' ], + [ 'Downloads', 'my-account/downloads' ], + [ 'Addresses', 'my-account/edit-address' ], + [ 'Account details', 'my-account/edit-account' ], +]; + +test.describe( 'My account page', () => { + test.use( { storageState: 'e2e/storage/customerState.json' } ); + + test( 'allows customer to login', async ( { page } ) => { + await page.goto( 'my-account/' ); + + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + 'My account' + ); + await expect( + page.locator( 'div.woocommerce-MyAccount-content > p >> nth=0' ) + ).toContainText( 'Jane Smith' ); + + // assert that navigation is visible + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link >> nth=0' ) + ).toContainText( 'Dashboard' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link >> nth=1' ) + ).toContainText( 'Orders' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link >> nth=2' ) + ).toContainText( 'Downloads' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link >> nth=3' ) + ).toContainText( 'Addresses' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link >> nth=4' ) + ).toContainText( 'Account details' ); + await expect( + page.locator( '.woocommerce-MyAccount-navigation-link >> nth=5' ) + ).toContainText( 'Logout' ); + } ); + + for ( let i = 0; i < pages.length; i++ ) { + test( `allows customer to see ${ pages[ i ][ 0 ] } page`, async ( { + page, + } ) => { + await page.goto( pages[ i ][ 1 ] ); + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + pages[ i ][ 0 ] + ); + } ); + } +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/order-email-receiving.spec.js b/plugins/woocommerce/e2e/tests/shopper/order-email-receiving.spec.js new file mode 100644 index 00000000000..0739eb72676 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/order-email-receiving.spec.js @@ -0,0 +1,109 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +let productId, orderId; +const productName = 'Order email product'; +const customerEmail = 'order-email-test@example.com'; +const storeName = 'WooCommerce Core E2E Test Suite'; + +test.describe( 'Shopper Order Email Receiving', () => { + test.use( { storageState: 'e2e/storage/adminState.json' } ); + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: productName, + type: 'simple', + regular_price: '42.77', + } ) + .then( ( response ) => { + productId = response.data.id; + } ); + // enable COD payment + await api.put( 'payment_gateways/cod', { + enabled: true, + } ); + } ); + + test.beforeEach( async ( { page } ) => { + await page.goto( + `wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerEmail + ) }` + ); + // clear out the email logs before each test + while ( ( await page.$( '#bulk-action-selector-top' ) ) !== null ) { + await page.click( '#cb-select-all-1' ); + await page.selectOption( '#bulk-action-selector-top', 'delete' ); + await page.click( '#doaction' ); + } + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ productId }`, { + force: true, + } ); + if ( orderId ) { + await api.delete( `orders/${ orderId }`, { + force: true, + } ); + } + await api.put( 'payment_gateways/cod', { + enabled: false, + } ); + } ); + + test( 'should receive order email after purchasing an item', async ( { + page, + } ) => { + await page.goto( `/shop/?add-to-cart=${ productId }` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( '/checkout/' ); + + await page.fill( '#billing_first_name', 'Maggie' ); + await page.fill( '#billing_last_name', 'Simpson' ); + await page.fill( '#billing_address_1', '123 Evergreen Terrace' ); + await page.fill( '#billing_city', 'Springfield' ); + await page.selectOption( '#billing_state', 'OR' ); + await page.fill( '#billing_postcode', '97403' ); + await page.fill( '#billing_phone', '555 555-5555' ); + await page.fill( '#billing_email', customerEmail ); + + await page.click( 'text=Place order' ); + + await page.waitForSelector( + 'li.woocommerce-order-overview__order > strong' + ); + orderId = await page.textContent( + 'li.woocommerce-order-overview__order > strong' + ); + + // search to narrow it down to just the messages we want + await page.goto( + `wp-admin/tools.php?page=wpml_plugin_log&s=${ encodeURIComponent( + customerEmail + ) }` + ); + await page.waitForLoadState( 'networkidle' ); + await expect( + page.locator( 'td.column-receiver >> nth=0' ) + ).toContainText( customerEmail ); + await expect( + page.locator( 'td.column-subject >> nth=1' ) + ).toContainText( `[${ storeName }]: New order #${ orderId }` ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/product-browse-search-sort.spec.js b/plugins/woocommerce/e2e/tests/shopper/product-browse-search-sort.spec.js new file mode 100644 index 00000000000..715f631e6a6 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/product-browse-search-sort.spec.js @@ -0,0 +1,161 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const singleProductPrice1 = '979.99'; +const singleProductPrice2 = '989.99'; +const singleProductPrice3 = '999.99'; + +const simpleProductName = 'AAA Search and Browse Product'; + +const categoryA = 'Dogs'; +const categoryB = 'Cats'; +const categoryC = 'Fish'; + +let categoryAId, categoryBId, categoryCId, product1Id, product2Id, product3Id; + +test.describe( + 'Search, browse by categories and sort items in the shop', + () => { + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product categories + await api + .post( 'products/categories', { + name: categoryA, + } ) + .then( ( response ) => { + categoryAId = response.data.id; + } ); + await api + .post( 'products/categories', { + name: categoryB, + } ) + .then( ( response ) => { + categoryBId = response.data.id; + } ); + await api + .post( 'products/categories', { + name: categoryC, + } ) + .then( ( response ) => { + categoryCId = response.data.id; + } ); + + // add products + await api + .post( 'products', { + name: simpleProductName + ' 1', + type: 'simple', + regular_price: singleProductPrice1, + categories: [ { id: categoryAId } ], + } ) + .then( ( response ) => { + product1Id = response.data.id; + } ); + await api + .post( 'products', { + name: simpleProductName + ' 2', + type: 'simple', + regular_price: singleProductPrice2, + categories: [ { id: categoryBId } ], + } ) + .then( ( response ) => { + product2Id = response.data.id; + } ); + await api + .post( 'products', { + name: simpleProductName + ' 3', + type: 'simple', + regular_price: singleProductPrice3, + categories: [ { id: categoryCId } ], + } ) + .then( ( response ) => { + product3Id = response.data.id; + } ); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.post( 'products/batch', { + delete: [ product1Id, product2Id, product3Id ], + } ); + await api.post( 'products/categories/batch', { + delete: [ categoryAId, categoryBId, categoryCId ], + } ); + } ); + + test( 'should let user search the store', async ( { page } ) => { + await page.goto( 'shop/' ); + + await page.fill( + '#wp-block-search__input-1', + simpleProductName + ' 1' + ); + await page.click( 'button.wp-block-search__button' ); + + await expect( page.locator( 'h1.page-title' ) ).toContainText( + `${ simpleProductName } 1` + ); + await expect( page.locator( 'h2.entry-title' ) ).toContainText( + simpleProductName + ' 1' + ); + } ); + + test( 'should let user browse products by categories', async ( { + page, + } ) => { + // browse the Audio category + await page.goto( 'shop/' ); + await page.click( `text=${ simpleProductName } 2` ); + await page.click( 'span.posted_in > a', { hasText: categoryB } ); + + // verify the Audio category page + await expect( page.locator( 'h1.page-title' ) ).toContainText( + categoryB + ); + await expect( + page.locator( 'h2.woocommerce-loop-product__title' ) + ).toContainText( simpleProductName + ' 2' ); + await page.click( `text=${ simpleProductName } 2` ); + await expect( page.locator( 'h1.entry-title' ) ).toContainText( + simpleProductName + ' 2' + ); + } ); + + test( 'should let user sort the products in the shop', async ( { + page, + } ) => { + await page.goto( 'shop/' ); + + // sort by price high to low + await page.selectOption( '.orderby', 'price-desc' ); + // last product is most expensive + await expect( + page.locator( 'ul.products > li:nth-child(1)' ) + ).toContainText( `${ simpleProductName } 3` ); + await expect( + page.locator( 'ul.products > li:nth-child(3)' ) + ).toContainText( `${ simpleProductName } 1` ); + + // sort by price low to high + await page.selectOption( '.orderby', 'price' ); + // last product is most expensive + await expect( + page.locator( 'ul.products > li:nth-last-child(3)' ) + ).toContainText( `${ simpleProductName } 1` ); + await expect( + page.locator( 'ul.products > li:nth-last-child(1)' ) + ).toContainText( `${ simpleProductName } 3` ); + } ); + } +); diff --git a/plugins/woocommerce/e2e/tests/shopper/single-product.spec.js b/plugins/woocommerce/e2e/tests/shopper/single-product.spec.js new file mode 100644 index 00000000000..0d3182333e7 --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/single-product.spec.js @@ -0,0 +1,324 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const productPrice = '18.16'; +const simpleProductName = 'Simple single product'; +const variableProductName = 'Variable single product'; +const variations = [ + { + regular_price: productPrice, + attributes: [ + { + name: 'Size', + option: 'Small', + }, + ], + }, + { + regular_price: ( +productPrice * 2 ).toString(), + attributes: [ + { + name: 'Size', + option: 'Medium', + }, + ], + }, + { + regular_price: ( +productPrice * 3 ).toString(), + attributes: [ + { + name: 'Size', + option: 'Large', + }, + ], + }, + { + regular_price: ( +productPrice * 4 ).toString(), + attributes: [ + { + name: 'Size', + option: 'XLarge', + }, + ], + }, +]; +const groupedProductName = 'Grouped single product'; + +let simpleProductId, simpleProduct2Id, variableProductId, groupedProductId; + +test.describe( 'Single Product Page', () => { + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: simpleProductName, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + simpleProductId = response.data.id; + } ); + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ simpleProductId }`, { + force: true, + } ); + } ); + + test( 'should be able to add simple products to the cart', async ( { + page, + } ) => { + const slug = simpleProductName.replace( / /gi, '-' ).toLowerCase(); + await page.goto( `product/${ slug }` ); + + await page.fill( 'input.qty', '5' ); + await page.click( 'text=Add to cart' ); + + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + 'have been added to your cart.' + ); + + await page.goto( 'cart/' ); + await expect( page.locator( 'td.product-name' ) ).toContainText( + simpleProductName + ); + await expect( page.locator( 'input.qty' ) ).toHaveValue( '5' ); + await expect( page.locator( 'td.product-subtotal' ) ).toContainText( + ( 5 * +productPrice ).toString() + ); + } ); + + test( 'should be able to remove simple products from the cart', async ( { + page, + } ) => { + await page.goto( `/shop/?add-to-cart=${ simpleProductId }` ); + await page.waitForLoadState( 'networkidle' ); + + await page.goto( 'cart/' ); + await page.click( 'a.remove' ); + + await expect( page.locator( 'p.cart-empty' ) ).toContainText( + 'Your cart is currently empty.' + ); + } ); +} ); + +test.describe( 'Variable Product Page', () => { + const slug = variableProductName.replace( / /gi, '-' ).toLowerCase(); + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: variableProductName, + type: 'variable', + attributes: [ + { + name: 'Size', + options: [ 'Small', 'Medium', 'Large', 'XLarge' ], + visible: true, + variation: true, + }, + ], + } ) + .then( ( response ) => { + variableProductId = response.data.id; + for ( const key in variations ) { + api.post( + `products/${ variableProductId }/variations`, + variations[ key ] + ); + } + } ); + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ variableProductId }`, { + force: true, + } ); + } ); + + test( 'should be able to add variation products to the cart', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + + for ( const attr of variations ) { + await page.selectOption( '#size', attr.attributes[ 0 ].option ); + await page.click( 'text=Add to cart' ); + await expect( + page.locator( '.woocommerce-message' ) + ).toContainText( 'has been added to your cart.' ); + } + + await page.goto( 'cart/' ); + await expect( + page.locator( 'td.product-name >> nth=0' ) + ).toContainText( variableProductName ); + await expect( page.locator( 'tr.order-total > td' ) ).toContainText( + ( +productPrice * 10 ).toString() + ); + } ); + + test( 'should be able to remove variation products from the cart', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + await page.selectOption( '#size', 'Large' ); + await page.click( 'text=Add to cart' ); + + await page.goto( 'cart/' ); + await page.click( 'a.remove' ); + + await expect( page.locator( 'p.cart-empty' ) ).toContainText( + 'Your cart is currently empty.' + ); + } ); +} ); + +test.describe( 'Grouped Product Page', () => { + const slug = groupedProductName.replace( / /gi, '-' ).toLowerCase(); + const simpleProduct1 = simpleProductName + ' 1'; + const simpleProduct2 = simpleProductName + ' 2'; + + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add products + await api + .post( 'products', { + name: simpleProduct1, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + simpleProductId = response.data.id; + } ); + await api + .post( 'products', { + name: simpleProduct2, + type: 'simple', + regular_price: productPrice, + } ) + .then( ( response ) => { + simpleProduct2Id = response.data.id; + } ); + await api + .post( 'products', { + name: groupedProductName, + type: 'grouped', + grouped_products: [ simpleProductId, simpleProduct2Id ], + } ) + .then( ( response ) => { + groupedProductId = response.data.id; + } ); + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ simpleProductId }`, { + force: true, + } ); + await api.delete( `products/${ simpleProduct2Id }`, { + force: true, + } ); + await api.delete( `products/${ groupedProductId }`, { + force: true, + } ); + } ); + + test( 'should be able to add grouped products to the cart', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + + await page.click( 'text=Add to cart' ); + await expect( page.locator( '.woocommerce-error' ) ).toContainText( + 'Please choose the quantity of items you wish to add to your cart…' + ); + + await page.fill( 'div.quantity input.qty >> nth=0', '5' ); + await page.fill( 'div.quantity input.qty >> nth=1', '5' ); + await page.click( 'text=Add to cart' ); + await expect( page.locator( '.woocommerce-message' ) ).toContainText( + `“${ simpleProduct1 }” and “${ simpleProduct2 }” have been added to your cart.` + ); + + await page.goto( 'cart/' ); + await expect( + page.locator( 'td.product-name >> nth=0' ) + ).toContainText( simpleProduct1 ); + await expect( + page.locator( 'td.product-name >> nth=1' ) + ).toContainText( simpleProduct2 ); + await expect( page.locator( 'tr.order-total > td' ) ).toContainText( + ( +productPrice * 10 ).toString() + ); + } ); + + test( 'should be able to remove grouped products from the cart', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + await page.fill( 'div.quantity input.qty >> nth=0', '1' ); + await page.fill( 'div.quantity input.qty >> nth=1', '1' ); + await page.click( 'text=Add to cart' ); + + await page.goto( 'cart/' ); + await page.click( 'a.remove >> nth=1' ); + await page.click( 'a.remove >> nth=0' ); + + await expect( page.locator( 'p.cart-empty' ) ).toContainText( + 'Your cart is currently empty.' + ); + } ); +} ); diff --git a/plugins/woocommerce/e2e/tests/shopper/variable-product-updates.spec.js b/plugins/woocommerce/e2e/tests/shopper/variable-product-updates.spec.js new file mode 100644 index 00000000000..9f31e198b8b --- /dev/null +++ b/plugins/woocommerce/e2e/tests/shopper/variable-product-updates.spec.js @@ -0,0 +1,254 @@ +const { test, expect } = require( '@playwright/test' ); +const wcApi = require( '@woocommerce/woocommerce-rest-api' ).default; + +const variableProductName = 'Variable Product Updates'; +const productPrice = '11.16'; +const variations = [ + { + attributes: [ + { + name: 'Colour', + option: 'Red', + }, + ], + }, + { + attributes: [ + { + name: 'Colour', + option: 'Blue', + }, + ], + }, + { + attributes: [ + { + name: 'Colour', + option: 'Green', + }, + ], + }, + { + regular_price: productPrice, + weight: '100', + dimensions: { + length: '5', + width: '10', + height: '10', + }, + attributes: [ + { + name: 'Size', + option: 'Small', + }, + ], + }, + { + regular_price: productPrice, + weight: '100', + dimensions: { + length: '5', + width: '10', + height: '10', + }, + attributes: [ + { + name: 'Size', + option: 'Medium', + }, + ], + }, + { + regular_price: ( +productPrice * 2 ).toString(), + weight: '200', + dimensions: { + length: '10', + width: '20', + height: '15', + }, + attributes: [ + { + name: 'Size', + option: 'Large', + }, + ], + }, + { + regular_price: ( +productPrice * 2 ).toString(), + weight: '400', + dimensions: { + length: '20', + width: '40', + height: '30', + }, + attributes: [ + { + name: 'Size', + option: 'XLarge', + }, + ], + }, +]; + +const cartDialogMessage = + 'Please select some product options before adding this product to your cart.'; + +test.describe( 'Shopper > Update variable product', () => { + let variableProductId; + const slug = variableProductName.replace( / /gi, '-' ).toLowerCase(); + test.beforeAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + // add product + await api + .post( 'products', { + name: variableProductName, + type: 'variable', + attributes: [ + { + name: 'Size', + options: [ 'Small', 'Medium', 'Large', 'XLarge' ], + visible: true, + variation: true, + }, + { + name: 'Colour', + options: [ 'Red', 'Green', 'Blue' ], + visible: true, + variation: true, + }, + ], + } ) + .then( ( response ) => { + variableProductId = response.data.id; + for ( const key in variations ) { + api.post( + `products/${ variableProductId }/variations`, + variations[ key ] + ); + } + } ); + } ); + + test.beforeEach( async ( { context } ) => { + // Shopping cart is very sensitive to cookies, so be explicit + context.clearCookies(); + } ); + + test.afterAll( async ( { baseURL } ) => { + const api = new wcApi( { + url: baseURL, + consumerKey: process.env.CONSUMER_KEY, + consumerSecret: process.env.CONSUMER_SECRET, + version: 'wc/v3', + } ); + await api.delete( `products/${ variableProductId }`, { + force: true, + } ); + } ); + + test( 'Shopper can change variable attributes to the same value', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + + await page.selectOption( '#size', 'Small' ); + + await page.selectOption( '#colour', 'Red' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + + await page.selectOption( '#colour', 'Green' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + + await page.selectOption( '#colour', 'Blue' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + } ); + + test( 'Shopper can change attributes to combination with dimentions and weight', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + + await page.selectOption( '#colour', 'Red' ); + + await page.selectOption( '#size', 'Small' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + await expect( + page.locator( '.woocommerce-product-attributes-item--weight' ) + ).toContainText( '100 kg' ); + await expect( + page.locator( '.woocommerce-product-attributes-item--dimensions' ) + ).toContainText( '5 × 10 × 10 cm' ); + + await page.selectOption( '#size', 'XLarge' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( ( +productPrice * 2 ).toString() ); + await expect( + page.locator( '.woocommerce-product-attributes-item--weight' ) + ).toContainText( '400 kg' ); + await expect( + page.locator( '.woocommerce-product-attributes-item--dimensions' ) + ).toContainText( '20 × 40 × 30 cm' ); + } ); + + test( 'Shopper can change variable product attributes to variation with a different price', async ( { + page, + } ) => { + await page.goto( `product/${ slug }` ); + + await page.selectOption( '#colour', 'Red' ); + + await page.selectOption( '#size', 'Small' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + + await page.selectOption( '#size', 'Medium' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + + await page.selectOption( '#size', 'Large' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( ( +productPrice * 2 ).toString() ); + + await page.selectOption( '#size', 'XLarge' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( ( +productPrice * 2 ).toString() ); + } ); + + test( 'Shopper can reset variations', async ( { page } ) => { + await page.goto( `product/${ slug }` ); + + await page.selectOption( '#colour', 'Red' ); + + await page.selectOption( '#size', 'Small' ); + await expect( + page.locator( '.woocommerce-variation-price' ) + ).toContainText( productPrice ); + + await page.click( 'a.reset_variations' ); + + // Verify the reset by attempting to add the product to the cart + page.on( 'dialog', async ( dialog ) => { + expect( dialog.message() ).toContain( cartDialogMessage ); + await dialog.dismiss(); + } ); + await page.click( '.single_add_to_cart_button' ); + } ); +} ); diff --git a/plugins/woocommerce/package.json b/plugins/woocommerce/package.json index 468a688a908..b77d6042ab8 100644 --- a/plugins/woocommerce/package.json +++ b/plugins/woocommerce/package.json @@ -78,6 +78,7 @@ "require-turbo": "workspace:*", "stylelint": "^13.8.0", "typescript": "3.9.7", + "uuid": "^8.3.2", "webpack": "5.70.0", "webpack-cli": "3.3.12", "wp-textdomain": "1.0.1" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0b8e67cbef8..9161e93ae96 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1270,6 +1270,7 @@ importers: require-turbo: workspace:* stylelint: ^13.8.0 typescript: 3.9.7 + uuid: ^8.3.2 webpack: 5.70.0 webpack-cli: 3.3.12 wp-textdomain: 1.0.1 @@ -1313,6 +1314,7 @@ importers: require-turbo: link:../../tools/require-turbo stylelint: 13.13.1 typescript: 3.9.7 + uuid: 8.3.2 webpack: 5.70.0_webpack-cli@3.3.12 webpack-cli: 3.3.12_webpack@5.70.0 wp-textdomain: 1.0.1 @@ -17027,7 +17029,7 @@ packages: loader-utils: 1.4.0 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0 dev: true /babel-loader/8.2.3_d3f6fe5812216e437b67a6bf164a056c: @@ -17057,7 +17059,7 @@ packages: loader-utils: 1.4.0 make-dir: 3.1.0 schema-utils: 2.7.1 - webpack: 5.70.0_webpack-cli@4.9.2 + webpack: 5.70.0 dev: true /babel-messages/6.23.0: @@ -19517,7 +19519,7 @@ packages: postcss-value-parser: 4.2.0 schema-utils: 2.7.1 semver: 6.3.0 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0 dev: true /css-loader/3.6.0_webpack@5.70.0: @@ -36552,7 +36554,7 @@ packages: serialize-javascript: 4.0.0 source-map: 0.6.1 terser: 4.8.0 - webpack: 4.46.0_webpack-cli@3.3.12 + webpack: 4.46.0 webpack-sources: 1.4.3 worker-farm: 1.7.0 dev: true