1
1
import { expect , Page } from "@playwright/test"
2
- import { spawn } from "child_process"
2
+ import { ChildProcess , exec , spawn } from "child_process"
3
3
import { randomUUID } from "crypto"
4
4
import path from "path"
5
5
import express from "express"
@@ -15,10 +15,12 @@ import {
15
15
Resource ,
16
16
RichParameter ,
17
17
} from "./provisionerGenerated"
18
+ import { prometheusPort , pprofPort } from "./constants"
18
19
import { port } from "./playwright.config"
19
20
import * as ssh from "ssh2"
20
21
import { Duplex } from "stream"
21
22
import { WorkspaceBuildParameter } from "api/typesGenerated"
23
+ import axios from "axios"
22
24
23
25
// createWorkspace creates a workspace for a template.
24
26
// It does not wait for it to be running, but it does navigate to the page.
@@ -29,7 +31,7 @@ export const createWorkspace = async (
29
31
buildParameters : WorkspaceBuildParameter [ ] = [ ] ,
30
32
) : Promise < string > => {
31
33
await page . goto ( "/templates/" + templateName + "/workspace" , {
32
- waitUntil : "networkidle " ,
34
+ waitUntil : "domcontentloaded " ,
33
35
} )
34
36
await expect ( page ) . toHaveURL ( "/templates/" + templateName + "/workspace" )
35
37
@@ -57,7 +59,7 @@ export const verifyParameters = async (
57
59
expectedBuildParameters : WorkspaceBuildParameter [ ] ,
58
60
) => {
59
61
await page . goto ( "/@admin/" + workspaceName + "/settings/parameters" , {
60
- waitUntil : "networkidle " ,
62
+ waitUntil : "domcontentloaded " ,
61
63
} )
62
64
await expect ( page ) . toHaveURL (
63
65
"/@admin/" + workspaceName + "/settings/parameters" ,
@@ -120,7 +122,7 @@ export const createTemplate = async (
120
122
content : "window.playwright = true" ,
121
123
} )
122
124
123
- await page . goto ( "/templates/new" , { waitUntil : "networkidle " } )
125
+ await page . goto ( "/templates/new" , { waitUntil : "domcontentloaded " } )
124
126
await expect ( page ) . toHaveURL ( "/templates/new" )
125
127
126
128
await page . getByTestId ( "file-upload" ) . setInputFiles ( {
@@ -229,7 +231,10 @@ export const buildWorkspaceWithParameters = async (
229
231
230
232
// startAgent runs the coder agent with the provided token.
231
233
// It awaits the agent to be ready before returning.
232
- export const startAgent = async ( page : Page , token : string ) : Promise < void > => {
234
+ export const startAgent = async (
235
+ page : Page ,
236
+ token : string ,
237
+ ) : Promise < ChildProcess > => {
233
238
return startAgentWithCommand ( page , token , "go" , "run" , coderMainPath ( ) )
234
239
}
235
240
@@ -308,14 +313,14 @@ export const startAgentWithCommand = async (
308
313
token : string ,
309
314
command : string ,
310
315
...args : string [ ]
311
- ) : Promise < void > => {
316
+ ) : Promise < ChildProcess > => {
312
317
const cp = spawn ( command , [ ...args , "agent" , "--no-reap" ] , {
313
318
env : {
314
319
...process . env ,
315
320
CODER_AGENT_URL : "http://localhost:" + port ,
316
321
CODER_AGENT_TOKEN : token ,
317
- CODER_AGENT_PPROF_ADDRESS : "127.0.0.1:2114" ,
318
- CODER_AGENT_PROMETHEUS_ADDRESS : "127.0.0.1:6061" ,
322
+ CODER_AGENT_PPROF_ADDRESS : "127.0.0.1:" + pprofPort ,
323
+ CODER_AGENT_PROMETHEUS_ADDRESS : "127.0.0.1:" + prometheusPort ,
319
324
} ,
320
325
} )
321
326
cp . stdout . on ( "data" , ( data : Buffer ) => {
@@ -332,6 +337,39 @@ export const startAgentWithCommand = async (
332
337
} )
333
338
334
339
await page . getByTestId ( "agent-status-ready" ) . waitFor ( { state : "visible" } )
340
+ return cp
341
+ }
342
+
343
+ export const stopAgent = async ( cp : ChildProcess , goRun : boolean = true ) => {
344
+ // When the web server is started with `go run`, it spawns a child process with coder server.
345
+ // `pkill -P` terminates child processes belonging the same group as `go run`.
346
+ // The command `kill` is used to terminate a web server started as a standalone binary.
347
+ exec ( goRun ? `pkill -P ${ cp . pid } ` : `kill ${ cp . pid } ` , ( error ) => {
348
+ if ( error ) {
349
+ throw new Error ( `exec error: ${ JSON . stringify ( error ) } ` )
350
+ }
351
+ } )
352
+ await waitUntilUrlIsNotResponding ( "http://localhost:" + prometheusPort )
353
+ }
354
+
355
+ const waitUntilUrlIsNotResponding = async ( url : string ) => {
356
+ const maxRetries = 30
357
+ const retryIntervalMs = 1000
358
+ let retries = 0
359
+
360
+ while ( retries < maxRetries ) {
361
+ try {
362
+ await axios . get ( url )
363
+ } catch ( error ) {
364
+ return
365
+ }
366
+
367
+ retries ++
368
+ await new Promise ( ( resolve ) => setTimeout ( resolve , retryIntervalMs ) )
369
+ }
370
+ throw new Error (
371
+ `URL ${ url } is still responding after ${ maxRetries * retryIntervalMs } ms` ,
372
+ )
335
373
}
336
374
337
375
const coderMainPath = ( ) : string => {
0 commit comments