json.title === title && json.type === 'table',
});
+ // Tab render is slow for playwright
await this.dashboard.waitForTabRender({ title });
}
diff --git a/tests/playwright/pages/Dashboard/WebhookForm/index.ts b/tests/playwright/pages/Dashboard/WebhookForm/index.ts
index ad1269d6d3..7eff8ebcea 100644
--- a/tests/playwright/pages/Dashboard/WebhookForm/index.ts
+++ b/tests/playwright/pages/Dashboard/WebhookForm/index.ts
@@ -23,7 +23,6 @@ export class WebhookFormPage extends BasePage {
return this.dashboard.get().locator(`.nc-drawer-webhook-body`);
}
- // todo: Removing opening webhook drawer logic as it belongs to `Toolbar` page
async create({ title, event, url = 'http://localhost:9090/hook' }: { title: string; event: string; url?: string }) {
await this.toolbar.clickActions();
await this.toolbar.actions.click('Webhooks');
diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
index a11d3c1755..6c7a8b70c4 100644
--- a/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
+++ b/tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
@@ -40,11 +40,6 @@ export class ToolbarFilterPage extends BasePage {
value: string;
isLocallySaved: boolean;
}) {
- await this.toolbar.clickFilter();
-
- // todo: If the filter menu is open for the first time for the table, there can will be a api call which will re render the filter menu
- await this.rootPage.waitForTimeout(1000);
-
await this.get().locator(`button:has-text("Add Filter")`).first().click();
await this.rootPage.locator('.nc-filter-field-select').last().click();
@@ -82,9 +77,6 @@ export class ToolbarFilterPage extends BasePage {
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
-
- await this.toolbar.clickFilter();
-
await this.toolbar.parent.waitLoading();
}
diff --git a/tests/playwright/pages/Dashboard/common/Toolbar/index.ts b/tests/playwright/pages/Dashboard/common/Toolbar/index.ts
index 5a91ce3e36..02c1f6bedb 100644
--- a/tests/playwright/pages/Dashboard/common/Toolbar/index.ts
+++ b/tests/playwright/pages/Dashboard/common/Toolbar/index.ts
@@ -69,13 +69,30 @@ export class ToolbarPage extends BasePage {
if (menuOpen) await this.sort.get().waitFor({ state: 'hidden' });
}
- async clickFilter() {
+ async clickFilter({
+ // `networkValidation` is used to verify that api calls are made when the button is clicked
+ // which happens when the filter is opened for the first time
+ networkValidation,
+ }: { networkValidation?: boolean } = {}) {
const menuOpen = await this.filter.get().isVisible();
- await this.get().locator(`button.nc-filter-menu-btn`).click();
-
+ const clickFilterAction = this.get().locator(`button.nc-filter-menu-btn`).click();
// Wait for the menu to close
- if (menuOpen) await this.filter.get().waitFor({ state: 'hidden' });
+ if (menuOpen) {
+ await clickFilterAction;
+ await this.filter.get().waitFor({ state: 'hidden' });
+ } else {
+ if (networkValidation) {
+ // Since on opening filter menu, api is called to fetch filter options, and will rerender the menu
+ await this.waitForResponse({
+ uiAction: clickFilterAction,
+ requestUrlPathToMatch: '/api/v1/db',
+ httpMethodsToMatch: ['GET'],
+ });
+ } else {
+ await clickFilterAction;
+ }
+ }
}
async clickShareView() {
diff --git a/tests/playwright/pages/Dashboard/index.ts b/tests/playwright/pages/Dashboard/index.ts
index 14b68125a6..6f70b854bf 100644
--- a/tests/playwright/pages/Dashboard/index.ts
+++ b/tests/playwright/pages/Dashboard/index.ts
@@ -61,7 +61,7 @@ export class DashboardPage extends BasePage {
}
async gotoSettings() {
- await this.rootPage.locator('[data-testid="nc-project-menu"]').click();
+ await this.rootPage.getByTestId('nc-project-menu').click();
await this.rootPage.locator('div.nc-project-menu-item:has-text(" Team & Settings")').click();
}
@@ -79,9 +79,6 @@ export class DashboardPage extends BasePage {
}
async clickHome() {
- // todo: Fast page transition breaks the vue router
- await this.rootPage.waitForTimeout(2000);
-
await this.rootPage.getByTestId('nc-noco-brand-icon').click();
const projectsPage = new ProjectsPage(this.rootPage);
await projectsPage.waitToBeRendered();
@@ -124,32 +121,9 @@ export class DashboardPage extends BasePage {
}
}
- async openPasswordChangeModal() {
- // open change password portal
- await this.rootPage.locator('.nc-menu-accounts').click();
- await this.rootPage
- .locator('.nc-dropdown-user-accounts-menu')
- .getByTestId('nc-menu-accounts__user-settings')
- .click();
- }
-
- // todo: Move this to a seperate page
- async changePassword({ oldPass, newPass, repeatPass }: { oldPass: string; newPass: string; repeatPass: string }) {
- // change password
- const currentPassword = this.rootPage.locator('input[data-testid="nc-user-settings-form__current-password"]');
- const newPassword = this.rootPage.locator('input[data-testid="nc-user-settings-form__new-password"]');
- const confirmPassword = this.rootPage.locator('input[data-testid="nc-user-settings-form__new-password-repeat"]');
-
- await currentPassword.fill(oldPass);
- await newPassword.fill(newPass);
- await confirmPassword.fill(repeatPass);
-
- await this.rootPage.locator('button[data-testid="nc-user-settings-form__submit"]').click();
- }
-
async signOut() {
- await this.rootPage.locator('[data-testid="nc-project-menu"]').click();
- const projMenu = await this.rootPage.locator('.nc-dropdown-project-menu');
+ await this.rootPage.getByTestId('nc-project-menu').click();
+ const projMenu = this.rootPage.locator('.nc-dropdown-project-menu');
await projMenu.locator('[data-menu-id="account"]:visible').click();
await this.rootPage.locator('div.nc-project-menu-item:has-text("Sign Out"):visible').click();
await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor();
diff --git a/tests/playwright/pages/ProjectsPage/ChangePassword.ts b/tests/playwright/pages/ProjectsPage/ChangePassword.ts
new file mode 100644
index 0000000000..d98e68205d
--- /dev/null
+++ b/tests/playwright/pages/ProjectsPage/ChangePassword.ts
@@ -0,0 +1,51 @@
+import { expect, Page } from '@playwright/test';
+import BasePage from '../Base';
+
+export class ChangePasswordPage extends BasePage {
+ constructor(rootPage: Page) {
+ super(rootPage);
+ }
+
+ get() {
+ return this.rootPage.getByTestId('user-change-password');
+ }
+
+ async changePassword({
+ oldPass,
+ newPass,
+ repeatPass,
+ networkValidation,
+ }: {
+ oldPass: string;
+ newPass: string;
+ repeatPass: string;
+ networkValidation?: boolean;
+ }) {
+ const currentPassword = this.get().locator('input[data-testid="nc-user-settings-form__current-password"]');
+ const newPassword = this.get().locator('input[data-testid="nc-user-settings-form__new-password"]');
+ const confirmPassword = this.get().locator('input[data-testid="nc-user-settings-form__new-password-repeat"]');
+
+ await currentPassword.fill(oldPass);
+ await newPassword.fill(newPass);
+ await confirmPassword.fill(repeatPass);
+
+ const submitChangePassword = this.get().locator('button[data-testid="nc-user-settings-form__submit"]').click();
+ if (networkValidation) {
+ await this.waitForResponse({
+ uiAction: submitChangePassword,
+ httpMethodsToMatch: ['POST'],
+ requestUrlPathToMatch: 'api/v1/auth/password/change',
+ });
+ } else {
+ await submitChangePassword;
+ }
+ }
+
+ async verifyFormError({ error }: { error: string }) {
+ await expect(this.get().getByTestId('nc-user-settings-form__error')).toHaveText(error);
+ }
+
+ async verifyPasswordDontMatchError() {
+ await expect(this.rootPage.locator('.ant-form-item-explain-error')).toHaveText('Passwords do not match');
+ }
+}
diff --git a/tests/playwright/pages/ProjectsPage/index.ts b/tests/playwright/pages/ProjectsPage/index.ts
index 3c336dd6ff..f44eab3283 100644
--- a/tests/playwright/pages/ProjectsPage/index.ts
+++ b/tests/playwright/pages/ProjectsPage/index.ts
@@ -1,10 +1,14 @@
import { expect, Page } from '@playwright/test';
import BasePage from '../Base';
import { DashboardPage } from '../Dashboard';
+import { ChangePasswordPage } from './ChangePassword';
export class ProjectsPage extends BasePage {
+ readonly changePasswordPage: ChangePasswordPage;
+
constructor(rootPage: Page) {
super(rootPage);
+ this.changePasswordPage = new ChangePasswordPage(rootPage);
}
prefixTitle(title: string) {
@@ -28,7 +32,7 @@ export class ProjectsPage extends BasePage {
}) {
if (!withoutPrefix) name = this.prefixTitle(name);
- await this.rootPage.locator('.nc-new-project-menu').click();
+ await this.get().locator('.nc-new-project-menu').click();
const createProjectMenu = await this.rootPage.locator('.nc-dropdown-create-project');
@@ -38,20 +42,15 @@ export class ProjectsPage extends BasePage {
await createProjectMenu.locator(`.ant-dropdown-menu-title-content`).nth(1).click();
}
- // todo: Fast page transition breaks the vue router
- await this.rootPage.waitForTimeout(2000);
-
await this.rootPage.locator(`.nc-metadb-project-name`).waitFor();
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
- await this.rootPage.waitForTimeout(2000);
-
- await this.rootPage.locator(`button:has-text("Create")`).click({
- delay: 2000,
+ const createProjectSubmitAction = this.rootPage.locator(`button:has-text("Create")`).click();
+ await this.waitForResponse({
+ uiAction: createProjectSubmitAction,
+ httpMethodsToMatch: ['POST'],
+ requestUrlPathToMatch: '/api/v1/db/meta/projects/',
});
-
- // fix me! wait for page to be rendered completely
- await this.rootPage.waitForTimeout(2000);
}
async checkProjectCreateButton({ exists = true }) {
@@ -91,9 +90,6 @@ export class ProjectsPage extends BasePage {
withoutPrefix?: boolean;
waitForAuthTab?: boolean;
}) {
- // todo: Fast page transition breaks the vue router
- await this.rootPage.waitForTimeout(2000);
-
if (!withoutPrefix) title = this.prefixTitle(title);
let project: any;
@@ -138,7 +134,13 @@ export class ProjectsPage extends BasePage {
if (!withoutPrefix) title = this.prefixTitle(title);
await this.get().locator(`[data-testid="delete-project-${title}"]`).click();
- await this.rootPage.locator(`button:has-text("Yes")`).click();
+
+ const deleteProjectAction = this.rootPage.locator(`button:has-text("Yes")`).click();
+ await this.waitForResponse({
+ uiAction: deleteProjectAction,
+ httpMethodsToMatch: ['DELETE'],
+ requestUrlPathToMatch: '/api/v1/db/meta/projects/',
+ });
await this.get().locator('.ant-table-row', { hasText: title }).waitFor({ state: 'hidden' });
}
@@ -161,9 +163,6 @@ export class ProjectsPage extends BasePage {
});
await projRow.locator('.nc-action-btn').nth(0).click();
- // todo: Fast page transition breaks the vue router
- await this.rootPage.waitForTimeout(2000);
-
await project.locator('input.nc-metadb-project-name').fill(newTitle);
// press enter to save
const submitAction = project.locator('input.nc-metadb-project-name').press('Enter');
@@ -172,9 +171,6 @@ export class ProjectsPage extends BasePage {
requestUrlPathToMatch: 'api/v1/db/meta/projects/',
httpMethodsToMatch: ['PATCH'],
});
-
- // todo: vue navigation breaks if page changes very quickly
- await this.rootPage.waitForTimeout(1000);
}
async openLanguageMenu() {
@@ -187,10 +183,23 @@ export class ProjectsPage extends BasePage {
}
async verifyLanguage(param: { json: any }) {
- const title = await this.rootPage.locator(`.nc-project-page-title`);
+ const title = this.rootPage.locator(`.nc-project-page-title`);
const menu = this.rootPage.locator(`.nc-new-project-menu`);
await expect(title).toHaveText(param.json.title.myProject);
await expect(menu).toHaveText(param.json.title.newProj);
await this.rootPage.locator(`[placeholder="${param.json.activity.searchProject}"]`).waitFor();
}
+
+ async openPasswordChangeModal() {
+ // open change password portal
+ await this.rootPage.locator('.nc-menu-accounts').click();
+ await this.rootPage
+ .locator('.nc-dropdown-user-accounts-menu')
+ .getByTestId('nc-menu-accounts__user-settings')
+ .click();
+ }
+
+ async waitForRender() {
+ await this.rootPage.locator('.nc-project-page-title:has-text("My Projects")').waitFor();
+ }
}
diff --git a/tests/playwright/tests/authChangePassword.spec.ts b/tests/playwright/tests/authChangePassword.spec.ts
index b348f3b0a9..c3be00eaed 100644
--- a/tests/playwright/tests/authChangePassword.spec.ts
+++ b/tests/playwright/tests/authChangePassword.spec.ts
@@ -4,17 +4,20 @@ import setup from '../setup';
import { LoginPage } from '../pages/LoginPage';
import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings';
import { SignupPage } from '../pages/SignupPage';
+import { ProjectsPage } from '../pages/ProjectsPage';
test.describe('Auth', () => {
+ let context: any;
let dashboard: DashboardPage;
let settings: SettingsPage;
- let context: any;
let signupPage: SignupPage;
+ let projectsPage: ProjectsPage;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
signupPage = new SignupPage(page);
+ projectsPage = new ProjectsPage(page);
settings = dashboard.settings;
});
@@ -37,31 +40,31 @@ test.describe('Auth', () => {
password: 'Password123.',
});
- await dashboard.openPasswordChangeModal();
+ await projectsPage.openPasswordChangeModal();
// Existing active pass incorrect
- await dashboard.changePassword({
+ await projectsPage.changePasswordPage.changePassword({
oldPass: '123456789',
newPass: '123456789',
repeatPass: '123456789',
});
- await dashboard.rootPage
- .locator('[data-testid="nc-user-settings-form__error"]:has-text("Current password is wrong")')
- .waitFor();
+ await projectsPage.changePasswordPage.verifyFormError({ error: 'Current password is wrong' });
// New pass and repeat pass mismatch
- await dashboard.changePassword({
+ await projectsPage.changePasswordPage.changePassword({
oldPass: 'Password123.',
newPass: '123456789',
repeatPass: '987654321',
+ networkValidation: false,
});
- await dashboard.rootPage.locator('.ant-form-item-explain-error:has-text("Passwords do not match")').waitFor();
+ await projectsPage.changePasswordPage.verifyPasswordDontMatchError();
// All good
- await dashboard.changePassword({
+ await projectsPage.changePasswordPage.changePassword({
oldPass: 'Password123.',
newPass: 'NewPasswordConfigured',
repeatPass: 'NewPasswordConfigured',
+ networkValidation: true,
});
const loginPage = new LoginPage(page);
@@ -69,6 +72,6 @@ test.describe('Auth', () => {
await loginPage.fillPassword('NewPasswordConfigured');
await loginPage.submit();
- await page.locator('.nc-project-page-title:has-text("My Projects")').waitFor();
+ await projectsPage.waitForRender();
});
});
diff --git a/tests/playwright/tests/metaSync.spec.ts b/tests/playwright/tests/metaSync.spec.ts
index 086d2bcb10..ab8a02d029 100644
--- a/tests/playwright/tests/metaSync.spec.ts
+++ b/tests/playwright/tests/metaSync.spec.ts
@@ -260,12 +260,14 @@ test.describe('Meta sync', () => {
isLocallySaved: false,
});
+ await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.toolbar.filter.addNew({
columnTitle: 'Col1',
opType: '>=',
value: '5',
isLocallySaved: false,
});
+ await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.verifyRowCount({ count: 5 });
});
diff --git a/tests/playwright/tests/toolbarOperations.spec.ts b/tests/playwright/tests/toolbarOperations.spec.ts
index 005cfd37e8..d5aab70b6f 100644
--- a/tests/playwright/tests/toolbarOperations.spec.ts
+++ b/tests/playwright/tests/toolbarOperations.spec.ts
@@ -56,12 +56,15 @@ test.describe('Toolbar operations (GRID)', () => {
await validateFirstRow('Afghanistan');
// Filter column
+ await toolbar.clickFilter();
await toolbar.filter.addNew({
columnTitle: 'Country',
value: 'India',
opType: 'is equal',
isLocallySaved: false,
});
+ await toolbar.clickFilter();
+
await validateFirstRow('India');
// Reset filter
diff --git a/tests/playwright/tests/viewGridShare.spec.ts b/tests/playwright/tests/viewGridShare.spec.ts
index 948aa11e19..6e6f82baa5 100644
--- a/tests/playwright/tests/viewGridShare.spec.ts
+++ b/tests/playwright/tests/viewGridShare.spec.ts
@@ -39,12 +39,14 @@ test.describe('Shared view', () => {
isLocallySaved: false,
});
// filter
+ await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.toolbar.filter.addNew({
columnTitle: 'Address',
value: 'Ab',
opType: 'is like',
isLocallySaved: false,
});
+ await dashboard.grid.toolbar.clickFilter();
// share with password disabled, download enabled
await dashboard.grid.toolbar.clickShareView();
@@ -106,12 +108,14 @@ test.describe('Shared view', () => {
});
if (isMysql(context)) {
+ await sharedPage.grid.toolbar.clickFilter();
await sharedPage.grid.toolbar.filter.addNew({
columnTitle: 'District',
value: 'Ta',
opType: 'is like',
isLocallySaved: true,
});
+ await sharedPage.grid.toolbar.clickFilter();
}
await sharedPage.grid.toolbar.fields.toggle({ title: 'LastUpdate', isLocallySaved: true });
expectedColumns[6].isVisible = false;
@@ -191,12 +195,15 @@ test.describe('Shared view', () => {
title: 'New Column',
isVisible: true,
});
+ await sharedPage2.grid.toolbar.clickFilter();
await sharedPage2.grid.toolbar.filter.addNew({
columnTitle: 'Country',
value: 'New Country',
opType: 'is like',
isLocallySaved: true,
});
+ await sharedPage2.grid.toolbar.clickFilter();
+
await sharedPage2.grid.cell.verify({
index: 0,
columnHeader: 'Country',
diff --git a/tests/playwright/tests/viewKanban.spec.ts b/tests/playwright/tests/viewKanban.spec.ts
index f28c1b94ba..b220e1e2d9 100644
--- a/tests/playwright/tests/viewKanban.spec.ts
+++ b/tests/playwright/tests/viewKanban.spec.ts
@@ -142,12 +142,17 @@ test.describe('View', () => {
});
// verify filter
+ await toolbar.clickFilter({
+ networkValidation: true,
+ });
await toolbar.filter.addNew({
columnTitle: 'Title',
opType: 'is like',
value: 'BA',
isLocallySaved: false,
});
+ await toolbar.clickFilter();
+
// verify card order
const order4 = [
['BAKED CLEOPATRA', 'BALLROOM MOCKINGBIRD'],
@@ -188,12 +193,16 @@ test.describe('View', () => {
isAscending: false,
isLocallySaved: false,
});
+
+ await toolbar.clickFilter();
await toolbar.filter.addNew({
columnTitle: 'Title',
opType: 'is like',
value: 'BA',
isLocallySaved: false,
});
+ await toolbar.clickFilter();
+
await toolbar.fields.hideAll();
await toolbar.fields.toggle({ title: 'Title' });