@rstest/browser 0.11.1 → 0.11.2
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 → 117.3da95caa39.js} +1006 -1006
- package/dist/browser-container/container-static/js/117.3da95caa39.js.LICENSE.txt +1 -0
- package/dist/browser-container/container-static/js/{index.c8fe4ff310.js → index.6e4da8c9ab.js} +9 -9
- 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 +539 -292
- package/dist/protocol.d.ts +2 -2
- package/package.json +4 -4
- package/src/AGENTS.md +17 -1
- package/src/browserExecutor.ts +138 -0
- package/src/client/AGENTS.md +20 -13
- package/src/client/entry.ts +62 -2
- package/src/client/snapshot.ts +1 -2
- package/src/concurrency.ts +42 -25
- package/src/configValidation.ts +182 -1
- package/src/hostController.ts +476 -337
- 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/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import { DEFAULT_TEST_TIMEOUT, PhaseTracker, RSTEST_ENV_SYMBOL_KEY, browserIgnoredRuntimeConfigKeys, buildBrowserCoverageMap, color, createCoverageProvider, createRunnerEventSink, createSilentConsoleController, getNoTestFilesMessage, getNumCpus as browser_getNumCpus, getSetupFiles, getTestEntries, hasUserRstestConfigPlugins, initModifyRstestConfigHooks, isDebug, isTTY, loadCoverageProvider, logger, 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),
|
|
@@ -3399,7 +3431,7 @@ const registerWatchCleanup = ()=>{
|
|
|
3399
3431
|
});
|
|
3400
3432
|
watchContext.cleanupRegistered = true;
|
|
3401
3433
|
};
|
|
3402
|
-
const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless })=>{
|
|
3434
|
+
const createBrowserRuntime = async ({ context, projectEntries: initialProjectEntries, browserProjects, shardedEntries, freezeShardedEntries, tempDir, isWatchMode, onTriggerRerun, containerDistPath, containerDevServer, forceHeadless, skipProviderLaunch, appliedModifyRstestConfigEnvironments })=>{
|
|
3403
3435
|
const containerHtmlTemplate = containerDistPath ? await promises.readFile(join(containerDistPath, 'index.html'), 'utf-8') : null;
|
|
3404
3436
|
let injectedContainerHtml = null;
|
|
3405
3437
|
let serializedOptions = 'null';
|
|
@@ -3408,8 +3440,57 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3408
3440
|
serializedOptions = serializeForInlineScript(options);
|
|
3409
3441
|
if (containerHtmlTemplate) injectedContainerHtml = containerHtmlTemplate.replace(OPTIONS_PLACEHOLDER, serializedOptions);
|
|
3410
3442
|
};
|
|
3411
|
-
|
|
3412
|
-
|
|
3443
|
+
let browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3444
|
+
let projectEntries = initialProjectEntries;
|
|
3445
|
+
const manifestModules = [];
|
|
3446
|
+
const createRuntimeWithoutProvider = ()=>{
|
|
3447
|
+
const firstProject = browserProjects[0];
|
|
3448
|
+
return {
|
|
3449
|
+
projectServers: new Map(),
|
|
3450
|
+
containerServer: {
|
|
3451
|
+
projectName: firstProject.name,
|
|
3452
|
+
environmentName: firstProject.environmentName,
|
|
3453
|
+
rsbuildInstance: void 0,
|
|
3454
|
+
devServer: {
|
|
3455
|
+
close: async ()=>void 0
|
|
3456
|
+
},
|
|
3457
|
+
port: 0,
|
|
3458
|
+
manifestPath: ''
|
|
3459
|
+
},
|
|
3460
|
+
browser: void 0,
|
|
3461
|
+
browserLaunchOptions,
|
|
3462
|
+
wsPort: 0,
|
|
3463
|
+
tempDir,
|
|
3464
|
+
setContainerOptions,
|
|
3465
|
+
dispatchHandlers,
|
|
3466
|
+
wss: void 0,
|
|
3467
|
+
projectEntries
|
|
3468
|
+
};
|
|
3469
|
+
};
|
|
3470
|
+
const getProjectEntry = (project)=>projectEntries.find((item)=>item.project.environmentName === project.environmentName);
|
|
3471
|
+
const refreshManifestModule = (manifestModule)=>{
|
|
3472
|
+
const entry = getProjectEntry(manifestModule.project);
|
|
3473
|
+
manifestModule.modules[manifestModule.manifestPath] = generateManifestModule({
|
|
3474
|
+
manifestPath: manifestModule.manifestPath,
|
|
3475
|
+
entries: [
|
|
3476
|
+
{
|
|
3477
|
+
project: manifestModule.project,
|
|
3478
|
+
testFiles: entry?.testFiles ?? [],
|
|
3479
|
+
setupFiles: entry?.setupFiles ?? []
|
|
3480
|
+
}
|
|
3481
|
+
],
|
|
3482
|
+
isWatchMode
|
|
3483
|
+
});
|
|
3484
|
+
};
|
|
3485
|
+
const refreshProjectEntries = async ()=>{
|
|
3486
|
+
validateBrowserConfig(context);
|
|
3487
|
+
browserLaunchOptions = ensureConsistentBrowserLaunchOptions(browserProjects);
|
|
3488
|
+
const updatedShardedEntries = freezeShardedEntries ? shardedEntries : context.normalizedConfig.shard ? await resolveShardedEntries(context, {
|
|
3489
|
+
silent: true
|
|
3490
|
+
}) : shardedEntries;
|
|
3491
|
+
projectEntries = await resolveProjectEntries(context, updatedShardedEntries, browserProjects);
|
|
3492
|
+
for (const manifestModule of manifestModules)refreshManifestModule(manifestModule);
|
|
3493
|
+
};
|
|
3413
3494
|
const browserRuntimePath = fileURLToPath(import.meta.resolve('@rstest/core/internal/browser-runtime'));
|
|
3414
3495
|
const staticRstestAliases = {
|
|
3415
3496
|
'@rstest/core': resolveBrowserFile('client/public.ts'),
|
|
@@ -3454,26 +3535,27 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3454
3535
|
return false;
|
|
3455
3536
|
}
|
|
3456
3537
|
};
|
|
3457
|
-
const entryByEnvironmentName = new Map(projectEntries.map((entry)=>[
|
|
3458
|
-
entry.project.environmentName,
|
|
3459
|
-
entry
|
|
3460
|
-
]));
|
|
3461
3538
|
const buildProjectServer = async (project, isContainerServer)=>{
|
|
3462
3539
|
const manifestPath = join(tempDir, toSafeVarName(project.environmentName), VIRTUAL_MANIFEST_FILENAME);
|
|
3463
|
-
const entry =
|
|
3464
|
-
const
|
|
3540
|
+
const entry = getProjectEntry(project);
|
|
3541
|
+
const virtualManifestModules = {
|
|
3542
|
+
[manifestPath]: generateManifestModule({
|
|
3543
|
+
manifestPath,
|
|
3544
|
+
entries: [
|
|
3545
|
+
{
|
|
3546
|
+
project,
|
|
3547
|
+
testFiles: entry?.testFiles ?? [],
|
|
3548
|
+
setupFiles: entry?.setupFiles ?? []
|
|
3549
|
+
}
|
|
3550
|
+
],
|
|
3551
|
+
isWatchMode
|
|
3552
|
+
})
|
|
3553
|
+
};
|
|
3554
|
+
const virtualManifestPlugin = new rspack.experiments.VirtualModulesPlugin(virtualManifestModules);
|
|
3555
|
+
manifestModules.push({
|
|
3465
3556
|
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
|
|
3557
|
+
project,
|
|
3558
|
+
modules: virtualManifestModules
|
|
3477
3559
|
});
|
|
3478
3560
|
const rstestInternalAliases = {
|
|
3479
3561
|
'@rstest/browser-manifest': manifestPath,
|
|
@@ -3482,11 +3564,10 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3482
3564
|
const isHeadless = forceHeadless || project.normalizedConfig.browser.headless;
|
|
3483
3565
|
const enableHmr = shouldEnableBrowserHmr(isWatchMode, isHeadless);
|
|
3484
3566
|
const rsbuildInstance = await createRsbuild({
|
|
3485
|
-
callerName: 'rstest
|
|
3567
|
+
callerName: 'rstest',
|
|
3486
3568
|
rsbuildConfig: {
|
|
3487
3569
|
root: context.rootPath,
|
|
3488
3570
|
mode: 'development',
|
|
3489
|
-
plugins: project.normalizedConfig.plugins || [],
|
|
3490
3571
|
server: {
|
|
3491
3572
|
printUrls: false,
|
|
3492
3573
|
port: project.normalizedConfig.browser.port ?? (isContainerServer ? 4000 : 0),
|
|
@@ -3494,10 +3575,19 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3494
3575
|
},
|
|
3495
3576
|
dev: createBrowserRsbuildDevConfig(enableHmr),
|
|
3496
3577
|
environments: {
|
|
3497
|
-
[project.environmentName]:
|
|
3578
|
+
[project.environmentName]: getBrowserRsbuildEnvironmentConfig(project)
|
|
3498
3579
|
}
|
|
3499
3580
|
}
|
|
3500
3581
|
});
|
|
3582
|
+
initModifyRstestConfigHooks(context, rsbuildInstance, [
|
|
3583
|
+
project
|
|
3584
|
+
], [
|
|
3585
|
+
project
|
|
3586
|
+
], {
|
|
3587
|
+
getEnvironmentConfig: getBrowserRsbuildEnvironmentConfig,
|
|
3588
|
+
onModifyRstestConfigApplied: refreshProjectEntries,
|
|
3589
|
+
appliedEnvironmentNames: appliedModifyRstestConfigEnvironments
|
|
3590
|
+
});
|
|
3501
3591
|
rsbuildInstance.addPlugins([
|
|
3502
3592
|
{
|
|
3503
3593
|
name: 'rstest:browser-user-config',
|
|
@@ -3593,6 +3683,21 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3593
3683
|
}
|
|
3594
3684
|
}
|
|
3595
3685
|
]);
|
|
3686
|
+
if (skipProviderLaunch) {
|
|
3687
|
+
await rsbuildInstance.initConfigs({
|
|
3688
|
+
action: 'dev'
|
|
3689
|
+
});
|
|
3690
|
+
return {
|
|
3691
|
+
projectName: project.name,
|
|
3692
|
+
environmentName: project.environmentName,
|
|
3693
|
+
rsbuildInstance,
|
|
3694
|
+
devServer: {
|
|
3695
|
+
close: async ()=>void 0
|
|
3696
|
+
},
|
|
3697
|
+
port: 0,
|
|
3698
|
+
manifestPath
|
|
3699
|
+
};
|
|
3700
|
+
}
|
|
3596
3701
|
const coverage = project.normalizedConfig.coverage;
|
|
3597
3702
|
if (coverage?.enabled && 'list' !== context.command) {
|
|
3598
3703
|
const { pluginCoverage } = await loadCoverageProvider(coverage, context.rootPath);
|
|
@@ -3688,6 +3793,7 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3688
3793
|
await closeAllProjectServers(projectServers.values());
|
|
3689
3794
|
throw error;
|
|
3690
3795
|
}
|
|
3796
|
+
if (skipProviderLaunch) return createRuntimeWithoutProvider();
|
|
3691
3797
|
const containerServer = projectServers.get(browserProjects[0].name);
|
|
3692
3798
|
const wss = new WebSocketServer({
|
|
3693
3799
|
port: 0
|
|
@@ -3715,7 +3821,8 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3715
3821
|
tempDir,
|
|
3716
3822
|
setContainerOptions,
|
|
3717
3823
|
dispatchHandlers,
|
|
3718
|
-
wss
|
|
3824
|
+
wss,
|
|
3825
|
+
projectEntries
|
|
3719
3826
|
};
|
|
3720
3827
|
} catch (error) {
|
|
3721
3828
|
wss.close();
|
|
@@ -3723,9 +3830,8 @@ const createBrowserRuntime = async ({ context, projectEntries, tempDir, isWatchM
|
|
|
3723
3830
|
throw error;
|
|
3724
3831
|
}
|
|
3725
3832
|
};
|
|
3726
|
-
async function resolveProjectEntries(context, shardedEntries) {
|
|
3833
|
+
async function resolveProjectEntries(context, shardedEntries, browserProjects) {
|
|
3727
3834
|
if (shardedEntries) {
|
|
3728
|
-
const browserProjects = getBrowserProjects(context);
|
|
3729
3835
|
const projectEntries = [];
|
|
3730
3836
|
for (const project of browserProjects){
|
|
3731
3837
|
const entryInfo = shardedEntries.get(project.environmentName);
|
|
@@ -3740,14 +3846,14 @@ async function resolveProjectEntries(context, shardedEntries) {
|
|
|
3740
3846
|
}
|
|
3741
3847
|
return projectEntries;
|
|
3742
3848
|
}
|
|
3743
|
-
return collectProjectEntries(context);
|
|
3849
|
+
return collectProjectEntries(context, browserProjects);
|
|
3744
3850
|
}
|
|
3745
3851
|
const runBrowserController = async (context, options)=>{
|
|
3746
|
-
const {
|
|
3852
|
+
const { allowEmptyWatchRun = false, allowEmptyRun = false, filesOnly = false, onTraceEvents, env } = options ?? {};
|
|
3747
3853
|
const buildStart = Date.now();
|
|
3748
3854
|
const isWatchMode = 'watch' === context.command;
|
|
3749
3855
|
const phaseTrackers = onTraceEvents ? new Map() : void 0;
|
|
3750
|
-
const browserProjects = getBrowserProjects(context);
|
|
3856
|
+
const browserProjects = options?.projects ?? getBrowserProjects(context);
|
|
3751
3857
|
const useHeadlessDirect = browserProjects.every((project)=>project.normalizedConfig.browser.headless);
|
|
3752
3858
|
const browserSourceMapCache = new Map();
|
|
3753
3859
|
const isHttpLikeFile = (file)=>/^https?:\/\//.test(file);
|
|
@@ -3795,7 +3901,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
3795
3901
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
3796
3902
|
close
|
|
3797
3903
|
};
|
|
3798
|
-
if (
|
|
3904
|
+
if (isWatchMode) for (const reporter of context.reporters)await reporter.onTestRunEnd?.({
|
|
3799
3905
|
results: [],
|
|
3800
3906
|
testResults: [],
|
|
3801
3907
|
duration: errorResult.duration,
|
|
@@ -3807,9 +3913,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
3807
3913
|
};
|
|
3808
3914
|
const toError = (error)=>error instanceof Error ? error : new Error(String(error));
|
|
3809
3915
|
const failWithError = async (error, cleanup)=>{
|
|
3810
|
-
ensureProcessExitCode(1);
|
|
3916
|
+
if (isWatchMode) ensureProcessExitCode(1);
|
|
3811
3917
|
const normalizedError = toError(error);
|
|
3812
|
-
if (cleanup &&
|
|
3918
|
+
if (cleanup && !isWatchMode) return buildErrorResult(normalizedError, cleanup);
|
|
3813
3919
|
try {
|
|
3814
3920
|
return await buildErrorResult(normalizedError);
|
|
3815
3921
|
} finally{
|
|
@@ -3821,13 +3927,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3821
3927
|
return previous.map((file)=>file.testPath).filter((testPath)=>!currentPathSet.has(testPath));
|
|
3822
3928
|
};
|
|
3823
3929
|
const notifyTestRunStart = async ()=>{
|
|
3824
|
-
if (
|
|
3930
|
+
if (!isWatchMode) return;
|
|
3825
3931
|
for (const reporter of context.reporters)await reporter.onTestRunStart?.();
|
|
3826
3932
|
};
|
|
3827
3933
|
const coverageConfig = browserProjects.find((project)=>project.normalizedConfig.coverage?.enabled)?.normalizedConfig.coverage;
|
|
3828
3934
|
const coverageProvider = coverageConfig?.enabled ? await createCoverageProvider(coverageConfig, context.rootPath) : null;
|
|
3829
3935
|
const notifyTestRunEnd = async ({ duration, unhandledErrors, filterRerunTestPaths })=>{
|
|
3830
|
-
if (
|
|
3936
|
+
if (!isWatchMode) return;
|
|
3831
3937
|
let mergedCoverage;
|
|
3832
3938
|
if (coverageProvider) {
|
|
3833
3939
|
const coverageMap = coverageProvider.createCoverageMap();
|
|
@@ -3867,12 +3973,28 @@ const runBrowserController = async (context, options)=>{
|
|
|
3867
3973
|
return failWithError(error);
|
|
3868
3974
|
}
|
|
3869
3975
|
}
|
|
3870
|
-
|
|
3871
|
-
|
|
3976
|
+
let projectEntries = await resolveProjectEntries(context, options?.shardedEntries, browserProjects);
|
|
3977
|
+
let totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
3872
3978
|
const shouldKeepWatchingWithEmptySet = isWatchMode && allowEmptyWatchRun;
|
|
3873
|
-
|
|
3979
|
+
const shouldInitializeEmptyBrowserHooks = 0 === totalTests && hasUserRstestConfigPlugins(browserProjects);
|
|
3980
|
+
const createEmptyRunResult = ()=>{
|
|
3981
|
+
const elapsed = Math.max(0, Date.now() - buildStart);
|
|
3982
|
+
return {
|
|
3983
|
+
results: [],
|
|
3984
|
+
testResults: [],
|
|
3985
|
+
duration: {
|
|
3986
|
+
totalTime: elapsed,
|
|
3987
|
+
buildTime: elapsed,
|
|
3988
|
+
testTime: 0
|
|
3989
|
+
},
|
|
3990
|
+
hasFailure: false,
|
|
3991
|
+
getSourcemap: getBrowserSourcemap,
|
|
3992
|
+
resolveSourcemap: resolveBrowserSourcemap
|
|
3993
|
+
};
|
|
3994
|
+
};
|
|
3995
|
+
const reportEmptyTestSet = ()=>{
|
|
3874
3996
|
const code = context.normalizedConfig.passWithNoTests ? 0 : 1;
|
|
3875
|
-
if (!
|
|
3997
|
+
if (isWatchMode || !allowEmptyRun) {
|
|
3876
3998
|
const message = shouldKeepWatchingWithEmptySet ? 'No test files found.' : getNoTestFilesMessage({
|
|
3877
3999
|
context,
|
|
3878
4000
|
code,
|
|
@@ -3883,10 +4005,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3883
4005
|
if (context.relatedFilters?.length) logger.log(color.gray('related: '), context.relatedFilters.join(color.gray(', ')));
|
|
3884
4006
|
else if (context.fileFilters?.length) logger.log(color.gray('filter: '), context.fileFilters.join(color.gray(', ')));
|
|
3885
4007
|
}
|
|
3886
|
-
if (0 !== code && !shouldKeepWatchingWithEmptySet) ensureProcessExitCode(code);
|
|
3887
|
-
|
|
4008
|
+
if (isWatchMode && 0 !== code && !shouldKeepWatchingWithEmptySet && !allowEmptyRun) ensureProcessExitCode(code);
|
|
4009
|
+
return !shouldKeepWatchingWithEmptySet;
|
|
4010
|
+
};
|
|
4011
|
+
if (0 === totalTests && !shouldInitializeEmptyBrowserHooks) {
|
|
4012
|
+
if (reportEmptyTestSet()) return allowEmptyRun ? createEmptyRunResult() : void 0;
|
|
3888
4013
|
}
|
|
3889
|
-
await notifyTestRunStart();
|
|
4014
|
+
if (!filesOnly) await notifyTestRunStart();
|
|
3890
4015
|
const enableCliShortcuts = isWatchMode && isBrowserWatchCliShortcutsEnabled();
|
|
3891
4016
|
const browserTempOutputRoot = context.normalizedConfig.output.distPath.root;
|
|
3892
4017
|
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 +4023,18 @@ const runBrowserController = async (context, options)=>{
|
|
|
3898
4023
|
runtime = await createBrowserRuntime({
|
|
3899
4024
|
context,
|
|
3900
4025
|
projectEntries,
|
|
4026
|
+
browserProjects,
|
|
4027
|
+
shardedEntries: options?.shardedEntries,
|
|
4028
|
+
freezeShardedEntries: options?.freezeShardedEntries,
|
|
3901
4029
|
tempDir,
|
|
3902
4030
|
isWatchMode,
|
|
3903
4031
|
onTriggerRerun: isWatchMode ? async ()=>{
|
|
3904
4032
|
await triggerRerun?.();
|
|
3905
4033
|
} : void 0,
|
|
3906
4034
|
containerDistPath,
|
|
3907
|
-
containerDevServer
|
|
4035
|
+
containerDevServer,
|
|
4036
|
+
skipProviderLaunch: filesOnly,
|
|
4037
|
+
appliedModifyRstestConfigEnvironments: options?.appliedModifyRstestConfigEnvironments
|
|
3908
4038
|
});
|
|
3909
4039
|
} catch (error) {
|
|
3910
4040
|
return failWithError(error, async ()=>{
|
|
@@ -3922,8 +4052,27 @@ const runBrowserController = async (context, options)=>{
|
|
|
3922
4052
|
});
|
|
3923
4053
|
}
|
|
3924
4054
|
}
|
|
3925
|
-
|
|
4055
|
+
projectEntries = runtime.projectEntries;
|
|
4056
|
+
totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
3926
4057
|
const buildTime = Date.now() - buildStart;
|
|
4058
|
+
if (filesOnly) return {
|
|
4059
|
+
results: [],
|
|
4060
|
+
testResults: [],
|
|
4061
|
+
duration: {
|
|
4062
|
+
totalTime: buildTime,
|
|
4063
|
+
buildTime,
|
|
4064
|
+
testTime: 0
|
|
4065
|
+
},
|
|
4066
|
+
hasFailure: false,
|
|
4067
|
+
getSourcemap: getBrowserSourcemap,
|
|
4068
|
+
resolveSourcemap: resolveBrowserSourcemap,
|
|
4069
|
+
close: ()=>destroyBrowserRuntime(runtime)
|
|
4070
|
+
};
|
|
4071
|
+
if (0 === totalTests && reportEmptyTestSet()) {
|
|
4072
|
+
await destroyBrowserRuntime(runtime);
|
|
4073
|
+
return allowEmptyRun ? createEmptyRunResult() : void 0;
|
|
4074
|
+
}
|
|
4075
|
+
const { browser, browserLaunchOptions, wsPort, wss } = runtime;
|
|
3927
4076
|
const allTestFiles = projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
3928
4077
|
testPath: normalize(testPath),
|
|
3929
4078
|
projectName: entry.project.name
|
|
@@ -3932,10 +4081,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
3932
4081
|
name: project.name,
|
|
3933
4082
|
environmentName: project.environmentName,
|
|
3934
4083
|
projectRoot: normalize(project.rootPath),
|
|
3935
|
-
runtimeConfig: serializableConfig(
|
|
4084
|
+
runtimeConfig: serializableConfig(projectRuntimeConfig(project, {
|
|
4085
|
+
envMode: 'static',
|
|
4086
|
+
envOverlay: env
|
|
4087
|
+
})),
|
|
3936
4088
|
viewport: project.normalizedConfig.browser.viewport
|
|
3937
4089
|
}));
|
|
3938
|
-
const maxTestTimeoutForRpc =
|
|
4090
|
+
const maxTestTimeoutForRpc = getMaxTestTimeoutForRpc(browserProjects);
|
|
3939
4091
|
const projectRunnerUrls = Object.fromEntries([
|
|
3940
4092
|
...runtime.projectServers
|
|
3941
4093
|
].map(([name, server])=>[
|
|
@@ -3996,11 +4148,28 @@ const runBrowserController = async (context, options)=>{
|
|
|
3996
4148
|
const reporterResults = [];
|
|
3997
4149
|
const caseResults = [];
|
|
3998
4150
|
let fatalError = null;
|
|
4151
|
+
const runnerSinks = new Map(browserProjects.map((project)=>[
|
|
4152
|
+
project.name,
|
|
4153
|
+
createRunnerEventSink(context, project.normalizedConfig)
|
|
4154
|
+
]));
|
|
4155
|
+
const firstBrowserSink = runnerSinks.get(browserProjects[0].name);
|
|
4156
|
+
const projectNameByTestPath = new Map();
|
|
4157
|
+
const sinkForProjectName = (projectName)=>runnerSinks.get(projectName) ?? firstBrowserSink;
|
|
4158
|
+
const sinkForTestPath = (testPath)=>{
|
|
4159
|
+
const projectName = projectNameByTestPath.get(testPath);
|
|
4160
|
+
return projectName ? sinkForProjectName(projectName) : firstBrowserSink;
|
|
4161
|
+
};
|
|
4162
|
+
const silentConsoleController = createSilentConsoleController({
|
|
4163
|
+
runtimeConfig: {
|
|
4164
|
+
silent: context.normalizedConfig.silent,
|
|
4165
|
+
disableConsoleIntercept: context.normalizedConfig.disableConsoleIntercept
|
|
4166
|
+
},
|
|
4167
|
+
emitInterceptedLog: (log)=>sinkForTestPath(log.testPath).onConsoleLog(log),
|
|
4168
|
+
writeOriginalLog: ()=>{}
|
|
4169
|
+
});
|
|
3999
4170
|
const snapshotRpcMethods = {
|
|
4000
4171
|
async resolveSnapshotPath (testPath) {
|
|
4001
|
-
|
|
4002
|
-
const resolver = context.normalizedConfig.resolveSnapshotPath || (()=>join(dirname(testPath), '__snapshots__', `${basename(testPath)}${snapExtension}`));
|
|
4003
|
-
return resolver(testPath, snapExtension);
|
|
4172
|
+
return resolveSnapshotPathDefault(testPath, context.normalizedConfig.resolveSnapshotPath);
|
|
4004
4173
|
},
|
|
4005
4174
|
async readSnapshotFile (filepath) {
|
|
4006
4175
|
try {
|
|
@@ -4023,6 +4192,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4023
4192
|
}
|
|
4024
4193
|
};
|
|
4025
4194
|
const handleTestFileStart = async (payload)=>{
|
|
4195
|
+
projectNameByTestPath.set(payload.testPath, payload.projectName);
|
|
4026
4196
|
if (phaseTrackers) {
|
|
4027
4197
|
const tracker = new PhaseTracker({
|
|
4028
4198
|
trace: {
|
|
@@ -4034,24 +4204,24 @@ const runBrowserController = async (context, options)=>{
|
|
|
4034
4204
|
tracker.transition('prepare');
|
|
4035
4205
|
phaseTrackers.set(payload.testPath, tracker);
|
|
4036
4206
|
}
|
|
4037
|
-
await
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4207
|
+
await sinkForProjectName(payload.projectName).onTestFileStart({
|
|
4208
|
+
testId: getFileTaskId(payload.testPath),
|
|
4209
|
+
testPath: payload.testPath,
|
|
4210
|
+
tests: []
|
|
4211
|
+
});
|
|
4042
4212
|
};
|
|
4043
4213
|
const handleTestFileReady = async (payload)=>{
|
|
4044
4214
|
phaseTrackers?.get(payload.testPath)?.transition('tests');
|
|
4045
|
-
await
|
|
4215
|
+
await sinkForTestPath(payload.testPath).onTestFileReady(payload);
|
|
4046
4216
|
};
|
|
4047
4217
|
const handleTestSuiteStart = async (payload)=>{
|
|
4048
4218
|
phaseTrackers?.get(payload.testPath)?.recordSuiteStart(payload);
|
|
4049
|
-
await
|
|
4219
|
+
await sinkForTestPath(payload.testPath).onTestSuiteStart(payload);
|
|
4050
4220
|
};
|
|
4051
4221
|
const handleTestSuiteResult = async (payload)=>{
|
|
4052
4222
|
phaseTrackers?.get(payload.testPath)?.recordSuiteResult(payload);
|
|
4053
|
-
await
|
|
4054
|
-
if ('passed-only' === context.normalizedConfig.silent)
|
|
4223
|
+
await sinkForTestPath(payload.testPath).onTestSuiteResult(payload);
|
|
4224
|
+
if ('passed-only' === context.normalizedConfig.silent) silentConsoleController.flushBufferedLogsForTask({
|
|
4055
4225
|
taskId: payload.testId,
|
|
4056
4226
|
status: payload.status,
|
|
4057
4227
|
taskParentNames: payload.parentNames,
|
|
@@ -4061,13 +4231,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
4061
4231
|
};
|
|
4062
4232
|
const handleTestCaseStart = async (payload)=>{
|
|
4063
4233
|
phaseTrackers?.get(payload.testPath)?.recordCaseStart(payload);
|
|
4064
|
-
|
|
4234
|
+
sinkForTestPath(payload.testPath).onTestCaseStart(payload);
|
|
4065
4235
|
};
|
|
4066
4236
|
const handleTestCaseResult = async (payload)=>{
|
|
4067
4237
|
caseResults.push(payload);
|
|
4068
4238
|
phaseTrackers?.get(payload.testPath)?.recordCaseResult(payload);
|
|
4069
|
-
await
|
|
4070
|
-
if ('passed-only' === context.normalizedConfig.silent)
|
|
4239
|
+
await sinkForTestPath(payload.testPath).onTestCaseResult(payload);
|
|
4240
|
+
if ('passed-only' === context.normalizedConfig.silent) silentConsoleController.flushBufferedLogsForTask({
|
|
4071
4241
|
taskId: payload.testId,
|
|
4072
4242
|
status: payload.status,
|
|
4073
4243
|
taskParentNames: payload.parentNames,
|
|
@@ -4080,7 +4250,6 @@ const runBrowserController = async (context, options)=>{
|
|
|
4080
4250
|
context.updateReporterResultState([
|
|
4081
4251
|
payload
|
|
4082
4252
|
], payload.results);
|
|
4083
|
-
if (payload.snapshotResult) context.snapshotManager.add(payload.snapshotResult);
|
|
4084
4253
|
if (phaseTrackers) {
|
|
4085
4254
|
const tracker = phaseTrackers.get(payload.testPath);
|
|
4086
4255
|
if (tracker) {
|
|
@@ -4090,15 +4259,15 @@ const runBrowserController = async (context, options)=>{
|
|
|
4090
4259
|
phaseTrackers.delete(payload.testPath);
|
|
4091
4260
|
}
|
|
4092
4261
|
}
|
|
4093
|
-
if ('passed-only' === context.normalizedConfig.silent)
|
|
4262
|
+
if ('passed-only' === context.normalizedConfig.silent) silentConsoleController.flushBufferedLogsForTask({
|
|
4094
4263
|
taskId: payload.testId,
|
|
4095
4264
|
status: payload.status,
|
|
4096
4265
|
taskParentNames: payload.parentNames,
|
|
4097
4266
|
taskType: 'file',
|
|
4098
4267
|
testPath: payload.testPath
|
|
4099
4268
|
});
|
|
4100
|
-
await
|
|
4101
|
-
if ('fail' === payload.status) ensureProcessExitCode(1);
|
|
4269
|
+
await sinkForTestPath(payload.testPath).onTestFileResult(payload);
|
|
4270
|
+
if (isWatchMode && 'fail' === payload.status) ensureProcessExitCode(1);
|
|
4102
4271
|
};
|
|
4103
4272
|
const handleLog = async (payload)=>{
|
|
4104
4273
|
const log = {
|
|
@@ -4112,62 +4281,13 @@ const runBrowserController = async (context, options)=>{
|
|
|
4112
4281
|
type: payload.type,
|
|
4113
4282
|
trace: payload.trace
|
|
4114
4283
|
};
|
|
4115
|
-
|
|
4116
|
-
if ('passed-only' === context.normalizedConfig.silent) return void bufferConsoleLog(log);
|
|
4117
|
-
if (context.normalizedConfig.disableConsoleIntercept) return;
|
|
4118
|
-
await emitUserConsoleLog(log);
|
|
4284
|
+
silentConsoleController.onConsoleLog(log);
|
|
4119
4285
|
};
|
|
4120
4286
|
const handleFatal = async (payload)=>{
|
|
4121
4287
|
const error = new Error(payload.message);
|
|
4122
4288
|
error.stack = payload.stack;
|
|
4123
4289
|
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
|
-
}
|
|
4290
|
+
if (isWatchMode) ensureProcessExitCode(1);
|
|
4171
4291
|
};
|
|
4172
4292
|
const runSnapshotRpc = async (request)=>{
|
|
4173
4293
|
switch(request.method){
|
|
@@ -4366,6 +4486,14 @@ const runBrowserController = async (context, options)=>{
|
|
|
4366
4486
|
await closeContextSafely(browserContext);
|
|
4367
4487
|
}
|
|
4368
4488
|
};
|
|
4489
|
+
const makeSkippedFileResult = (file)=>({
|
|
4490
|
+
testId: getFileTaskId(file.testPath),
|
|
4491
|
+
status: 'skip',
|
|
4492
|
+
name: '',
|
|
4493
|
+
testPath: file.testPath,
|
|
4494
|
+
project: file.projectName,
|
|
4495
|
+
results: []
|
|
4496
|
+
});
|
|
4369
4497
|
const runFilesWithPool = async (files)=>{
|
|
4370
4498
|
if (0 === files.length) return;
|
|
4371
4499
|
const previous = runLifecycle.activeSession;
|
|
@@ -4378,8 +4506,17 @@ const runBrowserController = async (context, options)=>{
|
|
|
4378
4506
|
...files
|
|
4379
4507
|
];
|
|
4380
4508
|
const concurrency = getHeadlessConcurrency(context, queue.length);
|
|
4509
|
+
const bail = context.normalizedConfig.bail;
|
|
4381
4510
|
const worker = async ()=>{
|
|
4382
4511
|
while(queue.length > 0 && !run.cancelled && runLifecycle.isTokenActive(run.token)){
|
|
4512
|
+
if (bail && context.stateManager.getCountOfFailedTests() >= bail) {
|
|
4513
|
+
let skipped = queue.shift();
|
|
4514
|
+
while(skipped){
|
|
4515
|
+
await handleTestFileComplete(makeSkippedFileResult(skipped));
|
|
4516
|
+
skipped = queue.shift();
|
|
4517
|
+
}
|
|
4518
|
+
return;
|
|
4519
|
+
}
|
|
4383
4520
|
const next = queue.shift();
|
|
4384
4521
|
if (!next) return;
|
|
4385
4522
|
await runSingleFile(run, next);
|
|
@@ -4401,6 +4538,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4401
4538
|
await cancelRun(run, false);
|
|
4402
4539
|
},
|
|
4403
4540
|
runFiles: async (files)=>{
|
|
4541
|
+
prepareWatchRerunState(context);
|
|
4404
4542
|
await notifyTestRunStart();
|
|
4405
4543
|
const rerunStartTime = Date.now();
|
|
4406
4544
|
const fatalErrorBeforeRun = fatalError;
|
|
@@ -4453,12 +4591,12 @@ const runBrowserController = async (context, options)=>{
|
|
|
4453
4591
|
hasFailure: false,
|
|
4454
4592
|
getSourcemap: getBrowserSourcemap,
|
|
4455
4593
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4456
|
-
close:
|
|
4594
|
+
close: isWatchMode ? void 0 : async ()=>{
|
|
4457
4595
|
sessionRegistry.clear();
|
|
4458
4596
|
await destroyBrowserRuntime(runtime);
|
|
4459
|
-
}
|
|
4597
|
+
}
|
|
4460
4598
|
};
|
|
4461
|
-
if (
|
|
4599
|
+
if (isWatchMode) await notifyTestRunEnd({
|
|
4462
4600
|
duration
|
|
4463
4601
|
});
|
|
4464
4602
|
if (isWatchMode) {
|
|
@@ -4533,7 +4671,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4533
4671
|
};
|
|
4534
4672
|
context.updateReporterResultState(reporterResults, caseResults);
|
|
4535
4673
|
const isFailure = reporterResults.some((result)=>'fail' === result.status);
|
|
4536
|
-
if (isFailure) ensureProcessExitCode(1);
|
|
4674
|
+
if (isWatchMode && isFailure) ensureProcessExitCode(1);
|
|
4537
4675
|
const result = {
|
|
4538
4676
|
results: reporterResults,
|
|
4539
4677
|
testResults: caseResults,
|
|
@@ -4541,9 +4679,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
4541
4679
|
hasFailure: isFailure,
|
|
4542
4680
|
getSourcemap: getBrowserSourcemap,
|
|
4543
4681
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4544
|
-
close:
|
|
4682
|
+
close: closeHeadlessRuntime
|
|
4545
4683
|
};
|
|
4546
|
-
if (
|
|
4684
|
+
if (isWatchMode) try {
|
|
4547
4685
|
await notifyTestRunEnd({
|
|
4548
4686
|
duration
|
|
4549
4687
|
});
|
|
@@ -4754,7 +4892,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4754
4892
|
}
|
|
4755
4893
|
} catch (error) {
|
|
4756
4894
|
fatalError = fatalError ?? toError(error);
|
|
4757
|
-
ensureProcessExitCode(1);
|
|
4895
|
+
if (isWatchMode) ensureProcessExitCode(1);
|
|
4758
4896
|
}
|
|
4759
4897
|
testTime = Date.now() - testStart;
|
|
4760
4898
|
}
|
|
@@ -4781,6 +4919,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4781
4919
|
}
|
|
4782
4920
|
if (rerunPlan.normalizedAffectedTestFiles.length > 0) {
|
|
4783
4921
|
logger.log(color.cyan(`Re-running ${rerunPlan.normalizedAffectedTestFiles.length} affected test file(s)...\n`));
|
|
4922
|
+
prepareWatchRerunState(context);
|
|
4784
4923
|
await notifyTestRunStart();
|
|
4785
4924
|
const rerunStartTime = Date.now();
|
|
4786
4925
|
const fatalErrorBeforeRun = fatalError;
|
|
@@ -4831,7 +4970,7 @@ const runBrowserController = async (context, options)=>{
|
|
|
4831
4970
|
};
|
|
4832
4971
|
context.updateReporterResultState(reporterResults, caseResults);
|
|
4833
4972
|
const isFailure = reporterResults.some((result)=>'fail' === result.status);
|
|
4834
|
-
if (isFailure) ensureProcessExitCode(1);
|
|
4973
|
+
if (isWatchMode && isFailure) ensureProcessExitCode(1);
|
|
4835
4974
|
const result = {
|
|
4836
4975
|
results: reporterResults,
|
|
4837
4976
|
testResults: caseResults,
|
|
@@ -4839,9 +4978,9 @@ const runBrowserController = async (context, options)=>{
|
|
|
4839
4978
|
hasFailure: isFailure,
|
|
4840
4979
|
getSourcemap: getBrowserSourcemap,
|
|
4841
4980
|
resolveSourcemap: resolveBrowserSourcemap,
|
|
4842
|
-
close:
|
|
4981
|
+
close: closeContainerRuntime
|
|
4843
4982
|
};
|
|
4844
|
-
if (
|
|
4983
|
+
if (isWatchMode) try {
|
|
4845
4984
|
await notifyTestRunEnd({
|
|
4846
4985
|
duration
|
|
4847
4986
|
});
|
|
@@ -4855,24 +4994,29 @@ const runBrowserController = async (context, options)=>{
|
|
|
4855
4994
|
return result;
|
|
4856
4995
|
};
|
|
4857
4996
|
const listBrowserTests = async (context, options)=>{
|
|
4858
|
-
const
|
|
4997
|
+
const browserProjects = options?.projects ?? getBrowserProjects(context);
|
|
4998
|
+
const projectEntries = await resolveProjectEntries(context, options?.shardedEntries, browserProjects);
|
|
4859
4999
|
const totalTests = projectEntries.reduce((total, item)=>total + item.testFiles.length, 0);
|
|
4860
|
-
if (0 === totalTests) return {
|
|
5000
|
+
if (0 === totalTests && !hasUserRstestConfigPlugins(browserProjects)) return {
|
|
4861
5001
|
list: [],
|
|
4862
5002
|
close: async ()=>{}
|
|
4863
5003
|
};
|
|
4864
5004
|
const tempDir = join(context.rootPath, context.normalizedConfig.output.distPath.root, 'browser', `list-${Date.now()}`);
|
|
4865
|
-
const browserProjects = getBrowserProjects(context);
|
|
4866
5005
|
let runtime;
|
|
4867
5006
|
try {
|
|
4868
5007
|
runtime = await createBrowserRuntime({
|
|
4869
5008
|
context,
|
|
4870
5009
|
projectEntries,
|
|
5010
|
+
browserProjects,
|
|
5011
|
+
shardedEntries: options?.shardedEntries,
|
|
5012
|
+
freezeShardedEntries: options?.freezeShardedEntries,
|
|
4871
5013
|
tempDir,
|
|
4872
5014
|
isWatchMode: false,
|
|
4873
5015
|
containerDistPath: void 0,
|
|
4874
5016
|
containerDevServer: void 0,
|
|
4875
|
-
forceHeadless: true
|
|
5017
|
+
forceHeadless: true,
|
|
5018
|
+
skipProviderLaunch: options?.filesOnly,
|
|
5019
|
+
appliedModifyRstestConfigEnvironments: options?.appliedModifyRstestConfigEnvironments
|
|
4876
5020
|
});
|
|
4877
5021
|
} catch (error) {
|
|
4878
5022
|
const providers = [
|
|
@@ -4881,15 +5025,36 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4881
5025
|
logger.error(color.red(`Failed to initialize browser provider runtime (${providers.join(', ')}).`), error);
|
|
4882
5026
|
throw error;
|
|
4883
5027
|
}
|
|
5028
|
+
if (options?.filesOnly) {
|
|
5029
|
+
const list = runtime.projectEntries.flatMap((entry)=>entry.testFiles.map((testPath)=>({
|
|
5030
|
+
testPath,
|
|
5031
|
+
project: entry.project.name,
|
|
5032
|
+
tests: []
|
|
5033
|
+
})));
|
|
5034
|
+
await destroyBrowserRuntime(runtime);
|
|
5035
|
+
return {
|
|
5036
|
+
list,
|
|
5037
|
+
close: async ()=>{}
|
|
5038
|
+
};
|
|
5039
|
+
}
|
|
5040
|
+
if (!runtime.projectEntries.some((entry)=>entry.testFiles.length > 0)) {
|
|
5041
|
+
await destroyBrowserRuntime(runtime);
|
|
5042
|
+
return {
|
|
5043
|
+
list: [],
|
|
5044
|
+
close: async ()=>{}
|
|
5045
|
+
};
|
|
5046
|
+
}
|
|
4884
5047
|
const { browser, browserLaunchOptions } = runtime;
|
|
4885
5048
|
const projectRuntimeConfigs = browserProjects.map((project)=>({
|
|
4886
5049
|
name: project.name,
|
|
4887
5050
|
environmentName: project.environmentName,
|
|
4888
5051
|
projectRoot: normalize(project.rootPath),
|
|
4889
|
-
runtimeConfig: serializableConfig(
|
|
5052
|
+
runtimeConfig: serializableConfig(projectRuntimeConfig(project, {
|
|
5053
|
+
envMode: 'static'
|
|
5054
|
+
})),
|
|
4890
5055
|
viewport: project.normalizedConfig.browser.viewport
|
|
4891
5056
|
}));
|
|
4892
|
-
const maxTestTimeoutForRpc =
|
|
5057
|
+
const maxTestTimeoutForRpc = getMaxTestTimeoutForRpc(browserProjects);
|
|
4893
5058
|
const hostOptions = {
|
|
4894
5059
|
rootPath: normalize(context.rootPath),
|
|
4895
5060
|
projects: projectRuntimeConfigs,
|
|
@@ -4906,6 +5071,7 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4906
5071
|
viewport: null
|
|
4907
5072
|
});
|
|
4908
5073
|
const serializedOptions = serializeForInlineScript(hostOptions);
|
|
5074
|
+
const collectTimeoutMs = 30000;
|
|
4909
5075
|
const collectFromServer = async (server)=>{
|
|
4910
5076
|
const results = [];
|
|
4911
5077
|
let error = null;
|
|
@@ -4950,13 +5116,12 @@ const listBrowserTests = async (context, options)=>{
|
|
|
4950
5116
|
await page.goto(`http://localhost:${server.port}/runner.html`, {
|
|
4951
5117
|
waitUntil: 'load'
|
|
4952
5118
|
});
|
|
4953
|
-
const timeoutMs = 30000;
|
|
4954
5119
|
let timeoutId;
|
|
4955
5120
|
const timeoutPromise = new Promise((resolve)=>{
|
|
4956
5121
|
timeoutId = setTimeout(()=>{
|
|
4957
|
-
if (!collectCompleted) logger.warn(color.yellow(`[List] Browser test collection timed out after ${
|
|
5122
|
+
if (!collectCompleted) logger.warn(color.yellow(`[List] Browser test collection timed out after ${collectTimeoutMs}ms`));
|
|
4958
5123
|
resolve();
|
|
4959
|
-
},
|
|
5124
|
+
}, collectTimeoutMs);
|
|
4960
5125
|
});
|
|
4961
5126
|
await Promise.race([
|
|
4962
5127
|
collectPromise,
|
|
@@ -5006,10 +5171,92 @@ const listBrowserTests = async (context, options)=>{
|
|
|
5006
5171
|
close: cleanup
|
|
5007
5172
|
};
|
|
5008
5173
|
};
|
|
5174
|
+
const emptyOutcome = ()=>({
|
|
5175
|
+
results: [],
|
|
5176
|
+
testResults: [],
|
|
5177
|
+
errors: [],
|
|
5178
|
+
testPaths: [],
|
|
5179
|
+
duration: {
|
|
5180
|
+
buildTime: 0,
|
|
5181
|
+
testTime: 0
|
|
5182
|
+
}
|
|
5183
|
+
});
|
|
5184
|
+
async function createBrowserExecutor(context, options) {
|
|
5185
|
+
const { projects, coverageProvider, freezeShardedEntries, filesOnly, allowEmptyRun, appliedModifyRstestConfigEnvironments } = options;
|
|
5186
|
+
let deferredClose;
|
|
5187
|
+
let inFlightCycle;
|
|
5188
|
+
const foldOutcome = (result)=>{
|
|
5189
|
+
if (!result) return emptyOutcome();
|
|
5190
|
+
const map = buildBrowserCoverageMap(result.results, coverageProvider);
|
|
5191
|
+
return {
|
|
5192
|
+
results: result.results,
|
|
5193
|
+
testResults: result.testResults,
|
|
5194
|
+
errors: result.unhandledErrors ?? [],
|
|
5195
|
+
testPaths: result.results.map((r)=>r.testPath),
|
|
5196
|
+
duration: {
|
|
5197
|
+
buildTime: result.duration.buildTime,
|
|
5198
|
+
testTime: result.duration.testTime
|
|
5199
|
+
},
|
|
5200
|
+
coverage: {
|
|
5201
|
+
map: map?.toJSON()
|
|
5202
|
+
},
|
|
5203
|
+
resolveSourcemap: result.resolveSourcemap
|
|
5204
|
+
};
|
|
5205
|
+
};
|
|
5206
|
+
return {
|
|
5207
|
+
name: 'browser',
|
|
5208
|
+
projects,
|
|
5209
|
+
async init () {},
|
|
5210
|
+
async runCycle (opts) {
|
|
5211
|
+
const cycle = runBrowserController(context, {
|
|
5212
|
+
projects,
|
|
5213
|
+
shardedEntries: opts.shardedEntries,
|
|
5214
|
+
freezeShardedEntries,
|
|
5215
|
+
allowEmptyRun,
|
|
5216
|
+
appliedModifyRstestConfigEnvironments,
|
|
5217
|
+
onTraceEvents: opts.onTraceEvents,
|
|
5218
|
+
env: opts.env
|
|
5219
|
+
});
|
|
5220
|
+
inFlightCycle = cycle;
|
|
5221
|
+
try {
|
|
5222
|
+
const result = await cycle;
|
|
5223
|
+
deferredClose = result?.close;
|
|
5224
|
+
return foldOutcome(result);
|
|
5225
|
+
} finally{
|
|
5226
|
+
inFlightCycle = void 0;
|
|
5227
|
+
}
|
|
5228
|
+
},
|
|
5229
|
+
async collect (opts) {
|
|
5230
|
+
const pending = listBrowserTests(context, {
|
|
5231
|
+
projects,
|
|
5232
|
+
shardedEntries: opts.shardedEntries,
|
|
5233
|
+
freezeShardedEntries,
|
|
5234
|
+
filesOnly,
|
|
5235
|
+
appliedModifyRstestConfigEnvironments
|
|
5236
|
+
});
|
|
5237
|
+
inFlightCycle = pending;
|
|
5238
|
+
try {
|
|
5239
|
+
const { list, close } = await pending;
|
|
5240
|
+
deferredClose = close;
|
|
5241
|
+
return {
|
|
5242
|
+
list
|
|
5243
|
+
};
|
|
5244
|
+
} finally{
|
|
5245
|
+
inFlightCycle = void 0;
|
|
5246
|
+
}
|
|
5247
|
+
},
|
|
5248
|
+
async close () {
|
|
5249
|
+
if (inFlightCycle) await inFlightCycle.catch(()=>void 0);
|
|
5250
|
+
const close = deferredClose;
|
|
5251
|
+
deferredClose = void 0;
|
|
5252
|
+
await close?.();
|
|
5253
|
+
}
|
|
5254
|
+
};
|
|
5255
|
+
}
|
|
5009
5256
|
async function runBrowserTests(context, options) {
|
|
5010
5257
|
return runBrowserController(context, options);
|
|
5011
5258
|
}
|
|
5012
5259
|
async function src_listBrowserTests(context, options) {
|
|
5013
5260
|
return listBrowserTests(context, options);
|
|
5014
5261
|
}
|
|
5015
|
-
export { runBrowserTests, src_listBrowserTests as listBrowserTests, validateBrowserConfig };
|
|
5262
|
+
export { createBrowserExecutor, runBrowserTests, src_listBrowserTests as listBrowserTests, validateBrowserConfig };
|