16
16
17
17
import * as fs from 'fs' ;
18
18
import * as path from 'path' ;
19
- import type { APIRequestContext , BrowserContext , Browser , BrowserContextOptions , LaunchOptions , Page , Tracing , Video } from 'playwright-core' ;
20
- import * as playwrightLibrary from 'playwright-core' ;
19
+ import type { APIRequestContext , BrowserContext , Browser , BrowserContextOptions , LaunchOptions , Page , Tracing , Video , PageScreenshotOptions } from 'playwright-core' ;
21
20
import { createGuid , debugMode , addInternalStackPrefix , isString , asLocator , jsonStringifyForceASCII } from 'playwright-core/lib/utils' ;
22
21
import type { Fixtures , PlaywrightTestArgs , PlaywrightTestOptions , PlaywrightWorkerArgs , PlaywrightWorkerOptions , ScreenshotMode , TestInfo , TestType , VideoMode } from '../types/test' ;
23
22
import type { TestInfoImpl } from './worker/testInfo' ;
24
23
import { rootTestType } from './common/testType' ;
25
24
import type { ContextReuseMode } from './common/config' ;
26
25
import type { ClientInstrumentation , ClientInstrumentationListener } from '../../playwright-core/src/client/clientInstrumentation' ;
27
- import { currentTestInfo } from './common/globals' ;
26
+ import { currentTestInfo , setTestLifecycleInstrumentation , type TestLifecycleInstrumentation } from './common/globals' ;
28
27
export { expect } from './matchers/expect' ;
29
28
export const _baseTest : TestType < { } , { } > = rootTestType . test ;
30
29
@@ -45,11 +44,12 @@ if ((process as any)['__pw_initiator__']) {
45
44
type TestFixtures = PlaywrightTestArgs & PlaywrightTestOptions & {
46
45
_combinedContextOptions : BrowserContextOptions ,
47
46
_setupContextOptions : void ;
48
- _setupArtifacts : void ;
49
47
_contextFactory : ( options ?: BrowserContextOptions ) => Promise < BrowserContext > ;
50
48
} ;
51
49
52
50
type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
51
+ // Same as "playwright", but exposed so that our internal tests can override it.
52
+ _playwrightImpl : PlaywrightWorkerArgs [ 'playwright' ] ;
53
53
_browserOptions : LaunchOptions ;
54
54
_optionContextReuseMode : ContextReuseMode ,
55
55
_optionConnectOptions : PlaywrightWorkerOptions [ 'connectOptions' ] ,
@@ -59,9 +59,14 @@ type WorkerFixtures = PlaywrightWorkerArgs & PlaywrightWorkerOptions & {
59
59
const playwrightFixtures : Fixtures < TestFixtures , WorkerFixtures > = ( {
60
60
defaultBrowserType : [ 'chromium' , { scope : 'worker' , option : true } ] ,
61
61
browserName : [ ( { defaultBrowserType } , use ) => use ( defaultBrowserType ) , { scope : 'worker' , option : true } ] ,
62
- playwright : [ async ( { } , use ) => {
63
- await use ( require ( 'playwright-core' ) ) ;
62
+ _playwrightImpl : [ ( { } , use ) => use ( require ( 'playwright-core' ) ) , { scope : 'worker' } ] ,
63
+
64
+ playwright : [ async ( { _playwrightImpl, screenshot } , use ) => {
65
+ await connector . setPlaywright ( _playwrightImpl , screenshot ) ;
66
+ await use ( _playwrightImpl ) ;
67
+ await connector . setPlaywright ( undefined , screenshot ) ;
64
68
} , { scope : 'worker' , _hideStep : true } as any ] ,
69
+
65
70
headless : [ ( { launchOptions } , use ) => use ( launchOptions . headless ?? true ) , { scope : 'worker' , option : true } ] ,
66
71
channel : [ ( { launchOptions } , use ) => use ( launchOptions . channel ) , { scope : 'worker' , option : true } ] ,
67
72
launchOptions : [ { } , { scope : 'worker' , option : true } ] ,
@@ -222,7 +227,7 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
222
227
223
228
_setupContextOptions : [ async ( { playwright, _combinedContextOptions, actionTimeout, navigationTimeout, testIdAttribute } , use , testInfo ) => {
224
229
if ( testIdAttribute )
225
- playwrightLibrary . selectors . setTestIdAttribute ( testIdAttribute ) ;
230
+ playwright . selectors . setTestIdAttribute ( testIdAttribute ) ;
226
231
testInfo . snapshotSuffix = process . platform ;
227
232
if ( debugMode ( ) )
228
233
testInfo . setTimeout ( 0 ) ;
@@ -243,58 +248,6 @@ const playwrightFixtures: Fixtures<TestFixtures, WorkerFixtures> = ({
243
248
}
244
249
} , { auto : 'all-hooks-included' , _title : 'context configuration' } as any ] ,
245
250
246
- _setupArtifacts : [ async ( { playwright, screenshot } , use , testInfo ) => {
247
- const artifactsRecorder = new ArtifactsRecorder ( playwright , tracing ( ) . artifactsDir ( ) , screenshot ) ;
248
- await artifactsRecorder . willStartTest ( testInfo as TestInfoImpl ) ;
249
- const csiListener : ClientInstrumentationListener = {
250
- onApiCallBegin : ( apiName : string , params : Record < string , any > , frames : StackFrame [ ] , userData : any , out : { stepId ?: string } ) => {
251
- const testInfo = currentTestInfo ( ) ;
252
- if ( ! testInfo || apiName . includes ( 'setTestIdAttribute' ) )
253
- return { userObject : null } ;
254
- const step = testInfo . _addStep ( {
255
- location : frames [ 0 ] as any ,
256
- category : 'pw:api' ,
257
- title : renderApiCall ( apiName , params ) ,
258
- apiName,
259
- params,
260
- } ) ;
261
- userData . userObject = step ;
262
- out . stepId = step . stepId ;
263
- } ,
264
- onApiCallEnd : ( userData : any , error ?: Error ) => {
265
- const step = userData . userObject ;
266
- step ?. complete ( { error } ) ;
267
- } ,
268
- onWillPause : ( ) => {
269
- currentTestInfo ( ) ?. setTimeout ( 0 ) ;
270
- } ,
271
- runAfterCreateBrowserContext : async ( context : BrowserContext ) => {
272
- await artifactsRecorder ?. didCreateBrowserContext ( context ) ;
273
- const testInfo = currentTestInfo ( ) ;
274
- if ( testInfo )
275
- attachConnectedHeaderIfNeeded ( testInfo , context . browser ( ) ) ;
276
- } ,
277
- runAfterCreateRequestContext : async ( context : APIRequestContext ) => {
278
- await artifactsRecorder ?. didCreateRequestContext ( context ) ;
279
- } ,
280
- runBeforeCloseBrowserContext : async ( context : BrowserContext ) => {
281
- await artifactsRecorder ?. willCloseBrowserContext ( context ) ;
282
- } ,
283
- runBeforeCloseRequestContext : async ( context : APIRequestContext ) => {
284
- await artifactsRecorder ?. willCloseRequestContext ( context ) ;
285
- } ,
286
- } ;
287
-
288
- const clientInstrumentation = ( playwright as any ) . _instrumentation as ClientInstrumentation ;
289
- clientInstrumentation . addListener ( csiListener ) ;
290
-
291
- await use ( ) ;
292
-
293
- clientInstrumentation . removeListener ( csiListener ) ;
294
- await artifactsRecorder . didFinishTest ( ) ;
295
-
296
- } , { auto : 'all-hooks-included' , _title : 'trace recording' } as any ] ,
297
-
298
251
_contextFactory : [ async ( { browser, video, _reuseContext } , use , testInfo ) => {
299
252
const testInfoImpl = testInfo as TestInfoImpl ;
300
253
const videoMode = normalizeVideoMode ( video ) ;
@@ -471,7 +424,7 @@ class ArtifactsRecorder {
471
424
private _playwright : Playwright ;
472
425
private _artifactsDir : string ;
473
426
private _screenshotMode : ScreenshotMode ;
474
- private _screenshotOptions : { mode : ScreenshotMode } & Pick < playwrightLibrary . PageScreenshotOptions , 'fullPage' | 'omitBackground' > | undefined ;
427
+ private _screenshotOptions : { mode : ScreenshotMode } & Pick < PageScreenshotOptions , 'fullPage' | 'omitBackground' > | undefined ;
475
428
private _temporaryScreenshots : string [ ] = [ ] ;
476
429
private _temporaryArtifacts : string [ ] = [ ] ;
477
430
private _reusedContexts = new Set < BrowserContext > ( ) ;
@@ -496,7 +449,6 @@ class ArtifactsRecorder {
496
449
497
450
async willStartTest ( testInfo : TestInfoImpl ) {
498
451
this . _testInfo = testInfo ;
499
- testInfo . _onDidFinishTestFunction = ( ) => this . didFinishTestFunction ( ) ;
500
452
501
453
// Since beforeAll(s), test and afterAll(s) reuse the same TestInfo, make sure we do not
502
454
// overwrite previous screenshots.
@@ -678,6 +630,101 @@ function tracing() {
678
630
return ( test . info ( ) as TestInfoImpl ) . _tracing ;
679
631
}
680
632
633
+ class InstrumentationConnector implements TestLifecycleInstrumentation , ClientInstrumentationListener {
634
+ private _playwright : PlaywrightWorkerArgs [ 'playwright' ] | undefined ;
635
+ private _screenshot : ScreenshotOption = 'off' ;
636
+ private _artifactsRecorder : ArtifactsRecorder | undefined ;
637
+ private _testIsRunning = false ;
638
+
639
+ constructor ( ) {
640
+ setTestLifecycleInstrumentation ( this ) ;
641
+ }
642
+
643
+ async setPlaywright ( playwright : PlaywrightWorkerArgs [ 'playwright' ] | undefined , screenshot : ScreenshotOption ) {
644
+ if ( this . _playwright ) {
645
+ if ( this . _testIsRunning ) {
646
+ // When "playwright" is destroyed during a test, collect artifacts immediately.
647
+ await this . onTestEnd ( ) ;
648
+ }
649
+ const clientInstrumentation = ( this . _playwright as any ) . _instrumentation as ClientInstrumentation ;
650
+ clientInstrumentation . removeListener ( this ) ;
651
+ }
652
+ this . _playwright = playwright ;
653
+ this . _screenshot = screenshot ;
654
+ if ( this . _playwright ) {
655
+ const clientInstrumentation = ( this . _playwright as any ) . _instrumentation as ClientInstrumentation ;
656
+ clientInstrumentation . addListener ( this ) ;
657
+ if ( this . _testIsRunning ) {
658
+ // When "playwright" is created during a test, wire it up immediately.
659
+ await this . onTestBegin ( ) ;
660
+ }
661
+ }
662
+ }
663
+
664
+ async onTestBegin ( ) {
665
+ this . _testIsRunning = true ;
666
+ if ( this . _playwright ) {
667
+ this . _artifactsRecorder = new ArtifactsRecorder ( this . _playwright , tracing ( ) . artifactsDir ( ) , this . _screenshot ) ;
668
+ await this . _artifactsRecorder . willStartTest ( currentTestInfo ( ) as TestInfoImpl ) ;
669
+ }
670
+ }
671
+
672
+ async onTestFunctionEnd ( ) {
673
+ await this . _artifactsRecorder ?. didFinishTestFunction ( ) ;
674
+ }
675
+
676
+ async onTestEnd ( ) {
677
+ await this . _artifactsRecorder ?. didFinishTest ( ) ;
678
+ this . _artifactsRecorder = undefined ;
679
+ this . _testIsRunning = false ;
680
+ }
681
+
682
+ onApiCallBegin ( apiName : string , params : Record < string , any > , frames : StackFrame [ ] , userData : any , out : { stepId ?: string } ) {
683
+ const testInfo = currentTestInfo ( ) ;
684
+ if ( ! testInfo || apiName . includes ( 'setTestIdAttribute' ) )
685
+ return { userObject : null } ;
686
+ const step = testInfo . _addStep ( {
687
+ location : frames [ 0 ] as any ,
688
+ category : 'pw:api' ,
689
+ title : renderApiCall ( apiName , params ) ,
690
+ apiName,
691
+ params,
692
+ } ) ;
693
+ userData . userObject = step ;
694
+ out . stepId = step . stepId ;
695
+ }
696
+
697
+ onApiCallEnd ( userData : any , error ?: Error ) {
698
+ const step = userData . userObject ;
699
+ step ?. complete ( { error } ) ;
700
+ }
701
+
702
+ onWillPause ( ) {
703
+ currentTestInfo ( ) ?. setTimeout ( 0 ) ;
704
+ }
705
+
706
+ async runAfterCreateBrowserContext ( context : BrowserContext ) {
707
+ await this . _artifactsRecorder ?. didCreateBrowserContext ( context ) ;
708
+ const testInfo = currentTestInfo ( ) ;
709
+ if ( testInfo )
710
+ attachConnectedHeaderIfNeeded ( testInfo , context . browser ( ) ) ;
711
+ }
712
+
713
+ async runAfterCreateRequestContext ( context : APIRequestContext ) {
714
+ await this . _artifactsRecorder ?. didCreateRequestContext ( context ) ;
715
+ }
716
+
717
+ async runBeforeCloseBrowserContext ( context : BrowserContext ) {
718
+ await this . _artifactsRecorder ?. willCloseBrowserContext ( context ) ;
719
+ }
720
+
721
+ async runBeforeCloseRequestContext ( context : APIRequestContext ) {
722
+ await this . _artifactsRecorder ?. willCloseRequestContext ( context ) ;
723
+ }
724
+ }
725
+
726
+ const connector = new InstrumentationConnector ( ) ;
727
+
681
728
export const test = _baseTest . extend < TestFixtures , WorkerFixtures > ( playwrightFixtures ) ;
682
729
683
730
export { defineConfig } from './common/configLoader' ;
0 commit comments