@lvce-editor/test-with-playwright-worker 18.1.0 → 20.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.
- package/dist/workerMain.js +377 -36
- package/package.json +3 -2
package/dist/workerMain.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
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 {
|
|
7
|
-
import {
|
|
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 =
|
|
908
|
-
const Skip$1 =
|
|
909
|
-
const Fail$1 =
|
|
914
|
+
const Pass$1 = 1;
|
|
915
|
+
const Skip$1 = 2;
|
|
916
|
+
const Fail$1 = 3;
|
|
910
917
|
|
|
911
|
-
const
|
|
912
|
-
const
|
|
913
|
-
const
|
|
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
|
|
1060
|
+
case Fail:
|
|
918
1061
|
// @ts-ignore
|
|
919
1062
|
return {
|
|
920
1063
|
error: text,
|
|
921
|
-
status: Fail
|
|
1064
|
+
status: Fail$1
|
|
922
1065
|
};
|
|
923
|
-
case Pass
|
|
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
|
|
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}`);
|
|
@@ -990,26 +1133,26 @@ const runTest = async ({
|
|
|
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
|
|
|
998
1141
|
const getResultCounts = status => {
|
|
999
1142
|
switch (status) {
|
|
1000
|
-
case Fail:
|
|
1143
|
+
case Fail$1:
|
|
1001
1144
|
return {
|
|
1002
1145
|
failed: 1,
|
|
1003
1146
|
passed: 0,
|
|
1004
1147
|
skipped: 0
|
|
1005
1148
|
};
|
|
1006
|
-
case Pass:
|
|
1149
|
+
case Pass$1:
|
|
1007
1150
|
return {
|
|
1008
1151
|
failed: 0,
|
|
1009
1152
|
passed: 1,
|
|
1010
1153
|
skipped: 0
|
|
1011
1154
|
};
|
|
1012
|
-
case Skip:
|
|
1155
|
+
case Skip$1:
|
|
1013
1156
|
return {
|
|
1014
1157
|
failed: 0,
|
|
1015
1158
|
passed: 0,
|
|
@@ -1195,7 +1338,6 @@ async function getPorts(options) {
|
|
|
1195
1338
|
}
|
|
1196
1339
|
|
|
1197
1340
|
const getPort = () => {
|
|
1198
|
-
// @ts-ignore
|
|
1199
1341
|
return getPorts();
|
|
1200
1342
|
};
|
|
1201
1343
|
|
|
@@ -1325,14 +1467,194 @@ const setupTests = async ({
|
|
|
1325
1467
|
};
|
|
1326
1468
|
};
|
|
1327
1469
|
|
|
1470
|
+
const getProcessEnv = () => {
|
|
1471
|
+
const env = Object.create(null);
|
|
1472
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
1473
|
+
if (value !== undefined) {
|
|
1474
|
+
env[key] = value;
|
|
1475
|
+
}
|
|
1476
|
+
}
|
|
1477
|
+
return env;
|
|
1478
|
+
};
|
|
1479
|
+
const getElectronLaunchOptions = ({
|
|
1480
|
+
args,
|
|
1481
|
+
env,
|
|
1482
|
+
executablePath
|
|
1483
|
+
}) => {
|
|
1484
|
+
const launchEnv = {
|
|
1485
|
+
...getProcessEnv(),
|
|
1486
|
+
...env
|
|
1487
|
+
};
|
|
1488
|
+
delete launchEnv['ELECTRON_RUN_AS_NODE'];
|
|
1489
|
+
return {
|
|
1490
|
+
args,
|
|
1491
|
+
env: launchEnv,
|
|
1492
|
+
executablePath
|
|
1493
|
+
};
|
|
1494
|
+
};
|
|
1495
|
+
|
|
1328
1496
|
const SIGINT = 'SIGINT';
|
|
1329
1497
|
|
|
1498
|
+
const devtoolsRegex = /^DevTools listening on (ws:\/\/.*)$/;
|
|
1499
|
+
const waitForDevtoolsEndpoint = async child => {
|
|
1500
|
+
const {
|
|
1501
|
+
stderr
|
|
1502
|
+
} = child;
|
|
1503
|
+
if (!stderr) {
|
|
1504
|
+
throw new Error('Electron stderr is unavailable');
|
|
1505
|
+
}
|
|
1506
|
+
const lines = createInterface({
|
|
1507
|
+
input: stderr
|
|
1508
|
+
});
|
|
1509
|
+
const {
|
|
1510
|
+
promise,
|
|
1511
|
+
reject,
|
|
1512
|
+
resolve
|
|
1513
|
+
} = Promise.withResolvers();
|
|
1514
|
+
const onExit = () => {
|
|
1515
|
+
reject(new Error('Electron exited before DevTools endpoint was available'));
|
|
1516
|
+
};
|
|
1517
|
+
const onLine = line => {
|
|
1518
|
+
const match = line.match(devtoolsRegex);
|
|
1519
|
+
if (match) {
|
|
1520
|
+
resolve(match[1]);
|
|
1521
|
+
}
|
|
1522
|
+
};
|
|
1523
|
+
child.once('exit', onExit);
|
|
1524
|
+
lines.on('line', onLine);
|
|
1525
|
+
try {
|
|
1526
|
+
return await promise;
|
|
1527
|
+
} finally {
|
|
1528
|
+
child.off('exit', onExit);
|
|
1529
|
+
lines.off('line', onLine);
|
|
1530
|
+
lines.close();
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
const getFirstPage = async browser => {
|
|
1534
|
+
const context = browser.contexts()[0];
|
|
1535
|
+
const pages = context.pages();
|
|
1536
|
+
if (pages.length > 0) {
|
|
1537
|
+
return pages[0];
|
|
1538
|
+
}
|
|
1539
|
+
return context.waitForEvent('page', {
|
|
1540
|
+
timeout: 15_000
|
|
1541
|
+
});
|
|
1542
|
+
};
|
|
1543
|
+
const closeElectron = async ({
|
|
1544
|
+
browser,
|
|
1545
|
+
child
|
|
1546
|
+
}) => {
|
|
1547
|
+
try {
|
|
1548
|
+
await browser.close();
|
|
1549
|
+
} catch {
|
|
1550
|
+
// ignore close errors during cleanup
|
|
1551
|
+
}
|
|
1552
|
+
if (!child.killed) {
|
|
1553
|
+
child.kill(SIGINT);
|
|
1554
|
+
}
|
|
1555
|
+
};
|
|
1556
|
+
const createElectronLaunch = ({
|
|
1557
|
+
browser,
|
|
1558
|
+
child,
|
|
1559
|
+
page,
|
|
1560
|
+
signal
|
|
1561
|
+
}) => {
|
|
1562
|
+
let disposed = false;
|
|
1563
|
+
const dispose = async () => {
|
|
1564
|
+
if (disposed) {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
disposed = true;
|
|
1568
|
+
signal.removeEventListener('abort', handleAbort);
|
|
1569
|
+
process.off('SIGINT', handleSigint);
|
|
1570
|
+
process.off('SIGTERM', handleSigterm);
|
|
1571
|
+
await closeElectron({
|
|
1572
|
+
browser,
|
|
1573
|
+
child
|
|
1574
|
+
});
|
|
1575
|
+
};
|
|
1576
|
+
const handleAbort = () => {
|
|
1577
|
+
void dispose();
|
|
1578
|
+
};
|
|
1579
|
+
const handleProcessSignal = processSignal => {
|
|
1580
|
+
void (async () => {
|
|
1581
|
+
try {
|
|
1582
|
+
await dispose();
|
|
1583
|
+
} finally {
|
|
1584
|
+
process.kill(process.pid, processSignal);
|
|
1585
|
+
}
|
|
1586
|
+
})();
|
|
1587
|
+
};
|
|
1588
|
+
const handleSigint = () => {
|
|
1589
|
+
handleProcessSignal('SIGINT');
|
|
1590
|
+
};
|
|
1591
|
+
const handleSigterm = () => {
|
|
1592
|
+
handleProcessSignal('SIGTERM');
|
|
1593
|
+
};
|
|
1594
|
+
signal.addEventListener('abort', handleAbort);
|
|
1595
|
+
process.once('SIGINT', handleSigint);
|
|
1596
|
+
process.once('SIGTERM', handleSigterm);
|
|
1597
|
+
const electronApp = {
|
|
1598
|
+
close: dispose,
|
|
1599
|
+
process: () => {
|
|
1600
|
+
return child;
|
|
1601
|
+
},
|
|
1602
|
+
[Symbol.asyncDispose]: dispose
|
|
1603
|
+
};
|
|
1604
|
+
return {
|
|
1605
|
+
electronApp,
|
|
1606
|
+
page,
|
|
1607
|
+
[Symbol.asyncDispose]: dispose
|
|
1608
|
+
};
|
|
1609
|
+
};
|
|
1610
|
+
const startElectron = async ({
|
|
1611
|
+
runtimeOptions,
|
|
1612
|
+
signal
|
|
1613
|
+
}) => {
|
|
1614
|
+
const launchOptions = getElectronLaunchOptions(runtimeOptions);
|
|
1615
|
+
const child = spawn(launchOptions.executablePath, ['--remote-debugging-port=0', ...launchOptions.args], {
|
|
1616
|
+
env: launchOptions.env,
|
|
1617
|
+
stdio: ['ignore', 'ignore', 'pipe']
|
|
1618
|
+
});
|
|
1619
|
+
let browser;
|
|
1620
|
+
try {
|
|
1621
|
+
const endpoint = await waitForDevtoolsEndpoint(child);
|
|
1622
|
+
browser = await chromium.connectOverCDP(endpoint);
|
|
1623
|
+
const page = await getFirstPage(browser);
|
|
1624
|
+
return createElectronLaunch({
|
|
1625
|
+
browser,
|
|
1626
|
+
child,
|
|
1627
|
+
page,
|
|
1628
|
+
signal
|
|
1629
|
+
});
|
|
1630
|
+
} catch (error) {
|
|
1631
|
+
if (browser) {
|
|
1632
|
+
try {
|
|
1633
|
+
await browser.close();
|
|
1634
|
+
} catch {
|
|
1635
|
+
// ignore close errors during cleanup
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (!child.killed) {
|
|
1639
|
+
child.kill(SIGINT);
|
|
1640
|
+
}
|
|
1641
|
+
throw error;
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
|
|
1330
1645
|
const tearDownTests = async ({
|
|
1331
1646
|
child,
|
|
1332
|
-
controller
|
|
1647
|
+
controller,
|
|
1648
|
+
kill
|
|
1333
1649
|
}) => {
|
|
1334
1650
|
controller.abort();
|
|
1335
|
-
|
|
1651
|
+
if (kill) {
|
|
1652
|
+
await kill();
|
|
1653
|
+
return;
|
|
1654
|
+
}
|
|
1655
|
+
if (child) {
|
|
1656
|
+
child.kill(SIGINT);
|
|
1657
|
+
}
|
|
1336
1658
|
};
|
|
1337
1659
|
|
|
1338
1660
|
/**
|
|
@@ -1346,31 +1668,19 @@ const tearDownTests = async ({
|
|
|
1346
1668
|
* @param {boolean} traceFocus
|
|
1347
1669
|
* @param {string} filter
|
|
1348
1670
|
*/
|
|
1349
|
-
const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout,
|
|
1671
|
+
const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter) => {
|
|
1350
1672
|
string(extensionPath);
|
|
1351
1673
|
string(testPath);
|
|
1352
1674
|
string(cwd);
|
|
1353
1675
|
string(browser);
|
|
1354
1676
|
boolean(headless);
|
|
1355
1677
|
number(timeout);
|
|
1678
|
+
object(runtimeOptions);
|
|
1356
1679
|
const rpc = get(Cli);
|
|
1357
1680
|
const controller = new AbortController();
|
|
1358
1681
|
const {
|
|
1359
1682
|
signal
|
|
1360
1683
|
} = controller;
|
|
1361
|
-
// @ts-ignore
|
|
1362
|
-
const {
|
|
1363
|
-
child,
|
|
1364
|
-
page,
|
|
1365
|
-
port
|
|
1366
|
-
} = await setupTests({
|
|
1367
|
-
browser,
|
|
1368
|
-
headless,
|
|
1369
|
-
onlyExtension: extensionPath,
|
|
1370
|
-
serverPath,
|
|
1371
|
-
signal,
|
|
1372
|
-
testPath
|
|
1373
|
-
});
|
|
1374
1684
|
const testSrc = join(testPath, 'src');
|
|
1375
1685
|
const tests = await getTests(testSrc);
|
|
1376
1686
|
const onResult = async result => {
|
|
@@ -1382,6 +1692,37 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
|
|
|
1382
1692
|
const filterOption = filter === undefined ? undefined : {
|
|
1383
1693
|
filter
|
|
1384
1694
|
};
|
|
1695
|
+
if (runtimeOptions.type === 'electron') {
|
|
1696
|
+
await using electron = await startElectron({
|
|
1697
|
+
runtimeOptions,
|
|
1698
|
+
signal
|
|
1699
|
+
});
|
|
1700
|
+
await runElectronTests({
|
|
1701
|
+
...filterOption,
|
|
1702
|
+
electronApp: electron.electronApp,
|
|
1703
|
+
onFinalResult,
|
|
1704
|
+
onResult,
|
|
1705
|
+
page: electron.page,
|
|
1706
|
+
tests,
|
|
1707
|
+
testSrc,
|
|
1708
|
+
timeout
|
|
1709
|
+
});
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
const {
|
|
1713
|
+
child,
|
|
1714
|
+
page,
|
|
1715
|
+
port
|
|
1716
|
+
} = await setupTests({
|
|
1717
|
+
browser,
|
|
1718
|
+
headless,
|
|
1719
|
+
onlyExtension: extensionPath,
|
|
1720
|
+
...(runtimeOptions.serverPath && {
|
|
1721
|
+
serverPath: runtimeOptions.serverPath
|
|
1722
|
+
}),
|
|
1723
|
+
signal,
|
|
1724
|
+
testPath
|
|
1725
|
+
});
|
|
1385
1726
|
await runTests({
|
|
1386
1727
|
...filterOption,
|
|
1387
1728
|
headless,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/test-with-playwright-worker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "20.0.0",
|
|
4
4
|
"description": "Worker package for test-with-playwright",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -12,7 +12,8 @@
|
|
|
12
12
|
"type": "module",
|
|
13
13
|
"main": "dist/workerMain.js",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@playwright/test": "1.61.0"
|
|
15
|
+
"@playwright/test": "1.61.0",
|
|
16
|
+
"get-port": "^7.2.0"
|
|
16
17
|
},
|
|
17
18
|
"engines": {
|
|
18
19
|
"node": ">=24"
|