@lvce-editor/test-with-playwright-worker 22.7.1 → 22.8.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.
@@ -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
@@ -1431,6 +1546,7 @@ const runTestsWithReusedPage = async ({
1431
1546
  try {
1432
1547
  const url = getAllTestsUrl(port, filter, traceFocus ?? false);
1433
1548
  await page.goto(url, {
1549
+ timeout,
1434
1550
  waitUntil: 'networkidle'
1435
1551
  });
1436
1552
  const text = await readTestResultsText(page, timeout);
@@ -1458,6 +1574,108 @@ const runTestsWithReusedPage = async ({
1458
1574
  });
1459
1575
  };
1460
1576
 
1577
+ const externalSourceMapCommentRegex = /(?:\/\/[#@]\s*sourceMappingURL=(?!data:).*?$|\/\*[#@]\s*sourceMappingURL=(?!data:).*?\*\/)/gm;
1578
+ const temporaryServerRootRegex = /^\/[a-f\d]{7,}(?=\/(?:js|packages)\/)/;
1579
+ const normalizeCoveragePath = path => {
1580
+ return path.replace(temporaryServerRootRegex, '');
1581
+ };
1582
+ const normalizeCoverageData = coverageData => {
1583
+ const normalized = Object.create(null);
1584
+ for (const data of Object.values(coverageData)) {
1585
+ const path = normalizeCoveragePath(data.path);
1586
+ normalized[path] = {
1587
+ ...data,
1588
+ path
1589
+ };
1590
+ }
1591
+ return normalized;
1592
+ };
1593
+ const getCoveragePath = url => {
1594
+ try {
1595
+ return decodeURIComponent(new URL(url).pathname);
1596
+ } catch {
1597
+ return url;
1598
+ }
1599
+ };
1600
+ const isTestScript = url => {
1601
+ try {
1602
+ return new URL(url).pathname.startsWith('/tests/');
1603
+ } catch {
1604
+ return false;
1605
+ }
1606
+ };
1607
+ const addEntryToCoverageMap = async (coverageMap, entry) => {
1608
+ if (!entry.source || !entry.url || isTestScript(entry.url)) {
1609
+ return;
1610
+ }
1611
+ const source = entry.source.replaceAll(externalSourceMapCommentRegex, '');
1612
+ const converter = v8ToIstanbul(getCoveragePath(entry.url), 0, {
1613
+ source
1614
+ });
1615
+ await converter.load();
1616
+ converter.applyCoverage(entry.functions);
1617
+ coverageMap.merge(normalizeCoverageData(converter.toIstanbul()));
1618
+ };
1619
+ const createJavascriptCoverage = async entries => {
1620
+ const coverageMap = IstanbulCoverage.createCoverageMap();
1621
+ for (const entry of entries) {
1622
+ await addEntryToCoverageMap(coverageMap, entry);
1623
+ }
1624
+ return coverageMap;
1625
+ };
1626
+
1627
+ const executeReport = (coverageMap, directory, name) => {
1628
+ const context = IstanbulReport.createContext({
1629
+ coverageMap,
1630
+ dir: directory
1631
+ });
1632
+ IstanbulReports.create(name).execute(context);
1633
+ };
1634
+ const writeJavascriptCoverage = async (coverageMap, directory) => {
1635
+ await rm(directory, {
1636
+ force: true,
1637
+ recursive: true
1638
+ });
1639
+ await mkdir(directory, {
1640
+ recursive: true
1641
+ });
1642
+ executeReport(coverageMap, directory, 'json');
1643
+ executeReport(coverageMap, directory, 'json-summary');
1644
+ executeReport(coverageMap, directory, 'lcovonly');
1645
+ const context = IstanbulReport.createContext({
1646
+ coverageMap,
1647
+ dir: directory
1648
+ });
1649
+ IstanbulReports.create('text', {
1650
+ file: 'coverage.txt'
1651
+ }).execute(context);
1652
+ const summaryText = await readFile(join(directory, 'coverage.txt'), 'utf8');
1653
+ const summary = summaryText.trimEnd();
1654
+ console.info(`[test-with-playwright] JavaScript coverage written to ${directory}\n${summary}`);
1655
+ };
1656
+
1657
+ const runWithJavascriptCoverage = async ({
1658
+ coverage,
1659
+ cwd,
1660
+ page,
1661
+ run
1662
+ }) => {
1663
+ if (!coverage) {
1664
+ await run();
1665
+ return;
1666
+ }
1667
+ await page.coverage.startJSCoverage({
1668
+ resetOnNavigation: false
1669
+ });
1670
+ try {
1671
+ await run();
1672
+ } finally {
1673
+ const entries = await page.coverage.stopJSCoverage();
1674
+ const coverageMap = await createJavascriptCoverage(entries);
1675
+ await writeJavascriptCoverage(coverageMap, join(cwd, 'coverage'));
1676
+ }
1677
+ };
1678
+
1461
1679
  class Locked extends Error {
1462
1680
  constructor(port) {
1463
1681
  super(`${port} is locked`);
@@ -1898,8 +2116,9 @@ const tearDownTests = async ({
1898
2116
  * @param {boolean} traceFocus
1899
2117
  * @param {string} filter
1900
2118
  * @param {boolean} reusePage
2119
+ * @param {boolean} coverage
1901
2120
  */
1902
- const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter, reusePage) => {
2121
+ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter, reusePage, svgScreenshotOptions, coverage) => {
1903
2122
  string(extensionPath);
1904
2123
  string(testPath);
1905
2124
  string(cwd);
@@ -1908,6 +2127,10 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1908
2127
  number(timeout);
1909
2128
  object(runtimeOptions);
1910
2129
  boolean(reusePage);
2130
+ boolean(coverage);
2131
+ if (svgScreenshotOptions !== undefined) {
2132
+ object(svgScreenshotOptions);
2133
+ }
1911
2134
  const rpc = get(Cli);
1912
2135
  const controller = new AbortController();
1913
2136
  const {
@@ -1929,15 +2152,25 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1929
2152
  runtimeOptions,
1930
2153
  signal
1931
2154
  });
1932
- await runElectronTests({
1933
- ...filterOption,
1934
- electronApp: electron.electronApp,
1935
- onFinalResult,
1936
- onResult,
2155
+ await runWithJavascriptCoverage({
2156
+ coverage,
2157
+ cwd,
1937
2158
  page: electron.page,
1938
- tests,
1939
- testSrc,
1940
- timeout
2159
+ run: async () => {
2160
+ await runElectronTests({
2161
+ ...filterOption,
2162
+ electronApp: electron.electronApp,
2163
+ onFinalResult,
2164
+ onResult,
2165
+ page: electron.page,
2166
+ tests,
2167
+ testSrc,
2168
+ timeout,
2169
+ ...(svgScreenshotOptions && {
2170
+ svgScreenshotOptions
2171
+ })
2172
+ });
2173
+ }
1941
2174
  });
1942
2175
  return;
1943
2176
  }
@@ -1955,39 +2188,55 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1955
2188
  signal,
1956
2189
  testPath
1957
2190
  });
1958
- if (reusePage) {
1959
- await runTestsWithReusedPage({
1960
- ...filterOption,
1961
- onFinalResult,
1962
- onResult,
2191
+ try {
2192
+ if (reusePage) {
2193
+ await runWithJavascriptCoverage({
2194
+ coverage,
2195
+ cwd,
2196
+ page,
2197
+ run: async () => {
2198
+ await runTestsWithReusedPage({
2199
+ ...filterOption,
2200
+ onFinalResult,
2201
+ onResult,
2202
+ page,
2203
+ port,
2204
+ timeout,
2205
+ traceFocus
2206
+ });
2207
+ }
2208
+ });
2209
+ return;
2210
+ }
2211
+ const tests = await getTests(testSrc);
2212
+ await runWithJavascriptCoverage({
2213
+ coverage,
2214
+ cwd,
1963
2215
  page,
1964
- port,
1965
- timeout,
1966
- traceFocus
2216
+ run: async () => {
2217
+ await runTests({
2218
+ ...filterOption,
2219
+ headless,
2220
+ onFinalResult,
2221
+ onResult,
2222
+ page,
2223
+ port,
2224
+ tests,
2225
+ testSrc,
2226
+ timeout,
2227
+ traceFocus,
2228
+ ...(svgScreenshotOptions && {
2229
+ svgScreenshotOptions
2230
+ })
2231
+ });
2232
+ }
1967
2233
  });
2234
+ } finally {
1968
2235
  await tearDownTests({
1969
2236
  child,
1970
2237
  controller
1971
2238
  });
1972
- return;
1973
2239
  }
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
2240
  };
1992
2241
 
1993
2242
  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.1",
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"