From fd50559eeee9e7a3cac0ecd5c2f8d8018fddf094 Mon Sep 17 00:00:00 2001 From: falltrades Date: Wed, 17 Dec 2025 22:13:51 +0100 Subject: [PATCH 1/2] test(playwright): :recycle: refactor integration tests for parallel run --- .../integration-tests/user-flow.spec.ts | 59 +++++++++++++++++-- playwright/playwright.config.integration.ts | 4 +- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/playwright/integration-tests/user-flow.spec.ts b/playwright/integration-tests/user-flow.spec.ts index f2cd408e6..40fb3c321 100644 --- a/playwright/integration-tests/user-flow.spec.ts +++ b/playwright/integration-tests/user-flow.spec.ts @@ -8,13 +8,13 @@ import { } from '../e2e-tests/utils' const projectsToDelete: string[] = [] +const projectName = 'socleprojecttest' +const repositoryName = 'socle-project-test' +const destinationCluster = process.env.CONSOLE_DESTINATION_CLUSTER || 'cpin-app-hp' +projectsToDelete.push(projectName) -test.describe('Integration tests user flow', { tag: '@integ' }, () => { +test.describe('Integration tests user flow: project creation', { tag: '@integ' }, () => { test.describe.configure({ mode: 'serial' }) - const projectName = 'socleprojecttest' - const repositoryName = 'socle-project-test' - const destinationCluster = process.env.CONSOLE_DESTINATION_CLUSTER || 'cpin-app-hp' - projectsToDelete.push(projectName) test('Preliminary checks', { tag: '@replayable' }, async ({ page }) => { await page.goto(clientURL) @@ -68,6 +68,10 @@ test.describe('Integration tests user flow', { tag: '@integ' }, () => { await page.getByTestId('updateRepoBtn').click() await expect(page.getByRole('heading', { name: 'Opération en cours...' })).toBeVisible() }) +}) + +test.describe('Integration tests user flow: first checks', { tag: '@integ' }, () => { + test.describe.configure({ mode: 'parallel' }) test('Check Vault kv', { tag: '@replayable' }, async ({ page }) => { await page.goto(clientURL) @@ -119,6 +123,10 @@ test.describe('Integration tests user flow', { tag: '@integ' }, () => { page1.getByRole('link', { name: 'Status: Passed test-sonar' }), ).toBeVisible() }) +}) + +test.describe('Integration tests user flow: after pipelines checks', { tag: '@integ' }, () => { + test.describe.configure({ mode: 'parallel' }) test('Prepare ArgoCD deployment', async ({ page }) => { await page.goto(clientURL) @@ -177,6 +185,10 @@ test.describe('Integration tests user flow', { tag: '@integ' }, () => { // Check trivy scan result, hopefully will stay at C await expect(page1.getByRole('button', { name: 'C', exact: true })).toBeVisible() }) +}) + +test.describe('Integration tests user flow: deployment and metrics', { tag: '@integ' }, () => { + test.describe.configure({ mode: 'serial' }) test('ArgoCD deployment', { tag: '@replayable' }, async ({ page }) => { await page.goto(clientURL) @@ -200,6 +212,43 @@ test.describe('Integration tests user flow', { tag: '@integ' }, () => { await expect(page2.locator('html')).toContainText('Application is running') }) + test('Check Grafana', { tag: '@replayable' }, async ({ page }) => { + await page.goto(clientURL) + await signInCloudPiNative({ page, credentials: testUser }) + await page.getByTestId('menuMyProjects').click() + await page.getByRole('link', { name: projectName }).click() + await page.getByTestId('test-tab-services').click() + const page1Promise = page.waitForEvent('popup') + await page.getByRole('link', { name: 'Grafana' }).click() + const page1 = await page1Promise + await page1.getByRole('link', { name: 'Sign in with grafana-projects' }).click() + await expect(page1.getByRole('link', { name: 'Grafana', exact: true })).toBeVisible() + await page1.getByTestId('data-testid Toggle menu').click() + await page1.getByRole('button', { name: 'Expand section Dashboards' }).click() + await page1.getByRole('link', { name: 'Dashboards', exact: true }).click() + await page1.getByRole('link', { name: 'dso-grafana' }).click() + // Check if we can see some metrics + await page1.getByRole('link', { name: 'Kubernetes / Views /' }).click() + await expect(page1.getByText('0.100')).toBeVisible() // Cpu request + await expect(page1.getByText('0.500')).toBeVisible() // Cpu limit + await expect(page1.getByText('256')).toBeVisible() // Memory request + await expect(page1.getByText('512')).toBeVisible() // Memory limit + // Check if we can see some logs + await page1.getByTestId('data-testid dso-grafana breadcrumb').click() + await page1.getByRole('link', { name: 'Loki Kubernetes Logs' }).click() + await expect(page1.getByTestId('data-testid Panel status error').first()).not.toBeVisible() + await expect(page1.locator('.rc-drawer-mask')).not.toBeVisible() + await page1.getByTestId('data-testid TimePicker Open Button').click() + await page1.getByText('Last 1 hour').click() + await page1.locator('.css-13x53bc-Icon-topVerticalAlign').first().click() + await expect(page1.getByRole('cell', { name: 'app_kubernetes_io_name' }).nth(1)).toBeVisible() + await expect(page1.getByText('demo-java-helm').nth(1)).toBeVisible() + }) +}) + +test.describe('Integration tests user flow: Cleanup', { tag: '@integ' }, () => { + test.describe.configure({ mode: 'serial' }) + test('Cleanup user test data', async ({ page }) => { await page.goto(clientURL) await signInCloudPiNative({ page, credentials: testUser }) diff --git a/playwright/playwright.config.integration.ts b/playwright/playwright.config.integration.ts index 0a788d8a1..c4ac98a4e 100644 --- a/playwright/playwright.config.integration.ts +++ b/playwright/playwright.config.integration.ts @@ -13,13 +13,11 @@ dotenv.config({ */ export default defineConfig({ testDir: './integration-tests', - /* Run tests in files in parallel */ - fullyParallel: false, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, /* Retry on CI only */ retries: process.env.CI ? 3 : 1, - workers: 1, + workers: 3, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ reporter: 'html', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ From 9bdd3428f6242d766f7007c225623ee19496df88 Mon Sep 17 00:00:00 2001 From: falltrades Date: Fri, 19 Dec 2025 17:33:26 +0100 Subject: [PATCH 2/2] test(playwright): :alembic: add tests for project permissions --- playwright/README.md | 3 + playwright/config/console.ts | 10 +++ .../integration-tests/user-flow.spec.ts | 64 +++++++++++++++++-- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/playwright/README.md b/playwright/README.md index 683df6371..48e6728a8 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -53,6 +53,9 @@ $ export CONSOLE_ADMIN_EMAIL= $ export CONSOLE_TEST_USERNAME= $ export CONSOLE_TEST_PASSWORD= $ export CONSOLE_TEST_EMAIL= +$ export CONSOLE_SECOND_TEST_USERNAME= +$ export CONSOLE_SECOND_TEST_PASSWORD= +$ export CONSOLE_SECOND_TEST_EMAIL= $ export CONSOLE_GLOBAL_TIMEOUT='900000' $ export CONSOLE_EXPECT_TIMEOUT='900000' $ export CONSOLE_DESTINATION_CLUSTER= diff --git a/playwright/config/console.ts b/playwright/config/console.ts index 1e7ffe8f7..cef81bca4 100644 --- a/playwright/config/console.ts +++ b/playwright/config/console.ts @@ -48,6 +48,16 @@ export const tcolinUser: Credentials = { email: 'thibault.colin@test.com', } +// User for integration test +export const secondTestUser: Credentials = { + id: 'test', + username: process.env.CONSOLE_SECOND_TEST_USERNAME.trim(), + password: process.env.CONSOLE_SECOND_TEST_PASSWORD.trim(), + firstName: 'test', + lastName: 'test', + email: process.env.CONSOLE_SECOND_TEST_EMAIL.trim(), +} + export async function signInCloudPiNative({ page, credentials, diff --git a/playwright/integration-tests/user-flow.spec.ts b/playwright/integration-tests/user-flow.spec.ts index 40fb3c321..b9720dd15 100644 --- a/playwright/integration-tests/user-flow.spec.ts +++ b/playwright/integration-tests/user-flow.spec.ts @@ -1,5 +1,5 @@ import { expect, test } from '@playwright/test' -import { adminUser, testUser, clientURL, signInCloudPiNative } from '../config/console' +import { adminUser, secondTestUser, testUser, clientURL, signInCloudPiNative } from '../config/console' import { addProject, @@ -94,6 +94,37 @@ test.describe('Integration tests user flow: first checks', { tag: '@integ' }, () await expect(page1.getByRole('link', { name: 'forge-dso' })).not.toBeVisible() }) + test('Project permissions', async ({ page }) => { + await page.goto(clientURL) + await signInCloudPiNative({ page, credentials: testUser }) + await page.getByTestId('menuMyProjects').click() + await page.getByRole('link', { name: projectName }).click() + // Add user to project + await page.getByTestId('test-tab-team').click() + await page.getByTestId('addUserSuggestionInput').locator('input').fill(secondTestUser.email) + await page.getByTestId('addUserBtn').click() + await expect(page.getByRole('heading', { name: 'Reprovisionnement nécessaire' })).toBeVisible() + // Create read-only role + await page.getByTestId('test-tab-roles').click() + await page.getByTestId('addRoleBtn').click() + await page.getByTestId('roleNameInput').fill('readOnly') + await page.getByText('Afficher les secrets Permet d').click() + await page.getByText('Voir les environnements').click() + await page.getByText('Voir les dépôts Permet de').click() + await page.getByTestId('saveBtn').click() + await expect(page.getByText('Rôle mis à jour')).toBeVisible() + await expect(page.getByRole('heading', { name: 'Reprovisionnement nécessaire' })).toBeVisible() + // Add user to read-only role + await page.getByTestId('test-members').click() + await page.getByLabel('Rôles', { exact: true }).getByText(secondTestUser.email).click() + await expect(page.getByText('Rôle mis à jour')).toBeVisible() + await expect(page.getByRole('heading', { name: 'Reprovisionnement nécessaire' })).toBeVisible() + // Replay project hooks + await page.getByTestId('replayHooksBtn').click() + await expect(page.getByRole('heading', { name: 'Opération en cours...' })).toBeVisible() + await expect(page.getByText('Le projet a été reprovisionn')).toBeVisible() + }) + test('Pipelines run', { tag: '@replayable' }, async ({ page }) => { await page.goto(clientURL) await signInCloudPiNative({ page, credentials: testUser }) @@ -249,10 +280,30 @@ test.describe('Integration tests user flow: deployment and metrics', { tag: '@in test.describe('Integration tests user flow: Cleanup', { tag: '@integ' }, () => { test.describe.configure({ mode: 'serial' }) - test('Cleanup user test data', async ({ page }) => { + test('Remove permissions and user', async ({ page }) => { + await page.goto(clientURL) + await signInCloudPiNative({ page, credentials: testUser }) + await page.getByTestId('menuMyProjects').click() + await page.getByRole('link', { name: projectName }).click() + // Remove role membership + await page.getByTestId('test-tab-roles').click() + await page.getByRole('button', { name: 'readOnly' }).click() + await page.getByTestId('test-members').click() + await page.getByLabel('Rôles', { exact: true }).getByText(secondTestUser.email).click() + await expect(page.getByText('Rôle mis à jour')).toBeVisible() + await expect(page.getByRole('heading', { name: 'Reprovisionnement nécessaire' })).toBeVisible() + // Remove role + await page.getByTestId('test-general').click() + await page.getByTestId('deleteBtn').click() + await page.getByText('Rôle supprimé').click() + // Remove project membership + await page.getByTestId('test-tab-team').click() + await page.getByTitle(`Retirer ${secondTestUser.email} du`).click() + }) + + test('Remove stage', async ({ page }) => { await page.goto(clientURL) await signInCloudPiNative({ page, credentials: testUser }) - // ArgoCD deployment will be deleted when stage is deleted await page.getByTestId('menuMyProjects').click() await page.getByRole('link', { name: projectName }).click() await page.getByRole('cell', { name: 'integ' }).click() @@ -263,6 +314,7 @@ test.describe('Integration tests user flow: Cleanup', { tag: '@integ' }, () => { await expect( page.getByRole('cell', { name: 'Aucun environnement existant' }), ).toBeVisible() + // ArgoCD deployment will be deleted when stage is deleted await page.getByTestId('test-tab-services').click() const page1Promise = page.waitForEvent('popup') await page.getByRole('link', { name: 'ArgoCD DSO' }).click() @@ -271,7 +323,11 @@ test.describe('Integration tests user flow: Cleanup', { tag: '@integ' }, () => { await expect( page1.locator('span').filter({ hasText: `${projectName}-integ-socle-` }), ).not.toBeVisible() - // Remove repository from project + }) + + test('Remove repository from project', async ({ page }) => { + await page.goto(clientURL) + await signInCloudPiNative({ page, credentials: testUser }) await page.getByTestId('test-tab-resources').click() await page.getByRole('cell', { name: repositoryName }).click() await page.getByTestId('showDeleteRepoBtn').click()