rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1//go:build browser
2
3// The whole site in a real Chromium: a reader opens the landing page,
4// its Arabic twin, the design system and one example, and the browser
5// asks the site's own origin for everything it needs, and gets it.
6package www_test
7
8import (
9 "context"
10 "fmt"
11 "net/http"
12 "regexp"
13 "strings"
14 "sync"
15 "testing"
16 "time"
17
18 "github.com/chromedp/cdproto/network"
19 "github.com/chromedp/chromedp"
20)
21
22// traffic is what the browser asked for while a page loaded: every
23// request's URL, and every response's status with its URL, from the
24// network events of the page and its frames.
25type traffic struct {
26 mu sync.Mutex
27 requests []string
28 responses []string
29}
30
31// listen records the browser's traffic from now on.
32func listen(ctx context.Context) *traffic {
33 tr := &traffic{}
34 chromedp.ListenTarget(ctx, func(ev any) {
35 switch e := ev.(type) {
36 case *network.EventRequestWillBeSent:
37 tr.mu.Lock()
38 tr.requests = append(tr.requests, e.Request.URL)
39 tr.mu.Unlock()
40 case *network.EventResponseReceived:
41 tr.mu.Lock()
42 tr.responses = append(tr.responses, fmt.Sprintf("%d %s", int(e.Response.Status), e.Response.URL))
43 tr.mu.Unlock()
44 }
45 })
46 return tr
47}
48
49// take hands back what was recorded and starts afresh, for the next
50// page.
51func (tr *traffic) take() (requests, responses []string) {
52 tr.mu.Lock()
53 defer tr.mu.Unlock()
54 requests, responses = tr.requests, tr.responses
55 tr.requests, tr.responses = nil, nil
56 return requests, responses
57}
58
59// loadedJS is true once the page and every frame in it have loaded: a
60// gallery page's frames are same-origin, so their documents can be
61// read, and a frame that is still about:blank or loading is not done.
62const loadedJS = `document.readyState === "complete" && [...document.querySelectorAll("iframe")].every(f => f.contentDocument && f.contentDocument.location.href !== "about:blank" && f.contentDocument.readyState === "complete")`
63
64// TestTheSiteServesFromItsOwnOrigin: the landing page, the Arabic
65// landing page, the design system's index and one example page each
66// load through the edge's rules with every request going to the
67// site's own origin and every response short of a 4xx. The viewport
68// is tall so that lazily loaded frames are in view and load, and the
69// drive waits for every frame to finish before it reads the traffic.
70// The browser's own probe for /favicon.ico is the one 404 allowed,
71// as in the harness: the browser asks for it, not the page.
72func TestTheSiteServesFromItsOwnOrigin(t *testing.T) {
73 rig, _ := siteRig(t)
74 ctx, cancel := context.WithTimeout(rig.Context(), 2*time.Minute)
75 defer cancel()
76 tr := listen(ctx)
77 mustRun(t, ctx, chromedp.EmulateViewport(1280, 6000))
78 // What each page must have asked for, so a page that loaded
79 // nothing of the kind the drive is here for cannot pass: the
80 // landing pages their theme through the /1/ rewrite, the example
81 // its frames.
82 expects := map[string]*regexp.Regexp{
83 "/": regexp.MustCompile(`^/1/fiesta\.css`),
84 "/ar/": regexp.MustCompile(`^/1/fiesta\.css`),
85 "/design-system/": regexp.MustCompile(`^/design-system/assets/gallery\.[0-9a-f]{16}\.css$`),
86 "/design-system/day/en/dropdown.html": regexp.MustCompile(`^/design-system/day/en/frames/dropdown/`),
87 }
88 for _, p := range []string{"/", "/ar/", "/design-system/", "/design-system/day/en/dropdown.html"} {
89 tr.take()
90 mustRun(t, ctx, chromedp.Navigate(rig.Origin+p), chromedp.WaitReady("body"))
91 deadline := time.Now().Add(20 * time.Second)
92 for {
93 var done bool
94 mustRun(t, ctx, chromedp.Evaluate(loadedJS, &done))
95 if done {
96 break
97 }
98 if !time.Now().Before(deadline) {
99 t.Fatalf("%s: the page or one of its frames never finished loading", p)
100 }
101 time.Sleep(50 * time.Millisecond)
102 }
103 // Give the browser a moment to report the last responses,
104 // which arrive as events after the documents say complete.
105 time.Sleep(200 * time.Millisecond)
106 requests, responses := tr.take()
107 if len(requests) < 2 {
108 t.Errorf("%s: only %d requests were seen; the page alone loads a stylesheet, so the listener read nothing", p, len(requests))
109 }
110 asked := false
111 for _, u := range requests {
112 if !strings.HasPrefix(u, rig.Origin+"/") && !strings.HasPrefix(u, "data:") {
113 t.Errorf("%s: a request left the origin: %s", p, u)
114 }
115 asked = asked || expects[p].MatchString(strings.TrimPrefix(u, rig.Origin))
116 }
117 if !asked {
118 t.Errorf("%s: nothing under %s was requested; this is not the page the drive is for, or its frames never loaded", p, expects[p])
119 }
120 for _, r := range responses {
121 var status int
122 var u string
123 if _, err := fmt.Sscanf(r, "%d %s", &status, &u); err != nil {
124 t.Fatalf("%s: reading a recorded response %q: %v", p, r, err)
125 }
126 if status == http.StatusNotFound && u == rig.Origin+"/favicon.ico" {
127 continue
128 }
129 if status >= http.StatusBadRequest {
130 t.Errorf("%s: the browser got a %d for %s", p, status, u)
131 }
132 }
133 rig.Screen("body", "the page at "+p)
134 }
135}
136