feat: trace explorer page end to end test (#3960)

* feat: added trace explorer tests

* feat: code refactor

---------

Co-authored-by: Palash Gupta <palashgdev@gmail.com>
This commit is contained in:
Vikrant Gupta 2023-11-17 18:14:10 +05:30 committed by GitHub
parent 29bfdb8909
commit d091d90d66
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 523 additions and 7 deletions

View File

@ -220,7 +220,11 @@ function ExplorerCard({
open={isOpen}
onOpenChange={handleOpenChange}
>
<Button type={saveButtonType} icon={saveButtonIcon}>
<Button
type={saveButtonType}
icon={saveButtonIcon}
data-testid="traces-save-view-action"
>
{isQueryUpdated
? SaveButtonText.SAVE_AS_NEW_VIEW
: SaveButtonText.SAVE_VIEW}

View File

@ -64,7 +64,12 @@ function SaveViewWithName({
>
<Input placeholder="Enter Name" />
</Form.Item>
<SaveButton htmlType="submit" type="primary" loading={isLoading}>
<SaveButton
htmlType="submit"
type="primary"
loading={isLoading}
data-testid="save-view-name-action-button"
>
Save
</SaveButton>
</Form>

View File

@ -34,6 +34,7 @@ function NewExplorerCTA(): JSX.Element | null {
icon={<CompassOutlined />}
onClick={onClickHandler}
danger
data-testid="newExplorerCTA"
type="primary"
>
{buttonText[location.pathname]}

View File

@ -36,7 +36,11 @@ export const getListColumns = (
width: 145,
render: (date: string): JSX.Element => {
const day = dayjs(date);
return <DateText>{day.format('YYYY/MM/DD HH:mm:ss')}</DateText>;
return (
<DateText data-testid="trace-explorer-date">
{day.format('YYYY/MM/DD HH:mm:ss')}
</DateText>
);
},
},
];
@ -48,18 +52,22 @@ export const getListColumns = (
key: `${key}-${dataType}-${type}`,
render: (value): JSX.Element => {
if (value === '') {
return <Typography>N/A</Typography>;
return <Typography data-testid={key}>N/A</Typography>;
}
if (key === 'httpMethod' || key === 'responseStatusCode') {
return <Tag color="magenta">{value}</Tag>;
return (
<Tag data-testid={key} color="magenta">
{value}
</Tag>
);
}
if (key === 'durationNano') {
return <Typography>{getMs(value)}ms</Typography>;
return <Typography data-testid={key}>{getMs(value)}ms</Typography>;
}
return <Typography>{value}</Typography>;
return <Typography data-testid={key}>{value}</Typography>;
},
})) || [];

View File

@ -42,6 +42,7 @@ export const columns: ColumnsType<ListItem['data']> = [
href={generatePath(ROUTES.TRACE_DETAIL, {
id: traceID,
})}
data-testid="trace-id"
>
{traceID}
</Typography.Link>

View File

@ -0,0 +1,14 @@
{
"status": "success",
"data": {
"attributeKeys": [
{
"key": "serviceName",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
}
]
}
}

View File

@ -0,0 +1,14 @@
{
"status": "success",
"data": {
"attributeKeys": [
{
"key": "durationNano",
"dataType": "float64",
"type": "tag",
"isColumn": true,
"isJSON": false
}
]
}
}

View File

@ -0,0 +1,21 @@
{
"status": "success",
"data": {
"attributeKeys": [
{
"key": "externalHttpMethod",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
},
{
"key": "httpMethod",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
}
]
}
}

View File

@ -0,0 +1,56 @@
{
"status": "success",
"data": {
"attributeKeys": [
{
"key": "dbName",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
},
{
"key": "host.name",
"dataType": "string",
"type": "resource",
"isColumn": false,
"isJSON": false
},
{
"key": "process.runtime.name",
"dataType": "string",
"type": "resource",
"isColumn": false,
"isJSON": false
},
{
"key": "service.name",
"dataType": "string",
"type": "resource",
"isColumn": false,
"isJSON": false
},
{
"key": "serviceName",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
},
{
"key": "name",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
},
{
"key": "telemetry.sdk.name",
"dataType": "string",
"type": "resource",
"isColumn": false,
"isJSON": false
}
]
}
}

View File

@ -0,0 +1,14 @@
{
"status": "success",
"data": {
"attributeKeys": [
{
"key": "responseStatusCode",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
}
]
}
}

View File

@ -0,0 +1,14 @@
{
"status": "success",
"data": {
"attributeKeys": [
{
"key": "serviceName",
"dataType": "string",
"type": "tag",
"isColumn": true,
"isJSON": false
}
]
}
}

View File

@ -0,0 +1,26 @@
{
"status": "success",
"data": {
"resultType": "",
"result": [
{
"queryName": "A",
"series": null,
"list": [
{
"timestamp": "2023-11-14T18:26:59.966905Z",
"data": {
"durationNano": 57896000,
"httpMethod": "GET",
"name": "HTTP GET /route",
"responseStatusCode": "200",
"serviceName": "route",
"spanID": "0e5da5411ccc8ea5",
"traceID": "00000000000000007008a05a3d9e5b97"
}
}
]
}
]
}
}

View File

@ -0,0 +1,4 @@
{
"status": "success",
"data": "336368a4-dba7-4d65-9f91-142a355edb23"
}

View File

@ -0,0 +1,52 @@
{
"status": "success",
"data": [
{
"uuid": "8402eda3-2be3-4e07-930b-9ff69c3da34f",
"name": "PlayWright",
"category": "",
"createdAt": "2023-11-16T19:24:49.875396768Z",
"createdBy": "contributors@signoz.io",
"updatedAt": "2023-11-16T19:24:49.875396872Z",
"updatedBy": "contributors@signoz.io",
"sourcePage": "traces",
"tags": [""],
"compositeQuery": {
"builderQueries": {
"A": {
"queryName": "A",
"stepInterval": 60,
"dataSource": "traces",
"aggregateOperator": "count",
"aggregateAttribute": {
"key": "",
"dataType": "",
"type": "",
"isColumn": false,
"isJSON": false
},
"filters": {
"op": "AND",
"items": []
},
"expression": "A",
"disabled": false,
"limit": 0,
"offset": 0,
"pageSize": 0,
"orderBy": [
{
"columnName": "timestamp",
"order": "desc"
}
],
"reduceTo": "sum"
}
},
"panelType": "table",
"queryType": "builder"
},
"extraData": ""
}
]
}

View File

@ -0,0 +1,24 @@
{
"status": "success",
"data": {
"resultType": "",
"result": [
{
"queryName": "A",
"series": null,
"list": [
{
"timestamp": "0001-01-01T00:00:00Z",
"data": {
"span_count": 51,
"subQuery.durationNano": 1533445000,
"subQuery.name": "HTTP GET /dispatch",
"subQuery.serviceName": "frontend",
"traceID": "0000000000000000013e51e33c929173"
}
}
]
}
]
}
}

View File

@ -0,0 +1,24 @@
{
"status": "success",
"data": {
"resultType": "",
"result": [
{
"queryName": "A",
"series": [
{
"labels": {},
"labelsArray": null,
"values": [
{
"timestamp": 1700161420000,
"value": "85784"
}
]
}
],
"list": null
}
]
}
}

View File

@ -0,0 +1,118 @@
import { Page, test, expect } from '@playwright/test';
import { loginApi } from '../fixtures/common';
import ROUTES from 'constants/routes';
import queryRangeSuccessResponse from '../fixtures/api/traces/queryRange200.json';
import tracesSuccessResponse from '../fixtures/api/traces/tracesRange200.json';
import tracesTableSuccessResponse from '../fixtures/api/traces/tracesTableView200.json';
import {
defaultAttributeKeysData,
httpMethodAttributeID,
newExplorerCtaID,
queryRangeApiEndpoint,
saveNewViewID,
saveNewViewWithNameID,
serviceAttributeID,
tableViewTabID,
traceExplorerViewsGetEndpoint,
traceExplorerViewsPostEndpoint,
traceRowTraceTabID,
traceTabID,
tracesExplorerQueryData,
tracesExplorerViewsData,
tracesExplorerViewsPostData,
} from './utils';
let page: Page;
test.describe('New Traces Explorer', () => {
test.beforeEach(async ({ baseURL, browser }) => {
const context = await browser.newContext({
storageState: 'tests/auth.json',
});
const newPage = await context.newPage();
await loginApi(newPage);
await newPage.goto(`${baseURL}${ROUTES.APPLICATION}`);
page = newPage;
});
test('Traces Explorer Tests', async ({}) => {
await page.locator(`li[data-menu-id*="/trace"]`).click();
await tracesExplorerQueryData(page, queryRangeSuccessResponse);
await defaultAttributeKeysData(page);
const queryRangeRequest = page.waitForRequest(`**/${queryRangeApiEndpoint}`);
await page.locator(`data-testid=${newExplorerCtaID}`).click();
await queryRangeRequest;
await page.getByText('List View').click();
const serviceName = await page
.locator(`data-testid=${serviceAttributeID}`)
.textContent();
expect(serviceName).toBe('route');
const httpMethod = await page
.locator(`data-testid=${httpMethodAttributeID}`)
.textContent();
expect(httpMethod).toBe('GET');
await tracesExplorerQueryData(page, tracesSuccessResponse);
await page.locator(`id=${traceTabID}`).click();
const traceID = await page
.locator(`data-testid=${traceRowTraceTabID}`)
.textContent();
expect(traceID).toBe(
tracesSuccessResponse.data.result[0].list[0].data.traceID,
);
await tracesExplorerQueryData(page, tracesTableSuccessResponse);
await page.locator(`id=${tableViewTabID}`).click();
await page.waitForLoadState('networkidle');
const count = await page.getByText('85784').isVisible();
await expect(count).toBeTruthy();
await page.locator(`data-testid=${saveNewViewID}`).click();
await page.locator('id=viewName').type('Playwright');
await tracesExplorerQueryData(page, queryRangeSuccessResponse);
await tracesExplorerViewsData(page);
await tracesExplorerViewsPostData(page);
const viewsSaveRequest = page.waitForRequest(
`**/${traceExplorerViewsPostEndpoint}`,
);
const viewsGetRequest = page.waitForRequest(
`**/${traceExplorerViewsGetEndpoint}`,
);
await page.locator(`data-testid=${saveNewViewWithNameID}`).click();
await viewsSaveRequest;
await viewsGetRequest;
const viewName = await page.getByText('Playwright').isVisible();
await expect(viewName).toBeTruthy();
});
});

View File

@ -0,0 +1,116 @@
import { Page } from '@playwright/test';
import attributeKeyServiceNameSuccessResponse from '../fixtures/api/traces/attributeKeysServiceName200.json';
import attributeKeyNameSuccessResponse from '../fixtures/api/traces/attributeKeysName200.json';
import attributeKeyDurationNanoSuccessResponse from '../fixtures/api/traces/attributeKeysDurationNano200.json';
import attributeKeyResponseStatusCodeSuccessResponse from '../fixtures/api/traces/attributeKeysResponseStatusCode200.json';
import attributeKeyHttpMethodSuccessResponse from '../fixtures/api/traces/attributeKeysHttpMethod200.json';
import traceExplorerViewsSuccessResponse from '../fixtures/api/traces/traceExplorerViews200.json';
import traceExplorerViewsPostSuccessResponse from '../fixtures/api/traces/traceExplorerViewPost200.json';
import { JsonApplicationType } from '../fixtures/constant';
export const queryRangeApiEndpoint = 'query_range';
export const attributeKeysApiEndpoint = 'autocomplete/attribute_keys';
export const traceExplorerViewsGetEndpoint = 'explorer/views?sourcePage=traces';
export const traceExplorerViewsPostEndpoint = 'explorer/views';
export const newExplorerCtaID = 'newExplorerCTA';
export const serviceAttributeID = 'serviceName';
export const httpMethodAttributeID = 'httpMethod';
export const traceTabID = 'rc-tabs-0-tab-trace';
export const traceRowTraceTabID = 'trace-id';
export const tableViewTabID = 'rc-tabs-0-tab-table';
export const saveNewViewID = 'traces-save-view-action';
export const saveNewViewWithNameID = 'save-view-name-action-button';
const DefaultAttributesExplorerPage = [
'serviceName',
'name',
'durationNano',
'httpMethod',
'responseStatusCode',
];
export const tracesExplorerQueryData = async (
page: Page,
response: any,
): Promise<void> => {
await page.route(`**/${queryRangeApiEndpoint}`, (route) =>
route.fulfill({
status: 200,
contentType: JsonApplicationType,
json: response,
}),
);
};
export const tracesExplorerViewsData = async (page: Page): Promise<void> => {
await page.route(`**/${traceExplorerViewsGetEndpoint}`, (route) =>
route.fulfill({
status: 200,
contentType: JsonApplicationType,
json: traceExplorerViewsSuccessResponse,
}),
);
};
export const tracesExplorerViewsPostData = async (
page: Page,
): Promise<void> => {
await page.route(`**/${traceExplorerViewsPostEndpoint}`, (route) =>
route.fulfill({
status: 200,
contentType: JsonApplicationType,
json: traceExplorerViewsPostSuccessResponse,
}),
);
};
function getAttributeResponseBySearchTerm(
searchTerm: string,
): Record<string, unknown> {
if (searchTerm) {
switch (searchTerm) {
case 'sericeName':
return attributeKeyServiceNameSuccessResponse;
case 'name':
return attributeKeyNameSuccessResponse;
case 'durationNano':
return attributeKeyDurationNanoSuccessResponse;
case 'httpMethod':
return attributeKeyResponseStatusCodeSuccessResponse;
case 'responseStatusCode':
return attributeKeyHttpMethodSuccessResponse;
default:
return {};
}
}
return {};
}
export const getAttributeKeysData = async (
page: Page,
searchTerm: string,
): Promise<void> => {
await page.route(
`**/${attributeKeysApiEndpoint}?**searchText=${searchTerm}**`,
(route) =>
route.fulfill({
status: 200,
json: getAttributeResponseBySearchTerm(searchTerm),
}),
);
};
export const defaultAttributeKeysData = async (page: Page): Promise<void> => {
await Promise.all([
...DefaultAttributesExplorerPage.map((att) =>
getAttributeKeysData(page, att),
),
]);
};