Skip to content

Commit

Permalink
Export products as csv file test (#4521)
Browse files Browse the repository at this point in the history
* test bulk delete on product list

* update single product test

* test title fix

* moved assertions to the end of tests

* better selector for grid text

* exclude rows eval from loop

* print navigation url

* Export products as csv test

* removed unused log
  • Loading branch information
wojteknowacki committed Dec 14, 2023
1 parent c7f36a6 commit 876d89b
Show file tree
Hide file tree
Showing 9 changed files with 163 additions and 48 deletions.
5 changes: 5 additions & 0 deletions .changeset/gentle-glasses-provide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": minor
---

Export products as csv test
82 changes: 59 additions & 23 deletions playwright/api/mailpit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,23 @@ export class MailpitService {
this.request = request;
}

async getLastEmails(getEmailsLimit = 100) {
async getLastEmails(getEmailsLimit = 50) {
let latestEmails: any;
await expect(async () => {
latestEmails = await this.request.get(
`${mailpitUrl}/api/v1/messages?limit=${getEmailsLimit}`,
);
expect(latestEmails.body()).not.toBeUndefined();
}).toPass({
intervals: [3_000, 3_000, 3_000],
timeout: 10_000,
});
await expect
.poll(
async () => {
latestEmails = await this.request.get(
`${mailpitUrl}/api/v1/messages?limit=${getEmailsLimit}`,
);
return latestEmails;
},
{
message: `Could not found last ${getEmailsLimit}`,
intervals: [2000, 3000, 5000, 5000],
timeout: 15000,
},
)
.not.toBeUndefined();
const latestEmailsJson = await latestEmails!.json();
return latestEmailsJson;
}
Expand All @@ -36,22 +42,52 @@ export class MailpitService {

async getEmailsForUser(userEmail: string) {
let userEmails: any[] = [];
await expect(async () => {
const emails = await this.getLastEmails();
userEmails = emails.messages.filter((mails: { To: any[] }) =>
mails.To.map(
(recipientObj: { Address: any }) => `${recipientObj.Address}`,
).includes(userEmail),
);
expect(userEmails.length).toBeGreaterThanOrEqual(1);
}).toPass({
intervals: [3_000, 3_000, 3_000],
timeout: 10_000,
});
await expect(userEmails).not.toEqual([]);
await expect
.poll(
async () => {
const emails = await this.getLastEmails();
userEmails = await emails.messages.filter((mails: { To: any[] }) =>
mails.To.map(
(recipientObj: { Address: any }) => `${recipientObj.Address}`,
).includes(userEmail),
);

return userEmails.length;
},
{
message: `User: ${userEmail} messages were not found`,
intervals: [2000, 3000, 5000, 5000],
timeout: 15000,
},
)
.toBeGreaterThanOrEqual(1);
return userEmails;
}
async checkDoesUserReceivedExportedData(
userEmail: string,
mailSubject: string,
) {
let confirmationMessageReceived: boolean = false;
await expect
.poll(
async () => {
let userEmails: any[] = await this.getEmailsForUser(userEmail);
for (const email of userEmails) {
if (email.Subject === mailSubject) {
confirmationMessageReceived = true;
break;
}
}
return confirmationMessageReceived;
},
{
message: `Message with subject: ${mailSubject} was not found`,
intervals: [2000, 3000, 5000, 5000],
timeout: 15000,
},
)
.toBe(true);
}

async generateResetPasswordUrl(userEmail: string) {
const tokenRegex = /token=([A-Za-z0-9]+(-[A-Za-z0-9]+)+)/;
Expand Down
1 change: 1 addition & 0 deletions playwright/data/commonLocators.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export const LOCATORS = {
successBanner: '[data-test-type="success"]',
errorBanner: '[data-test-type="error"]',
infoBanner: '[data-test-type="info"]',
dataGridTable: "[data-testid='data-grid-canvas']",
};
19 changes: 5 additions & 14 deletions playwright/pages/basePage.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { LOCATORS } from "@data/commonLocators";
import { URL_LIST } from "@data/url";
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";

Expand All @@ -15,22 +14,10 @@ export class BasePage {
.locator("textarea"),
readonly successBanner = page.locator(LOCATORS.successBanner),
readonly errorBanner = page.locator(LOCATORS.errorBanner),
readonly infoBanner = page.locator(LOCATORS.infoBanner),
) {
this.page = page;
}
async gotoCreateProductPage(productTypeId: string) {
await this.page.goto(
`${URL_LIST.products}${URL_LIST.productsAdd}${productTypeId}`,
);
await expect(this.pageHeader).toBeVisible({ timeout: 10000 });
}
async gotoExistingProductPage(productId: string) {
await console.log(
`Navigating to existing product: ${URL_LIST.products}${productId}`,
);
await this.page.goto(`${URL_LIST.products}${productId}`);
await expect(this.pageHeader).toBeVisible({ timeout: 10000 });
}
async expectGridToBeAttached() {
await expect(this.gridCanvas).toBeAttached({
timeout: 10000,
Expand All @@ -48,6 +35,10 @@ export class BasePage {
.waitFor({ state: "visible", timeout: 15000 });
await expect(this.errorBanner).not.toBeVisible();
}
async expectInfoBanner() {
await this.infoBanner.first().waitFor({ state: "visible", timeout: 15000 });
await expect(this.errorBanner).not.toBeVisible();
}

async getRandomInt(max: number) {
return Math.floor(Math.random() * (max + 1));
Expand Down
32 changes: 32 additions & 0 deletions playwright/pages/dialogs/exportProductsDialog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Page } from "@playwright/test";

export class ExportProductsDialog {
readonly page: Page;

constructor(
page: Page,
readonly channelsAccordion = page.getByTestId("channel-expand-button"),
readonly nextButton = page.getByTestId("next"),
readonly submitButton = page.getByTestId("submit"),
readonly exportSearchedProductsRadioButton = page.locator(
"input[value='FILTER']",
),
) {
this.page = page;
}
async clickChannelsAccordion() {
await this.channelsAccordion.click();
}
async clickNextButton() {
await this.nextButton.click();
}
async clickSubmitButton() {
await this.submitButton.click();
}
async clickExportSearchedProductsRadioButton() {
await this.exportSearchedProductsRadioButton.click();
}
async checkChannelCheckbox(channel = "PLN") {
await this.page.locator(`[name="Channel-${channel}"]`).click();
}
}
28 changes: 27 additions & 1 deletion playwright/pages/productPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import path from "path";

import { URL_LIST } from "@data/url";
import { ChannelSelectDialog } from "@pages/dialogs/channelSelectDialog";
import { ExportProductsDialog } from "@pages/dialogs/exportProductsDialog";
import { MetadataSeoPage } from "@pages/pageElements/metadataSeoPage";
import { RightSideDetailsPage } from "@pages/pageElements/rightSideDetailsSection";
import type { Page } from "@playwright/test";
import { expect, Page } from "@playwright/test";

import { BasePage } from "./basePage";
import { DeleteProductDialog } from "./dialogs/deleteProductDialog";
Expand All @@ -17,6 +18,7 @@ export class ProductPage {
readonly page: Page;

readonly metadataSeoPage: MetadataSeoPage;
readonly exportProductsDialog: ExportProductsDialog;
readonly rightSideDetailsPage: RightSideDetailsPage;
readonly basePage: BasePage;
readonly channelSelectDialog: ChannelSelectDialog;
Expand All @@ -29,6 +31,8 @@ export class ProductPage {
"product-available-in-channels-text",
),
readonly createProductButton = page.getByTestId("add-product"),
readonly cogShowMoreButtonButton = page.getByTestId("show-more-button"),
readonly exportButton = page.getByTestId("export"),
readonly bulkDeleteButton = page.getByTestId("bulk-delete-button"),
readonly deleteProductButton = page.getByTestId("button-bar-delete"),
readonly searchProducts = page.locator(
Expand Down Expand Up @@ -78,15 +82,37 @@ export class ProductPage {
) {
this.page = page;
this.basePage = new BasePage(page);
this.exportProductsDialog = new ExportProductsDialog(page);
this.deleteProductDialog = new DeleteProductDialog(page);
this.channelSelectDialog = new ChannelSelectDialog(page);
this.metadataSeoPage = new MetadataSeoPage(page);
this.rightSideDetailsPage = new RightSideDetailsPage(page);
}

async gotoCreateProductPage(productTypeId: string) {
await this.page.goto(
`${URL_LIST.products}${URL_LIST.productsAdd}${productTypeId}`,
);
await expect(this.basePage.pageHeader).toBeVisible({ timeout: 10000 });
}

async gotoExistingProductPage(productId: string) {
console.log(
`Navigating to existing product: ${URL_LIST.products}${productId}`,
);
await this.page.goto(`${URL_LIST.products}${productId}`);
await expect(this.basePage.pageHeader).toBeVisible({ timeout: 10000 });
}

async clickDeleteProductButton() {
await this.deleteProductButton.click();
}
async clickExportButton() {
await this.exportButton.click();
}
async clickCogShowMoreButtonButton() {
await this.cogShowMoreButtonButton.click();
}
async clickUploadImagesButtonButton() {
await this.uploadSavedImagesButton.click();
}
Expand Down
39 changes: 30 additions & 9 deletions playwright/tests/product.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { MailpitService } from "@api/mailpit";
import { PRODUCTS } from "@data/e2eTestData";
import { URL_LIST } from "@data/url";
import { BasePage } from "@pages/basePage";
Expand Down Expand Up @@ -32,7 +33,7 @@ test("TC: SALEOR_5 Create basic - single product type - product without variants
const basePage = new BasePage(page);
const productPage = new ProductPage(page);

await basePage.gotoCreateProductPage(PRODUCTS.singleProductType.id);
await productPage.gotoCreateProductPage(PRODUCTS.singleProductType.id);
await productPage.selectOneChannelAsAvailableWhenMoreSelected();
await productPage.typeNameDescAndRating();
await productPage.addSeo();
Expand All @@ -49,11 +50,10 @@ test("TC: SALEOR_26 Create basic info variant - via edit variant page @e2e @prod
page,
}) => {
const variantName = `TC: SALEOR_26 - variant name - ${new Date().toISOString()}`;
const basePage = new BasePage(page);
const productPage = new ProductPage(page);
const variantsPage = new VariantsPage(page);

await basePage.gotoExistingProductPage(PRODUCTS.productWithOneVariant.id);
await productPage.gotoExistingProductPage(PRODUCTS.productWithOneVariant.id);
await productPage.clickFirstEditVariantButton();
await variantsPage.clickAddVariantButton();
await variantsPage.typeVariantName(variantName);
Expand All @@ -75,11 +75,10 @@ test("TC: SALEOR_27 Create full info variant - via edit variant page @e2e @produ
page,
}) => {
const variantName = `TC: SALEOR_27 - variant name - ${new Date().toISOString()}`;
const basePage = new BasePage(page);
const productPage = new ProductPage(page);
const variantsPage = new VariantsPage(page);

await basePage.gotoExistingProductPage(PRODUCTS.productWithOneVariant.id);
await productPage.gotoExistingProductPage(PRODUCTS.productWithOneVariant.id);
await productPage.clickFirstEditVariantButton();
await variantsPage.clickAddVariantButton();
await variantsPage.typeVariantName(variantName);
Expand Down Expand Up @@ -133,7 +132,7 @@ test("TC: SALEOR_45 As an admin I should be able to delete a single products @ba
const basePage = new BasePage(page);
const productPage = new ProductPage(page);

await basePage.gotoExistingProductPage(
await productPage.gotoExistingProductPage(
PRODUCTS.productWithOneVariantToBeDeletedFromDetails.id,
);
await productPage.clickDeleteProductButton();
Expand All @@ -143,14 +142,13 @@ test("TC: SALEOR_45 As an admin I should be able to delete a single products @ba
PRODUCTS.productWithOneVariantToBeDeletedFromDetails.name,
);
});
test("TC: SALEOR_46 As an admin, I should be able to update a single product by uploading media, assigning channels, assigning tax, and adding a new variant @basic-regression @product @e2e", async ({
test("TC: SALEOR_46 As an admin, I should be able to update a product by uploading media, assigning channels, assigning tax, and adding a new variant @basic-regression @product @e2e", async ({
page,
}) => {
const newVariantName = "variant 2";
const basePage = new BasePage(page);
const productPage = new ProductPage(page);

await basePage.gotoExistingProductPage(
await productPage.gotoExistingProductPage(
PRODUCTS.singleProductTypeToBeUpdated.id,
);
await productPage.clickUploadMediaButton();
Expand Down Expand Up @@ -182,3 +180,26 @@ test("TC: SALEOR_46 As an admin, I should be able to update a single product by
);
expect(await productPage.productImage.count()).toEqual(1);
});

// blocked by bug https://github.com/saleor/saleor-dashboard/issues/4368
test.skip("TC: SALEOR_56 As an admin, I should be able to export products from single channel as CSV file @basic-regression @product @e2e", async ({
page,
request,
}) => {
const productPage = new ProductPage(page);
const mailpitService = new MailpitService(request);

await page.goto(URL_LIST.products);
await productPage.clickCogShowMoreButtonButton();
await productPage.clickExportButton();
await productPage.exportProductsDialog.clickChannelsAccordion();
await productPage.exportProductsDialog.checkChannelCheckbox("PLN");
await productPage.exportProductsDialog.clickNextButton();
await productPage.exportProductsDialog.clickExportSearchedProductsRadioButton();
await productPage.exportProductsDialog.clickSubmitButton();
await productPage.basePage.expectInfoBanner();
const userEmails = await mailpitService.checkDoesUserReceivedExportedData(
process.env.E2E_USER_NAME!,
"Your exported products data is ready",
);
});
4 changes: 3 additions & 1 deletion src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface AccordionProps {
initialExpand?: boolean;
quickPeek?: React.ReactNode;
title: string;
dataTestId?: string;
}

const AccordionItemId = "accordionItemId";
Expand All @@ -22,6 +23,7 @@ const Accordion: React.FC<AccordionProps> = ({
quickPeek,
title,
className,
dataTestId = "expand-icon",
}) => {
const [openedAccordionId, setOpendAccordionId] = useState<undefined | string>(
!!initialExpand ? AccordionItemId : undefined,
Expand All @@ -45,7 +47,7 @@ const Accordion: React.FC<AccordionProps> = ({
<Text paddingY={3} variant="body" size="small">
{title}
</Text>
<AccordionMacaw.TriggerButton dataTestId="expand-icon" />
<AccordionMacaw.TriggerButton dataTestId={dataTestId} />
</AccordionMacaw.Trigger>
<AccordionMacaw.Content>
<Divider />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ const ProductExportDialogInfo: React.FC<ProductExportDialogInfoProps> = ({
</Typography>
<div className={classes.scrollArea}>
<Accordion
dataTestId="channel-expand-button"
className={classes.accordion}
title={intl.formatMessage(sectionNames.channels)}
quickPeek={
Expand Down

0 comments on commit 876d89b

Please sign in to comment.