diff --git a/.changeset/nervous-mails-beg.md b/.changeset/nervous-mails-beg.md new file mode 100644 index 00000000000..a19cb4ff5ac --- /dev/null +++ b/.changeset/nervous-mails-beg.md @@ -0,0 +1,5 @@ +--- +"saleor-dashboard": minor +--- + +Collections tests: Create collection; Edit collection: assign product; Bulk delete collections diff --git a/playwright/data/e2eTestData.ts b/playwright/data/e2eTestData.ts index 8ea14a88961..a2f542a5d0e 100644 --- a/playwright/data/e2eTestData.ts +++ b/playwright/data/e2eTestData.ts @@ -58,6 +58,15 @@ export const CATEGORIES = { ], }, }; +export const COLLECTIONS = { + collectionToBeUpdated: { + id: "Q29sbGVjdGlvbjoxNjY%3D", + name: "Collection to be updated", + }, + collectionsToBeBulkDeleted: { + names: ["Collection to be deleted 1/2", "Collection to be deleted 2/2"], + }, +}; export const CHANNELS = { channelToBeEditedSettings: { id: "Q2hhbm5lbDoyMzkx", diff --git a/playwright/pages/collectionsPage.ts b/playwright/pages/collectionsPage.ts index ad6785ac056..3b24cde0286 100644 --- a/playwright/pages/collectionsPage.ts +++ b/playwright/pages/collectionsPage.ts @@ -1,12 +1,89 @@ +import path from "path"; + +import { URL_LIST } from "@data/url"; +import { AssignSpecificProductsDialog } from "@dialogs/assignSpecificProductsDialog"; +import { MetadataSeoPage } from "@pageElements/metadataSeoPage"; +import { RightSideDetailsPage } from "@pageElements/rightSideDetailsSection"; +import { BasePage } from "@pages/basePage"; import type { Page } from "@playwright/test"; -export class CollectionsPage { +import { DeleteDialog } from "./dialogs/deleteDialog"; + +export class CollectionsPage extends BasePage { readonly page: Page; + readonly metadataSeoPage: MetadataSeoPage; + readonly rightSideDetailsPage: RightSideDetailsPage; + readonly assignSpecificProductsDialog: AssignSpecificProductsDialog; + readonly deleteCollectionDialog: DeleteDialog; constructor( page: Page, readonly createCollectionButton = page.getByTestId("create-collection"), + readonly saveButton = page.getByTestId("button-bar-confirm"), + readonly bulkDeleteButton = page.getByTestId("bulk-delete-button"), + readonly assignedSpecificProductRow = page.getByTestId( + "assign-product-table-row", + ), + readonly assignProductButton = page.getByTestId("add-product"), + readonly collectionImages = page.getByTestId("product-image"), + readonly uploadImageButton = page.getByTestId("upload-image-button"), + readonly collectionNameInput = page + .getByTestId("collection-name-input") + .locator("input"), + readonly collectionDescriptionEditor = page.getByTestId( + "rich-text-editor-description", + ), + readonly descriptionLoader = page.locator(".codex-editor__loader"), ) { + super(page); this.page = page; + this.metadataSeoPage = new MetadataSeoPage(page); + this.rightSideDetailsPage = new RightSideDetailsPage(page); + this.deleteCollectionDialog = new DeleteDialog(page); + this.assignSpecificProductsDialog = new AssignSpecificProductsDialog(page); + } + + async clickCreateCollectionButton() { + await this.createCollectionButton.click(); + } + async clickBulkDeleteButton() { + await this.bulkDeleteButton.click(); + } + async clickAssignProductButton() { + await this.assignProductButton.click(); + } + async clickSaveButton() { + await this.saveButton.click(); + } + async clickUploadImageButton() { + await this.uploadImageButton.click(); + } + + async gotoCollectionsListView() { + await this.page.goto(URL_LIST.collections); + } + async gotoExistingCollectionView(collectionId: string) { + const collectionUrl = URL_LIST.collections + collectionId; + await console.log( + "Navigating to existing collection url: " + collectionUrl, + ); + await this.page.goto(collectionUrl); + } + async typeCollectionName(collectionName: string) { + await this.collectionNameInput.fill(collectionName); + } + async typeCollectionDescription(collectionDescription: string) { + await this.descriptionLoader.waitFor({ state: "hidden" }); + await this.collectionDescriptionEditor + .locator('[contenteditable="true"]') + .fill(collectionDescription); + } + + async uploadCollectionImage(fileName: string) { + const fileChooserPromise = this.page.waitForEvent("filechooser"); + await this.clickUploadImageButton(); + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles(path.join("playwright/data/images/", fileName)); + await this.page.waitForLoadState("domcontentloaded"); } } diff --git a/playwright/pages/dialogs/assignSpecificProductsDialog.ts b/playwright/pages/dialogs/assignSpecificProductsDialog.ts index ea7912453ae..70ad9f7d5fa 100644 --- a/playwright/pages/dialogs/assignSpecificProductsDialog.ts +++ b/playwright/pages/dialogs/assignSpecificProductsDialog.ts @@ -22,5 +22,6 @@ export class AssignSpecificProductsDialog { .getByRole("checkbox"); await specificProductCheckbox.click(); await this.clickAssignAndSaveButton(); + await this.assignAndSaveButton.waitFor({ state: "hidden" }); } } diff --git a/playwright/tests/collections.spec.ts b/playwright/tests/collections.spec.ts new file mode 100644 index 00000000000..76e025d5926 --- /dev/null +++ b/playwright/tests/collections.spec.ts @@ -0,0 +1,68 @@ +import { COLLECTIONS } from "@data/e2eTestData"; +import { CollectionsPage } from "@pages/collectionsPage"; +import { expect, test } from "@playwright/test"; + +test.use({ storageState: "playwright/.auth/admin.json" }); + +let collectionsPage: CollectionsPage; + +test.beforeEach(({ page }) => { + collectionsPage = new CollectionsPage(page); +}); + +test("TC: SALEOR_112 Create collection @collections @e2e", async () => { + await collectionsPage.gotoCollectionsListView(); + await collectionsPage.clickCreateCollectionButton(); + await collectionsPage.typeCollectionName("Saleor automation collection"); + await collectionsPage.typeCollectionDescription("Best collection ever"); + await collectionsPage.uploadCollectionImage("beer.avif"); + await collectionsPage.collectionImages.first().waitFor({ state: "visible" }); + + expect(await collectionsPage.collectionImages.count()).toEqual(1); + + await collectionsPage.metadataSeoPage.fillSeoSection(); + await collectionsPage.metadataSeoPage.expandAndAddAllMetadata(); + await collectionsPage.rightSideDetailsPage.selectOneChannelAsAvailableWhenMoreSelected( + "Channel-PLN", + ); + await collectionsPage.clickSaveButton(); + await collectionsPage.expectSuccessBanner(); +}); + +test("TC: SALEOR_113 Edit collection: assign product @collections @e2e", async () => { + const productToBeAssigned = "Bean Juice"; + await collectionsPage.gotoExistingCollectionView( + COLLECTIONS.collectionToBeUpdated.id, + ); + await collectionsPage.clickAssignProductButton(); + await collectionsPage.assignSpecificProductsDialog.assignSpecificProductsByNameAndSave( + productToBeAssigned, + ); + await collectionsPage.expectSuccessBanner(); + + await expect( + collectionsPage.assignedSpecificProductRow, + `Assigned product: ${productToBeAssigned} should be visible`, + ).toContainText(productToBeAssigned); + expect( + await collectionsPage.assignedSpecificProductRow.count(), + `Only 1 category should be visible in table`, + ).toEqual(1); +}); + +test("TC: SALEOR_114 Bulk delete collections @collections @e2e", async () => { + await collectionsPage.gotoCollectionsListView(); + await collectionsPage.checkListRowsBasedOnContainingText( + COLLECTIONS.collectionsToBeBulkDeleted.names, + ); + await collectionsPage.clickBulkDeleteButton(); + await collectionsPage.deleteCollectionDialog.clickDeleteButton(); + await collectionsPage.waitForGrid(); + + expect( + await collectionsPage.findRowIndexBasedOnText( + COLLECTIONS.collectionsToBeBulkDeleted.names, + ), + `Given collections: ${COLLECTIONS.collectionsToBeBulkDeleted.names} should be deleted from the list`, + ).toEqual([]); +}); diff --git a/src/collections/components/CollectionDetails/CollectionDetails.tsx b/src/collections/components/CollectionDetails/CollectionDetails.tsx index 97aceefa4aa..b274e2de701 100644 --- a/src/collections/components/CollectionDetails/CollectionDetails.tsx +++ b/src/collections/components/CollectionDetails/CollectionDetails.tsx @@ -40,6 +40,7 @@ const CollectionDetails: React.FC = ({ /> = props => { })} toolbar={ <> - = props => { return (