@rstest/browser 0.11.1 → 0.11.3
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/browser-container/container-static/js/{328.37af068e21.js → 949.7d935e2ac9.js} +1638 -1612
- package/dist/browser-container/container-static/js/949.7d935e2ac9.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.c8fe4ff310.js → index.492a6c2f8b.js} +11 -11
- package/dist/browser-container/index.html +1 -1
- package/dist/browserExecutor.d.ts +11 -0
- package/dist/configValidation.d.ts +6 -1
- package/dist/hostController.d.ts +2 -6
- package/dist/index.d.ts +4 -3
- package/dist/index.js +570 -301
- package/dist/protocol.d.ts +2 -2
- package/package.json +7 -7
- package/src/AGENTS.md +17 -1
- package/src/browserExecutor.ts +138 -0
- package/src/client/AGENTS.md +20 -13
- package/src/client/entry.ts +83 -15
- package/src/client/snapshot.ts +1 -2
- package/src/concurrency.ts +42 -25
- package/src/configValidation.ts +182 -1
- package/src/hostController.ts +546 -350
- package/src/index.ts +9 -5
- package/src/protocol.ts +2 -2
- package/dist/browser-container/container-static/js/328.37af068e21.js.LICENSE.txt +0 -1
- package/src/client/formatConsole.ts +0 -77
- package/src/client/public.ts +0 -12
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { DEFAULT_TEST_TIMEOUT, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, applyWebMockRspackConfig, browserIgnoredRuntimeConfigKeys, buildBrowserCoverageMap, color, createCoverageProvider, createRunnerEventSink, createSilentConsoleController, getNoTestFilesMessage, getNumCpus as browser_getNumCpus, getPrettyConsoleName, getSetupFiles, getTestEntries, hasUserRstestConfigPlugins, importMetaRstestDefine, initModifyRstestConfigHooks, isDebug, isTTY, loadCoverageProvider, logger, pluginMockRuntime, prepareWatchRerunState, projectRuntimeConfig, resolveProjectBuildCache, resolveShardedEntries, resolveSnapshotPathDefault, resolveWorkerCount, rsbuild, serializableConfig } from "@rstest/core/internal/browser";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
3
|
import promises from "node:fs/promises";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { isDeepStrictEqual } from "node:util";
|
|
5
|
-
import { DEFAULT_TEST_TIMEOUT, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, color, createCoverageProvider, getNoTestFilesMessage, getNumCpus, getSetupFiles, getTestEntries, isDebug, isTTY, loadCoverageProvider, logger, parseWorkers, resolveProjectBuildCache, rsbuild, serializableConfig } from "@rstest/core/internal/browser";
|
|
6
6
|
import open_editor from "open-editor";
|
|
7
|
-
import {
|
|
7
|
+
import { dirname, join, normalize, relative, resolve as external_pathe_resolve } from "pathe";
|
|
8
8
|
import sirv from "sirv";
|
|
9
9
|
import { WebSocketServer } from "ws";
|
|
10
10
|
import convert_source_map from "convert-source-map";
|
|
@@ -1654,113 +1654,6 @@ __webpack_require__.add({
|
|
|
1654
1654
|
};
|
|
1655
1655
|
}
|
|
1656
1656
|
});
|
|
1657
|
-
const BROWSER_VIEWPORT_PRESET_DIMENSIONS = {
|
|
1658
|
-
iPhoneSE: {
|
|
1659
|
-
width: 375,
|
|
1660
|
-
height: 667
|
|
1661
|
-
},
|
|
1662
|
-
iPhoneXR: {
|
|
1663
|
-
width: 414,
|
|
1664
|
-
height: 896
|
|
1665
|
-
},
|
|
1666
|
-
iPhone12Pro: {
|
|
1667
|
-
width: 390,
|
|
1668
|
-
height: 844
|
|
1669
|
-
},
|
|
1670
|
-
iPhone14ProMax: {
|
|
1671
|
-
width: 430,
|
|
1672
|
-
height: 932
|
|
1673
|
-
},
|
|
1674
|
-
Pixel7: {
|
|
1675
|
-
width: 412,
|
|
1676
|
-
height: 915
|
|
1677
|
-
},
|
|
1678
|
-
SamsungGalaxyS8Plus: {
|
|
1679
|
-
width: 360,
|
|
1680
|
-
height: 740
|
|
1681
|
-
},
|
|
1682
|
-
SamsungGalaxyS20Ultra: {
|
|
1683
|
-
width: 412,
|
|
1684
|
-
height: 915
|
|
1685
|
-
},
|
|
1686
|
-
iPadMini: {
|
|
1687
|
-
width: 768,
|
|
1688
|
-
height: 1024
|
|
1689
|
-
},
|
|
1690
|
-
iPadAir: {
|
|
1691
|
-
width: 820,
|
|
1692
|
-
height: 1180
|
|
1693
|
-
},
|
|
1694
|
-
iPadPro: {
|
|
1695
|
-
width: 1024,
|
|
1696
|
-
height: 1366
|
|
1697
|
-
},
|
|
1698
|
-
SurfacePro7: {
|
|
1699
|
-
width: 912,
|
|
1700
|
-
height: 1368
|
|
1701
|
-
},
|
|
1702
|
-
SurfaceDuo: {
|
|
1703
|
-
width: 540,
|
|
1704
|
-
height: 720
|
|
1705
|
-
},
|
|
1706
|
-
GalaxyZFold5: {
|
|
1707
|
-
width: 344,
|
|
1708
|
-
height: 882
|
|
1709
|
-
},
|
|
1710
|
-
AsusZenbookFold: {
|
|
1711
|
-
width: 853,
|
|
1712
|
-
height: 1280
|
|
1713
|
-
},
|
|
1714
|
-
SamsungGalaxyA51A71: {
|
|
1715
|
-
width: 412,
|
|
1716
|
-
height: 914
|
|
1717
|
-
},
|
|
1718
|
-
NestHub: {
|
|
1719
|
-
width: 1024,
|
|
1720
|
-
height: 600
|
|
1721
|
-
},
|
|
1722
|
-
NestHubMax: {
|
|
1723
|
-
width: 1280,
|
|
1724
|
-
height: 800
|
|
1725
|
-
}
|
|
1726
|
-
};
|
|
1727
|
-
const resolveBrowserViewportPreset = (presetId)=>{
|
|
1728
|
-
const size = BROWSER_VIEWPORT_PRESET_DIMENSIONS[presetId];
|
|
1729
|
-
return size ?? null;
|
|
1730
|
-
};
|
|
1731
|
-
const SUPPORTED_PROVIDERS = [
|
|
1732
|
-
'playwright'
|
|
1733
|
-
];
|
|
1734
|
-
const isPlainObject = (value)=>'[object Object]' === Object.prototype.toString.call(value);
|
|
1735
|
-
const validateViewport = (viewport)=>{
|
|
1736
|
-
if (null == viewport) return;
|
|
1737
|
-
if ('string' == typeof viewport) {
|
|
1738
|
-
const presetId = viewport.trim();
|
|
1739
|
-
if (!presetId) throw new Error('browser.viewport must be a non-empty preset id.');
|
|
1740
|
-
if (!resolveBrowserViewportPreset(presetId)) throw new Error(`browser.viewport must be a valid preset id. Received: ${viewport}`);
|
|
1741
|
-
return;
|
|
1742
|
-
}
|
|
1743
|
-
if (isPlainObject(viewport)) {
|
|
1744
|
-
const width = viewport.width;
|
|
1745
|
-
const height = viewport.height;
|
|
1746
|
-
if (!Number.isFinite(width) || width <= 0) throw new Error('browser.viewport.width must be a positive number.');
|
|
1747
|
-
if (!Number.isFinite(height) || height <= 0) throw new Error('browser.viewport.height must be a positive number.');
|
|
1748
|
-
return;
|
|
1749
|
-
}
|
|
1750
|
-
throw new Error('browser.viewport must be either a preset id or { width, height }.');
|
|
1751
|
-
};
|
|
1752
|
-
const validateBrowserConfig = (context)=>{
|
|
1753
|
-
for (const project of context.projects){
|
|
1754
|
-
const { browser, output } = project.normalizedConfig;
|
|
1755
|
-
if (browser.enabled) {
|
|
1756
|
-
if (!browser.provider) throw new Error('browser.provider is required when browser.enabled is true.');
|
|
1757
|
-
if (!SUPPORTED_PROVIDERS.includes(browser.provider)) throw new Error(`browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`);
|
|
1758
|
-
validateViewport(browser.viewport);
|
|
1759
|
-
if (!isPlainObject(browser.providerOptions)) throw new Error('browser.providerOptions must be a plain object.');
|
|
1760
|
-
if (output?.bundleDependencies === false) throw new Error('output.bundleDependencies false is not supported in browser mode.');
|
|
1761
|
-
}
|
|
1762
|
-
}
|
|
1763
|
-
};
|
|
1764
1657
|
const TYPE_REQUEST = "q";
|
|
1765
1658
|
const TYPE_RESPONSE = "s";
|
|
1766
1659
|
function createPromiseWithResolvers() {
|
|
@@ -1957,16 +1850,22 @@ function createBirpc($functions, options) {
|
|
|
1957
1850
|
return rpc;
|
|
1958
1851
|
}
|
|
1959
1852
|
const DEFAULT_MAX_HEADLESS_WORKERS = 12;
|
|
1960
|
-
const
|
|
1961
|
-
const
|
|
1962
|
-
return
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1853
|
+
const resolveHeadlessWorkerCount = ({ command, maxWorkers, totalTasks, numCpus = browser_getNumCpus() })=>{
|
|
1854
|
+
const base = Math.max(Math.min(DEFAULT_MAX_HEADLESS_WORKERS, numCpus - 1), 1);
|
|
1855
|
+
return resolveWorkerCount({
|
|
1856
|
+
command,
|
|
1857
|
+
maxWorkers,
|
|
1858
|
+
totalTasks,
|
|
1859
|
+
recommended: base,
|
|
1860
|
+
watchRecommended: Math.max(Math.floor(base / 2), 1),
|
|
1861
|
+
numCpus
|
|
1862
|
+
});
|
|
1969
1863
|
};
|
|
1864
|
+
const getHeadlessConcurrency = (context, totalTests)=>resolveHeadlessWorkerCount({
|
|
1865
|
+
command: context.command,
|
|
1866
|
+
maxWorkers: context.normalizedConfig.pool.maxWorkers,
|
|
1867
|
+
totalTasks: totalTests
|
|
1868
|
+
});
|
|
1970
1869
|
const toErrorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
1971
1870
|
class HostDispatchRouter {
|
|
1972
1871
|
handlers = new Map();
|
|
@@ -2071,6 +1970,172 @@ const createHostDispatchRouter = ({ routerOptions, runnerCallbacks, runSnapshotR
|
|
|
2071
1970
|
}
|
|
2072
1971
|
return router;
|
|
2073
1972
|
};
|
|
1973
|
+
const BROWSER_VIEWPORT_PRESET_DIMENSIONS = {
|
|
1974
|
+
iPhoneSE: {
|
|
1975
|
+
width: 375,
|
|
1976
|
+
height: 667
|
|
1977
|
+
},
|
|
1978
|
+
iPhoneXR: {
|
|
1979
|
+
width: 414,
|
|
1980
|
+
height: 896
|
|
1981
|
+
},
|
|
1982
|
+
iPhone12Pro: {
|
|
1983
|
+
width: 390,
|
|
1984
|
+
height: 844
|
|
1985
|
+
},
|
|
1986
|
+
iPhone14ProMax: {
|
|
1987
|
+
width: 430,
|
|
1988
|
+
height: 932
|
|
1989
|
+
},
|
|
1990
|
+
Pixel7: {
|
|
1991
|
+
width: 412,
|
|
1992
|
+
height: 915
|
|
1993
|
+
},
|
|
1994
|
+
SamsungGalaxyS8Plus: {
|
|
1995
|
+
width: 360,
|
|
1996
|
+
height: 740
|
|
1997
|
+
},
|
|
1998
|
+
SamsungGalaxyS20Ultra: {
|
|
1999
|
+
width: 412,
|
|
2000
|
+
height: 915
|
|
2001
|
+
},
|
|
2002
|
+
iPadMini: {
|
|
2003
|
+
width: 768,
|
|
2004
|
+
height: 1024
|
|
2005
|
+
},
|
|
2006
|
+
iPadAir: {
|
|
2007
|
+
width: 820,
|
|
2008
|
+
height: 1180
|
|
2009
|
+
},
|
|
2010
|
+
iPadPro: {
|
|
2011
|
+
width: 1024,
|
|
2012
|
+
height: 1366
|
|
2013
|
+
},
|
|
2014
|
+
SurfacePro7: {
|
|
2015
|
+
width: 912,
|
|
2016
|
+
height: 1368
|
|
2017
|
+
},
|
|
2018
|
+
SurfaceDuo: {
|
|
2019
|
+
width: 540,
|
|
2020
|
+
height: 720
|
|
2021
|
+
},
|
|
2022
|
+
GalaxyZFold5: {
|
|
2023
|
+
width: 344,
|
|
2024
|
+
height: 882
|
|
2025
|
+
},
|
|
2026
|
+
AsusZenbookFold: {
|
|
2027
|
+
width: 853,
|
|
2028
|
+
height: 1280
|
|
2029
|
+
},
|
|
2030
|
+
SamsungGalaxyA51A71: {
|
|
2031
|
+
width: 412,
|
|
2032
|
+
height: 914
|
|
2033
|
+
},
|
|
2034
|
+
NestHub: {
|
|
2035
|
+
width: 1024,
|
|
2036
|
+
height: 600
|
|
2037
|
+
},
|
|
2038
|
+
NestHubMax: {
|
|
2039
|
+
width: 1280,
|
|
2040
|
+
height: 800
|
|
2041
|
+
}
|
|
2042
|
+
};
|
|
2043
|
+
const resolveBrowserViewportPreset = (presetId)=>{
|
|
2044
|
+
const size = BROWSER_VIEWPORT_PRESET_DIMENSIONS[presetId];
|
|
2045
|
+
return size ?? null;
|
|
2046
|
+
};
|
|
2047
|
+
const ignoredKeyWarnings = {
|
|
2048
|
+
testEnvironment: {
|
|
2049
|
+
isNonDefault: (config)=>'node' !== config.testEnvironment.name,
|
|
2050
|
+
message: (config)=>`Ignoring testEnvironment '${config.testEnvironment.name}' in browser mode: the browser itself is the test environment.`
|
|
2051
|
+
},
|
|
2052
|
+
isolate: {
|
|
2053
|
+
isNonDefault: (config)=>false === config.isolate,
|
|
2054
|
+
message: ()=>"Ignoring isolate: false in browser mode: each test file still runs in a fresh context.",
|
|
2055
|
+
browserOnly: true
|
|
2056
|
+
},
|
|
2057
|
+
detectAsyncLeaks: {
|
|
2058
|
+
isNonDefault: (config)=>true === config.detectAsyncLeaks,
|
|
2059
|
+
message: ()=>"Ignoring detectAsyncLeaks in browser mode: it relies on node async_hooks."
|
|
2060
|
+
},
|
|
2061
|
+
logHeapUsage: {
|
|
2062
|
+
isNonDefault: (config)=>true === config.logHeapUsage,
|
|
2063
|
+
message: ()=>'Ignoring logHeapUsage in browser mode.'
|
|
2064
|
+
}
|
|
2065
|
+
};
|
|
2066
|
+
const speciallyHandledIgnoredKeys = [
|
|
2067
|
+
'coverage'
|
|
2068
|
+
];
|
|
2069
|
+
const browserValidatedIgnoredKeys = [
|
|
2070
|
+
...Object.keys(ignoredKeyWarnings),
|
|
2071
|
+
...speciallyHandledIgnoredKeys
|
|
2072
|
+
];
|
|
2073
|
+
const assertIgnoredKeysCovered = ()=>{
|
|
2074
|
+
const covered = new Set(browserValidatedIgnoredKeys);
|
|
2075
|
+
const uncovered = browserIgnoredRuntimeConfigKeys.filter((key)=>!covered.has(key));
|
|
2076
|
+
if (uncovered.length > 0) throw new Error(`Browser config validation is out of sync with executorCapabilities: no check for ignored RuntimeConfig field(s): ${uncovered.join(', ')}. Add a descriptor to \`ignoredKeyWarnings\` or \`speciallyHandledIgnoredKeys\`.`);
|
|
2077
|
+
};
|
|
2078
|
+
assertIgnoredKeysCovered();
|
|
2079
|
+
const SUPPORTED_PROVIDERS = [
|
|
2080
|
+
'playwright'
|
|
2081
|
+
];
|
|
2082
|
+
const isPlainObject = (value)=>'[object Object]' === Object.prototype.toString.call(value);
|
|
2083
|
+
const validateViewport = (viewport)=>{
|
|
2084
|
+
if (null == viewport) return;
|
|
2085
|
+
if ('string' == typeof viewport) {
|
|
2086
|
+
const presetId = viewport.trim();
|
|
2087
|
+
if (!presetId) throw new Error('browser.viewport must be a non-empty preset id.');
|
|
2088
|
+
if (!resolveBrowserViewportPreset(presetId)) throw new Error(`browser.viewport must be a valid preset id. Received: ${viewport}`);
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
if (isPlainObject(viewport)) {
|
|
2092
|
+
const width = viewport.width;
|
|
2093
|
+
const height = viewport.height;
|
|
2094
|
+
if (!Number.isFinite(width) || width <= 0) throw new Error('browser.viewport.width must be a positive number.');
|
|
2095
|
+
if (!Number.isFinite(height) || height <= 0) throw new Error('browser.viewport.height must be a positive number.');
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
throw new Error('browser.viewport must be either a preset id or { width, height }.');
|
|
2099
|
+
};
|
|
2100
|
+
const reportUnsupportedBrowserOptions = (context)=>{
|
|
2101
|
+
const browserProjects = context.projects.filter((project)=>project.normalizedConfig.browser.enabled);
|
|
2102
|
+
if (0 === browserProjects.length) return;
|
|
2103
|
+
const isBrowserOnlyRun = browserProjects.length === context.projects.length;
|
|
2104
|
+
const globalConfig = context.normalizedConfig;
|
|
2105
|
+
const { coverage } = globalConfig;
|
|
2106
|
+
if ('list' !== context.command && coverage.enabled && 'v8' === coverage.provider) {
|
|
2107
|
+
if (isBrowserOnlyRun) throw new Error("Coverage provider 'v8' is not supported in browser mode: browser projects produce no v8 coverage. Use the default istanbul provider (coverage.provider: 'istanbul') for browser coverage.");
|
|
2108
|
+
logger.warn(color.yellow("Coverage provider 'v8' produces no coverage for browser project files; use the 'istanbul' provider to collect browser coverage. Node projects will still use v8."));
|
|
2109
|
+
}
|
|
2110
|
+
const warnings = new Set();
|
|
2111
|
+
if ('forks' !== globalConfig.pool.type) warnings.add(`Ignoring pool.type '${globalConfig.pool.type}' in browser mode.`);
|
|
2112
|
+
if (globalConfig.pool.execArgv && globalConfig.pool.execArgv.length > 0) warnings.add('Ignoring pool.execArgv in browser mode.');
|
|
2113
|
+
for (const project of browserProjects){
|
|
2114
|
+
const config = project.normalizedConfig;
|
|
2115
|
+
for (const key of browserIgnoredRuntimeConfigKeys){
|
|
2116
|
+
const descriptor = ignoredKeyWarnings[key];
|
|
2117
|
+
if (descriptor) {
|
|
2118
|
+
if (!descriptor.browserOnly || isBrowserOnlyRun) {
|
|
2119
|
+
if (descriptor.isNonDefault(config)) warnings.add(descriptor.message(config));
|
|
2120
|
+
}
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
for (const message of warnings)logger.warn(color.yellow(message));
|
|
2125
|
+
};
|
|
2126
|
+
const validateBrowserConfig = (context)=>{
|
|
2127
|
+
for (const project of context.projects){
|
|
2128
|
+
const { browser, output } = project.normalizedConfig;
|
|
2129
|
+
if (browser.enabled) {
|
|
2130
|
+
if (!browser.provider) throw new Error('browser.provider is required when browser.enabled is true.');
|
|
2131
|
+
if (!SUPPORTED_PROVIDERS.includes(browser.provider)) throw new Error(`browser.provider must be one of: ${SUPPORTED_PROVIDERS.join(', ')}.`);
|
|
2132
|
+
validateViewport(browser.viewport);
|
|
2133
|
+
if (!isPlainObject(browser.providerOptions)) throw new Error('browser.providerOptions must be a plain object.');
|
|
2134
|
+
if (output?.bundleDependencies === false) throw new Error('output.bundleDependencies false is not supported in browser mode.');
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
reportUnsupportedBrowserOptions(context);
|
|
2138
|
+
};
|
|
2074
2139
|
const createHeadedSerialTaskQueue = ()=>{
|
|
2075
2140
|
let queue = Promise.resolve();
|
|
2076
2141
|
const enqueue = (task)=>{
|
|
@@ -2846,7 +2911,6 @@ const getBrowserProviderOptions = (project)=>{
|
|
|
2846
2911
|
return browserConfig.providerOptions ?? {};
|
|
2847
2912
|
};
|
|
2848
2913
|
const getFileTaskId = (testPath)=>`file:${testPath}`;
|
|
2849
|
-
const getBufferedLogTaskId = (log)=>log.taskId ?? getFileTaskId(log.testPath);
|
|
2850
2914
|
const createDeferredPromise = ()=>{
|
|
2851
2915
|
let resolve;
|
|
2852
2916
|
let reject;
|
|
@@ -3158,41 +3222,12 @@ const getAffectedTestFiles = (chunks, entryTestFiles)=>{
|
|
|
3158
3222
|
watchContext.chunkHashes = currentHashes;
|
|
3159
3223
|
return Array.from(affectedFiles);
|
|
3160
3224
|
};
|
|
3161
|
-
const getRuntimeConfigFromProject = (project)=>{
|
|
3162
|
-
const { testNamePattern, testTimeout, passWithNoTests, retry, globals, clearMocks, resetMocks, restoreMocks, unstubEnvs, unstubGlobals, maxConcurrency, printConsoleTrace, disableConsoleIntercept, testEnvironment, hookTimeout, isolate, coverage, snapshotFormat, env, bail, logHeapUsage, detectAsyncLeaks, chaiConfig, includeTaskLocation, silent } = project.normalizedConfig;
|
|
3163
|
-
return {
|
|
3164
|
-
env: {
|
|
3165
|
-
NODE_ENV: process.env.NODE_ENV,
|
|
3166
|
-
RSTEST: 'true',
|
|
3167
|
-
...env
|
|
3168
|
-
},
|
|
3169
|
-
testNamePattern,
|
|
3170
|
-
testTimeout,
|
|
3171
|
-
hookTimeout,
|
|
3172
|
-
passWithNoTests,
|
|
3173
|
-
retry,
|
|
3174
|
-
globals,
|
|
3175
|
-
clearMocks,
|
|
3176
|
-
resetMocks,
|
|
3177
|
-
restoreMocks,
|
|
3178
|
-
unstubEnvs,
|
|
3179
|
-
unstubGlobals,
|
|
3180
|
-
maxConcurrency,
|
|
3181
|
-
printConsoleTrace,
|
|
3182
|
-
disableConsoleIntercept,
|
|
3183
|
-
testEnvironment,
|
|
3184
|
-
isolate,
|
|
3185
|
-
coverage,
|
|
3186
|
-
snapshotFormat,
|
|
3187
|
-
bail,
|
|
3188
|
-
logHeapUsage,
|
|
3189
|
-
detectAsyncLeaks,
|
|
3190
|
-
chaiConfig,
|
|
3191
|
-
includeTaskLocation,
|
|
3192
|
-
silent
|
|
3193
|
-
};
|
|
3194
|
-
};
|
|
3195
3225
|
const getBrowserProjects = (context)=>context.projects.filter((project)=>project.normalizedConfig.browser.enabled);
|
|
3226
|
+
const getBrowserRsbuildEnvironmentConfig = (project)=>({
|
|
3227
|
+
plugins: project.normalizedConfig.plugins,
|
|
3228
|
+
root: project.rootPath
|
|
3229
|
+
});
|
|
3230
|
+
const getMaxTestTimeoutForRpc = (projects)=>Math.max(...projects.map((p)=>p.normalizedConfig.testTimeout ?? DEFAULT_TEST_TIMEOUT));
|
|
3196
3231
|
const getBrowserLaunchOptions = (project)=>({
|
|
3197
3232
|
provider: project.normalizedConfig.browser.provider,
|
|
3198
3233
|
browser: project.normalizedConfig.browser.browser,
|
|
@@ -3219,9 +3254,7 @@ const resolveProviderForTestPath = ({ testPath, browserProjects })=>{
|
|
|
3219
3254
|
for (const project of sortedProjects)if (normalizedTestPath.startsWith(project.rootPath)) return project.provider;
|
|
3220
3255
|
throw new Error(`Cannot resolve browser provider for test path: ${JSON.stringify(testPath)}. Known project roots: ${JSON.stringify(sortedProjects.map((p)=>p.rootPath))}`);
|
|
3221
3256
|
};
|
|
3222
|
-
const collectProjectEntries = async (context)=>{
|
|
3223
|
-
const browserProjects = getBrowserProjects(context);
|
|
3224
|
-
return Promise.all(browserProjects.map(async (project)=>{
|
|
3257
|
+
const collectProjectEntries = async (context, browserProjects = getBrowserProjects(context))=>Promise.all(browserProjects.map(async (project)=>{
|
|
3225
3258
|
const { normalizedConfig: { include, exclude, includeSource, setupFiles } } = project;
|
|
3226
3259
|
const tests = await getTestEntries({
|
|
3227
3260
|
include,
|
|
@@ -3239,7 +3272,6 @@ const collectProjectEntries = async (context)=>{
|
|
|
3239
3272
|
testFiles: Object.values(tests)
|
|
3240
3273
|
};
|
|
3241
3274
|
}));
|
|
3242
|
-
};
|
|
3243
3275
|
const resolveBrowserFile = (relativePath)=>{
|
|
3244
3276
|
const candidates = [
|
|
3245
3277
|
external_pathe_resolve(hostController_dirname, '../src', relativePath),
|
|
@@ -3300,12 +3332,29 @@ const generateManifestModule = ({ manifestPath, entries, isWatchMode })=>{
|
|
|
3300
3332
|
if (isWatchMode) {
|
|
3301
3333
|
const includeRegExp = globPatternsToRegExp(project.normalizedConfig.include);
|
|
3302
3334
|
const excludeRegExp = createBrowserContextExcludeRegExp(project.normalizedConfig.exclude.patterns, projectRootPosix);
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3335
|
+
const { includeSource } = project.normalizedConfig;
|
|
3336
|
+
const emitContext = (contextVarName, regExp)=>{
|
|
3337
|
+
lines.push(`const ${contextVarName} = import.meta.webpackContext(${JSON.stringify(projectRootPosix)}, {`);
|
|
3338
|
+
lines.push(' recursive: true,');
|
|
3339
|
+
lines.push(` regExp: ${regExp.toString()},`);
|
|
3340
|
+
if (excludeRegExp) lines.push(` exclude: ${excludeRegExp.toString()},`);
|
|
3341
|
+
lines.push(" mode: 'lazy',");
|
|
3342
|
+
lines.push('});');
|
|
3343
|
+
};
|
|
3344
|
+
if (0 === includeSource.length) emitContext(varName, includeRegExp);
|
|
3345
|
+
else {
|
|
3346
|
+
emitContext(`${varName}_include`, includeRegExp);
|
|
3347
|
+
emitContext(`${varName}_source`, globPatternsToRegExp(includeSource));
|
|
3348
|
+
const probedKeys = testFiles.map((filePath)=>toContextKey(filePath, projectRootPosix));
|
|
3349
|
+
lines.push(`const ${varName}_probed = ${JSON.stringify(probedKeys)};`);
|
|
3350
|
+
lines.push(`const ${varName}_includeKeys = new Set(${varName}_include.keys());`);
|
|
3351
|
+
lines.push(`const ${varName} = Object.assign(`);
|
|
3352
|
+
lines.push(` (key) => ${varName}_includeKeys.has(key) ? ${varName}_include(key) : ${varName}_source(key),`);
|
|
3353
|
+
lines.push(' {');
|
|
3354
|
+
lines.push(` keys: () => Array.from(new Set([...${varName}_includeKeys, ...${varName}_probed])),`);
|
|
3355
|
+
lines.push(' },');
|
|
3356
|
+
lines.push(');');
|
|
3357
|
+
}
|
|
3309
3358
|
} else {
|
|
3310
3359
|
lines.push(`const ${varName}_modules = {`);
|
|
3311
3360
|
for (const filePath of testFiles){
|
|
@@ -3399,7 +3448,7 @@ const registerWatchCleanup = ()=>{
|
|
|
3399
3448
|
});
|
|
3400
3449
|
watchContext.cleanupRegistered = true;
|
|
3401
3450
|
};
|
|
3402
|
-
const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless })=>{
|
|
3451
|
+
const createBrowserRuntime = async ({ context, projectEntries: initialProjectEntries, browserProjects, shardedEntries, freezeShardedEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless, skipProviderLaunch, appliedModifyRstestConfigEnvironments })=>{
|
|
3403
3452
|
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
3404
3453
|
let injectedContainerHtml = null;
|
|
3405
3454
|
let serializedOptions = 'null';
|
|
@@ -3408,11 +3457,59 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3408
3457
|
serializedOptions = serializeForInlineScript(options);
|
|
3409
3458
|
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
3410
3459
|
};
|
|
3411
|
-
|
|
3412
|
-
|
|
3460
|
+
let browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3461
|
+
let projectEntries = initialProjectEntries;
|
|
3462
|
+
const manifestModules = [];
|
|
3463
|
+
const createRuntimeWithoutProvider = ()=>{
|
|
3464
|
+
const firstProject = browserProjects[0];
|
|
3465
|
+
return {
|
|
3466
|
+
projectServers: new Map(),
|
|
3467
|
+
containerServer: {
|
|
3468
|
+
projectName: firstProject.name,
|
|
3469
|
+
environmentName: firstProject.environmentName,
|
|
3470
|
+
rsbuildInstance: void 0,
|
|
3471
|
+
devServer: {
|
|
3472
|
+
close: async ()=>void 0
|
|
3473
|
+
},
|
|
3474
|
+
port: 0,
|
|
3475
|
+
manifestPath: ''
|
|
3476
|
+
},
|
|
3477
|
+
browser: void 0,
|
|
3478
|
+
browserLaunchOptions,
|
|
3479
|
+
wsPort: 0,
|
|
3480
|
+
tempDir,
|
|
3481
|
+
setContainerOptions,
|
|
3482
|
+
dispatchHandlers,
|
|
3483
|
+
wss: void 0,
|
|
3484
|
+
projectEntries
|
|
3485
|
+
};
|
|
3486
|
+
};
|
|
3487
|
+
const getProjectEntry = (project)=>projectEntries.find((item)=>item.project.environmentName === project.environmentName);
|
|
3488
|
+
const refreshManifestModule = (manifestModule)=>{
|
|
3489
|
+
const entry = getProjectEntry(manifestModule.project);
|
|
3490
|
+
manifestModule.modules[manifestModule.manifestPath] = generateManifestModule({
|
|
3491
|
+
manifestPath: manifestModule.manifestPath,
|
|
3492
|
+
entries: [
|
|
3493
|
+
{
|
|
3494
|
+
project: manifestModule.project,
|
|
3495
|
+
testFiles: entry?.testFiles ?? [],
|
|
3496
|
+
setupFiles: entry?.setupFiles ?? []
|
|
3497
|
+
}
|
|
3498
|
+
],
|
|
3499
|
+
isWatchMode
|
|
3500
|
+
});
|
|
3501
|
+
};
|
|
3502
|
+
const refreshProjectEntries = async ()=>{
|
|
3503
|
+
validateBrowserConfig(context);
|
|
3504
|
+
browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3505
|
+
const updatedShardedEntries = freezeShardedEntries ? shardedEntries : context.normalizedConfig.shard ? await resolveShardedEntries(context, {
|
|
3506
|
+
silent: true
|
|
3507
|
+
}) : shardedEntries;
|
|
3508
|
+
projectEntries = await resolveProjectEntries(context, updatedShardedEntries, browserProjects);
|
|
3509
|
+
for (const manifestModule of manifestModules)refreshManifestModule(manifestModule);
|
|
3510
|
+
};
|
|
3413
3511
|
const browserRuntimePath = fileURLToPath(import.meta.resolve('@rstest/core/internal/browser-runtime'));
|
|
3414
3512
|
const staticRstestAliases = {
|
|
3415
|
-
'@rstest/core': resolveBrowserFile('client/public.ts'),
|
|
3416
3513
|
'@rstest/browser': resolveBrowserFile('browser.ts'),
|
|
3417
3514
|
'@rstest/core/internal/browser-runtime': browserRuntimePath
|
|
3418
3515
|
};
|
|
@@ -3454,26 +3551,27 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3454
3551
|
return false;
|
|
3455
3552
|
}
|
|
3456
3553
|
};
|
|
3457
|
-
const entryByEnvironmentName = new Map(projectEntries.map((entry)=>[
|
|
3458
|
-
entry.project.environmentName,
|
|
3459
|
-
entry
|
|
3460
|
-
]));
|
|
3461
3554
|
const buildProjectServer = async (project, isContainerServer)=>{
|
|
3462
3555
|
const manifestPath = join(tempDir, toSafeVarName(project.environmentName), VIRTUAL_MANIFEST_FILENAME);
|
|
3463
|
-
const entry =
|
|
3464
|
-
const
|
|
3556
|
+
const entry = getProjectEntry(project);
|
|
3557
|
+
const virtualManifestModules = {
|
|
3558
|
+
[manifestPath]: generateManifestModule({
|
|
3559
|
+
manifestPath,
|
|
3560
|
+
entries: [
|
|
3561
|
+
{
|
|
3562
|
+
project,
|
|
3563
|
+
testFiles: entry?.testFiles ?? [],
|
|
3564
|
+
setupFiles: entry?.setupFiles ?? []
|
|
3565
|
+
}
|
|
3566
|
+
],
|
|
3567
|
+
isWatchMode
|
|
3568
|
+
})
|
|
3569
|
+
};
|
|
3570
|
+
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin(virtualManifestModules);
|
|
3571
|
+
manifestModules.push({
|
|
3465
3572
|
manifestPath,
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
project,
|
|
3469
|
-
testFiles: entry?.testFiles ?? [],
|
|
3470
|
-
setupFiles: entry?.setupFiles ?? []
|
|
3471
|
-
}
|
|
3472
|
-
],
|
|
3473
|
-
isWatchMode
|
|
3474
|
-
});
|
|
3475
|
-
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin({
|
|
3476
|
-
[manifestPath]: manifestSource
|
|
3573
|
+
project,
|
|
3574
|
+
modules: virtualManifestModules
|
|
3477
3575
|
});
|
|
3478
3576
|
const rstestInternalAliases = {
|
|
3479
3577
|
'@rstest/browser-manifest': manifestPath,
|
|
@@ -3482,11 +3580,10 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3482
3580
|
const isHeadless = forceHeadless || project.normalizedConfig.browser.headless;
|
|
3483
3581
|
const enableHmr = shouldEnableBrowserHmr(isWatchMode, isHeadless);
|
|
3484
3582
|
const rsbuildInstance = await createRsbuild({
|
|
3485
|
-
callerName: 'rstest
|
|
3583
|
+
callerName: 'rstest',
|
|
3486
3584
|
rsbuildConfig: {
|
|
3487
3585
|
root: context.rootPath,
|
|
3488
3586
|
mode: 'development',
|
|
3489
|
-
plugins: project.normalizedConfig.plugins || [],
|
|
3490
3587
|
server: {
|
|
3491
3588
|
printUrls: false,
|
|
3492
3589
|
port: project.normalizedConfig.browser.port ?? (isContainerServer ? 4000 : 0),
|
|
@@ -3494,11 +3591,21 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3494
3591
|
},
|
|
3495
3592
|
dev: createBrowserRsbuildDevConfig(enableHmr),
|
|
3496
3593
|
environments: {
|
|
3497
|
-
[project.environmentName]:
|
|
3594
|
+
[project.environmentName]: getBrowserRsbuildEnvironmentConfig(project)
|
|
3498
3595
|
}
|
|
3499
3596
|
}
|
|
3500
3597
|
});
|
|
3598
|
+
initModifyRstestConfigHooks(context, rsbuildInstance, [
|
|
3599
|
+
project
|
|
3600
|
+
], [
|
|
3601
|
+
project
|
|
3602
|
+
], {
|
|
3603
|
+
getEnvironmentConfig: getBrowserRsbuildEnvironmentConfig,
|
|
3604
|
+
onModifyRstestConfigApplied: refreshProjectEntries,
|
|
3605
|
+
appliedEnvironmentNames: appliedModifyRstestConfigEnvironments
|
|
3606
|
+
});
|
|
3501
3607
|
rsbuildInstance.addPlugins([
|
|
3608
|
+
pluginMockRuntime,
|
|
3502
3609
|
{
|
|
3503
3610
|
name: 'rstest:browser-user-config',
|
|
3504
3611
|
setup (api) {
|
|
@@ -3529,7 +3636,8 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3529
3636
|
source: {
|
|
3530
3637
|
define: {
|
|
3531
3638
|
'process.env': rstestEnvDefine,
|
|
3532
|
-
'import.meta.env': rstestEnvDefine
|
|
3639
|
+
'import.meta.env': rstestEnvDefine,
|
|
3640
|
+
'import.meta.rstest': importMetaRstestDefine('web')
|
|
3533
3641
|
}
|
|
3534
3642
|
},
|
|
3535
3643
|
output: {
|
|
@@ -3541,6 +3649,10 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3541
3649
|
tools: {
|
|
3542
3650
|
rspack: (rspackConfig)=>{
|
|
3543
3651
|
rspackConfig.mode = 'development';
|
|
3652
|
+
applyWebMockRspackConfig(rspackConfig, {
|
|
3653
|
+
rspack: rspack,
|
|
3654
|
+
rootPath: project.rootPath
|
|
3655
|
+
});
|
|
3544
3656
|
rspackConfig.lazyCompilation = enableHmr ? createBrowserLazyCompilationConfig(setupFiles) : false;
|
|
3545
3657
|
rspackConfig.plugins = rspackConfig.plugins || [];
|
|
3546
3658
|
rspackConfig.plugins.push(virtualManifestPlugin);
|
|
@@ -3593,6 +3705,21 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3593
3705
|
}
|
|
3594
3706
|
}
|
|
3595
3707
|
]);
|
|
3708
|
+
if (skipProviderLaunch) {
|
|
3709
|
+
await rsbuildInstance.initConfigs({
|
|
3710
|
+
action: 'dev'
|
|
3711
|
+
});
|
|
3712
|
+
return {
|
|
3713
|
+
projectName: project.name,
|
|
3714
|
+
environmentName: project.environmentName,
|
|
3715
|
+
rsbuildInstance,
|
|
3716
|
+
devServer: {
|
|
3717
|
+
close: async ()=>void 0
|
|
3718
|
+
},
|
|
3719
|
+
port: 0,
|
|
3720
|
+
manifestPath
|
|
3721
|
+
};
|
|
3722
|
+
}
|
|
3596
3723
|
const coverage = project.normalizedConfig.coverage;
|
|
3597
3724
|
if (coverage?.enabled && 'list' !== context.command) {
|
|
3598
3725
|
const { pluginCoverage } = await loadCoverageProvider(coverage, context.rootPath);
|
|
@@ -3688,6 +3815,7 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3688
3815
|
await closeAllProjectServers(projectServers.values());
|
|
3689
3816
|
throw error;
|
|
3690
3817
|
}
|
|
3818
|
+
if (skipProviderLaunch) return createRuntimeWithoutProvider();
|
|
3691
3819
|
const containerServer = projectServers.get(browserProjects[0].name);
|
|
3692
3820
|
const wss = new WebSocketServer({
|
|
3693
3821
|
port: 0
|
|
@@ -3715,7 +3843,8 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3715
3843
|
tempDir,
|
|
3716
3844
|
setContainerOptions,
|
|
3717
3845
|
dispatchHandlers,
|
|
3718
|
-
wss
|
|
3846
|
+
wss,
|
|
3847
|
+
projectEntries
|
|
3719
3848
|
};
|
|
3720
3849
|
} catch (error) {
|
|
3721
3850
|
wss.close();
|
|
@@ -3723,9 +3852,8 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3723
3852
|
throw error;
|
|
3724
3853
|
}
|
|
3725
3854
|
};
|
|
3726
|
-
async function resolveProjectEntries(context, shardedEntries) {
|
|
3855
|
+
async function resolveProjectEntries(context, shardedEntries, browserProjects) {
|
|
3727
3856
|
if (shardedEntries) {
|
|
3728
|
-
const browserProjects = getBrowserProjects(context);
|
|
3729
3857
|
const projectEntries = [];
|
|
3730
3858
|
for (const project of browserProjects){
|
|
3731
3859
|
const entryInfo = shardedEntries.get(project.environmentName);
|
|
@@ -3740,14 +3868,14 @@ async function resolveProjectEntries(context, shardedEntries) {
|
|
|
3740
3868
|
}
|
|
3741
3869
|
return projectEntries;
|
|
3742
3870
|
}
|
|
3743
|
-
return collectProjectEntries(context);
|
|
3871
|
+
return collectProjectEntries(context, browserProjects);
|
|
3744
3872
|
}
|
|
3745
3873
|
const runBrowserController = async (context, options)=>{
|
|
3746
|
-
const {
|
|
3874
|
+
const { allowEmptyWatchRun = false, allowEmptyRun = false, filesOnly = false, onTraceEvents, env } = options ?? {};
|
|
3747
3875
|
const buildStart = Date.now();
|
|
3748
3876
|
const isWatchMode = 'watch' === context.command;
|
|
3749
3877
|
const phaseTrackers = onTraceEvents ? new Map() : void 0;
|
|
3750
|
-
const browserProjects = getBrowserProjects(context);
|
|
3878
|
+
const browserProjects = options?.projects ?? getBrowserProjects(context);
|
|
3751
3879
|
const useHeadlessDirect = browserProjects.every((project)=>project.normalizedConfig.browser.headless);
|
|
3752
3880
|
const browserSourceMapCache = new Map();
|
|
3753
3881
|
const isHttpLikeFile = (file)=>/^https?:\/\//.test(file);
|
|
@@ -3795,7 +3923,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3795
3923
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
3796
3924
|
close
|
|
3797
3925
|
};
|
|
3798
|
-
if (
|
|
3926
|
+
if (isWatchMode) for (const reporter of context.reporters)await reporter.onTestRunEnd?.({
|
|
3799
3927
|
results: [],
|
|
3800
3928
|
testResults: [],
|
|
3801
3929
|
duration: errorResult.duration,
|
|
@@ -3807,9 +3935,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
3807
3935
|
};
|
|
3808
3936
|
const toError = (error)=>error instanceof Error ? error : new Error(String(error));
|
|
3809
3937
|
const failWithError = async (error, cleanup)=>{
|
|
3810
|
-
ensureProcessExitCode(1);
|
|
3938
|
+
if (isWatchMode) ensureProcessExitCode(1);
|
|
3811
3939
|
const normalizedError = toError(error);
|
|
3812
|
-
if (cleanup &&
|
|
3940
|
+
if (cleanup && !isWatchMode) return buildErrorResult(normalizedError, cleanup);
|
|
3813
3941
|
try {
|
|
3814
3942
|
return await buildErrorResult(normalizedError);
|
|
3815
3943
|
} finally{
|
|
@@ -3821,13 +3949,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3821
3949
|
return previous.map((file)=>file.testPath).filter((testPath)=>!currentPathSet.has(testPath));
|
|
3822
3950
|
};
|
|
3823
3951
|
const notifyTestRunStart = async ()=>{
|
|
3824
|
-
if (
|
|
3952
|
+
if (!isWatchMode) return;
|
|
3825
3953
|
for (const reporter of context.reporters)await reporter.onTestRunStart?.();
|
|
3826
3954
|
};
|
|
3827
3955
|
const coverageConfig = browserProjects.find((project)=>project.normalizedConfig.coverage?.enabled)?.normalizedConfig.coverage;
|
|
3828
3956
|
const coverageProvider = coverageConfig?.enabled ? await createCoverageProvider(coverageConfig, context.rootPath) : null;
|
|
3829
3957
|
const notifyTestRunEnd = async ({ duration, unhandledErrors, filterRerunTestPaths })=>{
|
|
3830
|
-
if (
|
|
3958
|
+
if (!isWatchMode) return;
|
|
3831
3959
|
let mergedCoverage;
|
|
3832
3960
|
if (coverageProvider) {
|
|
3833
3961
|
const coverageMap = coverageProvider.createCoverageMap();
|
|
@@ -3867,12 +3995,28 @@ const runBrowserController = async (context, options)=>{
|
|
|
3867
3995
|
return failWithError(error);
|
|
3868
3996
|
}
|
|
3869
3997
|
}
|
|
3870
|
-
|
|
3871
|
-
|
|
3998
|
+
let projectEntries = await resolveProjectEntries(context, options?.shardedEntries, browserProjects);
|
|
3999
|
+
let totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
3872
4000
|
const shouldKeepWatchingWithEmptySet = isWatchMode && allowEmptyWatchRun;
|
|
3873
|
-
|
|
4001
|
+
const shouldInitializeEmptyBrowserHooks = 0 === totalTests && hasUserRstestConfigPlugins(browserProjects);
|
|
4002
|
+
const createEmptyRunResult = ()=>{
|
|
4003
|
+
const elapsed = Math.max(0, Date.now() - buildStart);
|
|
4004
|
+
return {
|
|
4005
|
+
results: [],
|
|
4006
|
+
testResults: [],
|
|
4007
|
+
duration: {
|
|
4008
|
+
totalTime: elapsed,
|
|
4009
|
+
buildTime: elapsed,
|
|
4010
|
+
testTime: 0
|
|
4011
|
+
},
|
|
4012
|
+
hasFailure: false,
|
|
4013
|
+
getSourcemap: getBrowserSourcemap,
|
|
4014
|
+
resolveSourcemap: resolveBrowserSourcemap
|
|
4015
|
+
};
|
|
4016
|
+
};
|
|
4017
|
+
const reportEmptyTestSet = ()=>{
|
|
3874
4018
|
const code = context.normalizedConfig.passWithNoTests ? 0 : 1;
|
|
3875
|
-
if (!
|
|
4019
|
+
if (isWatchMode || !allowEmptyRun) {
|
|
3876
4020
|
const message = shouldKeepWatchingWithEmptySet ? 'No test files found.' : getNoTestFilesMessage({
|
|
3877
4021
|
context,
|
|
3878
4022
|
code,
|
|
@@ -3883,10 +4027,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3883
4027
|
if (context.relatedFilters?.length) logger.log(color.gray('related: '), context.relatedFilters.join(color.gray(', ')));
|
|
3884
4028
|
else if (context.fileFilters?.length) logger.log(color.gray('filter: '), context.fileFilters.join(color.gray(', ')));
|
|
3885
4029
|
}
|
|
3886
|
-
if (0 !== code && !shouldKeepWatchingWithEmptySet) ensureProcessExitCode(code);
|
|
3887
|
-
|
|
4030
|
+
if (isWatchMode && 0 !== code && !shouldKeepWatchingWithEmptySet && !allowEmptyRun) ensureProcessExitCode(code);
|
|
4031
|
+
return !shouldKeepWatchingWithEmptySet;
|
|
4032
|
+
};
|
|
4033
|
+
if (0 === totalTests && !shouldInitializeEmptyBrowserHooks) {
|
|
4034
|
+
if (reportEmptyTestSet()) return allowEmptyRun ? createEmptyRunResult() : void 0;
|
|
3888
4035
|
}
|
|
3889
|
-
await notifyTestRunStart();
|
|
4036
|
+
if (!filesOnly) await notifyTestRunStart();
|
|
3890
4037
|
const enableCliShortcuts = isWatchMode && isBrowserWatchCliShortcutsEnabled();
|
|
3891
4038
|
const browserTempOutputRoot = context.normalizedConfig.output.distPath.root;
|
|
3892
4039
|
const tempDir = isWatchMode && watchContext.runtime ? watchContext.runtime.tempDir : isWatchMode ? join(context.rootPath, browserTempOutputRoot, 'browser', 'watch') : join(context.rootPath, browserTempOutputRoot, 'browser', Date.now().toString());
|
|
@@ -3898,13 +4045,18 @@ const runBrowserController = async (context, options)=>{
|
|
|
3898
4045
|
runtime = await createBrowserRuntime({
|
|
3899
4046
|
context,
|
|
3900
4047
|
projectEntries,
|
|
4048
|
+
browserProjects,
|
|
4049
|
+
shardedEntries: options?.shardedEntries,
|
|
4050
|
+
freezeShardedEntries: options?.freezeShardedEntries,
|
|
3901
4051
|
tempDir,
|
|
3902
4052
|
isWatchMode,
|
|
3903
4053
|
onTriggerRerun: isWatchMode ? async ()=>{
|
|
3904
4054
|
await triggerRerun?.();
|
|
3905
4055
|
} : void 0,
|
|
3906
4056
|
containerDistPath,
|
|
3907
|
-
containerDevServer
|
|
4057
|
+
containerDevServer,
|
|
4058
|
+
skipProviderLaunch: filesOnly,
|
|
4059
|
+
appliedModifyRstestConfigEnvironments: options?.appliedModifyRstestConfigEnvironments
|
|
3908
4060
|
});
|
|
3909
4061
|
} catch (error) {
|
|
3910
4062
|
return failWithError(error, async ()=>{
|
|
@@ -3922,8 +4074,27 @@ const runBrowserController = async (context, options)=>{
|
|
|
3922
4074
|
});
|
|
3923
4075
|
}
|
|
3924
4076
|
}
|
|
3925
|
-
|
|
4077
|
+
projectEntries = runtime.projectEntries;
|
|
4078
|
+
totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
3926
4079
|
const buildTime = Date.now() - buildStart;
|
|
4080
|
+
if (filesOnly) return {
|
|
4081
|
+
results: [],
|
|
4082
|
+
testResults: [],
|
|
4083
|
+
duration: {
|
|
4084
|
+
totalTime: buildTime,
|
|
4085
|
+
buildTime,
|
|
4086
|
+
testTime: 0
|
|
4087
|
+
},
|
|
4088
|
+
hasFailure: false,
|
|
4089
|
+
getSourcemap: getBrowserSourcemap,
|
|
4090
|
+
resolveSourcemap: resolveBrowserSourcemap,
|
|
4091
|
+
close: ()=>destroyBrowserRuntime(runtime)
|
|
4092
|
+
};
|
|
4093
|
+
if (0 === totalTests && reportEmptyTestSet()) {
|
|
4094
|
+
await destroyBrowserRuntime(runtime);
|
|
4095
|
+
return allowEmptyRun ? createEmptyRunResult() : void 0;
|
|
4096
|
+
}
|
|
4097
|
+
const { browser, browserLaunchOptions, wsPort, wss } = runtime;
|
|
3927
4098
|
const allTestFiles = projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
3928
4099
|
testPath: normalize(testPath),
|
|
3929
4100
|
projectName: entry.project.name
|
|
@@ -3932,10 +4103,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3932
4103
|
name: project.name,
|
|
3933
4104
|
environmentName: project.environmentName,
|
|
3934
4105
|
projectRoot: normalize(project.rootPath),
|
|
3935
|
-
runtimeConfig: serializableConfig(
|
|
4106
|
+
runtimeConfig: serializableConfig(projectRuntimeConfig(project, {
|
|
4107
|
+
envMode: 'static',
|
|
4108
|
+
envOverlay: env
|
|
4109
|
+
})),
|
|
3936
4110
|
viewport: project.normalizedConfig.browser.viewport
|
|
3937
4111
|
}));
|
|
3938
|
-
const maxTestTimeoutForRpc =
|
|
4112
|
+
const maxTestTimeoutForRpc = getMaxTestTimeoutForRpc(browserProjects);
|
|
3939
4113
|
const projectRunnerUrls = Object.fromEntries([
|
|
3940
4114
|
...runtime.projectServers
|
|
3941
4115
|
].map(([name, server])=>[
|
|
@@ -3996,11 +4170,28 @@ const runBrowserController = async (context, options)=>{
|
|
|
3996
4170
|
const reporterResults = [];
|
|
3997
4171
|
const caseResults = [];
|
|
3998
4172
|
let fatalError = null;
|
|
4173
|
+
const runnerSinks = new Map(browserProjects.map((project)=>[
|
|
4174
|
+
project.name,
|
|
4175
|
+
createRunnerEventSink(context, project.normalizedConfig)
|
|
4176
|
+
]));
|
|
4177
|
+
const firstBrowserSink = runnerSinks.get(browserProjects[0].name);
|
|
4178
|
+
const projectNameByTestPath = new Map();
|
|
4179
|
+
const sinkForProjectName = (projectName)=>runnerSinks.get(projectName) ?? firstBrowserSink;
|
|
4180
|
+
const sinkForTestPath = (testPath)=>{
|
|
4181
|
+
const projectName = projectNameByTestPath.get(testPath);
|
|
4182
|
+
return projectName ? sinkForProjectName(projectName) : firstBrowserSink;
|
|
4183
|
+
};
|
|
4184
|
+
const silentConsoleController = createSilentConsoleController({
|
|
4185
|
+
runtimeConfig: {
|
|
4186
|
+
silent: context.normalizedConfig.silent,
|
|
4187
|
+
disableConsoleIntercept: context.normalizedConfig.disableConsoleIntercept
|
|
4188
|
+
},
|
|
4189
|
+
emitInterceptedLog: (log)=>sinkForTestPath(log.testPath).onConsoleLog(log),
|
|
4190
|
+
writeOriginalLog: ()=>{}
|
|
4191
|
+
});
|
|
3999
4192
|
const snapshotRpcMethods = {
|
|
4000
4193
|
async resolveSnapshotPath (testPath) {
|
|
4001
|
-
|
|
4002
|
-
const resolver = context.normalizedConfig.resolveSnapshotPath || (()=>join(dirname(testPath), '__snapshots__', `${basename(testPath)}${snapExtension}`));
|
|
4003
|
-
return resolver(testPath, snapExtension);
|
|
4194
|
+
return resolveSnapshotPathDefault(testPath, context.normalizedConfig.resolveSnapshotPath);
|
|
4004
4195
|
},
|
|
4005
4196
|
async readSnapshotFile (filepath) {
|
|
4006
4197
|
try {
|
|
@@ -4023,6 +4214,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4023
4214
|
}
|
|
4024
4215
|
};
|
|
4025
4216
|
const handleTestFileStart = async (payload)=>{
|
|
4217
|
+
projectNameByTestPath.set(payload.testPath, payload.projectName);
|
|
4026
4218
|
if (phaseTrackers) {
|
|
4027
4219
|
const tracker = new PhaseTracker({
|
|
4028
4220
|
trace: {
|
|
@@ -4034,24 +4226,24 @@ const runBrowserController = async (context, options)=>{
|
|
|
4034
4226
|
tracker.transition('prepare');
|
|
4035
4227
|
phaseTrackers.set(payload.testPath, tracker);
|
|
4036
4228
|
}
|
|
4037
|
-
await
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4229
|
+
await sinkForProjectName(payload.projectName).onTestFileStart({
|
|
4230
|
+
testId: getFileTaskId(payload.testPath),
|
|
4231
|
+
testPath: payload.testPath,
|
|
4232
|
+
tests: []
|
|
4233
|
+
});
|
|
4042
4234
|
};
|
|
4043
4235
|
const handleTestFileReady = async (payload)=>{
|
|
4044
4236
|
phaseTrackers?.get(payload.testPath)?.transition('tests');
|
|
4045
|
-
await
|
|
4237
|
+
await sinkForTestPath(payload.testPath).onTestFileReady(payload);
|
|
4046
4238
|
};
|
|
4047
4239
|
const handleTestSuiteStart = async (payload)=>{
|
|
4048
4240
|
phaseTrackers?.get(payload.testPath)?.recordSuiteStart(payload);
|
|
4049
|
-
await
|
|
4241
|
+
await sinkForTestPath(payload.testPath).onTestSuiteStart(payload);
|
|
4050
4242
|
};
|
|
4051
4243
|
const handleTestSuiteResult = async (payload)=>{
|
|
4052
4244
|
phaseTrackers?.get(payload.testPath)?.recordSuiteResult(payload);
|
|
4053
|
-
await
|
|
4054
|
-
if ('passed-only' === context.normalizedConfig.silent)
|
|
4245
|
+
await sinkForTestPath(payload.testPath).onTestSuiteResult(payload);
|
|
4246
|
+
if ('passed-only' === context.normalizedConfig.silent) silentConsoleController.flushBufferedLogsForTask({
|
|
4055
4247
|
taskId: payload.testId,
|
|
4056
4248
|
status: payload.status,
|
|
4057
4249
|
taskParentNames: payload.parentNames,
|
|
@@ -4061,13 +4253,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
4061
4253
|
};
|
|
4062
4254
|
const handleTestCaseStart = async (payload)=>{
|
|
4063
4255
|
phaseTrackers?.get(payload.testPath)?.recordCaseStart(payload);
|
|
4064
|
-
|
|
4256
|
+
sinkForTestPath(payload.testPath).onTestCaseStart(payload);
|
|
4065
4257
|
};
|
|
4066
4258
|
const handleTestCaseResult = async (payload)=>{
|
|
4067
4259
|
caseResults.push(payload);
|
|
4068
4260
|
phaseTrackers?.get(payload.testPath)?.recordCaseResult(payload);
|
|
4069
|
-
await
|
|
4070
|
-
if ('passed-only' === context.normalizedConfig.silent)
|
|
4261
|
+
await sinkForTestPath(payload.testPath).onTestCaseResult(payload);
|
|
4262
|
+
if ('passed-only' === context.normalizedConfig.silent) silentConsoleController.flushBufferedLogsForTask({
|
|
4071
4263
|
taskId: payload.testId,
|
|
4072
4264
|
status: payload.status,
|
|
4073
4265
|
taskParentNames: payload.parentNames,
|
|
@@ -4080,7 +4272,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
4080
4272
|
context.updateReporterResultState([
|
|
4081
4273
|
payload
|
|
4082
4274
|
], payload.results);
|
|
4083
|
-
if (payload.snapshotResult) context.snapshotManager.add(payload.snapshotResult);
|
|
4084
4275
|
if (phaseTrackers) {
|
|
4085
4276
|
const tracker = phaseTrackers.get(payload.testPath);
|
|
4086
4277
|
if (tracker) {
|
|
@@ -4090,20 +4281,20 @@ const runBrowserController = async (context, options)=>{
|
|
|
4090
4281
|
phaseTrackers.delete(payload.testPath);
|
|
4091
4282
|
}
|
|
4092
4283
|
}
|
|
4093
|
-
if ('passed-only' === context.normalizedConfig.silent)
|
|
4284
|
+
if ('passed-only' === context.normalizedConfig.silent) silentConsoleController.flushBufferedLogsForTask({
|
|
4094
4285
|
taskId: payload.testId,
|
|
4095
4286
|
status: payload.status,
|
|
4096
4287
|
taskParentNames: payload.parentNames,
|
|
4097
4288
|
taskType: 'file',
|
|
4098
4289
|
testPath: payload.testPath
|
|
4099
4290
|
});
|
|
4100
|
-
await
|
|
4101
|
-
if ('fail' === payload.status) ensureProcessExitCode(1);
|
|
4291
|
+
await sinkForTestPath(payload.testPath).onTestFileResult(payload);
|
|
4292
|
+
if (isWatchMode && 'fail' === payload.status) ensureProcessExitCode(1);
|
|
4102
4293
|
};
|
|
4103
4294
|
const handleLog = async (payload)=>{
|
|
4104
4295
|
const log = {
|
|
4105
4296
|
content: payload.content,
|
|
4106
|
-
name: payload.level,
|
|
4297
|
+
name: getPrettyConsoleName(payload.level),
|
|
4107
4298
|
taskId: payload.taskId,
|
|
4108
4299
|
taskName: payload.taskName,
|
|
4109
4300
|
taskParentNames: payload.taskParentNames,
|
|
@@ -4112,62 +4303,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
4112
4303
|
type: payload.type,
|
|
4113
4304
|
trace: payload.trace
|
|
4114
4305
|
};
|
|
4115
|
-
|
|
4116
|
-
if ('passed-only' === context.normalizedConfig.silent) return void bufferConsoleLog(log);
|
|
4117
|
-
if (context.normalizedConfig.disableConsoleIntercept) return;
|
|
4118
|
-
await emitUserConsoleLog(log);
|
|
4306
|
+
silentConsoleController.onConsoleLog(log);
|
|
4119
4307
|
};
|
|
4120
4308
|
const handleFatal = async (payload)=>{
|
|
4121
4309
|
const error = new Error(payload.message);
|
|
4122
4310
|
error.stack = payload.stack;
|
|
4123
4311
|
fatalError = error;
|
|
4124
|
-
ensureProcessExitCode(1);
|
|
4125
|
-
};
|
|
4126
|
-
const bufferedConsoleLogs = new Map();
|
|
4127
|
-
const suiteIdsByChain = new Map();
|
|
4128
|
-
const getSuiteChainKey = (names)=>names.join('\u0000');
|
|
4129
|
-
const pushTaskId = (taskIds, taskId)=>{
|
|
4130
|
-
if (!taskIds.includes(taskId)) taskIds.push(taskId);
|
|
4131
|
-
};
|
|
4132
|
-
const shouldEmitUserConsoleLog = (log)=>context.normalizedConfig.onConsoleLog?.(log.content, log.type) !== false;
|
|
4133
|
-
const emitUserConsoleLog = async (log)=>{
|
|
4134
|
-
if (!shouldEmitUserConsoleLog(log)) return;
|
|
4135
|
-
await Promise.all(context.reporters.map((reporter)=>reporter.onUserConsoleLog?.(log)));
|
|
4136
|
-
};
|
|
4137
|
-
const bufferConsoleLog = (log)=>{
|
|
4138
|
-
const taskId = getBufferedLogTaskId(log);
|
|
4139
|
-
const logs = bufferedConsoleLogs.get(taskId) || [];
|
|
4140
|
-
logs.push(log);
|
|
4141
|
-
bufferedConsoleLogs.set(taskId, logs);
|
|
4142
|
-
if ('suite' === log.taskType && log.taskId) suiteIdsByChain.set(getSuiteChainKey([
|
|
4143
|
-
...log.taskParentNames || [],
|
|
4144
|
-
log.taskName || ''
|
|
4145
|
-
]), log.taskId);
|
|
4146
|
-
};
|
|
4147
|
-
const flushBufferedLogsForTask = async ({ taskId, status, taskParentNames, taskType, testPath })=>{
|
|
4148
|
-
if ('fail' !== status) return void bufferedConsoleLogs.delete(taskId);
|
|
4149
|
-
const taskIdsToFlush = [];
|
|
4150
|
-
if ('case' === taskType) {
|
|
4151
|
-
pushTaskId(taskIdsToFlush, getFileTaskId(testPath));
|
|
4152
|
-
const suiteNames = taskParentNames || [];
|
|
4153
|
-
for(let i = 0; i < suiteNames.length; i++){
|
|
4154
|
-
const suiteId = suiteIdsByChain.get(getSuiteChainKey(suiteNames.slice(0, i + 1)));
|
|
4155
|
-
if (suiteId) pushTaskId(taskIdsToFlush, suiteId);
|
|
4156
|
-
}
|
|
4157
|
-
pushTaskId(taskIdsToFlush, taskId);
|
|
4158
|
-
}
|
|
4159
|
-
if ('suite' === taskType) {
|
|
4160
|
-
pushTaskId(taskIdsToFlush, getFileTaskId(testPath));
|
|
4161
|
-
pushTaskId(taskIdsToFlush, taskId);
|
|
4162
|
-
}
|
|
4163
|
-
if ('file' === taskType) pushTaskId(taskIdsToFlush, taskId);
|
|
4164
|
-
for (const bufferedTaskId of taskIdsToFlush){
|
|
4165
|
-
const logs = bufferedConsoleLogs.get(bufferedTaskId);
|
|
4166
|
-
if (logs) {
|
|
4167
|
-
bufferedConsoleLogs.delete(bufferedTaskId);
|
|
4168
|
-
for (const log of logs)await Promise.all(context.reporters.map((reporter)=>reporter.onUserConsoleLog?.(log)));
|
|
4169
|
-
}
|
|
4170
|
-
}
|
|
4312
|
+
if (isWatchMode) ensureProcessExitCode(1);
|
|
4171
4313
|
};
|
|
4172
4314
|
const runSnapshotRpc = async (request)=>{
|
|
4173
4315
|
switch(request.method){
|
|
@@ -4366,6 +4508,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
4366
4508
|
await closeContextSafely(browserContext);
|
|
4367
4509
|
}
|
|
4368
4510
|
};
|
|
4511
|
+
const makeSkippedFileResult = (file)=>({
|
|
4512
|
+
testId: getFileTaskId(file.testPath),
|
|
4513
|
+
status: 'skip',
|
|
4514
|
+
name: '',
|
|
4515
|
+
testPath: file.testPath,
|
|
4516
|
+
project: file.projectName,
|
|
4517
|
+
results: []
|
|
4518
|
+
});
|
|
4369
4519
|
const runFilesWithPool = async (files)=>{
|
|
4370
4520
|
if (0 === files.length) return;
|
|
4371
4521
|
const previous = runLifecycle.activeSession;
|
|
@@ -4378,8 +4528,17 @@ const runBrowserController = async (context, options)=>{
|
|
|
4378
4528
|
...files
|
|
4379
4529
|
];
|
|
4380
4530
|
const concurrency = getHeadlessConcurrency(context, queue.length);
|
|
4531
|
+
const bail = context.normalizedConfig.bail;
|
|
4381
4532
|
const worker = async ()=>{
|
|
4382
4533
|
while(queue.length > 0 && !run.cancelled && runLifecycle.isTokenActive(run.token)){
|
|
4534
|
+
if (bail && context.stateManager.getCountOfFailedTests() >= bail) {
|
|
4535
|
+
let skipped = queue.shift();
|
|
4536
|
+
while(skipped){
|
|
4537
|
+
await handleTestFileComplete(makeSkippedFileResult(skipped));
|
|
4538
|
+
skipped = queue.shift();
|
|
4539
|
+
}
|
|
4540
|
+
return;
|
|
4541
|
+
}
|
|
4383
4542
|
const next = queue.shift();
|
|
4384
4543
|
if (!next) return;
|
|
4385
4544
|
await runSingleFile(run, next);
|
|
@@ -4401,6 +4560,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4401
4560
|
await cancelRun(run, false);
|
|
4402
4561
|
},
|
|
4403
4562
|
runFiles: async (files)=>{
|
|
4563
|
+
prepareWatchRerunState(context);
|
|
4404
4564
|
await notifyTestRunStart();
|
|
4405
4565
|
const rerunStartTime = Date.now();
|
|
4406
4566
|
const fatalErrorBeforeRun = fatalError;
|
|
@@ -4453,12 +4613,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
4453
4613
|
hasFailure: false,
|
|
4454
4614
|
getSourcemap: getBrowserSourcemap,
|
|
4455
4615
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4456
|
-
close:
|
|
4616
|
+
close: isWatchMode ? void 0 : async ()=>{
|
|
4457
4617
|
sessionRegistry.clear();
|
|
4458
4618
|
await destroyBrowserRuntime(runtime);
|
|
4459
|
-
}
|
|
4619
|
+
}
|
|
4460
4620
|
};
|
|
4461
|
-
if (
|
|
4621
|
+
if (isWatchMode) await notifyTestRunEnd({
|
|
4462
4622
|
duration
|
|
4463
4623
|
});
|
|
4464
4624
|
if (isWatchMode) {
|
|
@@ -4533,7 +4693,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4533
4693
|
};
|
|
4534
4694
|
context.updateReporterResultState(reporterResults, caseResults);
|
|
4535
4695
|
const isFailure = reporterResults.some((result)=>'fail' === result.status);
|
|
4536
|
-
if (isFailure) ensureProcessExitCode(1);
|
|
4696
|
+
if (isWatchMode && isFailure) ensureProcessExitCode(1);
|
|
4537
4697
|
const result = {
|
|
4538
4698
|
results: reporterResults,
|
|
4539
4699
|
testResults: caseResults,
|
|
@@ -4541,9 +4701,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
4541
4701
|
hasFailure: isFailure,
|
|
4542
4702
|
getSourcemap: getBrowserSourcemap,
|
|
4543
4703
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4544
|
-
close:
|
|
4704
|
+
close: closeHeadlessRuntime
|
|
4545
4705
|
};
|
|
4546
|
-
if (
|
|
4706
|
+
if (isWatchMode) try {
|
|
4547
4707
|
await notifyTestRunEnd({
|
|
4548
4708
|
duration
|
|
4549
4709
|
});
|
|
@@ -4754,7 +4914,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4754
4914
|
}
|
|
4755
4915
|
} catch (error) {
|
|
4756
4916
|
fatalError = fatalError ?? toError(error);
|
|
4757
|
-
ensureProcessExitCode(1);
|
|
4917
|
+
if (isWatchMode) ensureProcessExitCode(1);
|
|
4758
4918
|
}
|
|
4759
4919
|
testTime = Date.now() - testStart;
|
|
4760
4920
|
}
|
|
@@ -4781,6 +4941,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4781
4941
|
}
|
|
4782
4942
|
if (rerunPlan.normalizedAffectedTestFiles.length > 0) {
|
|
4783
4943
|
logger.log(color.cyan(`Re-running ${rerunPlan.normalizedAffectedTestFiles.length} affected test file(s)...\n`));
|
|
4944
|
+
prepareWatchRerunState(context);
|
|
4784
4945
|
await notifyTestRunStart();
|
|
4785
4946
|
const rerunStartTime = Date.now();
|
|
4786
4947
|
const fatalErrorBeforeRun = fatalError;
|
|
@@ -4831,7 +4992,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4831
4992
|
};
|
|
4832
4993
|
context.updateReporterResultState(reporterResults, caseResults);
|
|
4833
4994
|
const isFailure = reporterResults.some((result)=>'fail' === result.status);
|
|
4834
|
-
if (isFailure) ensureProcessExitCode(1);
|
|
4995
|
+
if (isWatchMode && isFailure) ensureProcessExitCode(1);
|
|
4835
4996
|
const result = {
|
|
4836
4997
|
results: reporterResults,
|
|
4837
4998
|
testResults: caseResults,
|
|
@@ -4839,9 +5000,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
4839
5000
|
hasFailure: isFailure,
|
|
4840
5001
|
getSourcemap: getBrowserSourcemap,
|
|
4841
5002
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4842
|
-
close:
|
|
5003
|
+
close: closeContainerRuntime
|
|
4843
5004
|
};
|
|
4844
|
-
if (
|
|
5005
|
+
if (isWatchMode) try {
|
|
4845
5006
|
await notifyTestRunEnd({
|
|
4846
5007
|
duration
|
|
4847
5008
|
});
|
|
@@ -4855,24 +5016,29 @@ const runBrowserController = async (context, options)=>{
|
|
|
4855
5016
|
return result;
|
|
4856
5017
|
};
|
|
4857
5018
|
const listBrowserTests = async (context, options)=>{
|
|
4858
|
-
const
|
|
5019
|
+
const browserProjects = options?.projects ?? getBrowserProjects(context);
|
|
5020
|
+
const projectEntries = await resolveProjectEntries(context, options?.shardedEntries, browserProjects);
|
|
4859
5021
|
const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
4860
|
-
if (0 === totalTests) return {
|
|
5022
|
+
if (0 === totalTests && !hasUserRstestConfigPlugins(browserProjects)) return {
|
|
4861
5023
|
list: [],
|
|
4862
5024
|
close: async ()=>{}
|
|
4863
5025
|
};
|
|
4864
5026
|
const tempDir = join(context.rootPath, context.normalizedConfig.output.distPath.root, 'browser', `list-${Date.now()}`);
|
|
4865
|
-
const browserProjects = getBrowserProjects(context);
|
|
4866
5027
|
let runtime;
|
|
4867
5028
|
try {
|
|
4868
5029
|
runtime = await createBrowserRuntime({
|
|
4869
5030
|
context,
|
|
4870
5031
|
projectEntries,
|
|
5032
|
+
browserProjects,
|
|
5033
|
+
shardedEntries: options?.shardedEntries,
|
|
5034
|
+
freezeShardedEntries: options?.freezeShardedEntries,
|
|
4871
5035
|
tempDir,
|
|
4872
5036
|
isWatchMode: false,
|
|
4873
5037
|
containerDistPath: void 0,
|
|
4874
5038
|
containerDevServer: void 0,
|
|
4875
|
-
forceHeadless: true
|
|
5039
|
+
forceHeadless: true,
|
|
5040
|
+
skipProviderLaunch: options?.filesOnly,
|
|
5041
|
+
appliedModifyRstestConfigEnvironments: options?.appliedModifyRstestConfigEnvironments
|
|
4876
5042
|
});
|
|
4877
5043
|
} catch (error) {
|
|
4878
5044
|
const providers = [
|
|
@@ -4881,15 +5047,36 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4881
5047
|
logger.error(color.red(`Failed to initialize browser provider runtime (${providers.join(', ')}).`), error);
|
|
4882
5048
|
throw error;
|
|
4883
5049
|
}
|
|
5050
|
+
if (options?.filesOnly) {
|
|
5051
|
+
const list = runtime.projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
5052
|
+
testPath,
|
|
5053
|
+
project: entry.project.name,
|
|
5054
|
+
tests: []
|
|
5055
|
+
})));
|
|
5056
|
+
await destroyBrowserRuntime(runtime);
|
|
5057
|
+
return {
|
|
5058
|
+
list,
|
|
5059
|
+
close: async ()=>{}
|
|
5060
|
+
};
|
|
5061
|
+
}
|
|
5062
|
+
if (!runtime.projectEntries.some((entry)=>entry.testFiles.length > 0)) {
|
|
5063
|
+
await destroyBrowserRuntime(runtime);
|
|
5064
|
+
return {
|
|
5065
|
+
list: [],
|
|
5066
|
+
close: async ()=>{}
|
|
5067
|
+
};
|
|
5068
|
+
}
|
|
4884
5069
|
const { browser, browserLaunchOptions } = runtime;
|
|
4885
5070
|
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
4886
5071
|
name: project.name,
|
|
4887
5072
|
environmentName: project.environmentName,
|
|
4888
5073
|
projectRoot: normalize(project.rootPath),
|
|
4889
|
-
runtimeConfig: serializableConfig(
|
|
5074
|
+
runtimeConfig: serializableConfig(projectRuntimeConfig(project, {
|
|
5075
|
+
envMode: 'static'
|
|
5076
|
+
})),
|
|
4890
5077
|
viewport: project.normalizedConfig.browser.viewport
|
|
4891
5078
|
}));
|
|
4892
|
-
const maxTestTimeoutForRpc =
|
|
5079
|
+
const maxTestTimeoutForRpc = getMaxTestTimeoutForRpc(browserProjects);
|
|
4893
5080
|
const hostOptions = {
|
|
4894
5081
|
rootPath: normalize(context.rootPath),
|
|
4895
5082
|
projects: projectRuntimeConfigs,
|
|
@@ -4906,6 +5093,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4906
5093
|
viewport: null
|
|
4907
5094
|
});
|
|
4908
5095
|
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
5096
|
+
const collectTimeoutMs = 30000;
|
|
4909
5097
|
const collectFromServer = async (server)=>{
|
|
4910
5098
|
const results = [];
|
|
4911
5099
|
let error = null;
|
|
@@ -4950,13 +5138,12 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4950
5138
|
await page.goto(`http://localhost:${server.port}/runner.html`, {
|
|
4951
5139
|
waitUntil: 'load'
|
|
4952
5140
|
});
|
|
4953
|
-
const timeoutMs = 30000;
|
|
4954
5141
|
let timeoutId;
|
|
4955
5142
|
const timeoutPromise = new Promise((resolve)=>{
|
|
4956
5143
|
timeoutId = setTimeout(()=>{
|
|
4957
|
-
if (!collectCompleted) logger.warn(color.yellow(`[List] Browser test collection timed out after ${
|
|
5144
|
+
if (!collectCompleted) logger.warn(color.yellow(`[List] Browser test collection timed out after ${collectTimeoutMs}ms`));
|
|
4958
5145
|
resolve();
|
|
4959
|
-
},
|
|
5146
|
+
}, collectTimeoutMs);
|
|
4960
5147
|
});
|
|
4961
5148
|
await Promise.race([
|
|
4962
5149
|
collectPromise,
|
|
@@ -5006,10 +5193,92 @@ const listBrowserTests = async (context, options)=>{
|
|
|
5006
5193
|
close: cleanup
|
|
5007
5194
|
};
|
|
5008
5195
|
};
|
|
5196
|
+
const emptyOutcome = ()=>({
|
|
5197
|
+
results: [],
|
|
5198
|
+
testResults: [],
|
|
5199
|
+
errors: [],
|
|
5200
|
+
testPaths: [],
|
|
5201
|
+
duration: {
|
|
5202
|
+
buildTime: 0,
|
|
5203
|
+
testTime: 0
|
|
5204
|
+
}
|
|
5205
|
+
});
|
|
5206
|
+
async function createBrowserExecutor(context, options) {
|
|
5207
|
+
const { projects, coverageProvider, freezeShardedEntries, filesOnly, allowEmptyRun, appliedModifyRstestConfigEnvironments } = options;
|
|
5208
|
+
let deferredClose;
|
|
5209
|
+
let inFlightCycle;
|
|
5210
|
+
const foldOutcome = (result)=>{
|
|
5211
|
+
if (!result) return emptyOutcome();
|
|
5212
|
+
const map = buildBrowserCoverageMap(result.results, coverageProvider);
|
|
5213
|
+
return {
|
|
5214
|
+
results: result.results,
|
|
5215
|
+
testResults: result.testResults,
|
|
5216
|
+
errors: result.unhandledErrors ?? [],
|
|
5217
|
+
testPaths: result.results.map((r)=>r.testPath),
|
|
5218
|
+
duration: {
|
|
5219
|
+
buildTime: result.duration.buildTime,
|
|
5220
|
+
testTime: result.duration.testTime
|
|
5221
|
+
},
|
|
5222
|
+
coverage: {
|
|
5223
|
+
map: map?.toJSON()
|
|
5224
|
+
},
|
|
5225
|
+
resolveSourcemap: result.resolveSourcemap
|
|
5226
|
+
};
|
|
5227
|
+
};
|
|
5228
|
+
return {
|
|
5229
|
+
name: 'browser',
|
|
5230
|
+
projects,
|
|
5231
|
+
async init () {},
|
|
5232
|
+
async runCycle (opts) {
|
|
5233
|
+
const cycle = runBrowserController(context, {
|
|
5234
|
+
projects,
|
|
5235
|
+
shardedEntries: opts.shardedEntries,
|
|
5236
|
+
freezeShardedEntries,
|
|
5237
|
+
allowEmptyRun,
|
|
5238
|
+
appliedModifyRstestConfigEnvironments,
|
|
5239
|
+
onTraceEvents: opts.onTraceEvents,
|
|
5240
|
+
env: opts.env
|
|
5241
|
+
});
|
|
5242
|
+
inFlightCycle = cycle;
|
|
5243
|
+
try {
|
|
5244
|
+
const result = await cycle;
|
|
5245
|
+
deferredClose = result?.close;
|
|
5246
|
+
return foldOutcome(result);
|
|
5247
|
+
} finally{
|
|
5248
|
+
inFlightCycle = void 0;
|
|
5249
|
+
}
|
|
5250
|
+
},
|
|
5251
|
+
async collect (opts) {
|
|
5252
|
+
const pending = listBrowserTests(context, {
|
|
5253
|
+
projects,
|
|
5254
|
+
shardedEntries: opts.shardedEntries,
|
|
5255
|
+
freezeShardedEntries,
|
|
5256
|
+
filesOnly,
|
|
5257
|
+
appliedModifyRstestConfigEnvironments
|
|
5258
|
+
});
|
|
5259
|
+
inFlightCycle = pending;
|
|
5260
|
+
try {
|
|
5261
|
+
const { list, close } = await pending;
|
|
5262
|
+
deferredClose = close;
|
|
5263
|
+
return {
|
|
5264
|
+
list
|
|
5265
|
+
};
|
|
5266
|
+
} finally{
|
|
5267
|
+
inFlightCycle = void 0;
|
|
5268
|
+
}
|
|
5269
|
+
},
|
|
5270
|
+
async close () {
|
|
5271
|
+
if (inFlightCycle) await inFlightCycle.catch(()=>void 0);
|
|
5272
|
+
const close = deferredClose;
|
|
5273
|
+
deferredClose = void 0;
|
|
5274
|
+
await close?.();
|
|
5275
|
+
}
|
|
5276
|
+
};
|
|
5277
|
+
}
|
|
5009
5278
|
async function runBrowserTests(context, options) {
|
|
5010
5279
|
return runBrowserController(context, options);
|
|
5011
5280
|
}
|
|
5012
5281
|
async function src_listBrowserTests(context, options) {
|
|
5013
5282
|
return listBrowserTests(context, options);
|
|
5014
5283
|
}
|
|
5015
|
-
export { runBrowserTests, src_listBrowserTests as listBrowserTests, validateBrowserConfig };
|
|
5284
|
+
export { createBrowserExecutor, runBrowserTests, src_listBrowserTests as listBrowserTests, validateBrowserConfig };
|