diff --git a/src/frontend/apps/e2e/.env b/src/frontend/apps/e2e/.env new file mode 100644 index 00000000..7979a0bd --- /dev/null +++ b/src/frontend/apps/e2e/.env @@ -0,0 +1,14 @@ +PORT=3000 +BASE_URL=http://localhost:3000 +CUSTOM_SIGN_IN=false +TEST_INSTANCE_ONLY=false +SIGN_IN_EL_TRIGGER=Start Writing +FIRST_NAME=E2E +SIGN_IN_USERNAME_CHROMIUM=user.test@chromium.test +SIGN_IN_USERNAME_WEBKIT=user.test@webkit.com +SIGN_IN_USERNAME_FIREFOX=user.test@firefox.com +# To test server to server API calls +SERVER_TO_SERVER_API_TOKENS='server-api-token' +SUB_CHROMIUM=user.test@chromium.test +SUB_WEBKIT=user.test@webkit.com +SUB_FIREFOX=user.test@firefox.com \ No newline at end of file diff --git a/src/frontend/apps/e2e/.env.example b/src/frontend/apps/e2e/.env.example new file mode 100644 index 00000000..9271b6bc --- /dev/null +++ b/src/frontend/apps/e2e/.env.example @@ -0,0 +1,21 @@ +PORT=3000 +BASE_URL=http://localhost:3000 +TEST_INSTANCE_ONLY=false +CUSTOM_SIGN_IN=false +SIGN_IN_EL_TRIGGER=Start Writing +FIRST_NAME=E2E +SIGN_IN_USERNAME_CHROMIUM=user.test@chromium.test +SIGN_IN_USERNAME_WEBKIT=user.test@webkit.com +SIGN_IN_USERNAME_FIREFOX=user.test@firefox.com +# Used only on instance with custom sign in +SIGN_IN_EL_USERNAME_INPUT= +SIGN_IN_EL_USERNAME_VALIDATION= +SIGN_IN_EL_PASSWORD_INPUT= +SIGN_IN_PASSWORD_CHROMIUM= +SIGN_IN_PASSWORD_WEBKIT= +SIGN_IN_PASSWORD_FIREFOX= +# To test server to server API calls +SERVER_TO_SERVER_API_TOKENS='server-api-token' +SUB_CHROMIUM=user.test@chromium.test +SUB_WEBKIT=user.test@webkit.com +SUB_FIREFOX=user.test@firefox.com \ No newline at end of file diff --git a/src/frontend/apps/e2e/.gitignore b/src/frontend/apps/e2e/.gitignore index c4488d95..594bc882 100644 --- a/src/frontend/apps/e2e/.gitignore +++ b/src/frontend/apps/e2e/.gitignore @@ -5,3 +5,4 @@ blob-report/ playwright/.auth/ playwright/.cache/ screenshots/ +.env.local \ No newline at end of file diff --git a/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts b/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts index b569b662..5ccee219 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/auth.setup.ts @@ -1,6 +1,6 @@ import { FullConfig, FullProject, chromium, expect } from '@playwright/test'; -import { keyCloakSignIn } from './utils-common'; +import { SignIn } from './utils-signin'; const saveStorageState = async ( browserConfig: FullProject, @@ -22,7 +22,7 @@ const saveStorageState = async ( await page.content(); await expect(page.getByText('Docs').first()).toBeVisible(); - await keyCloakSignIn(page, browserName); + await SignIn(page, browserName); await expect( page.locator('header').first().getByRole('button', { diff --git a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts index a11d897d..d9069160 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/config.spec.ts @@ -4,160 +4,166 @@ import { expect, test } from '@playwright/test'; import { CONFIG, createDoc, overrideConfig } from './utils-common'; -test.describe('Config', () => { - test('it checks that sentry is trying to init from config endpoint', async ({ - page, - }) => { - await overrideConfig(page, { - SENTRY_DSN: 'https://sentry.io/123', +if (process.env.TEST_INSTANCE_ONLY !== 'true') { + test.describe('Config', () => { + test('it checks that sentry is trying to init from config endpoint', async ({ + page, + }) => { + await overrideConfig(page, { + SENTRY_DSN: 'https://sentry.io/123', + }); + + const invalidMsg = 'Invalid Sentry Dsn: https://sentry.io/123'; + const consoleMessage = page.waitForEvent('console', { + timeout: 5000, + predicate: (msg) => msg.text().includes(invalidMsg), + }); + + await page.goto('/'); + + expect((await consoleMessage).text()).toContain(invalidMsg); }); - const invalidMsg = 'Invalid Sentry Dsn: https://sentry.io/123'; - const consoleMessage = page.waitForEvent('console', { - timeout: 5000, - predicate: (msg) => msg.text().includes(invalidMsg), + test('it checks that media server is configured from config endpoint', async ({ + page, + browserName, + }) => { + await page.goto('/'); + + await createDoc(page, 'doc-media', browserName, 1); + + const fileChooserPromise = page.waitForEvent('filechooser'); + + await page.locator('.bn-block-outer').last().fill('Anything'); + await page.locator('.bn-block-outer').last().fill('/'); + await page.getByText('Resizable image with caption').click(); + await page.getByText('Upload image').click(); + + const fileChooser = await fileChooserPromise; + await fileChooser.setFiles( + path.join(__dirname, 'assets/logo-suite-numerique.png'), + ); + + const image = page + .locator('.--docs--editor-container img.bn-visual-media') + .first(); + + await expect(image).toBeVisible(); + + // Wait for the media-check to be processed + + await page.waitForTimeout(1000); + + // Check src of image + expect(await image.getAttribute('src')).toMatch( + /http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/, + ); }); - await page.goto('/'); + test('it checks that collaboration server is configured from config endpoint', async ({ + page, + }) => { + await page.goto('/'); - expect((await consoleMessage).text()).toContain(invalidMsg); - }); + void page + .getByRole('button', { + name: 'New doc', + }) + .click(); - test('it checks that media server is configured from config endpoint', async ({ - page, - browserName, - }) => { - await page.goto('/'); - - await createDoc(page, 'doc-media', browserName, 1); - - const fileChooserPromise = page.waitForEvent('filechooser'); - - await page.locator('.bn-block-outer').last().fill('Anything'); - await page.locator('.bn-block-outer').last().fill('/'); - await page.getByText('Resizable image with caption').click(); - await page.getByText('Upload image').click(); - - const fileChooser = await fileChooserPromise; - await fileChooser.setFiles( - path.join(__dirname, 'assets/logo-suite-numerique.png'), - ); - - const image = page - .locator('.--docs--editor-container img.bn-visual-media') - .first(); - - await expect(image).toBeVisible(); - - // Wait for the media-check to be processed - - await page.waitForTimeout(1000); - - // Check src of image - expect(await image.getAttribute('src')).toMatch( - /http:\/\/localhost:8083\/media\/.*\/attachments\/.*.png/, - ); - }); - - test('it checks that collaboration server is configured from config endpoint', async ({ - page, - }) => { - await page.goto('/'); - - void page - .getByRole('button', { - name: 'New doc', - }) - .click(); - - const webSocket = await page.waitForEvent('websocket', (webSocket) => { - return webSocket.url().includes('ws://localhost:4444/collaboration/ws/'); - }); - expect(webSocket.url()).toContain('ws://localhost:4444/collaboration/ws/'); - }); - - test('it checks that Crisp is trying to init from config endpoint', async ({ - page, - }) => { - await overrideConfig(page, { - CRISP_WEBSITE_ID: '1234', + const webSocket = await page.waitForEvent('websocket', (webSocket) => { + return webSocket + .url() + .includes('ws://localhost:4444/collaboration/ws/'); + }); + expect(webSocket.url()).toContain( + 'ws://localhost:4444/collaboration/ws/', + ); }); - await page.goto('/'); + test('it checks that Crisp is trying to init from config endpoint', async ({ + page, + }) => { + await overrideConfig(page, { + CRISP_WEBSITE_ID: '1234', + }); - await expect( - page.locator('#crisp-chatbox').getByText('Invalid website'), - ).toBeVisible(); - }); + await page.goto('/'); - test('it checks FRONTEND_CSS_URL config', async ({ page }) => { - await overrideConfig(page, { - FRONTEND_CSS_URL: 'http://localhost:123465/css/style.css', + await expect( + page.locator('#crisp-chatbox').getByText('Invalid website'), + ).toBeVisible(); }); - await page.goto('/'); + test('it checks FRONTEND_CSS_URL config', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_CSS_URL: 'http://localhost:123465/css/style.css', + }); - await expect( - page - .locator('head link[href="http://localhost:123465/css/style.css"]') - .first(), - ).toBeAttached(); - }); + await page.goto('/'); - test('it checks FRONTEND_JS_URL config', async ({ page }) => { - await overrideConfig(page, { - FRONTEND_JS_URL: 'http://localhost:123465/js/script.js', + await expect( + page + .locator('head link[href="http://localhost:123465/css/style.css"]') + .first(), + ).toBeAttached(); }); - await page.goto('/'); + test('it checks FRONTEND_JS_URL config', async ({ page }) => { + await overrideConfig(page, { + FRONTEND_JS_URL: 'http://localhost:123465/js/script.js', + }); - await expect( - page - .locator('script[src="http://localhost:123465/js/script.js"]') - .first(), - ).toBeAttached(); - }); + await page.goto('/'); - test('it checks the config api is called', async ({ page }) => { - const responsePromise = page.waitForResponse( - (response) => - response.url().includes('/config/') && response.status() === 200, - ); - - await page.goto('/'); - - const response = await responsePromise; - expect(response.ok()).toBeTruthy(); - - const json = (await response.json()) as typeof CONFIG; - expect(json).toStrictEqual(CONFIG); - }); -}); - -test.describe('Config: Not logged', () => { - test.use({ storageState: { cookies: [], origins: [] } }); - - test('it checks that theme is configured from config endpoint', async ({ - page, - }) => { - await page.goto('/'); - - await expect( - page.getByText('Collaborative writing, Simplified.'), - ).toHaveCSS('font-family', /Roboto/i, { - timeout: 10000, + await expect( + page + .locator('script[src="http://localhost:123465/js/script.js"]') + .first(), + ).toBeAttached(); }); - await overrideConfig(page, { - FRONTEND_THEME: 'dsfr', - }); + test('it checks the config api is called', async ({ page }) => { + const responsePromise = page.waitForResponse( + (response) => + response.url().includes('/config/') && response.status() === 200, + ); - await page.goto('/'); + await page.goto('/'); - await expect( - page.getByText('Collaborative writing, Simplified.'), - ).toHaveCSS('font-family', /Marianne/i, { - timeout: 10000, + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + + const json = (await response.json()) as typeof CONFIG; + expect(json).toStrictEqual(CONFIG); }); }); -}); + + test.describe('Config: Not logged', () => { + test.use({ storageState: { cookies: [], origins: [] } }); + + test('it checks that theme is configured from config endpoint', async ({ + page, + }) => { + await page.goto('/'); + + await expect( + page.getByText('Collaborative writing, Simplified.'), + ).toHaveCSS('font-family', /Roboto/i, { + timeout: 10000, + }); + + await overrideConfig(page, { + FRONTEND_THEME: 'dsfr', + }); + + await page.goto('/'); + + await expect( + page.getByText('Collaborative writing, Simplified.'), + ).toHaveCSS('font-family', /Marianne/i, { + timeout: 10000, + }); + }); + }); +} diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts index 9b4b76f1..84546f6d 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-ai.spec.ts @@ -13,210 +13,295 @@ import { writeInEditor, } from './utils-editor'; -test.beforeEach(async ({ page }) => { - await page.goto('/'); -}); +if (process.env.TEST_INSTANCE_ONLY !== 'true') { + test.describe('Doc AI feature', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/'); + }); -test.describe('Doc AI feature', () => { - [ - { - AI_FEATURE_ENABLED: false, - selector: 'Ask AI', - }, - { - AI_FEATURE_ENABLED: true, - AI_FEATURE_BLOCKNOTE_ENABLED: false, - selector: 'Ask AI', - }, - { - AI_FEATURE_ENABLED: true, - AI_FEATURE_LEGACY_ENABLED: false, - selector: 'AI', - }, - ].forEach((config) => { - test(`it checks the AI feature flag from config endpoint: ${JSON.stringify(config)}`, async ({ + [ + { + AI_FEATURE_ENABLED: false, + selector: 'Ask AI', + }, + { + AI_FEATURE_ENABLED: true, + AI_FEATURE_BLOCKNOTE_ENABLED: false, + selector: 'Ask AI', + }, + { + AI_FEATURE_ENABLED: true, + AI_FEATURE_LEGACY_ENABLED: false, + selector: 'AI', + }, + ].forEach((config) => { + test(`it checks the AI feature flag from config endpoint: ${JSON.stringify(config)}`, async ({ + page, + browserName, + }) => { + await overrideConfig(page, config); + + await page.goto('/'); + + await createDoc(page, 'doc-ai-feature', browserName, 1); + + await page.locator('.bn-block-outer').last().fill('Anything'); + await page.getByText('Anything').selectText(); + await expect( + page.locator('button[data-test="convertMarkdown"]'), + ).toHaveCount(1); + await expect( + page.getByRole('button', { name: config.selector, exact: true }), + ).toBeHidden(); + }); + }); + + test('it checks the AI feature and accepts changes', async ({ page, browserName, }) => { - await overrideConfig(page, config); + await overrideConfig(page, { + AI_BOT: { + name: 'Albert AI', + color: '#8bc6ff', + }, + }); + + await mockAIResponse(page); await page.goto('/'); - await createDoc(page, 'doc-ai-feature', browserName, 1); + await createDoc(page, 'doc-ai', browserName, 1); - await page.locator('.bn-block-outer').last().fill('Anything'); - await page.getByText('Anything').selectText(); + await openSuggestionMenu({ page }); + await page.getByText('Ask AI').click(); await expect( - page.locator('button[data-test="convertMarkdown"]'), - ).toHaveCount(1); + page.getByRole('option', { name: 'Continue Writing' }), + ).toBeVisible(); await expect( - page.getByRole('button', { name: config.selector, exact: true }), - ).toBeHidden(); - }); - }); + page.getByRole('option', { name: 'Summarize' }), + ).toBeVisible(); - test('it checks the AI feature and accepts changes', async ({ - page, - browserName, - }) => { - await overrideConfig(page, { - AI_BOT: { - name: 'Albert AI', - color: '#8bc6ff', - }, + await page.keyboard.press('Escape'); + + const editor = await writeInEditor({ page, text: 'Hello World' }); + await editor.getByText('Hello World').selectText(); + + // Check from toolbar + await page.getByRole('button', { name: 'Ask AI' }).click(); + + await expect( + page.getByRole('option', { name: 'Improve Writing' }), + ).toBeVisible(); + await expect( + page.getByRole('option', { name: 'Fix Spelling' }), + ).toBeVisible(); + await expect( + page.getByRole('option', { name: 'Translate' }), + ).toBeVisible(); + + await page.getByRole('option', { name: 'Translate' }).click(); + await page + .getByRole('textbox', { name: 'Ask anything...' }) + .fill('Translate into french'); + await page + .getByRole('textbox', { name: 'Ask anything...' }) + .press('Enter'); + await expect(editor.getByText('Albert AI')).toBeVisible(); + await page + .locator('p.bn-mt-suggestion-menu-item-title') + .getByText('Accept') + .click(); + + await expect(editor.getByText('Bonjour le monde')).toBeVisible(); + + // Check Suggestion menu + await page.locator('.bn-block-outer').last().fill('/'); + await expect(page.getByText('Write with AI')).toBeVisible(); + + // Reload the page to check that the AI change is still there + await page.goto(page.url()); + await expect(editor.getByText('Bonjour le monde')).toBeVisible(); }); - await mockAIResponse(page); + test('it reverts with the AI feature', async ({ page, browserName }) => { + await overrideConfig(page, { + AI_BOT: { + name: 'Albert AI', + color: '#8bc6ff', + }, + }); - await page.goto('/'); + await mockAIResponse(page); - await createDoc(page, 'doc-ai', browserName, 1); + await page.goto('/'); - await openSuggestionMenu({ page }); - await page.getByText('Ask AI').click(); - await expect( - page.getByRole('option', { name: 'Continue Writing' }), - ).toBeVisible(); - await expect(page.getByRole('option', { name: 'Summarize' })).toBeVisible(); + await createDoc(page, 'doc-ai', browserName, 1); - await page.keyboard.press('Escape'); + const editor = await writeInEditor({ page, text: 'Hello World' }); + await editor.getByText('Hello World').selectText(); - const editor = await writeInEditor({ page, text: 'Hello World' }); - await editor.getByText('Hello World').selectText(); + // Check from toolbar + await page.getByRole('button', { name: 'Ask AI' }).click(); - // Check from toolbar - await page.getByRole('button', { name: 'Ask AI' }).click(); + await page.getByRole('option', { name: 'Translate' }).click(); + await page + .getByRole('textbox', { name: 'Ask anything...' }) + .fill('Translate into french'); + await page + .getByRole('textbox', { name: 'Ask anything...' }) + .press('Enter'); + await expect(editor.getByText('Albert AI')).toBeVisible(); + await expect(editor.getByText('Bonjour le monde')).toBeVisible(); + await page + .locator('p.bn-mt-suggestion-menu-item-title') + .getByText('Revert') + .click(); - await expect( - page.getByRole('option', { name: 'Improve Writing' }), - ).toBeVisible(); - await expect( - page.getByRole('option', { name: 'Fix Spelling' }), - ).toBeVisible(); - await expect(page.getByRole('option', { name: 'Translate' })).toBeVisible(); - - await page.getByRole('option', { name: 'Translate' }).click(); - await page - .getByRole('textbox', { name: 'Ask anything...' }) - .fill('Translate into french'); - await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter'); - await expect(editor.getByText('Albert AI')).toBeVisible(); - await page - .locator('p.bn-mt-suggestion-menu-item-title') - .getByText('Accept') - .click(); - - await expect(editor.getByText('Bonjour le monde')).toBeVisible(); - - // Check Suggestion menu - await page.locator('.bn-block-outer').last().fill('/'); - await expect(page.getByText('Write with AI')).toBeVisible(); - - // Reload the page to check that the AI change is still there - await page.goto(page.url()); - await expect(editor.getByText('Bonjour le monde')).toBeVisible(); - }); - - test('it reverts with the AI feature', async ({ page, browserName }) => { - await overrideConfig(page, { - AI_BOT: { - name: 'Albert AI', - color: '#8bc6ff', - }, + await expect(editor.getByText('Hello World')).toBeVisible(); }); - await mockAIResponse(page); - - await page.goto('/'); - - await createDoc(page, 'doc-ai', browserName, 1); - - const editor = await writeInEditor({ page, text: 'Hello World' }); - await editor.getByText('Hello World').selectText(); - - // Check from toolbar - await page.getByRole('button', { name: 'Ask AI' }).click(); - - await page.getByRole('option', { name: 'Translate' }).click(); - await page - .getByRole('textbox', { name: 'Ask anything...' }) - .fill('Translate into french'); - await page.getByRole('textbox', { name: 'Ask anything...' }).press('Enter'); - await expect(editor.getByText('Albert AI')).toBeVisible(); - await expect(editor.getByText('Bonjour le monde')).toBeVisible(); - await page - .locator('p.bn-mt-suggestion-menu-item-title') - .getByText('Revert') - .click(); - - await expect(editor.getByText('Hello World')).toBeVisible(); - }); - - test('it checks the AI buttons feature legacy', async ({ - page, - browserName, - }) => { - await page.route(/.*\/ai-translate\//, async (route) => { - const request = route.request(); - if (request.method().includes('POST')) { - await route.fulfill({ - json: { - answer: 'Hallo Welt', - }, - }); - } else { - await route.continue(); - } - }); - - await createDoc(page, 'doc-ai', browserName, 1); - - await page.locator('.bn-block-outer').last().fill('Hello World'); - - const editor = page.locator('.ProseMirror'); - await editor.getByText('Hello').selectText(); - - await page.getByRole('button', { name: 'AI', exact: true }).click(); - - await expect( - page.getByRole('menuitem', { name: 'Use as prompt' }), - ).toBeVisible(); - await expect( - page.getByRole('menuitem', { name: 'Rephrase' }), - ).toBeVisible(); - await expect( - page.getByRole('menuitem', { name: 'Summarize' }), - ).toBeVisible(); - await expect(page.getByRole('menuitem', { name: 'Correct' })).toBeVisible(); - await expect( - page.getByRole('menuitem', { name: 'Language' }), - ).toBeVisible(); - - await page.getByRole('menuitem', { name: 'Language' }).hover(); - await expect( - page.getByRole('menuitem', { name: 'English', exact: true }), - ).toBeVisible(); - await expect( - page.getByRole('menuitem', { name: 'French', exact: true }), - ).toBeVisible(); - await expect( - page.getByRole('menuitem', { name: 'German', exact: true }), - ).toBeVisible(); - - await page.getByRole('menuitem', { name: 'German', exact: true }).click(); - - await expect(editor.getByText('Hallo Welt')).toBeVisible(); - }); - - [ - { ai_transform: false, ai_translate: false }, - { ai_transform: true, ai_translate: false }, - { ai_transform: false, ai_translate: true }, - ].forEach(({ ai_transform, ai_translate }) => { - test(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({ + test('it checks the AI buttons feature legacy', async ({ page, browserName, }) => { + await page.route(/.*\/ai-translate\//, async (route) => { + const request = route.request(); + if (request.method().includes('POST')) { + await route.fulfill({ + json: { + answer: 'Hallo Welt', + }, + }); + } else { + await route.continue(); + } + }); + + await createDoc(page, 'doc-ai', browserName, 1); + + await page.locator('.bn-block-outer').last().fill('Hello World'); + + const editor = page.locator('.ProseMirror'); + await editor.getByText('Hello').selectText(); + + await page.getByRole('button', { name: 'AI', exact: true }).click(); + + await expect( + page.getByRole('menuitem', { name: 'Use as prompt' }), + ).toBeVisible(); + await expect( + page.getByRole('menuitem', { name: 'Rephrase' }), + ).toBeVisible(); + await expect( + page.getByRole('menuitem', { name: 'Summarize' }), + ).toBeVisible(); + await expect( + page.getByRole('menuitem', { name: 'Correct' }), + ).toBeVisible(); + await expect( + page.getByRole('menuitem', { name: 'Language' }), + ).toBeVisible(); + + await page.getByRole('menuitem', { name: 'Language' }).hover(); + await expect( + page.getByRole('menuitem', { name: 'English', exact: true }), + ).toBeVisible(); + await expect( + page.getByRole('menuitem', { name: 'French', exact: true }), + ).toBeVisible(); + await expect( + page.getByRole('menuitem', { name: 'German', exact: true }), + ).toBeVisible(); + + await page.getByRole('menuitem', { name: 'German', exact: true }).click(); + + await expect(editor.getByText('Hallo Welt')).toBeVisible(); + }); + + [ + { ai_transform: false, ai_translate: false }, + { ai_transform: true, ai_translate: false }, + { ai_transform: false, ai_translate: true }, + ].forEach(({ ai_transform, ai_translate }) => { + test(`it checks AI buttons when can transform is at "${ai_transform}" and can translate is at "${ai_translate}"`, async ({ + page, + browserName, + }) => { + await mockedDocument(page, { + accesses: [ + { + id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg', + role: 'owner', + user: { + email: 'super@owner.com', + full_name: 'Super Owner', + }, + }, + ], + abilities: { + destroy: true, // Means owner + link_configuration: true, + ai_transform, + ai_translate, + accesses_manage: true, + accesses_view: true, + update: true, + partial_update: true, + retrieve: true, + }, + link_reach: 'restricted', + link_role: 'editor', + created_at: '2021-09-01T09:00:00Z', + title: '', + }); + + const [randomDoc] = await createDoc( + page, + 'doc-editor-ai', + browserName, + 1, + ); + + await verifyDocName(page, randomDoc); + + await page.locator('.bn-block-outer').last().fill('Hello World'); + + const editor = page.locator('.ProseMirror'); + await editor.getByText('Hello').selectText(); + + if (!ai_transform && !ai_translate) { + await expect( + page.getByRole('button', { name: 'AI', exact: true }), + ).toBeHidden(); + return; + } + + await page.getByRole('button', { name: 'AI', exact: true }).click(); + + if (ai_transform) { + await expect( + page.getByRole('menuitem', { name: 'Use as prompt' }), + ).toBeVisible(); + } else { + await expect( + page.getByRole('menuitem', { name: 'Use as prompt' }), + ).toBeHidden(); + } + + if (ai_translate) { + await expect( + page.getByRole('menuitem', { name: 'Language' }), + ).toBeVisible(); + } else { + await expect( + page.getByRole('menuitem', { name: 'Language' }), + ).toBeHidden(); + } + }); + }); + + test(`it checks ai_proxy ability`, async ({ page, browserName }) => { await mockedDocument(page, { accesses: [ { @@ -231,8 +316,7 @@ test.describe('Doc AI feature', () => { abilities: { destroy: true, // Means owner link_configuration: true, - ai_transform, - ai_translate, + ai_proxy: false, accesses_manage: true, accesses_view: true, update: true, @@ -247,7 +331,7 @@ test.describe('Doc AI feature', () => { const [randomDoc] = await createDoc( page, - 'doc-editor-ai', + 'doc-editor-ai-proxy', browserName, 1, ); @@ -259,81 +343,9 @@ test.describe('Doc AI feature', () => { const editor = page.locator('.ProseMirror'); await editor.getByText('Hello').selectText(); - if (!ai_transform && !ai_translate) { - await expect( - page.getByRole('button', { name: 'AI', exact: true }), - ).toBeHidden(); - return; - } - - await page.getByRole('button', { name: 'AI', exact: true }).click(); - - if (ai_transform) { - await expect( - page.getByRole('menuitem', { name: 'Use as prompt' }), - ).toBeVisible(); - } else { - await expect( - page.getByRole('menuitem', { name: 'Use as prompt' }), - ).toBeHidden(); - } - - if (ai_translate) { - await expect( - page.getByRole('menuitem', { name: 'Language' }), - ).toBeVisible(); - } else { - await expect( - page.getByRole('menuitem', { name: 'Language' }), - ).toBeHidden(); - } + await expect(page.getByRole('button', { name: 'Ask AI' })).toBeHidden(); + await page.locator('.bn-block-outer').last().fill('/'); + await expect(page.getByText('Write with AI')).toBeHidden(); }); }); - - test(`it checks ai_proxy ability`, async ({ page, browserName }) => { - await mockedDocument(page, { - accesses: [ - { - id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg', - role: 'owner', - user: { - email: 'super@owner.com', - full_name: 'Super Owner', - }, - }, - ], - abilities: { - destroy: true, // Means owner - link_configuration: true, - ai_proxy: false, - accesses_manage: true, - accesses_view: true, - update: true, - partial_update: true, - retrieve: true, - }, - link_reach: 'restricted', - link_role: 'editor', - created_at: '2021-09-01T09:00:00Z', - title: '', - }); - - const [randomDoc] = await createDoc( - page, - 'doc-editor-ai-proxy', - browserName, - 1, - ); - - await verifyDocName(page, randomDoc); - - await page.locator('.bn-block-outer').last().fill('Hello World'); - - const editor = page.locator('.ProseMirror'); - await editor.getByText('Hello').selectText(); - - await expect(page.getByRole('button', { name: 'Ask AI' })).toBeHidden(); - await page.locator('.bn-block-outer').last().fill('/'); - await expect(page.getByText('Write with AI')).toBeHidden(); - }); -}); +} diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts index 6641803b..e9805870 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts @@ -58,10 +58,14 @@ test.describe('Doc Comments', () => { await page.getByRole('button', { name: '👍' }).click(); await expect( - thread.getByRole('img', { name: `E2E ${browserName}` }).first(), + thread + .getByRole('img', { name: `${process.env.FIRST_NAME} ${browserName}` }) + .first(), ).toBeVisible(); await expect(thread.getByText('This is a comment').first()).toBeVisible(); - await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible(); + await expect( + thread.getByText(`${process.env.FIRST_NAME} ${browserName}`).first(), + ).toBeVisible(); await expect(thread.locator('.bn-comment-reaction')).toHaveText('👍1'); const urlCommentDoc = page.url(); @@ -85,7 +89,7 @@ test.describe('Doc Comments', () => { otherThread.getByText('This is a comment').first(), ).toBeVisible(); await expect( - otherThread.getByText(`E2E ${browserName}`).first(), + otherThread.getByText(`${process.env.FIRST_NAME} ${browserName}`).first(), ).toBeVisible(); await expect(otherThread.locator('.bn-comment-reaction')).toHaveText('👍2'); @@ -98,13 +102,19 @@ test.describe('Doc Comments', () => { // We check that the second user can see the comment he just made await expect( - otherThread.getByRole('img', { name: `E2E ${otherBrowserName}` }).first(), + otherThread + .getByRole('img', { + name: `${process.env.FIRST_NAME} ${otherBrowserName}`, + }) + .first(), ).toBeVisible(); await expect( otherThread.getByText('This is a comment from the other user').first(), ).toBeVisible(); await expect( - otherThread.getByText(`E2E ${otherBrowserName}`).first(), + otherThread + .getByText(`${process.env.FIRST_NAME} ${otherBrowserName}`) + .first(), ).toBeVisible(); // We check that the first user can see the comment made by the second user in real time @@ -112,7 +122,7 @@ test.describe('Doc Comments', () => { thread.getByText('This is a comment from the other user').first(), ).toBeVisible(); await expect( - thread.getByText(`E2E ${otherBrowserName}`).first(), + thread.getByText(`${process.env.FIRST_NAME} ${otherBrowserName}`).first(), ).toBeVisible(); await cleanup(); @@ -134,7 +144,7 @@ test.describe('Doc Comments', () => { await expect(editor.getByText('Hello')).toHaveCSS( 'background-color', - 'color(srgb 0.882353 0.831373 0.717647 / 0.4)', + /color\(srgb\s+[\d\s.]+\s+\/\s+0\.4\)/, ); await editor.first().click(); @@ -201,7 +211,7 @@ test.describe('Doc Comments', () => { await expect(editor.getByText('Hello')).toHaveCSS( 'background-color', - 'color(srgb 0.882353 0.831373 0.717647 / 0.4)', + /color\(srgb\s+[\d\s.]+\s+\/\s+0\.4\)/, ); await editor.first().click(); @@ -267,11 +277,15 @@ test.describe('Doc Comments', () => { await expect(otherEditor.getByText('Hello')).toHaveCSS( 'background-color', - 'color(srgb 0.882353 0.831373 0.717647 / 0.4)', + /color\(srgb\s+[\d\s.]+\s+\/\s+0\.4\)/, ); // We change the role of the second user to reader - await updateRoleUser(page, 'Reader', `user.test@${otherBrowserName}.test`); + await updateRoleUser( + page, + 'Reader', + process.env[`SIGN_IN_USERNAME_${otherBrowserName.toUpperCase()}`] || '', + ); // With the reader role, the second user cannot see comments await otherPage.reload(); @@ -296,13 +310,15 @@ test.describe('Doc Comments', () => { // Anonymous user can see and add comments await otherPage.getByRole('button', { name: 'Logout' }).click(); + await otherPage.waitForTimeout(1500); + await otherPage.goto(urlCommentDoc); await verifyDocName(otherPage, docTitle); await expect(otherEditor.getByText('Hello')).toHaveCSS( 'background-color', - 'color(srgb 0.882353 0.831373 0.717647 / 0.4)', + /color\(srgb\s+[\d\s.]+\s+\/\s+0\.4\)/, ); await otherEditor.getByText('Hello').click(); await expect( @@ -348,7 +364,7 @@ test.describe('Doc Comments', () => { await expect(editor1.getByText('Document One')).toHaveCSS( 'background-color', - 'color(srgb 0.882353 0.831373 0.717647 / 0.4)', + /color\(srgb\s+[\d\s.]+\s+\/\s+0\.4\)/, ); await editor1.getByText('Document One').click(); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts index 7ff67ade..0bc3276e 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-create.spec.ts @@ -3,11 +3,11 @@ import { expect, test } from '@playwright/test'; import { createDoc, goToGridDoc, - keyCloakSignIn, randomName, verifyDocName, } from './utils-common'; import { connectOtherUserToDoc } from './utils-share'; +import { SignIn } from './utils-signin'; test.beforeEach(async ({ page }) => { await page.goto('/'); @@ -22,8 +22,7 @@ test.describe('Doc Create', () => { { timeout: 5000 }, ); - const header = page.locator('header').first(); - await header.locator('h1').getByText('Docs').click(); + await page.getByRole('button', { name: 'Back to homepage' }).click(); const docsGrid = page.getByTestId('docs-grid'); await expect(docsGrid).toBeVisible(); @@ -134,7 +133,7 @@ test.describe('Doc Create', () => { withoutSignIn: true, }); - await keyCloakSignIn(otherPage, otherBrowserName, false); + await SignIn(otherPage, otherBrowserName, false); await verifyDocName(otherPage, 'From unlogged doc from url'); @@ -160,14 +159,13 @@ test.describe('Doc Create: Not logged', () => { browserName, request, }) => { - const SERVER_TO_SERVER_API_TOKENS = 'server-api-token'; const markdown = `This is a normal text\n\n# And this is a large heading`; const [title] = randomName('My server way doc create', browserName, 1); const data = { title, content: markdown, - sub: `user.test@${browserName}.test`, - email: `user.test@${browserName}.test`, + sub: process.env[`SUB_${browserName.toUpperCase()}`], + email: process.env[`SIGN_IN_USERNAME_${browserName.toUpperCase()}`], }; const newDoc = await request.post( @@ -175,7 +173,7 @@ test.describe('Doc Create: Not logged', () => { { data, headers: { - Authorization: `Bearer ${SERVER_TO_SERVER_API_TOKENS}`, + Authorization: `Bearer ${process.env.SERVER_TO_SERVER_API_TOKENS}`, format: 'json', }, }, @@ -183,7 +181,7 @@ test.describe('Doc Create: Not logged', () => { expect(newDoc.ok()).toBeTruthy(); - await keyCloakSignIn(page, browserName); + await SignIn(page, browserName); await goToGridDoc(page, { title }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts index dc89907e..69dc4c51 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts @@ -3,9 +3,9 @@ import { Page, chromium, expect } from '@playwright/test'; import { BrowserName, getOtherBrowserName, - keyCloakSignIn, verifyDocName, } from './utils-common'; +import { SignIn } from './utils-signin'; export type Role = 'Administrator' | 'Owner' | 'Editor' | 'Reader'; export type LinkReach = 'Private' | 'Connected' | 'Public'; @@ -131,14 +131,14 @@ export const connectOtherUserToDoc = async ({ .getByRole('main', { name: 'Main content' }) .getByLabel('Login'); const loginFromHome = otherPage.getByRole('button', { - name: 'Start Writing', + name: process.env.SIGN_IN_EL_TRIGGER, }); await loginFromApp.or(loginFromHome).first().click({ timeout: 15000, }); - await keyCloakSignIn(otherPage, otherBrowserName, false); + await SignIn(otherPage, otherBrowserName, false); } if (docTitle) { await verifyDocName(otherPage, docTitle); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts b/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts new file mode 100644 index 00000000..4fb511d3 --- /dev/null +++ b/src/frontend/apps/e2e/__tests__/app-impress/utils-signin.ts @@ -0,0 +1,87 @@ +import { Page, expect } from '@playwright/test'; + +export const SignIn = async ( + page: Page, + browserName: string, + fromHome = true, +) => { + if (process.env.CUSTOM_SIGN_IN === 'true') { + await customSignIn(page, browserName, fromHome); + return; + } + + await keyCloakSignIn(page, browserName, fromHome); +}; + +export const customSignIn = async ( + page: Page, + browserName: string, + fromHome = true, +) => { + // Check if already signed in (Silent login or session still valid) + if ( + await page + .locator('header') + .first() + .getByRole('button', { + name: 'Logout', + }) + .isVisible() + ) { + return; + } + + if (fromHome) { + await page + .getByRole('button', { name: process.env.SIGN_IN_EL_TRIGGER }) + .first() + .click(); + } + + await page + .getByRole('textbox', { name: process.env.SIGN_IN_EL_USERNAME_INPUT }) + .fill(process.env[`SIGN_IN_USERNAME_${browserName.toUpperCase()}`] || ''); + + if (process.env.SIGN_IN_EL_USERNAME_VALIDATION) { + await page + .getByRole('button', { name: process.env.SIGN_IN_EL_USERNAME_VALIDATION }) + .first() + .click(); + } + + await page + .locator( + `input[name="${process.env.SIGN_IN_EL_PASSWORD_INPUT || 'password'}"]`, + ) + .fill(process.env[`SIGN_IN_PASSWORD_${browserName.toUpperCase()}`] || ''); + + await page.click('button[type="submit"]', { force: true }); +}; + +export const keyCloakSignIn = async ( + page: Page, + browserName: string, + fromHome = true, +) => { + if (fromHome) { + await page + .getByRole('button', { name: process.env.SIGN_IN_EL_TRIGGER }) + .first() + .click(); + } + + const login = `user-e2e-${browserName}`; + const password = `password-e2e-${browserName}`; + + await expect( + page.locator('.login-pf #kc-header-wrapper').getByText('impress'), + ).toBeVisible(); + + if (await page.getByLabel('Restart login').isVisible()) { + await page.getByLabel('Restart login').click(); + } + + await page.getByRole('textbox', { name: 'username' }).fill(login); + await page.getByRole('textbox', { name: 'password' }).fill(password); + await page.click('button[type="submit"]', { force: true }); +}; diff --git a/src/frontend/apps/e2e/package.json b/src/frontend/apps/e2e/package.json index f208d09d..a702373a 100644 --- a/src/frontend/apps/e2e/package.json +++ b/src/frontend/apps/e2e/package.json @@ -24,6 +24,7 @@ "dependencies": { "@types/pngjs": "6.0.5", "convert-stream": "1.0.2", + "dotenv": "17.3.1", "pdf-parse": "2.4.5", "pixelmatch": "7.1.0", "pngjs": "7.0.0" diff --git a/src/frontend/apps/e2e/playwright.config.ts b/src/frontend/apps/e2e/playwright.config.ts index cdb6aadc..37509424 100644 --- a/src/frontend/apps/e2e/playwright.config.ts +++ b/src/frontend/apps/e2e/playwright.config.ts @@ -1,8 +1,10 @@ import { defineConfig, devices } from '@playwright/test'; +import dotenv from 'dotenv'; -const PORT = process.env.PORT || 3000; +dotenv.config({ path: ['./.env.local', './.env'], quiet: true, debug: true }); -const baseURL = `http://localhost:${PORT}`; +const PORT = process.env.PORT; +const baseURL = process.env.BASE_URL; /** * See https://playwright.dev/docs/test-configuration. diff --git a/src/frontend/yarn.lock b/src/frontend/yarn.lock index dd839337..4e7530e2 100644 --- a/src/frontend/yarn.lock +++ b/src/frontend/yarn.lock @@ -1769,18 +1769,11 @@ minimatch "^10.2.4" "@eslint/config-helpers@^0.5.2": - version "0.5.2" - resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.2.tgz#314c7b03d02a371ad8c0a7f6821d5a8a8437ba9d" - integrity sha512-a5MxrdDXEvqnIq+LisyCX6tQMPF/dSJpCfBgBauY+pNZ28yCtSsTvyTYrMhaI+LK26bVyCJfJkT0u8KIj2i1dQ== + version "0.5.3" + resolved "https://registry.yarnpkg.com/@eslint/config-helpers/-/config-helpers-0.5.3.tgz#721fe6bbb90d74b0c80d6ff2428e5bbcb002becb" + integrity sha512-lzGN0onllOZCGroKJmRwY6QcEHxbjBw1gwB8SgRSqK8YbbtEXMvKynsXc3553ckIEBxsbMBU7oOZXKIPGZNeZw== dependencies: - "@eslint/core" "^1.1.0" - -"@eslint/core@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@eslint/core/-/core-1.1.0.tgz#51f5cd970e216fbdae6721ac84491f57f965836d" - integrity sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw== - dependencies: - "@types/json-schema" "^7.0.15" + "@eslint/core" "^1.1.1" "@eslint/core@^1.1.1": version "1.1.1" @@ -10351,9 +10344,9 @@ eslint@10.0.3: optionator "^0.9.3" espree@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-11.1.1.tgz#866f6bc9ccccd6f28876b7a6463abb281b9cb847" - integrity sha512-AVHPqQoZYc+RUM4/3Ly5udlZY/U4LS8pIG05jEjWM2lQMU/oaZ7qshzAl2YP1tfNmXfftH3ohurfwNAug+MnsQ== + version "11.2.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-11.2.0.tgz#01d5e47dc332aaba3059008362454a8cc34ccaa5" + integrity sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw== dependencies: acorn "^8.16.0" acorn-jsx "^5.3.2" @@ -10729,7 +10722,12 @@ flat-cache@^6.1.20: flatted "^3.3.3" hookified "^1.15.0" -flatted@^3.2.9, flatted@^3.3.3: +flatted@^3.2.9: + version "3.4.2" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726" + integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA== + +flatted@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==