@molecule/app-e2e-fixtures-default 1.0.2 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -2
- package/dist/page.d.ts +8 -1
- package/dist/page.d.ts.map +1 -1
- package/dist/page.js +29 -8
- package/dist/page.js.map +1 -1
- package/dist/runtime.d.ts +8 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +240 -254
- package/dist/runtime.js.map +1 -1
- package/package.json +5 -1
package/dist/runtime.js
CHANGED
|
@@ -14,18 +14,24 @@
|
|
|
14
14
|
* @module
|
|
15
15
|
*/
|
|
16
16
|
/** Bumped when the in-page runtime's protocol changes; a page holding an older one is re-installed. */
|
|
17
|
-
export const E2E_RUNTIME_VERSION =
|
|
17
|
+
export const E2E_RUNTIME_VERSION = 2;
|
|
18
18
|
/**
|
|
19
19
|
* Install the runtime. Runs INSIDE the page — see the module doc; the body
|
|
20
20
|
* must stay self-contained.
|
|
21
|
+
*
|
|
22
|
+
* The runtime never waits: every call is ONE attempt, and a call that would
|
|
23
|
+
* have to wait (an element not there yet, not visible yet, covered) answers
|
|
24
|
+
* `{ retry: true, error }` so the DRIVER polls with its own clock. A page in a
|
|
25
|
+
* background tab has its timers throttled to once a second (a minute, after a
|
|
26
|
+
* while) — a `setTimeout` in here would stretch every auto-wait by that much,
|
|
27
|
+
* while the WebSocket messages that carry the calls are delivered on time.
|
|
21
28
|
*/
|
|
22
29
|
export function installE2ERuntime() {
|
|
23
|
-
const VERSION =
|
|
30
|
+
const VERSION = 2;
|
|
24
31
|
const g = globalThis;
|
|
25
32
|
if (g.__molE2E && g.__molE2E.version === VERSION)
|
|
26
33
|
return { version: VERSION };
|
|
27
34
|
const norm = (s) => (s ?? '').replace(/\s+/g, ' ').trim();
|
|
28
|
-
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
29
35
|
const matchText = (text, m) => {
|
|
30
36
|
if (!m)
|
|
31
37
|
return true;
|
|
@@ -811,257 +817,242 @@ export function installE2ERuntime() {
|
|
|
811
817
|
'clear',
|
|
812
818
|
]);
|
|
813
819
|
const ACTIONS_NEEDING_POINTER = new Set(['click', 'dblclick', 'hover', 'tap']);
|
|
814
|
-
const act = async (steps, action, opts
|
|
820
|
+
const act = async (steps, action, opts) => {
|
|
815
821
|
let reason;
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
el.scrollIntoView({ block: 'center', inline: 'center' });
|
|
838
|
-
}
|
|
839
|
-
catch (_error) {
|
|
840
|
-
/* ignore */
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
if (action === 'scrollIntoViewIfNeeded')
|
|
844
|
-
return { ok: true };
|
|
845
|
-
const p = pointAt(el, opts.position);
|
|
846
|
-
if (!opts.force) {
|
|
847
|
-
const hit = typeof document.elementFromPoint === 'function'
|
|
848
|
-
? document.elementFromPoint(p.x, p.y)
|
|
849
|
-
: null;
|
|
850
|
-
// A null hit with the point inside the viewport is a document that cannot hit-test
|
|
851
|
-
// (jsdom, a detached rendering); treat it as uncovered rather than unreachable.
|
|
852
|
-
const inView = p.x >= 0 && p.y >= 0 && p.x <= innerWidth && p.y <= innerHeight;
|
|
853
|
-
if (!(hit === null && inView) && !hitOk(el, hit)) {
|
|
854
|
-
if (!hit || p.x < 0 || p.y < 0 || p.x > innerWidth || p.y > innerHeight)
|
|
855
|
-
reason = describe(steps) + ' — element is outside of the viewport';
|
|
856
|
-
else {
|
|
857
|
-
const h = hit;
|
|
858
|
-
reason =
|
|
859
|
-
describe(steps) +
|
|
860
|
-
' — <' +
|
|
861
|
-
h.tagName.toLowerCase() +
|
|
862
|
-
(h.id ? '#' + h.id : '') +
|
|
863
|
-
(h.className && typeof h.className === 'string'
|
|
864
|
-
? '.' + h.className.trim().split(/\s+/).slice(0, 2).join('.')
|
|
865
|
-
: '') +
|
|
866
|
-
'> intercepts pointer events';
|
|
867
|
-
}
|
|
868
|
-
if (Date.now() > deadline)
|
|
869
|
-
return {
|
|
870
|
-
ok: false,
|
|
871
|
-
error: reason ?? 'waiting for ' + describe(steps),
|
|
872
|
-
timeout: true,
|
|
873
|
-
};
|
|
874
|
-
await sleep(50);
|
|
875
|
-
continue;
|
|
876
|
-
}
|
|
822
|
+
const els = resolveChain(steps);
|
|
823
|
+
if (els.length > 1)
|
|
824
|
+
return { ok: false, strict: true, count: els.length };
|
|
825
|
+
const el = els[0];
|
|
826
|
+
if (!el)
|
|
827
|
+
reason = describe(steps) + ' — element not found';
|
|
828
|
+
else if (ACTIONS_NEEDING_VISIBLE.has(action) && !opts.force && !isVisible(el))
|
|
829
|
+
reason = describe(steps) + ' — element is not visible';
|
|
830
|
+
else if (ACTIONS_NEEDING_ENABLED.has(action) && !opts.force && isDisabled(el))
|
|
831
|
+
reason = describe(steps) + ' — element is disabled';
|
|
832
|
+
else if (['fill', 'type', 'clear'].includes(action) && !opts.force && !isEditable(el))
|
|
833
|
+
return {
|
|
834
|
+
ok: false,
|
|
835
|
+
error: describe(steps) +
|
|
836
|
+
' — element is not an <input>, <textarea>, <select> or [contenteditable] element',
|
|
837
|
+
};
|
|
838
|
+
else {
|
|
839
|
+
if (ACTIONS_NEEDING_POINTER.has(action) || action === 'scrollIntoViewIfNeeded') {
|
|
840
|
+
if (!opts.noScroll) {
|
|
841
|
+
try {
|
|
842
|
+
el.scrollIntoView({ block: 'center', inline: 'center' });
|
|
877
843
|
}
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
else if (action === 'tap') {
|
|
881
|
-
fire(el, 'touchstart');
|
|
882
|
-
fire(el, 'touchend');
|
|
883
|
-
clickAt(el, p.x, p.y, opts);
|
|
844
|
+
catch (_error) {
|
|
845
|
+
/* ignore */
|
|
884
846
|
}
|
|
885
|
-
else
|
|
886
|
-
clickAt(el, p.x, p.y, {
|
|
887
|
-
...opts,
|
|
888
|
-
clickCount: action === 'dblclick' ? 2 : (opts.clickCount ?? 1),
|
|
889
|
-
});
|
|
890
|
-
return { ok: true };
|
|
891
847
|
}
|
|
892
|
-
if (action === '
|
|
893
|
-
;
|
|
894
|
-
el.focus();
|
|
848
|
+
if (action === 'scrollIntoViewIfNeeded')
|
|
895
849
|
return { ok: true };
|
|
896
|
-
|
|
897
|
-
if (
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
const
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
!['checkbox', 'radio', 'file', 'button', 'submit', 'reset', 'image'].includes(el.type);
|
|
908
|
-
if (!simple)
|
|
909
|
-
return {
|
|
910
|
-
ok: false,
|
|
911
|
-
error: describe(steps) + ' — cannot fill an input of type ' + el.type,
|
|
912
|
-
};
|
|
913
|
-
if (el instanceof HTMLInputElement &&
|
|
914
|
-
[
|
|
915
|
-
'date',
|
|
916
|
-
'time',
|
|
917
|
-
'datetime-local',
|
|
918
|
-
'month',
|
|
919
|
-
'week',
|
|
920
|
-
'color',
|
|
921
|
-
'range',
|
|
922
|
-
'number',
|
|
923
|
-
].includes(el.type)) {
|
|
924
|
-
nativeSetValue(el, value);
|
|
925
|
-
fire(el, 'input', { inputType: 'insertText', data: value });
|
|
926
|
-
}
|
|
850
|
+
const p = pointAt(el, opts.position);
|
|
851
|
+
if (!opts.force) {
|
|
852
|
+
const hit = typeof document.elementFromPoint === 'function'
|
|
853
|
+
? document.elementFromPoint(p.x, p.y)
|
|
854
|
+
: null;
|
|
855
|
+
// A null hit with the point inside the viewport is a document that cannot hit-test
|
|
856
|
+
// (jsdom, a detached rendering); treat it as uncovered rather than unreachable.
|
|
857
|
+
const inView = p.x >= 0 && p.y >= 0 && p.x <= innerWidth && p.y <= innerHeight;
|
|
858
|
+
if (!(hit === null && inView) && !hitOk(el, hit)) {
|
|
859
|
+
if (!hit || p.x < 0 || p.y < 0 || p.x > innerWidth || p.y > innerHeight)
|
|
860
|
+
reason = describe(steps) + ' — element is outside of the viewport';
|
|
927
861
|
else {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
}
|
|
939
|
-
catch (_error) {
|
|
940
|
-
/* ignore */
|
|
941
|
-
}
|
|
942
|
-
fire(el, 'input', { inputType: 'insertText', data: value });
|
|
862
|
+
const h = hit;
|
|
863
|
+
reason =
|
|
864
|
+
describe(steps) +
|
|
865
|
+
' — <' +
|
|
866
|
+
h.tagName.toLowerCase() +
|
|
867
|
+
(h.id ? '#' + h.id : '') +
|
|
868
|
+
(h.className && typeof h.className === 'string'
|
|
869
|
+
? '.' + h.className.trim().split(/\s+/).slice(0, 2).join('.')
|
|
870
|
+
: '') +
|
|
871
|
+
'> intercepts pointer events';
|
|
943
872
|
}
|
|
944
|
-
|
|
873
|
+
return { ok: false, error: reason, retry: true };
|
|
945
874
|
}
|
|
946
|
-
|
|
875
|
+
}
|
|
876
|
+
if (action === 'hover')
|
|
877
|
+
hoverAt(el, p.x, p.y);
|
|
878
|
+
else if (action === 'tap') {
|
|
879
|
+
fire(el, 'touchstart');
|
|
880
|
+
fire(el, 'touchend');
|
|
881
|
+
clickAt(el, p.x, p.y, opts);
|
|
882
|
+
}
|
|
883
|
+
else
|
|
884
|
+
clickAt(el, p.x, p.y, {
|
|
885
|
+
...opts,
|
|
886
|
+
clickCount: action === 'dblclick' ? 2 : (opts.clickCount ?? 1),
|
|
887
|
+
});
|
|
888
|
+
return { ok: true };
|
|
889
|
+
}
|
|
890
|
+
if (action === 'focus') {
|
|
891
|
+
;
|
|
892
|
+
el.focus();
|
|
893
|
+
return { ok: true };
|
|
894
|
+
}
|
|
895
|
+
if (action === 'blur') {
|
|
896
|
+
;
|
|
897
|
+
el.blur();
|
|
898
|
+
return { ok: true };
|
|
899
|
+
}
|
|
900
|
+
if (action === 'fill' || action === 'clear') {
|
|
901
|
+
const value = action === 'clear' ? '' : String(opts.value ?? '');
|
|
902
|
+
el.focus();
|
|
903
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement) {
|
|
904
|
+
const simple = !(el instanceof HTMLInputElement) ||
|
|
905
|
+
!['checkbox', 'radio', 'file', 'button', 'submit', 'reset', 'image'].includes(el.type);
|
|
906
|
+
if (!simple)
|
|
907
|
+
return {
|
|
908
|
+
ok: false,
|
|
909
|
+
error: describe(steps) + ' — cannot fill an input of type ' + el.type,
|
|
910
|
+
};
|
|
911
|
+
if (el instanceof HTMLInputElement &&
|
|
912
|
+
[
|
|
913
|
+
'date',
|
|
914
|
+
'time',
|
|
915
|
+
'datetime-local',
|
|
916
|
+
'month',
|
|
917
|
+
'week',
|
|
918
|
+
'color',
|
|
919
|
+
'range',
|
|
920
|
+
'number',
|
|
921
|
+
].includes(el.type)) {
|
|
947
922
|
nativeSetValue(el, value);
|
|
948
|
-
fire(el, 'input');
|
|
949
|
-
fire(el, 'change');
|
|
923
|
+
fire(el, 'input', { inputType: 'insertText', data: value });
|
|
950
924
|
}
|
|
951
925
|
else {
|
|
952
|
-
const sel = window.getSelection();
|
|
953
|
-
if (sel) {
|
|
954
|
-
sel.selectAllChildren(el);
|
|
955
|
-
}
|
|
956
|
-
let done;
|
|
957
926
|
try {
|
|
958
|
-
|
|
927
|
+
el.select();
|
|
959
928
|
}
|
|
960
929
|
catch (_error) {
|
|
961
|
-
|
|
930
|
+
/* ignore */
|
|
962
931
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
932
|
+
fire(el, 'beforeinput', { inputType: 'insertText', data: value });
|
|
933
|
+
nativeSetValue(el, value);
|
|
934
|
+
try {
|
|
935
|
+
el.setSelectionRange(value.length, value.length);
|
|
966
936
|
}
|
|
937
|
+
catch (_error) {
|
|
938
|
+
/* ignore */
|
|
939
|
+
}
|
|
940
|
+
fire(el, 'input', { inputType: 'insertText', data: value });
|
|
967
941
|
}
|
|
968
|
-
|
|
969
|
-
}
|
|
970
|
-
if (action === 'type') {
|
|
971
|
-
;
|
|
972
|
-
el.focus();
|
|
973
|
-
for (const ch of String(opts.text ?? ''))
|
|
974
|
-
pressKey(el, ch === '\n' ? 'Enter' : ch);
|
|
975
|
-
return { ok: true };
|
|
942
|
+
fire(el, 'change');
|
|
976
943
|
}
|
|
977
|
-
if (
|
|
978
|
-
;
|
|
979
|
-
el
|
|
980
|
-
|
|
981
|
-
return { ok: true };
|
|
944
|
+
else if (el instanceof HTMLSelectElement) {
|
|
945
|
+
nativeSetValue(el, value);
|
|
946
|
+
fire(el, 'input');
|
|
947
|
+
fire(el, 'change');
|
|
982
948
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
if (isChecked(el) !== want) {
|
|
988
|
-
const p = pointAt(el);
|
|
989
|
-
clickAt(el, p.x, p.y, {});
|
|
990
|
-
if (isChecked(el) !== want)
|
|
991
|
-
return {
|
|
992
|
-
ok: false,
|
|
993
|
-
error: describe(steps) + ' — clicking did not change its checked state',
|
|
994
|
-
};
|
|
949
|
+
else {
|
|
950
|
+
const sel = window.getSelection();
|
|
951
|
+
if (sel) {
|
|
952
|
+
sel.selectAllChildren(el);
|
|
995
953
|
}
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
if (!(el instanceof HTMLSelectElement))
|
|
1000
|
-
return { ok: false, error: describe(steps) + ' — not a <select>' };
|
|
1001
|
-
const wanted = (Array.isArray(opts.values) ? opts.values : [opts.values]);
|
|
1002
|
-
const chosen = [];
|
|
1003
|
-
for (const option of Array.from(el.options)) {
|
|
1004
|
-
const match = wanted.some((w) => {
|
|
1005
|
-
if (w === null || w === undefined)
|
|
1006
|
-
return false;
|
|
1007
|
-
if (typeof w === 'string')
|
|
1008
|
-
return (option.value === w ||
|
|
1009
|
-
norm(option.label) === norm(w) ||
|
|
1010
|
-
norm(option.textContent) === norm(w));
|
|
1011
|
-
if (w.value !== undefined)
|
|
1012
|
-
return option.value === w.value;
|
|
1013
|
-
if (w.label !== undefined)
|
|
1014
|
-
return norm(option.label) === norm(w.label);
|
|
1015
|
-
if (w.index !== undefined)
|
|
1016
|
-
return option.index === w.index;
|
|
1017
|
-
return false;
|
|
1018
|
-
});
|
|
1019
|
-
option.selected = match;
|
|
1020
|
-
if (match)
|
|
1021
|
-
chosen.push(option.value);
|
|
954
|
+
let done;
|
|
955
|
+
try {
|
|
956
|
+
done = document.execCommand('insertText', false, value);
|
|
1022
957
|
}
|
|
1023
|
-
|
|
958
|
+
catch (_error) {
|
|
959
|
+
done = false;
|
|
960
|
+
}
|
|
961
|
+
if (!done) {
|
|
962
|
+
el.textContent = value;
|
|
963
|
+
fire(el, 'input', { inputType: 'insertText', data: value });
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return { ok: true };
|
|
967
|
+
}
|
|
968
|
+
if (action === 'type') {
|
|
969
|
+
;
|
|
970
|
+
el.focus();
|
|
971
|
+
for (const ch of String(opts.text ?? ''))
|
|
972
|
+
pressKey(el, ch === '\n' ? 'Enter' : ch);
|
|
973
|
+
return { ok: true };
|
|
974
|
+
}
|
|
975
|
+
if (action === 'press') {
|
|
976
|
+
;
|
|
977
|
+
el.focus();
|
|
978
|
+
pressKey(el, String(opts.key ?? ''));
|
|
979
|
+
return { ok: true };
|
|
980
|
+
}
|
|
981
|
+
if (action === 'check' || action === 'uncheck' || action === 'setChecked') {
|
|
982
|
+
if (!isCheckable(el))
|
|
983
|
+
return { ok: false, error: describe(steps) + ' — not a checkbox, radio or switch' };
|
|
984
|
+
const want = action === 'check' ? true : action === 'uncheck' ? false : Boolean(opts.checked);
|
|
985
|
+
if (isChecked(el) !== want) {
|
|
986
|
+
const p = pointAt(el);
|
|
987
|
+
clickAt(el, p.x, p.y, {});
|
|
988
|
+
if (isChecked(el) !== want)
|
|
1024
989
|
return {
|
|
1025
990
|
ok: false,
|
|
1026
|
-
error: describe(steps) + ' —
|
|
991
|
+
error: describe(steps) + ' — clicking did not change its checked state',
|
|
1027
992
|
};
|
|
1028
|
-
fire(el, 'input');
|
|
1029
|
-
fire(el, 'change');
|
|
1030
|
-
return { ok: true, values: chosen };
|
|
1031
993
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
return { ok:
|
|
994
|
+
return { ok: true };
|
|
995
|
+
}
|
|
996
|
+
if (action === 'selectOption') {
|
|
997
|
+
if (!(el instanceof HTMLSelectElement))
|
|
998
|
+
return { ok: false, error: describe(steps) + ' — not a <select>' };
|
|
999
|
+
const wanted = (Array.isArray(opts.values) ? opts.values : [opts.values]);
|
|
1000
|
+
const chosen = [];
|
|
1001
|
+
for (const option of Array.from(el.options)) {
|
|
1002
|
+
const match = wanted.some((w) => {
|
|
1003
|
+
if (w === null || w === undefined)
|
|
1004
|
+
return false;
|
|
1005
|
+
if (typeof w === 'string')
|
|
1006
|
+
return (option.value === w ||
|
|
1007
|
+
norm(option.label) === norm(w) ||
|
|
1008
|
+
norm(option.textContent) === norm(w));
|
|
1009
|
+
if (w.value !== undefined)
|
|
1010
|
+
return option.value === w.value;
|
|
1011
|
+
if (w.label !== undefined)
|
|
1012
|
+
return norm(option.label) === norm(w.label);
|
|
1013
|
+
if (w.index !== undefined)
|
|
1014
|
+
return option.index === w.index;
|
|
1015
|
+
return false;
|
|
1016
|
+
});
|
|
1017
|
+
option.selected = match;
|
|
1018
|
+
if (match)
|
|
1019
|
+
chosen.push(option.value);
|
|
1037
1020
|
}
|
|
1038
|
-
if (
|
|
1039
|
-
return {
|
|
1040
|
-
|
|
1021
|
+
if (chosen.length === 0 && wanted.length)
|
|
1022
|
+
return {
|
|
1023
|
+
ok: false,
|
|
1024
|
+
error: describe(steps) + ' — no option matched ' + JSON.stringify(wanted),
|
|
1025
|
+
};
|
|
1026
|
+
fire(el, 'input');
|
|
1027
|
+
fire(el, 'change');
|
|
1028
|
+
return { ok: true, values: chosen };
|
|
1041
1029
|
}
|
|
1042
|
-
if (
|
|
1043
|
-
|
|
1044
|
-
|
|
1030
|
+
if (action === 'dispatchEvent') {
|
|
1031
|
+
const type = String(opts.type ?? '');
|
|
1032
|
+
const init = opts.init ?? {};
|
|
1033
|
+
fire(el, type, init);
|
|
1034
|
+
return { ok: true };
|
|
1035
|
+
}
|
|
1036
|
+
if (action === 'highlight')
|
|
1037
|
+
return { ok: true };
|
|
1038
|
+
return { ok: false, error: 'unknown action: ' + action };
|
|
1045
1039
|
}
|
|
1040
|
+
return { ok: false, error: reason ?? 'waiting for ' + describe(steps), retry: true };
|
|
1046
1041
|
};
|
|
1047
1042
|
// ---- reads (strict) ----
|
|
1048
|
-
const single = async (steps,
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
},
|
|
1062
|
-
};
|
|
1063
|
-
await sleep(50);
|
|
1064
|
-
}
|
|
1043
|
+
const single = async (steps, waitAttached) => {
|
|
1044
|
+
const els = resolveChain(steps);
|
|
1045
|
+
if (els.length > 1)
|
|
1046
|
+
return { res: { ok: false, strict: true, count: els.length } };
|
|
1047
|
+
if (els.length === 1)
|
|
1048
|
+
return { el: els[0] };
|
|
1049
|
+
return {
|
|
1050
|
+
res: {
|
|
1051
|
+
ok: false,
|
|
1052
|
+
error: describe(steps) + ' — element not found',
|
|
1053
|
+
retry: waitAttached,
|
|
1054
|
+
},
|
|
1055
|
+
};
|
|
1065
1056
|
};
|
|
1066
1057
|
const boundingBox = (el) => {
|
|
1067
1058
|
if (!isVisible(el))
|
|
@@ -1069,7 +1060,7 @@ export function installE2ERuntime() {
|
|
|
1069
1060
|
const r = el.getBoundingClientRect();
|
|
1070
1061
|
return { x: r.x, y: r.y, width: r.width, height: r.height };
|
|
1071
1062
|
};
|
|
1072
|
-
const read = async (steps, what, args
|
|
1063
|
+
const read = async (steps, what, args) => {
|
|
1073
1064
|
if (what === 'isVisible' || what === 'isHidden') {
|
|
1074
1065
|
const els = resolveChain(steps);
|
|
1075
1066
|
if (els.length > 1)
|
|
@@ -1079,7 +1070,7 @@ export function installE2ERuntime() {
|
|
|
1079
1070
|
}
|
|
1080
1071
|
if (what === 'count')
|
|
1081
1072
|
return { ok: true, value: resolveChain(steps).length };
|
|
1082
|
-
const got = await single(steps,
|
|
1073
|
+
const got = await single(steps, true);
|
|
1083
1074
|
if (got.res)
|
|
1084
1075
|
return got.res;
|
|
1085
1076
|
const el = got.el;
|
|
@@ -1135,8 +1126,8 @@ export function installE2ERuntime() {
|
|
|
1135
1126
|
return { ok: false, error: 'unknown readAll: ' + what };
|
|
1136
1127
|
};
|
|
1137
1128
|
const runFn = (source) => new Function('return (' + source + ')')();
|
|
1138
|
-
const evalOn = async (steps, source, arg
|
|
1139
|
-
const got = await single(steps,
|
|
1129
|
+
const evalOn = async (steps, source, arg) => {
|
|
1130
|
+
const got = await single(steps, true);
|
|
1140
1131
|
if (got.res)
|
|
1141
1132
|
return got.res;
|
|
1142
1133
|
return { ok: true, value: await runFn(source)(got.el, arg) };
|
|
@@ -1145,27 +1136,23 @@ export function installE2ERuntime() {
|
|
|
1145
1136
|
ok: true,
|
|
1146
1137
|
value: await runFn(source)(resolveChain(steps), arg),
|
|
1147
1138
|
});
|
|
1148
|
-
const waitFor = async (steps, state
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
if (Date.now() > deadline)
|
|
1166
|
-
return { ok: false, error: describe(steps) + ' — waiting for ' + state, timeout: true };
|
|
1167
|
-
await sleep(50);
|
|
1168
|
-
}
|
|
1139
|
+
const waitFor = async (steps, state) => {
|
|
1140
|
+
const els = resolveChain(steps);
|
|
1141
|
+
if (els.length > 1 && state !== 'detached' && state !== 'hidden')
|
|
1142
|
+
return { ok: false, strict: true, count: els.length };
|
|
1143
|
+
const el = els[0];
|
|
1144
|
+
const met = state === 'attached'
|
|
1145
|
+
? !!el
|
|
1146
|
+
: state === 'detached'
|
|
1147
|
+
? !el
|
|
1148
|
+
: state === 'visible'
|
|
1149
|
+
? !!el && isVisible(el)
|
|
1150
|
+
: state === 'hidden'
|
|
1151
|
+
? !el || !isVisible(el)
|
|
1152
|
+
: false;
|
|
1153
|
+
if (met)
|
|
1154
|
+
return { ok: true };
|
|
1155
|
+
return { ok: false, error: describe(steps) + ' — waiting for ' + state, retry: true };
|
|
1169
1156
|
};
|
|
1170
1157
|
// ---- expect probes: one evaluation, the driver polls ----
|
|
1171
1158
|
const probe = (steps, matcher, args) => {
|
|
@@ -1457,22 +1444,21 @@ export function installE2ERuntime() {
|
|
|
1457
1444
|
const call = async (req) => {
|
|
1458
1445
|
const fn = String(req.fn);
|
|
1459
1446
|
const a = req.args ?? [];
|
|
1460
|
-
const deadline = Date.now() + Number(req.timeout ?? 10000);
|
|
1461
1447
|
switch (fn) {
|
|
1462
1448
|
case 'parse':
|
|
1463
1449
|
return parseSelector(String(a[0]));
|
|
1464
1450
|
case 'act':
|
|
1465
|
-
return act(a[0], String(a[1]), a[2] ?? {}
|
|
1451
|
+
return act(a[0], String(a[1]), a[2] ?? {});
|
|
1466
1452
|
case 'read':
|
|
1467
|
-
return read(a[0], String(a[1]), a[2] ?? []
|
|
1453
|
+
return read(a[0], String(a[1]), a[2] ?? []);
|
|
1468
1454
|
case 'readAll':
|
|
1469
1455
|
return readAll(a[0], String(a[1]));
|
|
1470
1456
|
case 'evalOn':
|
|
1471
|
-
return evalOn(a[0], String(a[1]), a[2]
|
|
1457
|
+
return evalOn(a[0], String(a[1]), a[2]);
|
|
1472
1458
|
case 'evalAll':
|
|
1473
1459
|
return evalAll(a[0], String(a[1]), a[2]);
|
|
1474
1460
|
case 'waitFor':
|
|
1475
|
-
return waitFor(a[0], String(a[1])
|
|
1461
|
+
return waitFor(a[0], String(a[1]));
|
|
1476
1462
|
case 'probe':
|
|
1477
1463
|
return probe(a[0], String(a[1]), a[2] ?? {});
|
|
1478
1464
|
case 'describe':
|