diff --git a/tests/e2e/core-tests/specs/shopper/front-end-single-product.test.js b/tests/e2e/core-tests/specs/shopper/front-end-single-product.test.js index 8d6fc05e9bd..d1473eb75a1 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-single-product.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-single-product.test.js @@ -4,18 +4,37 @@ */ const { shopper, - merchant, createSimpleProduct, createVariableProduct, createGroupedProduct, uiUnblocked } = require( '@woocommerce/e2e-utils' ); -let simplePostIdValue; -let variablePostIdValue; -let groupedPostIdValue; const config = require( 'config' ); + +// Variables for simple product const simpleProductName = config.get( 'products.simple.name' ); +let simplePostIdValue; + +// Variables for variable product +const defaultVariableProduct = config.get( 'products.variable' ); +let variableProductId; + +// Variables for grouped product +const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99'; +const simple1 = { + name: simpleProductName + ' 1', + regularPrice: simpleProductPrice +}; +const simple2 = { + name: simpleProductName + ' 2', + regularPrice: simpleProductPrice +}; +const groupedProduct = { + name: 'Grouped Product', + groupedProducts: [simple1, simple2] +}; +let groupedPostIdValue; const runSingleProductPageTest = () => { describe('Single Product Page', () => { @@ -43,30 +62,34 @@ const runSingleProductPageTest = () => { }); }); - describe.skip('Variable Product Page', () => { + describe('Variable Product Page', () => { beforeAll(async () => { - await merchant.login(); - variablePostIdValue = await createVariableProduct(); - await merchant.logout(); + variableProductId = await createVariableProduct(); }); it('should be able to add variation products to the cart', async () => { // Add a product with one set of variations to cart - await shopper.goToProduct(variablePostIdValue); - await expect(page).toSelect('#attr-1', 'val1'); - await expect(page).toSelect('#attr-2', 'val1'); - await expect(page).toSelect('#attr-3', 'val1'); + await shopper.goToProduct(variableProductId); + + for (const attr of defaultVariableProduct.attributes) { + const { name, options } = attr; + const selectElem = `#${name.toLowerCase()}`; + const value = options[0]; + + await expect(page).toSelect(selectElem, value); + } + await shopper.addToCart(); await expect(page).toMatchElement('.woocommerce-message', {text: 'has been added to your cart.'}); // Verify cart contents await shopper.goToCart(); - await shopper.productIsInCart('Variable Product with Three Variations'); + await shopper.productIsInCart(defaultVariableProduct.name); }); it('should be able to remove variation products from the cart', async () => { // Remove items from cart - await shopper.removeFromCart('Variable Product with Three Variations'); + await shopper.removeFromCart(defaultVariableProduct.name); await uiUnblocked(); await expect(page).toMatchElement('.cart-empty', {text: 'Your cart is currently empty.'}); }); @@ -74,9 +97,7 @@ const runSingleProductPageTest = () => { describe('Grouped Product Page', () => { beforeAll(async () => { - await merchant.login(); - groupedPostIdValue = await createGroupedProduct(); - await merchant.logout(); + groupedPostIdValue = await createGroupedProduct(groupedProduct); }); it('should be able to add grouped products to the cart', async () => { @@ -93,7 +114,7 @@ const runSingleProductPageTest = () => { await quantityFields[1].type('5'); await shopper.addToCart(); await expect(page).toMatchElement('.woocommerce-message', - {text: '“'+simpleProductName+' 1” and “'+simpleProductName+' 2” have been added to your cart.'}); + {text: '“'+simpleProductName+' 1” and “'+simpleProductName+' 2” have been added to your cart.'}); // Verify cart contents await shopper.goToCart(); diff --git a/tests/e2e/core-tests/specs/shopper/front-end-variable-product-updates.test.js b/tests/e2e/core-tests/specs/shopper/front-end-variable-product-updates.test.js index 2cce3fd22b1..8c0ac5a9c5f 100644 --- a/tests/e2e/core-tests/specs/shopper/front-end-variable-product-updates.test.js +++ b/tests/e2e/core-tests/specs/shopper/front-end-variable-product-updates.test.js @@ -4,73 +4,103 @@ */ const { shopper, - merchant, createVariableProduct, } = require( '@woocommerce/e2e-utils' ); +const config = require('config'); let variablePostIdValue; const cartDialogMessage = 'Please select some product options before adding this product to your cart.'; +const attributes = config.get( 'products.variable.attributes' ) const runVariableProductUpdateTest = () => { describe('Shopper > Update variable product',() => { beforeAll(async () => { - await merchant.login(); variablePostIdValue = await createVariableProduct(); - await merchant.logout(); }); it('shopper can change variable attributes to the same value', async () => { await shopper.goToProduct(variablePostIdValue); - await expect(page).toSelect('#attr-1', 'val1'); - await expect(page).toSelect('#attr-2', 'val1'); - await expect(page).toSelect('#attr-3', 'val1'); - await expect(page).toMatchElement('.woocommerce-variation-price', { text: '9.99' }); + for (const a of attributes) { + const { name, options } = a; + const attrHTMLId = `#${name.toLowerCase()}`; + + await expect(page).toSelect(attrHTMLId, options[0]); + } + + await expect(page).toMatchElement('.woocommerce-variation-price', { + text: '9.99' + }); }); it('shopper can change attributes to combination with dimensions and weight', async () => { await shopper.goToProduct(variablePostIdValue); - await expect(page).toSelect('#attr-1', 'val1'); - await expect(page).toSelect('#attr-2', 'val2'); - await expect(page).toSelect('#attr-3', 'val1'); + await expect(page).toSelect( + `#${attributes[0].name.toLowerCase()}`, + attributes[0].options[0] + ); + await expect(page).toSelect( + `#${attributes[1].name.toLowerCase()}`, + attributes[1].options[1] + ); + await expect(page).toSelect( + `#${attributes[2].name.toLowerCase()}`, + attributes[2].options[0] + ); await expect(page).toMatchElement('.woocommerce-variation-price', { text: '20.00' }); await expect(page).toMatchElement('.woocommerce-variation-availability', { text: 'Out of stock' }); await expect(page).toMatchElement('.woocommerce-product-attributes-item--weight', { text: '200 kg' }); await expect(page).toMatchElement('.woocommerce-product-attributes-item--dimensions', { text: '10 × 20 × 15 cm' }); + }); it('shopper can change variable product attributes to variation with a different price', async () => { await shopper.goToProduct(variablePostIdValue); - await expect(page).toSelect('#attr-1', 'val1'); - await expect(page).toSelect('#attr-2', 'val1'); - await expect(page).toSelect('#attr-3', 'val2'); + await expect(page).toSelect( + `#${attributes[0].name.toLowerCase()}`, + attributes[0].options[0] + ); + await expect(page).toSelect( + `#${attributes[1].name.toLowerCase()}`, + attributes[1].options[0] + ); + await expect(page).toSelect( + `#${attributes[2].name.toLowerCase()}`, + attributes[2].options[1] + ); await expect(page).toMatchElement('.woocommerce-variation-price', { text: '11.99' }); }); it('shopper can reset variations', async () => { await shopper.goToProduct(variablePostIdValue); - await expect(page).toSelect('#attr-1', 'val1'); - await expect(page).toSelect('#attr-2', 'val2'); - await expect(page).toSelect('#attr-3', 'val1'); + await expect(page).toSelect( + `#${attributes[0].name.toLowerCase()}`, + attributes[0].options[0] + ); + await expect(page).toSelect( + `#${attributes[1].name.toLowerCase()}`, + attributes[1].options[1] + ); + await expect(page).toSelect( + `#${attributes[2].name.toLowerCase()}`, + attributes[2].options[0] + ); await expect(page).toClick('.reset_variations'); // Verify the reset by attempting to add the product to the cart const couponDialog = await expect(page).toDisplayDialog(async () => { - await expect(page).toClick('.single_add_to_cart_button'); - }); + await expect(page).toClick('.single_add_to_cart_button'); + }); expect(couponDialog.message()).toMatch(cartDialogMessage); - - // Accept the dialog - await couponDialog.accept(); }); }); - + }; module.exports = runVariableProductUpdateTest; diff --git a/tests/e2e/utils/CHANGELOG.md b/tests/e2e/utils/CHANGELOG.md index 332eee4c055..01e698ec22e 100644 --- a/tests/e2e/utils/CHANGELOG.md +++ b/tests/e2e/utils/CHANGELOG.md @@ -1,5 +1,9 @@ # Unreleased +## Added + +- Factories for variable product, variation, and grouped product + # 0.1.5 ## Added diff --git a/tests/e2e/utils/src/components.js b/tests/e2e/utils/src/components.js index 47992f0bd37..d77351d66c7 100644 --- a/tests/e2e/utils/src/components.js +++ b/tests/e2e/utils/src/components.js @@ -7,10 +7,8 @@ */ import { merchant, IS_RETEST_MODE } from './flows'; import { - clickTab, uiUnblocked, verifyCheckboxIsUnset, - selectOptionInSelect2, setCheckbox, unsetCheckbox, evalAndClick, @@ -24,6 +22,8 @@ const client = factories.api.withDefaultPermalinks; const config = require( 'config' ); const simpleProductName = config.get( 'products.simple.name' ); const simpleProductPrice = config.has('products.simple.price') ? config.get('products.simple.price') : '9.99'; +const defaultVariableProduct = config.get('products.variable'); +const defaultGroupedProduct = config.get('products.grouped'); /** * Verify and publish @@ -223,172 +223,104 @@ const createSimpleProductWithCategory = async ( productName, productPrice, categ /** * Create variable product. + * Also, create variations for all attributes. + * + * @param varProduct Defaults to the variable product object in `default.json` + * @returns the ID of the created variable product */ -const createVariableProduct = async () => { +const createVariableProduct = async (varProduct = defaultVariableProduct) => { + const { attributes } = varProduct; + const { id } = await factories.products.variable.create(varProduct); // create the variable product + const variations = []; + const buffer = []; // accumulated attributes while looping + const aIdx = 0; // attributes[] index - // We need to remove any listeners on the `dialog` event otherwise we can't catch the dialogs below - page.removeAllListeners('dialog'); + // Create variation for all attributes + const createVariation = (aIdx) => { + const { name, options } = attributes[aIdx]; + const isLastAttribute = aIdx === attributes.length - 1; - // Go to "add product" page - await merchant.openNewProduct(); + // Add each attribute value to the buffer. + options.forEach((opt) => { + buffer.push({ + name: name, + option: opt + }); - // Make sure we're on the add product page - await expect( page.title() ).resolves.toMatch( 'Add new product' ); + if (isLastAttribute) { + // If this is the last attribute, it means the variation is now complete. + // Save whatever's been accumulated in the buffer to the `variations[]` array. + variations.push({ + attributes: [...buffer] + }); + } else { + // Otherwise, move to the next attribute first + // before proceeding to the next value in this attribute. + createVariation(aIdx + 1); + } - // Set product data - await expect( page ).toFill( '#title', 'Variable Product with Three Variations' ); - await expect( page ).toSelect( '#product-type', 'Variable product' ); + buffer.pop(); + }); + }; + createVariation(aIdx); - // Create attributes for variations - await clickTab( 'Attributes' ); - await expect( page ).toSelect( 'select[name="attribute_taxonomy"]', 'Custom product attribute' ); + // Set some properties of 1st variation + variations[0].regularPrice = '9.99'; + variations[0].virtual = true; - for ( let i = 0; i < 3; i++ ) { - await expect( page ).toClick( 'button.add_attribute', { text: 'Add' } ); - // Wait for attribute form to load - await uiUnblocked(); + // Set some properties of 2nd variation + variations[1].regularPrice = '11.99'; + variations[1].virtual = true; - await page.focus( `input[name="attribute_names[${ i }]"]` ); - await expect( page ).toFill( `input[name="attribute_names[${ i }]"]`, 'attr #' + ( i + 1 ) ); - await expect( page ).toFill( `textarea[name="attribute_values[${ i }]"]`, 'val1 | val2' ); - await expect( page ).toClick( `input[name="attribute_variation[${ i }]"]` ); + // Set some properties of 3rd variation + variations[2].regularPrice = '20'; + variations[2].weight = '200'; + variations[2].dimensions = { + length: '10', + width: '20', + height: '15' + }; + variations[2].manage_stock = true; + + // Use API to create each variation + for (const v of variations) { + await factories.products.variation.create({ + productId: id, + variation: v + }); } - await expect( page ).toClick( 'button', { text: 'Save attributes' } ); - - // Wait for attribute form to save (triggers 2 UI blocks) - await uiUnblocked(); - await page.waitFor( 1000 ); - await uiUnblocked(); - - // Create variations from attributes - await clickTab( 'Variations' ); - await page.waitForSelector( 'select.variation_actions:not([disabled])' ); - await page.focus( 'select.variation_actions' ); - await expect( page ).toSelect( 'select.variation_actions', 'Create variations from all attributes' ); - - const firstDialog = await expect( page ).toDisplayDialog( async () => { - // Using this technique since toClick() isn't working. - // See: https://github.com/GoogleChrome/puppeteer/issues/1805#issuecomment-464802876 - page.$eval( 'a.do_variation_action', elem => elem.click() ); - - } ); - - expect( firstDialog.message() ).toMatch( 'Are you sure you want to link all variations?' ); - - const secondDialog = await expect( page ).toDisplayDialog( async () => { - await firstDialog.accept(); - } ); - - expect( secondDialog.message() ).toMatch( '8 variations added' ); - await secondDialog.dismiss(); - - // Set some variation data - await uiUnblocked(); - await uiUnblocked(); - - await page.waitForSelector( '.woocommerce_variation .handlediv' ); - - // Verify that variations were created - await Promise.all( [ - expect( page ).toMatchElement( 'select[name="attribute_attr-1[0]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[0]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[0]"]', { text: 'val1' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[1]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[1]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[1]"]', { text: 'val2' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[2]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[2]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[2]"]', { text: 'val1' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[3]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[3]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[3]"]', { text: 'val2' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[4]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[4]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[4]"]', { text: 'val1' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[5]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[5]"]', { text: 'val1' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[5]"]', { text: 'val2' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[6]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[6]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[6]"]', { text: 'val1' } ), - - expect( page ).toMatchElement( 'select[name="attribute_attr-1[7]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-2[7]"]', { text: 'val2' } ), - expect( page ).toMatchElement( 'select[name="attribute_attr-3[7]"]', { text: 'val2' } ), - ] ); - - await expect( page ).toClick( '.woocommerce_variation:nth-of-type(2) .handlediv' ); - await page.waitFor( 2000 ); - await page.focus( 'input[name="variable_is_virtual[0]"]' ); - await expect( page ).toClick( 'input[name="variable_is_virtual[0]"]' ); - await expect( page ).toFill( 'input[name="variable_regular_price[0]"]', '9.99' ); - - await expect( page ).toClick( '.woocommerce_variation:nth-of-type(3) .handlediv' ); - await page.waitFor( 2000 ); - await page.focus( 'input[name="variable_is_virtual[1]"]' ); - await expect( page ).toClick( 'input[name="variable_is_virtual[1]"]' ); - await expect( page ).toFill( 'input[name="variable_regular_price[1]"]', '11.99' ); - - await expect( page ).toClick( '.woocommerce_variation:nth-of-type(4) .handlediv' ); - await page.waitFor( 2000 ); - await page.focus( 'input[name="variable_manage_stock[2]"]' ); - await expect( page ).toClick( 'input[name="variable_manage_stock[2]"]' ); - await expect( page ).toFill( 'input[name="variable_regular_price[2]"]', '20' ); - await expect( page ).toFill( 'input[name="variable_weight[2]"]', '200' ); - await expect( page ).toFill( 'input[name="variable_length[2]"]', '10' ); - await expect( page ).toFill( 'input[name="variable_width[2]"]', '20' ); - await expect( page ).toFill( 'input[name="variable_height[2]"]', '15' ); - - await page.focus( 'button.save-variation-changes' ); - await expect( page ).toClick( 'button.save-variation-changes', { text: 'Save changes' } ); - - await verifyAndPublish( 'Product published.' ); - - const variablePostId = await page.$( '#post_ID' ); - let variablePostIdValue = ( await ( await variablePostId.getProperty( 'value' ) ).jsonValue() ); - return variablePostIdValue; + return id; }; /** * Create grouped product. + * + * @param groupedProduct Defaults to the grouped product object in `default.json` + * @returns ID of the grouped product */ -const createGroupedProduct = async () => { - // Create two products to be linked in a grouped product after - await factories.products.simple.create( { - name: simpleProductName + ' 1', - regularPrice: simpleProductPrice - } ); - await factories.products.simple.create( { - name: simpleProductName + ' 2', - regularPrice: simpleProductPrice - } ); +const createGroupedProduct = async (groupedProduct = defaultGroupedProduct) => { + const { name, groupedProducts } = groupedProduct; + const simpleProductIds = []; + let groupedProductRequest; - // Go to "add product" page - await merchant.openNewProduct(); + // Using the api, create simple products to be grouped + for (const simpleProduct of groupedProducts) { + const { id } = await factories.products.simple.create(simpleProduct); + simpleProductIds.push(id); + } - // Make sure we're on the add product page - await expect( page.title() ).resolves.toMatch( 'Add new product' ); + // Using the api, create the grouped product + groupedProductRequest = { + name: name, + groupedProducts: simpleProductIds + }; + const { id } = await factories.products.grouped.create( + groupedProductRequest + ); - // Set product data and save the product - await expect( page ).toFill( '#title', 'Grouped Product' ); - await expect( page ).toSelect( '#product-type', 'Grouped product' ); - await clickTab( 'Linked Products' ); - await selectOptionInSelect2( simpleProductName + ' 1' ); - await selectOptionInSelect2( simpleProductName + ' 2' ); - await verifyAndPublish(); - - // Get product ID - const groupedPostId = await page.$( '#post_ID' ); - let groupedPostIdValue = ( await ( await groupedPostId.getProperty( 'value' ) ).jsonValue() ); - return groupedPostIdValue; -} + return id; +}; /** * Create a basic order with the provided order status. diff --git a/tests/e2e/utils/src/factories.js b/tests/e2e/utils/src/factories.js index 46f3b91f948..4ebc5e38041 100644 --- a/tests/e2e/utils/src/factories.js +++ b/tests/e2e/utils/src/factories.js @@ -1,6 +1,9 @@ import { HTTPClientFactory } from '@woocommerce/api'; const config = require( 'config' ); import { simpleProductFactory } from './factories/simple-product'; +import { variableProductFactory } from './factories/variable-product'; +import { variationFactory } from './factories/variation'; +import { groupedProductFactory } from './factories/grouped-product'; const apiUrl = config.get( 'url' ); const adminUsername = config.get( 'users.admin.username' ); @@ -20,6 +23,9 @@ const factories = { }, products: { simple: simpleProductFactory( withDefaultPermalinks ), + variable: variableProductFactory( withDefaultPermalinks ), + variation: variationFactory( withDefaultPermalinks ), + grouped: groupedProductFactory( withDefaultPermalinks ) }, }; diff --git a/tests/e2e/utils/src/factories/grouped-product.js b/tests/e2e/utils/src/factories/grouped-product.js new file mode 100644 index 00000000000..cb2315f586c --- /dev/null +++ b/tests/e2e/utils/src/factories/grouped-product.js @@ -0,0 +1,26 @@ +import { GroupedProduct } from '@woocommerce/api'; +import { Factory } from 'fishery'; + +/** + * Creates a new factory for creating variable products. + * This does not include creating product variations. + * Instead, use `variationFactory()` for that. + * + * @param {HTTPClient} httpClient The HTTP client we will give the repository. + * @return {AsyncFactory} The factory for creating models. + */ +export function groupedProductFactory(httpClient) { + const repository = GroupedProduct.restRepository(httpClient); + + return Factory.define(({ params, onCreate }) => { + onCreate((model) => { + return repository.create(model); + }); + + return { + name: params.name, + type: 'grouped', + groupedProducts: params.groupedProducts + }; + }); +} diff --git a/tests/e2e/utils/src/factories/variable-product.js b/tests/e2e/utils/src/factories/variable-product.js new file mode 100644 index 00000000000..5080dbc0422 --- /dev/null +++ b/tests/e2e/utils/src/factories/variable-product.js @@ -0,0 +1,27 @@ +import { VariableProduct } from '@woocommerce/api'; +import { Factory } from 'fishery'; + +/** + * Creates a new factory for creating variable products. + * This does not include creating product variations. + * Instead, use `variationFactory()` for that. + * + * @param {HTTPClient} httpClient The HTTP client we will give the repository. + * @return {AsyncFactory} The factory for creating models. + */ +export function variableProductFactory(httpClient) { + const repository = VariableProduct.restRepository(httpClient); + + return Factory.define(({ params, onCreate }) => { + onCreate((model) => { + return repository.create(model); + }); + + return { + name: params.name, + type: 'variable', + defaultAttributes: params.defaultAttributes, + attributes: params.attributes + }; + }); +} diff --git a/tests/e2e/utils/src/factories/variation.js b/tests/e2e/utils/src/factories/variation.js new file mode 100644 index 00000000000..4aed65ad353 --- /dev/null +++ b/tests/e2e/utils/src/factories/variation.js @@ -0,0 +1,22 @@ +import { ProductVariation } from '@woocommerce/api'; +import { Factory } from 'fishery'; + +/** + * Creates a new factory for creating a product variation. + * + * @param {HTTPClient} httpClient The HTTP client we will give the repository. + * @return {AsyncFactory} The factory for creating models. + */ +export function variationFactory(httpClient) { + const repository = ProductVariation.restRepository(httpClient); + + return Factory.define(({ params, onCreate }) => { + const { productId, variation } = params; + + onCreate((model) => { + return repository.create(productId, model); + }); + + return variation; + }); +}