fix(test runner): do not show TimeoutError for unhandled rejection (#21971)
Unhandled error/rejection interrupts current test and produces a TimeoutError for it that should be ignored.
This commit is contained in:
		
							parent
							
								
									97d2c4a635
								
							
						
					
					
						commit
						120aaa777e
					
				|  | @ -53,6 +53,7 @@ export class TestInfoImpl implements TestInfo { | ||||||
|   readonly _traceEvents: trace.TraceEvent[] = []; |   readonly _traceEvents: trace.TraceEvent[] = []; | ||||||
|   readonly _onTestFailureImmediateCallbacks = new Map<() => Promise<void>, string>(); // fn -> title
 |   readonly _onTestFailureImmediateCallbacks = new Map<() => Promise<void>, string>(); // fn -> title
 | ||||||
|   _didTimeout = false; |   _didTimeout = false; | ||||||
|  |   _wasInterrupted = false; | ||||||
|   _lastStepId = 0; |   _lastStepId = 0; | ||||||
| 
 | 
 | ||||||
|   // ------------ TestInfo fields ------------
 |   // ------------ TestInfo fields ------------
 | ||||||
|  | @ -184,7 +185,7 @@ export class TestInfoImpl implements TestInfo { | ||||||
|     const timeoutError = await this._timeoutManager.runWithTimeout(cb); |     const timeoutError = await this._timeoutManager.runWithTimeout(cb); | ||||||
|     // When interrupting, we arrive here with a timeoutError, but we should not
 |     // When interrupting, we arrive here with a timeoutError, but we should not
 | ||||||
|     // consider it a timeout.
 |     // consider it a timeout.
 | ||||||
|     if (this.status !== 'interrupted' && timeoutError && !this._didTimeout) { |     if (!this._wasInterrupted && timeoutError && !this._didTimeout) { | ||||||
|       this._didTimeout = true; |       this._didTimeout = true; | ||||||
|       this.errors.push(timeoutError); |       this.errors.push(timeoutError); | ||||||
|       // Do not overwrite existing failure upon hook/teardown timeout.
 |       // Do not overwrite existing failure upon hook/teardown timeout.
 | ||||||
|  | @ -254,6 +255,15 @@ export class TestInfoImpl implements TestInfo { | ||||||
|     return step; |     return step; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   _interrupt() { | ||||||
|  |     // Mark as interrupted so we can ignore TimeoutError thrown by interrupt() call.
 | ||||||
|  |     this._wasInterrupted = true; | ||||||
|  |     this._timeoutManager.interrupt(); | ||||||
|  |     // Do not overwrite existing failure (for example, unhandled rejection) with "interrupted".
 | ||||||
|  |     if (this.status === 'passed') | ||||||
|  |       this.status = 'interrupted'; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   _failWithError(error: TestInfoError, isHardError: boolean) { |   _failWithError(error: TestInfoError, isHardError: boolean) { | ||||||
|     // Do not overwrite any previous hard errors.
 |     // Do not overwrite any previous hard errors.
 | ||||||
|     // Some (but not all) scenarios include:
 |     // Some (but not all) scenarios include:
 | ||||||
|  |  | ||||||
|  | @ -99,12 +99,7 @@ export class WorkerMain extends ProcessRunner { | ||||||
|   private _stop(): Promise<void> { |   private _stop(): Promise<void> { | ||||||
|     if (!this._isStopped) { |     if (!this._isStopped) { | ||||||
|       this._isStopped = true; |       this._isStopped = true; | ||||||
| 
 |       this._currentTest?._interrupt(); | ||||||
|       // Interrupt current action.
 |  | ||||||
|       this._currentTest?._timeoutManager.interrupt(); |  | ||||||
| 
 |  | ||||||
|       if (this._currentTest && this._currentTest.status === 'passed') |  | ||||||
|         this._currentTest.status = 'interrupted'; |  | ||||||
|     } |     } | ||||||
|     return this._runFinished; |     return this._runFinished; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  | @ -394,6 +394,24 @@ test('test.{skip,fixme} should define a skipped test', async ({ runInlineTest }) | ||||||
|   expect(result.output).not.toContain('%%dontseethis'); |   expect(result.output).not.toContain('%%dontseethis'); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | test('should report unhandled error during test and not report timeout', async ({ runInlineTest }) => { | ||||||
|  |   const result = await runInlineTest({ | ||||||
|  |     'a.test.ts': ` | ||||||
|  |       import { test, expect } from '@playwright/test'; | ||||||
|  |       test('unhandled rejection', async () => { | ||||||
|  |         setTimeout(() => { | ||||||
|  |           throw new Error('Unhandled'); | ||||||
|  |         }, 0); | ||||||
|  |         await new Promise(f => setTimeout(f, 100)); | ||||||
|  |       }); | ||||||
|  |     `,
 | ||||||
|  |   }); | ||||||
|  |   expect(result.exitCode).toBe(1); | ||||||
|  |   expect(result.failed).toBe(1); | ||||||
|  |   expect(result.output).toContain('Error: Unhandled'); | ||||||
|  |   expect(result.output).not.toContain('Test timeout of 30000ms exceeded'); | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| test('should report unhandled rejection during worker shutdown', async ({ runInlineTest }) => { | test('should report unhandled rejection during worker shutdown', async ({ runInlineTest }) => { | ||||||
|   const result = await runInlineTest({ |   const result = await runInlineTest({ | ||||||
|     'a.test.ts': ` |     'a.test.ts': ` | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue