diff --git a/src/pages/BO/shipping/carriers/create.ts b/src/pages/BO/shipping/carriers/create.ts index 77323568..a4de29cb 100644 --- a/src/pages/BO/shipping/carriers/create.ts +++ b/src/pages/BO/shipping/carriers/create.ts @@ -1,8 +1,15 @@ import type {BOCarriersCreatePageInterface} from '@interfaces/BO/shipping/carriers/create'; +import testContext from '@utils/test'; +import semver from 'semver'; + +const psVersion = testContext.getPSVersion(); /* eslint-disable global-require, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ function requirePage(): BOCarriersCreatePageInterface { - return require('@versions/develop/pages/BO/shipping/carriers/create'); + if (semver.lt(psVersion, '9.0.0')) { + return require('@versions/8.2/pages/BO/shipping/carriers/create'); + } + return require('@versions/develop/pages/BO/shipping/carriers/create').boCarriersCreatePage; } /* eslint-enable global-require, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ diff --git a/src/pages/BO/shipping/carriers/index.ts b/src/pages/BO/shipping/carriers/index.ts index 645d172f..12eae1d7 100644 --- a/src/pages/BO/shipping/carriers/index.ts +++ b/src/pages/BO/shipping/carriers/index.ts @@ -1,8 +1,15 @@ import type {BOCarriersPageInterface} from '@interfaces/BO/shipping/carriers'; +import testContext from '@utils/test'; +import semver from 'semver'; + +const psVersion = testContext.getPSVersion(); /* eslint-disable global-require, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ function requirePage(): BOCarriersPageInterface { - return require('@versions/develop/pages/BO/shipping/carriers'); + if (semver.lt(psVersion, '9.0.0')) { + return require('@versions/8.2/pages/BO/shipping/carriers'); + } + return require('@versions/develop/pages/BO/shipping/carriers').boCarriersPage; } /* eslint-enable global-require, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires */ diff --git a/src/versions/8.2/pages/BO/shipping/carriers/create.ts b/src/versions/8.2/pages/BO/shipping/carriers/create.ts new file mode 100644 index 00000000..0ee4b497 --- /dev/null +++ b/src/versions/8.2/pages/BO/shipping/carriers/create.ts @@ -0,0 +1,225 @@ +import type FakerCarrier from '@data/faker/carrier'; +import type FakerGroup from '@data/faker/group'; +import {type CarrierRange} from '@data/types/carrier'; +import {BOCarriersCreatePageInterface} from '@interfaces/BO/shipping/carriers/create'; +import {BOCarriersCreatePage as BOCarriersCreatePageVersion} from '@versions/develop/pages/BO/shipping/carriers/create'; +import type {Page} from 'playwright'; + +class BOCarriersCreatePage extends BOCarriersCreatePageVersion implements BOCarriersCreatePageInterface { + private readonly zonesTable: string; + + private readonly rangeInfInput: (numColumn: number) => string; + + private readonly rangeSupInput: (numColumn: number) => string; + + private readonly allZonesRadioButton: string; + + private readonly allZonesValueInput: (numColumn: number) => string; + + private readonly rangeZoneCheckbox: string; + + private readonly rangeZoneIDCheckbox: (zoneID: string) => string; + + private readonly rangePriceInput: (numColumn: number, zoneID: string) => string; + + private readonly addNewRangeButton: string; + + private readonly deleteRangeButton: string; + + private readonly nextButton: string; + + private readonly finishButton: string; + + constructor() { + super(); + + this.pageTitleCreate = `Carriers > View • ${global.INSTALL.SHOP_NAME}`; + this.pageTitleEdit = 'Carriers >'; + + this.alertSuccessBlockParagraph = '.alert-success'; + + // General settings + this.carrierForm = '#carrier_wizard'; + this.nameInput = '#name'; + this.transitTimeInput = '#delay_1'; + this.speedGradeInput = '#grade'; + this.logoInput = '#carrier_logo_input'; + this.trackingURLInput = '#url'; + + // Shipping locations and costs + this.addHandlingCostsToggle = (toggle: string) => `${this.carrierForm} #shipping_handling_${toggle}`; + this.freeShippingToggle = (toggle: string) => `${this.carrierForm} #is_free_${toggle}`; + this.billingPriceRadioButton = '#billing_price'; + this.billingWeightButton = '#billing_weight'; + this.taxRuleSelect = '#id_tax_rules_group'; + this.rangeBehaviorSelect = '#range_behavior'; + this.zonesTable = '#zones_table'; + this.rangeInfInput = (numColumn: number) => `${this.zonesTable} tr.range_inf td:nth-child(${numColumn + 3}) ` + + 'input[name^=\'range_inf\']'; + this.rangeSupInput = (numColumn: number) => `${this.zonesTable} tr.range_sup td:nth-child(${numColumn + 3}) ` + + 'input[name^=\'range_sup\']'; + this.allZonesRadioButton = `${this.zonesTable} tr.fees_all input[onclick*='checkAllZones']`; + this.allZonesValueInput = (numColumn: number) => `${this.zonesTable} tr.fees_all td:nth-child(${numColumn + 3})` + + ' .input-group input'; + this.rangeZoneCheckbox = `${this.zonesTable} input.input_zone`; + this.rangeZoneIDCheckbox = (zoneID: string) => `${this.rangeZoneCheckbox}#zone_${zoneID}`; + this.rangePriceInput = (numColumn: number, zoneID: string) => `${this.zonesTable} td:nth-child(${numColumn + 3})` + + ` input[name^="fees[${zoneID}]"]`; + this.addNewRangeButton = '#add_new_range'; + this.deleteRangeButton = `${this.zonesTable} tr.delete_range td button`; + + // Size, weight and group access + this.maxWidthInput = '#max_width'; + this.maxHeightInput = '#max_height'; + this.maxDepthInput = '#max_depth'; + this.maxWeightInput = '#max_weight'; + this.groupAccessInput = 'input[name="groupBox[]"]'; + this.groupAccessIdInput = (groupAccessId: number) => `${this.groupAccessInput}#groupBox_${groupAccessId}`; + + // Summary + this.enableToggle = (toggle: string) => `${this.carrierForm} #active_${toggle}`; + + this.nextButton = `${this.carrierForm} .buttonNext`; + this.finishButton = `${this.carrierForm} .buttonFinish`; + } + + /** + * Fill carrier form in create or edit page and save + * @param page {Page} Browser tab + * @param carrierData {FakerCarrier} Carrier information + * @return {Promise} + */ + async createEditCarrier(page: Page, carrierData: FakerCarrier): Promise { + // Set general settings + await this.setValue(page, this.nameInput, carrierData.name); + await this.setValue(page, this.transitTimeInput, carrierData.transitName); + await this.setValue(page, this.speedGradeInput, carrierData.speedGrade); + await this.uploadFile(page, this.logoInput, `${carrierData.name}.jpg`); + await this.setValue(page, this.trackingURLInput, carrierData.trackingURL); + await page.locator(this.nextButton).click(); + + // Set shipping locations and costs + await this.setChecked(page, this.freeShippingToggle(carrierData.freeShipping ? 'on' : 'off')); + if (!carrierData.freeShipping) { + await this.setChecked(page, this.addHandlingCostsToggle(carrierData.handlingCosts ? 'on' : 'off')); + } + + if (carrierData.billing === 'According to total price') { + await page.locator(this.billingPriceRadioButton).click(); + } else { + await page.locator(this.billingWeightButton).click(); + } + await this.selectByVisibleText(page, this.taxRuleSelect, carrierData.taxRule); + await this.selectByVisibleText(page, this.rangeBehaviorSelect, carrierData.outOfRangeBehavior); + + // Reset data before adding + if (!carrierData.freeShipping) { + await this.dialogListener(page); + // Remove all colums + const numDeleteRangeButtons = await page.locator(this.deleteRangeButton).count(); + + for (let i: number = 0; i < numDeleteRangeButtons; i++) { + await page.locator(this.deleteRangeButton).nth(0).click(); + } + + // Remove all ranges + const locatorRangeZoneCheckboxes = await page.locator(this.rangeZoneCheckbox).all(); + + // eslint-disable-next-line no-restricted-syntax + for (const locatorRangeZoneCheckbox of locatorRangeZoneCheckboxes) { + await locatorRangeZoneCheckbox.setChecked(false); + } + } + // Set range sup only if free shipping is disabled + for (let idxRange: number = 0; idxRange < carrierData.ranges.length; idxRange++) { + const carrierRange: CarrierRange = carrierData.ranges[idxRange]; + + if (!carrierData.freeShipping) { + if (carrierRange.weightMin) { + await this.setValue(page, this.rangeInfInput(idxRange), carrierRange.weightMin); + } + if (carrierRange.weightMax) { + await this.setValue(page, this.rangeSupInput(idxRange), carrierRange.weightMax); + } + } + for (let idxZone: number = 0; idxZone < carrierRange.zones.length; idxZone++) { + const carrierRangeZone = carrierRange.zones[idxZone].zone; + const carrierRangePrice = carrierRange.zones[idxZone].price; + + if (typeof carrierRangeZone === 'string') { + await page.locator(this.allZonesRadioButton).setChecked(true); + if (typeof carrierRangePrice !== 'undefined' && !carrierData.freeShipping) { + await page.locator(this.allZonesValueInput(idxRange)).fill(carrierRangePrice.toString()); + await page.waitForTimeout(1000); + await page.locator(this.allZonesValueInput(idxRange)).dispatchEvent('change'); + await page.waitForTimeout(2000); + } + } else { + await page.locator( + this.rangeZoneIDCheckbox(carrierRangeZone.id!.toString()), + ).setChecked(true); + if (typeof carrierRangePrice !== 'undefined' && !carrierData.freeShipping) { + await page.locator( + this.rangePriceInput(idxRange, carrierRangeZone.id!.toString()), + ).fill(carrierRangePrice.toString()); + } + } + } + + // Click on the "Add new range" button + if (idxRange < (carrierData.ranges.length - 1)) { + // Only if there is no next range (useful for edition) + if ((await page.locator(this.rangeInfInput(idxRange + 1)).count() === 0)) { + await page.locator(this.addNewRangeButton).click(); + } + } + } + await page.locator(this.nextButton).click(); + + // Set size, weight and group access + await this.setValue(page, this.maxWidthInput, carrierData.maxWidth); + await this.setValue(page, this.maxHeightInput, carrierData.maxHeight); + await this.setValue(page, this.maxDepthInput, carrierData.maxDepth); + await this.setValue(page, this.maxWeightInput, carrierData.maxWeight); + + const locatorGroupAccessInputs = await page.locator(this.groupAccessInput).all(); + + if (carrierData.groupAccesses.length) { + // eslint-disable-next-line no-restricted-syntax + for (const locatorGroupAccessInput of locatorGroupAccessInputs) { + await locatorGroupAccessInput.setChecked(false); + } + for (let idxGroupAccess: number = 0; idxGroupAccess < carrierData.groupAccesses.length; idxGroupAccess++) { + const groupAccess: FakerGroup = carrierData.groupAccesses[idxGroupAccess]; + await page.locator(this.groupAccessIdInput(groupAccess.id)).setChecked(true); + } + } + + await page.locator(this.nextButton).click(); + + // Summary + await this.setChecked(page, this.enableToggle(carrierData.enable ? 'on' : 'off')); + await page.locator(this.finishButton).click(); + + // Return successful message + return this.getAlertSuccessBlockParagraphContent(page); + } + + /** + * Set handling cost + * @param page {Page} Browser tab + * @param toEnable {Boolean} Handling cost toggle button value + * @returns {Promise} + */ + async setHandlingCosts(page: Page, toEnable: boolean = true): Promise { + await page.locator(this.nextButton).click(); + await this.setChecked(page, this.addHandlingCostsToggle(toEnable ? 'on' : 'off')); + + await page.locator(this.finishButton).click(); + + // Return successful message + return this.getAlertSuccessBlockParagraphContent(page); + } +} + +module.exports = new BOCarriersCreatePage(); diff --git a/src/versions/8.2/pages/BO/shipping/carriers/index.ts b/src/versions/8.2/pages/BO/shipping/carriers/index.ts new file mode 100644 index 00000000..c818fbec --- /dev/null +++ b/src/versions/8.2/pages/BO/shipping/carriers/index.ts @@ -0,0 +1,379 @@ +import {BOCarriersPageInterface} from '@interfaces/BO/shipping/carriers'; +import {BOCarriersPage as BOCarriersPageVersion} from '@versions/develop/pages/BO/shipping/carriers'; +import type {Page} from 'playwright'; + +class BOCarriersPage extends BOCarriersPageVersion implements BOCarriersPageInterface { + private readonly sortColumnDivNth: (column: number) => string; + + private readonly sortColumnSpanButtonNth: (column: number) => string; + + private readonly paginationDiv: string; + + private readonly paginationDropdownButton: string; + + private readonly paginationItems: (number: number) => string; + + private readonly unselectAllLink: string; + + constructor() { + super(); + this.successfulMultiDeleteMessage = 'The selection has been successfully deleted.'; + + // Header links + this.addNewCarrierLink = 'a[data-role=page-header-desc-carrier-link]'; + + // Form selectors + this.gridForm = '#form-carrier'; + this.gridTableHeaderTitle = `${this.gridForm} .panel-heading`; + this.gridTableNumberOfTitlesSpan = `${this.gridTableHeaderTitle} span.badge`; + + // Table selectors + this.gridTable = '#table-carrier'; + + // Filter selectors + this.filterRow = `${this.gridTable} tr.filter`; + this.filterColumn = (filterBy: string) => `${this.filterRow} [name='carrierFilter_${filterBy}']`; + this.filterSearchButton = '#submitFilterButtoncarrier'; + this.filterResetButton = 'button[name=\'submitResetcarrier\']'; + + // Table body selectors + this.tableBody = `${this.gridTable} tbody`; + this.tableBodyRows = `${this.tableBody} tr`; + this.tableBodyRow = (row: number) => `${this.tableBodyRows}:nth-child(${row})`; + this.tableBodyColumn = (row: number) => `${this.tableBodyRow(row)} td`; + //this.tableBodyColumnNth = (column: number) => `${this.tableBodyRows} td:nth-child(${column})`; + + // Columns selectors + this.tableColumnId = (row: number) => `${this.tableBodyColumn(row)}:nth-child(2)`; + this.tableColumnName = (row: number) => `${this.tableBodyColumn(row)}:nth-child(3)`; + this.tableColumnDelay = (row: number) => `${this.tableBodyColumn(row)}:nth-child(5)`; + this.tableColumnActive = (row: number) => `${this.tableBodyColumn(row)}:nth-child(6) a`; + this.enableColumnValidIcon = (row: number) => `${this.tableColumnActive(row)} i.icon-check`; + this.tableColumnIsFree = (row: number) => `${this.tableBodyColumn(row)}:nth-child(7) a`; + this.tableColumnIsFreeIcon = (row: number) => `${this.tableColumnIsFree(row)} i.icon-check`; + this.tableColumnPosition = (row: number) => `${this.tableBodyColumn(row)}:nth-child(8)`; + + // Row actions selectors + this.tableColumnActions = (row: number) => `${this.tableBodyColumn(row)} .btn-group-action`; + this.tableColumnActionsEditLink = (row: number) => `${this.tableColumnActions(row)} a.edit`; + this.tableColumnActionsToggleButton = (row: number) => `${this.tableColumnActions(row)} button.dropdown-toggle`; + this.tableColumnActionsDropdownMenu = (row: number) => `${this.tableColumnActions(row)} .dropdown-menu`; + this.tableColumnActionsDeleteLink = (row: number) => `${this.tableColumnActionsDropdownMenu(row)} a.delete`; + + // Sort selectors + this.sortColumnDivNth = (column: number) => `${this.tableHead} th:nth-child(${column})`; + this.sortColumnSpanButtonNth = (column: number) => `${this.sortColumnDivNth(column)} span.ps-sort`; + + // Pagination selectors + this.paginationActiveLabel = `${this.gridForm} ul.pagination.pull-right li.active a`; + this.paginationDiv = `${this.gridForm} .pagination`; + this.paginationDropdownButton = `${this.paginationDiv} .dropdown-toggle`; + this.paginationItems = (number: number) => `${this.gridForm} .dropdown-menu a[data-items='${number}']`; + this.paginationPreviousLink = `${this.gridForm} .icon-angle-left`; + this.paginationNextLink = `${this.gridForm} .icon-angle-right`; + + // Confirmation modal + this.deleteModalButtonYes = '#popup_ok'; + + // Bulk actions selectors + this.bulkActionBlock = 'div.bulk-actions'; + this.bulkActionMenuButton = '#bulk_action_menu_carrier'; + this.bulkActionDropdownMenu = `${this.bulkActionBlock} ul.dropdown-menu`; + this.selectAllLink = `${this.bulkActionDropdownMenu} li:nth-child(1)`; + this.unselectAllLink = `${this.bulkActionDropdownMenu} li:nth-child(2)`; + this.bulkEnableLink = `${this.bulkActionDropdownMenu} li:nth-child(4)`; + this.bulkDisableLink = `${this.bulkActionDropdownMenu} li:nth-child(5)`; + this.bulkDeleteLink = `${this.bulkActionDropdownMenu} li:nth-child(7)`; + } + + /** + * Get text from column in table + * @param page {Page} Browser tab + * @param row {number} Row index in the table + * @param columnName {string} Column name in the table + * @return {Promise} + */ + async getTextColumn(page: Page, row: number, columnName: string): Promise { + let columnSelector; + + switch (columnName) { + case 'id_carrier': + columnSelector = this.tableColumnId(row); + break; + + case 'name': + columnSelector = this.tableColumnName(row); + break; + + case 'delay': + columnSelector = this.tableColumnDelay(row); + break; + + case 'active': + columnSelector = this.tableColumnActive(row); + break; + + case 'is_free': + columnSelector = this.tableColumnIsFree(row); + break; + + default: + throw new Error(`Column ${columnName} was not found`); + } + + if ((columnName === 'active') || (columnName === 'is_free')) { + return this.getAttributeContent(page, columnSelector, 'title'); + } + return this.getTextContent(page, columnSelector); + } + + /** + * Get carrier status + * @param page {Page} Browser tab + * @param row {number} Row index in the table + * @returns {Promise} + */ + async getStatus(page: Page, row: number = 1): Promise { + return this.elementVisible(page, this.enableColumnValidIcon(row), 100); + } + + /** + * Set carriers status + * @param page {Page} Browser tab + * @param row {number} Row index in the table + * @param valueWanted {boolean} The carrier status value + * @return {Promise}, true if click has been performed + */ + async setStatus(page: Page, row: number = 1, valueWanted: boolean = true): Promise { + await this.waitForVisibleSelector(page, this.tableColumnActive(row), 2000); + + if (await this.getStatus(page, row) !== valueWanted) { + await page.locator(this.tableColumnActive(row)).click(); + return true; + } + + return false; + } + + /** + * Is free shipping + * @param page {Page} Browser tab + * @param row {number} Row index in the table + * @returns {Promise} + */ + async isFreeShipping(page: Page, row: number = 1): Promise { + return this.elementVisible(page, this.tableColumnIsFreeIcon(row), 100); + } + + /** + * Set free shipping status + * @param page {Page} Browser tab + * @param row {number} Row index in the table + * @param valueWanted {boolean} The carrier status value + * @return {Promise} + */ + async setFreeShippingStatus(page: Page, row: number = 1, valueWanted: boolean = true): Promise { + if (await this.isFreeShipping(page, row) !== valueWanted) { + await page.locator(this.tableColumnIsFree(row)).click(); + return true; + } + + return false; + } + + /** + * Reset all filters + * @param page {Page} Browser tab + * @return {Promise} + */ + async resetFilter(page: Page): Promise { + if (!(await this.elementNotVisible(page, this.filterResetButton, 2000))) { + await this.clickAndWaitForURL(page, this.filterResetButton); + } + await this.waitForVisibleSelector(page, this.filterSearchButton, 2000); + } + + /** + * Filter carriers table + * @param page {Page} Browser tab + * @param filterType {string} Type of the filter (input or select) + * @param filterBy {string} Value to use for the select type filter + * @param value {string|number} Value for the select filter + * @return {Promise} + */ + async filterTable(page: Page, filterType: string, filterBy: string, value: string): Promise { + const currentUrl: string = page.url(); + + switch (filterType) { + case 'input': + await this.setValue(page, this.filterColumn(filterBy), value); + await this.clickAndWaitForURL(page, this.filterSearchButton); + break; + + case 'select': + await Promise.all([ + page.waitForURL((url: URL): boolean => url.toString() !== currentUrl, {waitUntil: 'networkidle'}), + this.selectByVisibleText(page, this.filterColumn(filterBy), value === '1' ? 'Yes' : 'No'), + ]); + break; + + default: + throw new Error(`Filter ${filterBy} was not found`); + } + } + + /** + * Sort table by clicking on column name + * @param page {Page} Browser tab + * @param sortBy {string} column to sort with + * @param sortDirection {string} asc or desc + * @return {Promise} + */ + async sortTable(page: Page, sortBy: string, sortDirection: string): Promise { + let columnSelector; + + switch (sortBy) { + case 'id_carrier': + columnSelector = this.sortColumnDivNth(2); + break; + + case 'name': + columnSelector = this.sortColumnDivNth(3); + break; + + case 'a!position': + columnSelector = this.sortColumnDivNth(8); + break; + + default: + throw new Error(`Column ${sortBy} was not found`); + } + + const sortColumnButton = `${columnSelector} i.icon-caret-${sortDirection}`; + await this.clickAndWaitForURL(page, sortColumnButton); + } + + /* Pagination methods */ + /** + * Select pagination limit + * @param page {Page} Browser tab + * @param number {Number} The pagination number value + * @returns {Promise} + */ + async selectPaginationLimit(page: Page, number: number): Promise { + await this.waitForSelectorAndClick(page, this.paginationDropdownButton); + await this.waitForSelectorAndClick(page, this.paginationItems(number)); + return this.getPaginationLabel(page); + } + + /* Bulk actions methods */ + /** + * Select all rows + * @param page {Page} Browser tab + * @param status {boolean} Select or Unselect all + * @returns {Promise} + */ + async bulkSetSelection(page: Page, status: boolean): Promise { + const selectorAll: string = status ? this.selectAllLink : this.unselectAllLink; + + await Promise.all([ + page.locator(this.bulkActionMenuButton).click(), + this.waitForVisibleSelector(page, selectorAll), + ]); + + await Promise.all([ + page.locator(selectorAll).click(), + this.waitForHiddenSelector(page, selectorAll), + ]); + } + + /** + * Bulk set carriers status + * @param page {Page} Browser tab + * @param action {string} The action to perform in bulk + * @returns {Promise} + */ + async bulkSetStatus(page: Page, action: string): Promise { + // Select all rows + await this.bulkSetSelection(page, true); + + // Perform delete + await Promise.all([ + page.locator(this.bulkActionMenuButton).click(), + this.waitForVisibleSelector(page, this.bulkDeleteLink), + ]); + + if (action === 'Enable') { + await this.clickAndWaitForURL(page, this.bulkEnableLink); + } else { + await this.clickAndWaitForURL(page, this.bulkDisableLink); + } + + // Return successful message + return this.getTextContent(page, this.alertSuccessBlock); + } + + /** + * Bulk delete carriers + * @param page {Page} Browser tab + * @return {Promise} + */ + async bulkDeleteCarriers(page: Page): Promise { + // To confirm bulk delete action with dialog + await this.dialogListener(page, true); + + // Select all rows + await this.bulkSetSelection(page, true); + + // Perform delete + await Promise.all([ + page.locator(this.bulkActionMenuButton).click(), + this.waitForVisibleSelector(page, this.bulkDeleteLink), + ]); + + await this.clickAndWaitForURL(page, this.bulkDeleteLink); + + // Return successful message + return this.getAlertSuccessBlockContent(page); + } + + /** + * Delete carrier from row + * @param page {Page} Browser tab + * @param row {number} Row index in the table + * @return {Promise} + */ + async deleteCarrier(page: Page, row: number): Promise { + await Promise.all([ + page.locator(this.tableColumnActionsToggleButton(row)).click(), + this.waitForVisibleSelector(page, this.tableColumnActionsDeleteLink(row)), + ]); + + await page.locator(this.tableColumnActionsDeleteLink(row)).click(); + + // Confirm delete action + await this.clickAndWaitForURL(page, this.deleteModalButtonYes); + + // Get successful message + return this.getAlertSuccessBlockContent(page); + } + + /** + * Change carrier position + * @param page {Page} Browser tab + * @param actualPosition {number} The actual row position + * @param newPosition {number} The new position for the row + * @return {Promise} + */ + async changePosition(page: Page, actualPosition: number, newPosition: number): Promise { + await this.dragAndDrop( + page, + this.tableColumnPosition(actualPosition), + this.tableColumnPosition(newPosition), + ); + + return this.getGrowlMessageContent(page); + } +} + +module.exports = new BOCarriersPage(); diff --git a/src/versions/develop/pages/BO/shipping/carriers/create.ts b/src/versions/develop/pages/BO/shipping/carriers/create.ts index 5f014a1d..811dab3c 100644 --- a/src/versions/develop/pages/BO/shipping/carriers/create.ts +++ b/src/versions/develop/pages/BO/shipping/carriers/create.ts @@ -1,3 +1,4 @@ +import dataZones from '@data/demo/zones'; import FakerCarrier from '@data/faker/carrier'; import FakerGroup from '@data/faker/group'; import {CarrierRange} from '@data/types/carrier'; @@ -11,71 +12,87 @@ import type {Page} from '@playwright/test'; * @extends BOBasePage */ class BOCarriersCreatePage extends BOBasePage implements BOCarriersCreatePageInterface { - public readonly pageTitleCreate: string; + public pageTitleCreate: string; - public readonly pageTitleEdit: string; + public pageTitleEdit: string; - private readonly carrierForm: string; + protected carrierForm: string; - private readonly nameInput: string; + private readonly tabGeneralSettings: string; - private readonly transitTimeInput: string; + protected nameInput: string; - private readonly speedGradeInput: string; + protected transitTimeInput: string; - private readonly logoInput: string; + protected speedGradeInput: string; - private readonly trackingURLInput: string; + protected logoInput: string; - private readonly addHandlingCostsToggle: (toggle: string) => string; + protected trackingURLInput: string; - private readonly freeShippingToggle: (toggle: string) => string; + private readonly tabShippingSettings: string; - private readonly billingPriceRadioButton: string; + protected addHandlingCostsToggle: (toggle: string) => string; - private readonly billingWeightButton: string; + protected freeShippingToggle: (toggle: string) => string; - private readonly taxRuleSelect: string; + protected billingPriceRadioButton: string; - private readonly rangeBehaviorSelect: string; + protected billingWeightButton: string; - private readonly zonesTable: string; + protected taxRuleSelect: string; - private readonly rangeInfInput: (numColumn: number) => string; + protected rangeBehaviorSelect: string; - private readonly rangeSupInput: (numColumn: number) => string; + private readonly zonesWidget: string; - private readonly allZonesRadioButton: string; + private readonly zonesRemoveButton: string; - private readonly allZonesValueInput: (numColumn: number) => string; + private readonly zonesSelect2: string; - private readonly rangeZoneCheckbox: string; + private readonly zonesSelect2Input: string; - private readonly rangeZoneIDCheckbox: (zoneID: string) => string; + private readonly rangesButton: string; - private readonly rangePriceInput: (numColumn: number, zoneID: string) => string; + private readonly rangesModal: string; - private readonly addNewRangeButton: string; + private readonly rangesRemoveButton: string; - private readonly deleteRangeButton: string; + private readonly rangesRow: (nth: number) => string; - private readonly maxWidthInput: string; + private readonly rangesRowInputFrom: (nth: number) => string; - private readonly maxHeightInput: string; + private readonly rangesRowInputTo: (nth: number) => string; - private readonly maxDepthInput: string; + private readonly rangesAddButton: string; - private readonly maxWeightInput: string; + private readonly rangesApplyButton: string; - private readonly groupAccessInput: string; + private readonly costsBlock: string; - private readonly groupAccessIdInput: (groupAccessId: number) => string; + private readonly costsZoneBlock: (zoneId: number) => string; - private readonly enableToggle: (toggle: string) => string; + private readonly costsPriceInput: (zoneId: number, from: number, to: number) => string; - private readonly nextButton: string; + private readonly tabSizeWeightSettings: string; - private readonly finishButton: string; + protected maxWidthInput: string; + + protected maxHeightInput: string; + + protected maxDepthInput: string; + + protected maxWeightInput: string; + + protected groupAccessInputAll: string; + + protected groupAccessInput: string; + + protected groupAccessIdInput: (groupAccessId: number) => string; + + protected enableToggle: (toggle: string) => string; + + private readonly submitButton: string; /** * @constructs @@ -84,54 +101,59 @@ class BOCarriersCreatePage extends BOBasePage implements BOCarriersCreatePageInt constructor() { super(); - this.pageTitleCreate = 'Carriers > View •'; - this.pageTitleEdit = 'Carriers >'; - - this.alertSuccessBlockParagraph = '.alert-success'; + this.pageTitleCreate = `New Carrier • ${global.INSTALL.SHOP_NAME}`; + this.pageTitleEdit = `Carrier • ${global.INSTALL.SHOP_NAME}`; + this.carrierForm = 'form[name="carrier"]'; // General settings - this.carrierForm = '#carrier_wizard'; - this.nameInput = '#name'; - this.transitTimeInput = '#delay_1'; - this.speedGradeInput = '#grade'; - this.logoInput = '#carrier_logo_input'; - this.trackingURLInput = '#url'; + this.tabGeneralSettings = '#carrier_general_settings-tab-nav a'; + this.nameInput = '#carrier_general_settings_name'; + this.transitTimeInput = '#carrier_general_settings_localized_delay_1'; + this.enableToggle = (toggle: string) => `${this.carrierForm} #carrier_general_settings_active_${toggle}`; + this.speedGradeInput = '#carrier_general_settings_grade'; + this.logoInput = '#carrier_general_settings_logo'; + this.trackingURLInput = '#carrier_general_settings_tracking_url'; + this.groupAccessInputAll = 'input.js-choice-table-select-all'; + this.groupAccessInput = 'input[name="carrier[general_settings][group_access][]"]'; + this.groupAccessIdInput = (groupAccessId: number) => `${this.groupAccessInput}[value="${groupAccessId}"]`; // Shipping locations and costs - this.addHandlingCostsToggle = (toggle: string) => `${this.carrierForm} #shipping_handling_${toggle}`; - this.freeShippingToggle = (toggle: string) => `${this.carrierForm} #is_free_${toggle}`; - this.billingPriceRadioButton = '#billing_price'; - this.billingWeightButton = '#billing_weight'; - this.taxRuleSelect = '#id_tax_rules_group'; - this.rangeBehaviorSelect = '#range_behavior'; - this.zonesTable = '#zones_table'; - this.rangeInfInput = (numColumn: number) => `${this.zonesTable} tr.range_inf td:nth-child(${numColumn + 3}) ` - + 'input[name^=\'range_inf\']'; - this.rangeSupInput = (numColumn: number) => `${this.zonesTable} tr.range_sup td:nth-child(${numColumn + 3}) ` - + 'input[name^=\'range_sup\']'; - this.allZonesRadioButton = `${this.zonesTable} tr.fees_all input[onclick*='checkAllZones']`; - this.allZonesValueInput = (numColumn: number) => `${this.zonesTable} tr.fees_all td:nth-child(${numColumn + 3})` - + ' .input-group input'; - this.rangeZoneCheckbox = `${this.zonesTable} input.input_zone`; - this.rangeZoneIDCheckbox = (zoneID: string) => `${this.rangeZoneCheckbox}#zone_${zoneID}`; - this.rangePriceInput = (numColumn: number, zoneID: string) => `${this.zonesTable} td:nth-child(${numColumn + 3})` - + ` input[name^="fees[${zoneID}]"]`; - this.addNewRangeButton = '#add_new_range'; - this.deleteRangeButton = `${this.zonesTable} tr.delete_range td button`; + this.tabShippingSettings = '#carrier_shipping_settings-tab-nav a'; + this.addHandlingCostsToggle = (toggle: string) => `${this.carrierForm + } #carrier_shipping_settings_has_additional_handling_fee_${toggle}`; + this.freeShippingToggle = (toggle: string) => `${this.carrierForm} #carrier_shipping_settings_is_free_${toggle}`; + this.billingPriceRadioButton = `${this.carrierForm} #carrier_shipping_settings_shipping_method_0`; + this.billingWeightButton = `${this.carrierForm} #carrier_shipping_settings_shipping_method_1`; + this.taxRuleSelect = `${this.carrierForm} #carrier_shipping_settings_id_tax_rule_group`; + this.rangeBehaviorSelect = `${this.carrierForm} #carrier_shipping_settings_range_behavior`; + /// Zones + this.zonesWidget = `${this.carrierForm} .multiple_zone_choice-widget`; + this.zonesRemoveButton = `${this.zonesWidget} .select2-selection__choice__remove`; + this.zonesSelect2 = `${this.zonesWidget} .select2-selection`; + this.zonesSelect2Input = `${this.zonesSelect2} .select2-search__field`; + /// Ranges + this.rangesButton = `${this.carrierForm} #carrier_shipping_settings_ranges_show_modal`; + this.rangesModal = '#carrier_shipping_settings_ranges-app .modal-content'; + this.rangesRemoveButton = `${this.rangesModal} .modal-body button.btn-delete`; + this.rangesRow = (nth: number) => `${this.rangesModal} .modal-body tr[data-row="${nth}"]`; + this.rangesRowInputFrom = (nth: number) => `${this.rangesRow(nth)} input.form-from`; + this.rangesRowInputTo = (nth: number) => `${this.rangesRow(nth)} input.form-to`; + this.rangesAddButton = `${this.rangesModal} .modal-body .table-container > button:not(.btn-delete)`; + this.rangesApplyButton = `${this.rangesModal} .modal-footer button.btn-primary`; + /// Costs + this.costsBlock = '#carrier_shipping_settings_ranges_costs'; + this.costsZoneBlock = (zoneId: number) => `${this.costsBlock} .js-carrier-zone-row[data-zone-id="${zoneId}"]`; + this.costsPriceInput = (zoneId: number, from: number, to: number) => `${this.costsZoneBlock(zoneId)} input.form-control` + + `[data-from="${from}"][data-to="${to}"]`; // Size, weight and group access - this.maxWidthInput = '#max_width'; - this.maxHeightInput = '#max_height'; - this.maxDepthInput = '#max_depth'; - this.maxWeightInput = '#max_weight'; - this.groupAccessInput = 'input[name="groupBox[]"]'; - this.groupAccessIdInput = (groupAccessId: number) => `${this.groupAccessInput}#groupBox_${groupAccessId}`; - - // Summary - this.enableToggle = (toggle: string) => `${this.carrierForm} #active_${toggle}`; + this.tabSizeWeightSettings = '#carrier_size_weight_settings-tab-nav a'; + this.maxWidthInput = '#carrier_size_weight_settings_max_width'; + this.maxHeightInput = '#carrier_size_weight_settings_max_height'; + this.maxDepthInput = '#carrier_size_weight_settings_max_depth'; + this.maxWeightInput = '#carrier_size_weight_settings_max_weight'; - this.nextButton = `${this.carrierForm} .buttonNext`; - this.finishButton = `${this.carrierForm} .buttonFinish`; + this.submitButton = '#carrier__footer_buttons_submit'; } /* Methods */ @@ -143,116 +165,137 @@ class BOCarriersCreatePage extends BOBasePage implements BOCarriersCreatePageInt * @return {Promise} */ async createEditCarrier(page: Page, carrierData: FakerCarrier): Promise { + // Tab generat settings + await page.locator(this.tabGeneralSettings).click(); // Set general settings await this.setValue(page, this.nameInput, carrierData.name); await this.setValue(page, this.transitTimeInput, carrierData.transitName); + await this.setHiddenCheckboxValue(page, this.enableToggle('1'), carrierData.enable); await this.setValue(page, this.speedGradeInput, carrierData.speedGrade); await this.uploadFile(page, this.logoInput, `${carrierData.name}.jpg`); await this.setValue(page, this.trackingURLInput, carrierData.trackingURL); - await page.locator(this.nextButton).click(); - // Set shipping locations and costs - await this.setChecked(page, this.freeShippingToggle(carrierData.freeShipping ? 'on' : 'off')); - if (!carrierData.freeShipping) { - await this.setChecked(page, this.addHandlingCostsToggle(carrierData.handlingCosts ? 'on' : 'off')); - } + if (carrierData.groupAccesses.length) { + await this.setCheckedWithIcon(page, this.groupAccessInputAll, false); - if (carrierData.billing === 'According to total price') { - await page.locator(this.billingPriceRadioButton).click(); - } else { - await page.locator(this.billingWeightButton).click(); + for (let idxGroupAccess: number = 0; idxGroupAccess < carrierData.groupAccesses.length; idxGroupAccess++) { + const groupAccess: FakerGroup = carrierData.groupAccesses[idxGroupAccess]; + await this.setCheckedWithIcon(page, this.groupAccessIdInput(groupAccess.id), true); + } } - await this.selectByVisibleText(page, this.taxRuleSelect, carrierData.taxRule); - await this.selectByVisibleText(page, this.rangeBehaviorSelect, carrierData.outOfRangeBehavior); - // Reset data before adding - if (!carrierData.freeShipping) { - await this.dialogListener(page); - // Remove all colums - const numDeleteRangeButtons = await page.locator(this.deleteRangeButton).count(); + // Tab shipping locations and costs + await page.locator(this.tabShippingSettings).click(); + // Set shipping locations and costs + /// Zones - START + // Remove all zones + const numZonesRemoveButton = await page.locator(this.zonesRemoveButton).count(); - for (let i: number = 0; i < numDeleteRangeButtons; i++) { - await page.locator(this.deleteRangeButton).nth(0).click(); + if (numZonesRemoveButton > 0) { + for (let i: number = 0; i < numZonesRemoveButton; i++) { + await page.locator(this.zonesRemoveButton).nth(0).click(); } - // Remove all ranges - const locatorRangeZoneCheckboxes = await page.locator(this.rangeZoneCheckbox).all(); - - // eslint-disable-next-line no-restricted-syntax - for (const locatorRangeZoneCheckbox of locatorRangeZoneCheckboxes) { - await locatorRangeZoneCheckbox.setChecked(false); - } + await page.mouse.click(5, 5); } - // Set range sup only if free shipping is disabled - for (let idxRange: number = 0; idxRange < carrierData.ranges.length; idxRange++) { - const carrierRange: CarrierRange = carrierData.ranges[idxRange]; - if (!carrierData.freeShipping) { - if (carrierRange.weightMin) { - await this.setValue(page, this.rangeInfInput(idxRange), carrierRange.weightMin); - } - if (carrierRange.weightMax) { - await this.setValue(page, this.rangeSupInput(idxRange), carrierRange.weightMax); - } - } + // Add zones + if (carrierData.ranges.length > 0) { + const carrierRange: CarrierRange = carrierData.ranges[0]; + for (let idxZone: number = 0; idxZone < carrierRange.zones.length; idxZone++) { const carrierRangeZone = carrierRange.zones[idxZone].zone; - const carrierRangePrice = carrierRange.zones[idxZone].price; - + await page.locator(this.zonesSelect2).click(); + await this.waitForVisibleSelector(page, `${this.zonesSelect2}[aria-expanded='true']`); if (typeof carrierRangeZone === 'string') { - await page.locator(this.allZonesRadioButton).setChecked(true); - if (typeof carrierRangePrice !== 'undefined' && !carrierData.freeShipping) { - await page.locator(this.allZonesValueInput(idxRange)).fill(carrierRangePrice.toString()); - await page.waitForTimeout(1000); - await page.locator(this.allZonesValueInput(idxRange)).dispatchEvent('change'); - await page.waitForTimeout(2000); + // eslint-disable-next-line no-restricted-syntax + for (const zone of Object.values(dataZones)) { + await this.setValue(page, this.zonesSelect2Input, zone.name); + await page.keyboard.press('Enter'); } } else { - await page.locator( - this.rangeZoneIDCheckbox(carrierRangeZone.id!.toString()), - ).setChecked(true); - if (typeof carrierRangePrice !== 'undefined' && !carrierData.freeShipping) { - await page.locator( - this.rangePriceInput(idxRange, carrierRangeZone.id!.toString()), - ).fill(carrierRangePrice.toString()); - } + await this.setValue(page, this.zonesSelect2Input, carrierRangeZone.name!); + await page.keyboard.press('Enter'); } } + } + /// Zones - END - // Click on the "Add new range" button - if (idxRange < (carrierData.ranges.length - 1)) { - // Only if there is no next range (useful for edition) - if ((await page.locator(this.rangeInfInput(idxRange + 1)).count() === 0)) { - await page.locator(this.addNewRangeButton).click(); - } + await this.setHiddenCheckboxValue(page, this.freeShippingToggle('1'), carrierData.freeShipping); + if (!carrierData.freeShipping) { + await this.setChecked(page, this.addHandlingCostsToggle('1'), carrierData.handlingCosts, true); + await this.selectByVisibleText(page, this.taxRuleSelect, carrierData.taxRule); + if (carrierData.billing === 'According to total price') { + await page.locator(this.billingPriceRadioButton).click(); + } else { + await page.locator(this.billingWeightButton).click(); } - } - await page.locator(this.nextButton).click(); + await this.selectByVisibleText(page, this.rangeBehaviorSelect, carrierData.outOfRangeBehavior); - // Set size, weight and group access - await this.setValue(page, this.maxWidthInput, carrierData.maxWidth); - await this.setValue(page, this.maxHeightInput, carrierData.maxHeight); - await this.setValue(page, this.maxDepthInput, carrierData.maxDepth); - await this.setValue(page, this.maxWeightInput, carrierData.maxWeight); + /// Ranges - START + await page.locator(this.rangesButton).click(); + await this.waitForVisibleSelector(page, this.rangesModal); + // Remove all ranges + const numRangesRemoveButton = await page.locator(this.rangesRemoveButton).count(); - const locatorGroupAccessInputs = await page.locator(this.groupAccessInput).all(); + for (let i: number = 0; i < numRangesRemoveButton; i++) { + await page.locator(this.rangesRemoveButton).nth(0).click(); + } + // Add ranges + for (let idxRange: number = 0; idxRange < carrierData.ranges.length; idxRange++) { + const carrierRange: CarrierRange = carrierData.ranges[idxRange]; - if (carrierData.groupAccesses.length) { - // eslint-disable-next-line no-restricted-syntax - for (const locatorGroupAccessInput of locatorGroupAccessInputs) { - await locatorGroupAccessInput.setChecked(false); + await this.setValue(page, this.rangesRowInputFrom(idxRange), carrierRange.weightMin!); + await this.setValue(page, this.rangesRowInputTo(idxRange), carrierRange.weightMax!); + + // Click on the "Add new range" button + if (idxRange < (carrierData.ranges.length - 1)) { + await page.locator(this.rangesAddButton).click(); + } } - for (let idxGroupAccess: number = 0; idxGroupAccess < carrierData.groupAccesses.length; idxGroupAccess++) { - const groupAccess: FakerGroup = carrierData.groupAccesses[idxGroupAccess]; - await page.locator(this.groupAccessIdInput(groupAccess.id)).setChecked(true); + await page.locator(this.rangesApplyButton).click(); + await this.waitForHiddenSelector(page, this.rangesModal); + /// Ranges - END + + /// Prices - START + for (let idxRange: number = 0; idxRange < carrierData.ranges.length; idxRange++) { + const carrierRange: CarrierRange = carrierData.ranges[idxRange]; + + for (let idxZone: number = 0; idxZone < carrierRange.zones.length; idxZone++) { + const carrierRangeZone = carrierRange.zones[idxZone].zone; + const carrierRangePrice = carrierRange.zones[idxZone].price; + + if (typeof carrierRangeZone === 'string') { + // eslint-disable-next-line no-restricted-syntax + for (const zone of Object.values(dataZones)) { + await this.setValue( + page, + this.costsPriceInput(zone.id, carrierRange.weightMin!, carrierRange.weightMax!), + carrierRangePrice!, + ); + } + } else { + await this.setValue( + page, + this.costsPriceInput(carrierRangeZone.id!, carrierRange.weightMin!, carrierRange.weightMax!), + carrierRangePrice!, + ); + } + } } } + /// Ranges - END - await page.locator(this.nextButton).click(); + // Tab size, weight + await page.locator(this.tabSizeWeightSettings).click(); + // Set size, weight + await this.setValue(page, this.maxWidthInput, carrierData.maxWidth); + await this.setValue(page, this.maxHeightInput, carrierData.maxHeight); + await this.setValue(page, this.maxDepthInput, carrierData.maxDepth); + await this.setValue(page, this.maxWeightInput, carrierData.maxWeight); // Summary - await this.setChecked(page, this.enableToggle(carrierData.enable ? 'on' : 'off')); - await page.locator(this.finishButton).click(); + await page.locator(this.submitButton).click(); // Return successful message return this.getAlertSuccessBlockParagraphContent(page); @@ -265,14 +308,15 @@ class BOCarriersCreatePage extends BOBasePage implements BOCarriersCreatePageInt * @returns {Promise} */ async setHandlingCosts(page: Page, toEnable: boolean = true): Promise { - await page.locator(this.nextButton).click(); - await this.setChecked(page, this.addHandlingCostsToggle(toEnable ? 'on' : 'off')); + await page.locator(this.tabShippingSettings).click(); + await this.setChecked(page, this.addHandlingCostsToggle('1'), toEnable, true); - await page.locator(this.finishButton).click(); + await page.locator(this.submitButton).click(); // Return successful message return this.getAlertSuccessBlockParagraphContent(page); } } -module.exports = new BOCarriersCreatePage(); +const boCarriersCreatePage = new BOCarriersCreatePage(); +export {boCarriersCreatePage, BOCarriersCreatePage}; diff --git a/src/versions/develop/pages/BO/shipping/carriers/index.ts b/src/versions/develop/pages/BO/shipping/carriers/index.ts index df6531f9..0dc3842c 100644 --- a/src/versions/develop/pages/BO/shipping/carriers/index.ts +++ b/src/versions/develop/pages/BO/shipping/carriers/index.ts @@ -12,95 +12,95 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { public readonly successfulUpdateStatusMessage: string; - private readonly addNewCarrierLink: string; + protected addNewCarrierLink: string; - private readonly gridForm: string; + protected gridForm: string; - private readonly gridTableHeaderTitle: string; + protected gridTableHeaderTitle: string; - private readonly gridTableNumberOfTitlesSpan: string; + protected gridTableNumberOfTitlesSpan: string; - private readonly gridTable: string; + protected gridTable: string; - private readonly filterRow: string; + protected filterRow: string; - private readonly filterColumn: (filterBy: string) => string; + protected filterColumn: (filterBy: string) => string; - private readonly filterSearchButton: string; + protected filterSearchButton: string; - private readonly filterResetButton: string; + protected filterResetButton: string; - private readonly tableBody: string; + protected tableBody: string; - private readonly tableBodyRows: string; + protected tableBodyRows: string; - private readonly tableBodyRow: (row: number) => string; + protected tableBodyRow: (row: number) => string; - private readonly tableBodyColumn: (row: number) => string; + protected tableBodyColumn: (row: number) => string; private readonly tableBodyColumnNth: (column: number) => string; - private readonly tableColumnId: (row: number) => string; + private readonly tableColumnHandle: (row: number) => string; - private readonly tableColumnName: (row: number) => string; + protected tableColumnId: (row: number) => string; - private readonly tableColumnDelay: (row: number) => string; + protected tableColumnName: (row: number) => string; - private readonly tableColumnActive: (row: number) => string; + protected tableColumnDelay: (row: number) => string; - private readonly enableColumnValidIcon: (row: number) => string; + protected tableColumnActive: (row: number) => string; - private readonly tableColumnIsFree: (row: number) => string; + protected enableColumnValidIcon: (row: number) => string; - private readonly tableColumnIsFreeIcon: (row: number) => string; + protected tableColumnIsFree: (row: number) => string; - private readonly tableColumnPosition: (row: number) => string; + protected tableColumnIsFreeIcon: (row: number) => string; - private readonly tableColumnActions: (row: number) => string; + protected tableColumnPosition: (row: number) => string; - private readonly tableColumnActionsEditLink: (row: number) => string; + protected tableColumnActions: (row: number) => string; - private readonly tableColumnActionsToggleButton: (row: number) => string; + protected tableColumnActionsEditLink: (row: number) => string; - private readonly tableColumnActionsDropdownMenu: (row: number) => string; + protected tableColumnActionsToggleButton: (row: number) => string; - private readonly tableColumnActionsDeleteLink: (row: number) => string; + protected tableColumnActionsDropdownMenu: (row: number) => string; - private readonly tableHead: string; + protected tableColumnActionsDeleteLink: (row: number) => string; - private readonly sortColumnDiv: (column: number) => string; + protected readonly tableHead: string; - private readonly sortColumnSpanButton: (column: number) => string; + private readonly sortColumnDiv: (column: string) => string; - private readonly paginationActiveLabel: string; + private readonly sortColumnSpanButton: (column: string) => string; - private readonly paginationDiv: string; + private readonly paginationLimitSelect: string; - private readonly paginationDropdownButton: string; + protected paginationActiveLabel: string; - private readonly paginationItems: (number: number) => string; + protected paginationPreviousLink: string; - private readonly paginationPreviousLink: string; + protected paginationNextLink: string; - private readonly paginationNextLink: string; + protected deleteModalButtonYes: string; - private readonly deleteModalButtonYes: string; + protected bulkActionBlock: string; - private readonly bulkActionBlock: string; + protected bulkActionMenuButton: string; - private readonly bulkActionMenuButton: string; + protected bulkActionDropdownMenu: string; - private readonly bulkActionDropdownMenu: string; + protected selectAllLink: string; - private readonly selectAllLink: string; + protected bulkEnableLink: string; - private readonly unselectAllLink: string; + protected bulkDisableLink: string; - private readonly bulkEnableLink: string; + protected bulkDeleteLink: string; - private readonly bulkDisableLink: string; + private readonly bulkDeleteConfirmDeleteModal: string; - private readonly bulkDeleteLink: string; + private readonly bulkDeleteConfirmDeleteButton: string; /** * @constructs @@ -111,26 +111,28 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { this.pageTitle = 'Carriers •'; this.successfulUpdateStatusMessage = 'The status has been successfully updated.'; + this.successfulMultiDeleteMessage = 'Successful deletion'; // Selectors this.growlMessageBlock = '#growls .growl-message:last-of-type'; // Header links - this.addNewCarrierLink = 'a[data-role=page-header-desc-carrier-link]'; + this.addNewCarrierLink = 'a#page-header-desc-configuration-add'; // Form selectors - this.gridForm = '#form-carrier'; - this.gridTableHeaderTitle = `${this.gridForm} .panel-heading`; - this.gridTableNumberOfTitlesSpan = `${this.gridTableHeaderTitle} span.badge`; + this.gridForm = '#carrier_grid_panel'; + this.gridTableHeaderTitle = ''; + this.gridTableNumberOfTitlesSpan = `${this.gridTableHeaderTitle} h3.card-header-title`; // Table selectors - this.gridTable = '#table-carrier'; + this.gridTable = '#carrier_grid_table'; // Filter selectors - this.filterRow = `${this.gridTable} tr.filter`; - this.filterColumn = (filterBy: string) => `${this.filterRow} [name='carrierFilter_${filterBy}']`; - this.filterSearchButton = '#submitFilterButtoncarrier'; - this.filterResetButton = 'button[name=\'submitResetcarrier\']'; + this.filterRow = `${this.gridTable} tr.column-filters`; + this.filterColumn = (filterBy: string) => `${this.filterRow} td[data-column-id="${filterBy}"] input, ` + + `${this.filterRow} td[data-column-id="${filterBy}"] select`; + this.filterSearchButton = 'button.grid-search-button[name="carrier[actions][search]"]'; + this.filterResetButton = 'button.js-reset-search[name="carrier[actions][reset]"]'; // Table body selectors this.tableBody = `${this.gridTable} tbody`; @@ -140,47 +142,47 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { this.tableBodyColumnNth = (column: number) => `${this.tableBodyRows} td:nth-child(${column})`; // Columns selectors - this.tableColumnId = (row: number) => `${this.tableBodyColumn(row)}:nth-child(2)`; - this.tableColumnName = (row: number) => `${this.tableBodyColumn(row)}:nth-child(3)`; - this.tableColumnDelay = (row: number) => `${this.tableBodyColumn(row)}:nth-child(5)`; - this.tableColumnActive = (row: number) => `${this.tableBodyColumn(row)}:nth-child(6) a`; - this.enableColumnValidIcon = (row: number) => `${this.tableColumnActive(row)} i.icon-check`; - this.tableColumnIsFree = (row: number) => `${this.tableBodyColumn(row)}:nth-child(7) a`; - this.tableColumnIsFreeIcon = (row: number) => `${this.tableColumnIsFree(row)} i.icon-check`; - this.tableColumnPosition = (row: number) => `${this.tableBodyColumn(row)}:nth-child(8)`; + this.tableColumnHandle = (row: number) => `${this.tableBodyColumn(row)}.column-position_handle .position-drag-handle`; + this.tableColumnId = (row: number) => `${this.tableBodyColumn(row)}.column-id_carrier`; + this.tableColumnName = (row: number) => `${this.tableBodyColumn(row)}.column-name`; + this.tableColumnDelay = (row: number) => `${this.tableBodyColumn(row)}.column-delay`; + this.tableColumnActive = (row: number) => `${this.tableBodyColumn(row)}.column-active`; + this.enableColumnValidIcon = (row: number) => `${this.tableColumnActive(row)} .ps-switch`; + this.tableColumnIsFree = (row: number) => `${this.tableBodyColumn(row)}.column-is_free`; + this.tableColumnIsFreeIcon = (row: number) => `${this.tableColumnIsFree(row)} .ps-switch`; + this.tableColumnPosition = (row: number) => `${this.tableBodyColumn(row)}.column-position`; // Row actions selectors - this.tableColumnActions = (row: number) => `${this.tableBodyColumn(row)} .btn-group-action`; - this.tableColumnActionsEditLink = (row: number) => `${this.tableColumnActions(row)} a.edit`; - this.tableColumnActionsToggleButton = (row: number) => `${this.tableColumnActions(row)} button.dropdown-toggle`; + this.tableColumnActions = (row: number) => `${this.tableBodyColumn(row)}.column-actions`; + this.tableColumnActionsEditLink = (row: number) => `${this.tableColumnActions(row)} a.grid-edit-row-link`; + this.tableColumnActionsToggleButton = (row: number) => `${this.tableColumnActions(row)} a.dropdown-toggle`; this.tableColumnActionsDropdownMenu = (row: number) => `${this.tableColumnActions(row)} .dropdown-menu`; - this.tableColumnActionsDeleteLink = (row: number) => `${this.tableColumnActionsDropdownMenu(row)} a.delete`; + this.tableColumnActionsDeleteLink = (row: number) => `${this.tableColumnActionsDropdownMenu(row)} .grid-delete-row-link`; // Sort Selectors this.tableHead = `${this.gridTable} thead`; - this.sortColumnDiv = (column: number) => `${this.tableHead} th:nth-child(${column})`; - this.sortColumnSpanButton = (column: number) => `${this.sortColumnDiv(column)} span.ps-sort`; + this.sortColumnDiv = (column: string) => `${this.tableHead} th div[data-sort-col-name="${column}"]`; + this.sortColumnSpanButton = (column: string) => `${this.sortColumnDiv(column)} span.ps-sort`; // Pagination selectors - this.paginationActiveLabel = `${this.gridForm} ul.pagination.pull-right li.active a`; - this.paginationDiv = `${this.gridForm} .pagination`; - this.paginationDropdownButton = `${this.paginationDiv} .dropdown-toggle`; - this.paginationItems = (number: number) => `${this.gridForm} .dropdown-menu a[data-items='${number}']`; - this.paginationPreviousLink = `${this.gridForm} .icon-angle-left`; - this.paginationNextLink = `${this.gridForm} .icon-angle-right`; + this.paginationLimitSelect = '#paginator_select_page_limit'; + this.paginationActiveLabel = 'div.pagination-block .col-form-label'; + this.paginationNextLink = 'div.pagination-block [data-role=next-page-link]'; + this.paginationPreviousLink = 'div.pagination-block [data-role=previous-page-link]'; // Confirmation modal - this.deleteModalButtonYes = '#popup_ok'; + this.deleteModalButtonYes = '#carrier-grid-confirm-modal button.btn-confirm-submit'; // Bulk actions selectors - this.bulkActionBlock = 'div.bulk-actions'; - this.bulkActionMenuButton = '#bulk_action_menu_carrier'; - this.bulkActionDropdownMenu = `${this.bulkActionBlock} ul.dropdown-menu`; - this.selectAllLink = `${this.bulkActionDropdownMenu} li:nth-child(1)`; - this.unselectAllLink = `${this.bulkActionDropdownMenu} li:nth-child(2)`; - this.bulkEnableLink = `${this.bulkActionDropdownMenu} li:nth-child(4)`; - this.bulkDisableLink = `${this.bulkActionDropdownMenu} li:nth-child(5)`; - this.bulkDeleteLink = `${this.bulkActionDropdownMenu} li:nth-child(7)`; + this.bulkActionBlock = '#carrier_grid'; + this.bulkActionMenuButton = `${this.bulkActionBlock} button.js-bulk-actions-btn`; + this.bulkActionDropdownMenu = `${this.bulkActionBlock} .dropdown-menu`; + this.selectAllLink = `${this.bulkActionBlock} tr.column-filters .grid_bulk_action_select_all`; + this.bulkEnableLink = `${this.bulkActionDropdownMenu} #carrier_grid_bulk_action_enable_selection`; + this.bulkDisableLink = `${this.bulkActionDropdownMenu} #carrier_grid_bulk_action_disable_selection`; + this.bulkDeleteLink = `${this.bulkActionDropdownMenu} #carrier_grid_bulk_action_delete_selection`; + this.bulkDeleteConfirmDeleteModal = '#carrier-grid-confirm-modal'; + this.bulkDeleteConfirmDeleteButton = `${this.bulkDeleteConfirmDeleteModal} button.btn-confirm-submit`; } /* @@ -237,8 +239,6 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @return {Promise} */ async filterTable(page: Page, filterType: string, filterBy: string, value: string): Promise { - const currentUrl: string = page.url(); - switch (filterType) { case 'input': await this.setValue(page, this.filterColumn(filterBy), value); @@ -246,10 +246,8 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { break; case 'select': - await Promise.all([ - page.waitForURL((url: URL): boolean => url.toString() !== currentUrl, {waitUntil: 'networkidle'}), - this.selectByVisibleText(page, this.filterColumn(filterBy), value === '1' ? 'Yes' : 'No'), - ]); + await this.selectByVisibleText(page, this.filterColumn(filterBy), value === '1' ? 'Yes' : 'No'); + await this.clickAndWaitForURL(page, this.filterSearchButton); break; default: @@ -309,7 +307,11 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { } if ((columnName === 'active') || (columnName === 'is_free')) { - return this.getAttributeContent(page, columnSelector, 'title'); + return (await this.getAttributeContent( + page, + `${columnSelector} input[checked]`, + 'value', + ) === '1' ? 'Enabled' : 'Disabled'); } return this.getTextContent(page, columnSelector); } @@ -332,7 +334,7 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { await this.clickAndWaitForURL(page, this.deleteModalButtonYes); // Get successful message - return this.getAlertSuccessBlockContent(page); + return this.getAlertSuccessBlockParagraphContent(page); } // Sort methods @@ -362,27 +364,38 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @return {Promise} */ async sortTable(page: Page, sortBy: string, sortDirection: string): Promise { - let columnSelector; + let columnSelector: string; + let sortColumnSpanButton: string; switch (sortBy) { case 'id_carrier': - columnSelector = this.sortColumnDiv(2); + columnSelector = this.sortColumnDiv('id_carrier'); + sortColumnSpanButton = this.sortColumnSpanButton(sortBy); break; case 'name': - columnSelector = this.sortColumnDiv(3); + columnSelector = this.sortColumnDiv('name'); + sortColumnSpanButton = this.sortColumnSpanButton(sortBy); break; case 'a!position': - columnSelector = this.sortColumnDiv(8); + columnSelector = this.sortColumnDiv('position'); + sortColumnSpanButton = this.sortColumnSpanButton('position'); break; default: throw new Error(`Column ${sortBy} was not found`); } - const sortColumnButton = `${columnSelector} i.icon-caret-${sortDirection}`; - await this.clickAndWaitForURL(page, sortColumnButton); + const sortColumnDiv = `${columnSelector}[data-sort-direction="${sortDirection}"]`; + + let i: number = 0; + while (await this.elementNotVisible(page, sortColumnDiv, 2000) && i < 2) { + await this.clickAndWaitForURL(page, sortColumnSpanButton); + i += 1; + } + + await this.waitForVisibleSelector(page, sortColumnDiv, 20000); } /* Pagination methods */ @@ -391,8 +404,15 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @param page {Page} Browser tab * @return {Promise} */ - getPaginationLabel(page: Page): Promise { - return this.getTextContent(page, this.paginationActiveLabel); + async getPaginationLabel(page: Page): Promise { + const label: string = await this.getTextContent(page, this.paginationActiveLabel); + const regexMatch: RegExpMatchArray | null = label.match(/\(page\s+(\d+)\s+\//); + + if (regexMatch === null) { + return ''; + } + + return regexMatch[1]; } /** @@ -402,8 +422,12 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @returns {Promise} */ async selectPaginationLimit(page: Page, number: number): Promise { - await this.waitForSelectorAndClick(page, this.paginationDropdownButton); - await this.waitForSelectorAndClick(page, this.paginationItems(number)); + const currentUrl: string = page.url(); + await Promise.all([ + this.selectByVisibleText(page, this.paginationLimitSelect, number), + page.waitForURL((url: URL): boolean => url.toString() !== currentUrl, {waitUntil: 'networkidle'}), + ]); + return this.getPaginationLabel(page); } @@ -434,22 +458,23 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @return {Promise} */ async bulkDeleteCarriers(page: Page): Promise { - // To confirm bulk delete action with dialog - await this.dialogListener(page, true); - - // Select all rows + // Click on Select All await this.bulkSetSelection(page, true); - - // Perform delete + // Click on Button Bulk actions await Promise.all([ page.locator(this.bulkActionMenuButton).click(), - this.waitForVisibleSelector(page, this.bulkDeleteLink), + this.waitForVisibleSelector(page, `${this.bulkActionMenuButton}[aria-expanded='true']`), ]); - await this.clickAndWaitForURL(page, this.bulkDeleteLink); + // Click on delete and wait for modal + await Promise.all([ + page.locator(this.bulkDeleteLink).click(), + this.waitForVisibleSelector(page, `${this.bulkDeleteConfirmDeleteModal}.show`), + ]); + + await this.clickAndWaitForURL(page, this.bulkDeleteConfirmDeleteButton); - // Return successful message - return this.getAlertSuccessBlockContent(page); + return this.getAlertSuccessBlockParagraphContent(page); } /** @@ -459,17 +484,16 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @returns {Promise} */ async bulkSetSelection(page: Page, status: boolean): Promise { - const selectorAll: string = status ? this.selectAllLink : this.unselectAllLink; - - await Promise.all([ - page.locator(this.bulkActionMenuButton).click(), - this.waitForVisibleSelector(page, selectorAll), - ]); + let i: number = 0; - await Promise.all([ - page.locator(selectorAll).click(), - this.waitForHiddenSelector(page, selectorAll), - ]); + while (await this.elementNotVisible( + page, + `${this.bulkActionMenuButton}${status ? ':not([disabled])' : '[disabled]'}`, + 2000, + ) && i < 2) { + await page.locator(this.selectAllLink).evaluate((el: HTMLElement) => el.click()); + i += 1; + } } /** @@ -503,8 +527,7 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { await this.clickAndWaitForURL(page, this.bulkDisableLink); } - // Return successful message - return this.getTextContent(page, this.alertSuccessBlock); + return this.getAlertSuccessBlockParagraphContent(page); } /** @@ -513,8 +536,16 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @param row {number} Row index in the table * @returns {Promise} */ - async getStatus(page: Page, row: number = 1): Promise { - return this.elementVisible(page, this.enableColumnValidIcon(row), 100); + async getStatus(page: Page, row: number): Promise { + // Get value of the check input + const inputValue = await this.getAttributeContent( + page, + `${this.enableColumnValidIcon(row)} input:checked`, + 'value', + ); + + // Return status=false if value='0' and true otherwise + return (inputValue !== '0'); } /** @@ -524,7 +555,15 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @returns {Promise} */ async isFreeShipping(page: Page, row: number = 1): Promise { - return this.elementVisible(page, this.tableColumnIsFreeIcon(row), 100); + // Get value of the check input + const inputValue = await this.getAttributeContent( + page, + `${this.tableColumnIsFreeIcon(row)} input:checked`, + 'value', + ); + + // Return status=false if value='0' and true otherwise + return (inputValue !== '0'); } /** @@ -553,6 +592,8 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @return {Promise} */ async setFreeShippingStatus(page: Page, row: number = 1, valueWanted: boolean = true): Promise { + await this.waitForVisibleSelector(page, this.tableColumnIsFree(row), 2000); + if (await this.isFreeShipping(page, row) !== valueWanted) { await page.locator(this.tableColumnIsFree(row)).click(); return true; @@ -569,14 +610,11 @@ class BOCarriersPage extends BOBasePage implements BOCarriersPageInterface { * @return {Promise} */ async changePosition(page: Page, actualPosition: number, newPosition: number): Promise { - await this.dragAndDrop( - page, - this.tableColumnPosition(actualPosition), - this.tableColumnPosition(newPosition), - ); + await this.dragAndDropSlowly(page, this.tableColumnHandle(actualPosition), this.tableBodyRow(newPosition)); - return this.getGrowlMessageContent(page); + return this.getAlertSuccessBlockParagraphContent(page); } } -module.exports = new BOCarriersPage(); +const boCarriersPage = new BOCarriersPage(); +export {boCarriersPage, BOCarriersPage};