Skip to content

Commit f09fda3

Browse files
committed
Add NEAR check mode for assert-(variable|property|document-property|window-property)
1 parent ea03675 commit f09fda3

File tree

6 files changed

+145
-28
lines changed

6 files changed

+145
-28
lines changed

goml-script.md

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ assert-attribute: ("#id > .class", {"attribute-name": "attribute-value"}, ALL)
199199
assert-attribute: ("//*[@id='id']/*[@class='class']", {"key1": "value1", "key2": "value2"}, ALL)
200200
```
201201

202-
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
202+
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
203203

204204
```
205205
assert-attribute: (
@@ -235,7 +235,7 @@ assert-attribute-false: ("#id > .class", {"attribute-name": "attribute-value"},
235235
assert-attribute-false: ("//*[@id='id']/*[@class='class']", {"key1": "value1", "key2": "value2"}, ALL)
236236
```
237237

238-
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
238+
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
239239

240240
```
241241
assert-attribute-false: (
@@ -321,7 +321,7 @@ assert-document-property: ({"URL": "https://some.where", "title": "a title"})
321321
assert-document-property: {"URL": "https://some.where", "title": "a title"}
322322
```
323323

324-
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
324+
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
325325

326326
```
327327
assert-document-property: ({"URL": "https://some.where", "title": "a title"}, STARTS_WITH)
@@ -343,7 +343,8 @@ assert-document-property-false: ({"URL": "https://some.where", "title": "a title
343343
assert-document-property-false: {"URL": "https://some.where", "title": "a title"}
344344
```
345345

346-
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
346+
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH",
347+
"STARTS_WITH" or "NEAR".
347348

348349
```
349350
assert-document-property-false: ({"URL": "https://some.where", "title": "a title"}, STARTS_WITH)
@@ -422,7 +423,7 @@ assert-property: ("#id > .class", { "offsetParent": "null" }, ALL)
422423
assert-property: ("//*[@id='id']/*[@class='class']", { "offsetParent": "null", "clientTop": "10px" }, ALL)
423424
```
424425

425-
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
426+
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH", "STARTS_WITH" or "NEAR".
426427

427428
```
428429
assert-property: (
@@ -457,7 +458,7 @@ assert-property-false: ("#id > .class", { "offsetParent": "null" }, ALL)
457458
assert-property-false: ("//*[@id='id']/*[@class='class']", { "offsetParent": "null", "clientTop": "10px" }, ALL)
458459
```
459460

460-
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
461+
You can use more specific checks as well by using one of the following identifiers: "ALL", "CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
461462

462463
```
463464
assert-property-false: (
@@ -544,12 +545,27 @@ assert-variable: (variable_name, 12)
544545
assert-variable: (variable_name, 12.1)
545546
```
546547

547-
Apart from "CONTAINS", you can also use "ENDS_WITH" and "STARTS_WITH" and even combine them if you want. Example:
548+
Apart from "CONTAINS", you can also use "ENDS_WITH", "STARTS_WITH" or "NEAR" and even combine them if you want. Example:
548549

549550
```
550551
assert-variable: (variable_name, "hel", [CONTAINS, STARTS_WITH])
551552
```
552553

554+
The `ENDS_WITH` and `STARTS_WITH` interpret the variable as a string, while `NEAR` interprets it as a number and asserts that the difference between the variable and the tested value is less than or equal to 1. This check is useful in cases where the browser rounds a potentially-fractional value to an integer, and may not always do it consistently from run to run:
555+
556+
```
557+
// all of these assertions will pass
558+
store-value: (variable_name, "hello")
559+
assert-variable: (variable_name, "he", STARTS_WITH)
560+
assert-variable: (variable_name, "o", ENDS_WITH)
561+
store-value: (variable_name, 10)
562+
assert-variable: (variable_name, 10, NEAR)
563+
assert-variable: (variable_name, 9, NEAR)
564+
assert-variable: (variable_name, 11, NEAR)
565+
assert-variable-false: (variable_name, 8, NEAR)
566+
assert-variable-false: (variable_name, 12, NEAR)
567+
```
568+
553569
For more information about variables, read the [variables section](#variables).
554570

555571
#### assert-variable-false
@@ -563,7 +579,7 @@ assert-variable-false: (variable_name, 12)
563579
assert-variable-false: (variable_name, 12.1)
564580
```
565581

566-
Apart from "CONTAINS", you can also use "ENDS_WITH" and "STARTS_WITH" and even combine them if you want. Example:
582+
Apart from "CONTAINS", you can also use "ENDS_WITH", "STARTS_WITH" or "NEAR" and even combine them if you want. Example:
567583

568584
```
569585
assert-variable-false: (variable_name, "hel", [CONTAINS, ENDS_WITH])
@@ -581,7 +597,7 @@ assert-window-property: ({"pageYOffset": "0", "location": "https://some.where"})
581597
assert-window-property: {"pageYOffset": "0", "location": "https://some.where"}
582598
```
583599

584-
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
600+
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH", "STARTS_WITH", or "NEAR".
585601

586602
```
587603
assert-window-property: (
@@ -609,7 +625,7 @@ assert-window-property-false: ({"location": "https://some.where", "pageYOffset":
609625
assert-window-property-false: {"location": "https://some.where", "pageYOffset": "10"}
610626
```
611627

612-
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH" or "STARTS_WITH".
628+
You can use more specific checks as well by using one of the following identifiers: "CONTAINS", "ENDS_WITH", "STARTS_WITH" or "NEAR".
613629

614630
```
615631
assert-window-property-false: (

src/commands/assert.js

Lines changed: 92 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ function parseAssertCssFalse(parser) {
137137

138138
function parseAssertObjPropertyInner(parser, assertFalse, objName) {
139139
const elems = parser.elems;
140-
const identifiers = ['CONTAINS', 'ENDS_WITH', 'STARTS_WITH'];
140+
const identifiers = ['CONTAINS', 'ENDS_WITH', 'STARTS_WITH', 'NEAR'];
141141

142142
if (elems.length === 0) {
143143
return {'error': 'expected a tuple or a JSON dict, found nothing'};
@@ -254,6 +254,27 @@ ENDS_WITH check)');
254254
if (!String(${objName}[${varKey}]).endsWith(${varValue})) {
255255
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + ${objName}[${varKey}] + '\
256256
\`) does not end with \`' + ${varValue} + '\`');
257+
}`);
258+
}
259+
}
260+
if (enabled_checks['NEAR']) {
261+
if (assertFalse) {
262+
checks.push(`\
263+
if (Number.isNaN(${objName}[${varKey}])) {
264+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + ${objName}[${varKey}] + '\
265+
\`) is NaN');
266+
} else if (Math.abs(${objName}[${varKey}] - ${varValue}) <= 1) {
267+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + ${objName}[${varKey}] + '\
268+
\`) is within 1 of \`' + ${varValue} + '\`');
269+
}`);
270+
} else {
271+
checks.push(`\
272+
if (Number.isNaN(${objName}[${varKey}])) {
273+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + ${objName}[${varKey}] + '\
274+
\`) is NaN');
275+
} else if (Math.abs(${objName}[${varKey}] - ${varValue}) > 1) {
276+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + ${objName}[${varKey}] + '\
277+
\`) is not within 1 of \`' + ${varValue} + '\`');
257278
}`);
258279
}
259280
}
@@ -308,8 +329,8 @@ ${indentString(checks.join('\n'), 2)}
308329
//
309330
// * {"DOM property": "value"}
310331
// * ({"DOM property": "value"})
311-
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH)
312-
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH])
332+
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH|NEAR)
333+
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH|NEAR])
313334
function parseAssertDocumentProperty(parser) {
314335
return parseAssertObjPropertyInner(parser, false, 'document');
315336
}
@@ -318,8 +339,8 @@ function parseAssertDocumentProperty(parser) {
318339
//
319340
// * {"DOM property": "value"}
320341
// * ({"DOM property": "value"})
321-
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH)
322-
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH])
342+
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH|NEAR)
343+
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH|NEAR])
323344
function parseAssertDocumentPropertyFalse(parser) {
324345
return parseAssertObjPropertyInner(parser, true, 'document');
325346
}
@@ -328,8 +349,8 @@ function parseAssertDocumentPropertyFalse(parser) {
328349
//
329350
// * {"DOM property": "value"}
330351
// * ({"DOM property": "value"})
331-
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH)
332-
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH])
352+
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH|NEAR)
353+
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH|NEAR])
333354
function parseAssertWindowProperty(parser) {
334355
return parseAssertObjPropertyInner(parser, false, 'window');
335356
}
@@ -338,16 +359,16 @@ function parseAssertWindowProperty(parser) {
338359
//
339360
// * {"DOM property": "value"}
340361
// * ({"DOM property": "value"})
341-
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH)
342-
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH])
362+
// * ({"DOM property": "value"}, CONTAINS|ENDS_WITH|STARTS_WITH|NEAR)
363+
// * ({"DOM property": "value"}, [CONTAINS|ENDS_WITH|STARTS_WITH|NEAR])
343364
function parseAssertWindowPropertyFalse(parser) {
344365
return parseAssertObjPropertyInner(parser, true, 'window');
345366
}
346367

347368
function parseAssertPropertyInner(parser, assertFalse) {
348369
const err = 'Read the documentation to see the accepted inputs';
349370
const elems = parser.elems;
350-
const identifiers = ['ALL', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'];
371+
const identifiers = ['ALL', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH', 'NEAR'];
351372
const warnings = [];
352373
const enabled_checks = Object.create(null);
353374

@@ -432,6 +453,27 @@ ENDS_WITH check)');
432453
if (!String(e[${varKey}]).endsWith(${varValue})) {
433454
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + e[${varKey}] + '\
434455
\`) does not end with \`' + ${varValue} + '\`');
456+
}`);
457+
}
458+
}
459+
if (enabled_checks['NEAR']) {
460+
if (assertFalse) {
461+
checks.push(`\
462+
if (Number.isNaN(e[${varKey}])) {
463+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + e[${varKey}] + '\
464+
\`) is NaN');
465+
} else if (Math.abs(e[${varKey}] - ${varValue}) <= 1) {
466+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + e[${varKey}] + '\
467+
\`) is within 1 of \`' + ${varValue} + '\`');
468+
}`);
469+
} else {
470+
checks.push(`\
471+
if (Number.isNaN(e[${varKey}])) {
472+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + e[${varKey}] + '\
473+
\`) is NaN');
474+
} else if (Math.abs(e[${varKey}] - ${varValue}) > 1) {
475+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + e[${varKey}] + '\
476+
\`) is not within 1 of \`' + ${varValue} + '\`');
435477
}`);
436478
}
437479
}
@@ -544,7 +586,7 @@ function parseAssertPropertyFalse(parser) {
544586
function parseAssertAttributeInner(parser, assertFalse) {
545587
const err = 'Read the documentation to see the accepted inputs';
546588
const elems = parser.elems;
547-
const identifiers = ['ALL', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH'];
589+
const identifiers = ['ALL', 'CONTAINS', 'STARTS_WITH', 'ENDS_WITH', 'NEAR'];
548590
const warnings = [];
549591
const enabled_checks = Object.create(null);
550592

@@ -629,6 +671,27 @@ if (attr.endsWith(${varValue})) {
629671
if (!attr.endsWith(${varValue})) {
630672
nonMatchingAttrs.push("attribute \`" + ${varKey} + "\` (\`" + attr + "\`) doesn't end with \`"\
631673
+ ${varValue} + "\`");
674+
}`);
675+
}
676+
}
677+
if (enabled_checks['NEAR']) {
678+
if (assertFalse) {
679+
checks.push(`\
680+
if (Number.isNaN(attr)) {
681+
nonMatchingProps.push('attribute \`' + ${varKey} + '\` (\`' + attr + '\
682+
\`) is NaN');
683+
} else if (Math.abs(attr] - ${varValue}) <= 1) {
684+
nonMatchingProps.push('attribute \`' + ${varKey} + '\` (\`' + attr + '\
685+
\`) is within 1 of \`' + ${varValue} '\`');
686+
}`);
687+
} else {
688+
checks.push(`\
689+
if (Number.isNaN(attr)) {
690+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + attr + '\
691+
\`) is NaN');
692+
} else if (Math.abs(attr - ${varValue}) > 1) {
693+
nonMatchingProps.push('Property \`' + ${varKey} + '\` (\`' + attr + '\
694+
\`) is not within 1 of \`' + ${varValue} '\`');
632695
}`);
633696
}
634697
}
@@ -1264,7 +1327,7 @@ function parseAssertVariableInner(parser, assertFalse) {
12641327
${tuple[1].getArticleKind()} (\`${tuple[1].getErrorText()}\`)`,
12651328
};
12661329
} else if (tuple.length > 2) {
1267-
const identifiers = ['CONTAINS', 'STARTS_WITH', 'ENDS_WITH'];
1330+
const identifiers = ['CONTAINS', 'STARTS_WITH', 'ENDS_WITH', 'NEAR'];
12681331
const ret = fillEnabledChecks(tuple[2], identifiers, enabled_checks, warnings, 'third');
12691332
if (ret !== null) {
12701333
return ret;
@@ -1309,6 +1372,23 @@ if (value1.endsWith(value2)) {
13091372
checks.push(`\
13101373
if (!value1.endsWith(value2)) {
13111374
errors.push("\`" + value1 + "\` doesn't end with \`" + value2 + "\` (for ENDS_WITH check)");
1375+
}`);
1376+
}
1377+
}
1378+
if (enabled_checks['NEAR']) {
1379+
if (assertFalse) {
1380+
checks.push(`\
1381+
if (Number.isNaN(value1])) {
1382+
nonMatchingProps.push('\`' + value1 + '\` is NaN');
1383+
} else if (Math.abs(value1 - value2) <= 1) {
1384+
nonMatchingProps.push('\`' + value1 + '\` is within 1 of \`' + value2 '\`');
1385+
}`);
1386+
} else {
1387+
checks.push(`\
1388+
if (Number.isNaN(value1])) {
1389+
nonMatchingProps.push('\`' + value1 + '\` is NaN');
1390+
} else if (Math.abs(value1 - value2) > 1) {
1391+
nonMatchingProps.push('\`' + value1 + '\` is not within 1 of \`' + value2 '\`');
13121392
}`);
13131393
}
13141394
}

tests/scripts/assert-document-property.goml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,10 @@ assert-document-property-false: ({"title": "bsic"}, STARTS_WITH)
1111
assert-document-property-false: ({"title": "bsic"}, CONTAINS)
1212
assert-document-property-false: ({"title": "bsic"}, ENDS_WITH)
1313
assert-document-property-false: ({"title": "bsic"}, [STARTS_WITH, CONTAINS])
14+
assert-document-property: ({"nodeType": 9})
15+
assert-document-property-false: ({"nodeType": 8})
16+
assert-document-property: ({"nodeType": 9}, NEAR)
17+
assert-document-property: ({"nodeType": 8}, NEAR)
18+
assert-document-property-false: ({"nodeType": 7}, NEAR)
19+
assert-document-property: ({"nodeType": 10}, NEAR)
20+
assert-document-property-false: ({"nodeType": 11}, NEAR)

tests/scripts/assert.goml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ assert-count: ("div", 1)
77
assert-css: ("#button", {"padding": "5px"})
88
assert-attribute: ("#button", {"href": "./other_page.html"})
99
assert-property: ("#button", {"offsetParent": "null"})
10+
assert-property: ("body", {"offsetTop": 0})
11+
assert-property-false: ("body", {"offsetTop": 1})
12+
assert-property: ("body", {"offsetTop": 0}, NEAR)
13+
assert-property: ("body", {"offsetTop": 1}, NEAR)
14+
assert-property-false: ("body", {"offsetTop": 2}, NEAR)

tests/scripts/attribute.goml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,12 @@ assert-attribute: ("#yet-another-id", {"\"e": "'2"})
1212
fail: false
1313
attribute: ("#yet-another-id", "e", "'2")
1414
assert-attribute: ("#yet-another-id", {"e": "'2"})
15+
attribute: ("#yet-another-id", "e", "2")
16+
assert-attribute: ("#yet-another-id", {"e": "2"})
17+
assert-attribute-false: ("#yet-another-id", {"e": "3"})
18+
assert-attribute: ("#yet-another-id", {"e": "2"}, NEAR)
19+
assert-attribute: ("#yet-another-id", {"e": "3"}, NEAR)
20+
assert-attribute: ("#yet-another-id", {"e": "1"}, NEAR)
21+
fail: true
22+
assert-attribute: ("#yet-another-id", {"e": "4"}, NEAR)
23+
assert-attribute: ("#yet-another-id", {"e": "0"}, NEAR)

tests/test-js/api.js

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -146,11 +146,11 @@ function checkAssertAttributeInner(x, func, notFound, equal, contains, startsWit
146146
});
147147
x.assert(func('("a::after", {"a": 1}, all)'), {
148148
'error': 'unknown identifier `all`. Available identifiers are: [`ALL`, `CONTAINS`, ' +
149-
'`STARTS_WITH`, `ENDS_WITH`]',
149+
'`STARTS_WITH`, `ENDS_WITH`, `NEAR`]',
150150
});
151151
x.assert(func('("a::after", {"a": 1}, ALLO)'), {
152152
'error': 'unknown identifier `ALLO`. Available identifiers are: [`ALL`, `CONTAINS`, ' +
153-
'`STARTS_WITH`, `ENDS_WITH`]',
153+
'`STARTS_WITH`, `ENDS_WITH`, `NEAR`]',
154154
});
155155
x.assert(func('("a", {"b": "c", "b": "d"})'), {'error': 'attribute `b` is duplicated'});
156156

@@ -667,7 +667,7 @@ ${equal(3)}
667667
// Multiline
668668
x.assert(func('("a::after", \n {"a": 1}, \n ALLO)'), {
669669
'error': 'unknown identifier `ALLO`. Available identifiers are: [`ALL`, `CONTAINS`, ' +
670-
'`STARTS_WITH`, `ENDS_WITH`]',
670+
'`STARTS_WITH`, `ENDS_WITH`, `NEAR`]',
671671
});
672672
x.assert(func('("//a",\n \n{"b": "c"}, \n ALL)'), {
673673
'instructions': [`\
@@ -878,7 +878,7 @@ function checkAssertObjPropertyInner(
878878
x.assert(func('("a", "b" "c", ALL)'), {'error': 'expected `,` or `)`, found `"` after `"b"`'});
879879
x.assert(func('({"a": "b"}, all)'), {
880880
'error': 'unknown identifier `all`. Available identifiers are: [`CONTAINS`, `ENDS_WITH`, ' +
881-
'`STARTS_WITH`]',
881+
'`STARTS_WITH`, `NEAR`]',
882882
});
883883
x.assert(func('("a::after", {"a": 1}, ALLO)'), {
884884
'error': 'expected a tuple of one or two elements, found 3 elements',
@@ -2186,11 +2186,11 @@ function checkAssertPropertyInner(x, func, exists, equal, startsWith, endsWith)
21862186
});
21872187
x.assert(func('("a::after", {"a": 1}, all)'), {
21882188
'error': 'unknown identifier `all`. Available identifiers are: [`ALL`, `CONTAINS`, ' +
2189-
'`STARTS_WITH`, `ENDS_WITH`]',
2189+
'`STARTS_WITH`, `ENDS_WITH`, `NEAR`]',
21902190
});
21912191
x.assert(func('("a::after", {"a": 1}, ALLO)'), {
21922192
'error': 'unknown identifier `ALLO`. Available identifiers are: [`ALL`, `CONTAINS`, ' +
2193-
'`STARTS_WITH`, `ENDS_WITH`]',
2193+
'`STARTS_WITH`, `ENDS_WITH`, `NEAR`]',
21942194
});
21952195
x.assert(func('("a", {"b": "c", "b": "d"})'), {'error': 'property `b` is duplicated'});
21962196
x.assert(func('("a", {"b": []})'), {

0 commit comments

Comments
 (0)