diff --git a/.changeset/calm-trainers-fly.md b/.changeset/calm-trainers-fly.md new file mode 100644 index 00000000000..a2a1e57adbf --- /dev/null +++ b/.changeset/calm-trainers-fly.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Update variant information in existing product diff --git a/playwright.config.ts b/playwright.config.ts index d4540f0819f..5447b2c0768 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,10 +8,11 @@ export default defineConfig({ testDir: "playwright/tests", fullyParallel: true, forbidOnly: !!process.env.CI, + // TODO hardcoded values should be extracted to ENVs retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 2 : undefined, reporter: process.env.CI ? "blob" : "html", - timeout: 60000, + timeout: process.env.CI ? 60000 : 10000, // webServer: { // command: "npm run dev", // url: "http://localhost:9000/", diff --git a/playwright/data/e2eTestData.ts b/playwright/data/e2eTestData.ts index cd4a64fa0e5..44a3437a8eb 100644 --- a/playwright/data/e2eTestData.ts +++ b/playwright/data/e2eTestData.ts @@ -26,6 +26,13 @@ export const PRODUCTS = { id: "UHJvZHVjdDo3MzM%3D", info: "Product that contains single variant", }, + productWithVariantWhichWillBeUpdated: { + id: "UHJvZHVjdDo3NjU%3D", + name: "product with variant which will be updated", + variantId: "UHJvZHVjdFZhcmlhbnQ6MTIzMg%3D%3D", + variantName: "update variant", + info: "Product with variant which will be updated", + }, productWithOneVariantToBeDeletedFromDetails: { name: "beer to be deleted", id: "UHJvZHVjdDo3NTc%3D", diff --git a/playwright/pages/dialogs/channelSelectDialog.ts b/playwright/pages/dialogs/channelSelectDialog.ts index 13999dfe750..3675e0b4ccc 100644 --- a/playwright/pages/dialogs/channelSelectDialog.ts +++ b/playwright/pages/dialogs/channelSelectDialog.ts @@ -24,6 +24,12 @@ export class ChannelSelectDialog { .locator(this.displayedChannelsCheckboxes) .click(); } + async selectLastChannel() { + await this.displayedChannels + .last() + .locator(this.displayedChannelsCheckboxes) + .click(); + } async clickConfirmButton() { await this.confirmButton.first().click(); } diff --git a/playwright/pages/productPage.ts b/playwright/pages/productPage.ts index e96c88aa9fe..3063392e7c9 100644 --- a/playwright/pages/productPage.ts +++ b/playwright/pages/productPage.ts @@ -56,7 +56,8 @@ export class ProductPage { readonly addWarehouseButton = page.getByTestId("add-warehouse"), readonly stockInput = page.getByTestId("stock-input"), readonly productImage = page.getByTestId("product-image"), - readonly uploadImageButton = page.getByTestId("button-upload-image"), + readonly uploadProductImageButton = page.getByTestId("button-upload-image"), + readonly chooseMediaVariantButton = page.getByTestId("choose-media-button"), readonly uploadSavedImagesButton = page.getByTestId("upload-images"), readonly uploadMediaUrlButton = page.getByTestId("upload-media-url"), readonly saveUploadUrlButton = page.getByTestId("upload-url-button"), @@ -113,11 +114,14 @@ export class ProductPage { async clickCogShowMoreButtonButton() { await this.cogShowMoreButtonButton.click(); } - async clickUploadImagesButtonButton() { + async clickUploadImagesButton() { await this.uploadSavedImagesButton.click(); } async clickUploadMediaButton() { - await this.uploadImageButton.click(); + await this.uploadProductImageButton.click(); + } + async clickChooseMediaVariantButton() { + await this.chooseMediaVariantButton.click(); } async clickBulkDeleteButton() { await this.bulkDeleteButton.click(); @@ -203,7 +207,7 @@ export class ProductPage { async uploadProductImage(fileName: string) { const fileChooserPromise = this.page.waitForEvent("filechooser"); - await this.clickUploadImagesButtonButton(); + await this.clickUploadImagesButton(); const fileChooser = await fileChooserPromise; await fileChooser.setFiles(path.join("playwright/data/images/", fileName)); await this.page.waitForLoadState("domcontentloaded"); diff --git a/playwright/pages/variantsPage.ts b/playwright/pages/variantsPage.ts index 8df122da8b0..19f0628b46a 100644 --- a/playwright/pages/variantsPage.ts +++ b/playwright/pages/variantsPage.ts @@ -1,3 +1,4 @@ +import { URL_LIST } from "@data/url"; import type { Page } from "@playwright/test"; import { BasePage } from "./basePage"; @@ -46,30 +47,36 @@ export class VariantsPage { } async typeVariantName(variantName = "XXL beverage") { + await this.variantNameInput.clear(); await this.variantNameInput.fill(variantName); } async typeShippingWeight(weight = "150") { + await this.shippingWeightInput.clear(); await this.shippingWeightInput.fill(weight); } async typeCheckoutLimit(checkoutLimit = "10") { + await this.checkoutLimitInput.clear(); await this.checkoutLimitInput.fill(checkoutLimit); } async typeSellingPriceInChannel( channelName: string, sellingPriceValue = "99", ) { - await this.page + const sellingPriceInput = await this.page .locator(`[data-test-id="Channel-${channelName}"]`) .locator(this.priceFieldInput) - .first() - .fill(sellingPriceValue); + .first(); + await sellingPriceInput.clear(); + await sellingPriceInput.fill(sellingPriceValue); } async typeCostPriceInChannel(channelName: string, costPriceValue = "10") { - await this.page + const costPriceInput = await this.page .locator(`[data-test-id="Channel-${channelName}"]`) .locator(this.priceFieldInput) - .last() - .fill(costPriceValue); + .last(); + + await costPriceInput.clear(); + await costPriceInput.fill(costPriceValue); } async clickMageChannelsButton() { @@ -83,6 +90,7 @@ export class VariantsPage { } async typeSku(sku = "sku dummy e2e") { + await this.skuTextField.clear(); await this.skuTextField.fill(sku); } async clickAssignWarehouseButton() { @@ -98,6 +106,11 @@ export class VariantsPage { await this.attributeSelector.click(); await this.attributeOption.first().click(); } + async selectLastAttributeValue() { + await this.attributeSelector.locator("input").clear(); + await this.attributeSelector.click(); + await this.attributeOption.last().click(); + } async selectWarehouse(warehouse = "Oceania") { await this.clickAssignWarehouseButton(); await this.warehouseOption.locator(`text=${warehouse}`).click(); @@ -112,4 +125,14 @@ export class VariantsPage { async addAllMetaData() { await this.metadataSeoPage.expandAndAddAllMetadata(); } + + async gotoExistingVariantPage(productId: string, variantId: string) { + console.log( + `Navigating to existing variant: ${URL_LIST.products}${productId}/${URL_LIST.variant}${variantId}`, + ); + await this.page.goto( + `${URL_LIST.products}${productId}/${URL_LIST.variant}${variantId}`, + ); + await this.variantNameInput.waitFor({ state: "visible" }); + } } diff --git a/playwright/tests/product.spec.ts b/playwright/tests/product.spec.ts index 66bfcc18b05..4beb6b57cc9 100644 --- a/playwright/tests/product.spec.ts +++ b/playwright/tests/product.spec.ts @@ -277,3 +277,42 @@ test("TC: SALEOR_59 As an admin I should be able to filter products by channel o `Product: ${PRODUCTS.productAvailableOnlyInPlnChannel.name} should be visible on grid table`, ).toContainText(PRODUCTS.productAvailableOnlyInPlnChannel.name); }); +test("TC: SALEOR_60 As an admin I should be able update existing variant @basic-regression @product @e2e", async ({ + page, +}) => { + const variantName = `TC: SALEOR_60 - variant name - ${new Date().toISOString()}`; + const sku = `SALEOR_60-sku-${new Date().toISOString()}`; + + const productPage = new ProductPage(page); + const variantsPage = new VariantsPage(page); + await variantsPage.gotoExistingVariantPage( + PRODUCTS.productWithVariantWhichWillBeUpdated.id, + PRODUCTS.productWithVariantWhichWillBeUpdated.variantId, + ); + await variantsPage.typeVariantName(variantName); + await variantsPage.clickMageChannelsButton(); + await variantsPage.channelSelectDialog.clickAllChannelsCheckbox(); + await variantsPage.channelSelectDialog.selectLastChannel(); + await variantsPage.channelSelectDialog.clickConfirmButton(); + await variantsPage.selectLastAttributeValue(); + await variantsPage.typeCheckoutLimit("50"); + await variantsPage.typeShippingWeight("1000"); + await variantsPage.typeSellingPriceInChannel("USD", "120"); + await variantsPage.typeCostPriceInChannel("USD", "100"); + await variantsPage.typeSku(sku); + await variantsPage.clickSaveVariantButton(); + + await variantsPage.expectSuccessBanner(); + await expect( + variantsPage.variantsList.locator(variantsPage.variantsNames, { + hasText: variantName, + }), + ).toBeVisible(); + + await variantsPage.selectWarehouse("Africa"); + await variantsPage.typeQuantityInStock("Africa", "5000"); + await variantsPage.clickSaveVariantButton(); + + await variantsPage.expectSuccessBanner(); + await productPage.productImage.waitFor({ state: "visible" }); +});