mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
test(chrome): drive the live-page parity test through both selector forms
This commit is contained in:
1 parent
cb880edf44
commit
8a98915118
2 files changed
+92
-13
No files matched your search
@@ -9,6 +9,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -54,17 +55,50 @@ func TestSelectors_ResolveTheSameElementsAsTheWebRuntime(t *testing.T) {
|
|||||||
// want is stated here rather than derived, so a matcher that stops matching
|
// want is stated here rather than derived, so a matcher that stops matching
|
||||||
// on both sides at once cannot pass this by agreeing on nothing.
|
// on both sides at once cannot pass this by agreeing on nothing.
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
|
name string
|
||||||
selector string
|
selector string
|
||||||
|
object hierarchy.Selector
|
||||||
want []string
|
want []string
|
||||||
}{
|
}{
|
||||||
{"idPrefix:customer_row_", []string{"customer_row_a1", "customer_row_b2"}},
|
{
|
||||||
{"idPrefix:customer_", []string{"customer_list", "customer_row_a1", "customer_row_b2"}},
|
name: "idPrefix",
|
||||||
{"idPrefix:invoice_row_", nil},
|
selector: "idPrefix:customer_row_",
|
||||||
{"id:summary_card", []string{"summary_card"}},
|
object: objectSelector("idPrefix", "customer_row_"),
|
||||||
{"descPrefix:customer_row_", []string{"customer_row_a1", "customer_row_b2"}},
|
want: []string{"customer_row_a1", "customer_row_b2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "idPrefix spanning the container",
|
||||||
|
selector: "idPrefix:customer_",
|
||||||
|
object: objectSelector("idPrefix", "customer_"),
|
||||||
|
want: []string{"customer_list", "customer_row_a1", "customer_row_b2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "idPrefix matching nothing",
|
||||||
|
selector: "idPrefix:invoice_row_",
|
||||||
|
object: objectSelector("idPrefix", "invoice_row_"),
|
||||||
|
want: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "id",
|
||||||
|
selector: "id:summary_card",
|
||||||
|
object: objectSelector("id", "summary_card"),
|
||||||
|
want: []string{"summary_card"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "descPrefix",
|
||||||
|
selector: "descPrefix:customer_row_",
|
||||||
|
object: objectSelector("descPrefix", "customer_row_"),
|
||||||
|
want: []string{"customer_row_a1", "customer_row_b2"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "desc",
|
||||||
|
selector: "desc:customer_row_a1",
|
||||||
|
object: objectSelector("desc", "customer_row_a1"),
|
||||||
|
want: []string{"customer_row_a1"},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, testCase := range cases {
|
for _, testCase := range cases {
|
||||||
t.Run(testCase.selector, func(t *testing.T) {
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
native := selectorIDsFromDump(tree, testCase.selector)
|
native := selectorIDsFromDump(tree, testCase.selector)
|
||||||
web := selectorIDsFromWebRuntime(ctx, t, d, testCase.selector)
|
web := selectorIDsFromWebRuntime(ctx, t, d, testCase.selector)
|
||||||
if !slices.Equal(native, testCase.want) {
|
if !slices.Equal(native, testCase.want) {
|
||||||
@@ -73,10 +107,59 @@ func TestSelectors_ResolveTheSameElementsAsTheWebRuntime(t *testing.T) {
|
|||||||
if !slices.Equal(web, testCase.want) {
|
if !slices.Equal(web, testCase.want) {
|
||||||
t.Errorf("the web runtime matched %v, want %v", web, testCase.want)
|
t.Errorf("the web runtime matched %v, want %v", web, testCase.want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The object form has to mean the same thing as the string form on
|
||||||
|
// both sides. It is the form the typed API pushes authors toward and
|
||||||
|
// the one that used to fall through to a raw attribute lookup.
|
||||||
|
nativeObject := selectorIDsFromDumpObject(tree, testCase.object)
|
||||||
|
webObject := selectorIDsFromWebRuntime(ctx, t, d, objectSelectorJSON(testCase.object))
|
||||||
|
if !slices.Equal(nativeObject, testCase.want) {
|
||||||
|
t.Errorf("hierarchy object form matched %v, want %v", nativeObject, testCase.want)
|
||||||
|
}
|
||||||
|
if !slices.Equal(webObject, testCase.want) {
|
||||||
|
t.Errorf("the web runtime object form matched %v, want %v", webObject, testCase.want)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func objectSelector(key, value string) hierarchy.Selector {
|
||||||
|
return hierarchy.Selector{Filters: []hierarchy.AttrFilter{{Attr: key, Value: value}}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectSelectorJSON(sel hierarchy.Selector) string {
|
||||||
|
fields := map[string]string{}
|
||||||
|
for _, filter := range sel.Filters {
|
||||||
|
fields[filter.Attr] = filter.Value
|
||||||
|
}
|
||||||
|
encoded, err := json.Marshal(fields)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectorIDsFromDumpObject(tree *hierarchy.Tree, sel hierarchy.Selector) []string {
|
||||||
|
var ids []string
|
||||||
|
for _, node := range tree.Root.FindAllBySelector(sel) {
|
||||||
|
ids = append(ids, node.Element.ResourceID)
|
||||||
|
}
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsArgument passes an object selector through as the JS object literal it
|
||||||
|
// already is, and quotes anything else as a string selector.
|
||||||
|
func jsArgument(selector string) string {
|
||||||
|
if strings.HasPrefix(selector, "{") {
|
||||||
|
return selector
|
||||||
|
}
|
||||||
|
quoted, err := json.Marshal(selector)
|
||||||
|
if err != nil {
|
||||||
|
return `""`
|
||||||
|
}
|
||||||
|
return string(quoted)
|
||||||
|
}
|
||||||
|
|
||||||
func selectorIDsFromDump(tree *hierarchy.Tree, selector string) []string {
|
func selectorIDsFromDump(tree *hierarchy.Tree, selector string) []string {
|
||||||
var ids []string
|
var ids []string
|
||||||
for _, element := range tree.FindAll(selector) {
|
for _, element := range tree.FindAll(selector) {
|
||||||
@@ -107,12 +190,8 @@ func selectorIDsFromWebRuntime(
|
|||||||
selector string,
|
selector string,
|
||||||
) []string {
|
) []string {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
request, err := json.Marshal(selector)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("encode selector: %v", err)
|
|
||||||
}
|
|
||||||
var encoded string
|
var encoded string
|
||||||
script := `JSON.stringify(window.__sanderlingSelectorMatches__(` + string(request) + `))`
|
script := `JSON.stringify(window.__sanderlingSelectorMatches__(` + jsArgument(selector) + `))`
|
||||||
if err := chromedp.Run(d.tabCtx, chromedp.Evaluate(script, &encoded)); err != nil {
|
if err := chromedp.Run(d.tabCtx, chromedp.Evaluate(script, &encoded)); err != nil {
|
||||||
t.Fatalf("read web runtime matches for %q: %v", selector, err)
|
t.Fatalf("read web runtime matches for %q: %v", selector, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ interface Ax {
|
|||||||
findAll(selector: unknown): Handle[];
|
findAll(selector: unknown): Handle[];
|
||||||
}
|
}
|
||||||
|
|
||||||
function selectorMatches(selector: string): string[] {
|
function selectorMatches(selector: unknown): string[] {
|
||||||
const ax = buildAx() as Ax;
|
const ax = buildAx() as Ax;
|
||||||
return ax.findAll(selector).map((element) => element.id ?? "");
|
return ax.findAll(selector).map((element) => element.id ?? "");
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectorGlobal = {
|
type SelectorGlobal = {
|
||||||
__sanderlingSelectorMatches__: (selector: string) => string[];
|
__sanderlingSelectorMatches__: (selector: unknown) => string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
(globalThis as unknown as SelectorGlobal).__sanderlingSelectorMatches__ = selectorMatches;
|
(globalThis as unknown as SelectorGlobal).__sanderlingSelectorMatches__ = selectorMatches;
|
||||||
Reference in new issue
Block a user