diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index e22b293d444..9a05543605d 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -209,8 +209,11 @@ jobs: REPORTER_ROCKETCHAT_DRAFT: ${{ github.event.pull_request.draft }} QASE_API_TOKEN: ${{ secrets.QASE_API_TOKEN }} QASE_REPORT: ${{ github.ref == 'refs/heads/develop' && 'true' || '' }} + CI: true working-directory: ./apps/meteor - run: yarn test:e2e --shard=${{ matrix.shard }}/${{ inputs.total-shard }} + run: | + yarn prepare + yarn test:e2e --shard=${{ matrix.shard }}/${{ inputs.total-shard }} - name: Store playwright test trace if: inputs.type == 'ui' && always() diff --git a/apps/meteor/package.json b/apps/meteor/package.json index 427ad86ddca..c2e17b3e82c 100644 --- a/apps/meteor/package.json +++ b/apps/meteor/package.json @@ -50,6 +50,7 @@ "set-version": "node .scripts/set-version.js", "release": "meteor npm run set-version --silent", "storybook": "cross-env NODE_OPTIONS=--max-old-space-size=8192 start-storybook -p 6006 --no-version-updates", + "prepare": "node playwright.prepare.mjs", "docker:start": "docker-compose up" }, "license": "MIT", diff --git a/apps/meteor/playwright.config.ts b/apps/meteor/playwright.config.ts index d94efbf6039..329d4daa194 100644 --- a/apps/meteor/playwright.config.ts +++ b/apps/meteor/playwright.config.ts @@ -5,6 +5,7 @@ import * as constants from './tests/e2e/config/constants'; export default { globalSetup: require.resolve('./tests/e2e/config/global-setup.ts'), use: { + channel: 'chromium', headless: true, ignoreHTTPSErrors: true, trace: 'retain-on-failure', @@ -51,4 +52,6 @@ export default { timeout: 60 * 1000, globalTimeout: (process.env.IS_EE === 'true' ? 50 : 40) * 60 * 1000, maxFailures: process.env.CI ? 5 : undefined, + // Retry on CI only. + retries: process.env.CI ? 2 : 0, } as PlaywrightTestConfig; diff --git a/apps/meteor/playwright.prepare.mjs b/apps/meteor/playwright.prepare.mjs new file mode 100644 index 00000000000..5f3a20f2162 --- /dev/null +++ b/apps/meteor/playwright.prepare.mjs @@ -0,0 +1,9 @@ +// prepare.js + +import { readFileSync, writeFileSync } from 'fs'; +import path from 'path'; + +const modules = path.resolve(`node_modules`); +const destination = path.join(modules, 'playwright-core', 'lib', 'server', 'chromium', 'crNetworkManager.js'); +const buffer = readFileSync(destination); +writeFileSync(destination, buffer.toString().replace('cacheDisabled: true', 'cacheDisabled: false')); diff --git a/apps/meteor/tests/e2e/channel-management.spec.ts b/apps/meteor/tests/e2e/channel-management.spec.ts index 7c153328251..7cad1be43cb 100644 --- a/apps/meteor/tests/e2e/channel-management.spec.ts +++ b/apps/meteor/tests/e2e/channel-management.spec.ts @@ -11,13 +11,9 @@ test.use({ storageState: Users.admin.state }); test.describe.serial('channel-management', () => { let poHomeChannel: HomeChannel; let targetChannel: string; - let regularUserPage: Page; - test.beforeAll(async ({ api, browser }) => { + test.beforeAll(async ({ api }) => { targetChannel = await createTargetChannel(api); - regularUserPage = await browser.newPage({ storageState: Users.user2.state }); - await regularUserPage.goto('/home'); - await regularUserPage.waitForSelector('[data-qa-id="home-header"]'); }); test.beforeEach(async ({ page }) => { @@ -26,10 +22,6 @@ test.describe.serial('channel-management', () => { await page.goto('/home'); }); - test.afterAll(async () => { - await regularUserPage.close(); - }); - test('expect add "user1" to "targetChannel"', async () => { await poHomeChannel.sidenav.openChat(targetChannel); await poHomeChannel.tabs.btnTabMembers.click(); @@ -113,7 +105,8 @@ test.describe.serial('channel-management', () => { await expect(poHomeChannel.toastSuccess).toBeVisible(); }); - test('expect "readOnlyChannel" to show join button', async () => { + let regularUserPage: Page; + test('expect "readOnlyChannel" to show join button', async ({ browser }) => { const channelName = faker.string.uuid(); await poHomeChannel.sidenav.openNewByLabel('Channel'); @@ -122,8 +115,15 @@ test.describe.serial('channel-management', () => { await poHomeChannel.sidenav.checkboxReadOnly.click(); await poHomeChannel.sidenav.btnCreate.click(); + regularUserPage = await browser.newPage({ storageState: Users.user2.state }); + + const channel = new HomeChannel(regularUserPage); + await regularUserPage.goto(`/channel/${channelName}`); + await channel.waitForChannel(); await expect(regularUserPage.locator('button >> text="Join"')).toBeVisible(); + + await regularUserPage.close(); }); test.skip('expect all notification preferences of "targetChannel" to be "Mentions"', async () => { diff --git a/apps/meteor/tests/e2e/homepage.spec.ts b/apps/meteor/tests/e2e/homepage.spec.ts index 465ecea03cb..380fa54d2af 100644 --- a/apps/meteor/tests/e2e/homepage.spec.ts +++ b/apps/meteor/tests/e2e/homepage.spec.ts @@ -26,16 +26,21 @@ test.describe.serial('homepage', () => { await adminPage.waitForSelector('[data-qa-id="home-header"]'); }); + test.afterAll(async ({ api }) => { + expect((await api.post('/settings/Layout_Home_Custom_Block_Visible', { value: false })).status()).toBe(200); + expect((await api.post('/settings/Layout_Custom_Body_Only', { value: false })).status()).toBe(200); + await adminPage.close(); + }); + test('layout', async () => { await test.step('expect show customize button', async () => { await expect(adminPage.locator('role=button[name="Customize"]')).toBeVisible(); }); - + await test.step('expect all cards to be visible', async () => { await Promise.all(Object.values(CardIds).map((id) => expect(adminPage.locator(`[data-qa-id="${id}"]`)).toBeVisible())); }); - }) - + }); test.describe('custom body with empty custom content', async () => { test.beforeAll(async ({ api }) => { @@ -48,17 +53,16 @@ test.describe.serial('homepage', () => { adminPage.locator('role=status[name="Admins may insert content html to be rendered in this white space."]'), ).toBeVisible(); }); - + await test.step('expect both change visibility and show only custom content buttons to be disabled', async () => { await expect(adminPage.locator('role=button[name="Show to workspace"]')).toBeDisabled(); await expect(adminPage.locator('role=button[name="Show only this content"]')).toBeDisabled(); }); - + await test.step('expect visibility tag to show "not visible"', async () => { await expect(adminPage.locator('role=status[name="Not visible to workspace"]')).toBeVisible(); }); - }) - + }); }); test.describe('custom body with custom content', () => { @@ -66,17 +70,16 @@ test.describe.serial('homepage', () => { await expect((await api.post('/settings/Layout_Home_Body', { value: 'Hello admin' })).status()).toBe(200); }); - test('layout', async() => { + test('layout', async () => { await test.step('expect custom body to be visible', async () => { await expect(adminPage.locator('role=status[name="Hello admin"]')).toBeVisible(); }); - + await test.step('expect correct state for card buttons', async () => { await expect(adminPage.locator('role=button[name="Show to workspace"]')).not.toBeDisabled(); await expect(adminPage.locator('role=button[name="Show only this content"]')).toBeDisabled(); }); - }) - + }); test.describe('enterprise edition', () => { test.skip(!IS_EE, 'Enterprise Only'); @@ -103,12 +106,6 @@ test.describe.serial('homepage', () => { }); }); }); - - test.afterAll(async ({ api }) => { - expect((await api.post('/settings/Layout_Home_Custom_Block_Visible', { value: false })).status()).toBe(200); - expect((await api.post('/settings/Layout_Custom_Body_Only', { value: false })).status()).toBe(200); - await adminPage.close(); - }); }); test.describe('for regular users', () => { @@ -121,23 +118,31 @@ test.describe.serial('homepage', () => { await regularUserPage.waitForSelector('[data-qa-id="home-header"]'); }); + test.afterAll(async () => { + await regularUserPage.close(); + }); + test('layout', async () => { await test.step('expect to not show customize button', async () => { await expect(regularUserPage.locator('role=button[name="Customize"]')).not.toBeVisible(); }); - + await test.step(`expect ${notVisibleCards.join(' and ')} cards to not be visible`, async () => { await Promise.all(notVisibleCards.map((id) => expect(regularUserPage.locator(`[data-qa-id="${id}"]`)).not.toBeVisible())); }); - + await test.step('expect all other cards to be visible', async () => { - await Promise.all(Object.values(CardIds).filter((id) => !notVisibleCards.includes(id)).map((id) => expect(regularUserPage.locator(`[data-qa-id="${id}"]`)).toBeVisible())); + await Promise.all( + Object.values(CardIds) + .filter((id) => !notVisibleCards.includes(id)) + .map((id) => expect(regularUserPage.locator(`[data-qa-id="${id}"]`)).toBeVisible()), + ); }); - + await test.step('expect welcome text to use Site_Name default setting', async () => { await expect(regularUserPage.locator('role=heading[name="Welcome to Rocket.Chat"]')).toBeVisible(); }); - + await test.step('expect header text to use Layout_Home_Title default setting', async () => { await expect(regularUserPage.locator('[data-qa-type="PageHeader-title"]')).toContainText('Home'); }); @@ -152,20 +157,19 @@ test.describe.serial('homepage', () => { await regularUserPage.waitForSelector('[data-qa-id="home-header"]'); }); + test.afterAll(async ({ api }) => { + expect((await api.post('/settings/Site_Name', { value: 'Rocket.Chat' })).status()).toBe(200); + expect((await api.post('/settings/Layout_Home_Title', { value: 'Home' })).status()).toBe(200); + }); + test('layout', async () => { await test.step('expect welcome text to be NewSiteName', async () => { await expect(regularUserPage.locator('role=heading[name="Welcome to NewSiteName"]')).toBeVisible(); }); - + await test.step('expect header text to be Layout_Home_Title setting', async () => { await expect(regularUserPage.locator('[data-qa-type="PageHeader-title"]')).toContainText('NewTitle'); }); - }) - - - test.afterAll(async ({ api }) => { - expect((await api.post('/settings/Site_Name', { value: 'Rocket.Chat' })).status()).toBe(200); - expect((await api.post('/settings/Layout_Home_Title', { value: 'Home' })).status()).toBe(200); }); }); @@ -178,6 +182,10 @@ test.describe.serial('homepage', () => { await regularUserPage.waitForSelector('[data-qa-id="home-header"]'); }); + test.afterAll(async ({ api }) => { + expect((await api.post('/settings/Layout_Home_Body', { value: '' })).status()).toBe(200); + expect((await api.post('/settings/Layout_Home_Custom_Block_Visible', { value: false })).status()).toBe(200); + }); test('expect custom body to be visible', async () => { await expect(regularUserPage.locator('role=status[name="Hello"]')).toBeVisible(); @@ -190,26 +198,20 @@ test.describe.serial('homepage', () => { expect((await api.post('/settings/Layout_Custom_Body_Only', { value: true })).status()).toBe(200); }); + test.afterAll(async ({ api }) => { + expect((await api.post('/settings/Layout_Custom_Body_Only', { value: false })).status()).toBe(200); + }); + test('layout', async () => { await test.step('expect default layout to not be visible', async () => { await expect(regularUserPage.locator('[data-qa-id="homepage-welcome-text"]')).not.toBeVisible(); }); - + await test.step('expect custom body to be visible', async () => { await expect(regularUserPage.locator('role=status[name="Hello"]')).toBeVisible(); }); - }) - - - test.afterAll(async ({ api }) => { - expect((await api.post('/settings/Layout_Custom_Body_Only', { value: false })).status()).toBe(200); }); }); - - test.afterAll(async ({ api }) => { - expect((await api.post('/settings/Layout_Home_Body', { value: '' })).status()).toBe(200); - expect((await api.post('/settings/Layout_Home_Custom_Block_Visible', { value: false })).status()).toBe(200); - }); }); }); }); diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts index c5250b1d115..8d8bf3eed19 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-livechat.spec.ts @@ -32,6 +32,7 @@ test.describe('Livechat', () => { test.afterAll(async ({ api }) => { await api.delete('/livechat/users/agent/user1'); await poAuxContext.page.close(); + await page.close(); }); test('Send message to online agent', async () => { diff --git a/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts b/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts index b521090e709..559d2488963 100644 --- a/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts +++ b/apps/meteor/tests/e2e/omnichannel/omnichannel-priorities-sidebar.spec.ts @@ -65,6 +65,7 @@ test.describe.serial('Omnichannel Priorities [Sidebar]', () => { await poLivechat.onlineAgentMessage.type('this_a_test_message_from_visitor'); await poLivechat.btnSendMessageToOnlineAgent.click(); await poHomeChannel.sidenav.getSidebarItemByName(NEW_USER.name).click(); + await poLivechat.page.close(); }); await test.step('Queue: Sidebar priority change', async () => { diff --git a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts index 5e5e3f93bfb..4f3d7b4886e 100644 --- a/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts +++ b/apps/meteor/tests/e2e/page-objects/fragments/home-sidenav.ts @@ -75,7 +75,10 @@ export class HomeSidenav { await this.page.locator('role=navigation >> role=button[name=Search]').click(); await this.page.locator('role=search >> role=searchbox').type(name); await this.page.locator(`role=search >> role=listbox >> role=link >> text="${name}"`).click(); + await this.waitForChannel(); + } + async waitForChannel(): Promise { await this.page.locator('role=main').waitFor(); await this.page.locator('role=main >> role=heading[level=1]').waitFor(); diff --git a/apps/meteor/tests/e2e/page-objects/home-channel.ts b/apps/meteor/tests/e2e/page-objects/home-channel.ts index 1d6e450d976..d2f43c5cfec 100644 --- a/apps/meteor/tests/e2e/page-objects/home-channel.ts +++ b/apps/meteor/tests/e2e/page-objects/home-channel.ts @@ -1,5 +1,6 @@ import type { Locator, Page } from '@playwright/test'; +import { expect } from '../utils/test'; import { HomeContent, HomeSidenav, HomeFlextab } from './fragments'; export class HomeChannel { @@ -25,4 +26,12 @@ export class HomeChannel { get btnContextualbarClose(): Locator { return this.page.locator('[data-qa="ContextualbarActionClose"]'); } + + async waitForChannel(): Promise { + await this.page.locator('role=main').waitFor(); + await this.page.locator('role=main >> role=heading[level=1]').waitFor(); + + await expect(this.page.locator('role=main >> .rcx-skeleton')).toHaveCount(0); + await expect(this.page.locator('role=main >> role=list')).not.toHaveAttribute('aria-busy', 'true'); + } } diff --git a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts index 754ab9c01d7..b4a7c9b9234 100644 --- a/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts +++ b/apps/meteor/tests/e2e/page-objects/omnichannel-livechat.ts @@ -1,7 +1,7 @@ import type { Page, Locator, APIResponse } from '@playwright/test'; export class OmnichannelLiveChat { - private readonly page: Page; + readonly page: Page; constructor(page: Page, private readonly api: { get(url: string): Promise }) { this.page = page;