Browse Source

chore(test): replace uiAction with a callback to trigger it only after initializing network watcher

Signed-off-by: Pranav C <pranavxc@gmail.com>
pull/5118/head
Pranav C 2 years ago
parent
commit
eb3d823e91
  1. 4
      packages/noco-docs/content/en/engineering/playwright.md
  2. 5
      tests/playwright/pages/Account/ChangePassword.ts
  3. 37
      tests/playwright/pages/Base.ts
  4. 7
      tests/playwright/pages/Dashboard/ExpandedForm/index.ts
  5. 2
      tests/playwright/pages/Dashboard/Grid/Column/LTAR/ChildList.ts
  6. 8
      tests/playwright/pages/Dashboard/Grid/Column/index.ts
  7. 16
      tests/playwright/pages/Dashboard/Grid/index.ts
  8. 2
      tests/playwright/pages/Dashboard/Import/ImportTemplate.ts
  9. 2
      tests/playwright/pages/Dashboard/Settings/Acl.ts
  10. 2
      tests/playwright/pages/Dashboard/Settings/Miscellaneous.ts
  11. 6
      tests/playwright/pages/Dashboard/TreeView.ts
  12. 12
      tests/playwright/pages/Dashboard/ViewSidebar/index.ts
  13. 2
      tests/playwright/pages/Dashboard/WebhookForm/index.ts
  14. 2
      tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts
  15. 2
      tests/playwright/pages/Dashboard/common/Cell/index.ts
  16. 15
      tests/playwright/pages/Dashboard/common/Toolbar/Fields.ts
  17. 4
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  18. 11
      tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts
  19. 6
      tests/playwright/pages/Dashboard/common/Toolbar/index.ts
  20. 8
      tests/playwright/pages/ProjectsPage/index.ts
  21. 2
      tests/playwright/pages/SharedForm/index.ts

4
packages/noco-docs/content/en/engineering/playwright.md

@ -189,7 +189,7 @@ This a method which will reset/clear all the filters. Since this is an action me
```js
async resetFilter() {
await this.waitForResponse({
uiAction: this.get().locator('.nc-filter-item-remove-btn').click(),
uiAction: () => this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});
@ -221,4 +221,4 @@ async verifyFilter({ title }: { title: string }) {
- Open `Summary` tab in the CI workflow in github actions.
- Scroll down to `Artifacts` section.
- Access reports which suffixed with the db type and shard number(corresponding to the CI workerflow name). i.e `playwright-report-mysql-2` is for `playwright-mysql-2` workflow.
- Download it and run `npm install -D @playwright/test && npx playwright show-report ./` inside the downloaded folder.
- Download it and run `npm install -D @playwright/test && npx playwright show-report ./` inside the downloaded folder.

5
tests/playwright/pages/Account/ChangePassword.ts

@ -29,7 +29,8 @@ export class ChangePasswordPage extends BasePage {
await newPassword.fill(newPass);
await confirmPassword.fill(repeatPass);
const submitChangePassword = this.get().locator('button[data-testid="nc-user-settings-form__submit"]').click();
const submitChangePassword = () =>
this.get().locator('button[data-testid="nc-user-settings-form__submit"]').click();
if (networkValidation) {
await this.waitForResponse({
uiAction: submitChangePassword,
@ -37,7 +38,7 @@ export class ChangePasswordPage extends BasePage {
requestUrlPathToMatch: 'api/v1/auth/password/change',
});
} else {
await submitChangePassword;
await submitChangePassword();
}
}

37
tests/playwright/pages/Base.ts

@ -23,30 +23,31 @@ export default abstract class BasePage {
// A function that takes the response body and returns true if the response is the one we are looking for
responseJsonMatcher,
}: {
uiAction: Promise<any>;
uiAction: () => Promise<any>;
requestUrlPathToMatch: string;
httpMethodsToMatch?: string[];
responseJsonMatcher?: ResponseSelector;
}) {
await Promise.all([
this.rootPage.waitForResponse(async res => {
let isResJsonMatched = true;
if (responseJsonMatcher) {
try {
isResJsonMatched = responseJsonMatcher(await res.json());
} catch (e) {
return false;
}
const waitForResposePromise = this.rootPage.waitForResponse(async res => {
let isResJsonMatched = true;
if (responseJsonMatcher) {
try {
isResJsonMatched = responseJsonMatcher(await res.json());
} catch (e) {
return false;
}
}
return (
res.request().url().includes(requestUrlPathToMatch) &&
httpMethodsToMatch.includes(res.request().method()) &&
isResJsonMatched
);
}),
uiAction,
]);
return (
res.request().url().includes(requestUrlPathToMatch) &&
httpMethodsToMatch.includes(res.request().method()) &&
isResJsonMatched
);
});
const uiActionPromise = uiAction();
await Promise.all([waitForResposePromise, uiActionPromise]);
}
async attachFile({ filePickUIAction, filePath }: { filePickUIAction: Promise<any>; filePath: string[] }) {

7
tests/playwright/pages/Dashboard/ExpandedForm/index.ts

@ -98,9 +98,10 @@ export class ExpandedFormPage extends BasePage {
await dropdownList.locator('.ant-dropdown-menu-item:has-text("Save & Stay")').click();
}
const saveRowAction = saveAndExitMode
? this.get().locator('button:has-text("Save & Exit")').click()
: this.get().locator('button:has-text("Save & Stay")').click();
const saveRowAction = () =>
saveAndExitMode
? this.get().locator('button:has-text("Save & Exit")').click()
: this.get().locator('button:has-text("Save & Stay")').click();
if (waitForRowsData) {
await this.waitForResponse({

2
tests/playwright/pages/Dashboard/Grid/Column/LTAR/ChildList.ts

@ -50,7 +50,7 @@ export class ChildList extends BasePage {
}
async openLinkRecord({ linkTableTitle }: { linkTableTitle: string }) {
const openActions = this.get().locator(`text=/Link to '.*${linkTableTitle}'/i`).click();
const openActions = () => this.get().locator(`text=/Link to '.*${linkTableTitle}'/i`).click();
await this.waitForResponse({
requestUrlPathToMatch: '/exclude',
httpMethodsToMatch: ['GET'],

8
tests/playwright/pages/Dashboard/Grid/Column/index.ts

@ -330,7 +330,7 @@ export class ColumnPageObject extends BasePage {
}
await this.waitForResponse({
uiAction: this.rootPage.locator('li[role="menuitem"]:has-text("Hide Field"):visible').click(),
uiAction: () => this.rootPage.locator('li[role="menuitem"]:has-text("Hide Field"):visible').click(),
requestUrlPathToMatch: 'api/v1/db/meta/views',
httpMethodsToMatch: ['PATCH'],
});
@ -340,7 +340,7 @@ export class ColumnPageObject extends BasePage {
async save({ isUpdated }: { isUpdated?: boolean } = {}) {
await this.waitForResponse({
uiAction: this.get().locator('button:has-text("Save")').click(),
uiAction: () => this.get().locator('button:has-text("Save")').click(),
requestUrlPathToMatch: 'api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
responseJsonMatcher: json => json['pageInfo'],
@ -375,9 +375,9 @@ export class ColumnPageObject extends BasePage {
await this.grid.get().locator(`th[data-title="${title}"] .nc-ui-dt-dropdown`).click();
let menuOption;
if (direction === 'desc') {
menuOption = this.rootPage.locator('li[role="menuitem"]:has-text("Sort Descending"):visible').click();
menuOption = () => this.rootPage.locator('li[role="menuitem"]:has-text("Sort Descending"):visible').click();
} else {
menuOption = this.rootPage.locator('li[role="menuitem"]:has-text("Sort Ascending"):visible').click();
menuOption = () => this.rootPage.locator('li[role="menuitem"]:has-text("Sort Ascending"):visible').click();
}
await this.waitForResponse({

16
tests/playwright/pages/Dashboard/Grid/index.ts

@ -79,10 +79,8 @@ export class GridPage extends BasePage {
await this._fillRow({ index, columnHeader, value: rowValue });
const clickOnColumnHeaderToSave = this.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
const clickOnColumnHeaderToSave = () =>
this.get().locator(`[data-title="${columnHeader}"]`).locator(`span[title="${columnHeader}"]`).click();
if (networkValidation) {
await this.waitForResponse({
@ -92,6 +90,7 @@ export class GridPage extends BasePage {
responseJsonMatcher: resJson => resJson?.[columnHeader] === rowValue,
});
} else {
await clickOnColumnHeaderToSave();
await this.rootPage.waitForTimeout(300);
}
@ -111,10 +110,8 @@ export class GridPage extends BasePage {
}) {
await this._fillRow({ index, columnHeader, value });
const clickOnColumnHeaderToSave = this.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
const clickOnColumnHeaderToSave = () =>
this.get().locator(`[data-title="${columnHeader}"]`).locator(`span[title="${columnHeader}"]`).click();
if (networkValidation) {
await this.waitForResponse({
@ -128,6 +125,7 @@ export class GridPage extends BasePage {
responseJsonMatcher: resJson => resJson?.[columnHeader] === value,
});
} else {
await clickOnColumnHeaderToSave();
await this.rootPage.waitForTimeout(300);
}
@ -231,7 +229,7 @@ export class GridPage extends BasePage {
async clickPagination({ page }: { page: string }) {
await this.waitForResponse({
uiAction: (await this.pagination({ page })).click(),
uiAction: () => (await this.pagination({ page })).click(),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: '/views/',
responseJsonMatcher: resJson => resJson?.pageInfo,

2
tests/playwright/pages/Dashboard/Import/ImportTemplate.ts

@ -65,7 +65,7 @@ export class ImportTemplatePage extends BasePage {
await this.waitForResponse({
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
uiAction: this.get().locator('button:has-text("Import"):visible').click(),
uiAction: () => this.get().locator('button:has-text("Import"):visible').click(),
});
await this.dashboard.waitForTabRender({
title: tblList[0],

2
tests/playwright/pages/Dashboard/Settings/Acl.ts

@ -19,7 +19,7 @@ export class AclPage extends BasePage {
async save() {
await this.waitForResponse({
uiAction: this.get().locator(`button:has-text("Save")`).click(),
uiAction: () => this.get().locator(`button:has-text("Save")`).click(),
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/visibility-rules',
});

2
tests/playwright/pages/Dashboard/Settings/Miscellaneous.ts

@ -14,7 +14,7 @@ export class MiscSettingsPage extends BasePage {
}
async clickShowM2MTables() {
const clickAction = this.get().locator('input[type="checkbox"]').first().click();
const clickAction = () => this.get().locator('input[type="checkbox"]').first().click();
await this.waitForResponse({
uiAction: clickAction,
requestUrlPathToMatch: 'tables?includeM2M',

6
tests/playwright/pages/Dashboard/TreeView.ts

@ -54,7 +54,7 @@ export class TreeViewPage extends BasePage {
if (networkResponse === true) {
await this.waitForResponse({
uiAction: this.get().locator(`.nc-project-tree-tbl-${title}`).click(),
uiAction: () => this.get().locator(`.nc-project-tree-tbl-${title}`).click(),
httpMethodsToMatch: ['GET'],
requestUrlPathToMatch: `/api/v1/db/data/noco/`,
responseJsonMatcher: json => json.pageInfo,
@ -74,7 +74,7 @@ export class TreeViewPage extends BasePage {
await this.dashboard.get().getByPlaceholder('Enter table name').fill(title);
await this.waitForResponse({
uiAction: this.dashboard.get().locator('button:has-text("Submit")').click(),
uiAction: () => this.dashboard.get().locator('button:has-text("Submit")').click(),
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: `/api/v1/db/meta/projects/`,
responseJsonMatcher: json => json.title === title && json.type === 'table',
@ -101,7 +101,7 @@ export class TreeViewPage extends BasePage {
await this.dashboard.get().locator('div.nc-project-menu-item:has-text("Delete")').click();
await this.waitForResponse({
uiAction: this.dashboard.get().locator('button:has-text("Yes")').click(),
uiAction: () => this.dashboard.get().locator('button:has-text("Yes")').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
});

12
tests/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -38,10 +38,8 @@ export class ViewSidebarPage extends BasePage {
private async createView({ title, locator }: { title: string; locator: Locator }) {
await locator.click();
await this.rootPage.locator('input[id="form_item_title"]:visible').fill(title);
const submitAction = this.rootPage
.locator('.ant-modal-content')
.locator('button:has-text("Submit"):visible')
.click();
const submitAction = () =>
this.rootPage.locator('.ant-modal-content').locator('button:has-text("Submit"):visible').click();
await this.waitForResponse({
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/tables/',
@ -128,10 +126,8 @@ export class ViewSidebarPage extends BasePage {
.locator(`[data-testid="view-sidebar-view-actions-${title}"]`)
.locator('.nc-view-copy-icon')
.click();
const submitAction = this.rootPage
.locator('.ant-modal-content')
.locator('button:has-text("Submit"):visible')
.click();
const submitAction = () =>
this.rootPage.locator('.ant-modal-content').locator('button:has-text("Submit"):visible').click();
await this.waitForResponse({
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/tables/',

2
tests/playwright/pages/Dashboard/WebhookForm/index.ts

@ -105,7 +105,7 @@ export class WebhookFormPage extends BasePage {
}
async save() {
const saveAction = this.saveButton.click();
const saveAction = () => this.saveButton.click();
await this.waitForResponse({
uiAction: saveAction,
requestUrlPathToMatch: '/hooks',

2
tests/playwright/pages/Dashboard/common/Cell/RatingCell.ts

@ -16,7 +16,7 @@ export class RatingCellPageObject extends BasePage {
async select({ index, columnHeader, rating }: { index?: number; columnHeader: string; rating: number }) {
await this.waitForResponse({
uiAction: this.get({ index, columnHeader }).locator('.ant-rate-star > div').nth(rating).click(),
uiAction: () => this.get({ index, columnHeader }).locator('.ant-rate-star > div').nth(rating).click(),
httpMethodsToMatch: ['POST', 'PATCH'],
requestUrlPathToMatch: 'api/v1/db/data/noco/',
});

2
tests/playwright/pages/Dashboard/common/Cell/index.ts

@ -75,7 +75,7 @@ export class CellPageObject extends BasePage {
async inCellExpand({ index, columnHeader }: CellProps) {
await this.get({ index, columnHeader }).hover();
await this.waitForResponse({
uiAction: this.get({ index, columnHeader }).locator('.nc-action-icon >> nth=0').click(),
uiAction: () => this.get({ index, columnHeader }).locator('.nc-action-icon >> nth=0').click(),
requestUrlPathToMatch: '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});

15
tests/playwright/pages/Dashboard/common/Toolbar/Fields.ts

@ -17,10 +17,8 @@ export class ToolbarFieldsPage extends BasePage {
// todo: Click and toggle are similar method. Remove one of them
async toggle({ title, isLocallySaved }: { title: string; isLocallySaved?: boolean }) {
await this.toolbar.clickFields();
const toggleColumn = this.get()
.locator(`[data-testid="nc-fields-menu-${title}"]`)
.locator('input[type="checkbox"]')
.click();
const toggleColumn = () =>
this.get().locator(`[data-testid="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]').click();
await this.waitForResponse({
uiAction: toggleColumn,
@ -43,7 +41,8 @@ export class ToolbarFieldsPage extends BasePage {
async click({ title, isLocallySaved }: { title: string; isLocallySaved?: boolean }) {
await this.waitForResponse({
uiAction: this.get().locator(`[data-testid="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]').click(),
uiAction: () =>
this.get().locator(`[data-testid="nc-fields-menu-${title}"]`).locator('input[type="checkbox"]').click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
@ -53,7 +52,7 @@ export class ToolbarFieldsPage extends BasePage {
async hideAll({ isLocallySaved }: { isLocallySaved?: boolean } = {}) {
await this.toolbar.clickFields();
await this.waitForResponse({
uiAction: this.get().locator(`button:has-text("Hide all")`).click(),
uiAction: () => this.get().locator(`button:has-text("Hide all")`).click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
@ -63,7 +62,7 @@ export class ToolbarFieldsPage extends BasePage {
async showAll({ isLocallySaved }: { isLocallySaved?: boolean } = {}) {
await this.toolbar.clickFields();
await this.waitForResponse({
uiAction: this.get().locator(`button:has-text("Show all")`).click(),
uiAction: () => this.get().locator(`button:has-text("Show all")`).click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});
@ -73,7 +72,7 @@ export class ToolbarFieldsPage extends BasePage {
async toggleShowSystemFields({ isLocallySaved }: { isLocallySaved?: boolean } = {}) {
await this.toolbar.clickFields();
await this.waitForResponse({
uiAction: this.get().locator(`.nc-fields-show-system-fields`).click(),
uiAction: () => this.get().locator(`.nc-fields-show-system-fields`).click(),
requestUrlPathToMatch: isLocallySaved ? '/api/v1/db/public/' : '/api/v1/db/data/noco/',
httpMethodsToMatch: ['GET'],
});

4
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -137,7 +137,7 @@ export class ToolbarFilterPage extends BasePage {
}
break;
default:
fillFilter = this.rootPage.locator('.nc-filter-value-select > input').last().fill(value);
fillFilter = () => this.rootPage.locator('.nc-filter-value-select > input').last().fill(value);
await this.waitForResponse({
uiAction: fillFilter,
httpMethodsToMatch: ['GET'],
@ -154,7 +154,7 @@ export class ToolbarFilterPage extends BasePage {
await this.toolbar.clickFilter();
if (networkValidation) {
await this.waitForResponse({
uiAction: this.get().locator('.nc-filter-item-remove-btn').click(),
uiAction: () => this.get().locator('.nc-filter-item-remove-btn').click(),
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/filters/',
});

11
tests/playwright/pages/Dashboard/common/Toolbar/Sort.ts

@ -61,11 +61,12 @@ export class ToolbarSortPage extends BasePage {
// await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.rootPage.locator('.nc-sort-dir-select').last().click();
const selectSortDirection = this.rootPage
.locator('.nc-dropdown-sort-dir')
.locator('.ant-select-item')
.nth(isAscending ? 0 : 1)
.click();
const selectSortDirection = () =>
this.rootPage
.locator('.nc-dropdown-sort-dir')
.locator('.ant-select-item')
.nth(isAscending ? 0 : 1)
.click();
await this.waitForResponse({
uiAction: selectSortDirection,

6
tests/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -82,10 +82,10 @@ export class ToolbarPage extends BasePage {
}: { networkValidation?: boolean } = {}) {
const menuOpen = await this.filter.get().isVisible();
const clickFilterAction = 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 clickFilterAction;
await clickFilterAction();
await this.filter.get().waitFor({ state: 'hidden' });
} else {
if (networkValidation) {
@ -96,7 +96,7 @@ export class ToolbarPage extends BasePage {
httpMethodsToMatch: ['GET'],
});
} else {
await clickFilterAction;
await clickFilterAction();
}
}
}

8
tests/playwright/pages/ProjectsPage/index.ts

@ -26,7 +26,7 @@ export class ProjectsPage extends BasePage {
await this.rootPage.locator(`.nc-metadb-project-name`).waitFor();
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
const createProjectSubmitAction = this.rootPage.locator(`button:has-text("Create")`).click();
const createProjectSubmitAction = () => this.rootPage.locator(`button:has-text("Create")`).click();
await this.waitForResponse({
uiAction: createProjectSubmitAction,
httpMethodsToMatch: ['POST'],
@ -42,7 +42,7 @@ export class ProjectsPage extends BasePage {
}
async reloadProjects() {
const reloadUiAction = this.get().locator('[data-testid="projects-reload-button"]').click();
const reloadUiAction = () => this.get().locator('[data-testid="projects-reload-button"]').click();
await this.waitForResponse({
uiAction: reloadUiAction,
requestUrlPathToMatch: '/api/v1/db/meta/projects',
@ -119,7 +119,7 @@ export class ProjectsPage extends BasePage {
await this.get().locator(`[data-testid="delete-project-${title}"]`).click();
const deleteProjectAction = 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'],
@ -149,7 +149,7 @@ export class ProjectsPage extends BasePage {
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');
const submitAction = () => project.locator('input.nc-metadb-project-name').press('Enter');
await this.waitForResponse({
uiAction: submitAction,
requestUrlPathToMatch: 'api/v1/db/meta/projects/',

2
tests/playwright/pages/SharedForm/index.ts

@ -16,7 +16,7 @@ export class SharedFormPage extends BasePage {
async submit() {
await this.waitForResponse({
uiAction: this.get().getByTestId('shared-form-submit-button').click(),
uiAction: () => this.get().getByTestId('shared-form-submit-button').click(),
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/rows',
});

Loading…
Cancel
Save