//go:build browser // The whole site in a real Chromium: a reader opens the landing page, // its Arabic twin, the design system and one example, and the browser // asks the site's own origin for everything it needs, and gets it. package www_test import ( "context" "fmt" "net/http" "regexp" "strings" "sync" "testing" "time" "github.com/chromedp/cdproto/network" "github.com/chromedp/chromedp" ) // traffic is what the browser asked for while a page loaded: every // request's URL, and every response's status with its URL, from the // network events of the page and its frames. type traffic struct { mu sync.Mutex requests []string responses []string } // listen records the browser's traffic from now on. func listen(ctx context.Context) *traffic { tr := &traffic{} chromedp.ListenTarget(ctx, func(ev any) { switch e := ev.(type) { case *network.EventRequestWillBeSent: tr.mu.Lock() tr.requests = append(tr.requests, e.Request.URL) tr.mu.Unlock() case *network.EventResponseReceived: tr.mu.Lock() tr.responses = append(tr.responses, fmt.Sprintf("%d %s", int(e.Response.Status), e.Response.URL)) tr.mu.Unlock() } }) return tr } // take hands back what was recorded and starts afresh, for the next // page. func (tr *traffic) take() (requests, responses []string) { tr.mu.Lock() defer tr.mu.Unlock() requests, responses = tr.requests, tr.responses tr.requests, tr.responses = nil, nil return requests, responses } // loadedJS is true once the page and every frame in it have loaded: a // gallery page's frames are same-origin, so their documents can be // read, and a frame that is still about:blank or loading is not done. const loadedJS = `document.readyState === "complete" && [...document.querySelectorAll("iframe")].every(f => f.contentDocument && f.contentDocument.location.href !== "about:blank" && f.contentDocument.readyState === "complete")` // TestTheSiteServesFromItsOwnOrigin: the landing page, the Arabic // landing page, the design system's index and one example page each // load through the edge's rules with every request going to the // site's own origin and every response short of a 4xx. The viewport // is tall so that lazily loaded frames are in view and load, and the // drive waits for every frame to finish before it reads the traffic. // The browser's own probe for /favicon.ico is the one 404 allowed, // as in the harness: the browser asks for it, not the page. func TestTheSiteServesFromItsOwnOrigin(t *testing.T) { rig, _ := siteRig(t) ctx, cancel := context.WithTimeout(rig.Context(), 2*time.Minute) defer cancel() tr := listen(ctx) mustRun(t, ctx, chromedp.EmulateViewport(1280, 6000)) // What each page must have asked for, so a page that loaded // nothing of the kind the drive is here for cannot pass: the // landing pages their theme through the /1/ rewrite, the example // its frames. expects := map[string]*regexp.Regexp{ "/": regexp.MustCompile(`^/1/fiesta\.css`), "/ar/": regexp.MustCompile(`^/1/fiesta\.css`), "/design-system/": regexp.MustCompile(`^/design-system/assets/gallery\.[0-9a-f]{16}\.css$`), "/design-system/day/en/dropdown.html": regexp.MustCompile(`^/design-system/day/en/frames/dropdown/`), } for _, p := range []string{"/", "/ar/", "/design-system/", "/design-system/day/en/dropdown.html"} { tr.take() mustRun(t, ctx, chromedp.Navigate(rig.Origin+p), chromedp.WaitReady("body")) deadline := time.Now().Add(20 * time.Second) for { var done bool mustRun(t, ctx, chromedp.Evaluate(loadedJS, &done)) if done { break } if !time.Now().Before(deadline) { t.Fatalf("%s: the page or one of its frames never finished loading", p) } time.Sleep(50 * time.Millisecond) } // Give the browser a moment to report the last responses, // which arrive as events after the documents say complete. time.Sleep(200 * time.Millisecond) requests, responses := tr.take() if len(requests) < 2 { t.Errorf("%s: only %d requests were seen; the page alone loads a stylesheet, so the listener read nothing", p, len(requests)) } asked := false for _, u := range requests { if !strings.HasPrefix(u, rig.Origin+"/") && !strings.HasPrefix(u, "data:") { t.Errorf("%s: a request left the origin: %s", p, u) } asked = asked || expects[p].MatchString(strings.TrimPrefix(u, rig.Origin)) } if !asked { 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]) } for _, r := range responses { var status int var u string if _, err := fmt.Sscanf(r, "%d %s", &status, &u); err != nil { t.Fatalf("%s: reading a recorded response %q: %v", p, r, err) } if status == http.StatusNotFound && u == rig.Origin+"/favicon.ico" { continue } if status >= http.StatusBadRequest { t.Errorf("%s: the browser got a %d for %s", p, status, u) } } rig.Screen("body", "the page at "+p) } }