/* * Copyright 2024 Collate. * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import test, { expect } from '@playwright/test'; import { GlobalSettingOptions } from '../../constant/settings'; import { EntityTypeEndpoint } from '../../support/entity/Entity.interface'; import { TableClass } from '../../support/entity/TableClass'; import { TeamClass } from '../../support/team/TeamClass'; import { UserClass } from '../../support/user/UserClass'; import { createNewPage, descriptionBox, getApiContext, redirectToHomePage, toastNotification, uuid, } from '../../utils/common'; import { addMultiOwner } from '../../utils/entity'; import { settingClick } from '../../utils/sidebar'; import { addTeamOwnerToEntity, createTeam, hardDeleteTeam, searchTeam, softDeleteTeam, verifyAssetsInTeamsPage, } from '../../utils/team'; // use the admin user to login test.use({ storageState: 'playwright/.auth/admin.json' }); const user = new UserClass(); const userName = user.data.email.split('@')[0]; let teamDetails: { name?: string; displayName?: string; email?: string; description?: string; updatedName: string; teamType: string; updatedEmail: string; } = { updatedName: `updated-pw%team-${uuid()}`, teamType: 'Group', updatedEmail: `pwteamUpdated${uuid()}@example.com`, }; test.describe('Teams Page', () => { test.slow(true); test.beforeAll('Setup pre-requests', async ({ browser }) => { const { apiContext, afterAction } = await createNewPage(browser); await user.create(apiContext); await afterAction(); }); test.afterAll('Cleanup', async ({ browser }) => { const { apiContext, afterAction } = await createNewPage(browser); await user.delete(apiContext); await afterAction(); }); test.beforeEach('Visit Home Page', async ({ page }) => { await redirectToHomePage(page); const fetchOrganizationResponse = page.waitForResponse( '/api/v1/teams?parentTeam=Organization&include=all&fields=**' ); await settingClick(page, GlobalSettingOptions.TEAMS); await fetchOrganizationResponse; }); test('Teams Page Flow', async ({ page }) => { await test.step('Create a new team', async () => { await settingClick(page, GlobalSettingOptions.TEAMS); await page.waitForLoadState('networkidle'); await page.waitForSelector('[data-testid="add-team"]'); await page.getByTestId('add-team').click(); const newTeamData = await createTeam(page); teamDetails = { ...teamDetails, ...newTeamData, }; }); await test.step('Add owner to created team', async () => { const getTeamResponse = page.waitForResponse(`/api/v1/teams/name/*?*`); await page.getByRole('link', { name: teamDetails.displayName }).click(); await getTeamResponse; await addMultiOwner({ page, ownerNames: [user.getUserName()], activatorBtnDataTestId: 'edit-owner', endpoint: EntityTypeEndpoint.Teams, type: 'Users', }); }); await test.step('Update email of created team', async () => { // Edit email await page.locator('[data-testid="edit-email"]').click(); await page .locator('[data-testid="email-input"]') .fill(teamDetails.updatedEmail); const saveEditEmailResponse = page.waitForResponse('/api/v1/teams/*'); await page.locator('[data-testid="save-edit-email"]').click(); await saveEditEmailResponse; // Reload the page await page.reload(); // Check for updated email await expect(page.locator('[data-testid="email-value"]')).toContainText( teamDetails.updatedEmail ); }); await test.step('Add user to created team', async () => { // Navigate to users tab and add new user await page.locator('[data-testid="users"]').click(); const fetchUsersResponse = page.waitForResponse( '/api/v1/users?limit=25&isBot=false' ); await page.locator('[data-testid="add-new-user"]').click(); await fetchUsersResponse; // Search and select the user await page .locator('[data-testid="selectable-list"] [data-testid="searchbar"]') .fill(user.getUserName()); await page .locator( `[data-testid="selectable-list"] [title="${user.getUserName()}"]` ) .click(); await expect( page.locator( `[data-testid="selectable-list"] [title="${user.getUserName()}"]` ) ).toHaveClass(/active/); const updateTeamResponse = page.waitForResponse('/api/v1/users*'); // Update the team with the new user await page.locator('[data-testid="selectable-list-update-btn"]').click(); await updateTeamResponse; // Verify the user is added to the team await expect( page.locator(`[data-testid="${userName.toLowerCase()}"]`) ).toBeVisible(); }); await test.step('Remove added user from created team', async () => { await page.locator('[data-testid="users"]').click(); // Click on add new user const fetchUsersResponse = page.waitForResponse( '/api/v1/users?limit=25&isBot=false' ); await page.locator('[data-testid="add-new-user"]').click(); await fetchUsersResponse; // Select the user to remove await page .locator( `[data-testid="selectable-list"] [title="${user.getUserName()}"]` ) .click(); const updateTeamResponse = page.waitForResponse('/api/v1/users*'); await page.locator('[data-testid="selectable-list-update-btn"]').click(); await updateTeamResponse; // Verify the user is removed from the team await expect( page.locator(`[data-testid="${userName.toLowerCase()}"]`) ).not.toBeVisible(); }); await test.step('Join team should work properly', async () => { await page.locator('[data-testid="users"]').click(); // Click on join teams button await page.locator('[data-testid="join-teams"]').click(); await toastNotification(page, 'Team joined successfully!'); // Verify leave team button exists await expect( page.locator('[data-testid="leave-team-button"]') ).toBeVisible(); }); await test.step('Update display name for created team', async () => { // Click on edit display name await page.locator('[data-testid="edit-team-name"]').click(); // Enter the updated team name await page .locator('[data-testid="team-name-input"]') .fill(teamDetails.updatedName); // Save the updated display name const patchTeamResponse = page.waitForResponse( (response) => response.url().includes('/api/v1/teams/') && response.request().method() === 'PATCH' ); await page.locator('[data-testid="saveAssociatedTag"]').click(); await patchTeamResponse; // Validate the updated display name await expect(page.locator('[data-testid="team-heading"]')).toHaveText( teamDetails.updatedName ); await expect(page.locator('[data-testid="inactive-link"]')).toContainText( teamDetails.updatedName ); }); await test.step('Update description for created team', async () => { // Validate the updated display name await expect(page.locator('[data-testid="team-heading"]')).toContainText( teamDetails.updatedName ); await expect(page.locator('[data-testid="inactive-link"]')).toContainText( teamDetails.updatedName ); await page.locator('[role="tablist"] [data-icon="right"]').click(); // Click on edit description button await page.locator('[data-testid="edit-description"]').click(); await page.waitForLoadState('domcontentloaded'); // Entering updated description const updatedDescription = 'This is an updated team description'; await page.click(descriptionBox); await page.keyboard.type(updatedDescription); const patchDescriptionResponse = page.waitForResponse( (response) => response.url().includes('/api/v1/teams/') && response.request().method() === 'PATCH' ); await page.locator('[data-testid="save"]').click(); await patchDescriptionResponse; // Validating the updated description await expect( page.locator('[data-testid="asset-description-container"] p') ).toContainText(updatedDescription); }); await test.step('Leave team flow should work properly', async () => { await expect(page.locator('[data-testid="team-heading"]')).toContainText( teamDetails?.updatedName ?? '' ); // Click on Leave team await page.locator('[data-testid="leave-team-button"]').click(); const leaveTeamResponse = page.waitForResponse( (response) => response.url().includes('/api/v1/users/') && response.request().method() === 'PATCH' ); // Click on confirm button await page.locator('.ant-modal-footer').getByText('Confirm').click(); await leaveTeamResponse; // Verify that the "Join Teams" button is now visible await expect(page.locator('[data-testid="join-teams"]')).toBeVisible(); }); await test.step('Soft Delete Team', async () => { await softDeleteTeam(page); const fetchOrganizationResponse = page.waitForResponse( '/api/v1/teams?*parentTeam=Organization*fields=*' ); await settingClick(page, GlobalSettingOptions.TEAMS); await fetchOrganizationResponse; // Check if the table does not contain the team name await expect(page.locator('table')).not.toContainText( teamDetails?.displayName ?? '' ); // Click on the show deleted button await page.locator('[data-testid="show-deleted"]').click(); // Check if the table contains the team name and click on it await expect( page.getByRole('link', { name: teamDetails?.updatedName }) ).toBeVisible(); }); await test.step('Hard Delete Team', async () => { const fetchTeamResponse = page.waitForResponse(`/api/v1/teams/name/*`); await page.getByRole('link', { name: teamDetails.updatedName }).click(); await fetchTeamResponse; // Verify the team heading contains the updated name await expect(page.locator('[data-testid="team-heading"]')).toContainText( teamDetails?.updatedName ?? '' ); await hardDeleteTeam(page); // Validate the deleted team await expect( page.getByRole('link', { name: teamDetails?.updatedName }) ).not.toBeVisible(); }); }); test('Create a new public team', async ({ page }) => { await settingClick(page, GlobalSettingOptions.TEAMS); await page.waitForSelector('[data-testid="add-team"]'); await page.getByTestId('add-team').click(); const publicTeam = await createTeam(page, true); await page.getByRole('link', { name: publicTeam.displayName }).click(); await page .getByTestId('team-details-collapse') .getByTestId('manage-button') .click(); await expect(page.locator('button[role="switch"]')).toHaveAttribute( 'aria-checked', 'true' ); await page.click('body'); // Equivalent to clicking outside await hardDeleteTeam(page); }); test('Create a new private team', async ({ page }) => { await settingClick(page, GlobalSettingOptions.TEAMS); await page.waitForSelector('[data-testid="add-team"]'); await page.getByTestId('add-team').click(); const publicTeam = await createTeam(page); await page.getByRole('link', { name: publicTeam.displayName }).click(); await page .getByTestId('team-details-collapse') .getByTestId('manage-button') .click(); await expect(page.locator('button[role="switch"]')).toHaveAttribute( 'aria-checked', 'false' ); await page.click('body'); // Equivalent to clicking outside await hardDeleteTeam(page); }); test('Permanently deleting a team without soft deleting should work properly', async ({ page, }) => { const { apiContext, afterAction } = await getApiContext(page); const team = new TeamClass(); await team.create(apiContext); await settingClick(page, GlobalSettingOptions.TEAMS); await page.waitForLoadState('networkidle'); const getTeamResponse = page.waitForResponse(`/api/v1/teams/name/*?*`); await page .getByRole('link', { name: team.responseData?.['displayName'] }) .click(); await getTeamResponse; await expect(page.locator('[data-testid="team-heading"]')).toContainText( team.responseData?.['displayName'] ); await hardDeleteTeam(page); await afterAction(); }); test('Team search should work properly', async ({ page }) => { const { apiContext, afterAction } = await getApiContext(page); const id = uuid(); const team1 = new TeamClass(); const team2 = new TeamClass({ name: `pw team space-${id}`, displayName: `pw team space ${id}`, description: 'playwright team with space description', teamType: 'Group', }); const team3 = new TeamClass({ name: `pw.team.dot-${id}`, displayName: `pw.team.dot ${id}`, description: 'playwright team with dot description', teamType: 'Group', }); await team1.create(apiContext); await team2.create(apiContext); await team3.create(apiContext); try { await settingClick(page, GlobalSettingOptions.TEAMS); await page.waitForLoadState('networkidle'); for (const team of [team1, team2, team3]) { await searchTeam(page, team.responseData?.['displayName']); } // Should not find the organization team and show errorPlaceholder await searchTeam(page, 'Organization', true); } finally { await team1.delete(apiContext); await team2.delete(apiContext); await team3.delete(apiContext); await afterAction(); } }); test('Export team', async ({ page }) => { const { apiContext } = await getApiContext(page); const id = uuid(); const team = new TeamClass({ name: `pw%team.export-${id}`, displayName: `pw team export ${id}`, description: 'playwright team export description', teamType: 'Department', }); await team.create(apiContext); try { await settingClick(page, GlobalSettingOptions.TEAMS); await page.waitForLoadState('networkidle'); await searchTeam(page, team.responseData?.['displayName']); await page .locator(`[data-row-key="${team.data.name}"]`) .getByRole('link') .click(); await page.waitForLoadState('networkidle'); await expect(page.getByTestId('team-heading')).toHaveText( team.data.displayName ); const downloadPromise = page.waitForEvent('download'); await page.getByTestId('manage-button').click(); await page.getByTestId('export-details-container').click(); await page.fill('#fileName', team.data.name); await page.click('#submit-button'); const download = await downloadPromise; // Wait for the download process to complete and save the downloaded file somewhere. await download.saveAs('downloads/' + download.suggestedFilename()); } finally { await team.delete(apiContext); } }); test('Team assets should', async ({ page }) => { const { apiContext, afterAction } = await getApiContext(page); const id = uuid(); const table1 = new TableClass(); const table2 = new TableClass(); const table3 = new TableClass(); const table4 = new TableClass(); const team1 = new TeamClass({ name: `pw%percent-${id}`, displayName: `pw team percent ${id}`, description: 'playwright team with percent description', teamType: 'Group', }); const team2 = new TeamClass({ name: `pw&-${id}`, displayName: `pw team ampersand ${id}`, description: 'playwright team with ampersand description', teamType: 'Group', }); const team3 = new TeamClass({ name: `pw.team.dot-${id}`, displayName: `pw.team.dot ${id}`, description: 'playwright team with dot description', teamType: 'Group', }); const team4 = new TeamClass({ name: `pw team space-${id}`, displayName: `pw team space ${id}`, description: 'playwright team with space description', teamType: 'Group', }); await table1.create(apiContext); await table2.create(apiContext); await table3.create(apiContext); await table4.create(apiContext); await team1.create(apiContext); await team2.create(apiContext); await team3.create(apiContext); await team4.create(apiContext); try { await addTeamOwnerToEntity(page, table1, team1); await addTeamOwnerToEntity(page, table2, team2); await addTeamOwnerToEntity(page, table3, team3); await addTeamOwnerToEntity(page, table4, team4); await verifyAssetsInTeamsPage(page, table1, team1, 1); await verifyAssetsInTeamsPage(page, table2, team2, 1); await verifyAssetsInTeamsPage(page, table3, team3, 1); await verifyAssetsInTeamsPage(page, table4, team4, 1); } finally { await table1.delete(apiContext); await table2.delete(apiContext); await table3.delete(apiContext); await table4.delete(apiContext); await team1.delete(apiContext); await team2.delete(apiContext); await team3.delete(apiContext); await team4.delete(apiContext); await afterAction(); } }); test('Delete a user from the table', async ({ page }) => { const { apiContext, afterAction } = await getApiContext(page); const id = uuid(); const table1 = new TableClass(); const team1 = new TeamClass({ name: `pw%percent-${id}`, displayName: `pw team percent ${id}`, description: 'playwright team with percent description', teamType: 'Group', }); await table1.create(apiContext); await team1.create(apiContext); await addTeamOwnerToEntity(page, table1, team1); await verifyAssetsInTeamsPage(page, table1, team1, 1); // Navigate to users tab and add new user await page.locator('[data-testid="users"]').click(); const fetchUsersResponse = page.waitForResponse( '/api/v1/users?limit=25&isBot=false' ); await page.locator('[data-testid="add-new-user"]').click(); await fetchUsersResponse; // Search and select the user await page .locator('[data-testid="selectable-list"] [data-testid="searchbar"]') .fill(user.getUserName()); await page .locator( `[data-testid="selectable-list"] [title="${user.getUserName()}"]` ) .click(); await expect( page.locator( `[data-testid="selectable-list"] [title="${user.getUserName()}"]` ) ).toHaveClass(/active/); const updateTeamResponse = page.waitForResponse('/api/v1/users*'); // Update the team with the new user await page.locator('[data-testid="selectable-list-update-btn"]').click(); await updateTeamResponse; // Verify the user is added to the team await expect( page.locator(`[data-row-key="${userName.toLowerCase()}"]`) ).toBeVisible(); await page .locator(`[data-row-key="${userName.toLowerCase()}"]`) .getByTestId('remove-user-btn') .click(); const updatedTeamResponse = page.waitForResponse('api/v1/users*'); await page.getByRole('button', { name: 'confirm' }).click(); await updatedTeamResponse; await expect( page.locator(`[data-row-key="${userName.toLowerCase()}"]`) ).not.toBeVisible(); await afterAction(); }); });