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
3package harness
4
5import (
6 "encoding/json"
7 "fmt"
8
9 "github.com/chromedp/chromedp"
10)
11
12// junkValues is what a missing field or an unhandled shape looks like
13// once it has been through String() — the bug class that renders
14// perfectly and says nothing. Kass's full set. Substring "null" can
15// false-positive on legitimate prose, which is why every hit carries
16// its surrounding text and AllowText exists for the rare deliberate
17// case.
18var junkValues = []string{"undefined", "null", "[object Object]", "NaN"}
19
20// AllowText exempts one exact string from the junk scan — for the
21// screen whose legitimate prose contains a junk substring ("null and
22// void"). The allowance is the surrounding string, not the junk value:
23// everything else on the screen is still scanned.
24func (r *Rig) AllowText(s string) {
25 r.mu.Lock()
26 defer r.mu.Unlock()
27 r.allowedText = append(r.allowedText, s)
28}
29
30// junkScanJS reads the screen the way a person would, plus the places
31// a person cannot see: input and textarea values, and the labels a
32// screen reader announces. The scan root is the screen's own selector
33// ("body" is the whole-page case). Each hit carries its surrounding
34// text so a prose false-positive is legible at a glance.
35const junkScanJS = `((sel, junk, allowed) => {
36 const root = document.querySelector(sel);
37 if (!root) return ["the junk-scan root " + sel + " is not on the page"];
38 const hay = [root.textContent ?? ""];
39 for (const f of root.querySelectorAll("input, textarea")) hay.push(f.value ?? "");
40 for (const n of root.querySelectorAll("[aria-label]")) hay.push(n.getAttribute("aria-label") ?? "");
41 const hits = [];
42 for (let text of hay) {
43 for (const a of allowed) text = text.split(a).join("");
44 for (const j of junk) {
45 const at = text.indexOf(j);
46 if (at < 0) continue;
47 const around = text.slice(Math.max(0, at - 40), at + j.length + 40).replace(/\s+/g, " ").trim();
48 hits.push(j + ' in: "' + around + '"');
49 }
50 }
51 return hits;
52})`
53
54// junkHits runs the scan rooted at selector and returns what it found.
55func (r *Rig) junkHits(selector string) []string {
56 r.t.Helper()
57 r.mu.Lock()
58 allowed := append([]string(nil), r.allowedText...)
59 r.mu.Unlock()
60 if allowed == nil {
61 allowed = []string{} // marshal as [], never null — JS iterates it
62 }
63 var args []string
64 for _, v := range []any{selector, junkValues, allowed} {
65 b, err := json.Marshal(v)
66 if err != nil {
67 r.t.Fatalf("harness: junk scan args: %v", err)
68 }
69 args = append(args, string(b))
70 }
71 var hits []string
72 expr := fmt.Sprintf("%s(%s, %s, %s)", junkScanJS, args[0], args[1], args[2])
73 if err := chromedp.Run(r.ctx, chromedp.Evaluate(expr, &hits)); err != nil {
74 r.t.Fatalf("harness: junk scan: %v", err)
75 }
76 return hits
77}
78