| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Copyright Microsoft Corporation. All rights reserved. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Licensed under the Apache License, Version 2.0 (the "License"); | 
					
						
							|  |  |  |  * you may not use this file except in compliance with the License. | 
					
						
							|  |  |  |  * You may obtain a copy of the License at | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  *     http://www.apache.org/licenses/LICENSE-2.0
 | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Unless required by applicable law or agreed to in writing, software | 
					
						
							|  |  |  |  * distributed under the License is distributed on an "AS IS" BASIS, | 
					
						
							|  |  |  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
					
						
							|  |  |  |  * See the License for the specific language governing permissions and | 
					
						
							|  |  |  |  * limitations under the License. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import fs from 'fs'; | 
					
						
							|  |  |  | import path from 'path'; | 
					
						
							| 
									
										
										
										
											2022-04-05 08:34:51 -07:00
										 |  |  | import type { TestError, TestInfo, TestStatus } from '../types/test'; | 
					
						
							|  |  |  | import type { FullConfigInternal, FullProjectInternal } from './types'; | 
					
						
							| 
									
										
										
										
											2022-04-06 13:57:14 -08:00
										 |  |  | import type { WorkerInitParams } from './ipc'; | 
					
						
							|  |  |  | import type { Loader } from './loader'; | 
					
						
							|  |  |  | import type { TestCase } from './test'; | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  | import { TimeoutManager } from './timeoutManager'; | 
					
						
							| 
									
										
										
										
											2022-04-06 13:57:14 -08:00
										 |  |  | import type { Annotation, TestStepInternal } from './types'; | 
					
						
							| 
									
										
										
										
											2022-04-08 13:22:14 -07:00
										 |  |  | import { addSuffixToFilePath, getContainedPath, monotonicTime, normalizeAndSaveAttachment, sanitizeForFilePath, serializeError, trimLongString } from './util'; | 
					
						
							| 
									
										
										
										
											2022-03-08 16:35:14 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  | export class TestInfoImpl implements TestInfo { | 
					
						
							|  |  |  |   private _addStepImpl: (data: Omit<TestStepInternal, 'complete'>) => TestStepInternal; | 
					
						
							|  |  |  |   readonly _test: TestCase; | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  |   readonly _timeoutManager: TimeoutManager; | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   readonly _startTime: number; | 
					
						
							|  |  |  |   readonly _startWallTime: number; | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  |   private _hasHardError: boolean = false; | 
					
						
							| 
									
										
										
										
											2022-03-10 17:50:26 -07:00
										 |  |  |   readonly _screenshotsDir: string; | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // ------------ TestInfo fields ------------
 | 
					
						
							|  |  |  |   readonly repeatEachIndex: number; | 
					
						
							|  |  |  |   readonly retry: number; | 
					
						
							|  |  |  |   readonly workerIndex: number; | 
					
						
							|  |  |  |   readonly parallelIndex: number; | 
					
						
							| 
									
										
										
										
											2022-04-05 08:34:51 -07:00
										 |  |  |   readonly project: FullProjectInternal; | 
					
						
							| 
									
										
										
										
											2022-03-28 15:53:42 -07:00
										 |  |  |   config: FullConfigInternal; | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   readonly title: string; | 
					
						
							|  |  |  |   readonly titlePath: string[]; | 
					
						
							|  |  |  |   readonly file: string; | 
					
						
							|  |  |  |   readonly line: number; | 
					
						
							|  |  |  |   readonly column: number; | 
					
						
							|  |  |  |   readonly fn: Function; | 
					
						
							|  |  |  |   expectedStatus: TestStatus; | 
					
						
							|  |  |  |   duration: number = 0; | 
					
						
							| 
									
										
										
										
											2022-03-08 16:35:14 -08:00
										 |  |  |   readonly annotations: Annotation[] = []; | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   readonly attachments: TestInfo['attachments'] = []; | 
					
						
							|  |  |  |   status: TestStatus = 'passed'; | 
					
						
							|  |  |  |   readonly stdout: TestInfo['stdout'] = []; | 
					
						
							|  |  |  |   readonly stderr: TestInfo['stderr'] = []; | 
					
						
							|  |  |  |   snapshotSuffix: string = ''; | 
					
						
							|  |  |  |   readonly outputDir: string; | 
					
						
							|  |  |  |   readonly snapshotDir: string; | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  |   errors: TestError[] = []; | 
					
						
							| 
									
										
										
										
											2022-04-05 16:47:35 -08:00
										 |  |  |   currentStep: TestStepInternal | undefined; | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |   get error(): TestError | undefined { | 
					
						
							|  |  |  |     return this.errors.length > 0 ? this.errors[0] : undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   set error(e: TestError | undefined) { | 
					
						
							|  |  |  |     if (e === undefined) | 
					
						
							|  |  |  |       throw new Error('Cannot assign testInfo.error undefined value!'); | 
					
						
							|  |  |  |     if (!this.errors.length) | 
					
						
							|  |  |  |       this.errors.push(e); | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |       this.errors[0] = e; | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  |   get timeout(): number { | 
					
						
							|  |  |  |     return this._timeoutManager.defaultTimeout(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   set timeout(timeout: number) { | 
					
						
							|  |  |  |     // Ignored.
 | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   constructor( | 
					
						
							|  |  |  |     loader: Loader, | 
					
						
							| 
									
										
										
										
											2022-05-03 13:36:24 +01:00
										 |  |  |     project: FullProjectInternal, | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |     workerParams: WorkerInitParams, | 
					
						
							|  |  |  |     test: TestCase, | 
					
						
							|  |  |  |     retry: number, | 
					
						
							|  |  |  |     addStepImpl: (data: Omit<TestStepInternal, 'complete'>) => TestStepInternal, | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     this._test = test; | 
					
						
							|  |  |  |     this._addStepImpl = addStepImpl; | 
					
						
							|  |  |  |     this._startTime = monotonicTime(); | 
					
						
							|  |  |  |     this._startWallTime = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.repeatEachIndex = workerParams.repeatEachIndex; | 
					
						
							|  |  |  |     this.retry = retry; | 
					
						
							|  |  |  |     this.workerIndex = workerParams.workerIndex; | 
					
						
							|  |  |  |     this.parallelIndex =  workerParams.parallelIndex; | 
					
						
							| 
									
										
										
										
											2022-05-03 13:36:24 +01:00
										 |  |  |     this.project = project; | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |     this.config = loader.fullConfig(); | 
					
						
							|  |  |  |     this.title = test.title; | 
					
						
							|  |  |  |     this.titlePath = test.titlePath(); | 
					
						
							|  |  |  |     this.file = test.location.file; | 
					
						
							|  |  |  |     this.line = test.location.line; | 
					
						
							|  |  |  |     this.column = test.location.column; | 
					
						
							|  |  |  |     this.fn = test.fn; | 
					
						
							|  |  |  |     this.expectedStatus = test.expectedStatus; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  |     this._timeoutManager = new TimeoutManager(this.project.timeout); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.outputDir = (() => { | 
					
						
							| 
									
										
										
										
											2022-04-29 15:05:08 -08:00
										 |  |  |       const sameName = loader.fullConfig().projects.filter(project => project.name === this.project.name); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |       let uniqueProjectNamePathSegment: string; | 
					
						
							|  |  |  |       if (sameName.length > 1) | 
					
						
							| 
									
										
										
										
											2022-05-03 13:36:24 +01:00
										 |  |  |         uniqueProjectNamePathSegment = this.project.name + (sameName.indexOf(this.project) + 1); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |       else | 
					
						
							|  |  |  |         uniqueProjectNamePathSegment = this.project.name; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const relativeTestFilePath = path.relative(this.project.testDir, test._requireFile.replace(/\.(spec|test)\.(js|ts|mjs)$/, '')); | 
					
						
							|  |  |  |       const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-'); | 
					
						
							| 
									
										
										
										
											2022-03-08 16:35:14 -08:00
										 |  |  |       const fullTitleWithoutSpec = test.titlePath().slice(1).join(' '); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-18 15:40:36 -08:00
										 |  |  |       let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec)); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |       if (uniqueProjectNamePathSegment) | 
					
						
							|  |  |  |         testOutputDir += '-' + sanitizeForFilePath(uniqueProjectNamePathSegment); | 
					
						
							|  |  |  |       if (this.retry) | 
					
						
							|  |  |  |         testOutputDir += '-retry' + this.retry; | 
					
						
							|  |  |  |       if (this.repeatEachIndex) | 
					
						
							|  |  |  |         testOutputDir += '-repeat' + this.repeatEachIndex; | 
					
						
							|  |  |  |       return path.join(this.project.outputDir, testOutputDir); | 
					
						
							|  |  |  |     })(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.snapshotDir = (() => { | 
					
						
							|  |  |  |       const relativeTestFilePath = path.relative(this.project.testDir, test._requireFile); | 
					
						
							|  |  |  |       return path.join(this.project.snapshotDir, relativeTestFilePath + '-snapshots'); | 
					
						
							|  |  |  |     })(); | 
					
						
							| 
									
										
										
										
											2022-03-10 17:50:26 -07:00
										 |  |  |     this._screenshotsDir = (() => { | 
					
						
							|  |  |  |       const relativeTestFilePath = path.relative(this.project.testDir, test._requireFile); | 
					
						
							| 
									
										
										
										
											2022-04-05 08:34:51 -07:00
										 |  |  |       return path.join(this.project._screenshotsDir, relativeTestFilePath); | 
					
						
							| 
									
										
										
										
											2022-03-10 17:50:26 -07:00
										 |  |  |     })(); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private _modifier(type: 'skip' | 'fail' | 'fixme' | 'slow', modifierArgs: [arg?: any, description?: string]) { | 
					
						
							|  |  |  |     if (typeof modifierArgs[1] === 'function') { | 
					
						
							|  |  |  |       throw new Error([ | 
					
						
							|  |  |  |         'It looks like you are calling test.skip() inside the test and pass a callback.', | 
					
						
							|  |  |  |         'Pass a condition instead and optional description instead:', | 
					
						
							|  |  |  |         `test('my test', async ({ page, isMobile }) => {`, | 
					
						
							|  |  |  |         `  test.skip(isMobile, 'This test is not applicable on mobile');`, | 
					
						
							|  |  |  |         `});`, | 
					
						
							|  |  |  |       ].join('\n')); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (modifierArgs.length >= 1 && !modifierArgs[0]) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const description = modifierArgs[1]; | 
					
						
							|  |  |  |     this.annotations.push({ type, description }); | 
					
						
							|  |  |  |     if (type === 'slow') { | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  |       this._timeoutManager.slow(); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |     } else if (type === 'skip' || type === 'fixme') { | 
					
						
							|  |  |  |       this.expectedStatus = 'skipped'; | 
					
						
							|  |  |  |       throw new SkipError('Test is skipped: ' + (description || '')); | 
					
						
							|  |  |  |     } else if (type === 'fail') { | 
					
						
							|  |  |  |       if (this.expectedStatus !== 'skipped') | 
					
						
							|  |  |  |         this.expectedStatus = 'failed'; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async _runWithTimeout(cb: () => Promise<any>): Promise<void> { | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  |     const timeoutError = await this._timeoutManager.runWithTimeout(cb); | 
					
						
							|  |  |  |     // Do not overwrite existing failure upon hook/teardown timeout.
 | 
					
						
							|  |  |  |     if (timeoutError && this.status === 'passed') { | 
					
						
							|  |  |  |       this.status = 'timedOut'; | 
					
						
							|  |  |  |       this.errors.push(timeoutError); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |     } | 
					
						
							|  |  |  |     this.duration = monotonicTime() - this._startTime; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async _runFn(fn: Function, skips?: 'allowSkips'): Promise<TestError | undefined> { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await fn(); | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       if (skips === 'allowSkips' && error instanceof SkipError) { | 
					
						
							|  |  |  |         if (this.status === 'passed') | 
					
						
							|  |  |  |           this.status = 'skipped'; | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         const serialized = serializeError(error); | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  |         this._failWithError(serialized, true /* isHardError */); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |         return serialized; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   _addStep(data: Omit<TestStepInternal, 'complete'>) { | 
					
						
							|  |  |  |     return this._addStepImpl(data); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  |   _failWithError(error: TestError, isHardError: boolean) { | 
					
						
							|  |  |  |     // Do not overwrite any previous hard errors.
 | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |     // Some (but not all) scenarios include:
 | 
					
						
							|  |  |  |     //   - expect() that fails after uncaught exception.
 | 
					
						
							|  |  |  |     //   - fail after the timeout, e.g. due to fixture teardown.
 | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  |     if (isHardError && this._hasHardError) | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     if (isHardError) | 
					
						
							|  |  |  |       this._hasHardError = true; | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |     if (this.status === 'passed') | 
					
						
							|  |  |  |       this.status = 'failed'; | 
					
						
							| 
									
										
										
										
											2022-02-02 19:33:51 -07:00
										 |  |  |     this.errors.push(error); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-17 19:33:01 -07:00
										 |  |  |   async _runAsStep<T>(cb: () => Promise<T>, stepInfo: Omit<TestStepInternal, 'complete'>): Promise<T> { | 
					
						
							|  |  |  |     const step = this._addStep(stepInfo); | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const result = await cb(); | 
					
						
							| 
									
										
										
										
											2022-03-30 20:52:00 -08:00
										 |  |  |       step.complete({}); | 
					
						
							| 
									
										
										
										
											2022-03-17 19:33:01 -07:00
										 |  |  |       return result; | 
					
						
							|  |  |  |     } catch (e) { | 
					
						
							| 
									
										
										
										
											2022-03-30 20:52:00 -08:00
										 |  |  |       step.complete({ error: e instanceof SkipError ? undefined : serializeError(e) }); | 
					
						
							| 
									
										
										
										
											2022-03-17 19:33:01 -07:00
										 |  |  |       throw e; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   // ------------ TestInfo methods ------------
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async attach(name: string, options: { path?: string, body?: string | Buffer, contentType?: string } = {}) { | 
					
						
							| 
									
										
										
										
											2022-04-08 13:22:14 -07:00
										 |  |  |     this.attachments.push(await normalizeAndSaveAttachment(this.outputPath(), name, options)); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   outputPath(...pathSegments: string[]){ | 
					
						
							|  |  |  |     fs.mkdirSync(this.outputDir, { recursive: true }); | 
					
						
							|  |  |  |     const joinedPath = path.join(...pathSegments); | 
					
						
							|  |  |  |     const outputPath = getContainedPath(this.outputDir, joinedPath); | 
					
						
							|  |  |  |     if (outputPath) | 
					
						
							|  |  |  |       return outputPath; | 
					
						
							|  |  |  |     throw new Error(`The outputPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\toutputPath: ${joinedPath}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   snapshotPath(...pathSegments: string[]) { | 
					
						
							|  |  |  |     let suffix = ''; | 
					
						
							|  |  |  |     const projectNamePathSegment = sanitizeForFilePath(this.project.name); | 
					
						
							|  |  |  |     if (projectNamePathSegment) | 
					
						
							|  |  |  |       suffix += '-' + projectNamePathSegment; | 
					
						
							|  |  |  |     if (this.snapshotSuffix) | 
					
						
							|  |  |  |       suffix += '-' + this.snapshotSuffix; | 
					
						
							|  |  |  |     const subPath = addSuffixToFilePath(path.join(...pathSegments), suffix); | 
					
						
							|  |  |  |     const snapshotPath =  getContainedPath(this.snapshotDir, subPath); | 
					
						
							|  |  |  |     if (snapshotPath) | 
					
						
							|  |  |  |       return snapshotPath; | 
					
						
							|  |  |  |     throw new Error(`The snapshotPath is not allowed outside of the parent directory. Please fix the defined path.\n\n\tsnapshotPath: ${subPath}`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-03-10 17:50:26 -07:00
										 |  |  |   _screenshotPath(...pathSegments: string[]) { | 
					
						
							|  |  |  |     const subPath = path.join(...pathSegments); | 
					
						
							|  |  |  |     const screenshotPath = getContainedPath(this._screenshotsDir, subPath); | 
					
						
							|  |  |  |     if (screenshotPath) | 
					
						
							|  |  |  |       return screenshotPath; | 
					
						
							|  |  |  |     throw new Error(`Screenshot name "${subPath}" should not point outside of the parent directory.`); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   skip(...args: [arg?: any, description?: string]) { | 
					
						
							|  |  |  |     this._modifier('skip', args); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fixme(...args: [arg?: any, description?: string]) { | 
					
						
							|  |  |  |     this._modifier('fixme', args); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   fail(...args: [arg?: any, description?: string]) { | 
					
						
							|  |  |  |     this._modifier('fail', args); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   slow(...args: [arg?: any, description?: string]) { | 
					
						
							|  |  |  |     this._modifier('slow', args); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setTimeout(timeout: number) { | 
					
						
							| 
									
										
										
										
											2022-03-17 09:36:03 -07:00
										 |  |  |     this._timeoutManager.setTimeout(timeout); | 
					
						
							| 
									
										
										
										
											2022-01-28 17:39:42 -08:00
										 |  |  |   } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class SkipError extends Error { | 
					
						
							|  |  |  | } |