/** * @format */ /** * Internal dependencies */ import { merchant, IS_RETEST_MODE } from './flows'; import { clickTab, uiUnblocked, verifyCheckboxIsUnset, selectOptionInSelect2, setCheckbox, unsetCheckbox, evalAndClick, backboneUnblocked, waitForSelectorWithoutThrow, } from './page-utils'; import factories from './factories'; import { Coupon, VariableProduct, ProductVariation } from '@woocommerce/api'; 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'; /** * Verify and publish * * @param noticeText The text that appears in the notice after publishing. */ const verifyAndPublish = async ( noticeText ) => { // Wait for auto save await page.waitFor( 2000 ); // Publish product await expect( page ).toClick( '#publish' ); await page.waitForSelector( '.updated.notice' ); // Verify await expect( page ).toMatchElement( '.updated.notice', { text: noticeText } ); }; /** * Wait for primary button to be enabled and click. * * @param waitForNetworkIdle - Wait for network idle after click * @returns {Promise} */ const waitAndClickPrimary = async ( waitForNetworkIdle = true ) => { // Wait for "Continue" button to become active await page.waitForSelector( 'button.is-primary:not(:disabled)' ); await page.click( 'button.is-primary' ); if ( waitForNetworkIdle ) { await page.waitForNavigation( { waitUntil: 'networkidle0' } ); } }; /** * Complete onboarding wizard. */ const completeOnboardingWizard = async () => { // Store Details section await merchant.runSetupWizard(); // Fill store's address - first line await expect( page ).toFill( '#inspector-text-control-0', config.get( 'addresses.admin.store.addressfirstline' ) ); // Fill store's address - second line await expect( page ).toFill( '#inspector-text-control-1', config.get( 'addresses.admin.store.addresssecondline' ) ); // Fill country and state where the store is located await expect( page ).toFill( '.woocommerce-select-control__control-input', config.get( 'addresses.admin.store.countryandstate' ) ); // Fill the city where the store is located await expect( page ).toFill( '#inspector-text-control-2', config.get( 'addresses.admin.store.city' ) ); // Fill postcode of the store await expect( page ).toFill( '#inspector-text-control-3', config.get( 'addresses.admin.store.postcode' ) ); // Verify that checkbox next to "I'm setting up a store for a client" is not selected await verifyCheckboxIsUnset( '.components-checkbox-control__input' ); // Wait for "Continue" button to become active await page.waitForSelector( 'button.is-primary:not(:disabled)' ); // Click on "Continue" button to move to the next step await page.click( 'button.is-primary', { text: 'Continue' } ); // Wait for usage tracking pop-up window to appear on a new site const usageTrackingHeader = await page.$('.components-modal__header-heading'); if ( usageTrackingHeader ) { await expect(page).toMatchElement( '.components-modal__header-heading', {text: 'Build a better WooCommerce'} ); // Query for "Continue" buttons const continueButtons = await page.$$( 'button.is-primary' ); expect( continueButtons ).toHaveLength( 2 ); await continueButtons[1].click(); } await page.waitForNavigation( { waitUntil: 'networkidle0' } ); // Industry section // Query for the industries checkboxes const industryCheckboxes = await page.$$( '.components-checkbox-control__input' ); expect( industryCheckboxes ).toHaveLength( 8 ); // Select all industries including "Other" for ( let i = 0; i < 8; i++ ) { await industryCheckboxes[i].click(); } // Fill "Other" industry await expect( page ).toFill( '.components-text-control__input', config.get( 'onboardingwizard.industry' ) ); // Wait for "Continue" button to become active await waitAndClickPrimary(); // Product types section // Query for the product types checkboxes const productTypesCheckboxes = await page.$$( '.components-checkbox-control__input' ); expect( productTypesCheckboxes ).toHaveLength( 7 ); // Select Physical and Downloadable products for ( let i = 1; i < 2; i++ ) { await productTypesCheckboxes[i].click(); } // Wait for "Continue" button to become active await waitAndClickPrimary(); // Business Details section // Query for the s const selectControls = await page.$$( '.woocommerce-select-control' ); expect( selectControls ).toHaveLength( 2 ); // Fill the number of products you plan to sell await selectControls[0].click(); await page.waitForSelector( '.woocommerce-select-control__control' ); await expect( page ).toClick( '.woocommerce-select-control__option', { text: config.get( 'onboardingwizard.numberofproducts' ) } ); // Fill currently selling elsewhere await selectControls[1].click(); await page.waitForSelector( '.woocommerce-select-control__control' ); await expect( page ).toClick( '.woocommerce-select-control__option', { text: config.get( 'onboardingwizard.sellingelsewhere' ) } ); // Wait for "Continue" button to become active await waitAndClickPrimary( false ); // Skip installing extensions await unsetCheckbox( '.components-checkbox-control__input' ); await verifyCheckboxIsUnset( '.components-checkbox-control__input' ); await waitAndClickPrimary(); // Theme section await waitAndClickPrimary(); // End of onboarding wizard if ( IS_RETEST_MODE ) { // Home screen modal can't be reset via the rest api. return; } // Wait for homescreen welcome modal to appear let welcomeHeader = await waitForSelectorWithoutThrow( '.woocommerce__welcome-modal__page-content' ); if ( ! welcomeHeader ) { return; } // Click two Next buttons for ( let b = 0; b < 2; b++ ) { await page.waitForSelector('button.components-guide__forward-button'); await page.click('button.components-guide__forward-button'); } // Wait for "Let's go" button to become active await page.waitForSelector( 'button.components-guide__finish-button' ); await page.click( 'button.components-guide__finish-button' ); }; /** * Create simple product. * * @param productTitle - Defaults to Simple Product. Customizable title. * @param productPrice - Defaults to $9.99. Customizable pricing. */ const createSimpleProduct = async ( productTitle = simpleProductName, productPrice = simpleProductPrice ) => { const product = await factories.products.simple.create( { name: productTitle, regularPrice: productPrice } ); return product.id; } ; /** * Create simple product with categories * * @param productName Product's name which can be changed when writing a test * @param productPrice Product's price which can be changed when writing a test * @param categoryName Product's category which can be changed when writing a test */ const createSimpleProductWithCategory = async ( productName, productPrice, categoryName ) => { const product = await factories.products.simple.create( { name: productName, regularPrice: productPrice, categories: [ { name: categoryName, } ], isVirtual: true, } ); return product.id; }; /** * Create variable product. */ const createVariableProduct = async () => { // Create a Variable Product (no variations yet) const defaultVariableProduct = config.get( 'products.variable' ); const variableProductRepo = VariableProduct.restRepository( client ); const variableProduct = await variableProductRepo.create( defaultVariableProduct ); // Create Variations const defaultVariations = config.get( 'products.variations' ); const variationsRepo = ProductVariation.restRepository( client ); for( const variation of defaultVariations ){ await variationsRepo.create( variableProduct.id, variation ); } return variableProduct.id; }; /** * Create 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 } ); // Go to "add product" page await merchant.openNewProduct(); // Make sure we're on the add product page await expect( page.title() ).resolves.toMatch( 'Add new product' ); // 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; } /** * Create a basic order with the provided order status. * * @param orderStatus Status of the new order. Defaults to `Pending payment`. */ const createSimpleOrder = async ( orderStatus = 'Pending payment' ) => { // Go to 'Add new order' page await merchant.openNewOrder(); // Make sure we're on the add order page await expect( page.title() ).resolves.toMatch( 'Add new order' ); // Set order status await expect( page ).toSelect( '#order_status', orderStatus ); // Wait for auto save await page.waitFor( 2000 ); // Create the order await expect( page ).toClick( 'button.save_order' ); await page.waitForSelector( '#message' ); // Verify await expect( page ).toMatchElement( '#message', { text: 'Order updated.' } ); const variablePostId = await page.$( '#post_ID' ); let variablePostIdValue = ( await ( await variablePostId.getProperty( 'value' ) ).jsonValue() ); return variablePostIdValue; }; /** * Adds a product to an order in the merchant. * * @param orderId ID of the order to add the product to. * @param productName Name of the product being added to the order. */ const addProductToOrder = async ( orderId, productName ) => { await merchant.goToOrder( orderId ); // Add a product to the order await expect( page ).toClick( 'button.add-line-item' ); await expect( page ).toClick( 'button.add-order-item' ); await page.waitForSelector( '.wc-backbone-modal-header' ); await expect( page ).toClick( '.wc-backbone-modal-content .wc-product-search' ); await expect( page ).toFill('#wc-backbone-modal-dialog + .select2-container .select2-search__field', productName); await page.waitForSelector( 'li[aria-selected="true"]', { timeout: 10000 } ); await expect( page ).toClick( 'li[aria-selected="true"]' ); await page.click( '.wc-backbone-modal-content #btn-ok' ); await backboneUnblocked(); // Verify the product we added shows as a line item now await expect( page ).toMatchElement( '.wc-order-item-name', { text: productName } ); } /** * Creates a basic coupon with the provided coupon amount. Returns the coupon code. * * @param couponAmount Amount to be applied. Defaults to 5. * @param discountType Type of a coupon. Defaults to Fixed cart discount. */ const createCoupon = async ( couponAmount = '5', discountType = 'Fixed cart discount' ) => { let couponType; switch ( discountType ) { case "Fixed cart discount": couponType = 'fixed_cart'; break; case "Fixed product discount": couponType = 'fixed_product'; break; case "Percentage discount": couponType = 'percent'; break; default: couponType = discountType; } // Fill in coupon code let couponCode = 'code-' + couponType + new Date().getTime().toString(); const repository = Coupon.restRepository( client ); await repository.create( { code: couponCode, discountType: couponType, amount: couponAmount, }); return couponCode; }; /** * Adds a shipping zone along with a shipping method. * * @param zoneName Shipping zone name. * @param zoneLocation Shiping zone location. Defaults to country:US. For states use: state:US:CA * @param zipCode Shipping zone zip code. Defaults to empty one space. * @param zoneMethod Shipping method type. Defaults to flat_rate (use also: free_shipping or local_pickup) */ const addShippingZoneAndMethod = async ( zoneName, zoneLocation = 'country:US', zipCode = ' ', zoneMethod = 'flat_rate' ) => { await merchant.openNewShipping(); // Fill shipping zone name await page.waitForSelector('input#zone_name'); await expect(page).toFill('input#zone_name', zoneName); // Select shipping zone location await expect(page).toSelect('select[name="zone_locations"]', zoneLocation); // Fill shipping zone postcode if needed otherwise just put empty space await page.waitForSelector('a.wc-shipping-zone-postcodes-toggle'); await expect(page).toClick('a.wc-shipping-zone-postcodes-toggle'); await expect(page).toFill('#zone_postcodes', zipCode); await expect(page).toMatchElement('#zone_postcodes', zipCode); await expect(page).toClick('button#submit'); // Add shipping zone method await page.waitFor(1000); await expect(page).toClick('button.wc-shipping-zone-add-method', {text:'Add shipping method'}); await page.waitForSelector('.wc-shipping-zone-method-selector'); await expect(page).toSelect('select[name="add_method_id"]', zoneMethod); await expect(page).toClick('button#btn-ok'); await page.waitForSelector('#zone_locations'); }; /** * Click the Update button on the order details page. * * @param noticeText The text that appears in the notice after updating the order. * @param waitForSave Optionally wait for auto save. */ const clickUpdateOrder = async ( noticeText, waitForSave = false ) => { if ( waitForSave ) { await page.waitFor( 2000 ); } // Update order await expect( page ).toClick( 'button.save_order' ); await page.waitForSelector( '.updated.notice' ); // Verify await expect( page ).toMatchElement( '.updated.notice', { text: noticeText } ); }; /** * Delete all email logs in the WP Mail Logging plugin page. */ const deleteAllEmailLogs = async () => { await merchant.openEmailLog(); // Make sure we have emails to delete. If we don't, this selector will return null. if ( await page.$( '#bulk-action-selector-top' ) !== null ) { await setCheckbox( '#cb-select-all-1' ); await expect( page ).toSelect( '#bulk-action-selector-top', 'Delete' ); await Promise.all( [ page.click( '#doaction' ), page.waitForNavigation( { waitUntil: 'networkidle0' } ), ] ); } }; /** * Delete all the existing shipping zones. */ const deleteAllShippingZones = async () => { await merchant.openSettings('shipping'); // Delete existing shipping zones. try { let zone = await page.$( '.wc-shipping-zone-delete' ); if ( zone ) { // WP action links aren't clickable because they are hidden with a left=-9999 style. await page.evaluate(() => { document.querySelector('.wc-shipping-zone-name .row-actions') .style .left = '0'; }); while ( zone ) { await evalAndClick( '.wc-shipping-zone-delete' ); await uiUnblocked(); zone = await page.$( '.wc-shipping-zone-delete' ); }; }; } catch (error) { // Prevent an error here causing the test to fail. }; }; export { completeOnboardingWizard, createSimpleProduct, createVariableProduct, createGroupedProduct, createSimpleOrder, verifyAndPublish, addProductToOrder, createCoupon, addShippingZoneAndMethod, createSimpleProductWithCategory, clickUpdateOrder, deleteAllEmailLogs, deleteAllShippingZones, };