PW: migrate query entity spec to playwright (#17619)

* PW: migrate query entity spec to playwright

* migrated to playwright

* minor test fix

* fixed api waiting issue

* fixed the api issue

* fixed API response await

* fixed await issue
This commit is contained in:
Shailesh Parmar 2024-08-31 13:18:25 +05:30 committed by GitHub
parent 183da3f2b1
commit 3a5539eb1c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 330 additions and 354 deletions

View File

@ -1,354 +0,0 @@
/*
* Copyright 2023 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 {
descriptionBox,
interceptURL,
verifyResponseStatusCode,
} from '../../common/common';
import {
createEntityTable,
createQueryByTableName,
generateRandomTable,
hardDeleteService,
} from '../../common/EntityUtils';
import { visitEntityDetailsPage } from '../../common/Utils/Entity';
import { getToken } from '../../common/Utils/LocalStorage';
import { generateRandomUser } from '../../common/Utils/Owner';
import { EntityType } from '../../constants/Entity.interface';
import {
DATABASE_SERVICE,
DATABASE_SERVICE_DETAILS,
} from '../../constants/EntityConstant';
import { SERVICE_CATEGORIES } from '../../constants/service.constants';
const queryTable = {
term: DATABASE_SERVICE.entity.name,
displayName: DATABASE_SERVICE.entity.name,
entity: EntityType.Table,
serviceName: DATABASE_SERVICE.service.name,
entityType: 'Table',
};
const table1 = generateRandomTable();
const table2 = generateRandomTable();
const user1 = generateRandomUser();
const user2 = generateRandomUser();
const owner = `${user2.firstName}${user2.lastName}`;
const userIds: string[] = [];
const DATA = {
...queryTable,
query: `select * from table ${queryTable.term}`,
description: 'select all the field from table',
owner: 'Aaron Johnson',
tag: 'Personal',
queryUsedIn: {
table1: table1.name,
table2: table2.name,
},
};
const queryFilters = ({
key,
filter,
apiKey,
}: {
key: string;
filter: string;
apiKey: string;
}) => {
cy.get(`[data-testid="search-dropdown-${key}"]`).click();
cy.get('[data-testid="search-input"]').type(filter);
verifyResponseStatusCode(apiKey, 200);
cy.get(`[data-testid="search-dropdown-${key}"]`).trigger('mouseout');
cy.get(`[data-testid="drop-down-menu"] [title="${filter}"]`).click();
cy.get('[data-testid="update-btn"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
};
describe('Query Entity', { tags: 'DataAssets' }, () => {
before(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
createEntityTable({
token,
...DATABASE_SERVICE,
tables: [DATABASE_SERVICE.entity, table1, table2],
});
// get Table by name and create query in the table
createQueryByTableName(token, table1);
// Create a new user
cy.request({
method: 'POST',
url: `/api/v1/users/signup`,
headers: { Authorization: `Bearer ${token}` },
body: user1,
}).then((response) => {
userIds.push(response.body.id);
});
cy.request({
method: 'POST',
url: `/api/v1/users/signup`,
headers: { Authorization: `Bearer ${token}` },
body: user2,
}).then((response) => {
userIds.push(response.body.id);
});
});
});
after(() => {
cy.login();
cy.getAllLocalStorage().then((data) => {
const token = getToken(data);
hardDeleteService({
token,
serviceFqn: DATABASE_SERVICE.service.name,
serviceType: SERVICE_CATEGORIES.DATABASE_SERVICES,
});
// Delete created user
userIds.forEach((userId) => {
cy.request({
method: 'DELETE',
url: `/api/v1/users/${userId}?hardDelete=true&recursive=false`,
headers: { Authorization: `Bearer ${token}` },
});
});
});
});
beforeEach(() => {
cy.login();
interceptURL(
'GET',
'/api/v1/search/query?q=*&index=query_search_index*',
'fetchQuery'
);
});
it('Create query', () => {
interceptURL(
'GET',
'/api/v1/search/query?q=*&from=0&size=15&index=table_search_index',
'explorePageSearch'
);
interceptURL('POST', '/api/v1/queries', 'createQuery');
visitEntityDetailsPage({
term: DATA.term,
serviceName: DATA.serviceName,
entity: DATA.entity,
});
cy.get('[data-testid="table_queries"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
cy.get('[data-testid="add-query-btn"]').click();
cy.get('[data-testid="code-mirror-container"]').type(DATA.query);
cy.get(descriptionBox).scrollIntoView().type(DATA.description);
cy.get('[data-testid="query-used-in"]').type(DATA.queryUsedIn.table1);
verifyResponseStatusCode('@explorePageSearch', 200);
cy.get(`[title="${DATA.queryUsedIn.table1}"]`).click();
cy.clickOutside();
cy.get('[data-testid="save-btn"]').click();
verifyResponseStatusCode('@createQuery', 201);
cy.get('[data-testid="query-card"]').should('have.length.above', 0);
cy.get('[data-testid="query-card"]')
.contains(DATA.query)
.scrollIntoView()
.should('be.visible');
});
it('Update owner, description and tag', () => {
interceptURL('GET', '/api/v1/users?*', 'getUsers');
interceptURL('PATCH', '/api/v1/queries/*', 'patchQuery');
interceptURL(
'GET',
'/api/v1/search/query?q=*&from=0&size=15&index=table_search_index',
'explorePageSearch'
);
visitEntityDetailsPage({
term: DATA.term,
serviceName: DATA.serviceName,
entity: DATA.entity,
});
cy.get('[data-testid="table_queries"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
cy.get('[data-testid="query-card"]').should('have.length.above', 0);
// Update owner
cy.get(':nth-child(2) > [data-testid="edit-owner"]').click();
verifyResponseStatusCode('@getUsers', 200);
cy.get('[data-testid="loader"]').should('not.exist');
interceptURL('GET', `api/v1/search/query?q=*`, 'searchOwner');
cy.get('[data-testid="owner-select-users-search-bar"]').type(owner);
verifyResponseStatusCode('@searchOwner', 200);
cy.get(`.ant-popover [title="${owner}"]`).click();
cy.get('[data-testid="selectable-list-update-btn"]').click();
verifyResponseStatusCode('@patchQuery', 200);
cy.get('[data-testid="owner-link"]').should('contain', owner);
// Update Description
cy.get('[data-testid="edit-description"]').filter(':visible').click();
cy.get(descriptionBox).clear().type('updated description');
cy.get('[data-testid="save"]').click();
verifyResponseStatusCode('@patchQuery', 200);
// Update Tags
cy.get('[data-testid="entity-tags"] .ant-tag').filter(':visible').click();
cy.get('[data-testid="tag-selector"]').type(DATA.tag);
cy.get('[data-testid="tag-PersonalData.Personal"]').click();
cy.get('[data-testid="saveAssociatedTag"]').scrollIntoView().click();
verifyResponseStatusCode('@patchQuery', 200);
});
it('Verify query filter', () => {
visitEntityDetailsPage({
term: DATA.term,
serviceName: DATA.serviceName,
entity: DATA.entity,
});
cy.get('[data-testid="table_queries"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
const userName = `${user1.firstName}${user1.lastName}`;
interceptURL(
'GET',
`/api/v1/search/query?*${encodeURI(
userName
)}*index=user_search_index,team_search_index*`,
'searchUserName'
);
queryFilters({
filter: `${user1.firstName}${user1.lastName}`,
apiKey: '@searchUserName',
key: 'Owner',
});
interceptURL(
'GET',
`/api/v1/search/query?*${encodeURI(
owner
)}*index=user_search_index,team_search_index*`,
'searchOwner'
);
cy.get('[data-testid="no-data-placeholder"]').should('be.visible');
queryFilters({
filter: owner,
apiKey: '@searchOwner',
key: 'Owner',
});
interceptURL(
'GET',
'/api/v1/search/query?*None*index=tag_search_index*',
'noneTagSearch'
);
cy.get('[data-testid="query-card"]').should('have.length.above', 0);
queryFilters({
filter: 'None',
apiKey: '@noneTagSearch',
key: 'Tag',
});
interceptURL(
'GET',
`/api/v1/search/query?*${DATA.tag}*index=tag_search_index*`,
'personalTagSearch'
);
cy.get('[data-testid="no-data-placeholder"]').should('be.visible');
queryFilters({
filter: DATA.tag,
apiKey: '@personalTagSearch',
key: 'Tag',
});
cy.get('[data-testid="query-card"]').should('have.length.above', 0);
});
it('Update query and QueryUsedIn', () => {
interceptURL('GET', '/api/v1/users?&isBot=false&limit=15', 'getUsers');
interceptURL('PATCH', '/api/v1/queries/*', 'patchQuery');
interceptURL(
'GET',
'/api/v1/search/query?q=*&from=0&size=15&index=table_search_index',
'explorePageSearch'
);
visitEntityDetailsPage({
term: DATA.term,
serviceName: DATA.serviceName,
entity: DATA.entity,
});
cy.get('[data-testid="table_queries"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
cy.get('[data-testid="query-btn"]').click();
cy.get('[data-menu-id*="edit-query"]').click();
cy.get('.CodeMirror-line')
.click()
.type(`{selectAll}{selectAll}${DATA.queryUsedIn.table1}`);
cy.get('[data-testid="edit-query-used-in"]').click();
cy.wait('@explorePageSearch');
cy.get('[data-testid="edit-query-used-in"]').type(DATA.queryUsedIn.table2);
verifyResponseStatusCode('@explorePageSearch', 200);
cy.get(`[title="${DATA.queryUsedIn.table2}"]`).click();
cy.clickOutside();
cy.get('[data-testid="save-query-btn"]').click();
verifyResponseStatusCode('@patchQuery', 200);
});
it('Visit full screen view of query', () => {
interceptURL('GET', '/api/v1/queries?*', 'fetchQuery');
interceptURL('GET', '/api/v1/users?&isBot=false&limit=15', 'getUsers');
interceptURL('GET', '/api/v1/queries/*', 'getQueryById');
interceptURL(
'GET',
'/api/v1/search/query?q=*&from=0&size=15&index=table_search_index',
'explorePageSearch'
);
visitEntityDetailsPage({
term: DATA.term,
serviceName: DATA.serviceName,
entity: DATA.entity,
});
cy.get('[data-testid="table_queries"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
cy.get('[data-testid="query-entity-expand-button"]').click();
verifyResponseStatusCode('@getQueryById', 200);
cy.get('[data-testid="query-btn"]').click();
cy.get('.ant-dropdown').should('be.visible');
cy.get('[data-menu-id*="delete-query"]').click();
cy.get('[data-testid="save-button"]').click();
});
it('Verify query duration', () => {
visitEntityDetailsPage({
term: table1.name,
serviceName: DATABASE_SERVICE_DETAILS.name,
entity: DATA.entity,
});
cy.get('[data-testid="table_queries"]').click();
verifyResponseStatusCode('@fetchQuery', 200);
// Validate that the duration is in sec or not
cy.get('[data-testid="query-run-duration"]')
.should('be.visible')
.should('contain', '6.199 sec');
});
});

View File

@ -0,0 +1,267 @@
/*
* 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 { TableClass } from '../../support/entity/TableClass';
import { UserClass } from '../../support/user/UserClass';
import {
clickOutside,
createNewPage,
descriptionBox,
redirectToHomePage,
} from '../../utils/common';
import { createQueryByTableName, queryFilters } from '../../utils/query';
// use the admin user to login
test.use({ storageState: 'playwright/.auth/admin.json' });
const table1 = new TableClass();
const table2 = new TableClass();
const table3 = new TableClass();
const user1 = new UserClass();
const user2 = new UserClass();
const entityData = [table1, table2, table3, user1, user2];
const queryData = {
query: `select * from table ${table1.entity.name}`,
description: 'select all the field from table',
owner: user1.getUserName(),
tagFqn: 'PersonalData.Personal',
tagName: 'Personal',
queryUsedIn: {
table1: table2.entity.name,
table2: table3.entity.name,
},
};
test.beforeAll(async ({ browser }) => {
const { afterAction, apiContext } = await createNewPage(browser);
for (const entity of entityData) {
await entity.create(apiContext);
}
await createQueryByTableName({
apiContext,
tableResponseData: table2.entityResponseData,
});
await afterAction();
});
test('Query Entity', async ({ page }) => {
test.slow(true);
await redirectToHomePage(page);
await table1.visitEntityPage(page);
await test.step('Create a new query entity', async () => {
const queryResponse = page.waitForResponse(
'/api/v1/search/query?q=*&index=query_search_index*'
);
await page.click(`[data-testid="table_queries"]`);
await queryResponse;
await page.click(`[data-testid="add-query-btn"]`);
await page
.getByTestId('code-mirror-container')
.getByRole('textbox')
.fill(queryData.query);
await page.click(descriptionBox);
await page.keyboard.type(queryData.description);
await page
.getByTestId('query-used-in')
.locator('div')
.filter({ hasText: 'Please Select a Query Used In' })
.click();
await page.keyboard.type(queryData.queryUsedIn.table1);
await page.click(`[title="${queryData.queryUsedIn.table1}"]`);
await clickOutside(page);
const createQueryResponse = page.waitForResponse('/api/v1/queries');
await page.click('[data-testid="save-btn"]');
await createQueryResponse;
await page.waitForURL('**/table_queries**');
await expect(page.locator(`text=${queryData.query}`)).toBeVisible();
});
await test.step('Update owner, description and tag', async () => {
const ownerListResponse = page.waitForResponse('/api/v1/users?*');
await page
.getByTestId(
'entity-summary-resizable-right-panel-container entity-resizable-panel-container'
)
.getByTestId('edit-owner')
.click();
await ownerListResponse;
await page.waitForSelector('[data-testid="loader"]', {
state: 'detached',
});
const searchOwnerResponse = page.waitForResponse('api/v1/search/query?q=*');
await page.fill(
'[data-testid="owner-select-users-search-bar"]',
queryData.owner
);
await searchOwnerResponse;
await page.click(`.ant-popover [title="${queryData.owner}"]`);
const updateOwnerResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/queries/') &&
response.request().method() === 'PATCH'
);
await page.click('[data-testid="selectable-list-update-btn"]');
await updateOwnerResponse;
await expect(page.getByRole('link', { name: 'admin' })).toBeVisible();
await expect(
page.getByRole('link', { name: queryData.owner })
).toBeVisible();
// Update Description
await page.click(`[data-testid="edit-description"]`);
await page.fill(descriptionBox, 'updated description');
const updateDescriptionResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/queries/') &&
response.request().method() === 'PATCH'
);
await page.click(`[data-testid="save"]`);
await updateDescriptionResponse;
await page.waitForSelector('.ant-modal-body', {
state: 'detached',
});
// Update Tags
await page.getByTestId('add-tag').click();
await page.locator('#tagsForm_tags').click();
await page.locator('#tagsForm_tags').fill(queryData.tagFqn);
await page.getByTestId(`tag-${queryData.tagFqn}`).click();
const updateTagResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/queries/') &&
response.request().method() === 'PATCH'
);
await page.getByTestId('saveAssociatedTag').click();
await updateTagResponse;
});
await test.step('Update query and QueryUsedIn', async () => {
await page.click('[data-testid="query-btn"]');
await page.click(`[data-menu-id*="edit-query"]`);
await page.click('.CodeMirror-line', { clickCount: 3 });
await page.keyboard.press('Backspace');
await page.keyboard.type(`${queryData.queryUsedIn.table1}`);
await page.click('[data-testid="edit-query-used-in"]');
const tableSearchResponse = page.waitForResponse(
'/api/v1/search/query?q=*&index=table_search_index'
);
await page.keyboard.type(queryData.queryUsedIn.table2);
await tableSearchResponse;
await page.click(`[title="${queryData.queryUsedIn.table2}"]`);
await clickOutside(page);
const updateQueryResponse = page.waitForResponse(
(response) =>
response.url().includes('/api/v1/queries/') &&
response.request().method() === 'PATCH'
);
await page.click('[data-testid="save-query-btn"]');
await updateQueryResponse;
});
await test.step('Verify query filter', async () => {
const userName = user2.getUserName();
await queryFilters({
filter: userName,
apiKey: `/api/v1/search/query?*${encodeURI(
userName
)}*index=user_search_index,team_search_index*`,
key: 'Owner',
page,
});
await expect(
page.locator('[data-testid="no-data-placeholder"]')
).toBeVisible();
await queryFilters({
filter: queryData.owner,
apiKey: `/api/v1/search/query?*${encodeURI(
queryData.owner
)}*index=user_search_index,team_search_index*`,
key: 'Owner',
page,
});
const queryCards = await page.$$('[data-testid="query-card"]');
expect(queryCards.length).toBeGreaterThan(0);
await queryFilters({
filter: 'None',
apiKey: '/api/v1/search/query?*None*index=tag_search_index*',
key: 'Tag',
page,
});
await expect(
page.locator('[data-testid="no-data-placeholder"]')
).toBeVisible();
await queryFilters({
filter: queryData.tagName,
apiKey: `/api/v1/search/query?*${queryData.tagName}*index=tag_search_index*`,
key: 'Tag',
page,
});
const updatedQueryCards = await page.$$('[data-testid="query-card"]');
expect(updatedQueryCards.length).toBeGreaterThan(0);
});
await test.step('Visit full screen view of query and Delete', async () => {
const queryResponse = page.waitForResponse('/api/v1/queries/*');
await page.click(`[data-testid="query-entity-expand-button"]`);
await queryResponse;
await page.click(`[data-testid="query-btn"]`);
await page.waitForSelector('.ant-dropdown', { state: 'visible' });
await page.click(`[data-menu-id*="delete-query"]`);
await page.click(`[data-testid="save-button"]`);
await page.waitForResponse('/api/v1/queries/*');
});
});
test('Verify query duration', async ({ page }) => {
await redirectToHomePage(page);
await table2.visitEntityPage(page);
const queryResponse = page.waitForResponse(
'/api/v1/search/query?q=*&index=query_search_index*'
);
await page.click(`[data-testid="table_queries"]`);
await queryResponse;
await page.waitForSelector('[data-testid="query-run-duration"]', {
state: 'visible',
});
const durationText = await page.textContent(
'[data-testid="query-run-duration"]'
);
expect(durationText).toContain('6.199 sec');
});
test.afterAll(async ({ browser }) => {
const { afterAction, apiContext } = await createNewPage(browser);
for (const entity of entityData) {
await entity.delete(apiContext);
}
await afterAction();
});

View File

@ -0,0 +1,63 @@
/*
* 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 { APIRequestContext, Page } from '@playwright/test';
export const createQueryByTableName = async (data: {
apiContext: APIRequestContext;
tableResponseData: unknown;
}) => {
const { apiContext, tableResponseData } = data;
const queryResponse = await apiContext
.post('/api/v1/queries', {
data: {
query: `SELECT * FROM SALES-${tableResponseData?.['name']}`,
description: 'this is query description',
queryUsedIn: [
{
id: tableResponseData?.['id'],
type: 'table',
},
],
duration: 6199,
queryDate: 1700225667191,
service: tableResponseData?.['service']?.['name'],
},
})
.then((response) => response.json());
return await queryResponse;
};
export const queryFilters = async ({
key,
filter,
apiKey,
page,
}: {
key: string;
filter: string;
apiKey: string;
page: Page;
}) => {
await page.click(`[data-testid="search-dropdown-${key}"]`);
const searchInputResponse = page.waitForResponse(apiKey);
await page.fill('[data-testid="search-input"]', filter);
await searchInputResponse;
await page.hover(`[data-testid="search-dropdown-${key}"]`);
await page.click(`[data-testid="drop-down-menu"] [title="${filter}"]`);
const queryResponse = page.waitForResponse(
'/api/v1/search/query?q=*&index=query_search_index*'
);
await page.click('[data-testid="update-btn"]');
await queryResponse;
};