Skip to content

Commit 0a87cab

Browse files
committed
chore(tests): add e2e tests for tracing
1 parent 64a72be commit 0a87cab

File tree

6 files changed

+394
-0
lines changed

6 files changed

+394
-0
lines changed

test_integrations/phoenix_app/config/config.exs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,14 @@ config :opentelemetry, span_processor: {Sentry.OpenTelemetry.SpanProcessor, []}
6565
config :opentelemetry,
6666
sampler: {Sentry.OpenTelemetry.Sampler, [drop: ["Elixir.Oban.Stager process"]]}
6767

68+
# Configure OpenTelemetry to use Sentry propagator for distributed tracing
69+
config :opentelemetry,
70+
text_map_propagators: [
71+
:trace_context,
72+
:baggage,
73+
Sentry.OpenTelemetry.Propagator
74+
]
75+
6876
# Import environment specific config. This must remain at the bottom
6977
# of this file so it overrides the configuration defined above.
7078
import_config "#{config_env()}.exs"

test_integrations/phoenix_app/lib/phoenix_app_web/controllers/page_controller.ex

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,35 @@ defmodule PhoenixAppWeb.PageController do
4848

4949
render(conn, :home, layout: false)
5050
end
51+
52+
# E2E tracing test endpoints
53+
54+
def api_error(_conn, _params) do
55+
raise ArithmeticError, "bad argument in arithmetic expression"
56+
end
57+
58+
def health(conn, _params) do
59+
json(conn, %{status: "ok"})
60+
end
61+
62+
def api_data(conn, _params) do
63+
Tracer.with_span "fetch_data" do
64+
users = Repo.all(User)
65+
66+
Tracer.with_span "process_data" do
67+
user_count = length(users)
68+
69+
first_user = Repo.get(User, 1)
70+
71+
json(conn, %{
72+
message: "Data fetched successfully",
73+
data: %{
74+
user_count: user_count,
75+
first_user: if(first_user, do: first_user.name, else: nil),
76+
timestamp: DateTime.utc_now() |> DateTime.to_iso8601()
77+
}
78+
})
79+
end
80+
end
81+
end
5182
end

test_integrations/phoenix_app/lib/phoenix_app_web/endpoint.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ defmodule PhoenixAppWeb.Endpoint do
4444
plug Plug.RequestId
4545
plug Plug.Telemetry, event_prefix: [:phoenix, :endpoint]
4646

47+
plug :cors
48+
4749
plug Plug.Parsers,
4850
parsers: [:urlencoded, :multipart, :json],
4951
pass: ["*/*"],
@@ -53,4 +55,22 @@ defmodule PhoenixAppWeb.Endpoint do
5355
plug Plug.Head
5456
plug Plug.Session, @session_options
5557
plug PhoenixAppWeb.Router
58+
59+
# CORS plug for e2e tests - allows the Svelte frontend to make
60+
# cross-origin requests to the Phoenix backend
61+
defp cors(conn, _opts) do
62+
conn
63+
|> put_resp_header("access-control-allow-origin", "*")
64+
|> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
65+
|> put_resp_header("access-control-allow-headers", "content-type, sentry-trace, baggage")
66+
|> handle_preflight()
67+
end
68+
69+
defp handle_preflight(%{method: "OPTIONS"} = conn) do
70+
conn
71+
|> send_resp(200, "")
72+
|> halt()
73+
end
74+
75+
defp handle_preflight(conn), do: conn
5676
end

test_integrations/phoenix_app/lib/phoenix_app_web/router.ex

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ defmodule PhoenixAppWeb.Router do
1212

1313
pipeline :api do
1414
plug :accepts, ["json"]
15+
plug :put_cors_headers
16+
end
17+
18+
defp put_cors_headers(conn, _opts) do
19+
conn
20+
|> put_resp_header("access-control-allow-origin", "*")
21+
|> put_resp_header("access-control-allow-methods", "GET, POST, PUT, DELETE, OPTIONS")
22+
|> put_resp_header("access-control-allow-headers", "content-type, authorization, sentry-trace, baggage")
1523
end
1624

1725
scope "/", PhoenixAppWeb do
@@ -32,6 +40,15 @@ defmodule PhoenixAppWeb.Router do
3240
live "/users/:id/show/edit", UserLive.Show, :edit
3341
end
3442

43+
# For e2e DT tests with a front-end app
44+
scope "/", PhoenixAppWeb do
45+
pipe_through :api
46+
47+
get "/error", PageController, :api_error
48+
get "/health", PageController, :health
49+
get "/api/data", PageController, :api_data
50+
end
51+
3552
# Other scopes may use custom stacks.
3653
# scope "/api", PhoenixAppWeb do
3754
# pipe_through :api
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { expect } from "@playwright/test";
2+
import fs from "fs";
3+
import path from "path";
4+
5+
const DEBUG_LOG_PATH = path.join(
6+
process.cwd(),
7+
"..",
8+
"phoenix_app",
9+
"tmp",
10+
"sentry_debug_events.log"
11+
);
12+
13+
export interface SentryEvent {
14+
type?: string;
15+
transaction?: string;
16+
exception?: Array<{
17+
type: string;
18+
value: string;
19+
}>;
20+
contexts?: {
21+
trace?: {
22+
trace_id?: string;
23+
span_id?: string;
24+
parent_span_id?: string;
25+
op?: string;
26+
data?: Record<string, any>;
27+
};
28+
};
29+
_meta?: {
30+
dsc?: {
31+
sample_rand?: string;
32+
};
33+
};
34+
request?: {
35+
headers?: Record<string, string>;
36+
};
37+
}
38+
39+
export interface SentryEnvelope {
40+
headers: {
41+
trace?: {
42+
trace_id?: string;
43+
sample_rate?: string;
44+
sample_rand?: string;
45+
sampled?: string;
46+
environment?: string;
47+
public_key?: string;
48+
[key: string]: any;
49+
};
50+
};
51+
items: SentryEvent[];
52+
}
53+
54+
export interface LoggedEvents {
55+
events: SentryEvent[];
56+
envelopes: SentryEnvelope[];
57+
event_count: number;
58+
}
59+
60+
export function getLoggedEvents(): LoggedEvents {
61+
const events: SentryEvent[] = [];
62+
const envelopes: SentryEnvelope[] = [];
63+
64+
if (!fs.existsSync(DEBUG_LOG_PATH)) {
65+
return { events: [], envelopes: [], event_count: 0 };
66+
}
67+
68+
const content = fs.readFileSync(DEBUG_LOG_PATH, "utf-8");
69+
const lines = content
70+
.trim()
71+
.split("\n")
72+
.filter((line) => line.trim());
73+
74+
for (const line of lines) {
75+
try {
76+
const data = JSON.parse(line);
77+
78+
// Check if it's an envelope format
79+
if (data.headers) {
80+
envelopes.push(data as SentryEnvelope);
81+
if (data.items) {
82+
events.push(...data.items);
83+
}
84+
} else {
85+
// Individual event
86+
events.push(data as SentryEvent);
87+
}
88+
} catch (e) {
89+
// Skip malformed lines
90+
}
91+
}
92+
93+
return { events, envelopes, event_count: events.length };
94+
}
95+
96+
export function clearLoggedEvents(): void {
97+
if (fs.existsSync(DEBUG_LOG_PATH)) {
98+
fs.writeFileSync(DEBUG_LOG_PATH, "");
99+
}
100+
}

0 commit comments

Comments
 (0)