@@ -19,123 +19,161 @@ const path = require('path');
19
19
const { spawn, execSync} = require ( 'child_process' ) ;
20
20
const { FFOX , CHROMIUM , WEBKIT , WIN , LINUX } = require ( './utils' ) . testOptions ( browserType ) ;
21
21
22
- async function testSignal ( state , action , exitOnClose ) {
23
- const options = Object . assign ( { } , state . defaultBrowserOptions , {
24
- handleSIGINT : true ,
25
- handleSIGTERM : true ,
26
- handleSIGHUP : true ,
27
- executablePath : state . browserType . executablePath ( ) ,
28
- logger : undefined ,
29
- } ) ;
30
- const res = spawn ( 'node' , [ path . join ( __dirname , 'fixtures' , 'closeme.js' ) , path . join ( state . playwrightPath , 'index-for-dev' ) , state . browserType . name ( ) , JSON . stringify ( options ) , exitOnClose ? 'true' : '' ] ) ;
31
- let wsEndPointCallback ;
32
- const wsEndPointPromise = new Promise ( x => wsEndPointCallback = x ) ;
33
- let output = '' ;
34
- let browserExitCode = 'none' ;
35
- let browserSignal = 'none' ;
36
- let browserPid ;
37
- res . stdout . on ( 'data' , data => {
38
- output += data . toString ( ) ;
39
- // Uncomment to debug these tests.
40
- // console.log(data.toString());
41
- let match = output . match ( / b r o w s e r W S : ( .+ ) : b r o w s e r W S / ) ;
42
- if ( match )
43
- wsEndPointCallback ( match [ 1 ] ) ;
44
- match = output . match ( / b r o w s e r C l o s e : ( [ ^ : ] + ) : ( [ ^ : ] + ) : b r o w s e r C l o s e / ) ;
45
- if ( match ) {
46
- browserExitCode = match [ 1 ] ;
47
- browserSignal = match [ 2 ] ;
48
- }
49
- match = output . match ( / b r o w s e r P i d : ( [ ^ : ] + ) : b r o w s e r P i d / ) ;
50
- if ( match )
51
- browserPid = + match [ 1 ] ;
52
- } ) ;
53
- res . on ( 'error' , ( ...args ) => console . log ( "ERROR" , ...args ) ) ;
54
- const browser = await state . browserType . connect ( { wsEndpoint : await wsEndPointPromise } ) ;
55
- const promises = [
56
- new Promise ( resolve => browser . once ( 'disconnected' , resolve ) ) ,
57
- new Promise ( resolve => res . on ( 'exit' , resolve ) ) ,
58
- ] ;
59
- action ( res , browserPid ) ;
60
- const [ , exitCode ] = await Promise . all ( promises ) ;
61
- return { exitCode, browserSignal, browserExitCode, output } ;
22
+ class Wrapper {
23
+ constructor ( state , extraOptions ) {
24
+ this . _output = new Map ( ) ;
25
+ this . _outputCallback = new Map ( ) ;
26
+
27
+ this . _browserType = state . browserType ;
28
+ const launchOptions = { ...state . defaultBrowserOptions ,
29
+ handleSIGINT : true ,
30
+ handleSIGTERM : true ,
31
+ handleSIGHUP : true ,
32
+ executablePath : state . browserType . executablePath ( ) ,
33
+ logger : undefined ,
34
+ } ;
35
+ const options = {
36
+ playwrightFile : path . join ( state . playwrightPath , 'index-for-dev' ) ,
37
+ browserTypeName : state . browserType . name ( ) ,
38
+ launchOptions,
39
+ ...extraOptions ,
40
+ } ;
41
+ this . _child = spawn ( 'node' , [ path . join ( __dirname , 'fixtures' , 'closeme.js' ) , JSON . stringify ( options ) ] ) ;
42
+ this . _child . on ( 'error' , ( ...args ) => console . log ( "ERROR" , ...args ) ) ;
43
+ this . _exitPromise = new Promise ( resolve => this . _child . on ( 'exit' , resolve ) ) ;
44
+
45
+ let outputString = '' ;
46
+ this . _child . stdout . on ( 'data' , data => {
47
+ outputString += data . toString ( ) ;
48
+ // Uncomment to debug.
49
+ // console.log(data.toString());
50
+ let match ;
51
+ while ( match = outputString . match ( / \( ( [ ^ ( ) ] + ) = > ( [ ^ ( ) ] + ) \) / ) ) {
52
+ const key = match [ 1 ] ;
53
+ const value = match [ 2 ] ;
54
+ this . _addOutput ( key , value ) ;
55
+ outputString = outputString . substring ( match . index + match [ 0 ] . length ) ;
56
+ }
57
+ } ) ;
58
+ }
59
+
60
+ _addOutput ( key , value ) {
61
+ this . _output . set ( key , value ) ;
62
+ const cb = this . _outputCallback . get ( key ) ;
63
+ this . _outputCallback . delete ( key ) ;
64
+ if ( cb )
65
+ cb ( ) ;
66
+ }
67
+
68
+ async out ( key ) {
69
+ if ( ! this . _output . has ( key ) )
70
+ await new Promise ( f => this . _outputCallback . set ( key , f ) ) ;
71
+ return this . _output . get ( key ) ;
72
+ }
73
+
74
+ async connect ( ) {
75
+ const wsEndpoint = await this . out ( 'wsEndpoint' ) ;
76
+ const browser = await this . _browserType . connect ( { wsEndpoint } ) ;
77
+ this . _exitAndDisconnectPromise = Promise . all ( [
78
+ this . _exitPromise ,
79
+ new Promise ( resolve => browser . once ( 'disconnected' , resolve ) ) ,
80
+ ] ) . then ( ( [ exitCode ] ) => exitCode ) ;
81
+ }
82
+
83
+ child ( ) {
84
+ return this . _child ;
85
+ }
86
+
87
+ async childExitCode ( ) {
88
+ return await this . _exitAndDisconnectPromise ;
89
+ }
90
+ }
91
+
92
+ async function setup ( state , options = { } ) {
93
+ const wrapper = new Wrapper ( state , options ) ;
94
+ await wrapper . connect ( ) ;
95
+ return wrapper ;
62
96
}
63
97
64
98
describe ( 'Fixtures' , function ( ) {
65
99
it . slow ( ) ( 'should close the browser when the node process closes' , async state => {
66
- const result = await testSignal ( state , child => {
67
- if ( WIN )
68
- execSync ( `taskkill /pid ${ child . pid } /T /F` ) ;
69
- else
70
- process . kill ( child . pid ) ;
71
- } ) ;
72
- expect ( result . exitCode ) . toBe ( WIN ? 1 : 0 ) ;
100
+ const wrapper = await setup ( state ) ;
101
+ if ( WIN )
102
+ execSync ( `taskkill /pid ${ wrapper . child ( ) . pid } /T /F` ) ;
103
+ else
104
+ process . kill ( wrapper . child ( ) . pid ) ;
105
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( WIN ? 1 : 0 ) ;
73
106
// We might not get browser exitCode in time when killing the parent node process,
74
107
// so we don't check it here.
75
108
} ) ;
76
109
77
110
describe . skip ( WIN ) . skip ( ! HEADLESS ) ( 'signals' , ( ) => {
78
111
// Cannot reliably send signals on Windows.
79
112
it . slow ( ) ( 'should report browser close signal' , async state => {
80
- const result = await testSignal ( state , ( child , browserPid ) => process . kill ( browserPid ) , true ) ;
81
- expect ( result . exitCode ) . toBe ( 0 ) ;
82
- expect ( result . browserExitCode ) . toBe ( 'null' ) ;
83
- expect ( result . browserSignal ) . toBe ( 'SIGTERM' ) ;
113
+ const wrapper = await setup ( state ) ;
114
+ const pid = await wrapper . out ( 'pid' ) ;
115
+ process . kill ( - pid , 'SIGTERM' ) ;
116
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( 'null' ) ;
117
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'SIGTERM' ) ;
118
+ process . kill ( wrapper . child ( ) . pid ) ;
119
+ await wrapper . childExitCode ( ) ;
84
120
} ) ;
85
121
it . slow ( ) ( 'should report browser close signal 2' , async state => {
86
- const result = await testSignal ( state , ( child , browserPid ) => process . kill ( browserPid , 'SIGKILL' ) , true ) ;
87
- expect ( result . exitCode ) . toBe ( 0 ) ;
88
- expect ( result . browserExitCode ) . toBe ( 'null' ) ;
89
- expect ( result . browserSignal ) . toBe ( 'SIGKILL' ) ;
122
+ const wrapper = await setup ( state ) ;
123
+ const pid = await wrapper . out ( 'pid' ) ;
124
+ process . kill ( - pid , 'SIGKILL' ) ;
125
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( 'null' ) ;
126
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'SIGKILL' ) ;
127
+ process . kill ( wrapper . child ( ) . pid ) ;
128
+ await wrapper . childExitCode ( ) ;
90
129
} ) ;
91
130
it . slow ( ) ( 'should close the browser on SIGINT' , async state => {
92
- const result = await testSignal ( state , child => process . kill ( child . pid , 'SIGINT' ) ) ;
93
- expect ( result . exitCode ) . toBe ( 130 ) ;
94
- expect ( result . browserExitCode ) . toBe ( '0' ) ;
95
- expect ( result . browserSignal ) . toBe ( 'null' ) ;
131
+ const wrapper = await setup ( state ) ;
132
+ process . kill ( wrapper . child ( ) . pid , 'SIGINT' ) ;
133
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( '0' ) ;
134
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'null' ) ;
135
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( 130 ) ;
96
136
} ) ;
97
137
it . slow ( ) ( 'should close the browser on SIGTERM' , async state => {
98
- const result = await testSignal ( state , child => process . kill ( child . pid , 'SIGTERM' ) ) ;
99
- expect ( result . exitCode ) . toBe ( 0 ) ;
100
- expect ( result . browserExitCode ) . toBe ( '0' ) ;
101
- expect ( result . browserSignal ) . toBe ( 'null' ) ;
138
+ const wrapper = await setup ( state ) ;
139
+ process . kill ( wrapper . child ( ) . pid , 'SIGTERM' ) ;
140
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( '0' ) ;
141
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'null' ) ;
142
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( 0 ) ;
102
143
} ) ;
103
144
it . slow ( ) ( 'should close the browser on SIGHUP' , async state => {
104
- const result = await testSignal ( state , child => process . kill ( child . pid , 'SIGHUP' ) ) ;
105
- expect ( result . exitCode ) . toBe ( 0 ) ;
106
- expect ( result . browserExitCode ) . toBe ( '0' ) ;
107
- expect ( result . browserSignal ) . toBe ( 'null' ) ;
145
+ const wrapper = await setup ( state ) ;
146
+ process . kill ( wrapper . child ( ) . pid , 'SIGHUP' ) ;
147
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( '0' ) ;
148
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'null' ) ;
149
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( 0 ) ;
108
150
} ) ;
109
151
it . slow ( ) ( 'should kill the browser on double SIGINT' , async state => {
110
- const result = await testSignal ( state , child => {
111
- process . kill ( child . pid , 'SIGINT' ) ;
112
- process . kill ( child . pid , 'SIGINT ') ;
113
- } ) ;
114
- expect ( result . exitCode ) . toBe ( 130 ) ;
115
- // TODO: ideally, we would expect the SIGKILL on the browser from
116
- // force kill, but that's racy with sending two signals.
152
+ const wrapper = await setup ( state , { stallOnClose : true } ) ;
153
+ process . kill ( wrapper . child ( ) . pid , 'SIGINT' ) ;
154
+ await wrapper . out ( 'stalled ') ;
155
+ process . kill ( wrapper . child ( ) . pid , 'SIGINT' ) ;
156
+ expect ( await wrapper . out ( ' exitCode' ) ) . toBe ( 'null' ) ;
157
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( ' SIGKILL' ) ;
158
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( 130 ) ;
117
159
} ) ;
118
- // TODO: flaky - https://app.circleci.com/pipelines/github/microsoft/playwright/582/workflows/b49033ce-fe20-4029-b665-13fb331f842e/jobs/579
119
- it . slow ( ) . fail ( FFOX && LINUX ) ( 'should kill the browser on SIGINT + SIGTERM' , async state => {
120
- const result = await testSignal ( state , child => {
121
- process . kill ( child . pid , 'SIGINT' ) ;
122
- process . kill ( child . pid , 'SIGTERM' ) ;
123
- } ) ;
124
- expect ( result . exitCode ) . toBe ( 130 ) ;
125
- // TODO: ideally, we would expect the SIGKILL on the browser from
126
- // force kill, but that's racy with sending two signals.
160
+ it . slow ( ) ( 'should kill the browser on SIGINT + SIGTERM' , async state => {
161
+ const wrapper = await setup ( state , { stallOnClose : true } ) ;
162
+ process . kill ( wrapper . child ( ) . pid , 'SIGINT' ) ;
163
+ await wrapper . out ( 'stalled' ) ;
164
+ process . kill ( wrapper . child ( ) . pid , 'SIGTERM' ) ;
165
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( 'null' ) ;
166
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'SIGKILL' ) ;
167
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( 0 ) ;
127
168
} ) ;
128
- // TODO: flaky!
129
- // - firefox: https://github.com/microsoft/playwright/pull/1911/checks?check_run_id=607148951
130
- // - chromium: https://travis-ci.com/github/microsoft/playwright/builds/161356178
131
- it . slow ( ) . fail ( ( FFOX || CHROMIUM ) && LINUX ) ( 'should kill the browser on SIGTERM + SIGINT' , async state => {
132
- const result = await testSignal ( state , child => {
133
- process . kill ( child . pid , 'SIGTERM' ) ;
134
- process . kill ( child . pid , 'SIGINT' ) ;
135
- } ) ;
136
- expect ( result . exitCode ) . toBe ( 130 ) ;
137
- // TODO: ideally, we would expect the SIGKILL on the browser from
138
- // force kill, but that's racy with sending two signals.
169
+ it . slow ( ) ( 'should kill the browser on SIGTERM + SIGINT' , async state => {
170
+ const wrapper = await setup ( state , { stallOnClose : true } ) ;
171
+ process . kill ( wrapper . child ( ) . pid , 'SIGTERM' ) ;
172
+ await wrapper . out ( 'stalled' ) ;
173
+ process . kill ( wrapper . child ( ) . pid , 'SIGINT' ) ;
174
+ expect ( await wrapper . out ( 'exitCode' ) ) . toBe ( 'null' ) ;
175
+ expect ( await wrapper . out ( 'signal' ) ) . toBe ( 'SIGKILL' ) ;
176
+ expect ( await wrapper . childExitCode ( ) ) . toBe ( 130 ) ;
139
177
} ) ;
140
178
} ) ;
141
179
} ) ;
0 commit comments