mirror of
https://github.com/hyperdxio/hyperdx
synced 2026-04-21 13:37:15 +00:00
TLDR: This PR changes playwright full-stack tests to run against a local clickhouse instance (with seeded data) instead of relying on the clickhouse demo server, which can be unpredictable at times. This workflow allows us to fully control the data to make tests more predictable. This PR: * Adds local CH instance to the e2e dockerfile * Adds a schema creation script * Adds a data seeding script * Updates playwright config * Updates various tests to change hardcoded fields, metrics, or areas relying on play demo data * Updates github workflow to use the dockerfile instead of separate services * Runs against a local clickhouse instead of the demo server Fixes: HDX-3193
182 lines
4.9 KiB
TypeScript
182 lines
4.9 KiB
TypeScript
/**
|
|
* ChartEditorComponent - Reusable component for chart/tile editor
|
|
* Used for creating and configuring dashboard tiles and chart explorer
|
|
*/
|
|
import { Locator, Page } from '@playwright/test';
|
|
|
|
import { getSqlEditor } from '../utils/locators';
|
|
|
|
export class ChartEditorComponent {
|
|
readonly page: Page;
|
|
private readonly chartNameInput: Locator;
|
|
private readonly sourceSelector: Locator;
|
|
private readonly metricSelector: Locator;
|
|
private readonly runQueryButton: Locator;
|
|
private readonly saveButton: Locator;
|
|
|
|
constructor(page: Page) {
|
|
this.page = page;
|
|
this.chartNameInput = page.getByTestId('chart-name-input');
|
|
this.sourceSelector = page.getByTestId('source-selector');
|
|
this.metricSelector = page.getByTestId('metric-name-selector');
|
|
this.runQueryButton = page.getByTestId('chart-run-query-button');
|
|
this.saveButton = page.getByTestId('chart-save-button');
|
|
}
|
|
|
|
/**
|
|
* Set chart name
|
|
*/
|
|
async setChartName(name: string) {
|
|
await this.chartNameInput.fill(name);
|
|
}
|
|
|
|
/**
|
|
* Set group by expression
|
|
*/
|
|
async setGroupBy(expression: string) {
|
|
const groupByInput = getSqlEditor(this.page, 'SQL Columns');
|
|
await groupByInput.click();
|
|
await this.page.keyboard.type(expression);
|
|
}
|
|
|
|
/**
|
|
* Select a data source
|
|
*/
|
|
async selectSource(sourceName: string) {
|
|
await this.sourceSelector.click();
|
|
// Use getByRole for more reliable selection
|
|
const sourceOption = this.page.getByRole('option', { name: sourceName });
|
|
if ((await sourceOption.getAttribute('data-combobox-active')) != 'true') {
|
|
await sourceOption.click({ timeout: 5000 });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Select a metric by name
|
|
*/
|
|
async selectMetric(metricName: string, metricValue?: string) {
|
|
// Wait for metric selector to be visible
|
|
await this.metricSelector.waitFor({ state: 'visible', timeout: 5000 });
|
|
|
|
// Click to open dropdown
|
|
await this.metricSelector.click();
|
|
|
|
// Type to filter
|
|
await this.metricSelector.fill(metricName);
|
|
|
|
// If a specific metric value is provided, wait for and click it
|
|
if (metricValue) {
|
|
// Use attribute selector for combobox options
|
|
const targetMetricOption = this.page.locator(
|
|
`[data-combobox-option="true"][value="${metricValue}"]`,
|
|
);
|
|
await targetMetricOption.waitFor({ state: 'visible', timeout: 5000 });
|
|
await targetMetricOption.click({ timeout: 5000 });
|
|
} else {
|
|
// Otherwise just press Enter to select the first match
|
|
await this.page.keyboard.press('Enter');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the query and wait for it to complete
|
|
*/
|
|
async runQuery() {
|
|
await this.runQueryButton.click();
|
|
// need to wait for the recharts graph to render
|
|
await this.page
|
|
.locator('.recharts-responsive-container')
|
|
.waitFor({ state: 'visible', timeout: 10000 });
|
|
}
|
|
|
|
/**
|
|
* Save the chart/tile and wait for modal to close
|
|
*/
|
|
async save() {
|
|
await this.saveButton.click();
|
|
// Wait for save button to disappear (modal closes)
|
|
await this.saveButton.waitFor({ state: 'hidden', timeout: 2000 });
|
|
}
|
|
|
|
/**
|
|
* Wait for chart editor data to load (sources, metrics, etc.)
|
|
*/
|
|
async waitForDataToLoad() {
|
|
await this.runQueryButton.waitFor({ state: 'visible', timeout: 2000 });
|
|
await this.page.waitForLoadState('networkidle');
|
|
}
|
|
|
|
/**
|
|
* Complete workflow: create a basic chart with name and save
|
|
*/
|
|
async createBasicChart(name: string) {
|
|
// Wait for data sources to load before interacting
|
|
await this.waitForDataToLoad();
|
|
await this.setChartName(name);
|
|
await this.runQuery();
|
|
await this.save();
|
|
}
|
|
|
|
/**
|
|
* Complete workflow: create a chart with specific source and metric
|
|
*/
|
|
async createChartWithMetric(
|
|
chartName: string,
|
|
sourceName: string,
|
|
metricName: string,
|
|
metricValue?: string,
|
|
) {
|
|
// Wait for data sources to load before interacting
|
|
await this.waitForDataToLoad();
|
|
await this.selectSource(sourceName);
|
|
await this.selectMetric(metricName, metricValue);
|
|
await this.runQuery();
|
|
await this.save();
|
|
}
|
|
|
|
/**
|
|
* Complete workflow: create a chart with specific source and metric
|
|
*/
|
|
async createTable({
|
|
chartName,
|
|
sourceName,
|
|
groupBy,
|
|
}: {
|
|
chartName: string;
|
|
sourceName: string;
|
|
groupBy?: string;
|
|
}) {
|
|
// Wait for data sources to load before interacting
|
|
await this.waitForDataToLoad();
|
|
|
|
const tableButton = this.page.getByRole('tab', { name: 'Table' });
|
|
await tableButton.click();
|
|
|
|
await this.setChartName(chartName);
|
|
await this.selectSource(sourceName);
|
|
if (groupBy) await this.setGroupBy(groupBy);
|
|
await this.save();
|
|
}
|
|
|
|
// Getters for assertions
|
|
|
|
get nameInput() {
|
|
return this.chartNameInput;
|
|
}
|
|
|
|
get source() {
|
|
return this.sourceSelector;
|
|
}
|
|
|
|
get metric() {
|
|
return this.metricSelector;
|
|
}
|
|
|
|
get runButton() {
|
|
return this.runQueryButton;
|
|
}
|
|
|
|
get saveBtn() {
|
|
return this.saveButton;
|
|
}
|
|
}
|