1319 lines
36 KiB
TypeScript

/*
* 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 { expect, Page } from '@playwright/test';
import { lowerCase } from 'lodash';
import {
customFormatDateTime,
getCurrentMillis,
getEpochMillisForFutureDays,
} from '../../src/utils/date-time/DateTimeUtils';
import {
ENTITIES_WITHOUT_FOLLOWING_BUTTON,
LIST_OF_FIELDS_TO_EDIT_NOT_TO_BE_PRESENT,
LIST_OF_FIELDS_TO_EDIT_TO_BE_DISABLED,
} from '../constant/delete';
import { ES_RESERVED_CHARACTERS } from '../constant/entity';
import { EntityTypeEndpoint } from '../support/entity/Entity.interface';
import { clickOutside, redirectToHomePage } from './common';
export const visitEntityPage = async (data: {
page: Page;
searchTerm: string;
dataTestId: string;
}) => {
const { page, searchTerm, dataTestId } = data;
const waitForSearchResponse = page.waitForResponse(
'/api/v1/search/query?q=*index=dataAsset*'
);
await page.getByTestId('searchBox').fill(searchTerm);
await waitForSearchResponse;
await page.getByTestId(dataTestId).getByTestId('data-name').click();
await page.getByTestId('searchBox').clear();
};
export const addOwner = async ({
page,
owner,
endpoint,
type = 'Users',
dataTestId,
initiatorId = 'edit-owner',
}: {
page: Page;
owner: string;
endpoint: EntityTypeEndpoint;
type?: 'Teams' | 'Users';
dataTestId?: string;
initiatorId?: string;
}) => {
await page.getByTestId(initiatorId).click();
if (type === 'Users') {
const userListResponse = page.waitForResponse(
'/api/v1/users?limit=*&isBot=false*'
);
await page.getByRole('tab', { name: type }).click();
await userListResponse;
}
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const ownerSearchBar = await page
.getByTestId(`owner-select-${lowerCase(type)}-search-bar`)
.isVisible();
if (!ownerSearchBar) {
await page.getByRole('tab', { name: type }).click();
}
const searchUser = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(owner)}*`
);
await page
.getByTestId(`owner-select-${lowerCase(type)}-search-bar`)
.fill(owner);
await searchUser;
if (type === 'Teams') {
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByRole('listitem', { name: owner, exact: true }).click();
await patchRequest;
} else {
await page.getByRole('listitem', { name: owner, exact: true }).click();
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByTestId('selectable-list-update-btn').click();
await patchRequest;
}
await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText(
owner
);
};
export const updateOwner = async ({
page,
owner,
endpoint,
type = 'Users',
dataTestId,
}: {
page: Page;
owner: string;
endpoint: EntityTypeEndpoint;
type?: 'Teams' | 'Users';
dataTestId?: string;
}) => {
await page.getByTestId('edit-owner').click();
await page.getByRole('tab', { name: type }).click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const searchUser = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(owner)}*`
);
await page
.getByTestId(`owner-select-${lowerCase(type)}-search-bar`)
.fill(owner);
await searchUser;
if (type === 'Teams') {
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByRole('listitem', { name: owner, exact: true }).click();
await patchRequest;
} else {
await page.getByRole('listitem', { name: owner, exact: true }).click();
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByTestId('selectable-list-update-btn').click();
await patchRequest;
}
await expect(page.getByTestId(dataTestId ?? 'owner-link')).toContainText(
owner
);
};
export const removeOwnersFromList = async ({
page,
endpoint,
ownerNames,
dataTestId,
}: {
page: Page;
endpoint: EntityTypeEndpoint;
ownerNames: string[];
dataTestId?: string;
}) => {
await page.getByTestId('edit-owner').click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
for (const ownerName of ownerNames) {
const ownerItem = page.getByRole('listitem', {
name: ownerName,
exact: true,
});
await ownerItem.click();
}
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.click('[data-testid="selectable-list-update-btn"]');
await patchRequest;
for (const ownerName of ownerNames) {
await expect(
page.getByTestId(dataTestId ?? 'owner-link')
).not.toContainText(ownerName);
}
};
// Removes All Owners
export const removeOwner = async ({
page,
endpoint,
ownerName,
type = 'Users',
dataTestId,
}: {
page: Page;
endpoint: EntityTypeEndpoint;
ownerName: string;
type?: 'Teams' | 'Users';
dataTestId?: string;
}) => {
await page.getByTestId('edit-owner').click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
if (type === 'Teams') {
await expect(page.getByTestId('remove-owner').locator('svg')).toBeVisible();
await page.getByTestId('remove-owner').locator('svg').click();
} else {
await page.click('[data-testid="clear-all-button"]');
await page.click('[data-testid="selectable-list-update-btn"]');
}
await patchRequest;
await expect(page.getByTestId(dataTestId ?? 'owner-link')).not.toContainText(
ownerName
);
};
export const addMultiOwner = async (data: {
page: Page;
ownerNames: string | string[];
activatorBtnDataTestId: string;
endpoint: EntityTypeEndpoint;
resultTestId?: string;
isSelectableInsideForm?: boolean;
type: 'Teams' | 'Users';
clearAll?: boolean;
}) => {
const {
page,
ownerNames,
activatorBtnDataTestId,
resultTestId = 'owner-link',
isSelectableInsideForm = false,
endpoint,
type,
clearAll = true,
} = data;
const isMultipleOwners = Array.isArray(ownerNames);
const owners = isMultipleOwners ? ownerNames : [ownerNames];
await page.click(`[data-testid="${activatorBtnDataTestId}"]`);
await expect(page.locator("[data-testid='select-owner-tabs']")).toBeVisible();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
await page
.locator("[data-testid='select-owner-tabs']")
.getByRole('tab', { name: 'Users' })
.click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
if (clearAll && isMultipleOwners) {
await page.click('[data-testid="clear-all-button"]');
}
for (const ownerName of owners) {
const searchOwner = page.waitForResponse(
'api/v1/search/query?q=*&index=user_search_index*'
);
await page.locator('[data-testid="owner-select-users-search-bar"]').clear();
await page.fill('[data-testid="owner-select-users-search-bar"]', ownerName);
await searchOwner;
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const ownerItem = page.getByRole('listitem', {
name: ownerName,
exact: true,
});
if (type === 'Teams') {
if (isSelectableInsideForm) {
await ownerItem.click();
} else {
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await ownerItem.click();
await patchRequest;
}
} else {
await ownerItem.click();
}
}
if (isMultipleOwners) {
const updateButton = page.getByTestId('selectable-list-update-btn');
if (isSelectableInsideForm) {
await updateButton.click();
} else {
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await updateButton.click();
await patchRequest;
}
}
for (const name of owners) {
await expect(page.locator(`[data-testid="${resultTestId}"]`)).toContainText(
name
);
}
};
export const assignTier = async (
page: Page,
tier: string,
endpoint: string
) => {
await page.getByTestId('edit-tier').click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
const patchRequest = page.waitForResponse(`/api/v1/${endpoint}/*`);
await page.getByTestId(`radio-btn-${tier}`).click();
await patchRequest;
await clickOutside(page);
await expect(page.getByTestId('Tier')).toContainText(tier);
};
export const removeTier = async (page: Page) => {
await page.getByTestId('edit-tier').click();
await page.waitForSelector('[data-testid="loader"]', { state: 'detached' });
await page.getByTestId('clear-tier').click();
await clickOutside(page);
await expect(page.getByTestId('Tier')).toContainText('No Tier');
};
export const updateDescription = async (page: Page, description: string) => {
await page.getByTestId('edit-description').click();
await page.locator('.ProseMirror').first().click();
await page.locator('.ProseMirror').first().clear();
await page.locator('.ProseMirror').first().fill(description);
await page.getByTestId('save').click();
await expect(
page.getByTestId('asset-description-container').getByRole('paragraph')
).toContainText(description);
};
export const assignTag = async (
page: Page,
tag: string,
action: 'Add' | 'Edit' = 'Add'
) => {
await page
.getByTestId('entity-right-panel')
.getByTestId('tags-container')
.getByTestId(action === 'Add' ? 'add-tag' : 'edit-button')
.click();
const searchTags = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(tag)}*`
);
await page.locator('#tagsForm_tags').fill(tag);
await searchTags;
await page.getByTestId(`tag-${tag}`).click();
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await expect(
page
.getByTestId('entity-right-panel')
.getByTestId('tags-container')
.getByTestId(`tag-${tag}`)
).toBeVisible();
};
export const assignTagToChildren = async ({
page,
tag,
rowId,
action = 'Add',
rowSelector = 'data-row-key',
}: {
page: Page;
tag: string;
rowId: string;
action?: 'Add' | 'Edit';
rowSelector?: string;
}) => {
await page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('tags-container')
.getByTestId(action === 'Add' ? 'add-tag' : 'edit-button')
.click();
const searchTags = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(tag)}*`
);
await page.locator('#tagsForm_tags').fill(tag);
await searchTags;
await page.getByTestId(`tag-${tag}`).click();
const patchRequest = page.waitForResponse(
(response) => response.request().method() === 'PATCH'
);
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await patchRequest;
await expect(
page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('tags-container')
.getByTestId(`tag-${tag}`)
).toBeVisible();
};
export const removeTag = async (page: Page, tags: string[]) => {
for (const tag of tags) {
await page
.getByTestId('entity-right-panel')
.getByTestId('tags-container')
.getByTestId('edit-button')
.click();
await page
.getByTestId(`selected-tag-${tag}`)
.getByTestId('remove-tags')
.locator('svg')
.click();
const patchRequest = page.waitForResponse(
(response) => response.request().method() === 'PATCH'
);
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await patchRequest;
expect(
page
.getByTestId('entity-right-panel')
.getByTestId('tags-container')
.getByTestId(`tag-${tag}`)
).not.toBeVisible();
}
};
export const removeTagsFromChildren = async ({
page,
rowId,
tags,
rowSelector = 'data-row-key',
}: {
page: Page;
tags: string[];
rowId: string;
rowSelector?: string;
}) => {
for (const tag of tags) {
await page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('tags-container')
.getByTestId('edit-button')
.click();
await page
.getByTestId('tag-selector')
.getByTestId(`selected-tag-${tag}`)
.getByTestId('remove-tags')
.click();
const patchTagRequest = page.waitForResponse(
(response) => response.request().method() === 'PATCH'
);
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await patchTagRequest;
await expect(
page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('tags-container')
.getByTestId(`tag-${tag}`)
).not.toBeVisible();
}
};
type GlossaryTermOption = {
displayName: string;
name: string;
fullyQualifiedName: string;
};
export const assignGlossaryTerm = async (
page: Page,
glossaryTerm: GlossaryTermOption,
action: 'Add' | 'Edit' = 'Add'
) => {
await page
.getByTestId('entity-right-panel')
.getByTestId('glossary-container')
.getByTestId(action === 'Add' ? 'add-tag' : 'edit-button')
.click();
const searchGlossaryTerm = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*`
);
await page.locator('#tagsForm_tags').fill(glossaryTerm.displayName);
await searchGlossaryTerm;
await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click();
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await expect(
page
.getByTestId('entity-right-panel')
.getByTestId('glossary-container')
.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`)
).toBeVisible();
};
export const assignGlossaryTermToChildren = async ({
page,
glossaryTerm,
action = 'Add',
rowId,
rowSelector = 'data-row-key',
}: {
page: Page;
glossaryTerm: GlossaryTermOption;
rowId: string;
action?: 'Add' | 'Edit';
rowSelector?: string;
}) => {
await page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('glossary-container')
.getByTestId(action === 'Add' ? 'add-tag' : 'edit-button')
.click();
const searchGlossaryTerm = page.waitForResponse(
`/api/v1/search/query?q=*${encodeURIComponent(glossaryTerm.displayName)}*`
);
await page.locator('#tagsForm_tags').fill(glossaryTerm.displayName);
await searchGlossaryTerm;
await page.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`).click();
const patchRequest = page.waitForResponse(
(response) => response.request().method() === 'PATCH'
);
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await patchRequest;
await expect(
page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('glossary-container')
.getByTestId(`tag-${glossaryTerm.fullyQualifiedName}`)
).toBeVisible();
};
export const removeGlossaryTerm = async (
page: Page,
glossaryTerms: GlossaryTermOption[]
) => {
for (const tag of glossaryTerms) {
await page
.getByTestId('entity-right-panel')
.getByTestId('glossary-container')
.getByTestId('edit-button')
.click();
await page
.getByTestId('glossary-container')
.getByTestId(new RegExp(tag.name))
.getByTestId('remove-tags')
.locator('svg')
.click();
const patchRequest = page.waitForResponse(
(response) => response.request().method() === 'PATCH'
);
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await patchRequest;
expect(
page
.getByTestId('entity-right-panel')
.getByTestId('glossary-container')
.getByTestId(`tag-${tag.fullyQualifiedName}`)
).not.toBeVisible();
}
};
export const removeGlossaryTermFromChildren = async ({
page,
glossaryTerms,
rowId,
rowSelector = 'data-row-key',
}: {
page: Page;
glossaryTerms: GlossaryTermOption[];
rowId: string;
rowSelector?: string;
}) => {
for (const tag of glossaryTerms) {
await page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('glossary-container')
.getByTestId('edit-button')
.click();
await page
.getByTestId('glossary-container')
.getByTestId(new RegExp(tag.name))
.getByTestId('remove-tags')
.locator('svg')
.click();
const patchRequest = page.waitForResponse(
(response) => response.request().method() === 'PATCH'
);
await page.waitForSelector(
'.ant-select-dropdown [data-testid="saveAssociatedTag"]',
{ state: 'visible' }
);
await expect(page.getByTestId('saveAssociatedTag')).toBeEnabled();
await page.getByTestId('saveAssociatedTag').click();
await patchRequest;
expect(
page
.locator(`[${rowSelector}="${rowId}"]`)
.getByTestId('glossary-container')
.getByTestId(`tag-${tag.fullyQualifiedName}`)
).not.toBeVisible();
}
};
export const upVote = async (page: Page, endPoint: string) => {
const patchRequest = page.waitForResponse(`/api/v1/${endPoint}/*/vote`);
await page.getByTestId('up-vote-btn').click();
await patchRequest;
await expect(page.getByTestId('up-vote-count')).toContainText('1');
};
export const downVote = async (page: Page, endPoint: string) => {
const patchRequest = page.waitForResponse(`/api/v1/${endPoint}/*/vote`);
await page.getByTestId('down-vote-btn').click();
await patchRequest;
await expect(page.getByTestId('down-vote-count')).toContainText('1');
};
export const followEntity = async (
page: Page,
endpoint: EntityTypeEndpoint
) => {
const followResponse = page.waitForResponse(
`/api/v1/${endpoint}/*/followers`
);
await page.getByTestId('entity-follow-button').click();
await followResponse;
await expect(page.getByTestId('entity-follow-button')).toContainText('1');
};
export const unFollowEntity = async (
page: Page,
endpoint: EntityTypeEndpoint
) => {
const unFollowResponse = page.waitForResponse(
`/api/v1/${endpoint}/*/followers/*`
);
await page.getByTestId('entity-follow-button').click();
await unFollowResponse;
await expect(page.getByTestId('entity-follow-button')).toContainText('0');
};
export const validateFollowedEntityToWidget = async (
page: Page,
entity: string,
isFollowing: boolean
) => {
await redirectToHomePage(page);
if (isFollowing) {
await page.getByTestId('following-widget').isVisible();
await page.getByTestId(`following-${entity}`).isVisible();
} else {
await page.getByTestId('following-widget').isVisible();
await expect(page.getByTestId(`following-${entity}`)).not.toBeVisible();
}
};
const announcementForm = async (
page: Page,
data: {
title: string;
startDate: string;
endDate: string;
description: string;
}
) => {
await page.fill('#title', data.title);
await page.click('#startTime');
await page.fill('#startTime', `${data.startDate}`);
await page.press('#startTime', 'Enter');
await page.click('#endTime');
await page.fill('#endTime', `${data.endDate}`);
await page.press('#startTime', 'Enter');
await page.fill(
'.toastui-editor-md-container > .toastui-editor > .ProseMirror',
data.description
);
await page.locator('#announcement-submit').scrollIntoViewIfNeeded();
const announcementSubmit = page.waitForResponse(
'/api/v1/feed?entityLink=*type=Announcement*'
);
await page.click('#announcement-submit');
await announcementSubmit;
await page.click('.Toastify__close-button');
};
export const createAnnouncement = async (
page: Page,
entityFqn: string,
data: { title: string; description: string }
) => {
await page.getByTestId('manage-button').click();
await page.getByTestId('announcement-button').click();
const startDate = customFormatDateTime(getCurrentMillis(), 'yyyy-MM-dd');
const endDate = customFormatDateTime(
getEpochMillisForFutureDays(5),
'yyyy-MM-dd'
);
await expect(page.getByTestId('announcement-error')).toContainText(
'No Announcements, Click on add announcement to add one.'
);
await page.getByTestId('add-announcement').click();
await expect(page.locator('.ant-modal-header')).toContainText(
'Make an announcement'
);
await announcementForm(page, { ...data, startDate, endDate });
await page.reload();
await page.getByTestId('announcement-card').isVisible();
await expect(page.getByTestId('announcement-card')).toContainText(data.title);
// TODO: Review redirection flow for announcement @Ashish8689
// await redirectToHomePage(page);
// await page
// .getByTestId('announcement-container')
// .getByTestId(`announcement-${entityFqn}`)
// .locator(`[data-testid="entity-link"] span`)
// .first()
// .scrollIntoViewIfNeeded();
// await page
// .getByTestId('announcement-container')
// .getByTestId(`announcement-${entityFqn}`)
// .locator(`[data-testid="entity-link"] span`)
// .first()
// .click();
// await page.getByTestId('announcement-card').isVisible();
// await expect(page.getByTestId('announcement-card')).toContainText(data.title);
};
export const replyAnnouncement = async (page: Page) => {
await page.click('[data-testid="announcement-card"]');
await page.hover(
'[data-testid="announcement-card"] [data-testid="main-message"]'
);
await page.waitForSelector('.ant-popover', { state: 'visible' });
await expect(page.getByTestId('add-reply').locator('svg')).toBeVisible();
await page.getByTestId('add-reply').locator('svg').click();
await expect(page.locator('.ql-editor')).toBeVisible();
const sendButtonIsDisabled = await page
.locator('[data-testid="send-button"]')
.isEnabled();
expect(sendButtonIsDisabled).toBe(false);
await page.fill('[data-testid="editor-wrapper"] .ql-editor', 'Reply message');
await page.click('[data-testid="send-button"]');
await expect(
page.locator('[data-testid="replies"] [data-testid="viewer-container"]')
).toHaveText('Reply message');
await expect(page.locator('[data-testid="show-reply-thread"]')).toHaveText(
'1 replies'
);
// Edit the reply message
await page.hover('[data-testid="replies"] > [data-testid="main-message"]');
await page.waitForSelector('.ant-popover', { state: 'visible' });
await page.click('[data-testid="edit-message"]');
await page.fill(
'[data-testid="editor-wrapper"] .ql-editor',
'Reply message edited'
);
await page.click('[data-testid="save-button"]');
await expect(
page.locator('[data-testid="replies"] [data-testid="viewer-container"]')
).toHaveText('Reply message edited');
await page.reload();
};
export const deleteAnnouncement = async (page: Page) => {
await page.getByTestId('manage-button').click();
await page.getByTestId('announcement-button').click();
await page.hover(
'[data-testid="announcement-card"] [data-testid="main-message"]'
);
await page.waitForSelector('.ant-popover', { state: 'visible' });
await page.click('[data-testid="delete-message"]');
const modalText = await page.textContent('.ant-modal-body');
expect(modalText).toContain(
'Are you sure you want to permanently delete this message?'
);
const getFeed = page.waitForResponse('/api/v1/feed/*');
await page.click('[data-testid="save-button"]');
await getFeed;
};
export const createInactiveAnnouncement = async (
page: Page,
data: { title: string; description: string }
) => {
await page.getByTestId('manage-button').click();
await page.getByTestId('announcement-button').click();
const startDate = customFormatDateTime(
getEpochMillisForFutureDays(6),
'yyyy-MM-dd'
);
const endDate = customFormatDateTime(
getEpochMillisForFutureDays(11),
'yyyy-MM-dd'
);
await page.getByTestId('add-announcement').click();
await expect(page.locator('.ant-modal-header')).toContainText(
'Make an announcement'
);
await announcementForm(page, { ...data, startDate, endDate });
await page.getByTestId('inActive-announcements').isVisible();
await page.reload();
};
export const updateDisplayNameForEntity = async (
page: Page,
displayName: string,
endPoint: string
) => {
await page.click('[data-testid="manage-button"]');
await page.click('[data-testid="rename-button"]');
const nameInputIsDisabled = await page.locator('#name').isEnabled();
expect(nameInputIsDisabled).toBe(false);
await expect(page.locator('#displayName')).toBeVisible();
await page.locator('#displayName').clear();
await page.fill('#displayName', displayName);
const updateNameResponse = page.waitForResponse(`/api/v1/${endPoint}/*`);
await page.click('[data-testid="save-button"]');
await updateNameResponse;
await expect(
page.locator('[data-testid="entity-header-display-name"]')
).toHaveText(displayName);
};
export const checkForEditActions = async ({ entityType, deleted, page }) => {
for (const {
containerSelector,
elementSelector,
} of LIST_OF_FIELDS_TO_EDIT_TO_BE_DISABLED) {
if (
elementSelector === '[data-testid="entity-follow-button"]' &&
ENTITIES_WITHOUT_FOLLOWING_BUTTON.includes(entityType)
) {
continue;
}
if (entityType.startsWith('services/')) {
continue;
}
const isDisabled = await page
.locator(`${containerSelector} ${elementSelector}`)
.isEnabled();
expect(isDisabled).toBe(!deleted);
}
for (const {
containerSelector,
elementSelector,
} of LIST_OF_FIELDS_TO_EDIT_NOT_TO_BE_PRESENT) {
if (!deleted) {
await expect(
page.locator(`${containerSelector} ${elementSelector}`)
).toBeVisible();
} else {
const exists = await page
.locator(`${containerSelector} ${elementSelector}`)
.isVisible();
expect(exists).toBe(false);
}
}
};
export const checkLineageTabActions = async (page: Page, deleted?: boolean) => {
// Click the lineage tab
const lineageApi = page.waitForResponse('/api/v1/lineage/getLineage?fqn=*');
await page.click('[data-testid="lineage"]');
// Ensure the response has been received and check the status code
await lineageApi;
// Check the presence or absence of the edit-lineage element based on the deleted flag
if (deleted) {
await expect(
page.locator('[data-testid="edit-lineage"]')
).not.toBeVisible();
} else {
await expect(page.locator('[data-testid="edit-lineage"]')).toBeVisible();
}
};
export const checkForTableSpecificFields = async (
page: Page,
deleted?: boolean
) => {
const queryDataUrl = `/api/v1/search/query?q=*index=query_search_index*`;
const queryApi = page.waitForResponse(queryDataUrl);
// Click the table queries tab
await page.click('[data-testid="table_queries"]');
if (!deleted) {
await queryApi;
// Check if the add-query button is enabled
const addQueryButton = page.locator('[data-testid="add-query-btn"]');
await expect(addQueryButton).toBeEnabled();
} else {
// Check for the no data placeholder message
const noDataPlaceholder = page
.getByTestId('no-queries')
.getByTestId('no-data-placeholder');
await expect(noDataPlaceholder).toContainText(
'Queries data is not available for deleted entities.'
);
}
// Click the profiler tab
await page.click('[data-testid="profiler"]');
// Check the visibility of profiler buttons based on the deleted flag
const addTableTestButton = page.locator(
'[data-testid="profiler-add-table-test-btn"]'
);
const settingButton = page.locator('[data-testid="profiler-setting-btn"]');
if (!deleted) {
await expect(addTableTestButton).toBeVisible();
await expect(settingButton).toBeVisible();
} else {
await expect(addTableTestButton).not.toBeVisible();
await expect(settingButton).not.toBeVisible();
}
};
export const deletedEntityCommonChecks = async ({
page,
endPoint,
deleted,
}: {
page: Page;
endPoint: EntityTypeEndpoint;
deleted?: boolean;
}) => {
const isTableEntity = endPoint === EntityTypeEndpoint.Table;
// Go to first tab before starts validating
await page.click('.ant-tabs-tab:nth-child(1)');
// Check if all the edit actions are available for the entity
await checkForEditActions({
page,
entityType: endPoint,
deleted,
});
if (isTableEntity) {
await checkLineageTabActions(page, deleted);
}
if (isTableEntity) {
await checkForTableSpecificFields(page, deleted);
}
await page.click('[data-testid="manage-button"]');
if (deleted) {
// only two menu options (restore and delete) should be present
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="announcement-button"]'
)
).toBeHidden();
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="rename-button"]'
)
).toBeHidden();
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="profiler-setting-button"]'
)
).toBeHidden();
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="restore-button"]'
)
).toBeVisible();
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="delete-button"]'
)
).toBeVisible();
} else {
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="announcement-button"]'
)
).toBeVisible();
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="rename-button"]'
)
).toBeVisible();
if (
[EntityTypeEndpoint.Database, EntityTypeEndpoint.DatabaseSchema].includes(
endPoint
)
) {
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="profiler-setting-button"]'
)
).toBeVisible();
}
await expect(
page.locator(
'[data-testid="manage-dropdown-list-container"] [data-testid="delete-button"]'
)
).toBeVisible();
}
await page.click('body');
};
export const restoreEntity = async (page: Page) => {
await expect(page.locator('[data-testid="deleted-badge"]')).toBeVisible();
await page.click('[data-testid="manage-button"]');
await page.click('[data-testid="restore-button"]');
await page.click('button:has-text("Restore")');
await expect(page.locator('.Toastify__toast-body')).toHaveText(
/restored successfully/
);
await page.click('.Toastify__close-button');
const exists = await page
.locator('[data-testid="deleted-badge"]')
.isVisible();
expect(exists).toBe(false);
};
export const softDeleteEntity = async (
page: Page,
entityName: string,
endPoint: EntityTypeEndpoint,
displayName: string
) => {
await deletedEntityCommonChecks({
page,
endPoint,
deleted: false,
});
await page.click('body'); // Equivalent to clicking outside
await page.click('[data-testid="manage-button"]');
await page.click('[data-testid="delete-button"]');
await page.waitForSelector('[role="dialog"].ant-modal');
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
await expect(page.locator('.ant-modal-title')).toContainText(displayName);
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
const deleteResponse = page.waitForResponse(
`/api/v1/${endPoint}/*?hardDelete=false&recursive=true`
);
await page.click('[data-testid="confirm-button"]');
await deleteResponse;
await expect(page.locator('.Toastify__toast-body')).toHaveText(
/deleted successfully!/
);
await page.click('.Toastify__close-button');
await page.reload();
const deletedBadge = await page.locator('[data-testid="deleted-badge"]');
await expect(deletedBadge).toHaveText('Deleted');
await deletedEntityCommonChecks({
page,
endPoint,
deleted: true,
});
await page.click('body'); // Equivalent to clicking outside
if (endPoint === EntityTypeEndpoint.Table) {
await page.click('[data-testid="breadcrumb-link"]:last-child');
const deletedTableResponse = page.waitForResponse(
'/api/v1/tables?databaseSchema=*'
);
await page.click('[data-testid="show-deleted"]');
await deletedTableResponse;
const tableCount = await page.locator(
'[data-testid="table"] [data-testid="count"]'
);
await expect(tableCount).toContainText('1');
await page.click(`[data-testid="${entityName}"]`);
}
await restoreEntity(page);
await page.reload();
await deletedEntityCommonChecks({
page,
endPoint,
deleted: false,
});
};
export const hardDeleteEntity = async (
page: Page,
entityName: string,
endPoint: EntityTypeEndpoint
) => {
await page.click('[data-testid="manage-button"]');
await page.click('[data-testid="delete-button"]');
await expect(page.locator('[role="dialog"].ant-modal')).toBeVisible();
await expect(
page.locator('[data-testid="delete-modal"] .ant-modal-title')
).toHaveText(new RegExp(entityName));
await page.click('[data-testid="hard-delete-option"]');
await page.check('[data-testid="hard-delete"]');
await page.fill('[data-testid="confirmation-text-input"]', 'DELETE');
const deleteResponse = page.waitForResponse(
`/api/v1/${endPoint}/*?hardDelete=true&recursive=true`
);
await page.click('[data-testid="confirm-button"]');
await deleteResponse;
await expect(page.locator('.Toastify__toast-body')).toHaveText(
/deleted successfully!/
);
await page.click('.Toastify__close-button');
};
export const checkDataAssetWidget = async (
page: Page,
type: string,
index: string,
serviceType: string
) => {
const quickFilterResponse = page.waitForResponse(
`/api/v1/search/query?q=&index=dataAsset*${serviceType}*`
);
await page
.locator(`[data-testid="data-asset-service-${serviceType}"]`)
.click();
await quickFilterResponse;
await expect(
page.locator('[data-testid="search-dropdown-Service Type"]')
).toContainText(serviceType);
await expect(
page
.getByTestId('explore-tree')
.locator('span')
.filter({ hasText: serviceType })
.first()
).toHaveClass(/ant-tree-node-selected/);
};
export const escapeESReservedCharacters = (text?: string) => {
const reUnescapedHtml = /[\\[\]#+=&|><!(){}^"~*?:/-]/g;
const reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
const getReplacedChar = (char: string) => {
return ES_RESERVED_CHARACTERS[char] ?? char;
};
return text && reHasUnescapedHtml.test(text)
? text.replace(reUnescapedHtml, getReplacedChar)
: text ?? '';
};