diff --git a/.github/workflows/nextjs.yml b/.github/workflows/nextjs.yml index e86401d4..0bffc7d2 100644 --- a/.github/workflows/nextjs.yml +++ b/.github/workflows/nextjs.yml @@ -36,7 +36,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: "18" + node-version: "22" cache: ${{ steps.detect-package-manager.outputs.manager }} - name: Restore cache diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index 83757c75..adfffbb3 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -16,7 +16,7 @@ jobs: services: falkordb: - image: falkordb/falkordb:latest + image: falkordb/falkordb:v4.4.1 ports: - 6379:6379 @@ -34,9 +34,20 @@ jobs: npm install npm run build NEXTAUTH_SECRET=SECRET npm start & npx playwright test --reporter=dot,list + - name: Ensure required directories exist + run: | + mkdir -p playwright-report + mkdir -p playwright-report/artifacts - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report path: playwright-report/ retention-days: 30 + - name: Upload failed test screenshots + if: always() + uses: actions/upload-artifact@v4 + with: + name: failed-test-screenshots + path: playwright-report/artifacts/ + retention-days: 30 diff --git a/Dockerfile b/Dockerfile index db11d29c..dfd37408 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:18-alpine AS base +FROM node:22-alpine AS base # Install dependencies only when needed FROM base AS deps @@ -68,4 +68,4 @@ ENV HOSTNAME "0.0.0.0" # server.js is created by next build from the standalone output # https://nextjs.org/docs/pages/api-reference/next-config-js/output -CMD ["node", "server.js"] \ No newline at end of file +CMD ["node", "server.js"] diff --git a/components/ui/table.tsx b/components/ui/table.tsx index dbe1675d..6e98a5b1 100644 --- a/components/ui/table.tsx +++ b/components/ui/table.tsx @@ -11,7 +11,7 @@ const Table = React.forwardRef< HTMLTableElement, TableProps >(({ className, parentClassName, ...props }, ref) => ( -
+
this.page.locator(`//tbody//tr[@data-id='${role}']/td[3]/div/div/button[1]`) } + private get toastCloseBtn(): Locator { + return this.page.locator("//li[@role='status']/button"); + } + + private get tableContent(): Locator { + return this.page.locator("//div[@id='tableContent']"); + } + async modifyRoleValue(role: string, input: string): Promise { await this.roleContentValue(role).hover(); await this.EditRoleButton(role).click(); @@ -33,4 +41,11 @@ export default class SettingsConfigPage extends BasePage { return value } + async clickOnToastCloseBtn(): Promise{ + await this.toastCloseBtn.click(); + } + + async scrollToBottomInTable(): Promise { + await this.tableContent.evaluate((el) => el.scrollTo(0, el.scrollHeight)); + } } \ No newline at end of file diff --git a/e2e/logic/api/apiCalls.ts b/e2e/logic/api/apiCalls.ts index 26881060..1697766c 100644 --- a/e2e/logic/api/apiCalls.ts +++ b/e2e/logic/api/apiCalls.ts @@ -35,6 +35,7 @@ export default class ApiCalls { async getSettingsRoleValue(roleName: string, data?: any): Promise { const result = await getRequest(urls.api.settingsConfig + roleName, data) const jsonData = await result.json(); + console.log("api calls res:", jsonData, " role: ", roleName); return jsonData } diff --git a/e2e/tests/auth.setup.ts b/e2e/tests/auth.setup.ts index 97c85499..f516bc55 100644 --- a/e2e/tests/auth.setup.ts +++ b/e2e/tests/auth.setup.ts @@ -13,6 +13,7 @@ setup("admin authentication", async () => { try { const browserWrapper = new BrowserWrapper(); const loginPage = await browserWrapper.createNewPage(LoginPage, urls.loginUrl); + await browserWrapper.setPageToFullScreen(); await loginPage.clickOnConnect(); await loginPage.dismissDialogAtStart(); const context = browserWrapper.getContext(); @@ -37,6 +38,7 @@ userRoles.forEach(({ name, file, userName }) => { try { const browserWrapper = new BrowserWrapper(); const loginPage = await browserWrapper.createNewPage(LoginPage, urls.loginUrl); + await browserWrapper.setPageToFullScreen(); await loginPage.connectWithCredentials(userName, user.password); await loginPage.dismissDialogAtStart(); const context = browserWrapper.getContext(); diff --git a/e2e/tests/settingsConfig.spec.ts b/e2e/tests/settingsConfig.spec.ts index 9b36bb4a..8f753fdc 100644 --- a/e2e/tests/settingsConfig.spec.ts +++ b/e2e/tests/settingsConfig.spec.ts @@ -17,7 +17,7 @@ test.describe('Settings Tests', () => { await browser.closeBrowser(); }) - Data.inputDataRejectsZero.forEach(({ input, description, expected }) => { + Data.inputDataRejectsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxQueuedQueries} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() @@ -25,56 +25,85 @@ test.describe('Settings Tests', () => { await apiCall.modifySettingsRole(roles.maxQueuedQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxQueuedQueries) - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataRejectsZero.length - 1) { + await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") + } + }); + }) + + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { + test(`@admin Modify ${roles.TimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + const apiCall = new ApiCalls() + await new Promise(resolve => { setTimeout(resolve, 1000) }); + await apiCall.modifySettingsRole(roles.TimeOut, input) + await settingsConfigPage.refreshPage() + const value = await settingsConfigPage.getRoleContentValue(roles.TimeOut) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.TimeOut, "1000") + } }); }) - Data.maxTimeOut.forEach(({ input, description, expected }) => { + Data.maxTimeOut.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxTimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.maxTimeOut, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxTimeOut) - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.maxTimeOut.length - 1) { + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.defaultTimeOut} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.defaultTimeOut, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.defaultTimeOut) - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.resultSetSize} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.resultSetSize, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.resultSetSize) - expect(value === input).toBe(expected) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.resultSetSize, "10000") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.queryMemCapacity} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.queryMemCapacity, input) await settingsConfigPage.refreshPage() - const value = await settingsConfigPage.getRoleContentValue(roles.queryMemCapacity) - await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") // update to default values - expect(value === input).toBe(expected) + const value = await settingsConfigPage.getRoleContentValue(roles.queryMemCapacity) + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.vKeyMaxEntityCount} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() @@ -82,10 +111,13 @@ test.describe('Settings Tests', () => { await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.vKeyMaxEntityCount) expect(value === input).toBe(expected) + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") + } }); }) - Data.CMDData.forEach(({ input, description, expected }) => { + Data.CMDData.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.cmdInfo} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() @@ -93,36 +125,120 @@ test.describe('Settings Tests', () => { await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.cmdInfo) expect(value === input).toBe(expected) + if (index === Data.CMDData.length - 1) { + await apiCall.modifySettingsRole(roles.cmdInfo, "yes") + } }); }) - Data.inputDataAcceptsZero.forEach(({ input, description, expected }) => { + Data.inputDataAcceptsZero.forEach(({ input, description, expected }, index) => { test(`@admin Modify ${roles.maxInfoQueries} via API validation via UI: Input value: ${input} description: ${description}`, async () => { const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) const apiCall = new ApiCalls() await apiCall.modifySettingsRole(roles.maxInfoQueries, input) await settingsConfigPage.refreshPage() const value = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries) - expect(value === input).toBe(expected) - }); - }) - - Data.roleModificationData.forEach(({ role, input, description, expected }) => { - test(`@admin Modify ${role} via UI validation via API: Input value: ${input} description: ${description}`, async () => { - const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) - await settingsConfigPage.modifyRoleValue(role, input) - const apiCall = new ApiCalls() - let value = String((await apiCall.getSettingsRoleValue(role)).config[1]); - // Convert numeric values to yes/no for boolean settings - if (value === '1') { - value = 'yes'; - } else if (value === '0') { - value = 'no'; + console.log(value); + expect(value === input).toBe(expected); + if (index === Data.inputDataAcceptsZero.length - 1) { + await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); } - await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") // update to default values - expect(value === input).toBe(expected) }); }) + test(`@admin Modify maxQueuedQueries via UI validation via API: Input value: 24`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.maxQueuedQueries, "24") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.maxQueuedQueries)).config[1]); + expect(value === "24").toBe(true); + await apiCall.modifySettingsRole(roles.maxQueuedQueries, "25") + }); + + test(`@admin Modify TimeOut via UI validation via API: Input value: 1001`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.TimeOut, "1001") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.TimeOut)).config[1]); + expect(value === "1001").toBe(true); + await apiCall.modifySettingsRole(roles.TimeOut, "1000") + }); + + test(`@admin Modify maxTimeOut via UI validation via API: Input value: 1`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.maxTimeOut, "1") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.maxTimeOut)).config[1]); + expect(value === "1").toBe(true); + await apiCall.modifySettingsRole(roles.maxTimeOut, "0") + }); + + test(`@admin Modify defaultTimeOut via UI validation via API: Input value: 1`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.defaultTimeOut, "1") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.defaultTimeOut)).config[1]); + expect(value === "1").toBe(true); + await apiCall.modifySettingsRole(roles.defaultTimeOut, "0") + }); + + test(`@admin Modify resultSetSize via UI validation via API: Input value: 10001`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.resultSetSize, "10001") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.resultSetSize)).config[1]); + expect(value === "10001").toBe(true); + await apiCall.modifySettingsRole(roles.resultSetSize, "10000") + }); + + test(`@admin Modify queryMemCapacity via UI validation via API: Input value: 1`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.queryMemCapacity, "1") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.queryMemCapacity)).config[1]); + expect(value === "1").toBe(true); + await apiCall.modifySettingsRole(roles.queryMemCapacity, "0") + }); + + test(`@admin Modify vKeyMaxEntityCount via UI validation via API: Input value: 100001`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.vKeyMaxEntityCount, "100001") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.vKeyMaxEntityCount)).config[1]); + expect(value === "100001").toBe(true); + await apiCall.modifySettingsRole(roles.vKeyMaxEntityCount, "100000") + }); + + test(`@admin Modify cmdInfo via UI validation via API: Input value: no`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.cmdInfo, "no") + const apiCall = new ApiCalls() + let value = String((await apiCall.getSettingsRoleValue(roles.cmdInfo)).config[1]); + value = value === '1' ? 'yes' : value === '0' ? 'no' : value; + expect(value === "no").toBe(true); + await apiCall.modifySettingsRole(roles.cmdInfo, "yes") + }); + + test(`@admin Modify maxInfoQueries via UI validation via API: Input value: 999`, async () => { + const settingsConfigPage = await browser.createNewPage(SettingsConfigPage, urls.settingsUrl) + await settingsConfigPage.modifyRoleValue(roles.maxInfoQueries, "999") + await settingsConfigPage.refreshPage(); + await settingsConfigPage.scrollToBottomInTable(); + const res = await settingsConfigPage.getRoleContentValue(roles.maxInfoQueries); + console.log("ui value: ", res); + + await new Promise(resolve => { setTimeout(resolve, 3000) }); + const apiCall = new ApiCalls() + let value; + for (let i = 0; i < 5; i++) { + value = String((await apiCall.getSettingsRoleValue(roles.maxInfoQueries)).config[1]); + if (value === "999") break; + await new Promise(resolve => setTimeout(resolve, 1500)); + } + + console.log("api value:", value); + expect(value).toBe("999"); + await apiCall.modifySettingsRole(roles.maxInfoQueries, "1000"); + }); }) \ No newline at end of file diff --git a/playwright.config.ts b/playwright.config.ts index b705a6f5..4bc5ebd2 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -21,7 +21,8 @@ export default defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + reporter: [['html', { outputFolder: 'playwright-report' }]], + outputDir: 'playwright-report/artifacts', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ @@ -29,6 +30,7 @@ export default defineConfig({ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'on-first-retry', + screenshot: 'only-on-failure', }, /* Configure projects for major browsers */