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

test(EndToEnd): Add "Flame graph" tests #136

Merged
merged 36 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 25 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
21fe8e6
test(EndToEnd): Add "All services" tests
grafakus Aug 27, 2024
dd392e5
chore: Lint
grafakus Aug 27, 2024
ae444fb
doc: Update E2E README
grafakus Aug 27, 2024
d1cfd07
test: Add missing screenshots
grafakus Aug 27, 2024
962890f
test(PanelActions): Use a different profile type
grafakus Aug 27, 2024
5e04798
test(EndToEnd): Add "Profile types" tests
grafakus Aug 27, 2024
a8ad698
test(AllServices): Add extra assertion
grafakus Aug 27, 2024
6dca7a5
test(EndToEnd): Add "Flame graph" tests
grafakus Aug 27, 2024
302e712
Merge branch 'main' into test/explore-profiles-e2e-ter
grafakus Aug 27, 2024
57915dc
fix: Fix E2E test
grafakus Aug 27, 2024
850d876
Merge branch 'main' into test/explore-profiles-e2e-ter
grafakus Aug 28, 2024
6c2a337
refactor: Move CSS file to the "fixtures" folder
grafakus Aug 28, 2024
f1c37e9
Merge branch 'main' into test/explore-profiles-e2e-ter
grafakus Aug 28, 2024
449bd12
test: Add dependency service <-> profile type selector
grafakus Aug 28, 2024
d257757
test: Add filters tests
grafakus Aug 28, 2024
c08cbc0
test: Improve page objects
grafakus Aug 28, 2024
f00cb84
test: Remove comparison view tests
grafakus Aug 28, 2024
7048c51
chore: Move onboarding test to their own folder
grafakus Aug 28, 2024
70623ce
test: Update settings tests to use the new app views
grafakus Aug 28, 2024
67816e0
chore: Better naming
grafakus Aug 28, 2024
59c609d
chore: Better naming
grafakus Aug 28, 2024
8259fad
test(Settings): Regenerate screenshots
grafakus Aug 28, 2024
eb4111f
test(FlameGraph): Add GitHub integration tests
grafakus Aug 28, 2024
d0077f9
fix: Fix GitHub integration tests
grafakus Aug 28, 2024
3ca72c5
Merge branch 'main' into test/explore-profiles-e2e-ter
grafakus Aug 28, 2024
8a15845
fix: Fix GitHub integration tests
grafakus Aug 29, 2024
929ac0b
test: Disable legacy comparison tests
grafakus Aug 29, 2024
e57711b
test: Fix GitHub integration tests?
grafakus Aug 29, 2024
dcb5958
test: Fix GitHub integration?
grafakus Aug 29, 2024
d90f315
test: Fix GitHub integration?
grafakus Aug 29, 2024
12f81eb
test: Fix GitHub integration.
grafakus Aug 29, 2024
3e5e23a
chore: Lint
grafakus Aug 29, 2024
9d32ce3
Revert "test: Remove comparison view tests"
grafakus Aug 29, 2024
b4cfa2b
test: Disable legacy comparison tests
grafakus Aug 29, 2024
bcb0686
chore: Fix typos
grafakus Aug 29, 2024
f022a1b
Merge branch 'main' into test/explore-profiles-e2e-ter
grafakus Aug 29, 2024
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
29 changes: 27 additions & 2 deletions e2e/config/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ export const DEFAULT_URL_PARAMS = ENV_VARS.E2E_BASE_URL.startsWith('http://local
})
: new URLSearchParams();

export const AUTH_FILE = path.join(process.cwd(), 'e2e', 'auth', 'user.json');

/* Explore Profiles */

export enum ExplorationType {
AllServices = 'all',
ProfileTypes = 'profiles',
Labels = 'labels',
FlameGraph = 'flame-graph',
DiffFlameGraph = 'diff-flame-graph',
Favorites = 'favorites',
}

export const DEFAULT_EXPLORE_PROFILES_DATASOURCE_UID = 'grafanacloud-profiles-local-a';

export const DEFAULT_EXPLORE_PROFILES_URL_PARAMS = ENV_VARS.E2E_BASE_URL.startsWith('http://localhost')
? new URLSearchParams({
// We use static data in local and PR build (where the host is http://localhost):
Expand All @@ -40,8 +55,18 @@ export const DEFAULT_EXPLORE_PROFILES_URL_PARAMS = ENV_VARS.E2E_BASE_URL.startsW
'var-dataSource': 'grafanacloud-profiles-local-a',
'var-serviceName': 'ride-sharing-app',
'var-profileMetricId': 'process_cpu:cpu:nanoseconds:cpu:nanoseconds',
maxNodes: '16384',
// no max nodes here so that the app fetches user settings and we can also test settings changes
})
: new URLSearchParams();

export const AUTH_FILE = path.join(process.cwd(), 'e2e', 'auth', 'user.json');
export const EXPLORE_PROFILES_DIFF_RANGES_URL_PARAMS = new URLSearchParams({
// no max nodes here neither so that the app fetches user settings and we can also test settings changes
'from-2': '2024-03-13T18:00:00.000Z',
'to-2': '2024-03-13T18:50:00.000Z',
'from-3': '2024-03-13T18:00:00.000Z',
'to-3': '2024-03-13T18:50:00.000Z',
diffFrom: '2024-03-13T18:15:00.000Z',
diffTo: '2024-03-13T18:20:00.000Z',
'diffFrom-2': '2024-03-13T18:35:00.000Z',
'diffTo-2': '2024-03-13T18:45:00.000Z',
});
150 changes: 126 additions & 24 deletions e2e/fixtures/pages/ExploreProfilesPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, type Page } from '@playwright/test';

import { DEFAULT_EXPLORE_PROFILES_URL_PARAMS } from '../../config/constants';
import { DEFAULT_EXPLORE_PROFILES_URL_PARAMS, ExplorationType } from '../../config/constants';
import { PyroscopePage } from './PyroscopePage';

export class ExploreProfilesPage extends PyroscopePage {
Expand All @@ -10,28 +10,32 @@ export class ExploreProfilesPage extends PyroscopePage {
super(page, '/a/grafana-pyroscope-app/profiles-explorer', urlParams.toString());
}

goto(explorationType: string | undefined) {
if (!explorationType) {
return super.goto();
}
goto(explorationType: ExplorationType, urlSearchParams = new URLSearchParams()) {
const urlParams = new URLSearchParams({
...Object.fromEntries(DEFAULT_EXPLORE_PROFILES_URL_PARAMS),
...Object.fromEntries(urlSearchParams),
});

const urlParams = new URLSearchParams(DEFAULT_EXPLORE_PROFILES_URL_PARAMS);
urlParams.set('explorationType', explorationType);

return super.goto(urlParams.toString());
}

/* Data source */
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a future PR, we should split the responsibilities among several, smaller classes.


getDataSourceSelector() {
return this.page.locator('#dataSource');
return this.locator('#dataSource');
}

async assertSelectedDataSource(expectedDataSource: string) {
const name = await this.getDataSourceSelector().textContent();
await expect(name?.trim()).toBe(expectedDataSource);
}

/* Exploration type */

getExplorationTypeSelector() {
return this.page.getByTestId('exploration-types');
return this.getByTestId('exploration-types');
}

async asserSelectedExplorationType(expectedLabel: string) {
Expand All @@ -43,16 +47,35 @@ export class ExploreProfilesPage extends PyroscopePage {
return this.getExplorationTypeSelector().getByLabel(explorationType).click();
}

/* Time picker */

getTimePicker() {
return this.page.getByTestId('data-testid TimePicker Open Button');
return this.getByTestId('data-testid TimePicker Open Button');
}

async assertSelectedTimeRange(expectedTimeRange: string) {
await expect(this.getTimePicker()).toContainText(expectedTimeRange);
}

/* Service */

getServiceSelector() {
return this.getByTestId('serviceName').locator('input');
}

async assertSelectedService(expectedService: string) {
await expect(this.getServiceSelector()).toHaveValue(expectedService);
}

async selectService(serviceName: string) {
await this.getServiceSelector().click();
await this.locator('[role="menu"]').getByText(serviceName, { exact: true }).click();
}

/* Profile type */

getProfileTypeSelector() {
return this.page.getByTestId('profileMetricId').locator('input');
return this.getByTestId('profileMetricId').locator('input');
}

async assertSelectedProfileType(expectedProfileType: string) {
Expand All @@ -63,25 +86,36 @@ export class ExploreProfilesPage extends PyroscopePage {
const [category, type] = profileType.split('/');

await this.getProfileTypeSelector().click();
await this.page.getByText(category, { exact: true }).click();
await this.page.getByText(type, { exact: true }).click();
}

getServiceSelector() {
return this.page.getByTestId('serviceName').locator('input');
const menu = this.locator('[role="menu"]');
await menu.getByText(category, { exact: true }).click();
await menu.getByText(type, { exact: true }).click();
}

async assertSelectedService(expectedService: string) {
await expect(this.getServiceSelector()).toHaveValue(expectedService);
}
async assertProfileTypeSelectorOptions(expectedCategories: string[], expectedTypesPerCategory: string[][]) {
await this.getProfileTypeSelector().click();

async selectService(serviceName: string) {
await this.getServiceSelector().click();
await this.page.getByText(serviceName, { exact: true }).click();
const menuItems = this.locator('[role="menu"] [role="menuitemcheckbox"]');
const categories = await menuItems.allTextContents();

expect(categories).toEqual(expectedCategories);

for (let i = 0; i < categories.length; i += 1) {
await menuItems.nth(i).click();

const categoryTypes = await this.locator('[role="menu"]')
.last()
.locator('[role="menuitemcheckbox"]')
.allTextContents();

expect(categoryTypes).toEqual(expectedTypesPerCategory[i]);
}
}

/* Quick filter */

getQuickFilterInput() {
return this.page.getByLabel('Quick filter');
return this.getByLabel('Quick filter');
}

async assertQuickFilterValue(expectedValue: string) {
Expand All @@ -93,8 +127,10 @@ export class ExploreProfilesPage extends PyroscopePage {
await this.waitForTimeout(250); // see SceneQuickFilter.DEBOUNCE_DELAY
}

/* Layout switcher */

getLayoutSwitcher() {
return this.page.getByLabel('Layout switcher');
return this.getByLabel('Layout switcher');
}

async assertSelectedLayout(expectedLayoutName: string) {
Expand All @@ -106,8 +142,10 @@ export class ExploreProfilesPage extends PyroscopePage {
return this.getLayoutSwitcher().getByLabel(layoutName).click();
}

/* Scene body & grid panels */

getSceneBody() {
return this.page.getByTestId('sceneBody');
return this.getByTestId('sceneBody');
}

getPanelByTitle(title: string) {
Expand All @@ -117,4 +155,68 @@ export class ExploreProfilesPage extends PyroscopePage {
getPanels() {
return this.getSceneBody().locator(`[data-viz-panel-key]`);
}

async clickOnPanelAction(panelTitle: string, actionLabel: string) {
const panel = await this.getPanelByTitle(panelTitle);
await panel.getByLabel(actionLabel).click();
}

/* Filters */

getFilters() {
return this.getByTestId('filters');
}

async assertFilters(expectedFilters: string[][]) {
const filters = this.getFilters().getByTestId('filtersList').getByLabel('Filter', { exact: true });

await expect(filters).toHaveCount(expectedFilters.length);

for (let i = 0; i < expectedFilters.length; i += 1) {
const [expectedLabel, expectedOperator, expectedValue] = expectedFilters[i];

const filter = filters.nth(0);
const filterParts = filter.locator('button');

await expect(filterParts.nth(0)).toHaveText(expectedLabel);
await expect(filterParts.nth(1)).toHaveText(expectedOperator);
await expect(filterParts.nth(2)).toHaveText(expectedValue);
}
}

async addFilter(parts: string[]) {
await this.getFilters().getByRole('combobox').click();

const selectMenu = this.getByLabel('Select options menu');

for (const part of parts) {
await selectMenu.getByText(part, { exact: true }).click();
}
}

/* Flame graph component */

getExportDataButton() {
return this.getByLabel('Export data');
}

getFlamegraph() {
return this.getByTestId('flameGraph');
}

clickOnFlameGraphNode({ x, y }: { x: number; y: number }) {
return this.getFlamegraph().click({ position: { x, y } });
}

getFlameGraphContextualMenu() {
return this.getByLabel('Context menu');
}

getFlameGraphContextualMenuItem(menuItemLabel: string) {
return this.getFlameGraphContextualMenu().getByRole('menuitem', { name: menuItemLabel, exact: true });
}

closeFlameGraphContextualMenu() {
return this.getByTestId('panel').getByRole('heading').click();
}
}
6 changes: 5 additions & 1 deletion e2e/fixtures/pages/PyroscopePage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,14 @@ export class PyroscopePage {
return this.page.locator(selector, options);
}

getByTestid(testId: string | RegExp) {
getByTestId(testId: string | RegExp) {
return this.page.getByTestId(testId);
}

getByLabel(label: string) {
return this.page.getByLabel(label);
}

getByText(text, options?: Record<string, unknown>) {
return this.page.getByText(text, options);
}
Expand Down
18 changes: 13 additions & 5 deletions e2e/fixtures/pages/SettingsPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { type Page } from '@playwright/test';

import { DEFAULT_SETTINGS } from '../../../src/shared/infrastructure/settings/PluginSettings';
import { DEFAULT_EXPLORE_PROFILES_DATASOURCE_UID } from '../../config/constants';
import { PyroscopePage } from './PyroscopePage';

export class SettingsPage extends PyroscopePage {
Expand All @@ -12,16 +13,21 @@ export class SettingsPage extends PyroscopePage {
await this.page.goto(this.pathname);
}

async resetTestSettings() {
async resetTestSettings(reloadPage = true) {
// see src/shared/infrastructure/http/ApiClient.ts
let appUrl = await this.page.evaluate(() => (window as any).grafanaBootData.settings.appUrl);
let { appUrl, dataSourceUid } = await this.page.evaluate(() => ({
appUrl: (window as any).grafanaBootData.settings.appUrl,
dataSourceUid: new URL(window.location.href).searchParams.get('var-dataSource'),
}));

if (appUrl.at(-1) !== '/') {
appUrl += '/';
}

// IMPORTANT: the path must match the default data source in samples/provisioning/datasources/datasources.yaml
const apiUrl = new URL(
'api/datasources/proxy/uid/grafanacloud-profiles-local-bis/settings.v1.SettingsService/Set',
`api/datasources/proxy/uid/${
dataSourceUid || DEFAULT_EXPLORE_PROFILES_DATASOURCE_UID
}/settings.v1.SettingsService/Set`,
appUrl
);

Expand All @@ -34,7 +40,9 @@ export class SettingsPage extends PyroscopePage {
},
});

await this.page.reload();
if (reloadPage) {
await this.page.reload();
}
}

getFlamegraphSettings() {
Expand Down
Loading
Loading