mirror of
https://git.mirrors.martin98.com/https://github.com/SigNoz/signoz
synced 2025-08-12 07:28:59 +08:00
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:
parent
29bfdb8909
commit
d091d90d66
@ -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}
|
||||
|
@ -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>
|
||||
|
@ -34,6 +34,7 @@ function NewExplorerCTA(): JSX.Element | null {
|
||||
icon={<CompassOutlined />}
|
||||
onClick={onClickHandler}
|
||||
danger
|
||||
data-testid="newExplorerCTA"
|
||||
type="primary"
|
||||
>
|
||||
{buttonText[location.pathname]}
|
||||
|
@ -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>;
|
||||
},
|
||||
})) || [];
|
||||
|
||||
|
@ -42,6 +42,7 @@ export const columns: ColumnsType<ListItem['data']> = [
|
||||
href={generatePath(ROUTES.TRACE_DETAIL, {
|
||||
id: traceID,
|
||||
})}
|
||||
data-testid="trace-id"
|
||||
>
|
||||
{traceID}
|
||||
</Typography.Link>
|
||||
|
14
frontend/tests/fixtures/api/traces/attributeKeys200.json
vendored
Normal file
14
frontend/tests/fixtures/api/traces/attributeKeys200.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"attributeKeys": [
|
||||
{
|
||||
"key": "serviceName",
|
||||
"dataType": "string",
|
||||
"type": "tag",
|
||||
"isColumn": true,
|
||||
"isJSON": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
14
frontend/tests/fixtures/api/traces/attributeKeysDurationNano200.json
vendored
Normal file
14
frontend/tests/fixtures/api/traces/attributeKeysDurationNano200.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"attributeKeys": [
|
||||
{
|
||||
"key": "durationNano",
|
||||
"dataType": "float64",
|
||||
"type": "tag",
|
||||
"isColumn": true,
|
||||
"isJSON": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
21
frontend/tests/fixtures/api/traces/attributeKeysHttpMethod200.json
vendored
Normal file
21
frontend/tests/fixtures/api/traces/attributeKeysHttpMethod200.json
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
56
frontend/tests/fixtures/api/traces/attributeKeysName200.json
vendored
Normal file
56
frontend/tests/fixtures/api/traces/attributeKeysName200.json
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
14
frontend/tests/fixtures/api/traces/attributeKeysResponseStatusCode200.json
vendored
Normal file
14
frontend/tests/fixtures/api/traces/attributeKeysResponseStatusCode200.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"attributeKeys": [
|
||||
{
|
||||
"key": "responseStatusCode",
|
||||
"dataType": "string",
|
||||
"type": "tag",
|
||||
"isColumn": true,
|
||||
"isJSON": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
14
frontend/tests/fixtures/api/traces/attributeKeysServiceName200.json
vendored
Normal file
14
frontend/tests/fixtures/api/traces/attributeKeysServiceName200.json
vendored
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"attributeKeys": [
|
||||
{
|
||||
"key": "serviceName",
|
||||
"dataType": "string",
|
||||
"type": "tag",
|
||||
"isColumn": true,
|
||||
"isJSON": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
26
frontend/tests/fixtures/api/traces/queryRange200.json
vendored
Normal file
26
frontend/tests/fixtures/api/traces/queryRange200.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
4
frontend/tests/fixtures/api/traces/traceExplorerViewPost200.json
vendored
Normal file
4
frontend/tests/fixtures/api/traces/traceExplorerViewPost200.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": "336368a4-dba7-4d65-9f91-142a355edb23"
|
||||
}
|
52
frontend/tests/fixtures/api/traces/traceExplorerViews200.json
vendored
Normal file
52
frontend/tests/fixtures/api/traces/traceExplorerViews200.json
vendored
Normal 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": ""
|
||||
}
|
||||
]
|
||||
}
|
24
frontend/tests/fixtures/api/traces/tracesRange200.json
vendored
Normal file
24
frontend/tests/fixtures/api/traces/tracesRange200.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
24
frontend/tests/fixtures/api/traces/tracesTableView200.json
vendored
Normal file
24
frontend/tests/fixtures/api/traces/tracesTableView200.json
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"status": "success",
|
||||
"data": {
|
||||
"resultType": "",
|
||||
"result": [
|
||||
{
|
||||
"queryName": "A",
|
||||
"series": [
|
||||
{
|
||||
"labels": {},
|
||||
"labelsArray": null,
|
||||
"values": [
|
||||
{
|
||||
"timestamp": 1700161420000,
|
||||
"value": "85784"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"list": null
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
118
frontend/tests/traces/newTracesExplorer.spec.ts
Normal file
118
frontend/tests/traces/newTracesExplorer.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
116
frontend/tests/traces/utils.ts
Normal file
116
frontend/tests/traces/utils.ts
Normal 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),
|
||||
),
|
||||
]);
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user