From 9d425aa3e24adabd89cb9f14cb7fc53236ac5650 Mon Sep 17 00:00:00 2001 From: Muhammed Mustafa Date: Wed, 19 Oct 2022 16:43:55 +0530 Subject: [PATCH] feat(testing): Improved flakyness --- scripts/playwright/pages/Base.ts | 50 +++++------- .../playwright/pages/Dashboard/Grid/index.ts | 76 +++++++++++++------ .../playwright/pages/Dashboard/TreeView.ts | 26 ++++--- .../pages/Dashboard/ViewSidebar/index.ts | 10 ++- .../pages/Dashboard/common/Toolbar/Filter.ts | 26 +++++-- .../pages/Dashboard/common/Toolbar/Sort.ts | 19 ++++- scripts/playwright/pages/Dashboard/index.ts | 15 ++++ scripts/playwright/playwright.config.ts | 2 +- .../tests/linkToAnotherRecord.spec.ts | 2 +- .../playwright/tests/viewGridShare.spec.ts | 5 ++ 10 files changed, 157 insertions(+), 74 deletions(-) diff --git a/scripts/playwright/pages/Base.ts b/scripts/playwright/pages/Base.ts index 44f1ff4205..27a3c6eef0 100644 --- a/scripts/playwright/pages/Base.ts +++ b/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; 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, + ]); } } diff --git a/scripts/playwright/pages/Dashboard/Grid/index.ts b/scripts/playwright/pages/Dashboard/Grid/index.ts index 3021745804..37b0af7b0c 100644 --- a/scripts/playwright/pages/Dashboard/Grid/index.ts +++ b/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(); } diff --git a/scripts/playwright/pages/Dashboard/TreeView.ts b/scripts/playwright/pages/Dashboard/TreeView.ts index ea76679754..9598ed7041 100644 --- a/scripts/playwright/pages/Dashboard/TreeView.ts +++ b/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'); } diff --git a/scripts/playwright/pages/Dashboard/ViewSidebar/index.ts b/scripts/playwright/pages/Dashboard/ViewSidebar/index.ts index d42fd0cea5..619e7170fb 100644 --- a/scripts/playwright/pages/Dashboard/ViewSidebar/index.ts +++ b/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 }) { diff --git a/scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts b/scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts index 5fb2ea6fa9..e6b6651fce 100644 --- a/scripts/playwright/pages/Dashboard/common/Toolbar/Filter.ts +++ b/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/", }) diff --git a/scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts b/scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts index 059e5701ff..59b0258e26 100644 --- a/scripts/playwright/pages/Dashboard/common/Toolbar/Sort.ts +++ b/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(); diff --git a/scripts/playwright/pages/Dashboard/index.ts b/scripts/playwright/pages/Dashboard/index.ts index a414bb1afa..599e316550 100644 --- a/scripts/playwright/pages/Dashboard/index.ts +++ b/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" }); diff --git a/scripts/playwright/playwright.config.ts b/scripts/playwright/playwright.config.ts index 8e6962d539..a7f454c6e4 100644 --- a/scripts/playwright/playwright.config.ts +++ b/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. diff --git a/scripts/playwright/tests/linkToAnotherRecord.spec.ts b/scripts/playwright/tests/linkToAnotherRecord.spec.ts index c2199b3d37..b46fa3e52a 100644 --- a/scripts/playwright/tests/linkToAnotherRecord.spec.ts +++ b/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" }); diff --git a/scripts/playwright/tests/viewGridShare.spec.ts b/scripts/playwright/tests/viewGridShare.spec.ts index b543f7be5d..d717699ab1 100644 --- a/scripts/playwright/tests/viewGridShare.spec.ts +++ b/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,