@jsenv/core 28.1.0 → 28.1.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/dist/js/script_type_module_supervisor.js +18 -25
- package/dist/js/supervisor.js +112 -118
- package/dist/main.js +17 -13
- package/package.json +4 -4
- package/src/execute/run.js +1 -1
- package/src/execute/runtimes/browsers/from_playwright.js +1 -1
- package/src/plugins/supervisor/client/script_type_module_supervisor.js +17 -26
- package/src/plugins/supervisor/client/supervisor.js +112 -118
- package/src/plugins/toolbar/client/execution/toolbar_execution.js +1 -1
- package/src/test/execute_test_plan.js +1 -1
|
@@ -1,45 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// https://twitter.com/damienmaillard/status/1554752482273787906
|
|
3
2
|
const isWebkitOrSafari = typeof window.webkitConvertPointFromNodeToPage === "function";
|
|
4
3
|
const superviseScriptTypeModule = async ({
|
|
5
4
|
src,
|
|
6
5
|
async
|
|
7
6
|
}) => {
|
|
7
|
+
const currentScript = document.querySelector(`script[type="module"][inlined-from-src="${src}"]`);
|
|
8
8
|
const execute = isWebkitOrSafari ? createExecuteWithDynamicImport({
|
|
9
9
|
src
|
|
10
10
|
}) : createExecuteWithScript({
|
|
11
|
+
currentScript,
|
|
11
12
|
src
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
src,
|
|
17
|
-
type: "js_module",
|
|
18
|
-
execute
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
return execution.start();
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
if (async) {
|
|
25
|
-
startExecution();
|
|
26
|
-
return;
|
|
27
|
-
} // there is guaranteed execution order for non async script type="module"
|
|
28
|
-
// see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
if (previousExecutionPromise) {
|
|
32
|
-
await previousExecutionPromise;
|
|
33
|
-
previousExecutionPromise = null;
|
|
15
|
+
if (!async) {
|
|
16
|
+
await window.__supervisor__.getPreviousExecutionDonePromise();
|
|
34
17
|
}
|
|
35
18
|
|
|
36
|
-
|
|
19
|
+
const execution = window.__supervisor__.createExecution({
|
|
20
|
+
src,
|
|
21
|
+
async,
|
|
22
|
+
type: "js_module",
|
|
23
|
+
execute
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return execution.start();
|
|
37
27
|
};
|
|
38
28
|
|
|
39
29
|
const createExecuteWithScript = ({
|
|
30
|
+
currentScript,
|
|
40
31
|
src
|
|
41
32
|
}) => {
|
|
42
|
-
const currentScript = document.querySelector(`script[type="module"][inlined-from-src="${src}"]`);
|
|
43
33
|
const parentNode = currentScript.parentNode;
|
|
44
34
|
let nodeToReplace;
|
|
45
35
|
let currentScriptClone;
|
|
@@ -48,7 +38,10 @@ const createExecuteWithScript = ({
|
|
|
48
38
|
}) => {
|
|
49
39
|
const urlObject = new URL(src, window.location);
|
|
50
40
|
const loadPromise = new Promise((resolve, reject) => {
|
|
51
|
-
currentScriptClone = document.createElement("script");
|
|
41
|
+
currentScriptClone = document.createElement("script"); // browsers set async by default when creating script(s)
|
|
42
|
+
// we want an exact copy to preserves how code is executed
|
|
43
|
+
|
|
44
|
+
currentScriptClone.async = false;
|
|
52
45
|
Array.from(currentScript.attributes).forEach(attribute => {
|
|
53
46
|
currentScriptClone.setAttribute(attribute.nodeName, attribute.nodeValue);
|
|
54
47
|
});
|
package/dist/js/supervisor.js
CHANGED
|
@@ -7,27 +7,17 @@ window.__supervisor__ = (() => {
|
|
|
7
7
|
reportError: notImplemented,
|
|
8
8
|
superviseScript: notImplemented,
|
|
9
9
|
reloadSupervisedScript: notImplemented,
|
|
10
|
-
|
|
11
|
-
getScriptExecutionResults: () => {
|
|
12
|
-
// wait for page to load before collecting script execution results
|
|
13
|
-
const htmlReadyPromise = new Promise((resolve) => {
|
|
14
|
-
if (document.readyState === "complete") {
|
|
15
|
-
resolve()
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
const loadCallback = () => {
|
|
19
|
-
window.removeEventListener("load", loadCallback)
|
|
20
|
-
resolve()
|
|
21
|
-
}
|
|
22
|
-
window.addEventListener("load", loadCallback)
|
|
23
|
-
})
|
|
24
|
-
return htmlReadyPromise.then(() => {
|
|
25
|
-
return supervisor.collectScriptResults()
|
|
26
|
-
})
|
|
27
|
-
},
|
|
10
|
+
getDocumentExecutionResult: notImplemented,
|
|
28
11
|
executionResults,
|
|
29
12
|
}
|
|
30
13
|
|
|
14
|
+
let navigationStartTime
|
|
15
|
+
try {
|
|
16
|
+
navigationStartTime = window.performance.timing.navigationStart
|
|
17
|
+
} catch (e) {
|
|
18
|
+
navigationStartTime = Date.now()
|
|
19
|
+
}
|
|
20
|
+
|
|
31
21
|
supervisor.setupReportException = ({
|
|
32
22
|
rootDirectoryUrl,
|
|
33
23
|
errorNotification,
|
|
@@ -728,11 +718,12 @@ window.__supervisor__ = (() => {
|
|
|
728
718
|
})
|
|
729
719
|
|
|
730
720
|
const supervisedScripts = []
|
|
731
|
-
const
|
|
732
|
-
supervisor.createExecution = ({ type, src, execute }) => {
|
|
721
|
+
const scriptExecutionPromises = []
|
|
722
|
+
supervisor.createExecution = ({ type, src, async, execute }) => {
|
|
733
723
|
const execution = {
|
|
734
724
|
type,
|
|
735
725
|
src,
|
|
726
|
+
async,
|
|
736
727
|
execute,
|
|
737
728
|
}
|
|
738
729
|
execution.start = () => {
|
|
@@ -753,21 +744,27 @@ window.__supervisor__ = (() => {
|
|
|
753
744
|
}
|
|
754
745
|
const executionResult = {
|
|
755
746
|
status: "pending",
|
|
747
|
+
startTime: Date.now(),
|
|
748
|
+
endTime: null,
|
|
756
749
|
exception: null,
|
|
757
750
|
namespace: null,
|
|
758
751
|
coverage: null,
|
|
759
752
|
}
|
|
760
753
|
executionResults[execution.src] = executionResult
|
|
761
|
-
let
|
|
762
|
-
const
|
|
763
|
-
|
|
754
|
+
let resolveScriptExecutionPromise
|
|
755
|
+
const scriptExecutionPromise = new Promise((resolve) => {
|
|
756
|
+
resolveScriptExecutionPromise = () => {
|
|
757
|
+
executionResult.endTime = Date.now()
|
|
758
|
+
if (measurePerf) {
|
|
759
|
+
performance.measure(`execution`, `execution_start`)
|
|
760
|
+
}
|
|
761
|
+
resolve()
|
|
762
|
+
}
|
|
764
763
|
})
|
|
765
|
-
|
|
764
|
+
scriptExecutionPromise.execution = execution
|
|
765
|
+
scriptExecutionPromises.push(scriptExecutionPromise)
|
|
766
766
|
try {
|
|
767
767
|
const result = await execution.execute({ isReload })
|
|
768
|
-
if (measurePerf) {
|
|
769
|
-
performance.measure(`execution`, `execution_start`)
|
|
770
|
-
}
|
|
771
768
|
executionResult.status = "completed"
|
|
772
769
|
executionResult.namespace = result
|
|
773
770
|
executionResult.coverage = window.__coverage__
|
|
@@ -775,11 +772,8 @@ window.__supervisor__ = (() => {
|
|
|
775
772
|
console.log(`${execution.type} load ended`)
|
|
776
773
|
console.groupEnd()
|
|
777
774
|
}
|
|
778
|
-
|
|
775
|
+
resolveScriptExecutionPromise()
|
|
779
776
|
} catch (e) {
|
|
780
|
-
if (measurePerf) {
|
|
781
|
-
performance.measure(`execution`, `execution_start`)
|
|
782
|
-
}
|
|
783
777
|
executionResult.status = "errored"
|
|
784
778
|
const exception = supervisor.createException({
|
|
785
779
|
reason: e,
|
|
@@ -792,73 +786,79 @@ window.__supervisor__ = (() => {
|
|
|
792
786
|
if (logs) {
|
|
793
787
|
console.groupEnd()
|
|
794
788
|
}
|
|
795
|
-
|
|
789
|
+
resolveScriptExecutionPromise()
|
|
796
790
|
}
|
|
797
791
|
}
|
|
798
|
-
|
|
792
|
+
|
|
793
|
+
// respect execution order
|
|
794
|
+
// - wait for classic scripts to be done (non async)
|
|
795
|
+
// - wait module script previous execution (non async)
|
|
796
|
+
// see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
|
|
797
|
+
supervisor.getPreviousExecutionDonePromise = async () => {
|
|
798
|
+
const previousNonAsyncScriptExecutions = scriptExecutionPromises.filter(
|
|
799
|
+
(promise) => !promise.execution.async,
|
|
800
|
+
)
|
|
801
|
+
await Promise.all(previousNonAsyncScriptExecutions)
|
|
802
|
+
}
|
|
799
803
|
supervisor.superviseScript = async ({ src, async }) => {
|
|
800
804
|
const { currentScript } = document
|
|
801
805
|
const parentNode = currentScript.parentNode
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
currentScriptClone.removeAttribute("jsenv-plugin-owner")
|
|
826
|
-
currentScriptClone.removeAttribute("jsenv-plugin-action")
|
|
827
|
-
currentScriptClone.removeAttribute("inlined-from-src")
|
|
828
|
-
currentScriptClone.removeAttribute("original-position")
|
|
829
|
-
currentScriptClone.removeAttribute("original-src-position")
|
|
830
|
-
nodeToReplace = currentScript
|
|
831
|
-
currentScriptClone.src = src
|
|
832
|
-
}
|
|
833
|
-
currentScriptClone.addEventListener("error", reject)
|
|
834
|
-
currentScriptClone.addEventListener("load", resolve)
|
|
835
|
-
parentNode.replaceChild(currentScriptClone, nodeToReplace)
|
|
806
|
+
if (!async) {
|
|
807
|
+
await supervisor.getPreviousExecutionDonePromise()
|
|
808
|
+
}
|
|
809
|
+
let nodeToReplace
|
|
810
|
+
let currentScriptClone
|
|
811
|
+
const execution = supervisor.createExecution({
|
|
812
|
+
src,
|
|
813
|
+
type: "js_classic",
|
|
814
|
+
async,
|
|
815
|
+
execute: async ({ isReload }) => {
|
|
816
|
+
const urlObject = new URL(src, window.location)
|
|
817
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
818
|
+
// do not use script.cloneNode()
|
|
819
|
+
// bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
|
|
820
|
+
currentScriptClone = document.createElement("script")
|
|
821
|
+
// browsers set async by default when creating script(s)
|
|
822
|
+
// we want an exact copy to preserves how code is executed
|
|
823
|
+
currentScriptClone.async = false
|
|
824
|
+
Array.from(currentScript.attributes).forEach((attribute) => {
|
|
825
|
+
currentScriptClone.setAttribute(
|
|
826
|
+
attribute.nodeName,
|
|
827
|
+
attribute.nodeValue,
|
|
828
|
+
)
|
|
836
829
|
})
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
830
|
+
if (isReload) {
|
|
831
|
+
urlObject.searchParams.set("hmr", Date.now())
|
|
832
|
+
nodeToReplace = currentScriptClone
|
|
833
|
+
currentScriptClone.src = urlObject.href
|
|
834
|
+
} else {
|
|
835
|
+
currentScriptClone.removeAttribute("jsenv-plugin-owner")
|
|
836
|
+
currentScriptClone.removeAttribute("jsenv-plugin-action")
|
|
837
|
+
currentScriptClone.removeAttribute("inlined-from-src")
|
|
838
|
+
currentScriptClone.removeAttribute("original-position")
|
|
839
|
+
currentScriptClone.removeAttribute("original-src-position")
|
|
840
|
+
nodeToReplace = currentScript
|
|
841
|
+
currentScriptClone.src = src
|
|
848
842
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
843
|
+
currentScriptClone.addEventListener("error", reject)
|
|
844
|
+
currentScriptClone.addEventListener("load", resolve)
|
|
845
|
+
parentNode.replaceChild(currentScriptClone, nodeToReplace)
|
|
846
|
+
})
|
|
847
|
+
try {
|
|
848
|
+
await loadPromise
|
|
849
|
+
} catch (e) {
|
|
850
|
+
// eslint-disable-next-line no-throw-literal
|
|
851
|
+
throw {
|
|
852
|
+
message: `Failed to fetch script: ${urlObject.href}`,
|
|
853
|
+
reportedBy: "script_error_event",
|
|
854
|
+
url: urlObject.href,
|
|
855
|
+
// window.error won't be dispatched for this error
|
|
856
|
+
needsReport: true,
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
})
|
|
861
|
+
return execution.start()
|
|
862
862
|
}
|
|
863
863
|
supervisor.reloadSupervisedScript = ({ type, src }) => {
|
|
864
864
|
const supervisedScript = supervisedScripts.find(
|
|
@@ -876,45 +876,39 @@ window.__supervisor__ = (() => {
|
|
|
876
876
|
supervisedScript.reload()
|
|
877
877
|
}
|
|
878
878
|
}
|
|
879
|
-
supervisor.
|
|
879
|
+
supervisor.getDocumentExecutionResult = async () => {
|
|
880
880
|
// just to be super safe and ensure any <script type="module"> got a chance to execute
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
scriptTypeModule.type = "module"
|
|
884
|
-
scriptTypeModule.innerText =
|
|
885
|
-
"window.__supervisor__.scriptModuleCallback()"
|
|
886
|
-
window.__supervisor__.scriptModuleCallback = () => {
|
|
887
|
-
scriptTypeModule.parentNode.removeChild(scriptTypeModule)
|
|
881
|
+
const documentReadyPromise = new Promise((resolve) => {
|
|
882
|
+
if (document.readyState === "complete") {
|
|
888
883
|
resolve()
|
|
884
|
+
return
|
|
889
885
|
}
|
|
890
|
-
|
|
886
|
+
const loadCallback = () => {
|
|
887
|
+
window.removeEventListener("load", loadCallback)
|
|
888
|
+
resolve()
|
|
889
|
+
}
|
|
890
|
+
window.addEventListener("load", loadCallback)
|
|
891
891
|
})
|
|
892
|
-
await
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
892
|
+
await documentReadyPromise
|
|
893
|
+
const waitScriptExecutions = async () => {
|
|
894
|
+
const numberOfScripts = scriptExecutionPromises.length
|
|
895
|
+
await Promise.all(scriptExecutionPromises)
|
|
896
|
+
// new scripts added while the other where executing
|
|
897
|
+
// (should happen only on webkit where
|
|
898
|
+
// script might be added after window load event)
|
|
899
|
+
if (scriptExecutionPromises.length > numberOfScripts) {
|
|
900
|
+
await waitScriptExecutions()
|
|
900
901
|
}
|
|
901
902
|
}
|
|
902
|
-
await
|
|
903
|
+
await waitScriptExecutions()
|
|
904
|
+
|
|
903
905
|
return {
|
|
904
906
|
status: "completed",
|
|
905
907
|
executionResults,
|
|
906
|
-
startTime:
|
|
908
|
+
startTime: navigationStartTime,
|
|
907
909
|
endTime: Date.now(),
|
|
908
910
|
}
|
|
909
911
|
}
|
|
910
|
-
|
|
911
|
-
const getNavigationStartTime = () => {
|
|
912
|
-
try {
|
|
913
|
-
return window.performance.timing.navigationStart
|
|
914
|
-
} catch (e) {
|
|
915
|
-
return Date.now()
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
912
|
}
|
|
919
913
|
|
|
920
914
|
return supervisor
|
package/dist/main.js
CHANGED
|
@@ -1320,29 +1320,33 @@ const createOperation = () => {
|
|
|
1320
1320
|
};
|
|
1321
1321
|
|
|
1322
1322
|
const addAbortSource = abortSourceCallback => {
|
|
1323
|
+
const abortSource = {
|
|
1324
|
+
cleaned: false,
|
|
1325
|
+
signal: null,
|
|
1326
|
+
remove: callbackNoop
|
|
1327
|
+
};
|
|
1323
1328
|
const abortSourceController = new AbortController();
|
|
1324
1329
|
const abortSourceSignal = abortSourceController.signal;
|
|
1330
|
+
abortSource.signal = abortSourceSignal;
|
|
1325
1331
|
|
|
1326
1332
|
if (operationSignal.aborted) {
|
|
1327
|
-
return
|
|
1328
|
-
signal: abortSourceSignal,
|
|
1329
|
-
remove: callbackNoop
|
|
1330
|
-
};
|
|
1333
|
+
return abortSource;
|
|
1331
1334
|
}
|
|
1332
1335
|
|
|
1333
1336
|
const returnValue = abortSourceCallback(value => {
|
|
1334
1337
|
abortSourceController.abort(value);
|
|
1335
1338
|
});
|
|
1336
|
-
const removeAbortSource = typeof returnValue === "function" ? returnValue : callbackNoop;
|
|
1337
1339
|
const removeAbortSignal = addAbortSignal(abortSourceSignal, {
|
|
1338
1340
|
onRemove: () => {
|
|
1339
|
-
|
|
1341
|
+
if (typeof returnValue === "function") {
|
|
1342
|
+
returnValue();
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
abortSource.cleaned = true;
|
|
1340
1346
|
}
|
|
1341
1347
|
});
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
remove: removeAbortSignal
|
|
1345
|
-
};
|
|
1348
|
+
abortSource.remove = removeAbortSignal;
|
|
1349
|
+
return abortSource;
|
|
1346
1350
|
};
|
|
1347
1351
|
|
|
1348
1352
|
const timeout = ms => {
|
|
@@ -26503,7 +26507,7 @@ const run = async ({
|
|
|
26503
26507
|
} catch (e) {
|
|
26504
26508
|
cb({
|
|
26505
26509
|
status: "errored",
|
|
26506
|
-
|
|
26510
|
+
errors: [e]
|
|
26507
26511
|
});
|
|
26508
26512
|
}
|
|
26509
26513
|
}
|
|
@@ -27575,7 +27579,7 @@ const executeTestPlan = async ({
|
|
|
27575
27579
|
testPlan,
|
|
27576
27580
|
updateProcessExitCode = true,
|
|
27577
27581
|
maxExecutionsInParallel = 1,
|
|
27578
|
-
defaultMsAllocatedPerExecution =
|
|
27582
|
+
defaultMsAllocatedPerExecution = 30_000,
|
|
27579
27583
|
failFast = false,
|
|
27580
27584
|
// keepRunning: false to ensure runtime is stopped once executed
|
|
27581
27585
|
// because we have what we wants: execution is completed and
|
|
@@ -28036,7 +28040,7 @@ const createRuntimeFromPlaywright = ({
|
|
|
28036
28040
|
throw new Error(`window.__supervisor__ not found`);
|
|
28037
28041
|
}
|
|
28038
28042
|
|
|
28039
|
-
return window.__supervisor__.
|
|
28043
|
+
return window.__supervisor__.getDocumentExecutionResult();
|
|
28040
28044
|
}
|
|
28041
28045
|
/* eslint-enable no-undef */
|
|
28042
28046
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jsenv/core",
|
|
3
|
-
"version": "28.1.
|
|
3
|
+
"version": "28.1.3",
|
|
4
4
|
"description": "Tool to develop, test and build js projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -65,15 +65,15 @@
|
|
|
65
65
|
"@babel/plugin-transform-modules-umd": "7.18.6",
|
|
66
66
|
"@c88/v8-coverage": "0.1.1",
|
|
67
67
|
"@financial-times/polyfill-useragent-normaliser": "2.0.1",
|
|
68
|
-
"@jsenv/abort": "4.2.
|
|
68
|
+
"@jsenv/abort": "4.2.4",
|
|
69
69
|
"@jsenv/ast": "1.2.0",
|
|
70
70
|
"@jsenv/babel-plugins": "1.0.6",
|
|
71
|
-
"@jsenv/filesystem": "4.1.
|
|
71
|
+
"@jsenv/filesystem": "4.1.3",
|
|
72
72
|
"@jsenv/importmap": "1.2.1",
|
|
73
73
|
"@jsenv/integrity": "0.0.1",
|
|
74
74
|
"@jsenv/log": "3.2.0",
|
|
75
75
|
"@jsenv/node-esm-resolution": "0.1.0",
|
|
76
|
-
"@jsenv/server": "14.1.
|
|
76
|
+
"@jsenv/server": "14.1.2",
|
|
77
77
|
"@jsenv/sourcemap": "1.0.4",
|
|
78
78
|
"@jsenv/uneval": "1.6.0",
|
|
79
79
|
"@jsenv/url-meta": "7.0.0",
|
package/src/execute/run.js
CHANGED
|
@@ -306,7 +306,7 @@ export const createRuntimeFromPlaywright = ({
|
|
|
306
306
|
if (!window.__supervisor__) {
|
|
307
307
|
throw new Error(`window.__supervisor__ not found`)
|
|
308
308
|
}
|
|
309
|
-
return window.__supervisor__.
|
|
309
|
+
return window.__supervisor__.getDocumentExecutionResult()
|
|
310
310
|
},
|
|
311
311
|
/* eslint-enable no-undef */
|
|
312
312
|
)
|
|
@@ -1,46 +1,37 @@
|
|
|
1
|
-
let previousExecutionPromise
|
|
2
|
-
|
|
3
1
|
// https://twitter.com/damienmaillard/status/1554752482273787906
|
|
4
2
|
const isWebkitOrSafari =
|
|
5
3
|
typeof window.webkitConvertPointFromNodeToPage === "function"
|
|
6
4
|
|
|
7
5
|
export const superviseScriptTypeModule = async ({ src, async }) => {
|
|
6
|
+
const currentScript = document.querySelector(
|
|
7
|
+
`script[type="module"][inlined-from-src="${src}"]`,
|
|
8
|
+
)
|
|
8
9
|
const execute = isWebkitOrSafari
|
|
9
10
|
? createExecuteWithDynamicImport({ src })
|
|
10
|
-
: createExecuteWithScript({ src })
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
src,
|
|
14
|
-
type: "js_module",
|
|
15
|
-
execute,
|
|
16
|
-
})
|
|
17
|
-
return execution.start()
|
|
18
|
-
}
|
|
19
|
-
if (async) {
|
|
20
|
-
startExecution()
|
|
21
|
-
return
|
|
11
|
+
: createExecuteWithScript({ currentScript, src })
|
|
12
|
+
if (!async) {
|
|
13
|
+
await window.__supervisor__.getPreviousExecutionDonePromise()
|
|
22
14
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
15
|
+
const execution = window.__supervisor__.createExecution({
|
|
16
|
+
src,
|
|
17
|
+
async,
|
|
18
|
+
type: "js_module",
|
|
19
|
+
execute,
|
|
20
|
+
})
|
|
21
|
+
return execution.start()
|
|
30
22
|
}
|
|
31
23
|
|
|
32
|
-
const createExecuteWithScript = ({ src }) => {
|
|
33
|
-
const currentScript = document.querySelector(
|
|
34
|
-
`script[type="module"][inlined-from-src="${src}"]`,
|
|
35
|
-
)
|
|
24
|
+
const createExecuteWithScript = ({ currentScript, src }) => {
|
|
36
25
|
const parentNode = currentScript.parentNode
|
|
37
26
|
let nodeToReplace
|
|
38
27
|
let currentScriptClone
|
|
39
|
-
|
|
40
28
|
return async ({ isReload }) => {
|
|
41
29
|
const urlObject = new URL(src, window.location)
|
|
42
30
|
const loadPromise = new Promise((resolve, reject) => {
|
|
43
31
|
currentScriptClone = document.createElement("script")
|
|
32
|
+
// browsers set async by default when creating script(s)
|
|
33
|
+
// we want an exact copy to preserves how code is executed
|
|
34
|
+
currentScriptClone.async = false
|
|
44
35
|
Array.from(currentScript.attributes).forEach((attribute) => {
|
|
45
36
|
currentScriptClone.setAttribute(attribute.nodeName, attribute.nodeValue)
|
|
46
37
|
})
|
|
@@ -7,27 +7,17 @@ window.__supervisor__ = (() => {
|
|
|
7
7
|
reportError: notImplemented,
|
|
8
8
|
superviseScript: notImplemented,
|
|
9
9
|
reloadSupervisedScript: notImplemented,
|
|
10
|
-
|
|
11
|
-
getScriptExecutionResults: () => {
|
|
12
|
-
// wait for page to load before collecting script execution results
|
|
13
|
-
const htmlReadyPromise = new Promise((resolve) => {
|
|
14
|
-
if (document.readyState === "complete") {
|
|
15
|
-
resolve()
|
|
16
|
-
return
|
|
17
|
-
}
|
|
18
|
-
const loadCallback = () => {
|
|
19
|
-
window.removeEventListener("load", loadCallback)
|
|
20
|
-
resolve()
|
|
21
|
-
}
|
|
22
|
-
window.addEventListener("load", loadCallback)
|
|
23
|
-
})
|
|
24
|
-
return htmlReadyPromise.then(() => {
|
|
25
|
-
return supervisor.collectScriptResults()
|
|
26
|
-
})
|
|
27
|
-
},
|
|
10
|
+
getDocumentExecutionResult: notImplemented,
|
|
28
11
|
executionResults,
|
|
29
12
|
}
|
|
30
13
|
|
|
14
|
+
let navigationStartTime
|
|
15
|
+
try {
|
|
16
|
+
navigationStartTime = window.performance.timing.navigationStart
|
|
17
|
+
} catch (e) {
|
|
18
|
+
navigationStartTime = Date.now()
|
|
19
|
+
}
|
|
20
|
+
|
|
31
21
|
supervisor.setupReportException = ({
|
|
32
22
|
rootDirectoryUrl,
|
|
33
23
|
errorNotification,
|
|
@@ -728,11 +718,12 @@ window.__supervisor__ = (() => {
|
|
|
728
718
|
})
|
|
729
719
|
|
|
730
720
|
const supervisedScripts = []
|
|
731
|
-
const
|
|
732
|
-
supervisor.createExecution = ({ type, src, execute }) => {
|
|
721
|
+
const scriptExecutionPromises = []
|
|
722
|
+
supervisor.createExecution = ({ type, src, async, execute }) => {
|
|
733
723
|
const execution = {
|
|
734
724
|
type,
|
|
735
725
|
src,
|
|
726
|
+
async,
|
|
736
727
|
execute,
|
|
737
728
|
}
|
|
738
729
|
execution.start = () => {
|
|
@@ -753,21 +744,27 @@ window.__supervisor__ = (() => {
|
|
|
753
744
|
}
|
|
754
745
|
const executionResult = {
|
|
755
746
|
status: "pending",
|
|
747
|
+
startTime: Date.now(),
|
|
748
|
+
endTime: null,
|
|
756
749
|
exception: null,
|
|
757
750
|
namespace: null,
|
|
758
751
|
coverage: null,
|
|
759
752
|
}
|
|
760
753
|
executionResults[execution.src] = executionResult
|
|
761
|
-
let
|
|
762
|
-
const
|
|
763
|
-
|
|
754
|
+
let resolveScriptExecutionPromise
|
|
755
|
+
const scriptExecutionPromise = new Promise((resolve) => {
|
|
756
|
+
resolveScriptExecutionPromise = () => {
|
|
757
|
+
executionResult.endTime = Date.now()
|
|
758
|
+
if (measurePerf) {
|
|
759
|
+
performance.measure(`execution`, `execution_start`)
|
|
760
|
+
}
|
|
761
|
+
resolve()
|
|
762
|
+
}
|
|
764
763
|
})
|
|
765
|
-
|
|
764
|
+
scriptExecutionPromise.execution = execution
|
|
765
|
+
scriptExecutionPromises.push(scriptExecutionPromise)
|
|
766
766
|
try {
|
|
767
767
|
const result = await execution.execute({ isReload })
|
|
768
|
-
if (measurePerf) {
|
|
769
|
-
performance.measure(`execution`, `execution_start`)
|
|
770
|
-
}
|
|
771
768
|
executionResult.status = "completed"
|
|
772
769
|
executionResult.namespace = result
|
|
773
770
|
executionResult.coverage = window.__coverage__
|
|
@@ -775,11 +772,8 @@ window.__supervisor__ = (() => {
|
|
|
775
772
|
console.log(`${execution.type} load ended`)
|
|
776
773
|
console.groupEnd()
|
|
777
774
|
}
|
|
778
|
-
|
|
775
|
+
resolveScriptExecutionPromise()
|
|
779
776
|
} catch (e) {
|
|
780
|
-
if (measurePerf) {
|
|
781
|
-
performance.measure(`execution`, `execution_start`)
|
|
782
|
-
}
|
|
783
777
|
executionResult.status = "errored"
|
|
784
778
|
const exception = supervisor.createException({
|
|
785
779
|
reason: e,
|
|
@@ -792,73 +786,79 @@ window.__supervisor__ = (() => {
|
|
|
792
786
|
if (logs) {
|
|
793
787
|
console.groupEnd()
|
|
794
788
|
}
|
|
795
|
-
|
|
789
|
+
resolveScriptExecutionPromise()
|
|
796
790
|
}
|
|
797
791
|
}
|
|
798
|
-
|
|
792
|
+
|
|
793
|
+
// respect execution order
|
|
794
|
+
// - wait for classic scripts to be done (non async)
|
|
795
|
+
// - wait module script previous execution (non async)
|
|
796
|
+
// see https://gist.github.com/jakub-g/385ee6b41085303a53ad92c7c8afd7a6#typemodule-vs-non-module-typetextjavascript-vs-script-nomodule
|
|
797
|
+
supervisor.getPreviousExecutionDonePromise = async () => {
|
|
798
|
+
const previousNonAsyncScriptExecutions = scriptExecutionPromises.filter(
|
|
799
|
+
(promise) => !promise.execution.async,
|
|
800
|
+
)
|
|
801
|
+
await Promise.all(previousNonAsyncScriptExecutions)
|
|
802
|
+
}
|
|
799
803
|
supervisor.superviseScript = async ({ src, async }) => {
|
|
800
804
|
const { currentScript } = document
|
|
801
805
|
const parentNode = currentScript.parentNode
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
currentScriptClone.removeAttribute("jsenv-plugin-owner")
|
|
826
|
-
currentScriptClone.removeAttribute("jsenv-plugin-action")
|
|
827
|
-
currentScriptClone.removeAttribute("inlined-from-src")
|
|
828
|
-
currentScriptClone.removeAttribute("original-position")
|
|
829
|
-
currentScriptClone.removeAttribute("original-src-position")
|
|
830
|
-
nodeToReplace = currentScript
|
|
831
|
-
currentScriptClone.src = src
|
|
832
|
-
}
|
|
833
|
-
currentScriptClone.addEventListener("error", reject)
|
|
834
|
-
currentScriptClone.addEventListener("load", resolve)
|
|
835
|
-
parentNode.replaceChild(currentScriptClone, nodeToReplace)
|
|
806
|
+
if (!async) {
|
|
807
|
+
await supervisor.getPreviousExecutionDonePromise()
|
|
808
|
+
}
|
|
809
|
+
let nodeToReplace
|
|
810
|
+
let currentScriptClone
|
|
811
|
+
const execution = supervisor.createExecution({
|
|
812
|
+
src,
|
|
813
|
+
type: "js_classic",
|
|
814
|
+
async,
|
|
815
|
+
execute: async ({ isReload }) => {
|
|
816
|
+
const urlObject = new URL(src, window.location)
|
|
817
|
+
const loadPromise = new Promise((resolve, reject) => {
|
|
818
|
+
// do not use script.cloneNode()
|
|
819
|
+
// bcause https://stackoverflow.com/questions/28771542/why-dont-clonenode-script-tags-execute
|
|
820
|
+
currentScriptClone = document.createElement("script")
|
|
821
|
+
// browsers set async by default when creating script(s)
|
|
822
|
+
// we want an exact copy to preserves how code is executed
|
|
823
|
+
currentScriptClone.async = false
|
|
824
|
+
Array.from(currentScript.attributes).forEach((attribute) => {
|
|
825
|
+
currentScriptClone.setAttribute(
|
|
826
|
+
attribute.nodeName,
|
|
827
|
+
attribute.nodeValue,
|
|
828
|
+
)
|
|
836
829
|
})
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
830
|
+
if (isReload) {
|
|
831
|
+
urlObject.searchParams.set("hmr", Date.now())
|
|
832
|
+
nodeToReplace = currentScriptClone
|
|
833
|
+
currentScriptClone.src = urlObject.href
|
|
834
|
+
} else {
|
|
835
|
+
currentScriptClone.removeAttribute("jsenv-plugin-owner")
|
|
836
|
+
currentScriptClone.removeAttribute("jsenv-plugin-action")
|
|
837
|
+
currentScriptClone.removeAttribute("inlined-from-src")
|
|
838
|
+
currentScriptClone.removeAttribute("original-position")
|
|
839
|
+
currentScriptClone.removeAttribute("original-src-position")
|
|
840
|
+
nodeToReplace = currentScript
|
|
841
|
+
currentScriptClone.src = src
|
|
848
842
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
843
|
+
currentScriptClone.addEventListener("error", reject)
|
|
844
|
+
currentScriptClone.addEventListener("load", resolve)
|
|
845
|
+
parentNode.replaceChild(currentScriptClone, nodeToReplace)
|
|
846
|
+
})
|
|
847
|
+
try {
|
|
848
|
+
await loadPromise
|
|
849
|
+
} catch (e) {
|
|
850
|
+
// eslint-disable-next-line no-throw-literal
|
|
851
|
+
throw {
|
|
852
|
+
message: `Failed to fetch script: ${urlObject.href}`,
|
|
853
|
+
reportedBy: "script_error_event",
|
|
854
|
+
url: urlObject.href,
|
|
855
|
+
// window.error won't be dispatched for this error
|
|
856
|
+
needsReport: true,
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
},
|
|
860
|
+
})
|
|
861
|
+
return execution.start()
|
|
862
862
|
}
|
|
863
863
|
supervisor.reloadSupervisedScript = ({ type, src }) => {
|
|
864
864
|
const supervisedScript = supervisedScripts.find(
|
|
@@ -876,45 +876,39 @@ window.__supervisor__ = (() => {
|
|
|
876
876
|
supervisedScript.reload()
|
|
877
877
|
}
|
|
878
878
|
}
|
|
879
|
-
supervisor.
|
|
879
|
+
supervisor.getDocumentExecutionResult = async () => {
|
|
880
880
|
// just to be super safe and ensure any <script type="module"> got a chance to execute
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
scriptTypeModule.type = "module"
|
|
884
|
-
scriptTypeModule.innerText =
|
|
885
|
-
"window.__supervisor__.scriptModuleCallback()"
|
|
886
|
-
window.__supervisor__.scriptModuleCallback = () => {
|
|
887
|
-
scriptTypeModule.parentNode.removeChild(scriptTypeModule)
|
|
881
|
+
const documentReadyPromise = new Promise((resolve) => {
|
|
882
|
+
if (document.readyState === "complete") {
|
|
888
883
|
resolve()
|
|
884
|
+
return
|
|
889
885
|
}
|
|
890
|
-
|
|
886
|
+
const loadCallback = () => {
|
|
887
|
+
window.removeEventListener("load", loadCallback)
|
|
888
|
+
resolve()
|
|
889
|
+
}
|
|
890
|
+
window.addEventListener("load", loadCallback)
|
|
891
891
|
})
|
|
892
|
-
await
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
892
|
+
await documentReadyPromise
|
|
893
|
+
const waitScriptExecutions = async () => {
|
|
894
|
+
const numberOfScripts = scriptExecutionPromises.length
|
|
895
|
+
await Promise.all(scriptExecutionPromises)
|
|
896
|
+
// new scripts added while the other where executing
|
|
897
|
+
// (should happen only on webkit where
|
|
898
|
+
// script might be added after window load event)
|
|
899
|
+
if (scriptExecutionPromises.length > numberOfScripts) {
|
|
900
|
+
await waitScriptExecutions()
|
|
900
901
|
}
|
|
901
902
|
}
|
|
902
|
-
await
|
|
903
|
+
await waitScriptExecutions()
|
|
904
|
+
|
|
903
905
|
return {
|
|
904
906
|
status: "completed",
|
|
905
907
|
executionResults,
|
|
906
|
-
startTime:
|
|
908
|
+
startTime: navigationStartTime,
|
|
907
909
|
endTime: Date.now(),
|
|
908
910
|
}
|
|
909
911
|
}
|
|
910
|
-
|
|
911
|
-
const getNavigationStartTime = () => {
|
|
912
|
-
try {
|
|
913
|
-
return window.performance.timing.navigationStart
|
|
914
|
-
} catch (e) {
|
|
915
|
-
return Date.now()
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
912
|
}
|
|
919
913
|
|
|
920
914
|
return supervisor
|
|
@@ -9,7 +9,7 @@ export const renderExecutionInToolbar = async () => {
|
|
|
9
9
|
removeForceHideElement(document.querySelector("#execution-indicator"))
|
|
10
10
|
|
|
11
11
|
const { status, startTime, endTime } =
|
|
12
|
-
await window.parent.__supervisor__.
|
|
12
|
+
await window.parent.__supervisor__.getDocumentExecutionResult()
|
|
13
13
|
const execution = { status, startTime, endTime }
|
|
14
14
|
applyExecutionIndicator(execution)
|
|
15
15
|
const executionStorageKey = window.location.href
|
|
@@ -52,7 +52,7 @@ export const executeTestPlan = async ({
|
|
|
52
52
|
testPlan,
|
|
53
53
|
updateProcessExitCode = true,
|
|
54
54
|
maxExecutionsInParallel = 1,
|
|
55
|
-
defaultMsAllocatedPerExecution =
|
|
55
|
+
defaultMsAllocatedPerExecution = 30_000,
|
|
56
56
|
failFast = false,
|
|
57
57
|
// keepRunning: false to ensure runtime is stopped once executed
|
|
58
58
|
// because we have what we wants: execution is completed and
|