@lvce-editor/test-with-playwright-worker 18.1.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.
- package/dist/workerMain.js +377 -35
- package/package.json +1 -1
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,
|
|
@@ -1325,14 +1468,194 @@ const setupTests = async ({
|
|
|
1325
1468
|
};
|
|
1326
1469
|
};
|
|
1327
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
|
+
|
|
1328
1497
|
const SIGINT = 'SIGINT';
|
|
1329
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
|
+
|
|
1330
1646
|
const tearDownTests = async ({
|
|
1331
1647
|
child,
|
|
1332
|
-
controller
|
|
1648
|
+
controller,
|
|
1649
|
+
kill
|
|
1333
1650
|
}) => {
|
|
1334
1651
|
controller.abort();
|
|
1335
|
-
|
|
1652
|
+
if (kill) {
|
|
1653
|
+
await kill();
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
if (child) {
|
|
1657
|
+
child.kill(SIGINT);
|
|
1658
|
+
}
|
|
1336
1659
|
};
|
|
1337
1660
|
|
|
1338
1661
|
/**
|
|
@@ -1346,31 +1669,19 @@ const tearDownTests = async ({
|
|
|
1346
1669
|
* @param {boolean} traceFocus
|
|
1347
1670
|
* @param {string} filter
|
|
1348
1671
|
*/
|
|
1349
|
-
const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout,
|
|
1672
|
+
const runAllTests = async (extensionPath, testPath, cwd, browser, headless, timeout, runtimeOptions, traceFocus, filter) => {
|
|
1350
1673
|
string(extensionPath);
|
|
1351
1674
|
string(testPath);
|
|
1352
1675
|
string(cwd);
|
|
1353
1676
|
string(browser);
|
|
1354
1677
|
boolean(headless);
|
|
1355
1678
|
number(timeout);
|
|
1679
|
+
object(runtimeOptions);
|
|
1356
1680
|
const rpc = get(Cli);
|
|
1357
1681
|
const controller = new AbortController();
|
|
1358
1682
|
const {
|
|
1359
1683
|
signal
|
|
1360
1684
|
} = 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
1685
|
const testSrc = join(testPath, 'src');
|
|
1375
1686
|
const tests = await getTests(testSrc);
|
|
1376
1687
|
const onResult = async result => {
|
|
@@ -1382,6 +1693,37 @@ const runAllTests = async (extensionPath, testPath, cwd, browser, headless, time
|
|
|
1382
1693
|
const filterOption = filter === undefined ? undefined : {
|
|
1383
1694
|
filter
|
|
1384
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
|
+
}
|
|
1713
|
+
const {
|
|
1714
|
+
child,
|
|
1715
|
+
page,
|
|
1716
|
+
port
|
|
1717
|
+
} = await setupTests({
|
|
1718
|
+
browser,
|
|
1719
|
+
headless,
|
|
1720
|
+
onlyExtension: extensionPath,
|
|
1721
|
+
...(runtimeOptions.serverPath && {
|
|
1722
|
+
serverPath: runtimeOptions.serverPath
|
|
1723
|
+
}),
|
|
1724
|
+
signal,
|
|
1725
|
+
testPath
|
|
1726
|
+
});
|
|
1385
1727
|
await runTests({
|
|
1386
1728
|
...filterOption,
|
|
1387
1729
|
headless,
|