test(test-runner): test round-robin sharding with stable-test-runner
This commit is contained in:
parent
bc30cc795e
commit
7f79e1feb6
|
|
@ -36,6 +36,7 @@ export default defineConfig({
|
|||
forbidOnly: !!process.env.CI,
|
||||
workers: process.env.CI ? 2 : undefined,
|
||||
snapshotPathTemplate: '__screenshots__/{testFilePath}/{arg}{ext}',
|
||||
shardingMode: 'round-robin',
|
||||
projects: [
|
||||
{
|
||||
name: 'playwright-test',
|
||||
|
|
|
|||
1229
tests/playwright-test/stable-test-runner/package-lock.json
generated
1229
tests/playwright-test/stable-test-runner/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,10 @@
|
|||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"postinstall": "patch-package"
|
||||
},
|
||||
"dependencies": {
|
||||
"patch-package": "8.0.0",
|
||||
"@playwright/test": "1.48.0-beta-1728384960000"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,331 @@
|
|||
diff --git a/node_modules/playwright/lib/common/config.js b/node_modules/playwright/lib/common/config.js
|
||||
index dc2f6d7..2c81f2a 100644
|
||||
--- a/node_modules/playwright/lib/common/config.js
|
||||
+++ b/node_modules/playwright/lib/common/config.js
|
||||
@@ -48,6 +48,8 @@ class FullConfigInternal {
|
||||
this.cliLastFailed = void 0;
|
||||
this.testIdMatcher = void 0;
|
||||
this.defineConfigWasUsed = false;
|
||||
+ this.shardingMode = void 0;
|
||||
+ this.lastRunFile = void 0;
|
||||
if (configCLIOverrides.projects && userConfig.projects) throw new Error(`Cannot use --browser option when configuration file defines projects. Specify browserName in the projects instead.`);
|
||||
const {
|
||||
resolvedConfigFile,
|
||||
@@ -87,6 +89,8 @@ class FullConfigInternal {
|
||||
workers: 0,
|
||||
webServer: null
|
||||
};
|
||||
+ this.shardingMode = takeFirst(configCLIOverrides.shardingMode, userConfig.shardingMode, 'partition');
|
||||
+ this.lastRunFile = configCLIOverrides.lastRunFile;
|
||||
for (const key in userConfig) {
|
||||
if (key.startsWith('@')) this.config[key] = userConfig[key];
|
||||
}
|
||||
diff --git a/node_modules/playwright/lib/common/configLoader.js b/node_modules/playwright/lib/common/configLoader.js
|
||||
index 75fc987..177e249 100644
|
||||
--- a/node_modules/playwright/lib/common/configLoader.js
|
||||
+++ b/node_modules/playwright/lib/common/configLoader.js
|
||||
@@ -192,6 +192,9 @@ function validateConfig(file, config) {
|
||||
if (!('total' in config.shard) || typeof config.shard.total !== 'number' || config.shard.total < 1) throw (0, _util.errorWithFile)(file, `config.shard.total must be a positive number`);
|
||||
if (!('current' in config.shard) || typeof config.shard.current !== 'number' || config.shard.current < 1 || config.shard.current > config.shard.total) throw (0, _util.errorWithFile)(file, `config.shard.current must be a positive number, not greater than config.shard.total`);
|
||||
}
|
||||
+ if ('shardingMode' in config && config.shardingMode !== undefined) {
|
||||
+ if (typeof config.shardingMode !== 'string' || !['partition', 'round-robin', 'duration-round-robin'].includes(config.shardingMode)) throw (0, _util.errorWithFile)(file, `config.shardingMode must be one of "partition", "round-robin" or "duration-round-robin"`);
|
||||
+ }
|
||||
if ('updateSnapshots' in config && config.updateSnapshots !== undefined) {
|
||||
if (typeof config.updateSnapshots !== 'string' || !['all', 'none', 'missing'].includes(config.updateSnapshots)) throw (0, _util.errorWithFile)(file, `config.updateSnapshots must be one of "all", "none" or "missing"`);
|
||||
}
|
||||
@@ -272,11 +275,11 @@ async function loadConfigFromFileRestartIfNeeded(configFile, overrides, ignoreDe
|
||||
if (restartWithExperimentalTsEsm(location.resolvedConfigFile)) return null;
|
||||
return await loadConfig(location, overrides, ignoreDeps);
|
||||
}
|
||||
-async function loadEmptyConfigForMergeReports() {
|
||||
+async function loadEmptyConfigForMergeReports(overrides) {
|
||||
// Merge reports is "different" for no good reason. It should not pick up local config from the cwd.
|
||||
return await loadConfig({
|
||||
configDir: process.cwd()
|
||||
- });
|
||||
+ }, overrides);
|
||||
}
|
||||
function restartWithExperimentalTsEsm(configFile, force = false) {
|
||||
// Opt-out switch.
|
||||
diff --git a/node_modules/playwright/lib/program.js b/node_modules/playwright/lib/program.js
|
||||
index 19ebe36..642ab1f 100644
|
||||
--- a/node_modules/playwright/lib/program.js
|
||||
+++ b/node_modules/playwright/lib/program.js
|
||||
@@ -156,6 +156,7 @@ function addMergeReportsCommand(program) {
|
||||
});
|
||||
command.option('-c, --config <file>', `Configuration file. Can be used to specify additional configuration for the output report.`);
|
||||
command.option('--reporter <reporter>', `Reporter to use, comma-separated, can be ${_config.builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${_config.defaultReporter}")`);
|
||||
+ command.option('--last-run-file <file>', `Path to a json file where the last run information is written to (default: test-results/.last-run.json)`);
|
||||
command.addHelpText('afterAll', `
|
||||
Arguments [dir]:
|
||||
Directory containing blob reports.
|
||||
@@ -253,7 +254,8 @@ async function listTestFiles(opts) {
|
||||
}
|
||||
async function mergeReports(reportDir, opts) {
|
||||
const configFile = opts.config;
|
||||
- const config = configFile ? await (0, _configLoader.loadConfigFromFileRestartIfNeeded)(configFile) : await (0, _configLoader.loadEmptyConfigForMergeReports)();
|
||||
+ const cliOverrides = overridesFromOptions(opts);
|
||||
+ const config = configFile ? await (0, _configLoader.loadConfigFromFileRestartIfNeeded)(configFile, cliOverrides) : await (0, _configLoader.loadEmptyConfigForMergeReports)(cliOverrides);
|
||||
if (!config) return;
|
||||
const dir = _path.default.resolve(process.cwd(), reportDir || '');
|
||||
const dirStat = await _fs.default.promises.stat(dir).catch(e => null);
|
||||
@@ -282,6 +284,8 @@ function overridesFromOptions(options) {
|
||||
current: shardPair[0],
|
||||
total: shardPair[1]
|
||||
} : undefined,
|
||||
+ shardingMode: options.shardingMode ? resolveShardingModeOption(options.shardingMode) : undefined,
|
||||
+ lastRunFile: options.lastRunFile ? _path.default.resolve(process.cwd(), options.lastRunFile) : undefined,
|
||||
timeout: options.timeout ? parseInt(options.timeout, 10) : undefined,
|
||||
tsconfig: options.tsconfig ? _path.default.resolve(process.cwd(), options.tsconfig) : undefined,
|
||||
ignoreSnapshots: options.ignoreSnapshots ? !!options.ignoreSnapshots : undefined,
|
||||
@@ -315,6 +319,12 @@ function overridesFromOptions(options) {
|
||||
}
|
||||
return overrides;
|
||||
}
|
||||
+const shardingModes = ['partition', 'round-robin', 'duration-round-robin'];
|
||||
+function resolveShardingModeOption(shardingMode) {
|
||||
+ if (!shardingMode) return undefined;
|
||||
+ if (!shardingModes.includes(shardingMode)) throw new Error(`Unsupported sharding mode "${shardingMode}", must be one of: ${shardingModes.map(mode => `"${mode}"`).join(', ')}`);
|
||||
+ return shardingMode;
|
||||
+}
|
||||
function resolveReporterOption(reporter) {
|
||||
if (!reporter || !reporter.length) return undefined;
|
||||
return reporter.split(',').map(r => [resolveReporter(r)]);
|
||||
@@ -328,7 +338,7 @@ function resolveReporter(id) {
|
||||
});
|
||||
}
|
||||
const kTraceModes = ['on', 'off', 'on-first-retry', 'on-all-retries', 'retain-on-failure', 'retain-on-first-failure'];
|
||||
-const testOptions = [['--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`], ['-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`], ['--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`], ['--fail-on-flaky-tests', `Fail if any test is flagged as flaky (default: false)`], ['--forbid-only', `Fail if test.only is called (default: false)`], ['--fully-parallel', `Run all tests in parallel (default: false)`], ['--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`], ['-g, --grep <grep>', `Only run tests matching this regular expression (default: ".*")`], ['-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`], ['--headed', `Run tests in headed browsers (default: headless)`], ['--ignore-snapshots', `Ignore screenshot and snapshot expectations`], ['--last-failed', `Only re-run the failures`], ['--list', `Collect all the tests and report them, but do not run`], ['--max-failures <N>', `Stop after the first N failures`], ['--no-deps', 'Do not run project dependencies'], ['--output <dir>', `Folder for output artifacts (default: "test-results")`], ['--only-changed [ref]', `Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.`], ['--pass-with-no-tests', `Makes test run succeed even if no tests were found`], ['--project <project-name...>', `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)`], ['--quiet', `Suppress stdio`], ['--repeat-each <N>', `Run each test N times (default: 1)`], ['--reporter <reporter>', `Reporter to use, comma-separated, can be ${_config.builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${_config.defaultReporter}")`], ['--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`], ['--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`], ['--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${_config.defaultTimeout})`], ['--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`], ['--tsconfig <path>', `Path to a single tsconfig applicable to all imported files (default: look up tsconfig for each imported file separately)`], ['--ui', `Run tests in interactive UI mode`], ['--ui-host <host>', 'Host to serve UI on; specifying this option opens UI in a browser tab'], ['--ui-port <port>', 'Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab'], ['-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`], ['-j, --workers <workers>', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`], ['-x', `Stop after the first failure`]];
|
||||
+const testOptions = [['--browser <browser>', `Browser to use for tests, one of "all", "chromium", "firefox" or "webkit" (default: "chromium")`], ['-c, --config <file>', `Configuration file, or a test directory with optional "playwright.config.{m,c}?{js,ts}"`], ['--debug', `Run tests with Playwright Inspector. Shortcut for "PWDEBUG=1" environment variable and "--timeout=0 --max-failures=1 --headed --workers=1" options`], ['--fail-on-flaky-tests', `Fail if any test is flagged as flaky (default: false)`], ['--forbid-only', `Fail if test.only is called (default: false)`], ['--fully-parallel', `Run all tests in parallel (default: false)`], ['--global-timeout <timeout>', `Maximum time this test suite can run in milliseconds (default: unlimited)`], ['-g, --grep <grep>', `Only run tests matching this regular expression (default: ".*")`], ['-gv, --grep-invert <grep>', `Only run tests that do not match this regular expression`], ['--headed', `Run tests in headed browsers (default: headless)`], ['--ignore-snapshots', `Ignore screenshot and snapshot expectations`], ['--last-failed', `Only re-run the failures`], ['--last-run-file <file>', `Path to a json file where the last run information is read from and written to (default: test-results/.last-run.json)`], ['--list', `Collect all the tests and report them, but do not run`], ['--max-failures <N>', `Stop after the first N failures`], ['--no-deps', 'Do not run project dependencies'], ['--output <dir>', `Folder for output artifacts (default: "test-results")`], ['--only-changed [ref]', `Only run test files that have been changed between 'HEAD' and 'ref'. Defaults to running all uncommitted changes. Only supports Git.`], ['--pass-with-no-tests', `Makes test run succeed even if no tests were found`], ['--project <project-name...>', `Only run tests from the specified list of projects, supports '*' wildcard (default: run all projects)`], ['--quiet', `Suppress stdio`], ['--repeat-each <N>', `Run each test N times (default: 1)`], ['--reporter <reporter>', `Reporter to use, comma-separated, can be ${_config.builtInReporters.map(name => `"${name}"`).join(', ')} (default: "${_config.defaultReporter}")`], ['--retries <retries>', `Maximum retry count for flaky tests, zero for no retries (default: no retries)`], ['--shard <shard>', `Shard tests and execute only the selected shard, specify in the form "current/all", 1-based, for example "3/5"`], ['--sharding-mode <mode>', `Sharding algorithm to use; "partition", "round-robin" or "duration-round-robin". Defaults to "partition".`], ['--timeout <timeout>', `Specify test timeout threshold in milliseconds, zero for unlimited (default: ${_config.defaultTimeout})`], ['--trace <mode>', `Force tracing mode, can be ${kTraceModes.map(mode => `"${mode}"`).join(', ')}`], ['--tsconfig <path>', `Path to a single tsconfig applicable to all imported files (default: look up tsconfig for each imported file separately)`], ['--ui', `Run tests in interactive UI mode`], ['--ui-host <host>', 'Host to serve UI on; specifying this option opens UI in a browser tab'], ['--ui-port <port>', 'Port to serve UI on, 0 for any free port; specifying this option opens UI in a browser tab'], ['-u, --update-snapshots', `Update snapshots with actual results (default: only create missing snapshots)`], ['-j, --workers <workers>', `Number of concurrent workers or percentage of logical CPU cores, use 1 to run in a single worker (default: 50%)`], ['-x', `Stop after the first failure`]];
|
||||
addTestCommand(_program.program);
|
||||
addShowReportCommand(_program.program);
|
||||
addListFilesCommand(_program.program);
|
||||
diff --git a/node_modules/playwright/lib/reporters/merge.js b/node_modules/playwright/lib/reporters/merge.js
|
||||
index fca737b..d16141e 100644
|
||||
--- a/node_modules/playwright/lib/reporters/merge.js
|
||||
+++ b/node_modules/playwright/lib/reporters/merge.js
|
||||
@@ -13,6 +13,7 @@ var _multiplexer = require("./multiplexer");
|
||||
var _utils = require("playwright-core/lib/utils");
|
||||
var _blob = require("./blob");
|
||||
var _util = require("../util");
|
||||
+var _lastRun = require("../runner/lastRun");
|
||||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
||||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
@@ -33,7 +34,8 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
|
||||
async function createMergedReport(config, dir, reporterDescriptions, rootDirOverride) {
|
||||
var _eventData$pathSepara;
|
||||
const reporters = await (0, _reporters.createReporters)(config, 'merge', false, reporterDescriptions);
|
||||
- const multiplexer = new _multiplexer.Multiplexer(reporters);
|
||||
+ const lastRun = new _lastRun.LastRunReporter(config);
|
||||
+ const multiplexer = new _multiplexer.Multiplexer([...reporters, lastRun]);
|
||||
const stringPool = new _stringInternPool.StringInternPool();
|
||||
let printStatus = () => {};
|
||||
if (!multiplexer.printsToStdio()) {
|
||||
diff --git a/node_modules/playwright/lib/runner/lastRun.js b/node_modules/playwright/lib/runner/lastRun.js
|
||||
index d84abea..440a96b 100644
|
||||
--- a/node_modules/playwright/lib/runner/lastRun.js
|
||||
+++ b/node_modules/playwright/lib/runner/lastRun.js
|
||||
@@ -30,16 +30,25 @@ class LastRunReporter {
|
||||
this._lastRunFile = void 0;
|
||||
this._suite = void 0;
|
||||
this._config = config;
|
||||
- const [project] = (0, _projectUtils.filterProjects)(config.projects, config.cliProjectFilter);
|
||||
- if (project) this._lastRunFile = _path.default.join(project.project.outputDir, '.last-run.json');
|
||||
+ if (config.lastRunFile) {
|
||||
+ // specified via command line argument
|
||||
+ this._lastRunFile = config.lastRunFile;
|
||||
+ } else {
|
||||
+ const [project] = (0, _projectUtils.filterProjects)(config.projects, config.cliProjectFilter);
|
||||
+ if (project) this._lastRunFile = _path.default.join(project.project.outputDir, '.last-run.json');
|
||||
+ }
|
||||
}
|
||||
- async filterLastFailed() {
|
||||
+ async lastRunInfo() {
|
||||
if (!this._lastRunFile) return;
|
||||
try {
|
||||
- const lastRunInfo = JSON.parse(await _fs.default.promises.readFile(this._lastRunFile, 'utf8'));
|
||||
- this._config.testIdMatcher = id => lastRunInfo.failedTests.includes(id);
|
||||
+ return JSON.parse(await _fs.default.promises.readFile(this._lastRunFile, 'utf8'));
|
||||
} catch {}
|
||||
}
|
||||
+ async filterLastFailed() {
|
||||
+ const lastRunInfo = await this.lastRunInfo();
|
||||
+ if (!lastRunInfo) return;
|
||||
+ this._config.testIdMatcher = id => lastRunInfo.failedTests.includes(id);
|
||||
+ }
|
||||
version() {
|
||||
return 'v2';
|
||||
}
|
||||
@@ -50,15 +59,20 @@ class LastRunReporter {
|
||||
this._suite = suite;
|
||||
}
|
||||
async onEnd(result) {
|
||||
- var _this$_suite;
|
||||
+ var _this$_suite, _this$_suite2;
|
||||
if (!this._lastRunFile || this._config.cliListOnly) return;
|
||||
await _fs.default.promises.mkdir(_path.default.dirname(this._lastRunFile), {
|
||||
recursive: true
|
||||
});
|
||||
const failedTests = (_this$_suite = this._suite) === null || _this$_suite === void 0 ? void 0 : _this$_suite.allTests().filter(t => !t.ok()).map(t => t.id);
|
||||
+ const testDurations = (_this$_suite2 = this._suite) === null || _this$_suite2 === void 0 ? void 0 : _this$_suite2.allTests().reduce((map, t) => {
|
||||
+ map[t.id] = t.results.map(r => r.duration).reduce((a, b) => a + b, 0);
|
||||
+ return map;
|
||||
+ }, {});
|
||||
const lastRunReport = JSON.stringify({
|
||||
status: result.status,
|
||||
- failedTests
|
||||
+ failedTests,
|
||||
+ testDurations
|
||||
}, undefined, 2);
|
||||
await _fs.default.promises.writeFile(this._lastRunFile, lastRunReport);
|
||||
}
|
||||
diff --git a/node_modules/playwright/lib/runner/loadUtils.js b/node_modules/playwright/lib/runner/loadUtils.js
|
||||
index 8404253..e85d86c 100644
|
||||
--- a/node_modules/playwright/lib/runner/loadUtils.js
|
||||
+++ b/node_modules/playwright/lib/runner/loadUtils.js
|
||||
@@ -182,7 +182,7 @@ async function createRootSuite(testRun, errors, shouldFilterOnly, additionalFile
|
||||
for (const projectSuite of rootSuite.suites) testGroups.push(...(0, _testGroups.createTestGroups)(projectSuite, config.config.workers));
|
||||
|
||||
// Shard test groups.
|
||||
- const testGroupsInThisShard = (0, _testGroups.filterForShard)(config.config.shard, testGroups);
|
||||
+ const testGroupsInThisShard = await (0, _testGroups.filterForShard)(config, testGroups);
|
||||
const testsInThisShard = new Set();
|
||||
for (const group of testGroupsInThisShard) {
|
||||
for (const test of group.tests) testsInThisShard.add(test);
|
||||
diff --git a/node_modules/playwright/lib/runner/testGroups.js b/node_modules/playwright/lib/runner/testGroups.js
|
||||
index 952aafe..58d8d31 100644
|
||||
--- a/node_modules/playwright/lib/runner/testGroups.js
|
||||
+++ b/node_modules/playwright/lib/runner/testGroups.js
|
||||
@@ -5,6 +5,7 @@ Object.defineProperty(exports, "__esModule", {
|
||||
});
|
||||
exports.createTestGroups = createTestGroups;
|
||||
exports.filterForShard = filterForShard;
|
||||
+var _lastRun = require("./lastRun");
|
||||
/**
|
||||
* Copyright Microsoft Corporation. All rights reserved.
|
||||
*
|
||||
@@ -106,14 +107,36 @@ function createTestGroups(projectSuite, workers) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
-function filterForShard(shard, testGroups) {
|
||||
+async function filterForShard(config, testGroups) {
|
||||
// Note that sharding works based on test groups.
|
||||
// This means parallel files will be sharded by single tests,
|
||||
// while non-parallel files will be sharded by the whole file.
|
||||
//
|
||||
// Shards are still balanced by the number of tests, not files,
|
||||
// even in the case of non-paralleled files.
|
||||
+ const mode = config.shardingMode;
|
||||
+ const shard = config.config.shard;
|
||||
+ if (mode === 'round-robin') return filterForShardRoundRobin(shard, testGroups);
|
||||
+ if (mode === 'duration-round-robin') {
|
||||
+ const lastRun = new _lastRun.LastRunReporter(config);
|
||||
+ const lastRunInfo = await lastRun.lastRunInfo();
|
||||
+ return filterForShardRoundRobin(shard, testGroups, lastRunInfo);
|
||||
+ }
|
||||
+ return filterForShardPartition(shard, testGroups);
|
||||
+}
|
||||
|
||||
+/**
|
||||
+ * Shards tests by partitioning them into equal parts.
|
||||
+ *
|
||||
+ * ```
|
||||
+ * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
+ * Shard 1: ^---------^ : [ 1, 2, 3 ]
|
||||
+ * Shard 2: ^---------^ : [ 4, 5, 6 ]
|
||||
+ * Shard 3: ^---------^ : [ 7, 8, 9 ]
|
||||
+ * Shard 4: ^---------^ : [ 10,11,12 ]
|
||||
+ * ```
|
||||
+ */
|
||||
+function filterForShardPartition(shard, testGroups) {
|
||||
let shardableTotal = 0;
|
||||
for (const group of testGroups) shardableTotal += group.tests.length;
|
||||
|
||||
@@ -134,3 +157,41 @@ function filterForShard(shard, testGroups) {
|
||||
}
|
||||
return result;
|
||||
}
|
||||
+
|
||||
+/**
|
||||
+ * Shards tests by round-robin.
|
||||
+ *
|
||||
+ * ```
|
||||
+ * [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
||||
+ * Shard 1: ^ ^ ^ : [ 1, 5, 9 ]
|
||||
+ * Shard 2: ^ ^ ^ : [ 2, 6,10 ]
|
||||
+ * Shard 3: ^ ^ ^ : [ 3, 7,11 ]
|
||||
+ * Shard 4: ^ ^ ^ : [ 4, 8,12 ]
|
||||
+ * ```
|
||||
+ */
|
||||
+function filterForShardRoundRobin(shard, testGroups, lastRunInfo) {
|
||||
+ const weights = new Array(shard.total).fill(0);
|
||||
+ const shardSet = new Array(shard.total).fill(0).map(() => new Set());
|
||||
+ const averageDuration = lastRunInfo ? Object.values((lastRunInfo === null || lastRunInfo === void 0 ? void 0 : lastRunInfo.testDurations) || {}).reduce((a, b) => a + b, 1) / Math.max(1, Object.values((lastRunInfo === null || lastRunInfo === void 0 ? void 0 : lastRunInfo.testDurations) || {}).length) : 0;
|
||||
+ const weight = group => {
|
||||
+ if (!lastRunInfo)
|
||||
+ // If we don't have last run info, we just count the number of tests.
|
||||
+ return group.tests.length;
|
||||
+ // If we have last run info, we use the duration of the tests.
|
||||
+ return group.tests.reduce((sum, test) => {
|
||||
+ var _lastRunInfo$testDura;
|
||||
+ return sum + Math.max(1, ((_lastRunInfo$testDura = lastRunInfo.testDurations) === null || _lastRunInfo$testDura === void 0 ? void 0 : _lastRunInfo$testDura[test.id]) || averageDuration);
|
||||
+ }, 0);
|
||||
+ };
|
||||
+
|
||||
+ // We sort the test groups by group duration in descending order.
|
||||
+ const sortedTestGroups = testGroups.slice().sort((a, b) => weight(b) - weight(a));
|
||||
+
|
||||
+ // Then we add each group to the shard with the smallest number of tests.
|
||||
+ for (const group of sortedTestGroups) {
|
||||
+ const index = weights.reduce((minIndex, currentLength, currentIndex) => currentLength < weights[minIndex] ? currentIndex : minIndex, 0);
|
||||
+ weights[index] += weight(group);
|
||||
+ shardSet[index].add(group);
|
||||
+ }
|
||||
+ return shardSet[shard.current - 1];
|
||||
+}
|
||||
diff --git a/node_modules/playwright/types/test.d.ts b/node_modules/playwright/types/test.d.ts
|
||||
index 400d7cb..c30ef7a 100644
|
||||
--- a/node_modules/playwright/types/test.d.ts
|
||||
+++ b/node_modules/playwright/types/test.d.ts
|
||||
@@ -1407,7 +1407,7 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||
/**
|
||||
* Shard tests and execute only the selected shard. Specify in the one-based form like `{ total: 5, current: 2 }`.
|
||||
*
|
||||
- * Learn more about [parallelism and sharding](https://playwright.dev/docs/test-parallel) with Playwright Test.
|
||||
+ * Learn more about [parallelism](https://playwright.dev/docs/test-parallel) and [sharding](https://playwright.dev/docs/test-sharding) with Playwright Test.
|
||||
*
|
||||
* **Usage**
|
||||
*
|
||||
@@ -1433,6 +1433,21 @@ interface TestConfig<TestArgs = {}, WorkerArgs = {}> {
|
||||
total: number;
|
||||
};
|
||||
|
||||
+ /**
|
||||
+ * Defines the algorithm to be used for sharding. Defaults to `'partition'`.
|
||||
+ * - `'partition'` - divide the set of test groups by number of shards. e.g. first half goes to shard 1/2 and
|
||||
+ * seconds half to shard 2/2.
|
||||
+ * - `'round-robin'` - spread test groups to shards in a round-robin way. e.g. loop over test groups and always
|
||||
+ * assign to the shard that has the lowest number of tests.
|
||||
+ * - `'duration-round-robin'` - use duration info from `.last-run.json` to spread test groups to shards in a
|
||||
+ * round-robin way. e.g. loop over test groups and always assign to the shard that has the lowest duration of
|
||||
+ * tests. new tests which were not present in the last run will use an average duration time. When no
|
||||
+ * `.last-run.json` could be found the behavior is identical to `'round-robin'`.
|
||||
+ *
|
||||
+ * Learn more about [sharding](https://playwright.dev/docs/test-sharding) with Playwright Test.
|
||||
+ */
|
||||
+ shardingMode?: "partition"|"round-robin"|"duration-round-robin";
|
||||
+
|
||||
/**
|
||||
* **NOTE** Use
|
||||
* [testConfig.snapshotPathTemplate](https://playwright.dev/docs/api/class-testconfig#test-config-snapshot-path-template)
|
||||
@@ -5145,6 +5160,7 @@ export interface PlaywrightWorkerOptions {
|
||||
video: VideoMode | /** deprecated */ 'retry-with-video' | { mode: VideoMode, size?: ViewportSize };
|
||||
}
|
||||
|
||||
+export type ShardingMode = Exclude<PlaywrightTestConfig['shardingMode'], undefined>;
|
||||
export type ScreenshotMode = 'off' | 'on' | 'only-on-failure';
|
||||
export type TraceMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry' | 'on-all-retries' | 'retain-on-first-failure';
|
||||
export type VideoMode = 'off' | 'on' | 'retain-on-failure' | 'on-first-retry';
|
||||
Loading…
Reference in a new issue