Skip to content

Commit 7ddaa36

Browse files
authored
Merge pull request #8 from networkteam/fix-download-urls
Fix download urls
2 parents 447e054 + f81d1cb commit 7ddaa36

5 files changed

Lines changed: 151 additions & 19 deletions

File tree

acceptance/dashboard_page.go

Lines changed: 73 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package acceptance
22

33
import (
44
"fmt"
5+
"net/url"
56
"testing"
67
"time"
78

@@ -332,26 +333,22 @@ func (dp *DashboardPage) WaitForEventDetails(timeout float64) {
332333
require.NoError(dp.t, err, "failed to wait for event details")
333334
}
334335

335-
// FetchAPI executes a fetch request from the browser context and waits for the response.
336+
// FetchAPI executes a GET request using Playwright's API request context (shares browser cookies).
336337
func (dp *DashboardPage) FetchAPI(path string) {
337338
dp.t.Helper()
338339

339-
// Wait for the fetch to complete fully (read the response body)
340-
_, err := dp.Page.Evaluate(fmt.Sprintf(`fetch('%s').then(r => r.text())`, path))
340+
_, err := dp.Page.Request().Get(dp.BaseURL() + path)
341341
require.NoError(dp.t, err)
342342
}
343343

344-
// FetchAPIWithBody executes a POST fetch request with JSON body from the browser context.
344+
// FetchAPIWithBody executes a POST request with JSON body using Playwright's API request context.
345345
func (dp *DashboardPage) FetchAPIWithBody(path string, body string) {
346346
dp.t.Helper()
347347

348-
js := fmt.Sprintf(`fetch('%s', {
349-
method: 'POST',
350-
headers: {'Content-Type': 'application/json'},
351-
body: JSON.stringify(%s)
352-
})`, path, body)
353-
354-
_, err := dp.Page.Evaluate(js)
348+
_, err := dp.Page.Request().Post(dp.BaseURL()+path, playwright.APIRequestContextPostOptions{
349+
Headers: map[string]string{"Content-Type": "application/json"},
350+
Data: body,
351+
})
355352
require.NoError(dp.t, err)
356353
}
357354

@@ -379,3 +376,68 @@ func (dp *DashboardPage) WaitForUsagePanel(timeout float64) {
379376
})
380377
require.NoError(dp.t, err, "usage panel did not load content")
381378
}
379+
380+
// BaseURL returns the origin (scheme://host) from the session URL.
381+
func (dp *DashboardPage) BaseURL() string {
382+
dp.t.Helper()
383+
384+
parsed, err := url.Parse(dp.SessionURL)
385+
require.NoError(dp.t, err)
386+
return fmt.Sprintf("%s://%s", parsed.Scheme, parsed.Host)
387+
}
388+
389+
// DownloadRequestBody finds the "Download" link in the Request Body section and fetches the content.
390+
// Returns the download URL path (for verification) and the response body content.
391+
func (dp *DashboardPage) DownloadRequestBody() (path string, body []byte, contentType string) {
392+
dp.t.Helper()
393+
394+
// Find the Request section's Body download link
395+
// Structure: div with h3 "Request" > div with h4 "Body" > a[download] "Download"
396+
link := dp.Page.Locator("#event-details").Locator("h3:has-text('Request')").Locator("..").Locator("h4:has-text('Body')").Locator("..").Locator("a[download]:has-text('Download')")
397+
398+
href, err := link.GetAttribute("href")
399+
require.NoError(dp.t, err, "failed to get request body download link href")
400+
require.NotEmpty(dp.t, href, "request body download link should have href")
401+
402+
// Use Playwright's native API request context (shares browser cookies/context)
403+
fullURL := dp.BaseURL() + href
404+
response, err := dp.Page.Request().Get(fullURL)
405+
require.NoError(dp.t, err, "failed to fetch request body download")
406+
require.Equal(dp.t, 200, response.Status(), "request body download should return 200")
407+
408+
bodyBytes, err := response.Body()
409+
require.NoError(dp.t, err, "failed to read request body")
410+
411+
headers := response.Headers()
412+
ct := headers["content-type"]
413+
414+
return href, bodyBytes, ct
415+
}
416+
417+
// DownloadResponseBody finds the "Download" link in the Response Body section and fetches the content.
418+
// Returns the download URL path (for verification) and the response body content.
419+
func (dp *DashboardPage) DownloadResponseBody() (path string, body []byte, contentType string) {
420+
dp.t.Helper()
421+
422+
// Find the Response section's Body download link
423+
// Structure: div with h3 "Response" > div with h4 "Body" > a[download] "Download"
424+
link := dp.Page.Locator("#event-details").Locator("h3:has-text('Response')").Locator("..").Locator("h4:has-text('Body')").Locator("..").Locator("a[download]:has-text('Download')")
425+
426+
href, err := link.GetAttribute("href")
427+
require.NoError(dp.t, err, "failed to get response body download link href")
428+
require.NotEmpty(dp.t, href, "response body download link should have href")
429+
430+
// Use Playwright's native API request context (shares browser cookies/context)
431+
fullURL := dp.BaseURL() + href
432+
response, err := dp.Page.Request().Get(fullURL)
433+
require.NoError(dp.t, err, "failed to fetch response body download")
434+
require.Equal(dp.t, 200, response.Status(), "response body download should return 200")
435+
436+
bodyBytes, err := response.Body()
437+
require.NoError(dp.t, err, "failed to read response body")
438+
439+
headers := response.Headers()
440+
ct := headers["content-type"]
441+
442+
return href, bodyBytes, ct
443+
}

acceptance/dashboard_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,66 @@ func TestNewSessionAfterBrowserClose(t *testing.T) {
231231
assert.Equal(t, 0, dashboard2.GetEventCount(), "new session should have no events")
232232
}
233233

234+
// TestDownloadRequestBody verifies that request body can be downloaded via the download link.
235+
func TestDownloadRequestBody(t *testing.T) {
236+
t.Parallel()
237+
238+
app := NewTestApp(t)
239+
defer app.Close()
240+
241+
pw := NewPlaywrightFixture(t)
242+
defer pw.Close()
243+
244+
ctx := pw.NewContext(t)
245+
defer ctx.Close()
246+
247+
dashboard := NewDashboardPage(t, ctx, app.DevlogURL)
248+
dashboard.StartCapture("global")
249+
250+
// Make a POST request with known body content
251+
dashboard.FetchAPIWithBody("/api/echo", `{"test": "request-body-data"}`)
252+
dashboard.WaitForEventCount(1, 5000)
253+
254+
// Click the event to see details
255+
dashboard.ClickFirstEvent()
256+
dashboard.WaitForEventDetails(5000)
257+
258+
// Download the request body and verify it contains what we sent
259+
_, body, contentType := dashboard.DownloadRequestBody()
260+
assert.Contains(t, contentType, "application/json")
261+
assert.Contains(t, string(body), "request-body-data")
262+
}
263+
264+
// TestDownloadResponseBody verifies that response body can be downloaded via the download link.
265+
func TestDownloadResponseBody(t *testing.T) {
266+
t.Parallel()
267+
268+
app := NewTestApp(t)
269+
defer app.Close()
270+
271+
pw := NewPlaywrightFixture(t)
272+
defer pw.Close()
273+
274+
ctx := pw.NewContext(t)
275+
defer ctx.Close()
276+
277+
dashboard := NewDashboardPage(t, ctx, app.DevlogURL)
278+
dashboard.StartCapture("global")
279+
280+
// Make a GET request - the /api/test endpoint returns {"status":"ok"}
281+
dashboard.FetchAPI("/api/test")
282+
dashboard.WaitForEventCount(1, 5000)
283+
284+
// Click the event to see details
285+
dashboard.ClickFirstEvent()
286+
dashboard.WaitForEventDetails(5000)
287+
288+
// Download the response body and verify it contains the server's response
289+
_, body, contentType := dashboard.DownloadResponseBody()
290+
assert.Contains(t, contentType, "application/json")
291+
assert.Contains(t, string(body), `"status":"ok"`)
292+
}
293+
234294
// TestUsagePanel verifies that the usage panel shows memory and session stats.
235295
func TestUsagePanel(t *testing.T) {
236296
t.Parallel()

dashboard/views/event-details.templ

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ templ HTTPRequestDetails(event *collector.Event, request collector.HTTPClientReq
167167
<div class="flex items-center justify-between mb-2">
168168
<h4 class="text-sm font-semibold">Body</h4>
169169
<a
170-
href={ templ.SafeURL(fmt.Sprintf("%s/download/request-body/%s", MustGetHandlerOptions(ctx).PathPrefix, event.ID)) }
170+
href={ templ.SafeURL(MustGetHandlerOptions(ctx).BuildDownloadRequestBodyURL(event.ID.String())) }
171171
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
172172
download
173173
>
@@ -215,7 +215,7 @@ templ HTTPRequestDetails(event *collector.Event, request collector.HTTPClientReq
215215
<div class="flex items-center justify-between mb-2">
216216
<h4 class="text-sm font-semibold">Body</h4>
217217
<a
218-
href={ templ.SafeURL(fmt.Sprintf("%s/download/response-body/%s", MustGetHandlerOptions(ctx).PathPrefix, event.ID)) }
218+
href={ templ.SafeURL(MustGetHandlerOptions(ctx).BuildDownloadResponseBodyURL(event.ID.String())) }
219219
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
220220
download
221221
>
@@ -314,7 +314,7 @@ templ HTTPServerRequestDetails(event *collector.Event, request collector.HTTPSer
314314
<div class="flex items-center justify-between mb-2">
315315
<h4 class="text-sm font-semibold">Body</h4>
316316
<a
317-
href={ templ.SafeURL(fmt.Sprintf("%s/download/request-body/%s", MustGetHandlerOptions(ctx).PathPrefix, event.ID)) }
317+
href={ templ.SafeURL(MustGetHandlerOptions(ctx).BuildDownloadRequestBodyURL(event.ID.String())) }
318318
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
319319
download
320320
>
@@ -362,7 +362,7 @@ templ HTTPServerRequestDetails(event *collector.Event, request collector.HTTPSer
362362
<div class="flex items-center justify-between mb-2">
363363
<h4 class="text-sm font-semibold">Body</h4>
364364
<a
365-
href={ templ.SafeURL(fmt.Sprintf("%s/download/response-body/%s", MustGetHandlerOptions(ctx).PathPrefix, event.ID)) }
365+
href={ templ.SafeURL(MustGetHandlerOptions(ctx).BuildDownloadResponseBodyURL(event.ID.String())) }
366366
class="text-sm text-blue-600 hover:text-blue-800 flex items-center gap-1"
367367
download
368368
>

dashboard/views/event-details_templ.go

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dashboard/views/helper.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,16 @@ type HandlerOptions struct {
7272
CaptureMode string // "session" or "global"
7373
}
7474

75+
// BuildDownloadRequestBodyURL builds a URL for downloading the request body of an event
76+
func (opts HandlerOptions) BuildDownloadRequestBodyURL(eventID string) string {
77+
return fmt.Sprintf("%s/s/%s/download/request-body/%s", opts.PathPrefix, opts.SessionID, eventID)
78+
}
79+
80+
// BuildDownloadResponseBodyURL builds a URL for downloading the response body of an event
81+
func (opts HandlerOptions) BuildDownloadResponseBodyURL(eventID string) string {
82+
return fmt.Sprintf("%s/s/%s/download/response-body/%s", opts.PathPrefix, opts.SessionID, eventID)
83+
}
84+
7585
// BuildEventDetailURL builds a URL for event detail view, preserving capture state
7686
func (opts HandlerOptions) BuildEventDetailURL(eventID string) string {
7787
base := fmt.Sprintf("%s/s/%s/", opts.PathPrefix, opts.SessionID)

0 commit comments

Comments
 (0)