feat(testing): Added prefexing to user email and project which will be deleted by each worker on reset and some cleanups

This commit is contained in:
Muhammed Mustafa
2022-10-27 14:02:33 +05:30
parent 8495d03f65
commit 86db88cce7
18 changed files with 319 additions and 160 deletions

View File

@@ -1,6 +1,8 @@
node_modules/
/test-results/
/playwright-report/
/playwright-report copy/
/playwright/.cache/
.env
output
/output copy/

View File

@@ -64,12 +64,10 @@ export class GridPage extends BasePage {
index = 0,
columnHeader = "Title",
value,
networkValidation = true,
}: {
index?: number;
columnHeader?: string;
value?: string;
networkValidation?: boolean;
} = {}) {
const rowValue = value ?? `Row ${index}`;
const rowCount = await this.get().locator(".nc-grid-row").count();
@@ -86,28 +84,22 @@ export class GridPage extends BasePage {
.locator(`span[title="${columnHeader}"]`)
.click();
if (networkValidation) {
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["POST"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
});
} else {
await this.rootPage.waitForTimeout(300);
}
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["POST"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
});
}
async editRow({
index = 0,
columnHeader = "Title",
value,
networkValidation = true,
}: {
index?: number;
columnHeader?: string;
value: string;
networkValidation?: boolean;
}) {
await this._fillRow({ index, columnHeader, value });
@@ -116,16 +108,12 @@ export class GridPage extends BasePage {
.locator(`span[title="${columnHeader}"]`)
.click();
if (networkValidation) {
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["PATCH"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
});
} else {
await this.rootPage.waitForTimeout(300);
}
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["PATCH"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
});
}
async verifyRow({ index }: { index: number }) {

View File

@@ -24,11 +24,18 @@ export class TeamsPage extends BasePage {
.locator(`[pw-data="nc-settings-subtab-Users Management"]`);
}
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
return `nc_test_${parallelId}_${email}`;
}
getSharedBaseSubModal() {
return this.rootPage.locator(`[data-pw="nc-share-base-sub-modal"]`);
}
async invite({ email, role }: { email: string; role: string }) {
email = this.prefixEmail(email);
await this.inviteTeamBtn.click();
await this.inviteTeamModal
.locator(`input[placeholder="E-mail"]`)

View File

@@ -73,6 +73,7 @@ export class ViewSidebarPage extends BasePage {
await this.createView({ title, locator: this.createKanbanButton });
}
// Todo: Make selection better
async verifyView({ title, index }: { title: string; index: number }) {
expect(
await this.get()

View File

@@ -137,6 +137,7 @@ export class DashboardPage extends BasePage {
.click();
}
// todo: Move this to a seperate page
async changePassword({
oldPass,
newPass,
@@ -189,41 +190,6 @@ export class DashboardPage extends BasePage {
.waitFor();
}
// create project
async createProject({
name = "sample",
type = "xcdb",
}: {
name?: string;
type?: string;
}) {
// fix me! wait for page to be rendered completely
await this.rootPage.waitForTimeout(1000);
await this.rootPage.locator(".nc-new-project-menu").click();
const createProjectMenu = await this.rootPage.locator(
".nc-dropdown-create-project"
);
if (type === "xcdb") {
await createProjectMenu
.locator(`.ant-dropdown-menu-title-content`)
.nth(0)
.click();
} else {
await createProjectMenu
.locator(`.ant-dropdown-menu-title-content`)
.nth(1)
.click();
}
await this.rootPage.locator(`.nc-metadb-project-name`).waitFor();
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
await this.rootPage.locator(`input.nc-metadb-project-name`).press("Enter");
// fix me! wait for page to be rendered completely
await this.rootPage.waitForTimeout(2000);
}
async signOut() {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
let projMenu = await this.rootPage.locator(".nc-dropdown-project-menu");
@@ -234,57 +200,6 @@ export class DashboardPage extends BasePage {
await this.rootPage.locator('[data-cy="nc-form-signin"]:visible').waitFor();
}
async signUp({ email, password }: { email: string; password: string }) {
const signUp = this.rootPage;
await signUp.locator('button:has-text("SIGN UP")').waitFor();
await signUp
.locator(`input[placeholder="Enter your work email"]`)
.fill(email);
await signUp
.locator(`input[placeholder="Enter your password"]`)
.fill(password);
await signUp.locator(`button:has-text("SIGN UP")`).click();
}
async openProject({ title }: { title?: string }) {
const project = this.rootPage;
await project.locator(`td.ant-table-cell:has-text("${title}")`).click();
}
async renameProject({
title,
newTitle,
}: {
title?: string;
newTitle?: string;
}) {
const project = this.rootPage;
const projRow = await project.locator(`tr`, {
has: project.locator(`td.ant-table-cell:has-text("${title}")`),
});
await projRow.locator(".nc-action-btn").nth(0).click();
await project.locator("input.nc-metadb-project-name").fill(newTitle);
// press enter to save
await project.locator("input.nc-metadb-project-name").press("Enter");
}
async deleteProject({ title }: { title?: string }) {
const project = this.rootPage;
const projRow = await project.locator(`tr`, {
has: project.locator(`td.ant-table-cell:has-text("${title}")`),
});
await projRow.locator(".nc-action-btn").nth(1).click();
const deleteModal = await project.locator(".nc-modal-project-delete");
await deleteModal.locator('button:has-text("Yes")').click();
await this.rootPage.waitForTimeout(1000);
expect(
await project.locator(`td.ant-table-cell:has-text("${title}")`).count()
).toBe(0);
}
async validateProjectMenu(param: { role: string; mode?: string }) {
await this.rootPage.locator('[pw-data="nc-project-menu"]').click();
let pMenu = this.rootPage.locator(`.nc-dropdown-project-menu:visible`);

View File

@@ -7,6 +7,11 @@ export class LoginPage extends BasePage {
super(rootPage);
}
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
return `nc_test_${parallelId}_${email}`;
}
goto() {
return this.rootPage.goto("/#/signin");
}
@@ -15,7 +20,9 @@ export class LoginPage extends BasePage {
return this.rootPage.locator("html");
}
async fillEmail(email: string) {
async fillEmail({email, withoutPrefix}:{email: string, withoutPrefix?: boolean}) {
if(!withoutPrefix) email = this.prefixEmail(email);
await this.get().locator(`[pw-data="nc-form-signin__email"]`).waitFor();
await this.get().locator(`[pw-data="nc-form-signin__email"]`).fill(email);
}
@@ -31,9 +38,9 @@ export class LoginPage extends BasePage {
await expect(this.rootPage).toHaveURL("http://localhost:3000/#/");
}
async signIn({ email, password }: { email: string; password: string }) {
async signIn({ email, password, withoutPrefix }: { email: string; password: string, withoutPrefix?: boolean }) {
await this.goto();
await this.fillEmail(email);
await this.fillEmail({email, withoutPrefix});
await this.fillPassword(password);
await this.submit();
}

View File

@@ -7,11 +7,56 @@ export class ProjectsPage extends BasePage {
super(rootPage);
}
prefixTitle(title: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
return `${title}${parallelId}`;
}
get() {
return this.rootPage.locator("html");
}
async selectAndGetProject(projectName: string) {
// create project
async createProject({
name = "sample",
type = "xcdb",
withoutPrefix,
}: {
name?: string;
type?: string;
withoutPrefix?: boolean;
}) {
if(!withoutPrefix) name = this.prefixTitle(name);
// fix me! wait for page to be rendered completely
await this.rootPage.waitForTimeout(1000);
await this.rootPage.locator(".nc-new-project-menu").click();
const createProjectMenu = await this.rootPage.locator(
".nc-dropdown-create-project"
);
if (type === "xcdb") {
await createProjectMenu
.locator(`.ant-dropdown-menu-title-content`)
.nth(0)
.click();
} else {
await createProjectMenu
.locator(`.ant-dropdown-menu-title-content`)
.nth(1)
.click();
}
await this.rootPage.locator(`.nc-metadb-project-name`).waitFor();
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
await this.rootPage.locator(`input.nc-metadb-project-name`).press("Enter");
// fix me! wait for page to be rendered completely
await this.rootPage.waitForTimeout(2000);
}
async openProject({title, withoutPrefix}: {title: string, withoutPrefix?: boolean}) {
if(!withoutPrefix) title = this.prefixTitle(title);
let project: any;
await Promise.all([
@@ -25,7 +70,7 @@ export class ProjectsPage extends BasePage {
const isRequiredResponse = res.request().url().includes('/api/v1/db/meta/projects') &&
['GET'].includes(res.request().method()) &&
json?.title === projectName;
json?.title === title;
if(isRequiredResponse){
project = json;
@@ -34,16 +79,44 @@ export class ProjectsPage extends BasePage {
return isRequiredResponse;
}),
this.get().locator(`.ant-table-cell`,{
hasText: projectName
hasText: title
}).click()
]);
return project;
}
async delete({title}: {title: string}) {
async deleteProject({title, withoutPrefix}: {title: string, withoutPrefix?: boolean}) {
if(!withoutPrefix) title = this.prefixTitle(title);
await this.get().locator(`[pw-data="delete-project-${title}"]`).click();
await this.rootPage.locator(`button:has-text("Yes")`).click();
await expect.poll(
async () => await this.get().locator(`[pw-data="delete-project-${title}"]`).count()
).toBe(0);
}
async renameProject({
title,
newTitle,
withoutPrefix,
}: {
title: string;
newTitle: string;
withoutPrefix?: boolean;
}) {
if(!withoutPrefix) title = this.prefixTitle(title);
if(!withoutPrefix) newTitle = this.prefixTitle(newTitle);
const project = this.rootPage;
const projRow = await project.locator(`tr`, {
has: project.locator(`td.ant-table-cell:has-text("${title}")`),
});
await projRow.locator(".nc-action-btn").nth(0).click();
await project.locator("input.nc-metadb-project-name").fill(newTitle);
// press enter to save
await project.locator("input.nc-metadb-project-name").press("Enter");
}
}

View File

@@ -0,0 +1,37 @@
// playwright-dev-page.ts
import { expect, Page } from "@playwright/test";
import BasePage from "../Base";
export class SignupPage extends BasePage {
constructor(rootPage: Page) {
super(rootPage);
}
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0'
return `nc_test_${parallelId}_${email}`;
}
goto() {
return this.rootPage.goto("/#/signup/");
}
get() {
return this.rootPage.locator("html");
}
async signUp({ email, password, withoutPrefix }: { email: string; password: string, withoutPrefix?: boolean }) {
if(!withoutPrefix) email = this.prefixEmail(email);
const signUp = this.rootPage;
await signUp.locator('button:has-text("SIGN UP")').waitFor();
await signUp
.locator(`input[placeholder="Enter your work email"]`)
.fill(email);
await signUp
.locator(`input[placeholder="Enter your password"]`)
.fill(password);
await signUp.locator(`button:has-text("SIGN UP")`).click();
}
}

View File

@@ -12,12 +12,12 @@ test.describe("Quick tests", () => {
test("Quick tests test", async ({page}) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await loginPage.fillEmail("user@nocodb.com");
await loginPage.fillEmail({email: "user@nocodb.com", withoutPrefix: true});
await loginPage.fillPassword("Password123.");
await loginPage.submit();
const projectsPage = new ProjectsPage(page);
const project = await projectsPage.selectAndGetProject("sample");
const project = await projectsPage.openProject({title: "sample", withoutPrefix: true});
dashboard = new DashboardPage(page, project);
const context: NcContext = {

View File

@@ -4,16 +4,19 @@ import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { LoginPage } from "../pages/LoginPage";
import { SettingsPage, SettingTab } from "../pages/Dashboard/Settings";
import { SignupPage } from "../pages/SignupPage";
test.describe("Auth", () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let settings: SettingsPage;
let context: any;
let signupPage: SignupPage;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
signupPage = new SignupPage(page);
toolbar = dashboard.grid.toolbar;
settings = dashboard.settings;
});
@@ -32,7 +35,7 @@ test.describe("Auth", () => {
await dashboard.signOut();
await dashboard.rootPage.goto(url);
await dashboard.signUp({
await signupPage.signUp({
email: "user-1@nocodb.com",
password: "Password123.",
});
@@ -71,7 +74,7 @@ test.describe("Auth", () => {
});
const loginPage = new LoginPage(page);
await loginPage.fillEmail("user-1@nocodb.com");
await loginPage.fillEmail({email: "user-1@nocodb.com"});
await loginPage.fillPassword("NewPasswordConfigured");
await loginPage.submit();

View File

@@ -3,12 +3,14 @@ import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { LoginPage } from "../pages/LoginPage";
import { ProjectsPage } from "../pages/ProjectsPage";
test.describe("Shared base", () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
let loginPage: LoginPage;
let projectPage: ProjectsPage;
async function roleTest(role: string) {
await dashboard.validateProjectMenu({
@@ -44,13 +46,12 @@ test.describe("Shared base", () => {
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
projectPage = new ProjectsPage(page);
toolbar = dashboard.grid.toolbar;
loginPage = new LoginPage(page);
});
test("#1", async () => {
let projId = process.env.TEST_PARALLEL_INDEX;
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });
@@ -69,8 +70,9 @@ test.describe("Shared base", () => {
await loginPage.signIn({
email: "user@nocodb.com",
password: "Password123.",
withoutPrefix: true,
});
await dashboard.openProject({ title: `externalREST${projId}` });
await projectPage.openProject({ title: 'externalREST' });
await dashboard.closeTab({ title: "Team & Auth" });
await dashboard.treeView.inviteTeamButton.click();

View File

@@ -19,7 +19,7 @@ test.describe('Multi select', () => {
await grid.addNewRow({index: 0, value: "Row 0"});
})
test.only('Select and clear options and rename options', async () => {
test('Select and clear options and rename options', async () => {
await grid.cell.selectOption.select({index: 0, columnHeader: 'MultiSelect', option: 'Option 1', multiSelect: true});
await grid.cell.selectOption.verify({index: 0, columnHeader: 'MultiSelect', option: 'Option 1', multiSelect: true});

View File

@@ -2,29 +2,32 @@ import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import { ProjectsPage } from "../pages/ProjectsPage";
test.describe("Project operations", () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let context: any;
let projectPage: ProjectsPage;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
projectPage = new ProjectsPage(page);
toolbar = dashboard.grid.toolbar;
});
test("rename, delete", async () => {
await dashboard.clickHome();
await dashboard.createProject({ name: "project-1", type: "xcdb" });
await projectPage.createProject({ name: "project-1", type: "xcdb" });
await dashboard.clickHome();
await dashboard.renameProject({
await projectPage.renameProject({
title: "project-1",
newTitle: "project-new",
});
await dashboard.clickHome();
await dashboard.openProject({ title: "project-new" });
await projectPage.openProject({ title: "project-new" });
await dashboard.clickHome();
await dashboard.deleteProject({ title: "project-new" });
await projectPage.deleteProject({ title: "project-new" });
});
});

View File

@@ -1,12 +1,13 @@
import { test } from "@playwright/test";
import { DashboardPage } from "../pages/Dashboard";
import setup from "../setup";
import { ToolbarPage } from "../pages/Dashboard/common/Toolbar";
import {
SettingsPage,
SettingsSubTab,
SettingTab,
} from "../pages/Dashboard/Settings";
import { SignupPage } from "../pages/SignupPage";
import { ProjectsPage } from "../pages/ProjectsPage";
let roleDb = [
{ email: "creator@nocodb.com", role: "creator", url: "" },
@@ -15,35 +16,19 @@ let roleDb = [
{ email: "viewer@nocodb.com", role: "viewer", url: "" },
];
async function roleSignup(roleIdx: number, db: any) {
let projIdx = process.env.TEST_PARALLEL_INDEX;
await db.signOut();
await db.rootPage.goto(roleDb[roleIdx].url);
await db.signUp({
email: roleDb[roleIdx].email,
password: "Password123.",
});
await db.openProject({ title: `externalREST${projIdx}` });
// close 'Team & Auth' tab
if (roleDb[roleIdx].role === "creator") {
await db.closeTab({ title: "Team & Auth" });
}
}
test.describe("User roles", () => {
let dashboard: DashboardPage;
let toolbar: ToolbarPage;
let settings: SettingsPage;
let signupPage: SignupPage;
let projectsPage: ProjectsPage;
let context: any;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
toolbar = dashboard.grid.toolbar;
settings = dashboard.settings;
signupPage = new SignupPage(page);
projectsPage = new ProjectsPage(page);
});
test("Create role", async () => {
@@ -78,12 +63,12 @@ test.describe("User roles", () => {
// Role test
for (let i = 0; i < roleDb.length; i++) {
console.log("Role: ", roleDb[i].role);
await roleTest(i, dashboard);
await roleTest(i);
}
});
async function roleTest(roleIdx: number, db: any) {
await roleSignup(roleIdx, dashboard);
async function roleTest(roleIdx: number) {
await roleSignup(roleIdx);
await dashboard.validateProjectMenu({
role: roleDb[roleIdx].role,
});
@@ -94,7 +79,7 @@ test.describe("User roles", () => {
role: roleDb[roleIdx].role,
});
await toolbar.validateRoleAccess({
await dashboard.grid.toolbar.validateRoleAccess({
role: roleDb[roleIdx].role,
});
@@ -121,4 +106,21 @@ test.describe("User roles", () => {
exists: roleDb[roleIdx].role === "creator" ? true : false,
});
}
async function roleSignup(roleIdx: number) {
await dashboard.signOut();
await dashboard.rootPage.goto(roleDb[roleIdx].url);
await signupPage.signUp({
email: roleDb[roleIdx].email,
password: "Password123.",
});
await projectsPage.openProject({ title: 'externalREST' });
// close 'Team & Auth' tab
if (roleDb[roleIdx].role === "creator") {
await dashboard.closeTab({ title: "Team & Auth" });
}
}
});

View File

@@ -18,7 +18,15 @@ async function clearServerData({ request }) {
}
async function verifyHookTrigger(count: number, value: string, request) {
let response = await request.get(hookPath + "/count");
// Retry since there can be lag between the time the hook is triggered and the time the server receives the request
let response;
for(let i = 0; i < 6; i++) {
response = await request.get(hookPath + "/count");
if(await response.json() === count) {
break;
}
await new Promise((resolve) => setTimeout(resolve, 100));
}
expect(await response.json()).toBe(count);
if (count) {
@@ -27,14 +35,15 @@ async function verifyHookTrigger(count: number, value: string, request) {
}
}
test.describe("Webhook", () => {
test.describe.serial("Webhook", async () => {
// start a server locally for webhook tests
let dashboard: DashboardPage, toolbar: ToolbarPage, webhook: WebhookFormPage;
let context: any;
test.beforeEach(async () => {
// start a server locally for webhook tests
test.beforeAll(async () => {
await makeServer();
});
})
test.beforeEach(async ({ page }) => {
context = await setup({ page });