Skip to content

Commit 75ff28d

Browse files
authored
chore: Use local clickhouse instance for playwright tests (#1711)
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
1 parent cfba838 commit 75ff28d

27 files changed

+1672
-494
lines changed

.github/workflows/main.yml

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -124,17 +124,6 @@ jobs:
124124
name: E2E Tests - Shard ${{ matrix.shard }}
125125
runs-on: ubuntu-24.04
126126
timeout-minutes: 15
127-
services:
128-
mongodb:
129-
image: mongo:5.0.32-focal
130-
options: >-
131-
--health-cmd "mongosh --quiet --eval 'db.adminCommand({ping: 1});
132-
db.getSiblingDB(\"test\").test.insertOne({_id: \"hc\"});
133-
db.getSiblingDB(\"test\").test.deleteOne({_id: \"hc\"})'"
134-
--health-interval 10s --health-timeout 5s --health-retries 10
135-
--health-start-period 10s
136-
ports:
137-
- 27017:27017
138127
permissions:
139128
contents: read
140129
pull-requests: write
@@ -163,15 +152,43 @@ jobs:
163152
- name: Install Playwright browsers
164153
run: cd packages/app && npx playwright install --with-deps chromium
165154

155+
- name: Start E2E Docker Compose
156+
run: |
157+
docker compose -p e2e -f packages/app/tests/e2e/docker-compose.yml up -d
158+
echo "Waiting for MongoDB..."
159+
for i in $(seq 1 30); do
160+
if docker compose -p e2e -f packages/app/tests/e2e/docker-compose.yml exec -T db mongosh --port 29998 --quiet --eval "db.adminCommand({ping:1})" >/dev/null 2>&1; then
161+
echo "MongoDB is ready"
162+
break
163+
fi
164+
if [ "$i" -eq 30 ]; then
165+
echo "MongoDB failed to become ready after 30 seconds"
166+
exit 1
167+
fi
168+
echo "Waiting for MongoDB... ($i/30)"
169+
sleep 1
170+
done
171+
echo "Waiting for ClickHouse..."
172+
for i in $(seq 1 60); do
173+
if curl -sf http://localhost:8123/ping >/dev/null 2>&1; then
174+
echo "ClickHouse is ready"
175+
break
176+
fi
177+
if [ "$i" -eq 60 ]; then
178+
echo "ClickHouse failed to become ready after 60 seconds"
179+
exit 1
180+
fi
181+
echo "Waiting for ClickHouse... ($i/60)"
182+
sleep 1
183+
done
184+
166185
- name: Run Playwright tests (full-stack mode)
167-
# MongoDB service health check ensures it's ready before this step runs
168-
# Note: Tests use ClickHouse demo instance (otel_demo with empty password)
169-
# This is intentionally public - it's ClickHouse's read-only demo instance
186+
# E2E uses local docker-compose (MongoDB on 29998, ClickHouse on 8123)
170187
env:
171188
E2E_FULLSTACK: 'true'
172189
E2E_UNIQUE_USER: 'true'
173190
E2E_API_HEALTH_CHECK_MAX_RETRIES: '60'
174-
MONGO_URI: mongodb://localhost:27017/hyperdx-e2e
191+
MONGO_URI: mongodb://localhost:29998/hyperdx-e2e
175192
run: |
176193
cd packages/app
177194
yarn test:e2e --shard=${{ matrix.shard }}/4
@@ -192,6 +209,12 @@ jobs:
192209
path: packages/app/test-results/
193210
retention-days: 30
194211

212+
- name: Stop E2E containers
213+
if: always()
214+
run:
215+
docker compose -p e2e -f packages/app/tests/e2e/docker-compose.yml
216+
down -v
217+
195218
e2e-report:
196219
name: End-to-End Tests
197220
if: always()

Makefile

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,10 @@ ci-unit:
6767

6868
.PHONY: e2e
6969
e2e:
70-
@# Run full-stack by default (MongoDB + API + demo ClickHouse)
71-
@# Use 'make e2e local=true' to skip MongoDB and run local mode only
72-
@# Use 'make e2e ui=true' to run tests with UI
73-
if [ "$(local)" = "true" ]; then set -- "$$@" --local; fi; \
74-
if [ -n "$(tags)" ]; then set -- "$$@" --tags "$(tags)"; fi; \
75-
if [ "$(ui)" = "true" ]; then set -- "$$@" --ui; fi; \
76-
./scripts/test-e2e.sh "$$@"
70+
# Run full-stack by default (MongoDB + API + local Docker ClickHouse)
71+
# For more control (--ui, --last-failed, --headed, etc), call the script directly:
72+
# ./scripts/test-e2e.sh --ui --last-failed
73+
./scripts/test-e2e.sh
7774

7875

7976

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# E2E-specific database initialization script
5+
# Creates tables with e2e_ prefix to avoid collision with local dev data
6+
7+
# We don't have a JSON schema yet, so let's let the collector create the tables
8+
if [ "$BETA_CH_OTEL_JSON_SCHEMA_ENABLED" = "true" ]; then
9+
exit 0
10+
fi
11+
12+
DATABASE=${HYPERDX_OTEL_EXPORTER_CLICKHOUSE_DATABASE:-default}
13+
14+
clickhouse client -n <<EOFSQL
15+
CREATE DATABASE IF NOT EXISTS ${DATABASE};
16+
17+
CREATE TABLE IF NOT EXISTS ${DATABASE}.e2e_otel_logs
18+
(
19+
\`Timestamp\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
20+
\`TimestampTime\` DateTime DEFAULT toDateTime(Timestamp),
21+
\`TraceId\` String CODEC(ZSTD(1)),
22+
\`SpanId\` String CODEC(ZSTD(1)),
23+
\`TraceFlags\` UInt8,
24+
\`SeverityText\` LowCardinality(String) CODEC(ZSTD(1)),
25+
\`SeverityNumber\` UInt8,
26+
\`ServiceName\` LowCardinality(String) CODEC(ZSTD(1)),
27+
\`Body\` String CODEC(ZSTD(1)),
28+
\`ResourceSchemaUrl\` LowCardinality(String) CODEC(ZSTD(1)),
29+
\`ResourceAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
30+
\`ScopeSchemaUrl\` LowCardinality(String) CODEC(ZSTD(1)),
31+
\`ScopeName\` String CODEC(ZSTD(1)),
32+
\`ScopeVersion\` LowCardinality(String) CODEC(ZSTD(1)),
33+
\`ScopeAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
34+
\`LogAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
35+
\`__hdx_materialized_k8s.cluster.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.cluster.name'] CODEC(ZSTD(1)),
36+
\`__hdx_materialized_k8s.container.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.container.name'] CODEC(ZSTD(1)),
37+
\`__hdx_materialized_k8s.deployment.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.deployment.name'] CODEC(ZSTD(1)),
38+
\`__hdx_materialized_k8s.namespace.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.namespace.name'] CODEC(ZSTD(1)),
39+
\`__hdx_materialized_k8s.node.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.node.name'] CODEC(ZSTD(1)),
40+
\`__hdx_materialized_k8s.pod.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.name'] CODEC(ZSTD(1)),
41+
\`__hdx_materialized_k8s.pod.uid\` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.uid'] CODEC(ZSTD(1)),
42+
\`__hdx_materialized_deployment.environment.name\` LowCardinality(String) MATERIALIZED ResourceAttributes['deployment.environment.name'] CODEC(ZSTD(1)),
43+
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
44+
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
45+
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
46+
INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
47+
INDEX idx_scope_attr_value mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
48+
INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
49+
INDEX idx_log_attr_value mapValues(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
50+
INDEX idx_lower_body lower(Body) TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 8
51+
)
52+
ENGINE = MergeTree
53+
PARTITION BY toDate(TimestampTime)
54+
PRIMARY KEY (ServiceName, TimestampTime)
55+
ORDER BY (ServiceName, TimestampTime, Timestamp)
56+
TTL TimestampTime + toIntervalDay(30)
57+
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1;
58+
59+
CREATE TABLE IF NOT EXISTS ${DATABASE}.e2e_otel_traces
60+
(
61+
\`Timestamp\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
62+
\`TraceId\` String CODEC(ZSTD(1)),
63+
\`SpanId\` String CODEC(ZSTD(1)),
64+
\`ParentSpanId\` String CODEC(ZSTD(1)),
65+
\`TraceState\` String CODEC(ZSTD(1)),
66+
\`SpanName\` LowCardinality(String) CODEC(ZSTD(1)),
67+
\`SpanKind\` LowCardinality(String) CODEC(ZSTD(1)),
68+
\`ServiceName\` LowCardinality(String) CODEC(ZSTD(1)),
69+
\`ResourceAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
70+
\`ScopeName\` String CODEC(ZSTD(1)),
71+
\`ScopeVersion\` String CODEC(ZSTD(1)),
72+
\`SpanAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
73+
\`Duration\` UInt64 CODEC(ZSTD(1)),
74+
\`StatusCode\` LowCardinality(String) CODEC(ZSTD(1)),
75+
\`StatusMessage\` String CODEC(ZSTD(1)),
76+
\`Events.Timestamp\` Array(DateTime64(9)) CODEC(ZSTD(1)),
77+
\`Events.Name\` Array(LowCardinality(String)) CODEC(ZSTD(1)),
78+
\`Events.Attributes\` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
79+
\`Links.TraceId\` Array(String) CODEC(ZSTD(1)),
80+
\`Links.SpanId\` Array(String) CODEC(ZSTD(1)),
81+
\`Links.TraceState\` Array(String) CODEC(ZSTD(1)),
82+
\`Links.Attributes\` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
83+
\`__hdx_materialized_rum.sessionId\` String MATERIALIZED ResourceAttributes['rum.sessionId'] CODEC(ZSTD(1)),
84+
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
85+
INDEX idx_rum_session_id __hdx_materialized_rum.sessionId TYPE bloom_filter(0.001) GRANULARITY 1,
86+
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
87+
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
88+
INDEX idx_span_attr_key mapKeys(SpanAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
89+
INDEX idx_span_attr_value mapValues(SpanAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
90+
INDEX idx_duration Duration TYPE minmax GRANULARITY 1,
91+
INDEX idx_lower_span_name lower(SpanName) TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 8
92+
)
93+
ENGINE = MergeTree
94+
PARTITION BY toDate(Timestamp)
95+
ORDER BY (ServiceName, SpanName, toDateTime(Timestamp))
96+
TTL toDate(Timestamp) + toIntervalDay(30)
97+
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1;
98+
99+
CREATE TABLE ${DATABASE}.e2e_hyperdx_sessions
100+
(
101+
\`Timestamp\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
102+
\`TimestampTime\` DateTime DEFAULT toDateTime(Timestamp),
103+
\`TraceId\` String CODEC(ZSTD(1)),
104+
\`SpanId\` String CODEC(ZSTD(1)),
105+
\`TraceFlags\` UInt8,
106+
\`SeverityText\` LowCardinality(String) CODEC(ZSTD(1)),
107+
\`SeverityNumber\` UInt8,
108+
\`ServiceName\` LowCardinality(String) CODEC(ZSTD(1)),
109+
\`Body\` String CODEC(ZSTD(1)),
110+
\`ResourceSchemaUrl\` LowCardinality(String) CODEC(ZSTD(1)),
111+
\`ResourceAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
112+
\`ScopeSchemaUrl\` LowCardinality(String) CODEC(ZSTD(1)),
113+
\`ScopeName\` String CODEC(ZSTD(1)),
114+
\`ScopeVersion\` LowCardinality(String) CODEC(ZSTD(1)),
115+
\`ScopeAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
116+
\`LogAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
117+
\`__hdx_materialized_rum.sessionId\` String MATERIALIZED ResourceAttributes['rum.sessionId'] CODEC(ZSTD(1)),
118+
\`__hdx_materialized_type\` LowCardinality(String) MATERIALIZED toString(simpleJSONExtractInt(Body, 'type')) CODEC(ZSTD(1)),
119+
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
120+
INDEX idx_rum_session_id __hdx_materialized_rum.sessionId TYPE bloom_filter(0.001) GRANULARITY 1,
121+
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
122+
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
123+
INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
124+
INDEX idx_scope_attr_value mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
125+
INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
126+
INDEX idx_log_attr_value mapValues(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
127+
INDEX idx_body Body TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 8
128+
)
129+
ENGINE = MergeTree
130+
PARTITION BY toDate(TimestampTime)
131+
PRIMARY KEY (ServiceName, TimestampTime)
132+
ORDER BY (ServiceName, TimestampTime, Timestamp)
133+
TTL TimestampTime + toIntervalDay(30)
134+
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1;
135+
136+
CREATE TABLE IF NOT EXISTS ${DATABASE}.e2e_otel_metrics_gauge
137+
(
138+
\`ResourceAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
139+
\`ResourceSchemaUrl\` String CODEC(ZSTD(1)),
140+
\`ScopeName\` String CODEC(ZSTD(1)),
141+
\`ScopeVersion\` String CODEC(ZSTD(1)),
142+
\`ScopeAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
143+
\`ScopeDroppedAttrCount\` UInt32 CODEC(ZSTD(1)),
144+
\`ScopeSchemaUrl\` String CODEC(ZSTD(1)),
145+
\`ServiceName\` LowCardinality(String) CODEC(ZSTD(1)),
146+
\`MetricName\` String CODEC(ZSTD(1)),
147+
\`MetricDescription\` String CODEC(ZSTD(1)),
148+
\`MetricUnit\` String CODEC(ZSTD(1)),
149+
\`Attributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
150+
\`StartTimeUnix\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
151+
\`TimeUnix\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
152+
\`Value\` Float64 CODEC(ZSTD(1)),
153+
\`Flags\` UInt32 CODEC(ZSTD(1)),
154+
\`Exemplars.FilteredAttributes\` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
155+
\`Exemplars.TimeUnix\` Array(DateTime64(9)) CODEC(ZSTD(1)),
156+
\`Exemplars.Value\` Array(Float64) CODEC(ZSTD(1)),
157+
\`Exemplars.SpanId\` Array(String) CODEC(ZSTD(1)),
158+
\`Exemplars.TraceId\` Array(String) CODEC(ZSTD(1)),
159+
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
160+
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
161+
INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
162+
INDEX idx_scope_attr_value mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
163+
INDEX idx_attr_key mapKeys(Attributes) TYPE bloom_filter(0.01) GRANULARITY 1,
164+
INDEX idx_attr_value mapValues(Attributes) TYPE bloom_filter(0.01) GRANULARITY 1
165+
)
166+
ENGINE = MergeTree
167+
PARTITION BY toDate(TimeUnix)
168+
ORDER BY (ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix))
169+
TTL toDate(TimeUnix) + toIntervalDay(30)
170+
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1;
171+
172+
CREATE TABLE IF NOT EXISTS ${DATABASE}.e2e_otel_metrics_sum
173+
(
174+
\`ResourceAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
175+
\`ResourceSchemaUrl\` String CODEC(ZSTD(1)),
176+
\`ScopeName\` String CODEC(ZSTD(1)),
177+
\`ScopeVersion\` String CODEC(ZSTD(1)),
178+
\`ScopeAttributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
179+
\`ScopeDroppedAttrCount\` UInt32 CODEC(ZSTD(1)),
180+
\`ScopeSchemaUrl\` String CODEC(ZSTD(1)),
181+
\`ServiceName\` LowCardinality(String) CODEC(ZSTD(1)),
182+
\`MetricName\` String CODEC(ZSTD(1)),
183+
\`MetricDescription\` String CODEC(ZSTD(1)),
184+
\`MetricUnit\` String CODEC(ZSTD(1)),
185+
\`Attributes\` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
186+
\`StartTimeUnix\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
187+
\`TimeUnix\` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
188+
\`Value\` Float64 CODEC(ZSTD(1)),
189+
\`Flags\` UInt32 CODEC(ZSTD(1)),
190+
\`AggregationTemporality\` Int32 CODEC(ZSTD(1)),
191+
\`IsMonotonic\` Bool CODEC(Delta(1), ZSTD(1)),
192+
\`Exemplars.FilteredAttributes\` Array(Map(LowCardinality(String), String)) CODEC(ZSTD(1)),
193+
\`Exemplars.TimeUnix\` Array(DateTime64(9)) CODEC(ZSTD(1)),
194+
\`Exemplars.Value\` Array(Float64) CODEC(ZSTD(1)),
195+
\`Exemplars.SpanId\` Array(String) CODEC(ZSTD(1)),
196+
\`Exemplars.TraceId\` Array(String) CODEC(ZSTD(1)),
197+
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
198+
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
199+
INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
200+
INDEX idx_scope_attr_value mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
201+
INDEX idx_attr_key mapKeys(Attributes) TYPE bloom_filter(0.01) GRANULARITY 1,
202+
INDEX idx_attr_value mapValues(Attributes) TYPE bloom_filter(0.01) GRANULARITY 1
203+
)
204+
ENGINE = MergeTree
205+
PARTITION BY toDate(TimeUnix)
206+
ORDER BY (ServiceName, MetricName, Attributes, toUnixTimestamp64Nano(TimeUnix))
207+
TTL toDate(TimeUnix) + toIntervalDay(30)
208+
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1
209+
EOFSQL

0 commit comments

Comments
 (0)