mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore(test runner): allow TestInfoImpl without a TestCase (#29534)
This will be useful to run `beforeAll`/`afterAll` hooks with a separate `TestInfo` instance, as well as run use helpers like `_runAndFailOnError()` during scope teardown.
This commit is contained in:
		
							parent
							
								
									dbf0b25146
								
							
						
					
					
						commit
						269a293ba1
					
				| @ -17,7 +17,7 @@ | |||||||
| import { formatLocation, debugTest, filterStackFile } from '../util'; | import { formatLocation, debugTest, filterStackFile } from '../util'; | ||||||
| import { ManualPromise } from 'playwright-core/lib/utils'; | import { ManualPromise } from 'playwright-core/lib/utils'; | ||||||
| import type { TestInfoImpl, TestStepInternal } from './testInfo'; | import type { TestInfoImpl, TestStepInternal } from './testInfo'; | ||||||
| import type { FixtureDescription, TimeoutManager } from './timeoutManager'; | import type { FixtureDescription } from './timeoutManager'; | ||||||
| import { fixtureParameterNames, type FixturePool, type FixtureRegistration, type FixtureScope } from '../common/fixtures'; | import { fixtureParameterNames, type FixturePool, type FixtureRegistration, type FixtureScope } from '../common/fixtures'; | ||||||
| import type { WorkerInfo } from '../../types/test'; | import type { WorkerInfo } from '../../types/test'; | ||||||
| import type { Location } from '../../types/testReporter'; | import type { Location } from '../../types/testReporter'; | ||||||
| @ -136,21 +136,21 @@ class Fixture { | |||||||
|     testInfo._timeoutManager.setCurrentFixture(undefined); |     testInfo._timeoutManager.setCurrentFixture(undefined); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async teardown(timeoutManager: TimeoutManager) { |   async teardown(testInfo: TestInfoImpl) { | ||||||
|     if (this._teardownWithDepsComplete) { |     if (this._teardownWithDepsComplete) { | ||||||
|       // When we are waiting for the teardown for the second time,
 |       // When we are waiting for the teardown for the second time,
 | ||||||
|       // most likely after the first time did timeout, annotate current fixture
 |       // most likely after the first time did timeout, annotate current fixture
 | ||||||
|       // for better error messages.
 |       // for better error messages.
 | ||||||
|       this._setTeardownDescription(timeoutManager); |       this._setTeardownDescription(testInfo); | ||||||
|       await this._teardownWithDepsComplete; |       await this._teardownWithDepsComplete; | ||||||
|       timeoutManager.setCurrentFixture(undefined); |       testInfo._timeoutManager.setCurrentFixture(undefined); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|     this._teardownWithDepsComplete = this._teardownInternal(timeoutManager); |     this._teardownWithDepsComplete = this._teardownInternal(testInfo); | ||||||
|     await this._teardownWithDepsComplete; |     await this._teardownWithDepsComplete; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private async _teardownInternal(timeoutManager: TimeoutManager) { |   private async _teardownInternal(testInfo: TestInfoImpl) { | ||||||
|     if (typeof this.registration.fn !== 'function') |     if (typeof this.registration.fn !== 'function') | ||||||
|       return; |       return; | ||||||
|     try { |     try { | ||||||
| @ -161,10 +161,10 @@ class Fixture { | |||||||
|       } |       } | ||||||
|       if (this._useFuncFinished) { |       if (this._useFuncFinished) { | ||||||
|         debugTest(`teardown ${this.registration.name}`); |         debugTest(`teardown ${this.registration.name}`); | ||||||
|         this._setTeardownDescription(timeoutManager); |         this._setTeardownDescription(testInfo); | ||||||
|         this._useFuncFinished.resolve(); |         this._useFuncFinished.resolve(); | ||||||
|         await this._selfTeardownComplete; |         await this._selfTeardownComplete; | ||||||
|         timeoutManager.setCurrentFixture(undefined); |         testInfo._timeoutManager.setCurrentFixture(undefined); | ||||||
|       } |       } | ||||||
|     } finally { |     } finally { | ||||||
|       for (const dep of this._deps) |       for (const dep of this._deps) | ||||||
| @ -173,9 +173,9 @@ class Fixture { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   private _setTeardownDescription(timeoutManager: TimeoutManager) { |   private _setTeardownDescription(testInfo: TestInfoImpl) { | ||||||
|     this._runnableDescription.phase = 'teardown'; |     this._runnableDescription.phase = 'teardown'; | ||||||
|     timeoutManager.setCurrentFixture(this._runnableDescription); |     testInfo._timeoutManager.setCurrentFixture(this._runnableDescription); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   _collectFixturesInTeardownOrder(scope: FixtureScope, collector: Set<Fixture>) { |   _collectFixturesInTeardownOrder(scope: FixtureScope, collector: Set<Fixture>) { | ||||||
| @ -206,14 +206,14 @@ export class FixtureRunner { | |||||||
|     this.pool = pool; |     this.pool = pool; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   async teardownScope(scope: FixtureScope, timeoutManager: TimeoutManager, onFixtureError: (error: Error) => void) { |   async teardownScope(scope: FixtureScope, testInfo: TestInfoImpl, onFixtureError: (error: Error) => void) { | ||||||
|     // Teardown fixtures in the reverse order.
 |     // Teardown fixtures in the reverse order.
 | ||||||
|     const fixtures = Array.from(this.instanceForId.values()).reverse(); |     const fixtures = Array.from(this.instanceForId.values()).reverse(); | ||||||
|     const collector = new Set<Fixture>(); |     const collector = new Set<Fixture>(); | ||||||
|     for (const fixture of fixtures) |     for (const fixture of fixtures) | ||||||
|       fixture._collectFixturesInTeardownOrder(scope, collector); |       fixture._collectFixturesInTeardownOrder(scope, collector); | ||||||
|     for (const fixture of collector) |     for (const fixture of collector) | ||||||
|       await fixture.teardown(timeoutManager).catch(onFixtureError); |       await fixture.teardown(testInfo).catch(onFixtureError); | ||||||
|     if (scope === 'test') |     if (scope === 'test') | ||||||
|       this.testScopeClean = true; |       this.testScopeClean = true; | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -52,7 +52,6 @@ export class TestInfoImpl implements TestInfo { | |||||||
|   private _onStepBegin: (payload: StepBeginPayload) => void; |   private _onStepBegin: (payload: StepBeginPayload) => void; | ||||||
|   private _onStepEnd: (payload: StepEndPayload) => void; |   private _onStepEnd: (payload: StepEndPayload) => void; | ||||||
|   private _onAttach: (payload: AttachmentPayload) => void; |   private _onAttach: (payload: AttachmentPayload) => void; | ||||||
|   readonly _test: TestCase; |  | ||||||
|   readonly _timeoutManager: TimeoutManager; |   readonly _timeoutManager: TimeoutManager; | ||||||
|   readonly _startTime: number; |   readonly _startTime: number; | ||||||
|   readonly _startWallTime: number; |   readonly _startWallTime: number; | ||||||
| @ -62,6 +61,7 @@ export class TestInfoImpl implements TestInfo { | |||||||
|   _didTimeout = false; |   _didTimeout = false; | ||||||
|   _wasInterrupted = false; |   _wasInterrupted = false; | ||||||
|   _lastStepId = 0; |   _lastStepId = 0; | ||||||
|  |   private readonly _requireFile: string; | ||||||
|   readonly _projectInternal: FullProjectInternal; |   readonly _projectInternal: FullProjectInternal; | ||||||
|   readonly _configInternal: FullConfigInternal; |   readonly _configInternal: FullConfigInternal; | ||||||
|   readonly _steps: TestStepInternal[] = []; |   readonly _steps: TestStepInternal[] = []; | ||||||
| @ -129,19 +129,19 @@ export class TestInfoImpl implements TestInfo { | |||||||
|     configInternal: FullConfigInternal, |     configInternal: FullConfigInternal, | ||||||
|     projectInternal: FullProjectInternal, |     projectInternal: FullProjectInternal, | ||||||
|     workerParams: WorkerInitParams, |     workerParams: WorkerInitParams, | ||||||
|     test: TestCase, |     test: TestCase | undefined, | ||||||
|     retry: number, |     retry: number, | ||||||
|     onStepBegin: (payload: StepBeginPayload) => void, |     onStepBegin: (payload: StepBeginPayload) => void, | ||||||
|     onStepEnd: (payload: StepEndPayload) => void, |     onStepEnd: (payload: StepEndPayload) => void, | ||||||
|     onAttach: (payload: AttachmentPayload) => void, |     onAttach: (payload: AttachmentPayload) => void, | ||||||
|   ) { |   ) { | ||||||
|     this._test = test; |     this.testId = test?.id ?? ''; | ||||||
|     this.testId = test.id; |  | ||||||
|     this._onStepBegin = onStepBegin; |     this._onStepBegin = onStepBegin; | ||||||
|     this._onStepEnd = onStepEnd; |     this._onStepEnd = onStepEnd; | ||||||
|     this._onAttach = onAttach; |     this._onAttach = onAttach; | ||||||
|     this._startTime = monotonicTime(); |     this._startTime = monotonicTime(); | ||||||
|     this._startWallTime = Date.now(); |     this._startWallTime = Date.now(); | ||||||
|  |     this._requireFile = test?._requireFile ?? ''; | ||||||
| 
 | 
 | ||||||
|     this.repeatEachIndex = workerParams.repeatEachIndex; |     this.repeatEachIndex = workerParams.repeatEachIndex; | ||||||
|     this.retry = retry; |     this.retry = retry; | ||||||
| @ -151,20 +151,20 @@ export class TestInfoImpl implements TestInfo { | |||||||
|     this.project = projectInternal.project; |     this.project = projectInternal.project; | ||||||
|     this._configInternal = configInternal; |     this._configInternal = configInternal; | ||||||
|     this.config = configInternal.config; |     this.config = configInternal.config; | ||||||
|     this.title = test.title; |     this.title = test?.title ?? ''; | ||||||
|     this.titlePath = test.titlePath(); |     this.titlePath = test?.titlePath() ?? []; | ||||||
|     this.file = test.location.file; |     this.file = test?.location.file ?? ''; | ||||||
|     this.line = test.location.line; |     this.line = test?.location.line ?? 0; | ||||||
|     this.column = test.location.column; |     this.column = test?.location.column ?? 0; | ||||||
|     this.fn = test.fn; |     this.fn = test?.fn ?? (() => {}); | ||||||
|     this.expectedStatus = test.expectedStatus; |     this.expectedStatus = test?.expectedStatus ?? 'skipped'; | ||||||
| 
 | 
 | ||||||
|     this._timeoutManager = new TimeoutManager(this.project.timeout); |     this._timeoutManager = new TimeoutManager(this.project.timeout); | ||||||
| 
 | 
 | ||||||
|     this.outputDir = (() => { |     this.outputDir = (() => { | ||||||
|       const relativeTestFilePath = path.relative(this.project.testDir, test._requireFile.replace(/\.(spec|test)\.(js|ts|mjs)$/, '')); |       const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile.replace(/\.(spec|test)\.(js|ts|mjs)$/, '')); | ||||||
|       const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-'); |       const sanitizedRelativePath = relativeTestFilePath.replace(process.platform === 'win32' ? new RegExp('\\\\', 'g') : new RegExp('/', 'g'), '-'); | ||||||
|       const fullTitleWithoutSpec = test.titlePath().slice(1).join(' '); |       const fullTitleWithoutSpec = this.titlePath.slice(1).join(' '); | ||||||
| 
 | 
 | ||||||
|       let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec)); |       let testOutputDir = trimLongString(sanitizedRelativePath + '-' + sanitizeForFilePath(fullTitleWithoutSpec)); | ||||||
|       if (projectInternal.id) |       if (projectInternal.id) | ||||||
| @ -177,7 +177,7 @@ export class TestInfoImpl implements TestInfo { | |||||||
|     })(); |     })(); | ||||||
| 
 | 
 | ||||||
|     this.snapshotDir = (() => { |     this.snapshotDir = (() => { | ||||||
|       const relativeTestFilePath = path.relative(this.project.testDir, test._requireFile); |       const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile); | ||||||
|       return path.join(this.project.snapshotDir, relativeTestFilePath + '-snapshots'); |       return path.join(this.project.snapshotDir, relativeTestFilePath + '-snapshots'); | ||||||
|     })(); |     })(); | ||||||
| 
 | 
 | ||||||
| @ -328,7 +328,7 @@ export class TestInfoImpl implements TestInfo { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const payload: StepEndPayload = { |         const payload: StepEndPayload = { | ||||||
|           testId: this._test.id, |           testId: this.testId, | ||||||
|           stepId, |           stepId, | ||||||
|           wallTime: step.endWallTime, |           wallTime: step.endWallTime, | ||||||
|           error: step.error, |           error: step.error, | ||||||
| @ -344,7 +344,7 @@ export class TestInfoImpl implements TestInfo { | |||||||
|     const parentStepList = parentStep ? parentStep.steps : this._steps; |     const parentStepList = parentStep ? parentStep.steps : this._steps; | ||||||
|     parentStepList.push(step); |     parentStepList.push(step); | ||||||
|     const payload: StepBeginPayload = { |     const payload: StepBeginPayload = { | ||||||
|       testId: this._test.id, |       testId: this.testId, | ||||||
|       stepId, |       stepId, | ||||||
|       parentStepId: parentStep ? parentStep.stepId : undefined, |       parentStepId: parentStep ? parentStep.stepId : undefined, | ||||||
|       title: data.title, |       title: data.title, | ||||||
| @ -434,7 +434,7 @@ export class TestInfoImpl implements TestInfo { | |||||||
|     }); |     }); | ||||||
|     this._attachmentsPush(attachment); |     this._attachmentsPush(attachment); | ||||||
|     this._onAttach({ |     this._onAttach({ | ||||||
|       testId: this._test.id, |       testId: this.testId, | ||||||
|       name: attachment.name, |       name: attachment.name, | ||||||
|       contentType: attachment.contentType, |       contentType: attachment.contentType, | ||||||
|       path: attachment.path, |       path: attachment.path, | ||||||
| @ -465,7 +465,7 @@ export class TestInfoImpl implements TestInfo { | |||||||
|   snapshotPath(...pathSegments: string[]) { |   snapshotPath(...pathSegments: string[]) { | ||||||
|     const subPath = path.join(...pathSegments); |     const subPath = path.join(...pathSegments); | ||||||
|     const parsedSubPath = path.parse(subPath); |     const parsedSubPath = path.parse(subPath); | ||||||
|     const relativeTestFilePath = path.relative(this.project.testDir, this._test._requireFile); |     const relativeTestFilePath = path.relative(this.project.testDir, this._requireFile); | ||||||
|     const parsedRelativeTestFilePath = path.parse(relativeTestFilePath); |     const parsedRelativeTestFilePath = path.parse(relativeTestFilePath); | ||||||
|     const projectNamePathSegment = sanitizeForFilePath(this.project.name); |     const projectNamePathSegment = sanitizeForFilePath(this.project.name); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -24,7 +24,6 @@ import type { Annotation, FullConfigInternal, FullProjectInternal } from '../com | |||||||
| import { FixtureRunner } from './fixtureRunner'; | import { FixtureRunner } from './fixtureRunner'; | ||||||
| import { ManualPromise, gracefullyCloseAll, removeFolders } from 'playwright-core/lib/utils'; | import { ManualPromise, gracefullyCloseAll, removeFolders } from 'playwright-core/lib/utils'; | ||||||
| import { TestInfoImpl } from './testInfo'; | import { TestInfoImpl } from './testInfo'; | ||||||
| import { TimeoutManager } from './timeoutManager'; |  | ||||||
| import { ProcessRunner } from '../common/process'; | import { ProcessRunner } from '../common/process'; | ||||||
| import { loadTestFile } from '../common/testLoader'; | import { loadTestFile } from '../common/testLoader'; | ||||||
| import { applyRepeatEachIndex, bindFileSuiteToProject, filterTestsRemoveEmptySuites } from '../common/suiteUtils'; | import { applyRepeatEachIndex, bindFileSuiteToProject, filterTestsRemoveEmptySuites } from '../common/suiteUtils'; | ||||||
| @ -53,7 +52,7 @@ export class WorkerMain extends ProcessRunner { | |||||||
|   // This promise resolves once the single "run test group" call finishes.
 |   // This promise resolves once the single "run test group" call finishes.
 | ||||||
|   private _runFinished = new ManualPromise<void>(); |   private _runFinished = new ManualPromise<void>(); | ||||||
|   private _currentTest: TestInfoImpl | null = null; |   private _currentTest: TestInfoImpl | null = null; | ||||||
|   private _lastRunningTests: TestInfoImpl[] = []; |   private _lastRunningTests: TestCase[] = []; | ||||||
|   private _totalRunningTests = 0; |   private _totalRunningTests = 0; | ||||||
|   // Suites that had their beforeAll hooks, but not afterAll hooks executed.
 |   // Suites that had their beforeAll hooks, but not afterAll hooks executed.
 | ||||||
|   // These suites still need afterAll hooks to be executed for the proper cleanup.
 |   // These suites still need afterAll hooks to be executed for the proper cleanup.
 | ||||||
| @ -129,7 +128,7 @@ export class WorkerMain extends ProcessRunner { | |||||||
|       '', |       '', | ||||||
|       '', |       '', | ||||||
|       colors.red(`Failed worker ran ${count}${lastMessage}:`), |       colors.red(`Failed worker ran ${count}${lastMessage}:`), | ||||||
|       ...this._lastRunningTests.map(testInfo => formatTestTitle(testInfo._test, testInfo.project.name)), |       ...this._lastRunningTests.map(test => formatTestTitle(test, this._project.project.name)), | ||||||
|     ].join('\n'); |     ].join('\n'); | ||||||
|     if (error.message) { |     if (error.message) { | ||||||
|       if (error.stack) { |       if (error.stack) { | ||||||
| @ -153,7 +152,7 @@ export class WorkerMain extends ProcessRunner { | |||||||
| 
 | 
 | ||||||
|   private async _teardownScopeAndReturnFirstError(scope: FixtureScope, testInfo: TestInfoImpl): Promise<Error | undefined> { |   private async _teardownScopeAndReturnFirstError(scope: FixtureScope, testInfo: TestInfoImpl): Promise<Error | undefined> { | ||||||
|     let error: Error | undefined; |     let error: Error | undefined; | ||||||
|     await this._fixtureRunner.teardownScope(scope, testInfo._timeoutManager, e => { |     await this._fixtureRunner.teardownScope(scope, testInfo, e => { | ||||||
|       testInfo._failWithError(e, true, false); |       testInfo._failWithError(e, true, false); | ||||||
|       if (error === undefined) |       if (error === undefined) | ||||||
|         error = e; |         error = e; | ||||||
| @ -163,15 +162,14 @@ export class WorkerMain extends ProcessRunner { | |||||||
| 
 | 
 | ||||||
|   private async _teardownScopes() { |   private async _teardownScopes() { | ||||||
|     // TODO: separate timeout for teardown?
 |     // TODO: separate timeout for teardown?
 | ||||||
|     const timeoutManager = new TimeoutManager(this._project.project.timeout); |     const fakeTestInfo = new TestInfoImpl(this._config, this._project, this._params, undefined, 0, () => {}, () => {}, () => {}); | ||||||
|     await timeoutManager.withRunnable({ type: 'teardown' }, async () => { |     await fakeTestInfo._timeoutManager.withRunnable({ type: 'teardown' }, async () => { | ||||||
|       const timeoutError = await timeoutManager.runWithTimeout(async () => { |       await fakeTestInfo._runWithTimeout(async () => { | ||||||
|         await this._fixtureRunner.teardownScope('test', timeoutManager, e => this._fatalErrors.push(serializeError(e))); |         await this._teardownScopeAndReturnFirstError('test', fakeTestInfo); | ||||||
|         await this._fixtureRunner.teardownScope('worker', timeoutManager, e => this._fatalErrors.push(serializeError(e))); |         await this._teardownScopeAndReturnFirstError('worker', fakeTestInfo); | ||||||
|       }); |       }); | ||||||
|       if (timeoutError) |  | ||||||
|         this._fatalErrors.push(serializeError(timeoutError)); |  | ||||||
|     }); |     }); | ||||||
|  |     this._fatalErrors.push(...fakeTestInfo.errors); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   unhandledError(error: Error | any) { |   unhandledError(error: Error | any) { | ||||||
| @ -322,7 +320,7 @@ export class WorkerMain extends ProcessRunner { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     this._totalRunningTests++; |     this._totalRunningTests++; | ||||||
|     this._lastRunningTests.push(testInfo); |     this._lastRunningTests.push(test); | ||||||
|     if (this._lastRunningTests.length > 10) |     if (this._lastRunningTests.length > 10) | ||||||
|       this._lastRunningTests.shift(); |       this._lastRunningTests.shift(); | ||||||
|     let didFailBeforeAllForSuite: Suite | undefined; |     let didFailBeforeAllForSuite: Suite | undefined; | ||||||
| @ -603,14 +601,14 @@ export class WorkerMain extends ProcessRunner { | |||||||
| 
 | 
 | ||||||
| function buildTestBeginPayload(testInfo: TestInfoImpl): TestBeginPayload { | function buildTestBeginPayload(testInfo: TestInfoImpl): TestBeginPayload { | ||||||
|   return { |   return { | ||||||
|     testId: testInfo._test.id, |     testId: testInfo.testId, | ||||||
|     startWallTime: testInfo._startWallTime, |     startWallTime: testInfo._startWallTime, | ||||||
|   }; |   }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function buildTestEndPayload(testInfo: TestInfoImpl): TestEndPayload { | function buildTestEndPayload(testInfo: TestInfoImpl): TestEndPayload { | ||||||
|   return { |   return { | ||||||
|     testId: testInfo._test.id, |     testId: testInfo.testId, | ||||||
|     duration: testInfo.duration, |     duration: testInfo.duration, | ||||||
|     status: testInfo.status!, |     status: testInfo.status!, | ||||||
|     errors: testInfo.errors, |     errors: testInfo.errors, | ||||||
|  | |||||||
| @ -42,13 +42,10 @@ test('should have correct tags', async ({ runInlineTest }) => { | |||||||
|     'stdio.spec.js': ` |     'stdio.spec.js': ` | ||||||
|       import { test, expect } from '@playwright/test'; |       import { test, expect } from '@playwright/test'; | ||||||
|       test('no-tags', () => { |       test('no-tags', () => { | ||||||
|         expect(test.info()._test.tags).toEqual([]); |  | ||||||
|       }); |       }); | ||||||
|       test('foo-tag @inline', { tag: '@foo' }, () => { |       test('foo-tag @inline', { tag: '@foo' }, () => { | ||||||
|         expect(test.info()._test.tags).toEqual(['@inline', '@foo']); |  | ||||||
|       }); |       }); | ||||||
|       test('foo-bar-tags', { tag: ['@foo', '@bar'] }, () => { |       test('foo-bar-tags', { tag: ['@foo', '@bar'] }, () => { | ||||||
|         expect(test.info()._test.tags).toEqual(['@foo', '@bar']); |  | ||||||
|       }); |       }); | ||||||
|       test.skip('skip-foo-tag', { tag: '@foo' }, () => { |       test.skip('skip-foo-tag', { tag: '@foo' }, () => { | ||||||
|       }); |       }); | ||||||
| @ -59,11 +56,9 @@ test('should have correct tags', async ({ runInlineTest }) => { | |||||||
|       }); |       }); | ||||||
|       test.describe('suite @inline', { tag: '@foo' }, () => { |       test.describe('suite @inline', { tag: '@foo' }, () => { | ||||||
|         test('foo-suite', () => { |         test('foo-suite', () => { | ||||||
|           expect(test.info()._test.tags).toEqual(['@inline', '@foo']); |  | ||||||
|         }); |         }); | ||||||
|         test.describe('inner', { tag: '@bar' }, () => { |         test.describe('inner', { tag: '@bar' }, () => { | ||||||
|           test('foo-bar-suite', () => { |           test('foo-bar-suite', () => { | ||||||
|             expect(test.info()._test.tags).toEqual(['@inline', '@foo', '@bar']); |  | ||||||
|           }); |           }); | ||||||
|         }); |         }); | ||||||
|       }); |       }); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user