//go:build browser
package check
import (
"context"
"errors"
"net/http"
"sort"
"testing"
"time"
"github.com/chromedp/chromedp"
"amadan.net/rastrillo/fiesta/internal/axe"
"amadan.net/rastrillo/fiesta/internal/harness"
)
// axePage is a page with the stylesheets and three planted WCAG
// failures, each a rule of its own: no lang on
// (html-has-lang), an image with no alt (image-alt) and a text input
// with no label (label). Three rather than one so that "sorted by id"
// is a claim about an order and not about a list of one.
const axePage = `
` +
`` +
``
// TestAxeScanFindsFloorsAndSorts is the gate's own control, ahead of
// the drives that scan with it: the three planted failures are found
// and come back in id order, Run is Scan's violations, and a scan that
// reaches almost nothing (a selector matching no element, or a lone
// button under the five-rule floor) is ErrNothingScanned rather than
// a clean result.
func TestAxeScanFindsFloorsAndSorts(t *testing.T) {
pages := map[string]string{"/": axePage}
rig := harness.New(t, func(string) http.Handler { return sizingMux(t, pages) })
ctx, cancel := context.WithTimeout(rig.Context(), 60*time.Second)
defer cancel()
mustRun(t, ctx, chromedp.Navigate(rig.Origin+"/"), chromedp.WaitVisible("#app", chromedp.ByQuery))
res, err := axe.Scan(ctx, "", nil)
if err != nil {
t.Fatalf("Scan(whole document): %v", err)
}
if len(res.Incomplete) > 0 {
t.Logf("needs review, axe could not decide: %v", res.Incomplete)
}
ids := make([]string, 0, len(res.Violations))
for _, v := range res.Violations {
ids = append(ids, v.ID)
if len(v.Nodes) == 0 {
t.Errorf("%s names no node; a failure with no selector is not a report", v.ID)
}
}
for _, want := range []string{"html-has-lang", "image-alt", "label"} {
found := false
for _, id := range ids {
found = found || id == want
}
if !found {
t.Errorf("the planted %s violation was not reported; got %v", want, ids)
}
}
if len(ids) < 2 {
t.Fatalf("%d violation(s) reported; an order of fewer than two proves nothing", len(ids))
}
if !sort.StringsAreSorted(ids) {
t.Errorf("violations are not sorted by id: %v", ids)
}
vs, err := axe.Run(ctx, "", nil)
if err != nil {
t.Fatalf("Run(whole document): %v", err)
}
if len(vs) != len(res.Violations) {
t.Errorf("Run reports %d violations, Scan %d", len(vs), len(res.Violations))
}
for i := range vs {
if i < len(res.Violations) && vs[i].ID != res.Violations[i].ID {
t.Errorf("Run's violation %d is %s, Scan's is %s", i, vs[i].ID, res.Violations[i].ID)
}
}
// A scan of nothing is ErrNothingScanned, by either route to it.
if _, err := axe.Scan(ctx, "#no-such-element", nil); !errors.Is(err, axe.ErrNothingScanned) {
t.Errorf("a selector matching nothing gave %v, want ErrNothingScanned", err)
}
if _, err := axe.Scan(ctx, "#b", nil); !errors.Is(err, axe.ErrNothingScanned) {
t.Errorf("a lone button gave %v, want ErrNothingScanned (under the five-rule floor)", err)
}
// The caller's copy of the default ruleset is its own.
tags := axe.DefaultTags()
tags[0] = "best-practice"
if got := axe.DefaultTags(); got[0] != "wcag2a" {
t.Errorf("DefaultTags() hands out its own backing array; a caller changed it to %v", got)
}
rig.Screen("#app", "the axe page")
}