@lvce-editor/test-with-playwright-worker 22.7.1 → 22.8.0

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.
@@ -1,6 +1,10 @@
1
1
  import { join, dirname, basename } from 'node:path';
2
- import { readFile, writeFile, readdir, mkdtemp, rm } from 'node:fs/promises';
3
- import { pathToFileURL } from 'node:url';
2
+ import { readFile, writeFile, readdir, mkdir, rm, access, mkdtemp } from 'node:fs/promises';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ import IstanbulCoverage from 'istanbul-lib-coverage';
5
+ import v8ToIstanbul from 'v8-to-istanbul';
6
+ import IstanbulReport from 'istanbul-lib-report';
7
+ import IstanbulReports from 'istanbul-reports';
4
8
  import net from 'node:net';
5
9
  import os, { tmpdir } from 'node:os';
6
10
  import { fork } from 'node:child_process';
@@ -119,7 +123,7 @@ const Object$1 = 1;
119
123
  const Number$1 = 2;
120
124
  const Array$1 = 3;
121
125
  const String$1 = 4;
122
- const Boolean = 5;
126
+ const Boolean$1 = 5;
123
127
  const Function = 6;
124
128
  const Null = 7;
125
129
  const Unknown = 8;
@@ -140,7 +144,7 @@ const getType = value => {
140
144
  }
141
145
  return Object$1;
142
146
  case 'boolean':
143
- return Boolean;
147
+ return Boolean$1;
144
148
  default:
145
149
  return Unknown;
146
150
  }
@@ -165,7 +169,7 @@ const string = value => {
165
169
  };
166
170
  const boolean = value => {
167
171
  const type = getType(value);
168
- if (type !== Boolean) {
172
+ if (type !== Boolean$1) {
169
173
  throw new AssertionError('expected value to be of type boolean');
170
174
  }
171
175
  };
@@ -944,7 +948,7 @@ const E_NO_TEST_FILES = 'E_NO_TEST_FILES';
944
948
  * @param {any} error
945
949
  */
946
950
  const isEnoentError = error => {
947
- return error && error.code === ENOENT;
951
+ return Boolean(error && error.code === ENOENT);
948
952
  };
949
953
 
950
954
  /**
@@ -987,6 +991,96 @@ const getTests = async testSrc => {
987
991
 
988
992
  const Cli = 6001;
989
993
 
994
+ const browserScriptCache = {};
995
+ const fileExtensionRegex = /\.[^.]+$/;
996
+ const tagBoundaryRegex = />\s*</g;
997
+ const getBrowserScriptCandidates = () => {
998
+ return [fileURLToPath(new URL('svgScreenshot.js', import.meta.url)), fileURLToPath(new URL('../../../../../dist/test-with-playwright-worker/dist/svgScreenshot.js', import.meta.url))];
999
+ };
1000
+ const readBrowserScript = async () => {
1001
+ for (const candidate of getBrowserScriptCandidates()) {
1002
+ try {
1003
+ await access(candidate);
1004
+ return await readFile(candidate, 'utf8');
1005
+ } catch {
1006
+ // Try the next development or distribution path.
1007
+ }
1008
+ }
1009
+ throw new Error('[test-with-playwright] SVG screenshot browser script not found; run npm run build');
1010
+ };
1011
+ const getBrowserScript = () => {
1012
+ browserScriptCache.promise ||= readBrowserScript();
1013
+ return browserScriptCache.promise;
1014
+ };
1015
+ const capture = async (page, selector) => {
1016
+ const browserScript = await getBrowserScript();
1017
+ await page.evaluate(browserScript);
1018
+ return page.evaluate(async selector => {
1019
+ const api = globalThis.SvgScreenshot;
1020
+ if (!api) {
1021
+ throw new Error('SVG screenshot browser API was not initialized');
1022
+ }
1023
+ return api.capture(selector);
1024
+ }, selector);
1025
+ };
1026
+ const formatSvg = svg => {
1027
+ return `${svg.replaceAll(tagBoundaryRegex, '>\n<').trim()}\n`;
1028
+ };
1029
+ const getSnapshotName = (test, name) => {
1030
+ const fileName = basename(test).replace(fileExtensionRegex, '');
1031
+ return `${fileName}.${name}.svg`;
1032
+ };
1033
+ const captureSvgScreenshot = async ({
1034
+ options,
1035
+ page,
1036
+ test
1037
+ }) => {
1038
+ const svg = await capture(page, options.selector);
1039
+ await compareSvgScreenshot({
1040
+ options,
1041
+ svg,
1042
+ test
1043
+ });
1044
+ };
1045
+ const compareSvgScreenshot = async ({
1046
+ options,
1047
+ svg,
1048
+ test
1049
+ }) => {
1050
+ const formattedSvg = formatSvg(svg);
1051
+ const snapshotPath = join(options.directory, getSnapshotName(test, options.name));
1052
+ const actualPath = `${snapshotPath}.actual`;
1053
+ if (options.update) {
1054
+ await mkdir(options.directory, {
1055
+ recursive: true
1056
+ });
1057
+ await writeFile(snapshotPath, formattedSvg);
1058
+ await rm(actualPath, {
1059
+ force: true
1060
+ });
1061
+ return;
1062
+ }
1063
+ let expected;
1064
+ try {
1065
+ expected = await readFile(snapshotPath, 'utf8');
1066
+ } catch (error) {
1067
+ if (isEnoentError(error)) {
1068
+ throw new Error(`[test-with-playwright] SVG screenshot is missing: ${snapshotPath}. Run with --update-svg-screenshots to create it.`);
1069
+ }
1070
+ throw error;
1071
+ }
1072
+ if (expected !== formattedSvg) {
1073
+ await mkdir(options.directory, {
1074
+ recursive: true
1075
+ });
1076
+ await writeFile(actualPath, formattedSvg);
1077
+ throw new Error(`[test-with-playwright] SVG screenshot differs: ${snapshotPath}. Actual output: ${actualPath}`);
1078
+ }
1079
+ await rm(actualPath, {
1080
+ force: true
1081
+ });
1082
+ };
1083
+
990
1084
  const Pass$1 = 1;
991
1085
  const Skip$1 = 2;
992
1086
  const Fail$1 = 3;
@@ -1016,6 +1110,7 @@ const withTimeout = async (promise, timeout) => {
1016
1110
  const runElectronTest = async ({
1017
1111
  electronApp,
1018
1112
  page,
1113
+ svgScreenshotOptions,
1019
1114
  test,
1020
1115
  testSrc,
1021
1116
  timeout
@@ -1042,6 +1137,13 @@ const runElectronTest = async ({
1042
1137
  Locator: page.locator.bind(page),
1043
1138
  page
1044
1139
  }), timeout);
1140
+ if (svgScreenshotOptions) {
1141
+ await captureSvgScreenshot({
1142
+ options: svgScreenshotOptions,
1143
+ page,
1144
+ test
1145
+ });
1146
+ }
1045
1147
  const end = performance.now();
1046
1148
  return {
1047
1149
  end,
@@ -1097,6 +1199,7 @@ const runElectronTests = async ({
1097
1199
  onFinalResult,
1098
1200
  onResult,
1099
1201
  page,
1202
+ svgScreenshotOptions,
1100
1203
  tests,
1101
1204
  testSrc,
1102
1205
  timeout
@@ -1112,7 +1215,10 @@ const runElectronTests = async ({
1112
1215
  page,
1113
1216
  test,
1114
1217
  testSrc,
1115
- timeout
1218
+ timeout,
1219
+ ...(svgScreenshotOptions && {
1220
+ svgScreenshotOptions
1221
+ })
1116
1222
  });
1117
1223
  await onResult(result);
1118
1224
  const resultCounts = getResultCounts$2(result.status);
@@ -1137,19 +1243,16 @@ const Fail = 'fail';
1137
1243
  const getTestState = (testOverlayState, text) => {
1138
1244
  switch (testOverlayState) {
1139
1245
  case Fail:
1140
- // @ts-ignore
1141
1246
  return {
1142
1247
  error: text,
1143
1248
  status: Fail$1
1144
1249
  };
1145
1250
  case Pass:
1146
- // @ts-ignore
1147
1251
  return {
1148
1252
  error: '',
1149
1253
  status: Pass$1
1150
1254
  };
1151
1255
  case Skip:
1152
- // @ts-ignore
1153
1256
  return {
1154
1257
  error: '',
1155
1258
  status: Skip$1
@@ -1176,6 +1279,7 @@ const getUrlFromTestFile = (absolutePath, port, traceFocus) => {
1176
1279
  const runTest = async ({
1177
1280
  page,
1178
1281
  port,
1282
+ svgScreenshotOptions,
1179
1283
  test,
1180
1284
  testSrc,
1181
1285
  timeout,
@@ -1198,6 +1302,13 @@ const runTest = async ({
1198
1302
  const testOverlayState = await testOverlay.getAttribute('data-state');
1199
1303
  // @ts-ignore
1200
1304
  const testState = getTestState(testOverlayState, text || '');
1305
+ if (testState.status === Pass$1 && svgScreenshotOptions) {
1306
+ await captureSvgScreenshot({
1307
+ options: svgScreenshotOptions,
1308
+ page,
1309
+ test
1310
+ });
1311
+ }
1201
1312
  const end = performance.now();
1202
1313
  return {
1203
1314
  // @ts-ignore
@@ -1260,6 +1371,7 @@ const runTests = async ({
1260
1371
  onResult,
1261
1372
  page,
1262
1373
  port,
1374
+ svgScreenshotOptions,
1263
1375
  tests,
1264
1376
  testSrc,
1265
1377
  timeout,
@@ -1278,7 +1390,10 @@ const runTests = async ({
1278
1390
  test,
1279
1391
  testSrc,
1280
1392
  timeout,
1281
- traceFocus: traceFocus ?? false
1393
+ traceFocus: traceFocus ?? false,
1394
+ ...(svgScreenshotOptions && {
1395
+ svgScreenshotOptions
1396
+ })
1282
1397
  });
1283
1398
  await onResult(result);
1284
1399
  // @ts-ignore
@@ -1458,6 +1573,108 @@ const runTestsWithReusedPage = async ({
1458
1573
  });
1459
1574
  };
1460
1575
 
1576
+ const externalSourceMapCommentRegex = /(?:\/\/[#@]\s*sourceMappingURL=(?!data:).*?$|\/\*[#@]\s*sourceMappingURL=(?!data:).*?\*\/)/gm;
1577
+ const temporaryServerRootRegex = /^\/[a-f\d]{7,}(?=\/(?:js|packages)\/)/;
1578
+ const normalizeCoveragePath = path => {
1579
+ return path.replace(temporaryServerRootRegex, '');
1580
+ };
1581
+ const normalizeCoverageData = coverageData => {
1582
+ const normalized = Object.create(null);
1583
+ for (const data of Object.values(coverageData)) {
1584
+ const path = normalizeCoveragePath(data.path);
1585
+ normalized[path] = {
1586
+ ...data,
1587
+ path
1588
+ };
1589
+ }
1590
+ return normalized;
1591
+ };
1592
+ const getCoveragePath = url => {
1593
+ try {
1594
+ return decodeURIComponent(new URL(url).pathname);
1595
+ } catch {
1596
+ return url;
1597
+ }
1598
+ };
1599
+ const isTestScript = url => {
1600
+ try {
1601
+ return new URL(url).pathname.startsWith('/tests/');
1602
+ } catch {
1603
+ return false;
1604
+ }
1605
+ };
1606
+ const addEntryToCoverageMap = async (coverageMap, entry) => {
1607
+ if (!entry.source || !entry.url || isTestScript(entry.url)) {
1608
+ return;
1609
+ }
1610
+ const source = entry.source.replaceAll(externalSourceMapCommentRegex, '');
1611
+ const converter = v8ToIstanbul(getCoveragePath(entry.url), 0, {
1612
+ source
1613
+ });
1614
+ await converter.load();
1615
+ converter.applyCoverage(entry.functions);
1616
+ coverageMap.merge(normalizeCoverageData(converter.toIstanbul()));
1617
+ };
1618
+ const createJavascriptCoverage = async entries => {
1619
+ const coverageMap = IstanbulCoverage.createCoverageMap();
1620
+ for (const entry of entries) {
1621
+ await addEntryToCoverageMap(coverageMap, entry);
1622
+ }
1623
+ return coverageMap;
1624
+ };
1625
+
1626
+ const executeReport = (coverageMap, directory, name) => {
1627
+ const context = IstanbulReport.createContext({
1628
+ coverageMap,
1629
+ dir: directory
1630
+ });
1631
+ IstanbulReports.create(name).execute(context);
1632
+ };
1633
+ const writeJavascriptCoverage = async (coverageMap, directory) => {
1634
+ await rm(directory, {
1635
+ force: true,
1636
+ recursive: true
1637
+ });
1638
+ await mkdir(directory, {
1639
+ recursive: true
1640
+ });
1641
+ executeReport(coverageMap, directory, 'json');
1642
+ executeReport(coverageMap, directory, 'json-summary');
1643
+ executeReport(coverageMap, directory, 'lcovonly');
1644
+ const context = IstanbulReport.createContext({
1645
+ coverageMap,
1646
+ dir: directory
1647
+ });
1648
+ IstanbulReports.create('text', {
1649
+ file: 'coverage.txt'
1650
+ }).execute(context);
1651
+ const summaryText = await readFile(join(directory, 'coverage.txt'), 'utf8');
1652
+ const summary = summaryText.trimEnd();
1653
+ console.info(`[test-with-playwright] JavaScript coverage written to ${directory}\n${summary}`);
1654
+ };
1655
+
1656
+ const runWithJavascriptCoverage = async ({
1657
+ coverage,
1658
+ cwd,
1659
+ page,
1660
+ run
1661
+ }) => {
1662
+ if (!coverage) {
1663
+ await run();
1664
+ return;
1665
+ }
1666
+ await page.coverage.startJSCoverage({
1667
+ resetOnNavigation: false
1668
+ });
1669
+ try {
1670
+ await run();
1671
+ } finally {
1672
+ const entries = await page.coverage.stopJSCoverage();
1673
+ const coverageMap = await createJavascriptCoverage(entries);
1674
+ await writeJavascriptCoverage(coverageMap, join(cwd, 'coverage'));
1675
+ }
1676
+ };
1677
+
1461
1678
  class Locked extends Error {
1462
1679
  constructor(port) {
1463
1680
  super(`${port} is locked`);
@@ -1898,8 +2115,9 @@ const tearDownTests = async ({
1898
2115
  * @param {boolean} traceFocus
1899
2116
  * @param {string} filter
1900
2117
  * @param {boolean} reusePage
2118
+ * @param {boolean} coverage
1901
2119
  */
1902
- const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter, reusePage) => {
2120
+ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter, reusePage, svgScreenshotOptions, coverage) => {
1903
2121
  string(extensionPath);
1904
2122
  string(testPath);
1905
2123
  string(cwd);
@@ -1908,6 +2126,10 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1908
2126
  number(timeout);
1909
2127
  object(runtimeOptions);
1910
2128
  boolean(reusePage);
2129
+ boolean(coverage);
2130
+ if (svgScreenshotOptions !== undefined) {
2131
+ object(svgScreenshotOptions);
2132
+ }
1911
2133
  const rpc = get(Cli);
1912
2134
  const controller = new AbortController();
1913
2135
  const {
@@ -1929,15 +2151,25 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1929
2151
  runtimeOptions,
1930
2152
  signal
1931
2153
  });
1932
- await runElectronTests({
1933
- ...filterOption,
1934
- electronApp: electron.electronApp,
1935
- onFinalResult,
1936
- onResult,
2154
+ await runWithJavascriptCoverage({
2155
+ coverage,
2156
+ cwd,
1937
2157
  page: electron.page,
1938
- tests,
1939
- testSrc,
1940
- timeout
2158
+ run: async () => {
2159
+ await runElectronTests({
2160
+ ...filterOption,
2161
+ electronApp: electron.electronApp,
2162
+ onFinalResult,
2163
+ onResult,
2164
+ page: electron.page,
2165
+ tests,
2166
+ testSrc,
2167
+ timeout,
2168
+ ...(svgScreenshotOptions && {
2169
+ svgScreenshotOptions
2170
+ })
2171
+ });
2172
+ }
1941
2173
  });
1942
2174
  return;
1943
2175
  }
@@ -1955,39 +2187,55 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1955
2187
  signal,
1956
2188
  testPath
1957
2189
  });
1958
- if (reusePage) {
1959
- await runTestsWithReusedPage({
1960
- ...filterOption,
1961
- onFinalResult,
1962
- onResult,
2190
+ try {
2191
+ if (reusePage) {
2192
+ await runWithJavascriptCoverage({
2193
+ coverage,
2194
+ cwd,
2195
+ page,
2196
+ run: async () => {
2197
+ await runTestsWithReusedPage({
2198
+ ...filterOption,
2199
+ onFinalResult,
2200
+ onResult,
2201
+ page,
2202
+ port,
2203
+ timeout,
2204
+ traceFocus
2205
+ });
2206
+ }
2207
+ });
2208
+ return;
2209
+ }
2210
+ const tests = await getTests(testSrc);
2211
+ await runWithJavascriptCoverage({
2212
+ coverage,
2213
+ cwd,
1963
2214
  page,
1964
- port,
1965
- timeout,
1966
- traceFocus
2215
+ run: async () => {
2216
+ await runTests({
2217
+ ...filterOption,
2218
+ headless,
2219
+ onFinalResult,
2220
+ onResult,
2221
+ page,
2222
+ port,
2223
+ tests,
2224
+ testSrc,
2225
+ timeout,
2226
+ traceFocus,
2227
+ ...(svgScreenshotOptions && {
2228
+ svgScreenshotOptions
2229
+ })
2230
+ });
2231
+ }
1967
2232
  });
2233
+ } finally {
1968
2234
  await tearDownTests({
1969
2235
  child,
1970
2236
  controller
1971
2237
  });
1972
- return;
1973
2238
  }
1974
- const tests = await getTests(testSrc);
1975
- await runTests({
1976
- ...filterOption,
1977
- headless,
1978
- onFinalResult,
1979
- onResult,
1980
- page,
1981
- port,
1982
- tests,
1983
- testSrc,
1984
- timeout,
1985
- traceFocus
1986
- });
1987
- await tearDownTests({
1988
- child,
1989
- controller
1990
- });
1991
2239
  };
1992
2240
 
1993
2241
  const RunAllTests = 'RunAllTests';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/test-with-playwright-worker",
3
- "version": "22.7.1",
3
+ "version": "22.8.0",
4
4
  "description": "Worker package for test-with-playwright",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,16 @@
13
13
  "main": "dist/workerMain.js",
14
14
  "dependencies": {
15
15
  "@playwright/test": "1.61.1",
16
- "get-port": "^7.2.0"
16
+ "get-port": "^7.2.0",
17
+ "istanbul-lib-coverage": "^3.2.2",
18
+ "istanbul-lib-report": "^3.0.1",
19
+ "istanbul-reports": "^3.2.0",
20
+ "v8-to-istanbul": "^9.3.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/istanbul-lib-coverage": "^2.0.6",
24
+ "@types/istanbul-lib-report": "^3.0.3",
25
+ "@types/istanbul-reports": "^3.0.4"
17
26
  },
18
27
  "engines": {
19
28
  "node": ">=24"