Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PMM-7 rbac tests fix #679

Merged
merged 5 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion playwright-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ this is it! tests are good to go on specified PMM server.
### Running tests:
Execute command in the **playwright-tests** folder
* run all tests: `npx playwright test`
* run a single test file: `npx playwright test tests/todo-page.spec.ts`
* run a single test file: `npx playwright test --projet=Cromium rbac.spec.ts`
* run Portal tests: `npx playwright test --projet=Portal -g @portal`
* run a set of test files: `npx playwright test tests/todo-page/ tests/landing-page/`
* run files that have **my-spec** or **my-spec-2** in the file name: `npx playwright test my-spec my-spec-2`
* run desired [groups/tags](https://playwright.dev/docs/test-annotations#tag-tests): `npx playwright test --grep @rbac`
Expand Down
41 changes: 32 additions & 9 deletions playwright-tests/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,45 @@ import { portalApi } from '@api/portal.api';
import { serviceNowApi } from '@api/service-now.api';
import { inventoryApi } from '@api/inventory.api';
import { managementApi } from '@api/management.api';
import { apiHelper } from '@api/helpers/api-helper';
import { orgApi } from '@api/org.api';

export interface OrgUser {
orgId: number,
userId: number,
email: string,
name: string,
avatarUrl: string,
login: string,
role: string,
lastSeenAt: string,
lastSeenAtAge: string,
accessControl: {
'org.users:add': boolean,
'org.users:read': boolean,
'org.users:remove': boolean,
'org.users:write': boolean
},
isDisabled: boolean
}

export interface ListRoles {
roles: Role[]
}

export interface Role {
role_id?: number,
title: string,
filter?: string,
description?: string
}

/**
* User facing api collection. Accessible on Frontend via /swagger path.
* API version intentionally included into the API groups to have
* obvious which API and which version is used.
*/
export const api = {
grafana: {
// TODO: move it to proper file API. Suggestion: grafanaApi
listOrgUsers: async () => {
const response = await apiHelper.get('/graph/api/org/users?accesscontrol=true');
console.log(`Response:\n${JSON.stringify(await response.json())}`);
return response.json();
},
},
grafana: { org: orgApi },
pmm: {
inventoryV1: inventoryApi,
settingsV1: settingsApi,
Expand Down
33 changes: 29 additions & 4 deletions playwright-tests/api/helpers/api-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@ import config from '@root/playwright.config';
import grafanaHelper from '@helpers/grafana-helper';
import { APIResponse } from 'playwright-core';
import { ReadStream } from 'fs';
import { Serializable } from 'playwright-core/types/structs';

const getConfiguredRestApi = async (): Promise<APIRequestContext> => {
return request.newContext({
baseURL: config.use?.baseURL,
extraHTTPHeaders: {
Authorization: `Basic ${grafanaHelper.getToken()}`,
},
extraHTTPHeaders: { Authorization: `Basic ${grafanaHelper.getToken()}` },
ignoreHTTPSErrors: true,
});
};
Expand Down Expand Up @@ -44,7 +43,7 @@ const apiHelper = {

/**
* Implements HTTP GET to PMM Server API
* Request parameters can be configured with original clinet options.
* Request parameters can be configured with original client options.
* See original doc for more details: {@link APIRequestContext#get()}
*
* @param path API endpoint path
Expand Down Expand Up @@ -86,5 +85,31 @@ const apiHelper = {
expect(response.status(), `Status: ${response.status()} ${response.statusText()}`).toEqual(200);
return response;
},

/**
* Implements HTTP DELETE to PMM Server API
* Request parameters can be configured with original client options.
* See original doc for more details: {@link APIRequestContext#delete()}
*
* @param path API endpoint path
* @param options see original doc: {@link APIRequestContext#delete()}
* @return Promise<APIResponse> instance
*/
delete: async (path: string, options?:
{
data?: string | Buffer | Serializable,
failOnStatusCode?: boolean,
form?: { [p: string]: string | number | boolean },
headers?: { [p: string]: string },
ignoreHTTPSErrors?: boolean,
maxRedirects?: number,
multipart?: { [p: string]: string | number | boolean | ReadStream | { name: string, mimeType: string, buffer: Buffer } },
params?: { [p: string]: string | number | boolean }, timeout?: number
}): Promise<APIResponse> => {
console.log(`DELETE: ${path}${options ? ` with ${JSON.stringify(options)}` : ''}`);
const response = await (await getConfiguredRestApi()).delete(path, options);
expect(response.status(), `Status: ${response.status()} ${response.statusText()}`).toEqual(200);
return response;
},
};
export default apiHelper;
2 changes: 1 addition & 1 deletion playwright-tests/api/inventory.api.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { apiHelper } from './helpers/api-helper';
import apiHelper from './helpers/api-helper';

interface ListNodes {
generic?: NodeDetails[],
Expand Down
43 changes: 30 additions & 13 deletions playwright-tests/api/management.api.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
import { apiHelper } from '@api/helpers/api-helper';

export interface ListRoles {
roles: Role[]
}

interface Role {
role_id: number,
title: string,
}
import apiHelper from '@api/helpers/api-helper';
import { ListRoles, Role } from '@api/api';

const ROLE_CREATE = '/v1/management/Role/Create';
const ROLE_LIST = '/v1/management/Role/List';
const ROLE_DELETE = '/v1/management/Role/Delete';
/**
* v1 API: "management" endpoints requests collection
*/
export const managementApi = {
listRoles: async (): Promise<ListRoles | undefined> => {
const response = await apiHelper.post('/v1/management/Role/List', {});
return await response.json() as ListRoles;
/**
* TODO: investigate filter stringify actually works as desired.
*
* @param title a name a role to be ctreated
* @param description optional description for a role
* @param filter Prometeus Filter string, ex: "{agent_type=\"mysqld_exporter\", agent_type=\"mongodb_exporter\"}"
*/
roleCreate: async (title: string, description?: string, filter?: string) => {
const payload: Role = {
title, description, filter,
};
return (await (await apiHelper.post(ROLE_CREATE, payload)).json()).role_id as number;
},

listRoles: async (): Promise<ListRoles> => {
return await (await apiHelper.post(ROLE_LIST, {})).json() as ListRoles;
},

listServices: async (): Promise<any | undefined> => {
const response = await apiHelper.post('/v1/management/Service/List', {});
return response.json();
},

async deleteRole(roleTile: string) {
const searchResult = (await this.listRoles()).roles.find((role: Role) => role.title === roleTile);
if (searchResult) {
await apiHelper.post(ROLE_DELETE, { role_id: searchResult.role_id, replacement_role_id: 1 });
}
},
};
25 changes: 25 additions & 0 deletions playwright-tests/api/org.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import apiHelper from '@api/helpers/api-helper';
import { OrgUser } from '@api/api';

/**
* grafana API: "org" endpoints requests collection
*/
export const orgApi = {
/**
* @param email email to search for the Org user.
*/
async lookupOrgUser(email: string): Promise<OrgUser | undefined> {
return (await this.listOrgUsers()).find((user: OrgUser) => user.email === email);
},

async listOrgUsers(): Promise<OrgUser[]> {
return await (await apiHelper.get('/graph/api/org/users?accesscontrol=true')).json() as OrgUser[];
},

async deleteOrgUser(email: string) {
const foundUser = (await this.listOrgUsers()).find((user: OrgUser) => user.email === email);
if (foundUser) {
await apiHelper.delete(`/graph/api/org/users/${foundUser.userId}`);
}
},
};
11 changes: 5 additions & 6 deletions playwright-tests/helpers/test-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PerconaPlatformPage from '@pages/pmm-settings/percona-platform.page';
import EntitlementsPage from '@pages/platformPages/entitlements.page';
import EnvironmentOverviewPage from '@pages/platformPages/environment-overview.page';
import TicketsPage from '@pages/platformPages/tickets.page';
import { RbacPage } from '@tests/configuration/pages/rbac.page';
import { AccessRolesPage } from '@tests/configuration/pages/access-roles.page';
import AdvancedSettingsPage from '@pages/pmm-settings/advanced-settings.page';
import { CreateRolePage } from '@tests/configuration/pages/create-role.page';
import { NewUserPage } from '@pages/serverAdmin/NewUser.page';
Expand All @@ -18,10 +18,10 @@ import { QanPage } from '@pages/QAN/QueryAnalytics.page';
import { ServicesPage } from '@tests/inventory/pages/services.page';
import { NodesPage } from '@tests/inventory/pages/nodes.page';
import { AddServicePage } from '@tests/inventory/pages/add-service.page';
import grafanaHelper from "@helpers/grafana-helper";

// Declare the types of fixtures.
type PagesCollection = {
accessRolesPage: AccessRolesPage;
addServicePage: AddServicePage;
advancedSettingsPage: AdvancedSettingsPage;
createRolePage: CreateRolePage;
Expand All @@ -38,7 +38,6 @@ type PagesCollection = {
postgresqlInstancesOverviewDashboard: PostgresqlInstancesOverviewDashboard;
qanPage: QanPage;
servicesPage: ServicesPage;
rbacPage: RbacPage;
ticketsPage: TicketsPage;
usersConfigurationPage: UsersConfigurationPage;
};
Expand All @@ -49,6 +48,9 @@ type PagesCollection = {
*/
export const test = base.extend<PagesCollection>({
// TODO: implement lazy init ex: loginPage() to save resources
accessRolesPage: async ({ page }, use) => {
await use(new AccessRolesPage(page));
},
addServicePage: async ({ page }, use) => {
await use(new AddServicePage(page));
},
Expand Down Expand Up @@ -97,9 +99,6 @@ export const test = base.extend<PagesCollection>({
servicesPage: async ({ page }, use) => {
await use(new ServicesPage(page));
},
rbacPage: async ({ page }, use) => {
await use(new RbacPage(page));
},
ticketsPage: async ({ page }, use) => {
await use(new TicketsPage(page));
},
Expand Down
2 changes: 1 addition & 1 deletion playwright-tests/pages/common.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export class CommonPage {
sideMenu = new LeftNavigationMenu(this.page);
optionMenu = new OptionsMenu(this.page);

landingUrl = 'graph/d/pmm-home/home-dashboard?orgId=1&refresh=1m';
PAGE_HEADING_LOCATOR = this.page.locator('//h1');

elements: object = {
mainView: this.page.locator('//*[@class="main-view"]'),
Expand Down
20 changes: 19 additions & 1 deletion playwright-tests/pages/home-dashboard.page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,28 @@ import PmmMenu from '@components/dashboards/pmm-menu';
import { BaseDashboard } from './dashboards/base-dashboard.page';

export default class HomeDashboardPage extends BaseDashboard {
/** Page "path" and "heading" defaults to the "Home Dashboard Page" */
// url: 'graph/d/pmm-home/home-dashboard?orgId=1&refresh=1m&from=now-5m&to=now',
PAGE_PATH = 'graph/d/pmm-home/home-dashboard?orgId=1&refresh=1m';
PAGE_HEADING = 'Home Dashboard';

pmmUpgrade = new PmmUpgrade(this.page);
upgradeModal = new UpgradeModal(this.page);
pmmMenu = new PmmMenu(this.page);

landingUrl = 'graph/d/pmm-home/home-dashboard?orgId=1&refresh=1m';

/**
* Opens given Page entering url into the address field.
*/
public open = async () => {
await this.openPageByPath(this.PAGE_PATH, this.PAGE_HEADING, this.PAGE_HEADING_LOCATOR);
};

async waitToBeOpened() {
await this.pmmUpgrade.containers.upgradeContainer.waitFor({ state: 'visible', timeout: Wait.OneMinute });
await expect(this.page).toHaveURL(this.PAGE_PATH);
}
upgradePmm = async () => {
await this.pmmUpgrade.buttons.upgradeButton.waitFor({ state: 'visible', timeout: Wait.ThreeMinutes });
const currentVersion = await this.pmmUpgrade.elements.currentVersion.textContent();
Expand All @@ -21,7 +39,7 @@ export default class HomeDashboardPage extends BaseDashboard {

await this.upgradeModal.containers.modalContainer.waitFor({ state: 'visible', timeout: Wait.OneMinute });
await this.upgradeModal.elements.upgradeInProgressHeader
.waitFor({ state: 'visible', timeout: Wait.OneMinute });
.waitFor({ state: 'visible', timeout: Wait.OneMinute as number });
await expect(this.upgradeModal.elements.upgradeSuccess)
.toHaveText(this.upgradeModal.messages.upgradeSuccess(availableVersion) as string, { timeout: Wait.TenMinutes });
await this.upgradeModal.buttons.close.click();
Expand Down
22 changes: 7 additions & 15 deletions playwright-tests/pages/login.page.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,31 @@
import { CommonPage } from '@pages/common.page';
import { Locator } from '@playwright/test';

export default class LoginPage extends CommonPage {
readonly PAGE_PATH = 'graph/login';
readonly PAGE_HEADING = 'Percona Monitoring and Management';

elements: any = {
...this.elements,
headingLocator: this.page.locator('//h1'),
usernameInput: this.page.locator('//input[@name="user"]'),
passwordInput: this.page.locator('//input[@name="password"]'),
oktaLoginButton: this.page.locator('//*[@href="login/generic_oauth"]'),
oktaLogin: {
usernameInput: this.page.locator('//*[@name="username"]'),
passwordInput: this.page.locator('//*[@name="password"]'),
usernameInput: this.page.locator('//input[@name="username"]'),
passwordInput: this.page.locator('//input[@name="password"]'),
nextButton: this.page.locator('//*[@id="idp-discovery-submit"]'),
signInButton: this.page.locator('//*[@id="okta-signin-submit"]'),
},
};

fields = {
username: this.page.locator('//*[@name="username"]'),
password: this.page.locator('//*[@name="password"]'),
};

buttons = {
oktaLogin: this.page.locator('//*[@href="login/generic_oauth"]'),
};

/**
* Opens given Page entering url into the address field.
*/
public open = async () => {
await this.openPageByPath(this.PAGE_PATH, this.PAGE_HEADING, this.elements.headingLocator as Locator);
await this.openPageByPath(this.PAGE_PATH, this.PAGE_HEADING, this.PAGE_HEADING_LOCATOR);
};

oktaLogin = async (username: string, password: string) => {
await this.buttons.oktaLogin.click();
await this.elements.oktaLoginButton.click();
await this.elements.oktaLogin.usernameInput.type(username);
await this.elements.oktaLogin.nextButton.click();
await this.elements.oktaLogin.passwordInput.type(password);
Expand Down
Loading
Loading