migrated cyprss Bots spec to playwright (#17673)

This commit is contained in:
Ashish Gupta 2024-09-02 23:50:54 +05:30 committed by GitHub
parent 00fe8399ad
commit d952c1bd1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 418 additions and 234 deletions

View File

@ -1,232 +0,0 @@
/*
* Copyright 2022 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 {
customFormatDateTime,
getEpochMillisForFutureDays,
} from '../../../src/utils/date-time/DateTimeUtils';
import {
descriptionBox,
interceptURL,
uuid,
verifyResponseStatusCode,
} from '../../common/common';
import { DELETE_TERM } from '../../constants/constants';
import { GlobalSettingOptions } from '../../constants/settings.constant';
const botName = `bot-ct-test-${uuid()}`;
const botEmail = `${botName}@mail.com`;
const description = 'This is bot description';
const updatedDescription = 'This is updated bot description';
const updatedBotName = `updated-${botName}`;
const unlimitedExpiryTime = 'This token has no expiration date.';
const JWTToken = 'OpenMetadata JWT';
const expirationTime = {
oneday: '1',
sevendays: '7',
onemonth: '30',
twomonths: '60',
threemonths: '90',
};
const getCreatedBot = () => {
interceptURL('GET', `/api/v1/bots/name/${botName}*`, 'getCreatedBot');
// Click on created Bot name
cy.get(`[data-testid="bot-link-${botName}"]`).should('exist').click();
verifyResponseStatusCode('@getCreatedBot', 200);
};
const revokeToken = () => {
// Click on revoke button
cy.get('[data-testid="revoke-button"]').click();
// Verify the revoke text
cy.get('[data-testid="body-text"]').should(
'contain',
'Are you sure you want to revoke access for JWT Token?'
);
interceptURL('PUT', `/api/v1/users/revokeToken`, 'revokeToken');
// Click on confirm button
cy.get('[data-testid="save-button"]').click();
verifyResponseStatusCode('@revokeToken', 200);
// Verify the revoke is successful
cy.get('[data-testid="revoke-button"]').should('not.exist');
cy.get('[data-testid="auth-mechanism"]')
.should('be.visible')
.invoke('text')
.should('eq', 'OpenMetadata JWT');
cy.get('[data-testid="token-expiry"]').should('exist').should('be.visible');
cy.get('[data-testid="save-edit"]').should('exist').should('be.visible');
};
describe('Bots Page should work properly', { tags: 'Settings' }, () => {
beforeEach(() => {
cy.login();
interceptURL(
'GET',
'api/v1/bots?limit=*&include=non-deleted',
'getBotsList'
);
cy.settingClick(GlobalSettingOptions.BOTS);
verifyResponseStatusCode('@getBotsList', 200);
});
it('Verify ingestion bot delete button is always disabled', () => {
cy.get('[data-testid="bot-delete-ingestion-bot"]')
.should('exist')
.should('be.disabled');
});
it('Create new Bot', () => {
cy.get('[data-testid="add-bot"]')
.should('exist')
.should('be.visible')
.as('addBotButton');
cy.wait(500);
// Click on add bot button
cy.get('@addBotButton').click();
// Enter email
cy.get('[data-testid="email"]').should('exist').type(botEmail);
// Enter display name
cy.get('[data-testid="displayName"]').should('exist').type(botName);
// Select expiry time
cy.get('[data-testid="token-expiry"]').should('be.visible').click();
cy.contains('1 hr').should('exist').should('be.visible').click();
// Enter description
cy.get(descriptionBox).type(description);
// Click on save button
cy.wait(1000);
interceptURL('post', '/api/v1/bots', 'createBot');
cy.get('[data-testid="save-user"]')
.scrollIntoView()
.should('be.visible')
.click();
verifyResponseStatusCode('@createBot', 201);
verifyResponseStatusCode('@getBotsList', 200);
// Verify bot is getting added in the bots listing page
cy.get('table').should('contain', botName).and('contain', description);
getCreatedBot();
cy.get('[data-testid="revoke-button"]')
.should('be.visible')
.should('contain', 'Revoke token');
cy.get('[data-testid="center-panel"]')
.should('be.visible')
.should('contain', `${JWTToken} Token`);
// Verify expiration time
cy.get('[data-testid="token-expiry"]').should('be.visible');
});
Object.values(expirationTime).forEach((expiry) => {
it(`Update token expiration for ${expiry} days`, () => {
getCreatedBot();
revokeToken();
// Click on dropdown
cy.get('[data-testid="token-expiry"]').click();
// Select the expiration period
cy.contains(`${expiry} days`).should('exist').click();
// Save the updated date
const expiryDate = customFormatDateTime(
getEpochMillisForFutureDays(expiry),
`ccc d'th' MMMM, yyyy`
);
cy.get('[data-testid="save-edit"]').should('be.visible').click();
cy.get('[data-testid="center-panel"]')
.find('[data-testid="revoke-button"]')
.should('be.visible');
// Verify the expiry time
cy.get('[data-testid="token-expiry"]')
.should('be.visible')
.invoke('text')
.should('contain', `Expires on ${expiryDate}`);
});
});
it('Update token expiration for unlimited days', () => {
getCreatedBot();
revokeToken();
// Click on expiry token dropdown
cy.get('[data-testid="token-expiry"]').click();
// Select unlimited days
cy.contains('Unlimited days').click();
// Save the selected changes
cy.get('[data-testid="save-edit"]').click();
// Verify the updated expiry time
cy.get('[data-testid="center-panel"]')
.find('[data-testid="revoke-button"]')
.should('be.visible');
// Verify the expiry time
cy.get('[data-testid="token-expiry"]')
.should('be.visible')
.invoke('text')
.should('contain', `${unlimitedExpiryTime}`);
});
it('Update display name and description', () => {
getCreatedBot();
// Click on edit display name
cy.get('[data-testid="edit-displayName"]')
.should('exist')
.should('be.visible')
.click();
// Enter new display name
cy.get('[data-testid="displayName"]')
.should('be.visible')
.clear()
.type(updatedBotName);
// Save the updated display name
cy.get('[data-testid="save-displayName"]').should('be.visible').click();
// Verify the display name is updated on bot details page
cy.get('[data-testid="left-panel"]').should('contain', updatedBotName);
cy.wait(1000);
// Click on edit description button
cy.get('[data-testid="edit-description"]').should('be.visible').click();
// Enter updated description and save
cy.get(descriptionBox).clear().type(updatedDescription);
cy.get('[data-testid="save"]').click();
interceptURL('GET', '/api/v1/bots*', 'getBotsPage');
cy.get('[data-testid="breadcrumb-link"]').first().click();
verifyResponseStatusCode('@getBotsPage', 200);
// Verify the updated name is displayed in the Bots listing page
cy.get(`[data-testid="bot-link-${updatedBotName}"]`).should(
'contain',
updatedBotName
);
cy.get('[data-testid="markdown-parser"]').should(
'contain',
updatedDescription
);
});
it('Delete created bot', () => {
// Click on delete button
cy.get(`[data-testid="bot-delete-${botName}"]`)
.should('be.visible')
.click();
// Select permanent delete
cy.get('[data-testid="hard-delete-option"]').should('be.visible').click();
// Enter confirmation text
cy.get('[data-testid="confirmation-text-input"]')
.should('be.visible')
.type(DELETE_TERM);
interceptURL('DELETE', '/api/v1/bots/*', 'deleteBot');
cy.get('[data-testid="confirm-button"]').should('be.visible').click();
verifyResponseStatusCode('@deleteBot', 200);
cy.get('[data-testid="page-layout-v1"]').should('not.contain', botName);
});
});

View File

@ -0,0 +1,92 @@
/*
* 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, test as base } from '@playwright/test';
import { GlobalSettingOptions } from '../../constant/settings';
import { BotClass } from '../../support/bot/BotClass';
import { UserClass } from '../../support/user/UserClass';
import { performAdminLogin } from '../../utils/admin';
import {
createBot,
deleteBot,
getCreatedBot,
tokenExpirationForDays,
tokenExpirationUnlimitedDays,
updateBotDetails,
} from '../../utils/bot';
import { redirectToHomePage } from '../../utils/common';
import { settingClick } from '../../utils/sidebar';
const adminUser = new UserClass();
const bot = new BotClass();
const test = base.extend<{ adminPage: Page }>({
adminPage: async ({ browser }, use) => {
const adminPage = await browser.newPage();
await adminUser.login(adminPage);
await use(adminPage);
await adminPage.close();
},
});
test.describe('Bots Page should work properly', () => {
test.beforeAll('Setup pre-requests', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await adminUser.create(apiContext);
await adminUser.setAdminRole(apiContext);
await bot.create(apiContext);
await afterAction();
});
test.afterAll('Cleanup', async ({ browser }) => {
const { apiContext, afterAction } = await performAdminLogin(browser);
await adminUser.delete(apiContext);
await bot.delete(apiContext);
await afterAction();
});
test('Verify ingestion bot delete button is always disabled', async ({
adminPage,
}) => {
await redirectToHomePage(adminPage);
await settingClick(adminPage, GlobalSettingOptions.BOTS);
await expect(
adminPage.getByTestId('bot-delete-ingestion-bot')
).toBeDisabled();
});
test('Create and Delete Bot', async ({ adminPage }) => {
await redirectToHomePage(adminPage);
await settingClick(adminPage, GlobalSettingOptions.BOTS);
await createBot(adminPage);
await deleteBot(adminPage);
});
test('Update display name and description', async ({ adminPage }) => {
await redirectToHomePage(adminPage);
await settingClick(adminPage, GlobalSettingOptions.BOTS);
await updateBotDetails(adminPage, bot.responseData);
});
test('Update token expiration', async ({ adminPage }) => {
test.slow(true);
await redirectToHomePage(adminPage);
await settingClick(adminPage, GlobalSettingOptions.BOTS);
await getCreatedBot(adminPage, bot.responseData.name);
await tokenExpirationForDays(adminPage);
await tokenExpirationUnlimitedDays(adminPage);
});
});

View File

@ -0,0 +1,112 @@
/*
* 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 } from '@playwright/test';
import { uuid } from '../../utils/common';
export type BotResponseDataType = {
name: string;
botUser: string;
description: string;
id?: string;
fullyQualifiedName?: string;
};
export type UserResponseDataType = {
botUser: undefined;
name: string;
description: string;
id?: string;
fullyQualifiedName?: string;
email: string;
isAdmin: boolean;
isBot: boolean;
authenticationMechanism: {
authType: string;
config: {
JWTTokenExpiry: string;
};
};
};
export class BotClass {
id = uuid();
data: BotResponseDataType;
userData: UserResponseDataType;
responseData: BotResponseDataType;
constructor(data?: BotResponseDataType) {
this.data = data ?? {
botUser: `PW%Bot-${this.id}`,
name: `PW%Bot-${this.id}`,
description: 'playwright for bot description',
};
this.userData = {
...this.data,
botUser: undefined,
email: `pw_bot${this.id}@gmail.com`,
isAdmin: false,
isBot: true,
authenticationMechanism: {
authType: 'JWT',
config: {
JWTTokenExpiry: 'OneHour',
},
},
};
}
get() {
return this.responseData;
}
async create(apiContext: APIRequestContext) {
const userResponse = await apiContext.put('/api/v1/users', {
data: this.userData,
});
const response = await apiContext.post('/api/v1/bots', {
data: this.data,
});
const data = await response.json();
this.responseData = data;
const userResponseData = await userResponse.json();
this.userData = userResponseData;
return data;
}
async delete(apiContext: APIRequestContext) {
const response = await apiContext.delete(
`/api/v1/bots/${this.responseData.id}?hardDelete=true&recursive=false`
);
return await response.json();
}
async patch(apiContext: APIRequestContext, data: Record<string, unknown>[]) {
const response = await apiContext.patch(
`/api/v1/bots/${this.responseData.id}`,
{
data,
headers: {
'Content-Type': 'application/json-patch+json',
},
}
);
this.responseData = await response.json();
return await response.json();
}
}

View File

@ -0,0 +1,210 @@
/*
* 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 {
customFormatDateTime,
getEpochMillisForFutureDays,
} from '../../src/utils/date-time/DateTimeUtils';
import { GlobalSettingOptions } from '../constant/settings';
import { BotResponseDataType } from '../support/bot/BotClass';
import { descriptionBox, toastNotification, uuid } from './common';
import { settingClick } from './sidebar';
import { revokeToken } from './user';
const botName = `bot-ct-test-${uuid()}`;
const BOT_DETAILS = {
botName: botName,
botEmail: `${botName}@mail.com`,
description: 'This is bot description',
updatedDescription: 'This is updated bot description',
updatedBotName: `updated-${botName}`,
unlimitedExpiryTime: 'This token has no expiration date.',
JWTToken: 'OpenMetadata JWT',
};
const EXPIRATION_TIME = [1, 7, 30, 60, 90];
export const getCreatedBot = async (page: Page, botName: string) => {
// Click on created Bot name
const fetchResponse = page.waitForResponse(
`/api/v1/bots/name/${encodeURIComponent(botName)}?*`
);
await page.getByTestId(`bot-link-${botName}`).click();
await fetchResponse;
};
export const createBot = async (page: Page) => {
// Click on add bot button
await page.getByTestId('add-bot').click();
// Fill the form details
await page.getByTestId('email').fill(BOT_DETAILS.botEmail);
await page.getByTestId('displayName').fill(BOT_DETAILS.botName);
// Select expiry time
await page.click('[data-testid="token-expiry"]');
await page.locator('[title="1 hr"] div').click();
await page.locator(descriptionBox).fill(BOT_DETAILS.description);
const saveResponse = page.waitForResponse('/api/v1/bots');
await page.click('[data-testid="save-user"]');
await saveResponse;
// Verify bot is getting added in the bots listing page
const table = page.locator('table');
await expect(table).toContainText(BOT_DETAILS.botName);
await expect(table).toContainText(BOT_DETAILS.description);
// Get created bot
await getCreatedBot(page, botName); // Replace with actual function to get created bot
await expect(page.getByTestId('revoke-button')).toContainText('Revoke token');
await expect(page.getByTestId('center-panel')).toContainText(
`${BOT_DETAILS.JWTToken} Token`
);
await expect(page.getByTestId('token-expiry')).toBeVisible();
await toastNotification(page, 'Bot created successfully.');
};
export const deleteBot = async (page: Page) => {
await settingClick(page, GlobalSettingOptions.BOTS);
// Click on delete button
await page.getByTestId(`bot-delete-${botName}`).click();
await page.getByTestId('hard-delete-option').click();
await page.getByTestId('confirmation-text-input').fill('DELETE');
const deleteResponse = page.waitForResponse(`/api/v1/bots/*`);
await page.getByTestId('confirm-button').click();
await deleteResponse;
await toastNotification(page, /deleted successfully!/);
await expect(page.getByTestId('page-layout-v1')).not.toContainText(botName);
};
export const updateBotDetails = async (
page: Page,
botData: BotResponseDataType
) => {
await getCreatedBot(page, botData.name);
await page.click('[data-testid="edit-displayName"]');
await page.getByTestId('displayName').fill(BOT_DETAILS.updatedBotName);
const updateDisplayNameResponse = page.waitForResponse(
`api/v1/bots/${botData.id ?? ''}`
);
await page.getByTestId('save-displayName').click();
await updateDisplayNameResponse;
// Verify the display name is updated on bot details page
await expect(
page.locator('[data-testid="left-panel"] .display-name')
).toContainText(BOT_DETAILS.updatedBotName);
// Click on edit description button
await page.getByTestId('edit-description').click();
await page.locator(descriptionBox).fill(BOT_DETAILS.updatedDescription);
const updateDescriptionResponse = page.waitForResponse(
`api/v1/bots/${botData.id ?? ''}`
);
await page.getByTestId('save').click();
await updateDescriptionResponse;
// Click on the breadcrumb link to go back to the bots listing page
const getBotsPageResponse = page.waitForResponse('/api/v1/bots*');
await page.locator('[data-testid="breadcrumb-link"]').first().click();
await getBotsPageResponse;
// Verify the updated name is displayed in the Bots listing page
await expect(
page.getByTestId(`bot-link-${BOT_DETAILS.updatedBotName}`)
).toContainText(BOT_DETAILS.updatedBotName);
await expect(
page.locator(
`[data-row-key="${botData.name}"] [data-testid="markdown-parser"]`
)
).toContainText(BOT_DETAILS.updatedDescription);
};
export const tokenExpirationForDays = async (page: Page) => {
for (const expiryTime of EXPIRATION_TIME) {
await revokeToken(page, true);
// Click on dropdown
await page.click('[data-testid="token-expiry"]');
// Select the expiration period
await page.locator(`text=${expiryTime} days`).click();
// Save the updated date
const expiryDate = customFormatDateTime(
getEpochMillisForFutureDays(expiryTime),
`ccc d'th' MMMM, yyyy`
);
await page.click('[data-testid="save-edit"]');
await expect(
page.locator('[data-testid="center-panel"] [data-testid="revoke-button"]')
).toBeVisible();
// Verify the expiry time
const tokenExpiryText = await page
.locator('[data-testid="token-expiry"]')
.innerText();
expect(tokenExpiryText).toContain(`Expires on ${expiryDate}`);
}
};
export const tokenExpirationUnlimitedDays = async (page: Page) => {
await revokeToken(page, true);
// Click on expiry token dropdown
await page.click('[data-testid="token-expiry"]');
// Select unlimited days
await page.getByText('Unlimited days').click();
// Save the selected changes
await page.click('[data-testid="save-edit"]');
// Verify the updated expiry time
const revokeButton = page.locator(
'[data-testid="center-panel"] [data-testid="revoke-button"]'
);
await expect(revokeButton).toBeVisible();
// Verify the expiry time
const tokenExpiry = page.locator('[data-testid="token-expiry"]');
await expect(tokenExpiry).toBeVisible();
const tokenExpiryText = await tokenExpiry.innerText();
expect(tokenExpiryText).toContain(BOT_DETAILS.unlimitedExpiryTime);
};

View File

@ -469,11 +469,13 @@ export const generateToken = async (page: Page) => {
await generateToken; await generateToken;
}; };
export const revokeToken = async (page: Page) => { export const revokeToken = async (page: Page, isBot?: boolean) => {
await page.click('[data-testid="revoke-button"]'); await page.click('[data-testid="revoke-button"]');
await expect(page.locator('[data-testid="body-text"]')).toContainText( await expect(page.locator('[data-testid="body-text"]')).toContainText(
'Are you sure you want to revoke access for Personal Access Token?' `Are you sure you want to revoke access for ${
isBot ? 'JWT Token' : 'Personal Access Token'
}?`
); );
await page.click('[data-testid="save-button"]'); await page.click('[data-testid="save-button"]');