Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[E2E] Backporting auth fixes #5070

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-masks-lay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"saleor-dashboard": patch
---

You can now run E2E tests locally with no authentication issues
5 changes: 2 additions & 3 deletions playwright/api/basics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,11 @@ export class BasicApiService {
}
}
}`;

const loginResponse = await this.request.post(process.env.API_URI || "", {
const loginResponse = await this.request.post(process.env.API_URL || "", {
data: { query },
});
const loginResponseJson = await loginResponse.json();

return loginResponseJson as ApiResponse<TokenCreateResponse>;
return loginResponseJson;
}
}
10 changes: 6 additions & 4 deletions playwright/pages/appPageThirdparty.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,12 @@ export class AppPage extends BasePage {
this.deleteAppDialog = new DeleteDialog(page);
}

async goToExistingAppPage(appId: string) {
const appUrl = URL_LIST.apps + appId;
await this.page.goto(appUrl);
}
async goToExistingAppPage(appId: string) {
const appUrl = URL_LIST.apps + appId;

await this.page.goto(appUrl);
await this.pageHeader.waitFor({ state: "visible", timeout: 10000 });
}

async clickAppSettingsButton() {
await this.appSettingsButton.click();
Expand Down
1 change: 1 addition & 0 deletions playwright/pages/appsPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export class AppsPage extends BasePage {

async gotoAppsList() {
await this.page.goto(URL_LIST.apps);
await this.waitForDOMToFullyLoad();
}

async typeManifestUrl(manifestUrl: string) {
Expand Down
4 changes: 1 addition & 3 deletions playwright/pages/shippingRatesPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,7 @@ export class ShippingRatesPage extends BasePage {
await this.assignDialogProductRow.filter({ hasText: name }).waitFor({ state: "visible" });
await this.assignProductsDialog.selectProduct(name);
await expect(this.assignProductsDialog.assignAndSaveButton).toBeEnabled();
await this.waitForNetworkIdleAfterAction(() =>
this.assignProductsDialog.assignAndSaveButton.click(),
);
await this.assignProductsDialog.assignAndSaveButton.click();
await this.assignProductsDialog.assignAndSaveButton.waitFor({
state: "hidden",
timeout: 5000,
Expand Down
6 changes: 1 addition & 5 deletions playwright/tests/apps.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ test.skip("TC: SALEOR_119 User should be able to install and configure app from
page,
}) => {
await appsPage.gotoAppsList();
await appsPage.waitForDOMToFullyLoad();
await expect(appsPage.installExternalAppButton).toBeVisible();
await appsPage.installExternalAppButton.click();
await appsPage.typeManifestUrl("https://klaviyo.saleor.app/api/manifest");
Expand Down Expand Up @@ -49,10 +48,7 @@ test.skip("TC: SALEOR_119 User should be able to install and configure app from
});

test("TC: SALEOR_120 User should be able to delete thirdparty app @e2e", async () => {
await appPage.waitForNetworkIdleAfterAction(() =>
appPage.goToExistingAppPage(APPS.appToBeDeleted.id),
);
await appPage.pageHeader.waitFor({ state: "visible", timeout: 10000 });
await appPage.goToExistingAppPage(APPS.appToBeDeleted.id);
await expect(appPage.pageHeader).toContainText("Saleor QA App");
await appPage.deleteButton.click();
await appPage.deleteAppDialog.clickDeleteButton();
Expand Down
89 changes: 68 additions & 21 deletions playwright/tests/attributes.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AttributesPage } from "@pages/attributesPage";
import { ATTRIBUTES } from "@data/e2eTestData";
import { AttributesPage } from "@pages/attributesPage";
import { ConfigurationPage } from "@pages/configurationPage";
import { expect, test } from "@playwright/test";
import faker from "faker";
Expand All @@ -16,11 +16,20 @@ test.beforeEach(({ page }) => {

const SALEOR_124_uuid = faker.datatype.uuid();
const attributeClasses = ["PRODUCT_TYPE", "PAGE_TYPE"];

for (const attr of attributeClasses) {
for (const type of ATTRIBUTES.attributeTypesWithAbilityToAddValues.names) {
const uniqueSlug = `${attr}-${type}-${SALEOR_124_uuid}`;
test(`TC: SALEOR_124 User should be able to create ${attr} ${type} attribute with ability to add values, required, public @e2e @attributes`, async ({page}) => {
await page.context().storageState({ path: "./playwright/.auth/admin.json" });
const uniqueSlug = `${attr}-${type}-${SALEOR_124_uuid}`.replace(
/\s+/g,
"-",
);

test(`TC: SALEOR_124 User should be able to create ${attr} ${type} attribute with ability to add values, required, public @e2e @attributes`, async ({
page,
}) => {
await page
.context()
.storageState({ path: "./playwright/.auth/admin.json" });
await configurationPage.goToConfigurationView();
await configurationPage.openAttributes();
await attributesPage.clickCreateAttributeButton();
Expand All @@ -31,7 +40,9 @@ for (const attr of attributeClasses) {
await expect(attributesPage.attrValuesSection).toBeVisible();
await attributesPage.clickAssignAttributeValueButton();
await attributesPage.addValueDialog.typeAndSaveAttributeValue();
await attributesPage.waitForNetworkIdleAfterAction(() => attributesPage.clickSaveButton());
await attributesPage.waitForNetworkIdleAfterAction(() =>
attributesPage.clickSaveButton(),
);
await attributesPage.expectSuccessBanner();
await expect(await attributesPage.attributesRows.count()).toEqual(1);
await attributesPage.valueRequiredCheckbox.waitFor({
Expand All @@ -46,11 +57,20 @@ for (const attr of attributeClasses) {
}

const SALEOR_125_uuid = faker.datatype.uuid();

for (const attr of attributeClasses) {
for (const type of ATTRIBUTES.attributeTypesWithoutAbilityToAddValues.names) {
const uniqueSlug = `${attr}-${type}-${SALEOR_125_uuid}`;
test(`TC: SALEOR_125 User should be able to create ${attr} ${type} attribute without ability to add values, NOT required, private @e2e @attributes`, async ({page}) => {
await page.context().storageState({ path: "./playwright/.auth/admin.json" });
const uniqueSlug = `${attr}-${type}-${SALEOR_125_uuid}`.replace(
/\s+/g,
"-",
);

test(`TC: SALEOR_125 User should be able to create ${attr} ${type} attribute without ability to add values, NOT required, private @e2e @attributes`, async ({
page,
}) => {
await page
.context()
.storageState({ path: "./playwright/.auth/admin.json" });
await configurationPage.goToConfigurationView();
await configurationPage.openAttributes();
await attributesPage.waitForDOMToFullyLoad();
Expand All @@ -63,7 +83,9 @@ for (const attr of attributeClasses) {
await expect(attributesPage.assignAttributeValueButton).not.toBeVisible();
await attributesPage.clickValueRequiredCheckbox();
await attributesPage.changeAttributeVisibility();
await attributesPage.waitForNetworkIdleAfterAction(() => attributesPage.clickSaveButton());
await attributesPage.waitForNetworkIdleAfterAction(() =>
attributesPage.clickSaveButton(),
);
await attributesPage.expectSuccessBanner();
await attributesPage.valueRequiredCheckbox.waitFor({
state: "visible",
Expand All @@ -79,11 +101,20 @@ for (const attr of attributeClasses) {
}

const SALEOR_126_uuid = faker.datatype.uuid();

for (const attr of attributeClasses) {
for (const entity of ATTRIBUTES.attributeReferencesEntities.names) {
const uniqueSlug = `${attr}-${entity}-${SALEOR_126_uuid}`;
test(`TC: SALEOR_126 User should be able to create ${attr} References attribute for ${entity}, NOT required, public @e2e @attributes`, async ({page}) => {
await page.context().storageState({ path: "./playwright/.auth/admin.json" });
const uniqueSlug = `${attr}-${entity}-${SALEOR_126_uuid}`.replace(
/\s+/g,
"-",
);

test(`TC: SALEOR_126 User should be able to create ${attr} References attribute for ${entity}, NOT required, public @e2e @attributes`, async ({
page,
}) => {
await page
.context()
.storageState({ path: "./playwright/.auth/admin.json" });
await configurationPage.goToConfigurationView();
await configurationPage.openAttributes();
await attributesPage.waitForDOMToFullyLoad();
Expand All @@ -96,7 +127,9 @@ for (const attr of attributeClasses) {
await attributesPage.selectAttributeInputType("REFERENCE");
await attributesPage.selectAttributeEntityType(entity);
await attributesPage.clickValueRequiredCheckbox();
await attributesPage.waitForNetworkIdleAfterAction(() => attributesPage.clickSaveButton());
await attributesPage.waitForNetworkIdleAfterAction(() =>
attributesPage.clickSaveButton(),
);
await attributesPage.expectSuccessBanner();
await attributesPage.valueRequiredCheckbox.waitFor({
state: "visible",
Expand Down Expand Up @@ -129,6 +162,7 @@ const attributesWithValuesToBeUpdated = [
productAttrWithValues,
contentAttrWithValues,
];

for (const attribute of attributesWithValuesToBeUpdated) {
test(`TC: SALEOR_127 User should be able to update attribute values in existing ${attribute.name} attribute @e2e @attributes`, async () => {
await attributesPage.waitForNetworkIdleAfterAction(() =>
Expand All @@ -148,9 +182,13 @@ for (const attribute of attributesWithValuesToBeUpdated) {
`new value for ${attribute.name}`,
);
await attributesPage.expectSuccessBanner();
await attributesPage.waitForNetworkIdleAfterAction(() => attributesPage.clickSaveButton());
await attributesPage.waitForNetworkIdleAfterAction(() =>
attributesPage.clickSaveButton(),
);
await attributesPage.expectSuccessBanner();
await expect(attributesPage.attrValuesSection).not.toContainText(attribute.valueToBeDeleted);
await expect(attributesPage.attrValuesSection).not.toContainText(
attribute.valueToBeDeleted,
);
await expect(attributesPage.attrValuesSection).toContainText(
`updated value for ${attribute.name}`,
);
Expand All @@ -170,12 +208,18 @@ for (const attr of ATTRIBUTES.attributesToBeUpdated) {
await attributesPage.expandMetadataSection();
await attributesPage.metadataAddFieldButton.click();
await attributesPage.fillMetadataFields("new key", "new value");
await attributesPage.waitForNetworkIdleAfterAction(() => attributesPage.clickSaveButton());
await attributesPage.expectSuccessBanner();
await expect(attributesPage.attributeSelect.getByRole("button")).toHaveAttribute(
"aria-disabled",
"true",
await attributesPage.waitForNetworkIdleAfterAction(() =>
attributesPage.clickSaveButton(),
);
await attributesPage.expectSuccessBanner();
await attributesPage.expectElementIsHidden(attributesPage.successBanner);
await attributesPage.attributeSelect
.locator("div")
.getByRole("button")
.waitFor({ state: "visible" });
await expect(
attributesPage.attributeSelect.locator("div").getByRole("button"),
).toHaveAttribute("aria-disabled", "true");
await expect(attributesPage.metadataKeyInput).toHaveValue("new key");
await expect(attributesPage.metadataValueInput).toHaveValue("new value");
await expect(attributesPage.attributeDefaultLabelInput).toHaveValue(
Expand All @@ -193,6 +237,7 @@ const contentAttribute = {
name: ATTRIBUTES.contentAttributeToBeDeleted.name,
};
const attributesToBeDeleted = [productAttribute, contentAttribute];

for (const attribute of attributesToBeDeleted) {
test(`TC: SALEOR_129 Delete a single ${attribute.name} @e2e @attributes`, async () => {
await attributesPage.gotoExistingAttributePage(
Expand All @@ -214,7 +259,9 @@ for (const attribute of attributesToBeDeleted) {

test("TC: SALEOR_130 Bulk delete attributes @e2e @attributes", async () => {
await attributesPage.gotoListView();
await attributesPage.searchAndFindRowIndexes("e2e attribute to be bulk deleted");
await attributesPage.searchAndFindRowIndexes(
"e2e attribute to be bulk deleted",
);
await attributesPage.clickGridCell(0, 0);
await attributesPage.clickGridCell(0, 1);
await attributesPage.clickGridCell(0, 2);
Expand Down
118 changes: 82 additions & 36 deletions playwright/tests/auth.setup.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,98 @@
import { APIRequestContext, test as setup } from "@playwright/test";
import { BasicApiService } from "@api/basics";
import fs from 'fs';
import path from 'path';
import { USER_PERMISSION, UserPermissionType, permissions } from "@data/userPermissions";
import { permissions, USER_PERMISSION } from "@data/userPermissions";
import { APIRequestContext, expect, test as setup } from "@playwright/test";
import fs from "fs";
import path from "path";

setup.describe.configure({ mode: 'serial' });
setup.describe.configure({ mode: "serial" });

const authenticateAndSaveState = async (request: APIRequestContext, email: string, password: string, filePath: string) => {
const removeAuthFolder = () => {
const authDir = path.join(__dirname, "../.auth");

const basicApiService = new BasicApiService(request);
await basicApiService.logInUserViaApi({ email, password });
if (fs.existsSync(authDir)) {
fs.rmSync(authDir, { recursive: true });
console.log(".auth folder removed");
}
};

setup.beforeAll(() => {
removeAuthFolder();
});

const authenticateAndSaveState = async (
request: APIRequestContext,
email: string,
password: string,
filePath: string,
) => {
const basicApiService = new BasicApiService(request);

const loginResponse = await basicApiService.logInUserViaApi({
email,
password,
});
const errors = loginResponse.data.tokenCreate.errors;

const loginJsonInfo = await request.storageState();
if (
setup.info().title ===
"TC: SALEOR_137 Admin User should be able to deactivate other user @e2e @staff-members"
) {
await expect(errors[0].code).toEqual("INACTIVE");
} else {
await expect(errors).toEqual([]);
}

const loginJsonInfo = await request.storageState();

loginJsonInfo.origins.push({
origin: process.env.BASE_URL!,
localStorage: [
{
name: "_saleorRefreshToken",
value: loginJsonInfo.cookies[0].value,
}
]
});
fs.writeFileSync(filePath, JSON.stringify(loginJsonInfo, null, 2));
loginJsonInfo.origins = [
{
origin: process.env.BASE_URL!,
localStorage: [
{
name: "_saleorRefreshToken",
value: loginResponse.data.tokenCreate.refreshToken,
},
],
},
];

fs.writeFileSync(filePath, JSON.stringify(loginJsonInfo, null, 2));
};
const authSetup = async (
request: APIRequestContext,
email: string,
password: string,
fileName: string,
) => {
const tempDir = path.join(__dirname, "../.auth");

if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}

const authSetup = async (request: APIRequestContext, email: string, password: string, fileName: string) => {
const tempDir = path.join(__dirname, '../.auth');
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
const tempFilePath = path.join(tempDir, fileName);
const tempFilePath = path.join(tempDir, fileName);

if (!fs.existsSync(tempFilePath)) {
await authenticateAndSaveState(request, email, password, tempFilePath);
}
if (!fs.existsSync(tempFilePath)) {
await authenticateAndSaveState(request, email, password, tempFilePath);
}
};

setup("Authenticate as admin via API", async ({ request }) => {
await authSetup(request, process.env.E2E_USER_NAME!, process.env.E2E_USER_PASSWORD!, 'admin.json');
await authSetup(
request,
process.env.E2E_USER_NAME!,
process.env.E2E_USER_PASSWORD!,
"admin.json",
);
});

const user: UserPermissionType = USER_PERMISSION;
const password: string = process.env.E2E_PERMISSIONS_USERS_PASSWORD!;
setup("Authenticate permission users via API", async ({ request }) => {
for (const permission of permissions) {
const email = USER_PERMISSION[permission];
const password = process.env.E2E_PERMISSIONS_USERS_PASSWORD!;
const fileName = `${permission}.json`;

await authSetup(request, email, password, fileName);
}
});

for (const permission of permissions) {
setup(`Authenticate as ${permission} user via API`, async ({ request }) => {
await authSetup(request, user[permission], password, `${permission}.json`);
});
}
Loading
Loading