diff --git a/tests/playwright/pages/Base.ts b/tests/playwright/pages/Base.ts index ae8db5bf72..b2064df98d 100644 --- a/tests/playwright/pages/Base.ts +++ b/tests/playwright/pages/Base.ts @@ -1,4 +1,4 @@ -import { Locator, Page } from '@playwright/test'; +import { expect, Locator, Page } from '@playwright/test'; import { readFileSync } from 'fs'; type ResponseSelector = (json: any) => boolean; @@ -16,6 +16,12 @@ export default abstract class BasePage { await this.rootPage.locator('.ant-message .ant-message-notice-content', { hasText: message }).last().isVisible(); } + async verifyLastToast({ message }: { message: string }) { + await expect( + this.rootPage.locator('.ant-message .ant-message-notice-content', { hasText: message }).last() + ).toHaveText(message); + } + async waitForResponse({ // Playwright action that triggers the request i.e locatorSomething.click() uiAction, @@ -24,18 +30,20 @@ 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, timeout, + responseStatusCodeToMatch = 200, }: { uiAction: () => Promise; requestUrlPathToMatch: string; httpMethodsToMatch?: string[]; responseJsonMatcher?: ResponseSelector; timeout?: number; + responseStatusCodeToMatch?: number; }) { const [res] = await Promise.all([ this.rootPage.waitForResponse( res => res.url().includes(requestUrlPathToMatch) && - res.status() === 200 && + res.status() === responseStatusCodeToMatch && httpMethodsToMatch.includes(res.request().method()), timeout ? { timeout } : undefined ), diff --git a/tests/playwright/pages/Dashboard/Grid/Column/index.ts b/tests/playwright/pages/Dashboard/Grid/Column/index.ts index 097951c51f..d7b598fd79 100644 --- a/tests/playwright/pages/Dashboard/Grid/Column/index.ts +++ b/tests/playwright/pages/Dashboard/Grid/Column/index.ts @@ -375,6 +375,20 @@ export class ColumnPageObject extends BasePage { await this.rootPage.waitForTimeout(200); } + async saveFail({ errorMessage }: { errorMessage?: string } = {}) { + await this.waitForResponse({ + uiAction: async () => await this.get().locator('button:has-text("Save")').click(), + requestUrlPathToMatch: 'api/v1/db/meta', + httpMethodsToMatch: ['GET', 'PATCH'], + responseStatusCodeToMatch: 400, + }); + await this.verifyLastToast({ + message: errorMessage, + }); + await this.get().waitFor({ state: 'visible' }); + await this.rootPage.waitForTimeout(200); + } + async verify({ title, isVisible = true }: { title: string; isVisible?: boolean }) { if (!isVisible) { return await expect(this.getColumnHeader(title)).not.toBeVisible(); diff --git a/tests/playwright/tests/db/columns/columnFormula.spec.ts b/tests/playwright/tests/db/columns/columnFormula.spec.ts index b8b0ee4dd6..fb6f06040d 100644 --- a/tests/playwright/tests/db/columns/columnFormula.spec.ts +++ b/tests/playwright/tests/db/columns/columnFormula.spec.ts @@ -1,7 +1,7 @@ import { test } from '@playwright/test'; import { DashboardPage } from '../../../pages/Dashboard'; import setup, { NcContext, unsetup } from '../../../setup'; -import { enableQuickRun, isPg, isSqlite } from '../../../setup/db'; +import { enableQuickRun, isMysql, isPg, isSqlite } from '../../../setup/db'; // Add formula to be verified here & store expected results for 5 rows // Column data from City table (Sakila DB) @@ -19,6 +19,7 @@ const formulaDataByDbType = (context: NcContext, index: number) => { { formula: '1 + 1', result: ['2', '2', '2', '2', '2'], + unSupDbType: [], }, { formula: 'ADD({CityId}, {CountryId}) + AVG({CityId}, {CountryId}) + LEN({City})', @@ -92,6 +93,7 @@ const formulaDataByDbType = (context: NcContext, index: number) => { { formula: 'VALUE("12ab-c345")', result: ['-12345', '-12345', '-12345', '-12345', '-12345'], + unSupDbType: ['sqlite3'], }, { formula: 'TRUE()', @@ -101,19 +103,6 @@ const formulaDataByDbType = (context: NcContext, index: number) => { formula: 'FALSE()', result: ['0', '0', '0', '0', '0'], }, - { - formula: 'REGEX_MATCH({City}, "a[a-z]a")', - result: ['0', '0', '0', '0', '1'], - }, - { - formula: 'REGEX_EXTRACT({City}, "a[a-z]a")', - result: ['', '', '', '', 'ana'], - }, - { - formula: 'REGEX_REPLACE({City}, "a[a-z]a","...")', - result: ['A Corua (La Corua)', 'Abha', 'Abu Dhabi', 'Acua', 'Ad...'], - }, - { formula: '"City Name: " & {City}', result: [ @@ -137,12 +126,30 @@ const formulaDataByDbType = (context: NcContext, index: number) => { formula: 'RECORD_ID()', result: ['1', '2', '3', '4', '5'], }, + { + formula: 'REGEX_MATCH({City}, "a[a-z]a")', + result: ['0', '0', '0', '0', '1'], + unSupDbType: ['sqlite3'], + }, + { + // TODO: this is bug in mysql, its being case in-sensitive. + formula: 'REGEX_EXTRACT({City}, "a[a-z]a")', + result: ['', '', '', '', 'ana'], + unSupDbType: ['sqlite3'], + }, + { + // TODO: this is bug in mysql, its being case in-sensitive. + formula: 'REGEX_REPLACE({City}, "a[a-z]a","...")', + result: ['A Corua (La Corua)', 'Abha', 'Abu Dhabi', 'Acua', 'Ad...'], + unSupDbType: ['sqlite3'], + }, ]; else return [ { formula: `DATETIME_DIFF("2023/10/14", "2023/01/12", "y")`, result: ['0', '0', '0', '0', '0'], + unSupDbType: [], }, { formula: `DATETIME_DIFF("2023-01-12", "2021-08-29", "y")`, @@ -180,9 +187,16 @@ const formulaDataByDbType = (context: NcContext, index: number) => { }, { formula: `LOG({CityId}) + EXP({CityId}) + POWER({CityId}, 3) + SQRT({CountryId})`, - result: isPg(context) - ? ['13.04566088154786', '24.74547123273205', '57.61253379902822', '126.94617671688704', '283.9609869087087'] - : ['13.04566088154786', '25.137588417628013', '58.23402483297667', '127.73041108667896', '284.8714548168068'], + result: + isPg(context) || isSqlite(context) + ? ['13.04566088154786', '24.74547123273205', '57.61253379902822', '126.94617671688704', '283.9609869087087'] + : [ + '13.04566088154786', + '25.137588417628013', + '58.23402483297667', + '127.73041108667896', + '284.8714548168068', + ], }, { formula: `NOW()`, @@ -245,26 +259,29 @@ test.describe('Virtual Columns', () => { async function formulaTestSpec(index: number) { // close 'Team & Auth' tab const formulaData = formulaDataByDbType(context, index); + const dbType = context.base.sources[0].type; await dashboard.closeTab({ title: 'Team & Auth' }); await dashboard.treeView.openTable({ title: 'City' }); - // Create formula column + // Create dummy formula column which will then be updated for every testcase await dashboard.grid.column.create({ title: 'NC_MATH_0', type: 'Formula', - formula: formulaData[1].formula, + formula: '1 + 1', }); // verify different formula's - for (let i = 1; i < formulaData.length; i++) { - // Sqlite does not support log function - if (isSqlite(context) && formulaData[i].formula.includes('LOG(')) continue; - + for (let i = 0; i < formulaData.length; i++) { await dashboard.grid.column.openEdit({ title: 'NC_MATH_0', type: 'Formula', formula: formulaData[i].formula, }); + if (formulaData[i].unSupDbType?.includes(dbType)) { + // assert for message not supported or greyed out save button. + await dashboard.grid.column.saveFail({ errorMessage: 'Invalid Formula' }); + continue; + } await dashboard.grid.column.save({ isUpdated: true }); if (formulaData[i].formula !== `NOW()`) { await formulaResultVerify({