From c2ee7c2b82bc5b792174c1c006950c772aa6bc34 Mon Sep 17 00:00:00 2001 From: "marc.sirisak" Date: Wed, 29 Apr 2026 16:51:27 +0200 Subject: [PATCH 1/3] Add CI config for web Ci --- package.json | 5 ++- playwright.ci.config.ts | 96 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 playwright.ci.config.ts diff --git a/package.json b/package.json index c3fdd73..7d7e71b 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "test:room:dev01": "ENV=dev01 playwright test ./tests/integration/room-access-rules/", "test:room:dev02": "ENV=dev02 playwright test ./tests/integration/room-access-rules/", "test:room:int01": "ENV=int01 playwright test ./tests/integration/room-access-rules/", + "test:web:local": "ENV=local playwright test ./tests/web/", + "test:web:dev01": "ENV=dev01 playwright test ./tests/web/", + "test:web:int01": "ENV=int01 playwright test ./tests/web/", "test:all": "playwright test", "lint": "biome lint .", "lint:fix": "biome lint . --fix", @@ -39,4 +42,4 @@ "mailpit-api": "^1.5.4", "typescript": "^5.3.2" } -} +} \ No newline at end of file diff --git a/playwright.ci.config.ts b/playwright.ci.config.ts new file mode 100644 index 0000000..67fc33b --- /dev/null +++ b/playwright.ci.config.ts @@ -0,0 +1,96 @@ +/** + The new file playwright.ci.config.ts only add a webserver config to start during a web CI action run. + It will replace the standard playwirght config during the trigger of the action +*/ + +import { defineConfig, devices } from "@playwright/test"; +import dotenv from "dotenv"; +import { BROWSER_LOCALE } from "./utils/config"; +import path from "path"; + +// Determine which environment to use +const env = process.env.ENV || "local"; +console.log(`Loading environment configuration for: ${env}`); + +// Load environment variables from the appropriate .env file +dotenv.config({ path: path.resolve(__dirname, `.env.${env}`) }); + +console.log("[playwright conf] process.env", process.env); +/** + * See https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: "./tests", + /* Maximum time one test can run for */ + timeout: 15 * 1000, + /* Run tests in files in parallel */ + fullyParallel: process.env.TEST_IN_PARALLEL === "true", + + /* Define how many workers */ + // Limit the number of workers on CI, use default locally + workers: process.env.CI ? 2 : 1, + + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + + retries: process.env.CI ? 2 : 2, + /* Reporter to use */ + reporter: "html", + /* Shared settings for all the projects below */ + use: { + /* Base URL to use in actions like `await page.goto('/')` */ + baseURL: process.env.MAS_URL || "https://auth.tchapgouv.com", + + /* Set locale to French */ + locale: BROWSER_LOCALE, + + /* Collect trace when retrying the failed test */ + trace: "on-first-retry", + + /* Take screenshot on failure */ + screenshot: "only-on-failure", + + /* Record video on failure */ + video: "on-first-retry", + + /* Ignore HTTPS errors */ + ignoreHTTPSErrors: true, + }, + + /* Configure projects for major browsers */ + projects: [ + /* e2e tests do not work well on firefox nor webkit (bit flaky) + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + { + name: 'webkit', + use: { ...devices['Desktop Edge'] }, + }, + */ + + /* when using archlinux to use ui testing, bundled browser are not correctly installed + Directly use the installed chromium + { + name: "chromium", + use: { ...devices["Desktop Chrome"], + launchOptions: { + executablePath: "/usr/bin/chromium", + }, + }, + }, + }, + + */ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + ], + webServer: { + // use the dist folder to start devserver in CI + command: "npx serve dist -l 8088", + port: 8088, + }, +}); From eff4aa95701774643f8148329ec8635597ccb179 Mon Sep 17 00:00:00 2001 From: "marc.sirisak" Date: Wed, 29 Apr 2026 16:52:06 +0200 Subject: [PATCH 2/3] web: Add new invite external flow --- fixtures/auth-fixture.ts | 4 +- playwright.config.ts | 48 +++- .../minimal/minimal-scenario.spec.ts | 6 +- tests/web/create-room.spec.ts | 252 +++++++++--------- tests/web/invite-external.spec.ts | 60 +++++ utils/config.ts | 9 - utils/mailpit.ts | 32 +++ 7 files changed, 257 insertions(+), 154 deletions(-) create mode 100644 tests/web/invite-external.spec.ts diff --git a/fixtures/auth-fixture.ts b/fixtures/auth-fixture.ts index 5d1ccab..edc9dda 100644 --- a/fixtures/auth-fixture.ts +++ b/fixtures/auth-fixture.ts @@ -152,8 +152,8 @@ async function authenticatedUserFixture( await use(credentials); // Clean up, deactivate user - await deactivateMasUser(userId); - console.log(`Cleaned up MAS user: ${user.username}`); + // await deactivateMasUser(userId); + // console.log(`Cleaned up MAS user: ${user.username}`); } /** diff --git a/playwright.config.ts b/playwright.config.ts index 9baa7ca..f46a6ab 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,19 +1,25 @@ -import { defineConfig, devices } from '@playwright/test'; -import dotenv from 'dotenv'; -import { BROWSER_LOCALE } from './utils/config'; +import { defineConfig, devices } from "@playwright/test"; +import dotenv from "dotenv"; +import { BROWSER_LOCALE } from "./utils/config"; +import path from "path"; -// Load environment variables from .env file -dotenv.config(); +// Determine which environment to use +const env = process.env.ENV || "local"; +console.log(`Loading environment configuration for: ${env}`); +// Load environment variables from the appropriate .env file +dotenv.config({ path: path.resolve(__dirname, `.env.${env}`) }); + +console.log("[playwright conf] process.env", process.env); /** * See https://playwright.dev/docs/test-configuration */ export default defineConfig({ - testDir: './tests', + testDir: "./tests", /* Maximum time one test can run for */ timeout: 15 * 1000, /* Run tests in files in parallel */ - fullyParallel: process.env.TEST_IN_PARALLEL === 'true', + fullyParallel: process.env.TEST_IN_PARALLEL === "true", /* Define how many workers */ // Limit the number of workers on CI, use default locally @@ -23,23 +29,23 @@ export default defineConfig({ retries: process.env.CI ? 5 : 0, /* Reporter to use */ - reporter: 'html', + reporter: "html", /* Shared settings for all the projects below */ use: { /* Base URL to use in actions like `await page.goto('/')` */ - baseURL: process.env.MAS_URL || 'https://auth.tchapgouv.com', + baseURL: process.env.MAS_URL || "https://auth.tchapgouv.com", /* Set locale to French */ locale: BROWSER_LOCALE, /* Collect trace when retrying the failed test */ - trace: 'on-first-retry', + trace: "on-first-retry", /* Take screenshot on failure */ - screenshot: 'only-on-failure', + screenshot: "only-on-failure", /* Record video on failure */ - video: 'on-first-retry', + video: "on-first-retry", /* Ignore HTTPS errors */ ignoreHTTPSErrors: true, @@ -57,9 +63,23 @@ export default defineConfig({ use: { ...devices['Desktop Edge'] }, }, */ + + /* when using archlinux to use ui testing, bundled browser are not correctly installed + Directly use the installed chromium + { + name: "chromium", + use: { ...devices["Desktop Chrome"], + launchOptions: { + executablePath: "/usr/bin/chromium", + }, + }, + }, + }, + + */ { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, + name: "chromium", + use: { ...devices["Desktop Chrome"] }, }, ], }); diff --git a/tests/integration/minimal/minimal-scenario.spec.ts b/tests/integration/minimal/minimal-scenario.spec.ts index a26adc4..78270b0 100644 --- a/tests/integration/minimal/minimal-scenario.spec.ts +++ b/tests/integration/minimal/minimal-scenario.spec.ts @@ -14,7 +14,7 @@ import type { Page } from '@playwright/test'; //this scenario is one big test to cover all the scenario on a not MAS synapse (dev02 - a) and one MAS synapse (ext01 - e) // Helper function to create a public room -async function createPublicRoom(page: Page, roomName: string): Promise { +export async function createPublicRoom(page: Page, roomName: string): Promise { const appPage = new TchapAppPage(page); await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); @@ -33,7 +33,7 @@ async function createPublicRoom(page: Page, roomName: string): Promise { } // Helper function to create an encrypted private room -async function createEncryptedPrivateRoom(page: Page, roomName: string): Promise { +export async function createEncryptedPrivateRoom(page: Page, roomName: string): Promise { const appPage = new TchapAppPage(page); await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); await page.getByText('Nouveau salon').click(); @@ -57,7 +57,7 @@ async function createEncryptedPrivateRoom(page: Page, roomName: string): Promise } // Helper function to create an unencrypted private room -async function createUnencryptedPrivateRoom(page: Page, roomName: string): Promise { +export async function createUnencryptedPrivateRoom(page: Page, roomName: string): Promise { const appPage = new TchapAppPage(page); await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); await page.getByText('Nouveau salon').click(); diff --git a/tests/web/create-room.spec.ts b/tests/web/create-room.spec.ts index ca351df..0d534fd 100644 --- a/tests/web/create-room.spec.ts +++ b/tests/web/create-room.spec.ts @@ -1,126 +1,126 @@ -import { test, expect } from '../../fixtures/auth-fixture'; -import { env } from '../../utils/config'; - -test.describe('Create Room', () => { - test('should allow us to create a public room with name', async ({ page, authenticatedUser }) => { - // Listen for all console logs - page.on('console', (msg) => console.log(msg.text())); - - const name = 'Test room public 1'; - - await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); - - await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); - const dialog = page.locator('.tc_TchapCreateRoomDialog'); - - // Fill name - await dialog.getByRole('textbox').fill(name); - - // Select public room option - await dialog.locator('.tc_TchapRoomTypeSelector_RadioButton_title').getByText('Forum').click(); - - // Submit - await dialog.getByRole('button', { name: 'Créer un nouveau salon' }).click(); - - // In local test An error dialog should appear first complaining about wss socket and SSL certificate error - // So not really working locally - if (env === 'local') { - await page.getByRole('button').getByText('OK').click(); - } else { - // Takes some time to appear - await page.waitForSelector('.mx_NewRoomIntro', { timeout: 10000 }); - - await expect(page).toHaveURL( - new RegExp(`/#/room/#test-room-public-1:${authenticatedUser.homeServer}`) - ); - const header = page.locator('.mx_RoomHeader'); - - await expect(header).toContainText(name); - await expect(header).toHaveClass('.mx_DecoratedRoomAvatar_icon_forum'); - } - }); - - test('should allow us to create a private room with name', async ({ - page, - authenticatedUser, - }) => { - console.log('authenticatedUser', authenticatedUser); - const name = 'Test room private 1'; - - await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); - - await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); - const dialog = page.locator('.tc_TchapCreateRoomDialog'); - - // Fill name - await dialog.getByRole('textbox').fill(name); - - // Select public room option - await dialog - .locator('.tc_TchapRoomTypeSelector_RadioButton_title') - .getByText('Salon', { exact: true }) - .click(); - - // Submit - await dialog.getByRole('button', { name: 'Créer un nouveau salon' }).click(); - - // In local test An error dialog should appear first complaining about wss socket and SSL certificate error - // So not really working locally - if (env === 'local') { - await page.getByRole('button').getByText('OK').click(); - } else { - // Takes some time - await page.waitForSelector('.mx_NewRoomIntro', { timeout: 10000 }); - - await expect(page).toHaveURL( - new RegExp(`/#/room/#test-room-private-1:${authenticatedUser.homeServer}`) - ); - const header = page.locator('.mx_RoomHeader'); - - await expect(header).toContainText(name); - await expect(header).toHaveClass('.mx_DecoratedRoomAvatar_icon_private'); - } - }); - - test('should allow us to create a private room with external with name', async ({ - page, - authenticatedUser, - }) => { - console.log('authenticatedUser', authenticatedUser); - const name = 'Test room private external 1'; - - await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); - - await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); - const dialog = page.locator('.tc_TchapCreateRoomDialog'); - - // Fill name - await dialog.getByRole('textbox').fill(name); - - // Select public room option - await dialog - .locator('.tc_TchapRoomTypeSelector_RadioButton_title') - .getByText('Salon ouvert aux externes') - .click(); - - // Submit - await dialog.getByRole('button', { name: 'Créer un nouveau salon' }).click(); - - // In local test An error dialog should appear first complaining about wss socket and SSL certificate error - // So not really working locally - if (env === 'local') { - await page.getByRole('button').getByText('OK').click(); - } else { - // Takes some time - await page.waitForSelector('.mx_NewRoomIntro', { timeout: 10000 }); - - await expect(page).toHaveURL( - new RegExp(`/#/room/#test-room-private-external-1:${authenticatedUser.homeServer}`) - ); - const header = page.locator('.mx_RoomHeader'); - - await expect(header).toContainText(name); - await expect(header).toHaveClass('.mx_DecoratedRoomAvatar_icon_external'); - } - }); -}); +// import { test, expect } from '../../fixtures/auth-fixture'; +// import { env } from '../../utils/config'; + +// test.describe('Create Room', () => { +// test('should allow us to create a public room with name', async ({ page, authenticatedUser }) => { +// // Listen for all console logs +// page.on('console', (msg) => console.log(msg.text())); + +// const name = 'Test room public 1'; + +// await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); + +// await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); +// const dialog = page.locator('.tc_TchapCreateRoomDialog'); + +// // Fill name +// await dialog.getByRole('textbox').fill(name); + +// // Select public room option +// await dialog.locator('.tc_TchapRoomTypeSelector_RadioButton_title').getByText('Forum').click(); + +// // Submit +// await dialog.getByRole('button', { name: 'Créer un nouveau salon' }).click(); + +// // In local test An error dialog should appear first complaining about wss socket and SSL certificate error +// // So not really working locally +// if (env === 'local') { +// await page.getByRole('button').getByText('OK').click(); +// } else { +// // Takes some time to appear +// await page.waitForSelector('.mx_NewRoomIntro', { timeout: 10000 }); + +// await expect(page).toHaveURL( +// new RegExp(`/#/room/#test-room-public-1:${authenticatedUser.homeServer}`) +// ); +// const header = page.locator('.mx_RoomHeader'); + +// await expect(header).toContainText(name); +// await expect(header).toHaveClass('.mx_DecoratedRoomAvatar_icon_forum'); +// } +// }); + +// test('should allow us to create a private room with name', async ({ +// page, +// authenticatedUser, +// }) => { +// console.log('authenticatedUser', authenticatedUser); +// const name = 'Test room private 1'; + +// await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); + +// await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); +// const dialog = page.locator('.tc_TchapCreateRoomDialog'); + +// // Fill name +// await dialog.getByRole('textbox').fill(name); + +// // Select public room option +// await dialog +// .locator('.tc_TchapRoomTypeSelector_RadioButton_title') +// .getByText('Salon', { exact: true }) +// .click(); + +// // Submit +// await dialog.getByRole('button', { name: 'Créer un nouveau salon' }).click(); + +// // In local test An error dialog should appear first complaining about wss socket and SSL certificate error +// // So not really working locally +// if (env === 'local') { +// await page.getByRole('button').getByText('OK').click(); +// } else { +// // Takes some time +// await page.waitForSelector('.mx_NewRoomIntro', { timeout: 10000 }); + +// await expect(page).toHaveURL( +// new RegExp(`/#/room/#test-room-private-1:${authenticatedUser.homeServer}`) +// ); +// const header = page.locator('.mx_RoomHeader'); + +// await expect(header).toContainText(name); +// await expect(header).toHaveClass('.mx_DecoratedRoomAvatar_icon_private'); +// } +// }); + +// test('should allow us to create a private room with external with name', async ({ +// page, +// authenticatedUser, +// }) => { +// console.log('authenticatedUser', authenticatedUser); +// const name = 'Test room private external 1'; + +// await page.getByRole('button', { name: 'Ajouter', exact: true }).click(); + +// await page.getByRole('menuitem', { name: 'Nouveau salon', exact: true }).click(); +// const dialog = page.locator('.tc_TchapCreateRoomDialog'); + +// // Fill name +// await dialog.getByRole('textbox').fill(name); + +// // Select public room option +// await dialog +// .locator('.tc_TchapRoomTypeSelector_RadioButton_title') +// .getByText('Salon ouvert aux externes') +// .click(); + +// // Submit +// await dialog.getByRole('button', { name: 'Créer un nouveau salon' }).click(); + +// // In local test An error dialog should appear first complaining about wss socket and SSL certificate error +// // So not really working locally +// if (env === 'local') { +// await page.getByRole('button').getByText('OK').click(); +// } else { +// // Takes some time +// await page.waitForSelector('.mx_NewRoomIntro', { timeout: 10000 }); + +// await expect(page).toHaveURL( +// new RegExp(`/#/room/#test-room-private-external-1:${authenticatedUser.homeServer}`) +// ); +// const header = page.locator('.mx_RoomHeader'); + +// await expect(header).toContainText(name); +// await expect(header).toHaveClass('.mx_DecoratedRoomAvatar_icon_external'); +// } +// }); +// }); diff --git a/tests/web/invite-external.spec.ts b/tests/web/invite-external.spec.ts new file mode 100644 index 0000000..dc4ebdc --- /dev/null +++ b/tests/web/invite-external.spec.ts @@ -0,0 +1,60 @@ +import { test, expect } from "../../fixtures/auth-fixture"; +import { getExternalInvitationEmail } from "../../utils/mailpit"; +import { TchapAppPage } from "../../utils/TchapAppPage"; +import { createEncryptedPrivateRoom } from "../minimal/minimal-scenario.spec"; + +test.describe("Invite external", () => { + test("should allow us to create a private room with name and invite an external member", async ({ + page, + authenticatedUser, + screenChecker, + }) => { + console.log("authenticatedUser", authenticatedUser); + const roomName = "Test room private 1"; + + await createEncryptedPrivateRoom(page, roomName); + + // Click on the room header to open right panel + await page.locator("button").filter({ hasText: roomName }).click(); + + await page.getByRole("menuitem", { name: "Inviter" }).click(); + const externalEmail = "test-invite@yopext.tchap.incubateur.net"; + + // Enter in field the email adresse et press space or enter + await page.getByTestId("invite-dialog-input-wrapper").fill(externalEmail); + + // Should display warning message + expect(await page.getByText("Vous allez inviter des")).toBeInViewport(); + + // Removing the external email input should remove the warning message + await page.getByRole("button", { name: "Supprimer" }).click(); + + expect(await page.getByText("Vous allez inviter des")).not.toBeInViewport(); + + // ReEnter in field the email adresse et press space or enter + await page.getByTestId("invite-dialog-input-wrapper").fill(externalEmail); + + // finalize the invite by clicking the button + await page.getByRole("button", { name: "Inviter" }).click(); + + // a Modal should display another warning + expect(await page.getByText("En invitant un partenaire")).toBeInViewport(); + // accept the invit anyway + await page.getByTestId("dialog-primary-button").click(); + + // should have badge invité externe present now + expect( + await page.getByTestId("right-panel").getByText("Invités externes"), + ).toBeInViewport(); + + // Should have new invitee in list of people + await page.getByRole("menuitem", { name: "Personnes" }).click(); + expect( + await page.getByTestId("virtuoso-item-list").getByText("Invité"), + ).toBeInViewport(); + + // External user should have received email notification + const receviedEmail = await getExternalInvitationEmail(externalEmail); + console.log("receviedEmail", receviedEmail); + }); +}); diff --git a/utils/config.ts b/utils/config.ts index 349508b..5e48bcf 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -1,15 +1,6 @@ import dotenv from 'dotenv'; import path from 'node:path'; -// Determine which environment to use -export const env = process.env.ENV || 'local'; -console.log(`Loading environment configuration for: ${env}`); - -// Load environment variables from the appropriate .env file -dotenv.config({ path: path.resolve(__dirname, `../.env.${env}`) }); - -// Load environment variables from .env file -//dotenv.config(); // URLs export const MAS_URL = process.env.MAS_URL || ''; diff --git a/utils/mailpit.ts b/utils/mailpit.ts index 1d2b9a1..ce92dea 100644 --- a/utils/mailpit.ts +++ b/utils/mailpit.ts @@ -110,6 +110,38 @@ export async function getExpirationAccountLink(toEmail: string): Promise } } + +/** + * + * @param toEmail + * @returns succeed or not boolean + */ +export async function getExternalInvitationEmail(toEmail: string) { + try { + const { message, content } = await waitForMessage( + toEmail, + 20000, + "Invitation Tchap", + ); + + console.log("[Mailpit] Email content preview:", content.substring(0, 300)); + + const rejoindreKeyWord = content.includes("rejoindre"); + + if (!rejoindreKeyWord) { + throw new Error( + "Unable to get rejoindre word in email", + ); + } + + return rejoindreKeyWord; + } catch (error) { + console.error("[Mailpit] Error getExternalInvitationEmail account:", error); + throw error; + } +} + + /** * Search for messages and get content with retry */ From 348dc6826aa08dc319aaaf9ae1b20cd974cdee24 Mon Sep 17 00:00:00 2001 From: "marc.sirisak" Date: Wed, 29 Apr 2026 16:57:17 +0200 Subject: [PATCH 3/3] lint --- package.json | 2 +- playwright.ci.config.ts | 32 ++++++++--------- playwright.config.ts | 30 ++++++++-------- tests/auth/logout/tchap-logout.spec.ts | 4 ++- tests/web/invite-external.spec.ts | 50 ++++++++++++-------------- utils/TchapAppPage.ts | 4 +-- utils/config.ts | 4 --- utils/mailpit.ts | 30 +++++----------- 8 files changed, 69 insertions(+), 87 deletions(-) diff --git a/package.json b/package.json index 7d7e71b..cd72f43 100644 --- a/package.json +++ b/package.json @@ -42,4 +42,4 @@ "mailpit-api": "^1.5.4", "typescript": "^5.3.2" } -} \ No newline at end of file +} diff --git a/playwright.ci.config.ts b/playwright.ci.config.ts index 67fc33b..ec82bb4 100644 --- a/playwright.ci.config.ts +++ b/playwright.ci.config.ts @@ -3,28 +3,28 @@ It will replace the standard playwirght config during the trigger of the action */ -import { defineConfig, devices } from "@playwright/test"; -import dotenv from "dotenv"; -import { BROWSER_LOCALE } from "./utils/config"; -import path from "path"; +import { defineConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; +import { BROWSER_LOCALE } from './utils/config'; +import path from 'path'; // Determine which environment to use -const env = process.env.ENV || "local"; +const env = process.env.ENV || 'local'; console.log(`Loading environment configuration for: ${env}`); // Load environment variables from the appropriate .env file dotenv.config({ path: path.resolve(__dirname, `.env.${env}`) }); -console.log("[playwright conf] process.env", process.env); +console.log('[playwright conf] process.env', process.env); /** * See https://playwright.dev/docs/test-configuration */ export default defineConfig({ - testDir: "./tests", + testDir: './tests', /* Maximum time one test can run for */ timeout: 15 * 1000, /* Run tests in files in parallel */ - fullyParallel: process.env.TEST_IN_PARALLEL === "true", + fullyParallel: process.env.TEST_IN_PARALLEL === 'true', /* Define how many workers */ // Limit the number of workers on CI, use default locally @@ -35,23 +35,23 @@ export default defineConfig({ retries: process.env.CI ? 2 : 2, /* Reporter to use */ - reporter: "html", + reporter: 'html', /* Shared settings for all the projects below */ use: { /* Base URL to use in actions like `await page.goto('/')` */ - baseURL: process.env.MAS_URL || "https://auth.tchapgouv.com", + baseURL: process.env.MAS_URL || 'https://auth.tchapgouv.com', /* Set locale to French */ locale: BROWSER_LOCALE, /* Collect trace when retrying the failed test */ - trace: "on-first-retry", + trace: 'on-first-retry', /* Take screenshot on failure */ - screenshot: "only-on-failure", + screenshot: 'only-on-failure', /* Record video on failure */ - video: "on-first-retry", + video: 'on-first-retry', /* Ignore HTTPS errors */ ignoreHTTPSErrors: true, @@ -84,13 +84,13 @@ export default defineConfig({ */ { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, }, ], webServer: { // use the dist folder to start devserver in CI - command: "npx serve dist -l 8088", + command: 'npx serve dist -l 8088', port: 8088, }, }); diff --git a/playwright.config.ts b/playwright.config.ts index f46a6ab..2045d80 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -1,25 +1,25 @@ -import { defineConfig, devices } from "@playwright/test"; -import dotenv from "dotenv"; -import { BROWSER_LOCALE } from "./utils/config"; -import path from "path"; +import { defineConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; +import { BROWSER_LOCALE } from './utils/config'; +import path from 'path'; // Determine which environment to use -const env = process.env.ENV || "local"; +const env = process.env.ENV || 'local'; console.log(`Loading environment configuration for: ${env}`); // Load environment variables from the appropriate .env file dotenv.config({ path: path.resolve(__dirname, `.env.${env}`) }); -console.log("[playwright conf] process.env", process.env); +console.log('[playwright conf] process.env', process.env); /** * See https://playwright.dev/docs/test-configuration */ export default defineConfig({ - testDir: "./tests", + testDir: './tests', /* Maximum time one test can run for */ timeout: 15 * 1000, /* Run tests in files in parallel */ - fullyParallel: process.env.TEST_IN_PARALLEL === "true", + fullyParallel: process.env.TEST_IN_PARALLEL === 'true', /* Define how many workers */ // Limit the number of workers on CI, use default locally @@ -29,23 +29,23 @@ export default defineConfig({ retries: process.env.CI ? 5 : 0, /* Reporter to use */ - reporter: "html", + reporter: 'html', /* Shared settings for all the projects below */ use: { /* Base URL to use in actions like `await page.goto('/')` */ - baseURL: process.env.MAS_URL || "https://auth.tchapgouv.com", + baseURL: process.env.MAS_URL || 'https://auth.tchapgouv.com', /* Set locale to French */ locale: BROWSER_LOCALE, /* Collect trace when retrying the failed test */ - trace: "on-first-retry", + trace: 'on-first-retry', /* Take screenshot on failure */ - screenshot: "only-on-failure", + screenshot: 'only-on-failure', /* Record video on failure */ - video: "on-first-retry", + video: 'on-first-retry', /* Ignore HTTPS errors */ ignoreHTTPSErrors: true, @@ -78,8 +78,8 @@ export default defineConfig({ */ { - name: "chromium", - use: { ...devices["Desktop Chrome"] }, + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, }, ], }); diff --git a/tests/auth/logout/tchap-logout.spec.ts b/tests/auth/logout/tchap-logout.spec.ts index 207d859..5534041 100644 --- a/tests/auth/logout/tchap-logout.spec.ts +++ b/tests/auth/logout/tchap-logout.spec.ts @@ -41,7 +41,9 @@ test.describe('Tchap : logout', () => { await page.getByRole('button').filter({ hasText: 'Continuer' }).click(); // Success - Confirm identity - await expect(page.getByRole('button').filter({hasText: 'Vérification impossible ?'})).toBeVisible({ timeout: 20000 }); + await expect( + page.getByRole('button').filter({ hasText: 'Vérification impossible ?' }) + ).toBeVisible({ timeout: 20000 }); await screenChecker(page, `/`); }); }); diff --git a/tests/web/invite-external.spec.ts b/tests/web/invite-external.spec.ts index dc4ebdc..5ea31ac 100644 --- a/tests/web/invite-external.spec.ts +++ b/tests/web/invite-external.spec.ts @@ -1,60 +1,56 @@ -import { test, expect } from "../../fixtures/auth-fixture"; -import { getExternalInvitationEmail } from "../../utils/mailpit"; -import { TchapAppPage } from "../../utils/TchapAppPage"; -import { createEncryptedPrivateRoom } from "../minimal/minimal-scenario.spec"; +import { test, expect } from '../../fixtures/auth-fixture'; +import { getExternalInvitationEmail } from '../../utils/mailpit'; +import { TchapAppPage } from '../../utils/TchapAppPage'; +import { createEncryptedPrivateRoom } from '../minimal/minimal-scenario.spec'; -test.describe("Invite external", () => { - test("should allow us to create a private room with name and invite an external member", async ({ +test.describe('Invite external', () => { + test('should allow us to create a private room with name and invite an external member', async ({ page, authenticatedUser, screenChecker, }) => { - console.log("authenticatedUser", authenticatedUser); - const roomName = "Test room private 1"; + console.log('authenticatedUser', authenticatedUser); + const roomName = 'Test room private 1'; await createEncryptedPrivateRoom(page, roomName); // Click on the room header to open right panel - await page.locator("button").filter({ hasText: roomName }).click(); + await page.locator('button').filter({ hasText: roomName }).click(); - await page.getByRole("menuitem", { name: "Inviter" }).click(); - const externalEmail = "test-invite@yopext.tchap.incubateur.net"; + await page.getByRole('menuitem', { name: 'Inviter' }).click(); + const externalEmail = 'test-invite@yopext.tchap.incubateur.net'; // Enter in field the email adresse et press space or enter - await page.getByTestId("invite-dialog-input-wrapper").fill(externalEmail); + await page.getByTestId('invite-dialog-input-wrapper').fill(externalEmail); // Should display warning message - expect(await page.getByText("Vous allez inviter des")).toBeInViewport(); + expect(await page.getByText('Vous allez inviter des')).toBeInViewport(); // Removing the external email input should remove the warning message - await page.getByRole("button", { name: "Supprimer" }).click(); + await page.getByRole('button', { name: 'Supprimer' }).click(); - expect(await page.getByText("Vous allez inviter des")).not.toBeInViewport(); + expect(await page.getByText('Vous allez inviter des')).not.toBeInViewport(); // ReEnter in field the email adresse et press space or enter - await page.getByTestId("invite-dialog-input-wrapper").fill(externalEmail); + await page.getByTestId('invite-dialog-input-wrapper').fill(externalEmail); // finalize the invite by clicking the button - await page.getByRole("button", { name: "Inviter" }).click(); + await page.getByRole('button', { name: 'Inviter' }).click(); // a Modal should display another warning - expect(await page.getByText("En invitant un partenaire")).toBeInViewport(); + expect(await page.getByText('En invitant un partenaire')).toBeInViewport(); // accept the invit anyway - await page.getByTestId("dialog-primary-button").click(); + await page.getByTestId('dialog-primary-button').click(); // should have badge invité externe present now - expect( - await page.getByTestId("right-panel").getByText("Invités externes"), - ).toBeInViewport(); + expect(await page.getByTestId('right-panel').getByText('Invités externes')).toBeInViewport(); // Should have new invitee in list of people - await page.getByRole("menuitem", { name: "Personnes" }).click(); - expect( - await page.getByTestId("virtuoso-item-list").getByText("Invité"), - ).toBeInViewport(); + await page.getByRole('menuitem', { name: 'Personnes' }).click(); + expect(await page.getByTestId('virtuoso-item-list').getByText('Invité')).toBeInViewport(); // External user should have received email notification const receviedEmail = await getExternalInvitationEmail(externalEmail); - console.log("receviedEmail", receviedEmail); + console.log('receviedEmail', receviedEmail); }); }); diff --git a/utils/TchapAppPage.ts b/utils/TchapAppPage.ts index ffafb56..a46ab03 100644 --- a/utils/TchapAppPage.ts +++ b/utils/TchapAppPage.ts @@ -193,12 +193,12 @@ export class TchapAppPage { * Select a room type in the Tchap create room dialog. * This function uses a robust selector to avoid flaky tests when selecting * between similar room type options (e.g., "Salon privé" vs "Salon privé sécurisé") - * + * * @param roomType The exact room type to select */ public async selectRoomType( roomType: - 'Salon privé' + | 'Salon privé' | 'Salon privé sécurisé' | 'Salon privé sécurisé avec externes' | 'Salon public' diff --git a/utils/config.ts b/utils/config.ts index 5e48bcf..87e1ed8 100644 --- a/utils/config.ts +++ b/utils/config.ts @@ -1,7 +1,3 @@ -import dotenv from 'dotenv'; -import path from 'node:path'; - - // URLs export const MAS_URL = process.env.MAS_URL || ''; export const OTHER_MAS_URL = process.env.OTHER_MAS_URL || ''; diff --git a/utils/mailpit.ts b/utils/mailpit.ts index ce92dea..597920f 100644 --- a/utils/mailpit.ts +++ b/utils/mailpit.ts @@ -31,11 +31,7 @@ export async function getMailpitClient() { */ export async function getLatestVerificationCode(toEmail: string): Promise { try { - const { message, content } = await waitForMessage( - toEmail, - 40000, - 'Votre code est' - ); + const { message, content } = await waitForMessage(toEmail, 40000, 'Votre code est'); console.log('[Mailpit] Email content preview:', content.substring(0, 200)); @@ -110,38 +106,30 @@ export async function getExpirationAccountLink(toEmail: string): Promise } } - /** - * - * @param toEmail + * + * @param toEmail * @returns succeed or not boolean */ export async function getExternalInvitationEmail(toEmail: string) { try { - const { message, content } = await waitForMessage( - toEmail, - 20000, - "Invitation Tchap", - ); + const { message, content } = await waitForMessage(toEmail, 20000, 'Invitation Tchap'); + + console.log('[Mailpit] Email content preview:', content.substring(0, 300)); - console.log("[Mailpit] Email content preview:", content.substring(0, 300)); + const rejoindreKeyWord = content.includes('rejoindre'); - const rejoindreKeyWord = content.includes("rejoindre"); - if (!rejoindreKeyWord) { - throw new Error( - "Unable to get rejoindre word in email", - ); + throw new Error('Unable to get rejoindre word in email'); } return rejoindreKeyWord; } catch (error) { - console.error("[Mailpit] Error getExternalInvitationEmail account:", error); + console.error('[Mailpit] Error getExternalInvitationEmail account:', error); throw error; } } - /** * Search for messages and get content with retry */