@jsenv/core 34.0.3 → 34.1.1
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/jsenv.js +281 -202
- package/package.json +1 -1
- package/src/dev/file_service.js +5 -10
- package/src/execute/runtimes/browsers/from_playwright.js +29 -127
- package/src/execute/runtimes/browsers/middleware_istanbul.js +65 -0
- package/src/execute/runtimes/browsers/middleware_js_supervisor.js +100 -0
- package/src/plugins/transpilation/babel/jsenv_plugin_babel.js +0 -19
- package/src/test/execute_steps.js +0 -2
- package/src/test/execute_test_plan.js +23 -4
- package/src/web_url_converter.js +28 -0
package/dist/jsenv.js
CHANGED
|
@@ -18947,54 +18947,6 @@ const splitFileExtension$1 = filename => {
|
|
|
18947
18947
|
return [filename.slice(0, dotLastIndex), filename.slice(dotLastIndex)];
|
|
18948
18948
|
};
|
|
18949
18949
|
|
|
18950
|
-
// https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
|
|
18951
|
-
|
|
18952
|
-
const babelPluginInstrument = (api, {
|
|
18953
|
-
useInlineSourceMaps = false
|
|
18954
|
-
}) => {
|
|
18955
|
-
const {
|
|
18956
|
-
programVisitor
|
|
18957
|
-
} = requireFromJsenv("istanbul-lib-instrument");
|
|
18958
|
-
const {
|
|
18959
|
-
types
|
|
18960
|
-
} = api;
|
|
18961
|
-
return {
|
|
18962
|
-
name: "transform-instrument",
|
|
18963
|
-
visitor: {
|
|
18964
|
-
Program: {
|
|
18965
|
-
enter(path) {
|
|
18966
|
-
const {
|
|
18967
|
-
file
|
|
18968
|
-
} = this;
|
|
18969
|
-
const {
|
|
18970
|
-
opts
|
|
18971
|
-
} = file;
|
|
18972
|
-
let inputSourceMap;
|
|
18973
|
-
if (useInlineSourceMaps) {
|
|
18974
|
-
// https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
|
|
18975
|
-
inputSourceMap = opts.inputSourceMap || file.inputMap ? file.inputMap.sourcemap : null;
|
|
18976
|
-
} else {
|
|
18977
|
-
inputSourceMap = opts.inputSourceMap;
|
|
18978
|
-
}
|
|
18979
|
-
this.__dv__ = programVisitor(types, opts.filenameRelative || opts.filename, {
|
|
18980
|
-
coverageVariable: "__coverage__",
|
|
18981
|
-
inputSourceMap
|
|
18982
|
-
});
|
|
18983
|
-
this.__dv__.enter(path);
|
|
18984
|
-
},
|
|
18985
|
-
exit(path) {
|
|
18986
|
-
if (!this.__dv__) {
|
|
18987
|
-
return;
|
|
18988
|
-
}
|
|
18989
|
-
const object = this.__dv__.exit(path);
|
|
18990
|
-
// object got two properties: fileCoverage and sourceMappingURL
|
|
18991
|
-
this.file.metadata.coverage = object.fileCoverage;
|
|
18992
|
-
}
|
|
18993
|
-
}
|
|
18994
|
-
}
|
|
18995
|
-
};
|
|
18996
|
-
};
|
|
18997
|
-
|
|
18998
18950
|
/*
|
|
18999
18951
|
* Generated helpers
|
|
19000
18952
|
* - https://github.com/babel/babel/commits/main/packages/babel-helpers/src/helpers.ts
|
|
@@ -19804,21 +19756,6 @@ const jsenvPluginBabel = ({
|
|
|
19804
19756
|
isJsModule,
|
|
19805
19757
|
getImportSpecifier
|
|
19806
19758
|
});
|
|
19807
|
-
if (context.dev) {
|
|
19808
|
-
const requestHeaders = context.request.headers;
|
|
19809
|
-
if (requestHeaders["x-coverage-instanbul"]) {
|
|
19810
|
-
const coverageConfig = JSON.parse(requestHeaders["x-coverage-instanbul"]);
|
|
19811
|
-
const associations = URL_META.resolveAssociations({
|
|
19812
|
-
cover: coverageConfig
|
|
19813
|
-
}, context.rootDirectoryUrl);
|
|
19814
|
-
if (URL_META.applyAssociations({
|
|
19815
|
-
url: urlInfo.url,
|
|
19816
|
-
associations
|
|
19817
|
-
}).cover) {
|
|
19818
|
-
babelPluginStructure["transform-instrument"] = [babelPluginInstrument];
|
|
19819
|
-
}
|
|
19820
|
-
}
|
|
19821
|
-
}
|
|
19822
19759
|
if (getCustomBabelPlugins) {
|
|
19823
19760
|
Object.assign(babelPluginStructure, getCustomBabelPlugins(context));
|
|
19824
19761
|
}
|
|
@@ -22696,6 +22633,35 @@ const canUseVersionedUrl = urlInfo => {
|
|
|
22696
22633
|
return urlInfo.type !== "webmanifest";
|
|
22697
22634
|
};
|
|
22698
22635
|
|
|
22636
|
+
const WEB_URL_CONVERTER = {
|
|
22637
|
+
asWebUrl: (fileUrl, webServer) => {
|
|
22638
|
+
if (urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
|
|
22639
|
+
return moveUrl({
|
|
22640
|
+
url: fileUrl,
|
|
22641
|
+
from: webServer.rootDirectoryUrl,
|
|
22642
|
+
to: `${webServer.origin}/`
|
|
22643
|
+
});
|
|
22644
|
+
}
|
|
22645
|
+
const fsRootUrl = ensureWindowsDriveLetter("file:///", fileUrl);
|
|
22646
|
+
return `${webServer.origin}/@fs/${fileUrl.slice(fsRootUrl.length)}`;
|
|
22647
|
+
},
|
|
22648
|
+
asFileUrl: (webUrl, webServer) => {
|
|
22649
|
+
const {
|
|
22650
|
+
pathname,
|
|
22651
|
+
search
|
|
22652
|
+
} = new URL(webUrl);
|
|
22653
|
+
if (pathname.startsWith("/@fs/")) {
|
|
22654
|
+
const fsRootRelativeUrl = pathname.slice("/@fs/".length);
|
|
22655
|
+
return `file:///${fsRootRelativeUrl}${search}`;
|
|
22656
|
+
}
|
|
22657
|
+
return moveUrl({
|
|
22658
|
+
url: webUrl,
|
|
22659
|
+
from: `${webServer.origin}/`,
|
|
22660
|
+
to: webServer.rootDirectoryUrl
|
|
22661
|
+
});
|
|
22662
|
+
}
|
|
22663
|
+
};
|
|
22664
|
+
|
|
22699
22665
|
/*
|
|
22700
22666
|
* This plugin is very special because it is here
|
|
22701
22667
|
* to provide "serverEvents" used by other plugins
|
|
@@ -23145,18 +23111,9 @@ const inferParentFromRequest = (request, sourceDirectoryUrl) => {
|
|
|
23145
23111
|
const refererUrlObject = new URL(referer);
|
|
23146
23112
|
refererUrlObject.searchParams.delete("hmr");
|
|
23147
23113
|
refererUrlObject.searchParams.delete("v");
|
|
23148
|
-
|
|
23149
|
-
|
|
23150
|
-
|
|
23151
|
-
} = refererUrlObject;
|
|
23152
|
-
if (pathname.startsWith("/@fs/")) {
|
|
23153
|
-
const fsRootRelativeUrl = pathname.slice("/@fs/".length);
|
|
23154
|
-
return `file:///${fsRootRelativeUrl}${search}`;
|
|
23155
|
-
}
|
|
23156
|
-
return moveUrl({
|
|
23157
|
-
url: referer,
|
|
23158
|
-
from: `${request.origin}/`,
|
|
23159
|
-
to: sourceDirectoryUrl
|
|
23114
|
+
return WEB_URL_CONVERTER.asFileUrl(referer, {
|
|
23115
|
+
origin: request.origin,
|
|
23116
|
+
rootDirectoryUrl: sourceDirectoryUrl
|
|
23160
23117
|
});
|
|
23161
23118
|
};
|
|
23162
23119
|
|
|
@@ -23921,6 +23878,54 @@ const listRelativeFileUrlToCover = async ({
|
|
|
23921
23878
|
}) => relativeUrl);
|
|
23922
23879
|
};
|
|
23923
23880
|
|
|
23881
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/blob/321740f7b25d803f881466ea819d870f7ed6a254/src/index.js
|
|
23882
|
+
|
|
23883
|
+
const babelPluginInstrument = (api, {
|
|
23884
|
+
useInlineSourceMaps = false
|
|
23885
|
+
}) => {
|
|
23886
|
+
const {
|
|
23887
|
+
programVisitor
|
|
23888
|
+
} = requireFromJsenv("istanbul-lib-instrument");
|
|
23889
|
+
const {
|
|
23890
|
+
types
|
|
23891
|
+
} = api;
|
|
23892
|
+
return {
|
|
23893
|
+
name: "transform-instrument",
|
|
23894
|
+
visitor: {
|
|
23895
|
+
Program: {
|
|
23896
|
+
enter(path) {
|
|
23897
|
+
const {
|
|
23898
|
+
file
|
|
23899
|
+
} = this;
|
|
23900
|
+
const {
|
|
23901
|
+
opts
|
|
23902
|
+
} = file;
|
|
23903
|
+
let inputSourceMap;
|
|
23904
|
+
if (useInlineSourceMaps) {
|
|
23905
|
+
// https://github.com/istanbuljs/babel-plugin-istanbul/commit/a9e15643d249a2985e4387e4308022053b2cd0ad#diff-1fdf421c05c1140f6d71444ea2b27638R65
|
|
23906
|
+
inputSourceMap = opts.inputSourceMap || file.inputMap ? file.inputMap.sourcemap : null;
|
|
23907
|
+
} else {
|
|
23908
|
+
inputSourceMap = opts.inputSourceMap;
|
|
23909
|
+
}
|
|
23910
|
+
this.__dv__ = programVisitor(types, opts.filenameRelative || opts.filename, {
|
|
23911
|
+
coverageVariable: "__coverage__",
|
|
23912
|
+
inputSourceMap
|
|
23913
|
+
});
|
|
23914
|
+
this.__dv__.enter(path);
|
|
23915
|
+
},
|
|
23916
|
+
exit(path) {
|
|
23917
|
+
if (!this.__dv__) {
|
|
23918
|
+
return;
|
|
23919
|
+
}
|
|
23920
|
+
const object = this.__dv__.exit(path);
|
|
23921
|
+
// object got two properties: fileCoverage and sourceMappingURL
|
|
23922
|
+
this.file.metadata.coverage = object.fileCoverage;
|
|
23923
|
+
}
|
|
23924
|
+
}
|
|
23925
|
+
}
|
|
23926
|
+
};
|
|
23927
|
+
};
|
|
23928
|
+
|
|
23924
23929
|
const relativeUrlToEmptyCoverage = async (relativeUrl, {
|
|
23925
23930
|
signal,
|
|
23926
23931
|
rootDirectoryUrl
|
|
@@ -24800,7 +24805,6 @@ const executeSteps = async (executionSteps, {
|
|
|
24800
24805
|
process.exitCode !== 1;
|
|
24801
24806
|
const startMs = Date.now();
|
|
24802
24807
|
let rawOutput = "";
|
|
24803
|
-
logger.info("");
|
|
24804
24808
|
let executionLog = createLog({
|
|
24805
24809
|
newLine: ""
|
|
24806
24810
|
});
|
|
@@ -25106,10 +25110,13 @@ const executeTestPlan = async ({
|
|
|
25106
25110
|
gcBetweenExecutions = logMemoryHeapUsage,
|
|
25107
25111
|
coverageEnabled = process.argv.includes("--coverage"),
|
|
25108
25112
|
coverageConfig = {
|
|
25109
|
-
"file:///**/.*": false,
|
|
25110
|
-
"file:///**/.*/": false,
|
|
25111
25113
|
"file:///**/node_modules/": false,
|
|
25112
|
-
"
|
|
25114
|
+
"./**/.*": false,
|
|
25115
|
+
"./**/.*/": false,
|
|
25116
|
+
"./**/src/**/*.js": true,
|
|
25117
|
+
"./**/src/**/*.ts": true,
|
|
25118
|
+
"./**/src/**/*.jsx": true,
|
|
25119
|
+
"./**/src/**/*.tsx": true,
|
|
25113
25120
|
"./**/tests/": false,
|
|
25114
25121
|
"./**/*.test.html": false,
|
|
25115
25122
|
"./**/*.test.js": false,
|
|
@@ -25118,8 +25125,15 @@ const executeTestPlan = async ({
|
|
|
25118
25125
|
coverageIncludeMissing = true,
|
|
25119
25126
|
coverageAndExecutionAllowed = false,
|
|
25120
25127
|
coverageMethodForNodeJs = process.env.NODE_V8_COVERAGE ? "NODE_V8_COVERAGE" : "Profiler",
|
|
25121
|
-
|
|
25122
|
-
//
|
|
25128
|
+
// - When chromium only -> coverage generated by v8
|
|
25129
|
+
// - When chromium + node -> coverage generated by v8 are merged
|
|
25130
|
+
// - When firefox only -> coverage generated by babel+istanbul
|
|
25131
|
+
// - When chromium + firefox
|
|
25132
|
+
// -> by default only coverage from chromium is used
|
|
25133
|
+
// and a warning is logged according to coverageV8ConflictWarning
|
|
25134
|
+
// -> to collect coverage from both browsers, pass coverageMethodForBrowsers: "istanbul"
|
|
25135
|
+
coverageMethodForBrowsers,
|
|
25136
|
+
// undefined | "playwright" | "istanbul"
|
|
25123
25137
|
coverageV8ConflictWarning = true,
|
|
25124
25138
|
coverageTempDirectoryUrl,
|
|
25125
25139
|
// skip empty means empty files won't appear in the coverage reports (json and html)
|
|
@@ -25134,6 +25148,7 @@ const executeTestPlan = async ({
|
|
|
25134
25148
|
...rest
|
|
25135
25149
|
}) => {
|
|
25136
25150
|
let someNeedsServer = false;
|
|
25151
|
+
let someHasCoverageV8 = false;
|
|
25137
25152
|
let someNodeRuntime = false;
|
|
25138
25153
|
const runtimes = {};
|
|
25139
25154
|
// param validation
|
|
@@ -25160,6 +25175,9 @@ const executeTestPlan = async ({
|
|
|
25160
25175
|
if (runtime) {
|
|
25161
25176
|
runtimes[runtime.name] = runtime.version;
|
|
25162
25177
|
if (runtime.type === "browser") {
|
|
25178
|
+
if (runtime.capabilities && runtime.capabilities.coverageV8) {
|
|
25179
|
+
someHasCoverageV8 = true;
|
|
25180
|
+
}
|
|
25163
25181
|
someNeedsServer = true;
|
|
25164
25182
|
}
|
|
25165
25183
|
if (runtime.type === "node") {
|
|
@@ -25172,6 +25190,9 @@ const executeTestPlan = async ({
|
|
|
25172
25190
|
await assertAndNormalizeWebServer(webServer);
|
|
25173
25191
|
}
|
|
25174
25192
|
if (coverageEnabled) {
|
|
25193
|
+
if (coverageMethodForBrowsers === undefined) {
|
|
25194
|
+
coverageMethodForBrowsers = someHasCoverageV8 ? "playwright" : "istanbul";
|
|
25195
|
+
}
|
|
25175
25196
|
if (typeof coverageConfig !== "object") {
|
|
25176
25197
|
throw new TypeError(`coverageConfig must be an object, got ${coverageConfig}`);
|
|
25177
25198
|
}
|
|
@@ -25332,6 +25353,168 @@ const executeTestPlan = async ({
|
|
|
25332
25353
|
};
|
|
25333
25354
|
};
|
|
25334
25355
|
|
|
25356
|
+
const initJsSupervisorMiddleware = async (page, {
|
|
25357
|
+
webServer,
|
|
25358
|
+
fileUrl,
|
|
25359
|
+
fileServerUrl
|
|
25360
|
+
}) => {
|
|
25361
|
+
const inlineScriptContents = new Map();
|
|
25362
|
+
const interceptHtmlToExecute = async ({
|
|
25363
|
+
route
|
|
25364
|
+
}) => {
|
|
25365
|
+
const response = await route.fetch();
|
|
25366
|
+
const originalBody = await response.text();
|
|
25367
|
+
const injectionResult = await injectSupervisorIntoHTML({
|
|
25368
|
+
content: originalBody,
|
|
25369
|
+
url: fileUrl
|
|
25370
|
+
}, {
|
|
25371
|
+
supervisorScriptSrc: `/@fs/${supervisorFileUrl$1.slice("file:///".length)}`,
|
|
25372
|
+
supervisorOptions: {},
|
|
25373
|
+
inlineAsRemote: true,
|
|
25374
|
+
webServer,
|
|
25375
|
+
onInlineScript: ({
|
|
25376
|
+
src,
|
|
25377
|
+
textContent
|
|
25378
|
+
}) => {
|
|
25379
|
+
const inlineScriptWebUrl = new URL(src, `${webServer.origin}/`).href;
|
|
25380
|
+
inlineScriptContents.set(inlineScriptWebUrl, textContent);
|
|
25381
|
+
}
|
|
25382
|
+
});
|
|
25383
|
+
route.fulfill({
|
|
25384
|
+
response,
|
|
25385
|
+
body: injectionResult.content,
|
|
25386
|
+
headers: {
|
|
25387
|
+
...response.headers(),
|
|
25388
|
+
"content-length": Buffer.byteLength(injectionResult.content)
|
|
25389
|
+
}
|
|
25390
|
+
});
|
|
25391
|
+
};
|
|
25392
|
+
const interceptInlineScript = ({
|
|
25393
|
+
url,
|
|
25394
|
+
route
|
|
25395
|
+
}) => {
|
|
25396
|
+
const inlineScriptContent = inlineScriptContents.get(url);
|
|
25397
|
+
route.fulfill({
|
|
25398
|
+
status: 200,
|
|
25399
|
+
body: inlineScriptContent,
|
|
25400
|
+
headers: {
|
|
25401
|
+
"content-type": "text/javascript",
|
|
25402
|
+
"content-length": Buffer.byteLength(inlineScriptContent)
|
|
25403
|
+
}
|
|
25404
|
+
});
|
|
25405
|
+
};
|
|
25406
|
+
const interceptFileSystemUrl = ({
|
|
25407
|
+
url,
|
|
25408
|
+
route
|
|
25409
|
+
}) => {
|
|
25410
|
+
const relativeUrl = url.slice(webServer.origin.length);
|
|
25411
|
+
const fsPath = relativeUrl.slice("/@fs/".length);
|
|
25412
|
+
const fsUrl = `file:///${fsPath}`;
|
|
25413
|
+
const fileContent = readFileSync$1(new URL(fsUrl), "utf8");
|
|
25414
|
+
route.fulfill({
|
|
25415
|
+
status: 200,
|
|
25416
|
+
body: fileContent,
|
|
25417
|
+
headers: {
|
|
25418
|
+
"content-type": "text/javascript",
|
|
25419
|
+
"content-length": Buffer.byteLength(fileContent)
|
|
25420
|
+
}
|
|
25421
|
+
});
|
|
25422
|
+
};
|
|
25423
|
+
await page.route("**", async route => {
|
|
25424
|
+
const request = route.request();
|
|
25425
|
+
const url = request.url();
|
|
25426
|
+
if (url === fileServerUrl && urlToExtension$1(url) === ".html") {
|
|
25427
|
+
interceptHtmlToExecute({
|
|
25428
|
+
url,
|
|
25429
|
+
request,
|
|
25430
|
+
route
|
|
25431
|
+
});
|
|
25432
|
+
return;
|
|
25433
|
+
}
|
|
25434
|
+
if (inlineScriptContents.has(url)) {
|
|
25435
|
+
interceptInlineScript({
|
|
25436
|
+
url,
|
|
25437
|
+
request,
|
|
25438
|
+
route
|
|
25439
|
+
});
|
|
25440
|
+
return;
|
|
25441
|
+
}
|
|
25442
|
+
const fsServerUrl = new URL("/@fs/", webServer.origin);
|
|
25443
|
+
if (url.startsWith(fsServerUrl)) {
|
|
25444
|
+
interceptFileSystemUrl({
|
|
25445
|
+
url,
|
|
25446
|
+
request,
|
|
25447
|
+
route
|
|
25448
|
+
});
|
|
25449
|
+
return;
|
|
25450
|
+
}
|
|
25451
|
+
route.fallback();
|
|
25452
|
+
});
|
|
25453
|
+
};
|
|
25454
|
+
|
|
25455
|
+
const initIstanbulMiddleware = async (page, {
|
|
25456
|
+
webServer,
|
|
25457
|
+
rootDirectoryUrl,
|
|
25458
|
+
coverageConfig
|
|
25459
|
+
}) => {
|
|
25460
|
+
const associations = URL_META.resolveAssociations({
|
|
25461
|
+
cover: coverageConfig
|
|
25462
|
+
}, rootDirectoryUrl);
|
|
25463
|
+
await page.route("**", async route => {
|
|
25464
|
+
const request = route.request();
|
|
25465
|
+
const url = request.url(); // transform into a local url
|
|
25466
|
+
const fileUrl = WEB_URL_CONVERTER.asFileUrl(url, webServer);
|
|
25467
|
+
const needsInstrumentation = URL_META.applyAssociations({
|
|
25468
|
+
url: fileUrl,
|
|
25469
|
+
associations
|
|
25470
|
+
}).cover;
|
|
25471
|
+
if (!needsInstrumentation) {
|
|
25472
|
+
route.fallback();
|
|
25473
|
+
return;
|
|
25474
|
+
}
|
|
25475
|
+
const response = await route.fetch();
|
|
25476
|
+
const originalBody = await response.text();
|
|
25477
|
+
try {
|
|
25478
|
+
const result = await applyBabelPlugins({
|
|
25479
|
+
babelPlugins: [babelPluginInstrument],
|
|
25480
|
+
urlInfo: {
|
|
25481
|
+
originalUrl: fileUrl,
|
|
25482
|
+
// jsenv server could send info to know it's a js module or js classic
|
|
25483
|
+
// but in the end it's not super important
|
|
25484
|
+
// - it's ok to parse js classic as js module considering it's only for istanbul instrumentation
|
|
25485
|
+
type: "js_module",
|
|
25486
|
+
content: originalBody
|
|
25487
|
+
}
|
|
25488
|
+
});
|
|
25489
|
+
let code = result.code;
|
|
25490
|
+
code = SOURCEMAP.writeComment({
|
|
25491
|
+
contentType: "text/javascript",
|
|
25492
|
+
content: code,
|
|
25493
|
+
specifier: generateSourcemapDataUrl(result.map)
|
|
25494
|
+
});
|
|
25495
|
+
route.fulfill({
|
|
25496
|
+
response,
|
|
25497
|
+
body: code,
|
|
25498
|
+
headers: {
|
|
25499
|
+
...response.headers(),
|
|
25500
|
+
"content-length": Buffer.byteLength(code)
|
|
25501
|
+
}
|
|
25502
|
+
});
|
|
25503
|
+
} catch (e) {
|
|
25504
|
+
if (e.code === "PARSE_ERROR") {
|
|
25505
|
+
route.fulfill({
|
|
25506
|
+
response
|
|
25507
|
+
});
|
|
25508
|
+
} else {
|
|
25509
|
+
console.error(e);
|
|
25510
|
+
route.fulfill({
|
|
25511
|
+
response
|
|
25512
|
+
});
|
|
25513
|
+
}
|
|
25514
|
+
}
|
|
25515
|
+
});
|
|
25516
|
+
};
|
|
25517
|
+
|
|
25335
25518
|
const createRuntimeFromPlaywright = ({
|
|
25336
25519
|
browserName,
|
|
25337
25520
|
browserVersion,
|
|
@@ -25343,7 +25526,10 @@ const createRuntimeFromPlaywright = ({
|
|
|
25343
25526
|
const runtime = {
|
|
25344
25527
|
type: "browser",
|
|
25345
25528
|
name: browserName,
|
|
25346
|
-
version: browserVersion
|
|
25529
|
+
version: browserVersion,
|
|
25530
|
+
capabilities: {
|
|
25531
|
+
coverageV8: coveragePlaywrightAPIAvailable
|
|
25532
|
+
}
|
|
25347
25533
|
};
|
|
25348
25534
|
let browserAndContextPromise;
|
|
25349
25535
|
runtime.run = async ({
|
|
@@ -25374,11 +25560,7 @@ ${fileUrl}
|
|
|
25374
25560
|
--- web server root directory url ---
|
|
25375
25561
|
${webServer.rootDirectoryUrl}`);
|
|
25376
25562
|
}
|
|
25377
|
-
const fileServerUrl =
|
|
25378
|
-
url: fileUrl,
|
|
25379
|
-
from: webServer.rootDirectoryUrl,
|
|
25380
|
-
to: `${webServer.origin}/`
|
|
25381
|
-
});
|
|
25563
|
+
const fileServerUrl = WEB_URL_CONVERTER.asWebUrl(fileUrl, webServer);
|
|
25382
25564
|
const cleanupCallbackList = createCallbackListNotifiedOnce();
|
|
25383
25565
|
const cleanup = memoize(async reason => {
|
|
25384
25566
|
await cleanupCallbackList.notify({
|
|
@@ -25434,16 +25616,17 @@ ${webServer.rootDirectoryUrl}`);
|
|
|
25434
25616
|
}
|
|
25435
25617
|
await disconnected;
|
|
25436
25618
|
};
|
|
25437
|
-
const
|
|
25438
|
-
const
|
|
25439
|
-
|
|
25440
|
-
|
|
25441
|
-
|
|
25442
|
-
|
|
25443
|
-
|
|
25444
|
-
|
|
25619
|
+
const page = await browserContext.newPage();
|
|
25620
|
+
const istanbulInstrumentationEnabled = coverageEnabled && (!runtime.capabilities.coverageV8 || coverageMethodForBrowsers === "istanbul");
|
|
25621
|
+
if (istanbulInstrumentationEnabled) {
|
|
25622
|
+
await initIstanbulMiddleware(page, {
|
|
25623
|
+
webServer,
|
|
25624
|
+
rootDirectoryUrl,
|
|
25625
|
+
coverageConfig
|
|
25626
|
+
});
|
|
25627
|
+
}
|
|
25445
25628
|
if (!webServer.isJsenvDevServer) {
|
|
25446
|
-
await
|
|
25629
|
+
await initJsSupervisorMiddleware(page, {
|
|
25447
25630
|
webServer,
|
|
25448
25631
|
fileUrl,
|
|
25449
25632
|
fileServerUrl
|
|
@@ -25466,7 +25649,7 @@ ${webServer.rootDirectoryUrl}`);
|
|
|
25466
25649
|
};
|
|
25467
25650
|
const callbacks = [];
|
|
25468
25651
|
if (coverageEnabled) {
|
|
25469
|
-
if (
|
|
25652
|
+
if (runtime.capabilities.coverageV8 && coverageMethodForBrowsers === "playwright") {
|
|
25470
25653
|
await page.coverage.startJSCoverage({
|
|
25471
25654
|
// reportAnonymousScripts: true,
|
|
25472
25655
|
});
|
|
@@ -25475,11 +25658,7 @@ ${webServer.rootDirectoryUrl}`);
|
|
|
25475
25658
|
// we convert urls starting with http:// to file:// because we later
|
|
25476
25659
|
// convert the url to filesystem path in istanbulCoverageFromV8Coverage function
|
|
25477
25660
|
const v8CoveragesWithFsUrls = v8CoveragesWithWebUrls.map(v8CoveragesWithWebUrl => {
|
|
25478
|
-
const fsUrl =
|
|
25479
|
-
url: v8CoveragesWithWebUrl.url,
|
|
25480
|
-
from: `${webServer.origin}/`,
|
|
25481
|
-
to: webServer.rootDirectoryUrl
|
|
25482
|
-
});
|
|
25661
|
+
const fsUrl = WEB_URL_CONVERTER.asFileUrl(v8CoveragesWithWebUrl.url, webServer);
|
|
25483
25662
|
return {
|
|
25484
25663
|
...v8CoveragesWithWebUrl,
|
|
25485
25664
|
url: fsUrl
|
|
@@ -25848,106 +26027,6 @@ const extractTextFromConsoleMessage = consoleMessage => {
|
|
|
25848
26027
|
// return text
|
|
25849
26028
|
};
|
|
25850
26029
|
|
|
25851
|
-
const initJsExecutionMiddleware = async (page, {
|
|
25852
|
-
webServer,
|
|
25853
|
-
fileUrl,
|
|
25854
|
-
fileServerUrl
|
|
25855
|
-
}) => {
|
|
25856
|
-
const inlineScriptContents = new Map();
|
|
25857
|
-
const interceptHtmlToExecute = async ({
|
|
25858
|
-
route
|
|
25859
|
-
}) => {
|
|
25860
|
-
// Fetch original response.
|
|
25861
|
-
const response = await route.fetch();
|
|
25862
|
-
// Add a prefix to the title.
|
|
25863
|
-
const originalBody = await response.text();
|
|
25864
|
-
const injectionResult = await injectSupervisorIntoHTML({
|
|
25865
|
-
content: originalBody,
|
|
25866
|
-
url: fileUrl
|
|
25867
|
-
}, {
|
|
25868
|
-
supervisorScriptSrc: `/@fs/${supervisorFileUrl$1.slice("file:///".length)}`,
|
|
25869
|
-
supervisorOptions: {},
|
|
25870
|
-
inlineAsRemote: true,
|
|
25871
|
-
webServer,
|
|
25872
|
-
onInlineScript: ({
|
|
25873
|
-
src,
|
|
25874
|
-
textContent
|
|
25875
|
-
}) => {
|
|
25876
|
-
const inlineScriptWebUrl = new URL(src, `${webServer.origin}/`).href;
|
|
25877
|
-
inlineScriptContents.set(inlineScriptWebUrl, textContent);
|
|
25878
|
-
}
|
|
25879
|
-
});
|
|
25880
|
-
route.fulfill({
|
|
25881
|
-
response,
|
|
25882
|
-
body: injectionResult.content,
|
|
25883
|
-
headers: {
|
|
25884
|
-
...response.headers(),
|
|
25885
|
-
"content-length": Buffer.byteLength(injectionResult.content)
|
|
25886
|
-
}
|
|
25887
|
-
});
|
|
25888
|
-
};
|
|
25889
|
-
const interceptInlineScript = ({
|
|
25890
|
-
url,
|
|
25891
|
-
route
|
|
25892
|
-
}) => {
|
|
25893
|
-
const inlineScriptContent = inlineScriptContents.get(url);
|
|
25894
|
-
route.fulfill({
|
|
25895
|
-
status: 200,
|
|
25896
|
-
body: inlineScriptContent,
|
|
25897
|
-
headers: {
|
|
25898
|
-
"content-type": "text/javascript",
|
|
25899
|
-
"content-length": Buffer.byteLength(inlineScriptContent)
|
|
25900
|
-
}
|
|
25901
|
-
});
|
|
25902
|
-
};
|
|
25903
|
-
const interceptFileSystemUrl = ({
|
|
25904
|
-
url,
|
|
25905
|
-
route
|
|
25906
|
-
}) => {
|
|
25907
|
-
const relativeUrl = url.slice(webServer.origin.length);
|
|
25908
|
-
const fsPath = relativeUrl.slice("/@fs/".length);
|
|
25909
|
-
const fsUrl = `file:///${fsPath}`;
|
|
25910
|
-
const fileContent = readFileSync$1(new URL(fsUrl), "utf8");
|
|
25911
|
-
route.fulfill({
|
|
25912
|
-
status: 200,
|
|
25913
|
-
body: fileContent,
|
|
25914
|
-
headers: {
|
|
25915
|
-
"content-type": "text/javascript",
|
|
25916
|
-
"content-length": Buffer.byteLength(fileContent)
|
|
25917
|
-
}
|
|
25918
|
-
});
|
|
25919
|
-
};
|
|
25920
|
-
await page.route("**", async route => {
|
|
25921
|
-
const request = route.request();
|
|
25922
|
-
const url = request.url();
|
|
25923
|
-
if (url === fileServerUrl && urlToExtension$1(url) === ".html") {
|
|
25924
|
-
interceptHtmlToExecute({
|
|
25925
|
-
url,
|
|
25926
|
-
request,
|
|
25927
|
-
route
|
|
25928
|
-
});
|
|
25929
|
-
return;
|
|
25930
|
-
}
|
|
25931
|
-
if (inlineScriptContents.has(url)) {
|
|
25932
|
-
interceptInlineScript({
|
|
25933
|
-
url,
|
|
25934
|
-
request,
|
|
25935
|
-
route
|
|
25936
|
-
});
|
|
25937
|
-
return;
|
|
25938
|
-
}
|
|
25939
|
-
const fsServerUrl = new URL("/@fs/", webServer.origin);
|
|
25940
|
-
if (url.startsWith(fsServerUrl)) {
|
|
25941
|
-
interceptFileSystemUrl({
|
|
25942
|
-
url,
|
|
25943
|
-
request,
|
|
25944
|
-
route
|
|
25945
|
-
});
|
|
25946
|
-
return;
|
|
25947
|
-
}
|
|
25948
|
-
route.fallback();
|
|
25949
|
-
});
|
|
25950
|
-
};
|
|
25951
26030
|
const registerEvent = ({
|
|
25952
26031
|
object,
|
|
25953
26032
|
eventType,
|
package/package.json
CHANGED
package/src/dev/file_service.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs"
|
|
2
2
|
import { serveDirectory, composeTwoResponses } from "@jsenv/server"
|
|
3
3
|
import { bufferToEtag } from "@jsenv/filesystem"
|
|
4
|
-
import {
|
|
4
|
+
import { asUrlWithoutSearch } from "@jsenv/urls"
|
|
5
5
|
import { URL_META } from "@jsenv/url-meta"
|
|
6
6
|
|
|
7
|
+
import { WEB_URL_CONVERTER } from "../web_url_converter.js"
|
|
7
8
|
import { watchSourceFiles } from "../watch_source_files.js"
|
|
8
9
|
import { explorerHtmlFileUrl } from "@jsenv/core/src/plugins/explorer/jsenv_plugin_explorer.js"
|
|
9
10
|
import { createUrlGraph } from "@jsenv/core/src/kitchen/url_graph.js"
|
|
@@ -437,14 +438,8 @@ const inferParentFromRequest = (request, sourceDirectoryUrl) => {
|
|
|
437
438
|
const refererUrlObject = new URL(referer)
|
|
438
439
|
refererUrlObject.searchParams.delete("hmr")
|
|
439
440
|
refererUrlObject.searchParams.delete("v")
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
return `file:///${fsRootRelativeUrl}${search}`
|
|
444
|
-
}
|
|
445
|
-
return moveUrl({
|
|
446
|
-
url: referer,
|
|
447
|
-
from: `${request.origin}/`,
|
|
448
|
-
to: sourceDirectoryUrl,
|
|
441
|
+
return WEB_URL_CONVERTER.asFileUrl(referer, {
|
|
442
|
+
origin: request.origin,
|
|
443
|
+
rootDirectoryUrl: sourceDirectoryUrl,
|
|
449
444
|
})
|
|
450
445
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { writeFileSync } from "node:fs"
|
|
3
2
|
import { createDetailedMessage } from "@jsenv/log"
|
|
4
3
|
import {
|
|
5
4
|
Abort,
|
|
@@ -7,15 +6,14 @@ import {
|
|
|
7
6
|
raceProcessTeardownEvents,
|
|
8
7
|
raceCallbacks,
|
|
9
8
|
} from "@jsenv/abort"
|
|
10
|
-
import {
|
|
9
|
+
import { urlIsInsideOf } from "@jsenv/urls"
|
|
11
10
|
import { memoize } from "@jsenv/utils/src/memoize/memoize.js"
|
|
12
11
|
|
|
12
|
+
import { WEB_URL_CONVERTER } from "../../../web_url_converter.js"
|
|
13
|
+
import { initJsSupervisorMiddleware } from "./middleware_js_supervisor.js"
|
|
14
|
+
import { initIstanbulMiddleware } from "./middleware_istanbul.js"
|
|
13
15
|
import { filterV8Coverage } from "@jsenv/core/src/test/coverage/v8_coverage.js"
|
|
14
16
|
import { composeTwoFileByFileIstanbulCoverages } from "@jsenv/core/src/test/coverage/istanbul_coverage_composition.js"
|
|
15
|
-
import {
|
|
16
|
-
injectSupervisorIntoHTML,
|
|
17
|
-
supervisorFileUrl,
|
|
18
|
-
} from "../../../plugins/supervisor/html_supervisor_injection.js"
|
|
19
17
|
|
|
20
18
|
export const createRuntimeFromPlaywright = ({
|
|
21
19
|
browserName,
|
|
@@ -29,6 +27,9 @@ export const createRuntimeFromPlaywright = ({
|
|
|
29
27
|
type: "browser",
|
|
30
28
|
name: browserName,
|
|
31
29
|
version: browserVersion,
|
|
30
|
+
capabilities: {
|
|
31
|
+
coverageV8: coveragePlaywrightAPIAvailable,
|
|
32
|
+
},
|
|
32
33
|
}
|
|
33
34
|
let browserAndContextPromise
|
|
34
35
|
runtime.run = async ({
|
|
@@ -62,12 +63,7 @@ ${fileUrl}
|
|
|
62
63
|
--- web server root directory url ---
|
|
63
64
|
${webServer.rootDirectoryUrl}`)
|
|
64
65
|
}
|
|
65
|
-
const fileServerUrl =
|
|
66
|
-
url: fileUrl,
|
|
67
|
-
from: webServer.rootDirectoryUrl,
|
|
68
|
-
to: `${webServer.origin}/`,
|
|
69
|
-
})
|
|
70
|
-
|
|
66
|
+
const fileServerUrl = WEB_URL_CONVERTER.asWebUrl(fileUrl, webServer)
|
|
71
67
|
const cleanupCallbackList = createCallbackListNotifiedOnce()
|
|
72
68
|
const cleanup = memoize(async (reason) => {
|
|
73
69
|
await cleanupCallbackList.notify({ reason })
|
|
@@ -116,21 +112,22 @@ ${webServer.rootDirectoryUrl}`)
|
|
|
116
112
|
}
|
|
117
113
|
await disconnected
|
|
118
114
|
}
|
|
119
|
-
|
|
115
|
+
|
|
116
|
+
const page = await browserContext.newPage()
|
|
117
|
+
|
|
118
|
+
const istanbulInstrumentationEnabled =
|
|
120
119
|
coverageEnabled &&
|
|
121
|
-
(!
|
|
122
|
-
coverageMethodForBrowsers
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
},
|
|
131
|
-
})
|
|
120
|
+
(!runtime.capabilities.coverageV8 ||
|
|
121
|
+
coverageMethodForBrowsers === "istanbul")
|
|
122
|
+
if (istanbulInstrumentationEnabled) {
|
|
123
|
+
await initIstanbulMiddleware(page, {
|
|
124
|
+
webServer,
|
|
125
|
+
rootDirectoryUrl,
|
|
126
|
+
coverageConfig,
|
|
127
|
+
})
|
|
128
|
+
}
|
|
132
129
|
if (!webServer.isJsenvDevServer) {
|
|
133
|
-
await
|
|
130
|
+
await initJsSupervisorMiddleware(page, {
|
|
134
131
|
webServer,
|
|
135
132
|
fileUrl,
|
|
136
133
|
fileServerUrl,
|
|
@@ -155,8 +152,8 @@ ${webServer.rootDirectoryUrl}`)
|
|
|
155
152
|
const callbacks = []
|
|
156
153
|
if (coverageEnabled) {
|
|
157
154
|
if (
|
|
158
|
-
|
|
159
|
-
coverageMethodForBrowsers === "
|
|
155
|
+
runtime.capabilities.coverageV8 &&
|
|
156
|
+
coverageMethodForBrowsers === "playwright"
|
|
160
157
|
) {
|
|
161
158
|
await page.coverage.startJSCoverage({
|
|
162
159
|
// reportAnonymousScripts: true,
|
|
@@ -167,11 +164,10 @@ ${webServer.rootDirectoryUrl}`)
|
|
|
167
164
|
// convert the url to filesystem path in istanbulCoverageFromV8Coverage function
|
|
168
165
|
const v8CoveragesWithFsUrls = v8CoveragesWithWebUrls.map(
|
|
169
166
|
(v8CoveragesWithWebUrl) => {
|
|
170
|
-
const fsUrl =
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
})
|
|
167
|
+
const fsUrl = WEB_URL_CONVERTER.asFileUrl(
|
|
168
|
+
v8CoveragesWithWebUrl.url,
|
|
169
|
+
webServer,
|
|
170
|
+
)
|
|
175
171
|
return {
|
|
176
172
|
...v8CoveragesWithWebUrl,
|
|
177
173
|
url: fsUrl,
|
|
@@ -570,100 +566,6 @@ const extractTextFromConsoleMessage = (consoleMessage) => {
|
|
|
570
566
|
// return text
|
|
571
567
|
}
|
|
572
568
|
|
|
573
|
-
const initJsExecutionMiddleware = async (
|
|
574
|
-
page,
|
|
575
|
-
{ webServer, fileUrl, fileServerUrl },
|
|
576
|
-
) => {
|
|
577
|
-
const inlineScriptContents = new Map()
|
|
578
|
-
|
|
579
|
-
const interceptHtmlToExecute = async ({ route }) => {
|
|
580
|
-
// Fetch original response.
|
|
581
|
-
const response = await route.fetch()
|
|
582
|
-
// Add a prefix to the title.
|
|
583
|
-
const originalBody = await response.text()
|
|
584
|
-
const injectionResult = await injectSupervisorIntoHTML(
|
|
585
|
-
{
|
|
586
|
-
content: originalBody,
|
|
587
|
-
url: fileUrl,
|
|
588
|
-
},
|
|
589
|
-
{
|
|
590
|
-
supervisorScriptSrc: `/@fs/${supervisorFileUrl.slice(
|
|
591
|
-
"file:///".length,
|
|
592
|
-
)}`,
|
|
593
|
-
supervisorOptions: {},
|
|
594
|
-
inlineAsRemote: true,
|
|
595
|
-
webServer,
|
|
596
|
-
onInlineScript: ({ src, textContent }) => {
|
|
597
|
-
const inlineScriptWebUrl = new URL(src, `${webServer.origin}/`).href
|
|
598
|
-
inlineScriptContents.set(inlineScriptWebUrl, textContent)
|
|
599
|
-
},
|
|
600
|
-
},
|
|
601
|
-
)
|
|
602
|
-
route.fulfill({
|
|
603
|
-
response,
|
|
604
|
-
body: injectionResult.content,
|
|
605
|
-
headers: {
|
|
606
|
-
...response.headers(),
|
|
607
|
-
"content-length": Buffer.byteLength(injectionResult.content),
|
|
608
|
-
},
|
|
609
|
-
})
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
const interceptInlineScript = ({ url, route }) => {
|
|
613
|
-
const inlineScriptContent = inlineScriptContents.get(url)
|
|
614
|
-
route.fulfill({
|
|
615
|
-
status: 200,
|
|
616
|
-
body: inlineScriptContent,
|
|
617
|
-
headers: {
|
|
618
|
-
"content-type": "text/javascript",
|
|
619
|
-
"content-length": Buffer.byteLength(inlineScriptContent),
|
|
620
|
-
},
|
|
621
|
-
})
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
const interceptFileSystemUrl = ({ url, route }) => {
|
|
625
|
-
const relativeUrl = url.slice(webServer.origin.length)
|
|
626
|
-
const fsPath = relativeUrl.slice("/@fs/".length)
|
|
627
|
-
const fsUrl = `file:///${fsPath}`
|
|
628
|
-
const fileContent = readFileSync(new URL(fsUrl), "utf8")
|
|
629
|
-
route.fulfill({
|
|
630
|
-
status: 200,
|
|
631
|
-
body: fileContent,
|
|
632
|
-
headers: {
|
|
633
|
-
"content-type": "text/javascript",
|
|
634
|
-
"content-length": Buffer.byteLength(fileContent),
|
|
635
|
-
},
|
|
636
|
-
})
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
await page.route("**", async (route) => {
|
|
640
|
-
const request = route.request()
|
|
641
|
-
const url = request.url()
|
|
642
|
-
if (url === fileServerUrl && urlToExtension(url) === ".html") {
|
|
643
|
-
interceptHtmlToExecute({
|
|
644
|
-
url,
|
|
645
|
-
request,
|
|
646
|
-
route,
|
|
647
|
-
})
|
|
648
|
-
return
|
|
649
|
-
}
|
|
650
|
-
if (inlineScriptContents.has(url)) {
|
|
651
|
-
interceptInlineScript({
|
|
652
|
-
url,
|
|
653
|
-
request,
|
|
654
|
-
route,
|
|
655
|
-
})
|
|
656
|
-
return
|
|
657
|
-
}
|
|
658
|
-
const fsServerUrl = new URL("/@fs/", webServer.origin)
|
|
659
|
-
if (url.startsWith(fsServerUrl)) {
|
|
660
|
-
interceptFileSystemUrl({ url, request, route })
|
|
661
|
-
return
|
|
662
|
-
}
|
|
663
|
-
route.fallback()
|
|
664
|
-
})
|
|
665
|
-
}
|
|
666
|
-
|
|
667
569
|
const registerEvent = ({ object, eventType, callback }) => {
|
|
668
570
|
object.on(eventType, callback)
|
|
669
571
|
return () => {
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { URL_META } from "@jsenv/url-meta"
|
|
2
|
+
import { applyBabelPlugins } from "@jsenv/ast"
|
|
3
|
+
import { SOURCEMAP, generateSourcemapDataUrl } from "@jsenv/sourcemap"
|
|
4
|
+
|
|
5
|
+
import { WEB_URL_CONVERTER } from "../../../web_url_converter.js"
|
|
6
|
+
import { babelPluginInstrument } from "../../../test/coverage/babel_plugin_instrument.js"
|
|
7
|
+
|
|
8
|
+
export const initIstanbulMiddleware = async (
|
|
9
|
+
page,
|
|
10
|
+
{ webServer, rootDirectoryUrl, coverageConfig },
|
|
11
|
+
) => {
|
|
12
|
+
const associations = URL_META.resolveAssociations(
|
|
13
|
+
{ cover: coverageConfig },
|
|
14
|
+
rootDirectoryUrl,
|
|
15
|
+
)
|
|
16
|
+
await page.route("**", async (route) => {
|
|
17
|
+
const request = route.request()
|
|
18
|
+
const url = request.url() // transform into a local url
|
|
19
|
+
const fileUrl = WEB_URL_CONVERTER.asFileUrl(url, webServer)
|
|
20
|
+
const needsInstrumentation = URL_META.applyAssociations({
|
|
21
|
+
url: fileUrl,
|
|
22
|
+
associations,
|
|
23
|
+
}).cover
|
|
24
|
+
if (!needsInstrumentation) {
|
|
25
|
+
route.fallback()
|
|
26
|
+
return
|
|
27
|
+
}
|
|
28
|
+
const response = await route.fetch()
|
|
29
|
+
const originalBody = await response.text()
|
|
30
|
+
try {
|
|
31
|
+
const result = await applyBabelPlugins({
|
|
32
|
+
babelPlugins: [babelPluginInstrument],
|
|
33
|
+
urlInfo: {
|
|
34
|
+
originalUrl: fileUrl,
|
|
35
|
+
// jsenv server could send info to know it's a js module or js classic
|
|
36
|
+
// but in the end it's not super important
|
|
37
|
+
// - it's ok to parse js classic as js module considering it's only for istanbul instrumentation
|
|
38
|
+
type: "js_module",
|
|
39
|
+
content: originalBody,
|
|
40
|
+
},
|
|
41
|
+
})
|
|
42
|
+
let code = result.code
|
|
43
|
+
code = SOURCEMAP.writeComment({
|
|
44
|
+
contentType: "text/javascript",
|
|
45
|
+
content: code,
|
|
46
|
+
specifier: generateSourcemapDataUrl(result.map),
|
|
47
|
+
})
|
|
48
|
+
route.fulfill({
|
|
49
|
+
response,
|
|
50
|
+
body: code,
|
|
51
|
+
headers: {
|
|
52
|
+
...response.headers(),
|
|
53
|
+
"content-length": Buffer.byteLength(code),
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
} catch (e) {
|
|
57
|
+
if (e.code === "PARSE_ERROR") {
|
|
58
|
+
route.fulfill({ response })
|
|
59
|
+
} else {
|
|
60
|
+
console.error(e)
|
|
61
|
+
route.fulfill({ response })
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs"
|
|
2
|
+
|
|
3
|
+
import { urlToExtension } from "@jsenv/urls"
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
injectSupervisorIntoHTML,
|
|
7
|
+
supervisorFileUrl,
|
|
8
|
+
} from "../../../plugins/supervisor/html_supervisor_injection.js"
|
|
9
|
+
|
|
10
|
+
export const initJsSupervisorMiddleware = async (
|
|
11
|
+
page,
|
|
12
|
+
{ webServer, fileUrl, fileServerUrl },
|
|
13
|
+
) => {
|
|
14
|
+
const inlineScriptContents = new Map()
|
|
15
|
+
|
|
16
|
+
const interceptHtmlToExecute = async ({ route }) => {
|
|
17
|
+
const response = await route.fetch()
|
|
18
|
+
const originalBody = await response.text()
|
|
19
|
+
const injectionResult = await injectSupervisorIntoHTML(
|
|
20
|
+
{
|
|
21
|
+
content: originalBody,
|
|
22
|
+
url: fileUrl,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
supervisorScriptSrc: `/@fs/${supervisorFileUrl.slice(
|
|
26
|
+
"file:///".length,
|
|
27
|
+
)}`,
|
|
28
|
+
supervisorOptions: {},
|
|
29
|
+
inlineAsRemote: true,
|
|
30
|
+
webServer,
|
|
31
|
+
onInlineScript: ({ src, textContent }) => {
|
|
32
|
+
const inlineScriptWebUrl = new URL(src, `${webServer.origin}/`).href
|
|
33
|
+
inlineScriptContents.set(inlineScriptWebUrl, textContent)
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
)
|
|
37
|
+
route.fulfill({
|
|
38
|
+
response,
|
|
39
|
+
body: injectionResult.content,
|
|
40
|
+
headers: {
|
|
41
|
+
...response.headers(),
|
|
42
|
+
"content-length": Buffer.byteLength(injectionResult.content),
|
|
43
|
+
},
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const interceptInlineScript = ({ url, route }) => {
|
|
48
|
+
const inlineScriptContent = inlineScriptContents.get(url)
|
|
49
|
+
route.fulfill({
|
|
50
|
+
status: 200,
|
|
51
|
+
body: inlineScriptContent,
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "text/javascript",
|
|
54
|
+
"content-length": Buffer.byteLength(inlineScriptContent),
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const interceptFileSystemUrl = ({ url, route }) => {
|
|
60
|
+
const relativeUrl = url.slice(webServer.origin.length)
|
|
61
|
+
const fsPath = relativeUrl.slice("/@fs/".length)
|
|
62
|
+
const fsUrl = `file:///${fsPath}`
|
|
63
|
+
const fileContent = readFileSync(new URL(fsUrl), "utf8")
|
|
64
|
+
route.fulfill({
|
|
65
|
+
status: 200,
|
|
66
|
+
body: fileContent,
|
|
67
|
+
headers: {
|
|
68
|
+
"content-type": "text/javascript",
|
|
69
|
+
"content-length": Buffer.byteLength(fileContent),
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await page.route("**", async (route) => {
|
|
75
|
+
const request = route.request()
|
|
76
|
+
const url = request.url()
|
|
77
|
+
if (url === fileServerUrl && urlToExtension(url) === ".html") {
|
|
78
|
+
interceptHtmlToExecute({
|
|
79
|
+
url,
|
|
80
|
+
request,
|
|
81
|
+
route,
|
|
82
|
+
})
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
if (inlineScriptContents.has(url)) {
|
|
86
|
+
interceptInlineScript({
|
|
87
|
+
url,
|
|
88
|
+
request,
|
|
89
|
+
route,
|
|
90
|
+
})
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
const fsServerUrl = new URL("/@fs/", webServer.origin)
|
|
94
|
+
if (url.startsWith(fsServerUrl)) {
|
|
95
|
+
interceptFileSystemUrl({ url, request, route })
|
|
96
|
+
return
|
|
97
|
+
}
|
|
98
|
+
route.fallback()
|
|
99
|
+
})
|
|
100
|
+
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import { applyBabelPlugins } from "@jsenv/ast"
|
|
2
|
-
import { URL_META } from "@jsenv/url-meta"
|
|
3
2
|
|
|
4
|
-
import { babelPluginInstrument } from "@jsenv/core/src/test/coverage/babel_plugin_instrument.js"
|
|
5
3
|
import { RUNTIME_COMPAT } from "@jsenv/core/src/kitchen/compat/runtime_compat.js"
|
|
6
4
|
import { getBaseBabelPluginStructure } from "./helpers/babel_plugin_structure.js"
|
|
7
5
|
import { babelPluginBabelHelpersAsJsenvImports } from "./helpers/babel_plugin_babel_helpers_as_jsenv_imports.js"
|
|
@@ -33,23 +31,6 @@ export const jsenvPluginBabel = ({
|
|
|
33
31
|
isJsModule,
|
|
34
32
|
getImportSpecifier,
|
|
35
33
|
})
|
|
36
|
-
if (context.dev) {
|
|
37
|
-
const requestHeaders = context.request.headers
|
|
38
|
-
if (requestHeaders["x-coverage-instanbul"]) {
|
|
39
|
-
const coverageConfig = JSON.parse(
|
|
40
|
-
requestHeaders["x-coverage-instanbul"],
|
|
41
|
-
)
|
|
42
|
-
const associations = URL_META.resolveAssociations(
|
|
43
|
-
{ cover: coverageConfig },
|
|
44
|
-
context.rootDirectoryUrl,
|
|
45
|
-
)
|
|
46
|
-
if (
|
|
47
|
-
URL_META.applyAssociations({ url: urlInfo.url, associations }).cover
|
|
48
|
-
) {
|
|
49
|
-
babelPluginStructure["transform-instrument"] = [babelPluginInstrument]
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
34
|
if (getCustomBabelPlugins) {
|
|
54
35
|
Object.assign(babelPluginStructure, getCustomBabelPlugins(context))
|
|
55
36
|
}
|
|
@@ -64,10 +64,13 @@ export const executeTestPlan = async ({
|
|
|
64
64
|
|
|
65
65
|
coverageEnabled = process.argv.includes("--coverage"),
|
|
66
66
|
coverageConfig = {
|
|
67
|
-
"file:///**/.*": false,
|
|
68
|
-
"file:///**/.*/": false,
|
|
69
67
|
"file:///**/node_modules/": false,
|
|
70
|
-
"
|
|
68
|
+
"./**/.*": false,
|
|
69
|
+
"./**/.*/": false,
|
|
70
|
+
"./**/src/**/*.js": true,
|
|
71
|
+
"./**/src/**/*.ts": true,
|
|
72
|
+
"./**/src/**/*.jsx": true,
|
|
73
|
+
"./**/src/**/*.tsx": true,
|
|
71
74
|
"./**/tests/": false,
|
|
72
75
|
"./**/*.test.html": false,
|
|
73
76
|
"./**/*.test.js": false,
|
|
@@ -78,7 +81,14 @@ export const executeTestPlan = async ({
|
|
|
78
81
|
coverageMethodForNodeJs = process.env.NODE_V8_COVERAGE
|
|
79
82
|
? "NODE_V8_COVERAGE"
|
|
80
83
|
: "Profiler",
|
|
81
|
-
|
|
84
|
+
// - When chromium only -> coverage generated by v8
|
|
85
|
+
// - When chromium + node -> coverage generated by v8 are merged
|
|
86
|
+
// - When firefox only -> coverage generated by babel+istanbul
|
|
87
|
+
// - When chromium + firefox
|
|
88
|
+
// -> by default only coverage from chromium is used
|
|
89
|
+
// and a warning is logged according to coverageV8ConflictWarning
|
|
90
|
+
// -> to collect coverage from both browsers, pass coverageMethodForBrowsers: "istanbul"
|
|
91
|
+
coverageMethodForBrowsers, // undefined | "playwright" | "istanbul"
|
|
82
92
|
coverageV8ConflictWarning = true,
|
|
83
93
|
coverageTempDirectoryUrl,
|
|
84
94
|
// skip empty means empty files won't appear in the coverage reports (json and html)
|
|
@@ -93,6 +103,7 @@ export const executeTestPlan = async ({
|
|
|
93
103
|
...rest
|
|
94
104
|
}) => {
|
|
95
105
|
let someNeedsServer = false
|
|
106
|
+
let someHasCoverageV8 = false
|
|
96
107
|
let someNodeRuntime = false
|
|
97
108
|
const runtimes = {}
|
|
98
109
|
// param validation
|
|
@@ -123,6 +134,9 @@ export const executeTestPlan = async ({
|
|
|
123
134
|
if (runtime) {
|
|
124
135
|
runtimes[runtime.name] = runtime.version
|
|
125
136
|
if (runtime.type === "browser") {
|
|
137
|
+
if (runtime.capabilities && runtime.capabilities.coverageV8) {
|
|
138
|
+
someHasCoverageV8 = true
|
|
139
|
+
}
|
|
126
140
|
someNeedsServer = true
|
|
127
141
|
}
|
|
128
142
|
if (runtime.type === "node") {
|
|
@@ -137,6 +151,11 @@ export const executeTestPlan = async ({
|
|
|
137
151
|
}
|
|
138
152
|
|
|
139
153
|
if (coverageEnabled) {
|
|
154
|
+
if (coverageMethodForBrowsers === undefined) {
|
|
155
|
+
coverageMethodForBrowsers = someHasCoverageV8
|
|
156
|
+
? "playwright"
|
|
157
|
+
: "istanbul"
|
|
158
|
+
}
|
|
140
159
|
if (typeof coverageConfig !== "object") {
|
|
141
160
|
throw new TypeError(
|
|
142
161
|
`coverageConfig must be an object, got ${coverageConfig}`,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { ensureWindowsDriveLetter } from "@jsenv/filesystem"
|
|
2
|
+
import { urlIsInsideOf, moveUrl } from "@jsenv/urls"
|
|
3
|
+
|
|
4
|
+
export const WEB_URL_CONVERTER = {
|
|
5
|
+
asWebUrl: (fileUrl, webServer) => {
|
|
6
|
+
if (urlIsInsideOf(fileUrl, webServer.rootDirectoryUrl)) {
|
|
7
|
+
return moveUrl({
|
|
8
|
+
url: fileUrl,
|
|
9
|
+
from: webServer.rootDirectoryUrl,
|
|
10
|
+
to: `${webServer.origin}/`,
|
|
11
|
+
})
|
|
12
|
+
}
|
|
13
|
+
const fsRootUrl = ensureWindowsDriveLetter("file:///", fileUrl)
|
|
14
|
+
return `${webServer.origin}/@fs/${fileUrl.slice(fsRootUrl.length)}`
|
|
15
|
+
},
|
|
16
|
+
asFileUrl: (webUrl, webServer) => {
|
|
17
|
+
const { pathname, search } = new URL(webUrl)
|
|
18
|
+
if (pathname.startsWith("/@fs/")) {
|
|
19
|
+
const fsRootRelativeUrl = pathname.slice("/@fs/".length)
|
|
20
|
+
return `file:///${fsRootRelativeUrl}${search}`
|
|
21
|
+
}
|
|
22
|
+
return moveUrl({
|
|
23
|
+
url: webUrl,
|
|
24
|
+
from: `${webServer.origin}/`,
|
|
25
|
+
to: webServer.rootDirectoryUrl,
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
}
|