chore(test runner): make 'debug' an explicit option internally (#32154)
This allows any time slot that has a legitimate timeout of zero to be updated later on. See test for an example. Previously, setting timeout to zero at any moment was considered a "debug mode" and any subsequent timeouts were ignored.
This commit is contained in:
parent
b2ccfc3d01
commit
1537d3c2de
|
|
@ -79,7 +79,7 @@ export class FullConfigInternal {
|
||||||
globalTimeout: takeFirst(configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
|
globalTimeout: takeFirst(configCLIOverrides.globalTimeout, userConfig.globalTimeout, 0),
|
||||||
grep: takeFirst(userConfig.grep, defaultGrep),
|
grep: takeFirst(userConfig.grep, defaultGrep),
|
||||||
grepInvert: takeFirst(userConfig.grepInvert, null),
|
grepInvert: takeFirst(userConfig.grepInvert, null),
|
||||||
maxFailures: takeFirst(configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
|
maxFailures: takeFirst(configCLIOverrides.debug ? 1 : undefined, configCLIOverrides.maxFailures, userConfig.maxFailures, 0),
|
||||||
metadata: takeFirst(userConfig.metadata, {}),
|
metadata: takeFirst(userConfig.metadata, {}),
|
||||||
preserveOutput: takeFirst(userConfig.preserveOutput, 'always'),
|
preserveOutput: takeFirst(userConfig.preserveOutput, 'always'),
|
||||||
reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(userConfig.reporter, configDir), [[defaultReporter]]),
|
reporter: takeFirst(configCLIOverrides.reporter, resolveReporters(userConfig.reporter, configDir), [[defaultReporter]]),
|
||||||
|
|
@ -99,7 +99,7 @@ export class FullConfigInternal {
|
||||||
|
|
||||||
(this.config as any)[configInternalSymbol] = this;
|
(this.config as any)[configInternalSymbol] = this;
|
||||||
|
|
||||||
const workers = takeFirst(configCLIOverrides.workers, userConfig.workers, '50%');
|
const workers = takeFirst(configCLIOverrides.debug ? 1 : undefined, configCLIOverrides.workers, userConfig.workers, '50%');
|
||||||
if (typeof workers === 'string') {
|
if (typeof workers === 'string') {
|
||||||
if (workers.endsWith('%')) {
|
if (workers.endsWith('%')) {
|
||||||
const cpus = os.cpus().length;
|
const cpus = os.cpus().length;
|
||||||
|
|
@ -179,7 +179,7 @@ export class FullProjectInternal {
|
||||||
snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
|
snapshotDir: takeFirst(pathResolve(configDir, projectConfig.snapshotDir), pathResolve(configDir, config.snapshotDir), testDir),
|
||||||
testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
|
testIgnore: takeFirst(projectConfig.testIgnore, config.testIgnore, []),
|
||||||
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(c|m)[jt]s?(x)'),
|
testMatch: takeFirst(projectConfig.testMatch, config.testMatch, '**/*.@(spec|test).?(c|m)[jt]s?(x)'),
|
||||||
timeout: takeFirst(configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
|
timeout: takeFirst(configCLIOverrides.debug ? 0 : undefined, configCLIOverrides.timeout, projectConfig.timeout, config.timeout, defaultTimeout),
|
||||||
use: mergeObjects(config.use, projectConfig.use, configCLIOverrides.use),
|
use: mergeObjects(config.use, projectConfig.use, configCLIOverrides.use),
|
||||||
dependencies: projectConfig.dependencies || [],
|
dependencies: projectConfig.dependencies || [],
|
||||||
teardown: projectConfig.teardown,
|
teardown: projectConfig.teardown,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import type { ConfigLocation, FullConfigInternal } from './config';
|
||||||
import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test';
|
import type { ReporterDescription, TestInfoError, TestStatus } from '../../types/test';
|
||||||
|
|
||||||
export type ConfigCLIOverrides = {
|
export type ConfigCLIOverrides = {
|
||||||
|
debug?: boolean;
|
||||||
forbidOnly?: boolean;
|
forbidOnly?: boolean;
|
||||||
fullyParallel?: boolean;
|
fullyParallel?: boolean;
|
||||||
globalTimeout?: number;
|
globalTimeout?: number;
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
playwrightLibrary.selectors.setTestIdAttribute(testIdAttribute);
|
||||||
testInfo.snapshotSuffix = process.platform;
|
testInfo.snapshotSuffix = process.platform;
|
||||||
if (debugMode())
|
if (debugMode())
|
||||||
testInfo.setTimeout(0);
|
(testInfo as TestInfoImpl)._setDebugMode();
|
||||||
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
|
for (const browserType of [playwright.chromium, playwright.firefox, playwright.webkit]) {
|
||||||
(browserType as any)._defaultContextOptions = _combinedContextOptions;
|
(browserType as any)._defaultContextOptions = _combinedContextOptions;
|
||||||
(browserType as any)._defaultContextTimeout = actionTimeout || 0;
|
(browserType as any)._defaultContextTimeout = actionTimeout || 0;
|
||||||
|
|
@ -270,7 +270,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
|
||||||
step?.complete({ error });
|
step?.complete({ error });
|
||||||
},
|
},
|
||||||
onWillPause: () => {
|
onWillPause: () => {
|
||||||
currentTestInfo()?.setTimeout(0);
|
currentTestInfo()?._setDebugMode();
|
||||||
},
|
},
|
||||||
runAfterCreateBrowserContext: async (context: BrowserContext) => {
|
runAfterCreateBrowserContext: async (context: BrowserContext) => {
|
||||||
await artifactsRecorder?.didCreateBrowserContext(context);
|
await artifactsRecorder?.didCreateBrowserContext(context);
|
||||||
|
|
|
||||||
|
|
@ -308,9 +308,7 @@ function overridesFromOptions(options: { [key: string]: any }): ConfigCLIOverrid
|
||||||
if (options.headed || options.debug)
|
if (options.headed || options.debug)
|
||||||
overrides.use = { headless: false };
|
overrides.use = { headless: false };
|
||||||
if (!options.ui && options.debug) {
|
if (!options.ui && options.debug) {
|
||||||
overrides.maxFailures = 1;
|
overrides.debug = true;
|
||||||
overrides.timeout = 0;
|
|
||||||
overrides.workers = 1;
|
|
||||||
process.env.PWDEBUG = '1';
|
process.env.PWDEBUG = '1';
|
||||||
}
|
}
|
||||||
if (!options.ui && options.trace) {
|
if (!options.ui && options.trace) {
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,8 @@ export class TestInfoImpl implements TestInfo {
|
||||||
this.expectedStatus = test?.expectedStatus ?? 'skipped';
|
this.expectedStatus = test?.expectedStatus ?? 'skipped';
|
||||||
|
|
||||||
this._timeoutManager = new TimeoutManager(this.project.timeout);
|
this._timeoutManager = new TimeoutManager(this.project.timeout);
|
||||||
|
if (configInternal.configCLIOverrides.debug)
|
||||||
|
this._setDebugMode();
|
||||||
|
|
||||||
this.outputDir = (() => {
|
this.outputDir = (() => {
|
||||||
const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile.replace(/\.(spec|test)\.(js|ts|jsx|tsx|mjs|mts|cjs|cts)$/, ''));
|
const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile.replace(/\.(spec|test)\.(js|ts|jsx|tsx|mjs|mts|cjs|cts)$/, ''));
|
||||||
|
|
@ -225,17 +227,6 @@ export class TestInfoImpl implements TestInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _findLastNonFinishedStep(filter: (step: TestStepInternal) => boolean) {
|
|
||||||
let result: TestStepInternal | undefined;
|
|
||||||
const visit = (step: TestStepInternal) => {
|
|
||||||
if (!step.endWallTime && filter(step))
|
|
||||||
result = step;
|
|
||||||
step.steps.forEach(visit);
|
|
||||||
};
|
|
||||||
this._steps.forEach(visit);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private _findLastStageStep() {
|
private _findLastStageStep() {
|
||||||
for (let i = this._stages.length - 1; i >= 0; i--) {
|
for (let i = this._stages.length - 1; i >= 0; i--) {
|
||||||
if (this._stages[i].step)
|
if (this._stages[i].step)
|
||||||
|
|
@ -404,6 +395,10 @@ export class TestInfoImpl implements TestInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_setDebugMode() {
|
||||||
|
this._timeoutManager.setIgnoreTimeouts();
|
||||||
|
}
|
||||||
|
|
||||||
// ------------ TestInfo methods ------------
|
// ------------ TestInfo methods ------------
|
||||||
|
|
||||||
async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) {
|
async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) {
|
||||||
|
|
|
||||||
|
|
@ -52,11 +52,16 @@ export const kMaxDeadline = 2147483647; // 2^31-1
|
||||||
export class TimeoutManager {
|
export class TimeoutManager {
|
||||||
private _defaultSlot: TimeSlot;
|
private _defaultSlot: TimeSlot;
|
||||||
private _running?: Running;
|
private _running?: Running;
|
||||||
|
private _ignoreTimeouts = false;
|
||||||
|
|
||||||
constructor(timeout: number) {
|
constructor(timeout: number) {
|
||||||
this._defaultSlot = { timeout, elapsed: 0 };
|
this._defaultSlot = { timeout, elapsed: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setIgnoreTimeouts() {
|
||||||
|
this._ignoreTimeouts = true;
|
||||||
|
}
|
||||||
|
|
||||||
interrupt() {
|
interrupt() {
|
||||||
if (this._running)
|
if (this._running)
|
||||||
this._running.timeoutPromise.reject(this._createTimeoutError(this._running));
|
this._running.timeoutPromise.reject(this._createTimeoutError(this._running));
|
||||||
|
|
@ -94,7 +99,7 @@ export class TimeoutManager {
|
||||||
if (running.timer)
|
if (running.timer)
|
||||||
clearTimeout(running.timer);
|
clearTimeout(running.timer);
|
||||||
running.timer = undefined;
|
running.timer = undefined;
|
||||||
if (!running.slot.timeout) {
|
if (this._ignoreTimeouts || !running.slot.timeout) {
|
||||||
running.deadline = kMaxDeadline;
|
running.deadline = kMaxDeadline;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -119,8 +124,6 @@ export class TimeoutManager {
|
||||||
|
|
||||||
setTimeout(timeout: number) {
|
setTimeout(timeout: number) {
|
||||||
const slot = this._running ? this._running.slot : this._defaultSlot;
|
const slot = this._running ? this._running.slot : this._defaultSlot;
|
||||||
if (!slot.timeout)
|
|
||||||
return; // Zero timeout means some debug mode - do not set a timeout.
|
|
||||||
slot.timeout = timeout;
|
slot.timeout = timeout;
|
||||||
if (this._running)
|
if (this._running)
|
||||||
this._updateTimeout(this._running);
|
this._updateTimeout(this._running);
|
||||||
|
|
|
||||||
|
|
@ -159,7 +159,7 @@ test('should ignore test.setTimeout when debugging', async ({ runInlineTest }) =
|
||||||
await new Promise(f => setTimeout(f, 2000));
|
await new Promise(f => setTimeout(f, 2000));
|
||||||
});
|
});
|
||||||
`
|
`
|
||||||
}, { timeout: 0 });
|
}, { debug: true });
|
||||||
expect(result.exitCode).toBe(0);
|
expect(result.exitCode).toBe(0);
|
||||||
expect(result.passed).toBe(1);
|
expect(result.passed).toBe(1);
|
||||||
});
|
});
|
||||||
|
|
@ -558,3 +558,25 @@ test('should allow custom worker fixture timeout longer than force exit cap', as
|
||||||
expect(result.output).toContain(`Error: Oh my!`);
|
expect(result.output).toContain(`Error: Oh my!`);
|
||||||
expect(result.output).toContain(`1 error was not a part of any test, see above for details`);
|
expect(result.output).toContain(`1 error was not a part of any test, see above for details`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('test.setTimeout should be able to change custom fixture timeout', async ({ runInlineTest }) => {
|
||||||
|
const result = await runInlineTest({
|
||||||
|
'a.spec.ts': `
|
||||||
|
import { test as base, expect } from '@playwright/test';
|
||||||
|
const test = base.extend({
|
||||||
|
foo: [async ({}, use) => {
|
||||||
|
console.log('\\n%%foo setup');
|
||||||
|
test.setTimeout(100);
|
||||||
|
await new Promise(f => setTimeout(f, 3000));
|
||||||
|
await use('foo');
|
||||||
|
console.log('\\n%%foo teardown');
|
||||||
|
}, { timeout: 0 }],
|
||||||
|
});
|
||||||
|
test('times out', async ({ foo }) => {
|
||||||
|
});
|
||||||
|
`
|
||||||
|
});
|
||||||
|
expect(result.exitCode).toBe(1);
|
||||||
|
expect(result.failed).toBe(1);
|
||||||
|
expect(result.output).toContain(`Fixture "foo" timeout of 100ms exceeded during setup`);
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue