Browse Source

feat(testing): Improved flakyness

pull/3848/head
Muhammed Mustafa 2 years ago
parent
commit
9d425aa3e2
  1. 50
      scripts/playwright/pages/Base.ts
  2. 76
      scripts/playwright/pages/Dashboard/Grid/index.ts
  3. 26
      scripts/playwright/pages/Dashboard/TreeView.ts
  4. 10
      scripts/playwright/pages/Dashboard/ViewSidebar/index.ts
  5. 26
      scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  6. 19
      scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts
  7. 15
      scripts/playwright/pages/Dashboard/index.ts
  8. 2
      scripts/playwright/playwright.config.ts
  9. 2
      scripts/playwright/tests/linkToAnotherRecord.spec.ts
  10. 5
      scripts/playwright/tests/viewGridShare.spec.ts

50
scripts/playwright/pages/Base.ts

@ -26,42 +26,34 @@ export default abstract class BasePage {
}
async waitForResponse({
requestHttpMethod,
uiAction,
httpMethodsToMatch = [],
requestUrlPathToMatch,
responseJsonMatcher,
}: {
requestHttpMethod: string;
uiAction: Promise<any>;
requestUrlPathToMatch: string;
httpMethodsToMatch?: string[];
responseJsonMatcher?: ResponseSelector;
}) {
await this.rootPage.waitForResponse(async (res) => {
let isResJsonMatched = true;
if(responseJsonMatcher){
try {
isResJsonMatched = responseJsonMatcher(await res.json());
} catch (e) {
return false;
await Promise.all([
this.rootPage.waitForResponse(async (res) => {
let isResJsonMatched = true;
if(responseJsonMatcher){
try {
isResJsonMatched = responseJsonMatcher(await res.json());
} catch (e) {
return false;
}
}
}
return (
res.request().method() === requestHttpMethod &&
res.request().url().includes(requestUrlPathToMatch) &&
isResJsonMatched
);
});
}
async waitForResponseJson({
responseSelector,
}: {
responseSelector: ResponseSelector;
}) {
await this.rootPage.waitForResponse(async (res) => {
try {
return responseSelector(await res.json());
} catch (e) {
return false;
}
});
return (
res.request().url().includes(requestUrlPathToMatch) &&
httpMethodsToMatch.includes(res.request().method()) &&
isResJsonMatched
);
}),
uiAction,
]);
}
}

76
scripts/playwright/pages/Dashboard/Grid/index.ts

@ -39,11 +39,30 @@ export class GridPage extends BasePage {
return expect(await this.get().locator(".nc-grid-row").count()).toBe(count);
}
private async _fillRow({
index,
columnHeader,
value,
}: {
index: number;
columnHeader: string;
value: string;
}) {
const cell = this.cell.get({ index, columnHeader });
await this.cell.dblclick({
index,
columnHeader,
});
await cell.locator("input").fill(value);
}
async addNewRow({
index = 0,
columnHeader = "Title",
value,
}: { index?: number; columnHeader?: string; value?: string } = {}) {
const rowValue = value ?? `Row ${index}`;
const rowCount = await this.get().locator(".nc-grid-row").count();
await this.get().locator(".nc-grid-add-new-cell").click();
@ -51,30 +70,41 @@ export class GridPage extends BasePage {
.poll(async () => await this.get().locator(".nc-grid-row").count())
.toBe(rowCount + 1);
await this.editRow({ index, columnHeader, value });
await this._fillRow({ index, columnHeader, value: rowValue });
const clickOnColumnHeaderToSave = this.cell.grid
.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
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,
}: { index?: number; columnHeader?: string; value?: string } = {}) {
const cell = this.cell.get({ index, columnHeader });
await this.cell.dblclick({
index,
columnHeader,
});
await cell.locator("input").fill(value ?? `Row ${index}`);
await this.cell.grid
.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
await this.waitForResponseJson({
responseSelector: (resJson) => resJson?.[columnHeader] === value,
}: { index?: number; columnHeader?: string; value: string }) {
await this._fillRow({ index, columnHeader, value });
const clickOnColumnHeaderToSave = this.cell.grid
.get()
.locator(`[data-title="${columnHeader}"]`)
.locator(`span[title="${columnHeader}"]`)
.click();
await this.waitForResponse({
uiAction: clickOnColumnHeaderToSave,
requestUrlPathToMatch: "api/v1/db/data/noco",
httpMethodsToMatch: ["PATCH"],
responseJsonMatcher: (resJson) => resJson?.[columnHeader] === value,
});
}
@ -176,10 +206,12 @@ export class GridPage extends BasePage {
async clickPagination({ page }: { page: string }) {
(await this.pagination({ page })).click();
await this.waitForResponseJson({
responseSelector: (resJson) => resJson?.pageInfo,
});
await this.waitForResponse({
uiAction: (await this.pagination({ page })).click(),
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: "/views/",
responseJsonMatcher: (resJson) => resJson?.pageInfo,
})
await this.waitLoading();
}

26
scripts/playwright/pages/Dashboard/TreeView.ts

@ -30,11 +30,9 @@ export class TreeViewPage extends BasePage {
}
}
await this.get().locator(`.nc-project-tree-tbl-${title}`).click({
noWaitAfter: true,
});
await this.waitForResponse({
requestHttpMethod: "GET",
uiAction: this.get().locator(`.nc-project-tree-tbl-${title}`).click(),
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
responseJsonMatcher: (json) => json.title === title,
});
@ -50,9 +48,13 @@ export class TreeViewPage extends BasePage {
.get()
.locator('[placeholder="Enter table name"]')
.fill(title);
await this.dashboard.get().locator('button:has-text("Submit")').click(),
await this.waitForResponseJson({responseSelector:(json) => json.title === title && json.type === 'table'}),
await this.waitForResponse({
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',
});
await this.dashboard.waitForTabRender({ title });
@ -77,7 +79,7 @@ export class TreeViewPage extends BasePage {
}
async deleteTable({ title }: { title: string }) {
const tabCount = await this.rootPage.locator('.nc-container').count()
const tabCount = await this.dashboard.tabBar.locator('.ant-tabs-tab').count()
await this.get()
.locator(`.nc-project-tree-tbl-${title}`)
.click({ button: "right" });
@ -85,13 +87,13 @@ export class TreeViewPage extends BasePage {
.get()
.locator('div.nc-project-menu-item:has-text("Delete")')
.click();
await this.dashboard.get().locator('button:has-text("Yes")').click();
// await this.toastWait({ message: "Deleted table successfully" });
await this.waitForResponse({
requestHttpMethod: "DELETE",
uiAction: this.dashboard.get().locator('button:has-text("Yes")').click(),
httpMethodsToMatch: ["DELETE"],
requestUrlPathToMatch: `/api/v1/db/meta/tables/`,
});
await expect.poll(async () => await this.rootPage.locator('.nc-container').count() === tabCount - 1).toBe(true);
await expect.poll(async () => await this.dashboard.tabBar.locator('.ant-tabs-tab').count()).toBe(tabCount - 1);
(await this.rootPage.locator('.nc-container').last().elementHandle())?.waitForElementState('stable');
}

10
scripts/playwright/pages/Dashboard/ViewSidebar/index.ts

@ -34,11 +34,19 @@ export class ViewSidebarPage extends BasePage {
await this.rootPage
.locator('input[id="form_item_title"]:visible')
.fill(title);
await this.rootPage
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/",
uiAction: submitAction,
responseJsonMatcher: (json) => json.title === title,
});
await this.toastWait({ message: "View created successfully" });
// Todo: Wait for view to be rendered
await this.rootPage.waitForTimeout(1000);
}
async createGalleryView({ title }: { title: string }) {

26
scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -13,14 +13,17 @@ export class ToolbarFilterPage extends BasePage {
return this.rootPage.locator(`[pw-data="nc-filter-menu"]`);
}
// Todo: Handle the case of operator does not need a value
async addNew({
columnTitle,
opType,
value,
isLocallySaved,
}: {
columnTitle: string;
opType: string;
value: string;
isLocallySaved: boolean;
}) {
await this.toolbar.clickFilter();
@ -38,12 +41,21 @@ export class ToolbarFilterPage extends BasePage {
.locator(`.ant-select-item:has-text("${opType}")`)
.click();
await this.rootPage.locator(".nc-filter-value-select").last().fill(value);
const fillFilter = this.rootPage.locator(".nc-filter-value-select").last().fill(value);
await this.waitForResponse({
requestHttpMethod: "POST",
requestUrlPathToMatch: "/filters",
})
if (isLocallySaved) {
await this.waitForResponse({
uiAction: fillFilter,
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: `${value.replace(' ', '+')}`,
});
} else {
await this.waitForResponse({
uiAction: fillFilter,
httpMethodsToMatch: ["POST", "PATCH"],
requestUrlPathToMatch: "/filters",
});
}
await this.toolbar.clickFilter();
}
@ -57,9 +69,9 @@ export class ToolbarFilterPage extends BasePage {
async resetFilter() {
await this.toolbar.clickFilter();
await this.get().locator(".nc-filter-item-remove-btn").click();
await this.waitForResponse({
requestHttpMethod: "DELETE",
uiAction: this.get().locator(".nc-filter-item-remove-btn").click(),
httpMethodsToMatch: ["DELETE"],
requestUrlPathToMatch: "/api/v1/db/meta/filters/",
})

19
scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts

@ -16,9 +16,11 @@ export class ToolbarSortPage extends BasePage {
async addSort({
columnTitle,
isAscending,
isLocallySaved,
}: {
columnTitle: string;
isAscending: boolean;
isLocallySaved: boolean;
}) {
// open sort menu
await this.toolbar.clickSort();
@ -33,11 +35,26 @@ export class ToolbarSortPage extends BasePage {
.click();
await this.rootPage.locator(".nc-sort-dir-select").last().click();
await this.rootPage
const selectSortDirection = this.rootPage
.locator(".nc-dropdown-sort-dir")
.locator(".ant-select-item")
.nth(isAscending ? 0 : 1)
.click();
if(isLocallySaved) {
await this.waitForResponse({
uiAction: selectSortDirection,
httpMethodsToMatch: ["GET"],
requestUrlPathToMatch: `${isAscending ? "asc" : "desc"}`,
})
} else {
await this.waitForResponse({
uiAction: selectSortDirection,
httpMethodsToMatch: ["POST", "PATCH"],
requestUrlPathToMatch: "/sorts",
})
}
// close sort menu
await this.toolbar.clickSort();

15
scripts/playwright/pages/Dashboard/index.ts

@ -75,6 +75,21 @@ export class DashboardPage extends BasePage {
async waitForTabRender({ title }: { title: string }) {
await this.get().locator('[pw-data="grid-id-column"]').waitFor();
await this.tabBar
.locator(`.ant-tabs-tab-active:has-text("${title}")`)
.waitFor();
// wait active tab animation to finish
await expect
.poll(async () => {
return await this.tabBar
.locator(`[data-pw="nc-root-tabs-${title}"]`)
.evaluate((el) => {
return window.getComputedStyle(el).getPropertyValue("color");
});
})
.toBe("rgb(67, 81, 232)"); // active tab text color
await this.get()
.locator('[pw-data="grid-load-spinner"]')
.waitFor({ state: "hidden" });

2
scripts/playwright/playwright.config.ts

@ -13,7 +13,7 @@ require('dotenv').config();
const config: PlaywrightTestConfig = {
testDir: './tests',
/* Maximum time one test can run for. */
timeout: process.env.CI ? 80 * 1000 : 50 * 1000,
timeout: process.env.CI ? 80 * 1000 : 65 * 1000,
expect: {
/**
* Maximum time expect() should wait for the condition to be met.

2
scripts/playwright/tests/linkToAnotherRecord.spec.ts

@ -11,7 +11,7 @@ test.describe("LTAR create & update", () => {
dashboard = new DashboardPage(page, context.project);
});
test("LTAR", async () => {
test.only("LTAR", async () => {
// close 'Team & Auth' tab
await dashboard.closeTab({ title: "Team & Auth" });

5
scripts/playwright/tests/viewGridShare.spec.ts

@ -36,12 +36,14 @@ test.describe("Shared view", () => {
await dashboard.grid.toolbar.sort.addSort({
columnTitle: "District",
isAscending: false,
isLocallySaved: false
});
// filter
await dashboard.grid.toolbar.filter.addNew({
columnTitle: "Address",
value: "Ab",
opType: "is like",
isLocallySaved: false
});
mainPageLink = page.url();
@ -123,11 +125,13 @@ test.describe("Shared view", () => {
await sharedPage.grid.toolbar.sort.addSort({
columnTitle: "Address",
isAscending: true,
isLocallySaved: true,
});
await sharedPage.grid.toolbar.filter.addNew({
columnTitle: "District",
value: "Ta",
opType: "is like",
isLocallySaved: true,
});
await sharedPage.grid.toolbar.fields.toggle({ title: "LastUpdate" });
expectedColumns[6].isVisible = false;
@ -228,6 +232,7 @@ test.describe("Shared view", () => {
columnTitle: "Country",
value: "New Country",
opType: "is like",
isLocallySaved: true,
});
await sharedPage2.grid.cell.verify({
index: 0,

Loading…
Cancel
Save