diff --git a/.github/workflows/playwright.yml b/.github/workflows/playwright.yml index de9d4e5e..3928fd3f 100644 --- a/.github/workflows/playwright.yml +++ b/.github/workflows/playwright.yml @@ -3,7 +3,6 @@ on: push: branches: - main - # TODO: Add pull request after optimizing the total excecution time of the test suite. # pull_request: # paths: # - 'packages/backend/**' @@ -56,27 +55,44 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v4 with: - node-version: 18 - - name: Install web dependencies - run: yarn - working-directory: ./packages/web + node-version: '18' + cache: 'yarn' + cache-dependency-path: | + packages/backend/yarn.lock + packages/web/yarn.lock + packages/e2e-tests/yarn.lock - name: Install backend dependencies - run: yarn + run: yarn --frozen-lockfile working-directory: ./packages/backend + - name: Install web dependencies + run: yarn --frozen-lockfile + working-directory: ./packages/web - name: Install e2e-tests dependencies - run: yarn + run: yarn --frozen-lockfile working-directory: ./packages/e2e-tests + - name: Get installed Playwright version + id: playwright-version + run: echo "PLAYWRIGHT_VERSION=$(node -e "console.log(require('./package.json').devDependencies['@playwright/test'])")" >> $GITHUB_ENV + working-directory: ./packages/e2e-tests + - name: Cache playwright binaries + uses: actions/cache@v3 + id: playwright-cache + with: + path: | + ~/.cache/ms-playwright + key: ${{ runner.os }}-playwright-${{ env.PLAYWRIGHT_VERSION }} - name: Install Playwright Browsers run: yarn playwright install --with-deps working-directory: ./packages/e2e-tests + if: steps.playwright-cache.outputs.cache-hit != 'true' - name: Build Automatisch web run: yarn build - working-directory: ./packages/web env: # Keep this until we clean up warnings in build processes CI: false + working-directory: ./packages/web - name: Migrate database working-directory: ./packages/backend run: yarn db:migrate @@ -116,11 +132,12 @@ jobs: env: LOGIN_EMAIL: user@automatisch.io LOGIN_PASSWORD: sample + BACKEND_APP_URL: http://localhost:3000 BASE_URL: http://localhost:3000 GITHUB_CLIENT_ID: 1c0417daf898adfbd99a GITHUB_CLIENT_SECRET: 3328fa814dd582ccd03dbe785cfd683fb8da92b3 run: yarn test - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: playwright-report diff --git a/packages/e2e-tests/.env-example b/packages/e2e-tests/.env-example index b7a2b62f..0cf8775e 100644 --- a/packages/e2e-tests/.env-example +++ b/packages/e2e-tests/.env-example @@ -2,4 +2,5 @@ POSTGRES_DB=automatisch POSTGRES_USER=automatisch_user POSTGRES_PASSWORD=automatisch_password POSTGRES_PORT=5432 -POSTGRES_HOST=localhost \ No newline at end of file +POSTGRES_HOST=localhost +BACKEND_APP_URL=http://localhost:3000 \ No newline at end of file diff --git a/packages/e2e-tests/fixtures/admin/create-role-page.js b/packages/e2e-tests/fixtures/admin/create-role-page.js index 3426e520..fe0ecde0 100644 --- a/packages/e2e-tests/fixtures/admin/create-role-page.js +++ b/packages/e2e-tests/fixtures/admin/create-role-page.js @@ -1,3 +1,5 @@ +import { expect } from '@playwright/test'; + const { AuthenticatedPage } = require('../authenticated-page'); const { RoleConditionsModal } = require('./role-conditions-modal'); @@ -16,6 +18,7 @@ export class AdminCreateRolePage extends AuthenticatedPage { this.executionRow = page.getByTestId('Execution-permission-row'); this.flowRow = page.getByTestId('Flow-permission-row'); this.pageTitle = page.getByTestId('create-role-title'); + this.permissionsCatalog = page.getByTestId('permissions-catalog'); } /** @@ -104,4 +107,8 @@ export class AdminCreateRolePage extends AuthenticatedPage { throw new Error(`${subject} does not have action ${action}`); } } + + async waitForPermissionsCatalogToVisible() { + await expect(this.permissionsCatalog).toBeVisible(); + } } diff --git a/packages/e2e-tests/fixtures/admin/users-page.js b/packages/e2e-tests/fixtures/admin/users-page.js index af6dbac3..6b7f6263 100644 --- a/packages/e2e-tests/fixtures/admin/users-page.js +++ b/packages/e2e-tests/fixtures/admin/users-page.js @@ -95,7 +95,6 @@ export class AdminUsersPage extends AuthenticatedPage { }); } const rowLocator = await this.getUserRowByEmail(email); - console.log('rowLocator.count', email, await rowLocator.count()); if ((await rowLocator.count()) === 1) { return rowLocator; } diff --git a/packages/e2e-tests/fixtures/base-page.js b/packages/e2e-tests/fixtures/base-page.js index 1b057899..ade03437 100644 --- a/packages/e2e-tests/fixtures/base-page.js +++ b/packages/e2e-tests/fixtures/base-page.js @@ -51,10 +51,20 @@ export class BasePage { }; } + async closeSnackbar() { + await this.snackbar.click(); + } + + async closeSnackbarAndWaitUntilDetached() { + const snackbar = await this.snackbar; + await snackbar.click(); + await snackbar.waitFor({ state: 'detached' }); + } + /** * Closes all snackbars, should be replaced later */ - async closeSnackbar() { + async closeAllSnackbars() { const snackbars = await this.snackbar.all(); for (const snackbar of snackbars) { await snackbar.click(); diff --git a/packages/e2e-tests/helpers/auth-api-helper.js b/packages/e2e-tests/helpers/auth-api-helper.js new file mode 100644 index 00000000..f8067ede --- /dev/null +++ b/packages/e2e-tests/helpers/auth-api-helper.js @@ -0,0 +1,16 @@ +const { expect } = require('../fixtures/index'); + +export const getToken = async (apiRequest) => { + const tokenResponse = await apiRequest.post( + `${process.env.BACKEND_APP_URL}/api/v1/access-tokens`, + { + data: { + email: process.env.LOGIN_EMAIL, + password: process.env.LOGIN_PASSWORD, + }, + } + ); + await expect(tokenResponse.status()).toBe(200); + + return await tokenResponse.json(); +}; diff --git a/packages/e2e-tests/helpers/flow-api-helper.js b/packages/e2e-tests/helpers/flow-api-helper.js new file mode 100644 index 00000000..525274a8 --- /dev/null +++ b/packages/e2e-tests/helpers/flow-api-helper.js @@ -0,0 +1,69 @@ +const { expect } = require('../fixtures/index'); + +export const createFlow = async (request, token) => { + const response = await request.post( + `${process.env.BACKEND_APP_URL}/api/v1/flows`, + { headers: { Authorization: token } } + ); + await expect(response.status()).toBe(201); + return await response.json(); +}; + +export const getFlow = async (request, token, flowId) => { + const response = await request.get( + `${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}`, + { headers: { Authorization: token } } + ); + await expect(response.status()).toBe(200); + return await response.json(); +}; + +export const updateFlowName = async (request, token, flowId) => { + const updateFlowNameResponse = await request.patch( + `${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}`, + { + headers: { Authorization: token }, + data: { name: flowId }, + } + ); + await expect(updateFlowNameResponse.status()).toBe(200); +}; + +export const updateFlowStep = async (request, token, stepId, requestBody) => { + const updateTriggerStepResponse = await request.patch( + `${process.env.BACKEND_APP_URL}/api/v1/steps/${stepId}`, + { + headers: { Authorization: token }, + data: requestBody, + } + ); + await expect(updateTriggerStepResponse.status()).toBe(200); + return await updateTriggerStepResponse.json(); +}; + +export const testStep = async (request, token, stepId) => { + const testTriggerStepResponse = await request.post( + `${process.env.BACKEND_APP_URL}/api/v1/steps/${stepId}/test`, + { + headers: { Authorization: token }, + } + ); + await expect(testTriggerStepResponse.status()).toBe(200); +}; + +export const publishFlow = async (request, token, flowId) => { + const publishFlowResponse = await request.patch( + `${process.env.BACKEND_APP_URL}/api/v1/flows/${flowId}/status`, + { + headers: { Authorization: token }, + data: { active: true }, + } + ); + await expect(publishFlowResponse.status()).toBe(200); + return publishFlowResponse.json(); +}; + +export const triggerFlow = async (request, url) => { + const triggerFlowResponse = await request.get(url); + await expect(triggerFlowResponse.status()).toBe(204); +}; diff --git a/packages/e2e-tests/helpers/user-api-helper.js b/packages/e2e-tests/helpers/user-api-helper.js new file mode 100644 index 00000000..57d96e75 --- /dev/null +++ b/packages/e2e-tests/helpers/user-api-helper.js @@ -0,0 +1,24 @@ +const { expect } = require('../fixtures/index'); + +export const addUser = async (apiRequest, token, request) => { + const addUserResponse = await apiRequest.post( + `${process.env.BACKEND_APP_URL}/api/v1/admin/users`, + { + headers: { Authorization: token }, + data: request, + } + ); + await expect(addUserResponse.status()).toBe(201); + + return await addUserResponse.json(); +}; + +export const acceptInvitation = async (apiRequest, request) => { + const acceptInvitationResponse = await apiRequest.post( + `${process.env.BACKEND_APP_URL}/api/v1/users/invitation`, + { + data: request, + } + ); + await expect(acceptInvitationResponse.status()).toBe(204); +}; diff --git a/packages/e2e-tests/knexfile.js b/packages/e2e-tests/knexfile.js index 6ed3bd37..66e54510 100644 --- a/packages/e2e-tests/knexfile.js +++ b/packages/e2e-tests/knexfile.js @@ -1,3 +1,5 @@ +import { knexSnakeCaseMappers } from 'objection'; + const fileExtension = 'js'; const knexConfig = { @@ -7,7 +9,7 @@ const knexConfig = { user: process.env.POSTGRES_USERNAME, port: process.env.POSTGRES_PORT, password: process.env.POSTGRES_PASSWORD, - database: process.env.POSTGRES_DATABASE + database: process.env.POSTGRES_DATABASE, }, searchPath: ['public'], pool: { min: 0, max: 20 }, diff --git a/packages/e2e-tests/package.json b/packages/e2e-tests/package.json index ddb5979d..3f495a54 100644 --- a/packages/e2e-tests/package.json +++ b/packages/e2e-tests/package.json @@ -26,7 +26,8 @@ }, "devDependencies": { "@faker-js/faker": "^8.2.0", - "@playwright/test": "^1.45.1" + "@playwright/test": "1.49.0", + "objection": "^3.1.5" }, "dependencies": { "axios": "^1.6.0", diff --git a/packages/e2e-tests/playwright.config.js b/packages/e2e-tests/playwright.config.js index ec034c39..3b44a97a 100644 --- a/packages/e2e-tests/playwright.config.js +++ b/packages/e2e-tests/playwright.config.js @@ -15,9 +15,9 @@ module.exports = defineConfig({ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ forbidOnly: !!process.env.CI, - retries: 0, + retries: process.env.CI ? 1 : 0, /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, + workers: undefined, /* Timeout threshold for each test */ timeout: 30000, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ @@ -30,7 +30,7 @@ module.exports = defineConfig({ baseURL: process.env.BASE_URL || 'http://localhost:3001', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'retain-on-failure', + trace: 'on-first-retry', testIdAttribute: 'data-test', viewport: { width: 1280, height: 720 }, }, @@ -42,10 +42,15 @@ module.exports = defineConfig({ /* Configure projects for major browsers */ projects: [ + { + name: 'db-restore', + testMatch: /.*\.teardown\.js/, + }, { name: 'setup', testMatch: /.*\.setup\.js/, teardown: 'teardown', + dependencies: ['db-restore'], }, { name: 'teardown', diff --git a/packages/e2e-tests/tests/admin/applications.spec.js b/packages/e2e-tests/tests/admin/applications.spec.js index c487ae3f..d66d5917 100644 --- a/packages/e2e-tests/tests/admin/applications.spec.js +++ b/packages/e2e-tests/tests/admin/applications.spec.js @@ -43,7 +43,8 @@ test.describe('Admin Applications', () => { await adminApplicationsPage.navigateTo(); }); - test('Admin should be able to toggle Application settings', async ({ + // TODO skip until https://github.com/automatisch/automatisch/pull/2244 + test.skip('Admin should be able to toggle Application settings', async ({ adminApplicationsPage, adminApplicationSettingsPage, page, @@ -181,7 +182,7 @@ test.describe('Admin Applications', () => { const triggerStep = flowEditorPage.flowStep.last(); await triggerStep.click(); - await flowEditorPage.chooseAppAndEvent('Spotify', 'Create playlist'); + await flowEditorPage.chooseAppAndEvent('Spotify', 'Create Playlist'); await flowEditorPage.connectionAutocomplete.click(); const newConnectionOption = page @@ -221,6 +222,7 @@ test.describe('Admin Applications', () => { await adminApplicationOAuthClientsPage.openAuthClientsTab(); await adminApplicationOAuthClientsPage.openFirstAuthClientCreateForm(); + const authClientForm = page.getByTestId('auth-client-form'); await authClientForm.locator(page.getByTestId('switch')).check(); await authClientForm @@ -232,6 +234,7 @@ test.describe('Admin Applications', () => { await authClientForm .locator(page.locator('[name="clientSecret"]')) .fill('redditClientSecret'); + await adminApplicationOAuthClientsPage.submitAuthClientForm(); await adminApplicationOAuthClientsPage.authClientShouldBeVisible( 'redditAuthClient' diff --git a/packages/e2e-tests/tests/admin/manage-roles.spec.js b/packages/e2e-tests/tests/admin/manage-roles.spec.js index 9198db3e..2419ee84 100644 --- a/packages/e2e-tests/tests/admin/manage-roles.spec.js +++ b/packages/e2e-tests/tests/admin/manage-roles.spec.js @@ -22,17 +22,14 @@ test.describe('Role management page', () => { await adminRolesPage.navigateTo(); await adminRolesPage.createRoleButton.click(); await adminCreateRolePage.isMounted(); + await adminCreateRolePage.waitForPermissionsCatalogToVisible(); await adminCreateRolePage.nameInput.fill('Create Edit Test'); await adminCreateRolePage.descriptionInput.fill('Test description'); await adminCreateRolePage.createButton.click(); - await adminCreateRolePage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminCreateRolePage.getSnackbarData( 'snackbar-create-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminCreateRolePage.closeSnackbar(); }); let roleRow = @@ -55,14 +52,10 @@ test.describe('Role management page', () => { await adminEditRolePage.nameInput.fill('Create Update Test'); await adminEditRolePage.descriptionInput.fill('Update test description'); await adminEditRolePage.updateButton.click(); - await adminEditRolePage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminEditRolePage.getSnackbarData( 'snackbar-edit-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminEditRolePage.closeSnackbar(); }); roleRow = @@ -87,14 +80,10 @@ test.describe('Role management page', () => { state: 'attached', }); await deleteModal.deleteButton.click(); - await adminRolesPage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminRolesPage.getSnackbarData( 'snackbar-delete-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminRolesPage.closeSnackbar(); await deleteModal.modal.waitFor({ state: 'detached', }); @@ -169,16 +158,13 @@ test.describe('Role management page', () => { await test.step('Create a new role', async () => { await adminRolesPage.createRoleButton.click(); await adminCreateRolePage.isMounted(); + await adminCreateRolePage.waitForPermissionsCatalogToVisible(); await adminCreateRolePage.nameInput.fill('Delete Role'); await adminCreateRolePage.createButton.click(); - await adminCreateRolePage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminCreateRolePage.getSnackbarData( 'snackbar-create-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminCreateRolePage.closeSnackbar(); }); await test.step('Create a new user with the "Delete Role" role', async () => { @@ -222,14 +208,10 @@ test.describe('Role management page', () => { .getByRole('option', { name: 'Admin' }) .click(); await adminEditUserPage.updateButton.click(); - await adminEditUserPage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminEditUserPage.getSnackbarData( 'snackbar-edit-user-success' ); await expect(snackbar.variant).toBe('success'); - await adminEditUserPage.closeSnackbar(); }); await test.step('Delete the original role', async () => { await adminRolesPage.navigateTo(); @@ -237,14 +219,10 @@ test.describe('Role management page', () => { const modal = await adminRolesPage.clickDeleteRole(row); await expect(modal.modal).toBeVisible(); await modal.deleteButton.click(); - await adminRolesPage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminRolesPage.getSnackbarData( 'snackbar-delete-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminRolesPage.closeSnackbar(); }); }); @@ -258,16 +236,13 @@ test.describe('Role management page', () => { await test.step('Create a new role', async () => { await adminRolesPage.createRoleButton.click(); await adminCreateRolePage.isMounted(); + await adminCreateRolePage.waitForPermissionsCatalogToVisible(); await adminCreateRolePage.nameInput.fill('Cannot Delete Role'); await adminCreateRolePage.createButton.click(); - await adminCreateRolePage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminCreateRolePage.getSnackbarData( 'snackbar-create-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminCreateRolePage.closeSnackbar(); }); await test.step('Create a new user with this role', async () => { await adminUsersPage.navigateTo(); @@ -287,6 +262,7 @@ test.describe('Role management page', () => { }); await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); }); + await test.step('Delete this user', async () => { await adminUsersPage.navigateTo(); const row = await adminUsersPage.findUserPageWithEmail( @@ -294,14 +270,10 @@ test.describe('Role management page', () => { ); const modal = await adminUsersPage.clickDeleteUser(row); await modal.deleteButton.click(); - await adminUsersPage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminUsersPage.getSnackbarData( 'snackbar-delete-user-success' ); await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); }); await test.step('Try deleting this role', async () => { await adminRolesPage.navigateTo(); @@ -309,7 +281,6 @@ test.describe('Role management page', () => { const modal = await adminRolesPage.clickDeleteRole(row); await modal.deleteButton.click(); await expect(modal.deleteAlert).toHaveCount(1); - await adminRolesPage.closeSnackbar(); }); }); }); @@ -327,16 +298,13 @@ test('Accessibility of role management page', async ({ await adminRolesPage.navigateTo(); await adminRolesPage.createRoleButton.click(); await adminCreateRolePage.isMounted(); + await adminCreateRolePage.waitForPermissionsCatalogToVisible(); await adminCreateRolePage.nameInput.fill('Basic Test'); await adminCreateRolePage.createButton.click(); - await adminCreateRolePage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminCreateRolePage.getSnackbarData( 'snackbar-create-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminCreateRolePage.closeSnackbar(); }); await test.step('Create a new user with the basic role', async () => { @@ -358,9 +326,7 @@ test('Accessibility of role management page', async ({ await test.step('Logout and login to the basic role user', async () => { const acceptInvitationLink = await adminCreateUserPage.acceptInvitationLink; - console.log(acceptInvitationLink); const acceptInvitationUrl = await acceptInvitationLink.textContent(); - console.log(acceptInvitationUrl); const acceptInvitatonToken = acceptInvitationUrl.split('?token=')[1]; await page.getByTestId('profile-menu-button').click(); @@ -416,10 +382,10 @@ test('Accessibility of role management page', async ({ await adminEditUserPage.roleInput.click(); await adminEditUserPage.page.getByRole('option', { name: 'Admin' }).click(); await adminEditUserPage.updateButton.click(); - await adminEditUserPage.snackbar.waitFor({ - state: 'attached', - }); - await adminEditUserPage.closeSnackbar(); + const snackbar = await adminEditUserPage.getSnackbarData( + 'snackbar-edit-user-success' + ); + await expect(snackbar.variant).toBe('success'); }); await test.step('Delete the role', async () => { @@ -431,14 +397,10 @@ test('Accessibility of role management page', async ({ state: 'attached', }); await deleteModal.deleteButton.click(); - await adminRolesPage.snackbar.waitFor({ - state: 'attached', - }); const snackbar = await adminRolesPage.getSnackbarData( 'snackbar-delete-role-success' ); await expect(snackbar.variant).toBe('success'); - await adminRolesPage.closeSnackbar(); await deleteModal.modal.waitFor({ state: 'detached', }); diff --git a/packages/e2e-tests/tests/admin/manage-users.spec.js b/packages/e2e-tests/tests/admin/manage-users.spec.js index 8b5aaf5b..e62b6bb8 100644 --- a/packages/e2e-tests/tests/admin/manage-users.spec.js +++ b/packages/e2e-tests/tests/admin/manage-users.spec.js @@ -7,7 +7,7 @@ const { test, expect } = require('../../fixtures/index'); test.describe('User management page', () => { test.beforeEach(async ({ adminUsersPage }) => { await adminUsersPage.navigateTo(); - await adminUsersPage.closeSnackbar(); + await adminUsersPage.closeAllSnackbars(); }); test('User creation and deletion process', async ({ @@ -36,7 +36,6 @@ test.describe('User management page', () => { await adminCreateUserPage.invitationEmailInfoAlert.waitFor({ state: 'attached', }); - await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); await adminUsersPage.navigateTo(); }); @@ -62,7 +61,6 @@ test.describe('User management page', () => { 'snackbar-edit-user-success' ); await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); await adminUsersPage.findUserPageWithEmail(user.email); userRow = await adminUsersPage.getUserRowByEmail(user.email); @@ -80,8 +78,6 @@ test.describe('User management page', () => { 'snackbar-delete-user-success' ); await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - await expect(userRow).not.toBeVisible(false); }); }); @@ -91,7 +87,6 @@ test.describe('User management page', () => { }) => { adminCreateUserPage.seed(9100); const testUser = adminCreateUserPage.generateUser(); - await test.step('Create the test user', async () => { await adminUsersPage.navigateTo(); await adminUsersPage.createUserButton.click(); @@ -117,8 +112,6 @@ test.describe('User management page', () => { ); await expect(snackbar).not.toBeNull(); await expect(snackbar.variant).toBe('success'); - await adminUsersPage.closeSnackbar(); - await expect(userRow).not.toBeVisible(false); }); await test.step('Create the user again', async () => { diff --git a/packages/e2e-tests/tests/connections/enabled-pop-up-reminder.spec.js b/packages/e2e-tests/tests/connections/enabled-pop-up-reminder.spec.js index 32d30525..066f6cf1 100644 --- a/packages/e2e-tests/tests/connections/enabled-pop-up-reminder.spec.js +++ b/packages/e2e-tests/tests/connections/enabled-pop-up-reminder.spec.js @@ -1,24 +1,60 @@ +const { request } = require('@playwright/test'); const { test, expect } = require('../../fixtures/index'); -const {AddMattermostConnectionModal} = require('../../fixtures/apps/mattermost/add-mattermost-connection-modal'); +const { + AddMattermostConnectionModal, +} = require('../../fixtures/apps/mattermost/add-mattermost-connection-modal'); +const { + createFlow, + updateFlowName, + getFlow, + updateFlowStep, + testStep, +} = require('../../helpers/flow-api-helper'); +const { getToken } = require('../../helpers/auth-api-helper'); test.describe('Pop-up message on connections', () => { test.beforeEach(async ({ flowEditorPage, page }) => { - await page.getByTestId('create-flow-button').click(); - await page.waitForURL( - /\/editor\/[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}/ - ); - await expect(page.getByTestId('flow-step')).toHaveCount(2); + const apiRequest = await request.newContext(); + const tokenJsonResponse = await getToken(apiRequest); + const token = tokenJsonResponse.data.token; - await flowEditorPage.flowName.click(); - await flowEditorPage.flowNameInput.fill('PopupFlow'); - await flowEditorPage.createWebhookTrigger(true); + let flow = await createFlow(apiRequest, token); + const flowId = flow.data.id; + await updateFlowName(apiRequest, token, flowId); + flow = await getFlow(apiRequest, token, flowId); + const flowSteps = flow.data.steps; + const triggerStepId = flowSteps.find((step) => step.type === 'trigger').id; + const actionStepId = flowSteps.find((step) => step.type === 'action').id; - await flowEditorPage.chooseAppAndEvent('Mattermost', 'Send a message to channel'); - await expect(flowEditorPage.continueButton).toHaveCount(1); - await expect(flowEditorPage.continueButton).not.toBeEnabled(); + const triggerStep = await updateFlowStep(apiRequest, token, triggerStepId, { + appKey: 'webhook', + key: 'catchRawWebhook', + parameters: { + workSynchronously: false, + }, + }); + await apiRequest.get(triggerStep.data.webhookUrl); + await testStep(apiRequest, token, triggerStepId); + + await updateFlowStep(apiRequest, token, actionStepId, { + appKey: 'mattermost', + key: 'sendMessageToChannel', + }); + await testStep(apiRequest, token, actionStepId); + + await page.reload(); + + const flowRow = await page.getByTestId('flow-row').filter({ + hasText: flowId, + }); + await flowRow.click(); + const flowTriggerStep = await page.getByTestId('flow-step').nth(1); + await flowTriggerStep.click(); + await page.getByText('Choose connection').click(); await flowEditorPage.connectionAutocomplete.click(); - await flowEditorPage.addNewConnectionItem.click(); }); + await flowEditorPage.addNewConnectionItem.click(); + }); test('should show error to remind to enable pop-up on connection create', async ({ page, @@ -28,7 +64,7 @@ test.describe('Pop-up message on connections', () => { // Inject script to override window.open await page.evaluate(() => { // eslint-disable-next-line no-undef - window.open = function() { + window.open = function () { console.log('Popup blocked!'); return null; }; @@ -37,8 +73,10 @@ test.describe('Pop-up message on connections', () => { await addMattermostConnectionModal.fillConnectionForm(); await addMattermostConnectionModal.submitConnectionForm(); - await expect(page.getByTestId("add-connection-error")).toHaveCount(1); - await expect(page.getByTestId("add-connection-error")).toHaveText('Make sure pop-ups are enabled in your browser.'); + await expect(page.getByTestId('add-connection-error')).toHaveCount(1); + await expect(page.getByTestId('add-connection-error')).toHaveText( + 'Make sure pop-ups are enabled in your browser.' + ); }); test('should not show pop-up error if pop-ups are enabled on connection create', async ({ @@ -51,13 +89,15 @@ test.describe('Pop-up message on connections', () => { await addMattermostConnectionModal.submitConnectionForm(); const popup = await popupPromise; - await expect(popup.url()).toContain("mattermost"); - await expect(page.getByTestId("add-connection-error")).toHaveCount(0); + await expect(popup.url()).toContain('mattermost'); + await expect(page.getByTestId('add-connection-error')).toHaveCount(0); await test.step('Should show error on failed credentials verification', async () => { await popup.close(); - await expect(page.getByTestId("add-connection-error")).toHaveCount(1); - await expect(page.getByTestId("add-connection-error")).toHaveText('Error occured while verifying credentials!'); + await expect(page.getByTestId('add-connection-error')).toHaveCount(1); + await expect(page.getByTestId('add-connection-error')).toHaveText( + 'Error occured while verifying credentials!' + ); }); }); -}); \ No newline at end of file +}); diff --git a/packages/e2e-tests/tests/my-profile/profile-updates.spec.js b/packages/e2e-tests/tests/my-profile/profile-updates.spec.js index fc0ce7d0..06841738 100644 --- a/packages/e2e-tests/tests/my-profile/profile-updates.spec.js +++ b/packages/e2e-tests/tests/my-profile/profile-updates.spec.js @@ -1,63 +1,48 @@ +const { request } = require('@playwright/test'); const { publicTest, expect } = require('../../fixtures/index'); -const { AdminUsersPage } = require('../../fixtures/admin/users-page'); const { MyProfilePage } = require('../../fixtures/my-profile-page'); const { LoginPage } = require('../../fixtures/login-page'); +const { addUser, acceptInvitation } = require('../../helpers/user-api-helper'); +const { getToken } = require('../../helpers/auth-api-helper'); publicTest.describe('My Profile', () => { let testUser; - publicTest.beforeEach( - async ({ acceptInvitationPage, adminCreateUserPage, loginPage, page }) => { - let acceptInvitationLink; + publicTest.beforeEach(async ({ adminCreateUserPage, loginPage, page }) => { + let addUserResponse; + const apiRequest = await request.newContext(); - adminCreateUserPage.seed( - Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) + adminCreateUserPage.seed( + Math.ceil(Math.random() * Number.MAX_SAFE_INTEGER) + ); + testUser = adminCreateUserPage.generateUser(); + + await publicTest.step('create new user', async () => { + const tokenJsonResponse = await getToken(apiRequest); + addUserResponse = await addUser( + apiRequest, + tokenJsonResponse.data.token, + { + fullName: testUser.fullName, + email: testUser.email, + } ); - testUser = adminCreateUserPage.generateUser(); + }); - const adminUsersPage = new AdminUsersPage(page); - const myProfilePage = new MyProfilePage(page); - - await publicTest.step('login as Admin', async () => { - await loginPage.login(); - await expect(loginPage.page).toHaveURL('/flows'); + await publicTest.step('accept invitation', async () => { + let acceptToken = addUserResponse.data.acceptInvitationUrl.split('=')[1]; + await acceptInvitation(apiRequest, { + token: acceptToken, + password: LoginPage.defaultPassword, }); + }); - await publicTest.step('create new user', async () => { - await adminUsersPage.navigateTo(); - await adminUsersPage.createUserButton.click(); - await adminCreateUserPage.fullNameInput.fill(testUser.fullName); - await adminCreateUserPage.emailInput.fill(testUser.email); - await adminCreateUserPage.roleInput.click(); - await adminCreateUserPage.page - .getByRole('option', { name: 'Admin' }) - .click(); - await adminCreateUserPage.createButton.click(); - await adminCreateUserPage.expectCreateUserSuccessAlertToBeVisible(); - }); - - await publicTest.step('copy invitation link', async () => { - const invitationMessage = - await adminCreateUserPage.acceptInvitationLink; - acceptInvitationLink = await invitationMessage.getAttribute('href'); - }); - - await publicTest.step('logout', async () => { - await myProfilePage.logout(); - }); - - await publicTest.step('accept invitation', async () => { - await page.goto(acceptInvitationLink); - await acceptInvitationPage.acceptInvitation(LoginPage.defaultPassword); - }); - - await publicTest.step('login as new Admin', async () => { - await loginPage.login(testUser.email, LoginPage.defaultPassword); - await expect(loginPage.loginButton).not.toBeVisible(); - await expect(page).toHaveURL('/flows'); - }); - } - ); + await publicTest.step('login as new Admin', async () => { + await loginPage.login(testUser.email, LoginPage.defaultPassword); + await expect(loginPage.loginButton).not.toBeVisible(); + await expect(page).toHaveURL('/flows'); + }); + }); publicTest('user should be able to change own data', async ({ page }) => { const myProfilePage = new MyProfilePage(page); diff --git a/packages/e2e-tests/yarn.lock b/packages/e2e-tests/yarn.lock index 4953ecb7..22f7c304 100644 --- a/packages/e2e-tests/yarn.lock +++ b/packages/e2e-tests/yarn.lock @@ -79,7 +79,7 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@playwright/test@^1.45.1": +"@playwright/test@1.49.0": version "1.49.0" resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.49.0.tgz#74227385b58317ee076b86b56d0e1e1b25cff01e" integrity sha512-DMulbwQURa8rNIQrf94+jPJQ4FmOVdpE5ZppRNvWVjvhC+6sOeo28r8MgIpQRYouXRtt/FCCXU7zn20jnHR4Qw== @@ -101,6 +101,13 @@ acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0" integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA== +ajv-formats@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-2.1.1.tgz#6e669400659eb74973bbf2e33327180a0996b520" + integrity sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA== + dependencies: + ajv "^8.0.0" + ajv@^6.12.4: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -111,6 +118,16 @@ ajv@^6.12.4: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^8.0.0, ajv@^8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.17.1.tgz#37d9a5c776af6bc92d7f4f9510eba4c0a60d11a6" + integrity sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g== + dependencies: + fast-deep-equal "^3.1.3" + fast-uri "^3.0.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + ansi-regex@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" @@ -226,6 +243,11 @@ cross-spawn@^7.0.2: shebang-command "^2.0.0" which "^2.0.1" +db-errors@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/db-errors/-/db-errors-0.2.3.tgz#a6a38952e00b20e790f2695a6446b3c65497ffa2" + integrity sha512-OOgqgDuCavHXjYSJoV2yGhv6SeG8nk42aoCSoyXLZUH7VwFG27rxbavU1z+VrZbZjphw5UkDQwUlD21MwZpUng== + debug@4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" @@ -404,6 +426,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-uri@^3.0.1: + version "3.0.3" + resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.0.3.tgz#892a1c91802d5d7860de728f18608a0573142241" + integrity sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw== + fastq@^1.6.0: version "1.17.1" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" @@ -622,6 +649,11 @@ json-schema-traverse@^0.4.1: resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2" + integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug== + json-stable-stringify-without-jsonify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" @@ -727,6 +759,15 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +objection@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/objection/-/objection-3.1.5.tgz#53c32f6b6cba2958bc28cf723de96c2676da8286" + integrity sha512-Hx/ipAwXSuRBbOMWFKtRsAN0yITafqXtWB4OT4Z9wED7ty1h7bOnBdhLtcNus23GwLJqcMsRWdodL2p5GwlnfQ== + dependencies: + ajv "^8.17.1" + ajv-formats "^2.1.1" + db-errors "^0.2.3" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -933,6 +974,11 @@ rechoir@^0.8.0: dependencies: resolve "^1.20.0" +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" diff --git a/packages/web/src/components/PermissionCatalogField/index.ee.jsx b/packages/web/src/components/PermissionCatalogField/index.ee.jsx index 21c89f81..1c8d6902 100644 --- a/packages/web/src/components/PermissionCatalogField/index.ee.jsx +++ b/packages/web/src/components/PermissionCatalogField/index.ee.jsx @@ -32,7 +32,7 @@ const PermissionCatalogField = ({ return ; return ( - +