@lvce-editor/test-with-playwright-worker 18.0.0 → 19.0.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.
Files changed (2) hide show
  1. package/dist/workerMain.js +411 -46
  2. package/package.json +1 -1
@@ -1,10 +1,11 @@
1
- import { basename, join } from 'node:path';
1
+ import { join, basename } from 'node:path';
2
2
  import { readdir } from 'node:fs/promises';
3
3
  import { expect, firefox, chromium } from '@playwright/test';
4
+ import { pathToFileURL } from 'node:url';
4
5
  import net from 'node:net';
5
6
  import os from 'node:os';
6
- import { pathToFileURL } from 'node:url';
7
- import { fork } from 'node:child_process';
7
+ import { fork, spawn } from 'node:child_process';
8
+ import { createInterface } from 'node:readline';
8
9
 
9
10
  const normalizeLine = line => {
10
11
  if (line.startsWith('Error: ')) {
@@ -98,6 +99,12 @@ const getType = value => {
98
99
  return Unknown;
99
100
  }
100
101
  };
102
+ const object = value => {
103
+ const type = getType(value);
104
+ if (type !== Object$1) {
105
+ throw new AssertionError('expected value to be of type object');
106
+ }
107
+ };
101
108
  const number = value => {
102
109
  const type = getType(value);
103
110
  if (type !== Number) {
@@ -904,33 +911,169 @@ const getTests = async testSrc => {
904
911
 
905
912
  const Cli = 6001;
906
913
 
907
- const Pass$1 = 'pass';
908
- const Skip$1 = 'skip';
909
- const Fail$1 = 'fail';
914
+ const Pass$1 = 1;
915
+ const Skip$1 = 2;
916
+ const Fail$1 = 3;
910
917
 
911
- const Pass = 1;
912
- const Skip = 2;
913
- const Fail = 3;
918
+ const importTestModule = async (testSrc, test) => {
919
+ const testPath = join(testSrc, test);
920
+ const testModule = await import(pathToFileURL(testPath).toString());
921
+ return testModule;
922
+ };
923
+ const withTimeout = async (promise, timeout) => {
924
+ return new Promise((resolve, reject) => {
925
+ const timer = setTimeout(() => {
926
+ reject(new Error(`Electron test timed out after ${timeout}ms`));
927
+ }, timeout);
928
+ void (async () => {
929
+ try {
930
+ const value = await promise;
931
+ resolve(value);
932
+ } catch (error) {
933
+ reject(error instanceof Error ? error : new Error(String(error)));
934
+ } finally {
935
+ clearTimeout(timer);
936
+ }
937
+ })();
938
+ });
939
+ };
940
+ const runElectronTest = async ({
941
+ electronApp,
942
+ page,
943
+ test,
944
+ testSrc,
945
+ timeout
946
+ }) => {
947
+ const start = performance.now();
948
+ try {
949
+ const testModule = await importTestModule(testSrc, test);
950
+ if (testModule.skip) {
951
+ const end = performance.now();
952
+ return {
953
+ end,
954
+ error: '',
955
+ name: test,
956
+ start,
957
+ status: Skip$1
958
+ };
959
+ }
960
+ await withTimeout(testModule.test({
961
+ electronApp,
962
+ expect,
963
+ Locator: page.locator.bind(page),
964
+ page
965
+ }), timeout);
966
+ const end = performance.now();
967
+ return {
968
+ end,
969
+ error: '',
970
+ name: test,
971
+ start,
972
+ status: Pass$1
973
+ };
974
+ } catch (error) {
975
+ const end = performance.now();
976
+ const message = error instanceof Error ? error.message : String(error);
977
+ return {
978
+ end,
979
+ error: message,
980
+ name: test,
981
+ start,
982
+ status: Fail$1
983
+ };
984
+ }
985
+ };
986
+
987
+ const getResultCounts$1 = status => {
988
+ switch (status) {
989
+ case Fail$1:
990
+ return {
991
+ failed: 1,
992
+ passed: 0,
993
+ skipped: 0
994
+ };
995
+ case Pass$1:
996
+ return {
997
+ failed: 0,
998
+ passed: 1,
999
+ skipped: 0
1000
+ };
1001
+ case Skip$1:
1002
+ return {
1003
+ failed: 0,
1004
+ passed: 0,
1005
+ skipped: 1
1006
+ };
1007
+ default:
1008
+ return {
1009
+ failed: 0,
1010
+ passed: 0,
1011
+ skipped: 0
1012
+ };
1013
+ }
1014
+ };
1015
+ const runElectronTests = async ({
1016
+ electronApp,
1017
+ filter,
1018
+ onFinalResult,
1019
+ onResult,
1020
+ page,
1021
+ tests,
1022
+ testSrc,
1023
+ timeout
1024
+ }) => {
1025
+ let failed = 0;
1026
+ let skipped = 0;
1027
+ let passed = 0;
1028
+ const start = performance.now();
1029
+ const filteredTests = filter ? tests.filter(test => test.includes(filter)) : tests;
1030
+ for (const test of filteredTests) {
1031
+ const result = await runElectronTest({
1032
+ electronApp,
1033
+ page,
1034
+ test,
1035
+ testSrc,
1036
+ timeout
1037
+ });
1038
+ await onResult(result);
1039
+ const resultCounts = getResultCounts$1(result.status);
1040
+ failed += resultCounts.failed;
1041
+ passed += resultCounts.passed;
1042
+ skipped += resultCounts.skipped;
1043
+ }
1044
+ const end = performance.now();
1045
+ await onFinalResult({
1046
+ end,
1047
+ failed,
1048
+ passed,
1049
+ skipped,
1050
+ start
1051
+ });
1052
+ };
1053
+
1054
+ const Pass = 'pass';
1055
+ const Skip = 'skip';
1056
+ const Fail = 'fail';
914
1057
 
915
1058
  const getTestState = (testOverlayState, text) => {
916
1059
  switch (testOverlayState) {
917
- case Fail$1:
1060
+ case Fail:
918
1061
  // @ts-ignore
919
1062
  return {
920
- error: `${text}`,
921
- status: Fail
1063
+ error: text,
1064
+ status: Fail$1
922
1065
  };
923
- case Pass$1:
1066
+ case Pass:
924
1067
  // @ts-ignore
925
1068
  return {
926
1069
  error: '',
927
- status: Pass
1070
+ status: Pass$1
928
1071
  };
929
- case Skip$1:
1072
+ case Skip:
930
1073
  // @ts-ignore
931
1074
  return {
932
1075
  error: '',
933
- status: Skip
1076
+ status: Skip$1
934
1077
  };
935
1078
  default:
936
1079
  throw new Error(`unexpected test state: ${testOverlayState}`);
@@ -972,7 +1115,7 @@ const runTest = async ({
972
1115
  const text = await testOverlay.textContent();
973
1116
  const testOverlayState = await testOverlay.getAttribute('data-state');
974
1117
  // @ts-ignore
975
- const testState = getTestState(testOverlayState);
1118
+ const testState = getTestState(testOverlayState, text || '');
976
1119
  const end = performance.now();
977
1120
  return {
978
1121
  // @ts-ignore
@@ -984,17 +1127,46 @@ const runTest = async ({
984
1127
  };
985
1128
  } catch (error) {
986
1129
  const end = performance.now();
987
- const message = error instanceof Error ? error.message : `${error}`;
1130
+ const message = error instanceof Error ? error.message : String(error);
988
1131
  return {
989
1132
  end,
990
1133
  error: message,
991
1134
  name: test,
992
1135
  start,
993
- status: Fail
1136
+ status: Fail$1
994
1137
  };
995
1138
  }
996
1139
  };
997
1140
 
1141
+ const getResultCounts = status => {
1142
+ switch (status) {
1143
+ case Fail$1:
1144
+ return {
1145
+ failed: 1,
1146
+ passed: 0,
1147
+ skipped: 0
1148
+ };
1149
+ case Pass$1:
1150
+ return {
1151
+ failed: 0,
1152
+ passed: 1,
1153
+ skipped: 0
1154
+ };
1155
+ case Skip$1:
1156
+ return {
1157
+ failed: 0,
1158
+ passed: 0,
1159
+ skipped: 1
1160
+ };
1161
+ default:
1162
+ return {
1163
+ failed: 0,
1164
+ passed: 0,
1165
+ skipped: 0
1166
+ };
1167
+ }
1168
+ };
1169
+
998
1170
  /**
999
1171
  *
1000
1172
  * @param {{testSrc:string, tests:string[], filter?: string, headless:boolean, page: import('@playwright/test').Page, port:number, timeout:number, onResult:any, onFinalResult:any}} param0
@@ -1028,17 +1200,10 @@ const runTests = async ({
1028
1200
  });
1029
1201
  await onResult(result);
1030
1202
  // @ts-ignore
1031
- switch (result.status) {
1032
- case Fail:
1033
- failed++;
1034
- break;
1035
- case Pass:
1036
- passed++;
1037
- break;
1038
- case Skip:
1039
- skipped++;
1040
- break;
1041
- }
1203
+ const resultCounts = getResultCounts(result.status);
1204
+ failed += resultCounts.failed;
1205
+ passed += resultCounts.passed;
1206
+ skipped += resultCounts.skipped;
1042
1207
  }
1043
1208
  const end = performance.now();
1044
1209
  await onFinalResult({
@@ -1303,14 +1468,194 @@ const setupTests = async ({
1303
1468
  };
1304
1469
  };
1305
1470
 
1471
+ const getProcessEnv = () => {
1472
+ const env = Object.create(null);
1473
+ for (const [key, value] of Object.entries(process.env)) {
1474
+ if (value !== undefined) {
1475
+ env[key] = value;
1476
+ }
1477
+ }
1478
+ return env;
1479
+ };
1480
+ const getElectronLaunchOptions = ({
1481
+ args,
1482
+ env,
1483
+ executablePath
1484
+ }) => {
1485
+ const launchEnv = {
1486
+ ...getProcessEnv(),
1487
+ ...env
1488
+ };
1489
+ delete launchEnv['ELECTRON_RUN_AS_NODE'];
1490
+ return {
1491
+ args,
1492
+ env: launchEnv,
1493
+ executablePath
1494
+ };
1495
+ };
1496
+
1306
1497
  const SIGINT = 'SIGINT';
1307
1498
 
1499
+ const devtoolsRegex = /^DevTools listening on (ws:\/\/.*)$/;
1500
+ const waitForDevtoolsEndpoint = async child => {
1501
+ const {
1502
+ stderr
1503
+ } = child;
1504
+ if (!stderr) {
1505
+ throw new Error('Electron stderr is unavailable');
1506
+ }
1507
+ const lines = createInterface({
1508
+ input: stderr
1509
+ });
1510
+ const {
1511
+ promise,
1512
+ reject,
1513
+ resolve
1514
+ } = Promise.withResolvers();
1515
+ const onExit = () => {
1516
+ reject(new Error('Electron exited before DevTools endpoint was available'));
1517
+ };
1518
+ const onLine = line => {
1519
+ const match = line.match(devtoolsRegex);
1520
+ if (match) {
1521
+ resolve(match[1]);
1522
+ }
1523
+ };
1524
+ child.once('exit', onExit);
1525
+ lines.on('line', onLine);
1526
+ try {
1527
+ return await promise;
1528
+ } finally {
1529
+ child.off('exit', onExit);
1530
+ lines.off('line', onLine);
1531
+ lines.close();
1532
+ }
1533
+ };
1534
+ const getFirstPage = async browser => {
1535
+ const context = browser.contexts()[0];
1536
+ const pages = context.pages();
1537
+ if (pages.length > 0) {
1538
+ return pages[0];
1539
+ }
1540
+ return context.waitForEvent('page', {
1541
+ timeout: 15_000
1542
+ });
1543
+ };
1544
+ const closeElectron = async ({
1545
+ browser,
1546
+ child
1547
+ }) => {
1548
+ try {
1549
+ await browser.close();
1550
+ } catch {
1551
+ // ignore close errors during cleanup
1552
+ }
1553
+ if (!child.killed) {
1554
+ child.kill(SIGINT);
1555
+ }
1556
+ };
1557
+ const createElectronLaunch = ({
1558
+ browser,
1559
+ child,
1560
+ page,
1561
+ signal
1562
+ }) => {
1563
+ let disposed = false;
1564
+ const dispose = async () => {
1565
+ if (disposed) {
1566
+ return;
1567
+ }
1568
+ disposed = true;
1569
+ signal.removeEventListener('abort', handleAbort);
1570
+ process.off('SIGINT', handleSigint);
1571
+ process.off('SIGTERM', handleSigterm);
1572
+ await closeElectron({
1573
+ browser,
1574
+ child
1575
+ });
1576
+ };
1577
+ const handleAbort = () => {
1578
+ void dispose();
1579
+ };
1580
+ const handleProcessSignal = processSignal => {
1581
+ void (async () => {
1582
+ try {
1583
+ await dispose();
1584
+ } finally {
1585
+ process.kill(process.pid, processSignal);
1586
+ }
1587
+ })();
1588
+ };
1589
+ const handleSigint = () => {
1590
+ handleProcessSignal('SIGINT');
1591
+ };
1592
+ const handleSigterm = () => {
1593
+ handleProcessSignal('SIGTERM');
1594
+ };
1595
+ signal.addEventListener('abort', handleAbort);
1596
+ process.once('SIGINT', handleSigint);
1597
+ process.once('SIGTERM', handleSigterm);
1598
+ const electronApp = {
1599
+ close: dispose,
1600
+ process: () => {
1601
+ return child;
1602
+ },
1603
+ [Symbol.asyncDispose]: dispose
1604
+ };
1605
+ return {
1606
+ electronApp,
1607
+ page,
1608
+ [Symbol.asyncDispose]: dispose
1609
+ };
1610
+ };
1611
+ const startElectron = async ({
1612
+ runtimeOptions,
1613
+ signal
1614
+ }) => {
1615
+ const launchOptions = getElectronLaunchOptions(runtimeOptions);
1616
+ const child = spawn(launchOptions.executablePath, ['--remote-debugging-port=0', ...launchOptions.args], {
1617
+ env: launchOptions.env,
1618
+ stdio: ['ignore', 'ignore', 'pipe']
1619
+ });
1620
+ let browser;
1621
+ try {
1622
+ const endpoint = await waitForDevtoolsEndpoint(child);
1623
+ browser = await chromium.connectOverCDP(endpoint);
1624
+ const page = await getFirstPage(browser);
1625
+ return createElectronLaunch({
1626
+ browser,
1627
+ child,
1628
+ page,
1629
+ signal
1630
+ });
1631
+ } catch (error) {
1632
+ if (browser) {
1633
+ try {
1634
+ await browser.close();
1635
+ } catch {
1636
+ // ignore close errors during cleanup
1637
+ }
1638
+ }
1639
+ if (!child.killed) {
1640
+ child.kill(SIGINT);
1641
+ }
1642
+ throw error;
1643
+ }
1644
+ };
1645
+
1308
1646
  const tearDownTests = async ({
1309
1647
  child,
1310
- controller
1648
+ controller,
1649
+ kill
1311
1650
  }) => {
1312
1651
  controller.abort();
1313
- child.kill(SIGINT);
1652
+ if (kill) {
1653
+ await kill();
1654
+ return;
1655
+ }
1656
+ if (child) {
1657
+ child.kill(SIGINT);
1658
+ }
1314
1659
  };
1315
1660
 
1316
1661
  /**
@@ -1324,19 +1669,47 @@ const tearDownTests = async ({
1324
1669
  * @param {boolean} traceFocus
1325
1670
  * @param {string} filter
1326
1671
  */
1327
- const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, serverPath, traceFocus, filter) => {
1672
+ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter) => {
1328
1673
  string(extensionPath);
1329
1674
  string(testPath);
1330
1675
  string(cwd);
1331
1676
  string(browser);
1332
1677
  boolean(headless);
1333
1678
  number(timeout);
1679
+ object(runtimeOptions);
1334
1680
  const rpc = get(Cli);
1335
1681
  const controller = new AbortController();
1336
1682
  const {
1337
1683
  signal
1338
1684
  } = controller;
1339
- // @ts-ignore
1685
+ const testSrc = join(testPath, 'src');
1686
+ const tests = await getTests(testSrc);
1687
+ const onResult = async result => {
1688
+ await rpc.invoke(HandleResult, result);
1689
+ };
1690
+ const onFinalResult = async finalResult => {
1691
+ await rpc.invoke(HandleFinalResult, finalResult);
1692
+ };
1693
+ const filterOption = filter === undefined ? undefined : {
1694
+ filter
1695
+ };
1696
+ if (runtimeOptions.type === 'electron') {
1697
+ await using electron = await startElectron({
1698
+ runtimeOptions,
1699
+ signal
1700
+ });
1701
+ await runElectronTests({
1702
+ ...filterOption,
1703
+ electronApp: electron.electronApp,
1704
+ onFinalResult,
1705
+ onResult,
1706
+ page: electron.page,
1707
+ tests,
1708
+ testSrc,
1709
+ timeout
1710
+ });
1711
+ return;
1712
+ }
1340
1713
  const {
1341
1714
  child,
1342
1715
  page,
@@ -1345,22 +1718,14 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
1345
1718
  browser,
1346
1719
  headless,
1347
1720
  onlyExtension: extensionPath,
1348
- serverPath,
1721
+ ...(runtimeOptions.serverPath && {
1722
+ serverPath: runtimeOptions.serverPath
1723
+ }),
1349
1724
  signal,
1350
1725
  testPath
1351
1726
  });
1352
- const testSrc = join(testPath, 'src');
1353
- const tests = await getTests(testSrc);
1354
- const onResult = async result => {
1355
- await rpc.invoke(HandleResult, result);
1356
- };
1357
- const onFinalResult = async finalResult => {
1358
- await rpc.invoke(HandleFinalResult, finalResult);
1359
- };
1360
1727
  await runTests({
1361
- ...(filter ? {
1362
- filter
1363
- } : {}),
1728
+ ...filterOption,
1364
1729
  headless,
1365
1730
  onFinalResult,
1366
1731
  onResult,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lvce-editor/test-with-playwright-worker",
3
- "version": "18.0.0",
3
+ "version": "19.0.0",
4
4
  "description": "Worker package for test-with-playwright",
5
5
  "repository": {
6
6
  "type": "git",