@frontmcp/testing 1.9.1 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2989,6 +2989,7 @@ var ProgressCollector = class {
2989
2989
 
2990
2990
  // libs/testing/src/server/test-server.ts
2991
2991
  import { execFile, spawn } from "child_process";
2992
+ import { connect } from "net";
2992
2993
  import { sha256Hex } from "@frontmcp/utils";
2993
2994
 
2994
2995
  // libs/testing/src/errors/index.ts
@@ -3217,6 +3218,7 @@ async function releasePort(port) {
3217
3218
  }
3218
3219
 
3219
3220
  // libs/testing/src/server/test-server.ts
3221
+ var SERVER_EXIT_TIMEOUT_MS = 12e3;
3220
3222
  var DEBUG_SERVER = process.env["DEBUG_SERVER"] === "1" || process.env["DEBUG"] === "1";
3221
3223
  var TestServer = class _TestServer {
3222
3224
  process = null;
@@ -3372,9 +3374,12 @@ var TestServer = class _TestServer {
3372
3374
  async stopProcess() {
3373
3375
  if (this.process) {
3374
3376
  this.log("Stopping server...");
3377
+ const serverPid = this._info.pid !== this._info.shellPid ? this._info.pid : void 0;
3375
3378
  if (this.process.exitCode !== null || this.process.signalCode !== null) {
3376
3379
  this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
3377
3380
  this.process = null;
3381
+ if (serverPid !== void 0) signalIfRunning(serverPid, "SIGTERM");
3382
+ await this.waitForServerGone(serverPid);
3378
3383
  return;
3379
3384
  }
3380
3385
  const pid = this.process.pid;
@@ -3412,9 +3417,26 @@ var TestServer = class _TestServer {
3412
3417
  await exitPromise;
3413
3418
  clearTimeout(killTimeout);
3414
3419
  this.process = null;
3420
+ await this.waitForServerGone(serverPid);
3415
3421
  this.log("Server stopped");
3416
3422
  }
3417
3423
  }
3424
+ /**
3425
+ * Wait until the server process has exited and its port refuses connections. A server that
3426
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
3427
+ */
3428
+ async waitForServerGone(serverPid) {
3429
+ const deadline = Date.now() + SERVER_EXIT_TIMEOUT_MS;
3430
+ while (Date.now() < deadline) {
3431
+ const serverRunning = serverPid !== void 0 && pidIsAlive(serverPid);
3432
+ if (!serverRunning && !await acceptsConnections(this._info.port)) return;
3433
+ await sleep2(100);
3434
+ }
3435
+ if (serverPid !== void 0 && pidIsAlive(serverPid)) {
3436
+ this.log("Force killing server process after timeout...");
3437
+ signalIfRunning(serverPid, "SIGKILL");
3438
+ }
3439
+ }
3418
3440
  /**
3419
3441
  * Wait for server to be ready
3420
3442
  */
@@ -3634,6 +3656,23 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
3634
3656
  function sleep2(ms) {
3635
3657
  return new Promise((resolve) => setTimeout(resolve, ms));
3636
3658
  }
3659
+ function signalIfRunning(pid, signal) {
3660
+ try {
3661
+ process.kill(pid, signal);
3662
+ } catch (error) {
3663
+ if (error.code !== "ESRCH") throw error;
3664
+ }
3665
+ }
3666
+ function acceptsConnections(port) {
3667
+ return new Promise((resolve) => {
3668
+ const socket = connect(port, "127.0.0.1");
3669
+ socket.once("connect", () => {
3670
+ socket.destroy();
3671
+ resolve(true);
3672
+ });
3673
+ socket.once("error", () => resolve(false));
3674
+ });
3675
+ }
3637
3676
  function findListeningPid(port) {
3638
3677
  if (process.platform === "win32") return Promise.resolve(void 0);
3639
3678
  return new Promise((resolve) => {
package/esm/index.mjs CHANGED
@@ -4567,6 +4567,7 @@ var MockCimdServer = class {
4567
4567
 
4568
4568
  // libs/testing/src/server/test-server.ts
4569
4569
  import { execFile, spawn } from "child_process";
4570
+ import { connect } from "net";
4570
4571
  import { sha256Hex } from "@frontmcp/utils";
4571
4572
 
4572
4573
  // libs/testing/src/errors/index.ts
@@ -4831,6 +4832,7 @@ async function releasePort(port) {
4831
4832
  }
4832
4833
 
4833
4834
  // libs/testing/src/server/test-server.ts
4835
+ var SERVER_EXIT_TIMEOUT_MS = 12e3;
4834
4836
  var DEBUG_SERVER = process.env["DEBUG_SERVER"] === "1" || process.env["DEBUG"] === "1";
4835
4837
  var TestServer = class _TestServer {
4836
4838
  process = null;
@@ -4986,9 +4988,12 @@ var TestServer = class _TestServer {
4986
4988
  async stopProcess() {
4987
4989
  if (this.process) {
4988
4990
  this.log("Stopping server...");
4991
+ const serverPid = this._info.pid !== this._info.shellPid ? this._info.pid : void 0;
4989
4992
  if (this.process.exitCode !== null || this.process.signalCode !== null) {
4990
4993
  this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
4991
4994
  this.process = null;
4995
+ if (serverPid !== void 0) signalIfRunning(serverPid, "SIGTERM");
4996
+ await this.waitForServerGone(serverPid);
4992
4997
  return;
4993
4998
  }
4994
4999
  const pid = this.process.pid;
@@ -5026,9 +5031,26 @@ var TestServer = class _TestServer {
5026
5031
  await exitPromise;
5027
5032
  clearTimeout(killTimeout);
5028
5033
  this.process = null;
5034
+ await this.waitForServerGone(serverPid);
5029
5035
  this.log("Server stopped");
5030
5036
  }
5031
5037
  }
5038
+ /**
5039
+ * Wait until the server process has exited and its port refuses connections. A server that
5040
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
5041
+ */
5042
+ async waitForServerGone(serverPid) {
5043
+ const deadline = Date.now() + SERVER_EXIT_TIMEOUT_MS;
5044
+ while (Date.now() < deadline) {
5045
+ const serverRunning = serverPid !== void 0 && pidIsAlive(serverPid);
5046
+ if (!serverRunning && !await acceptsConnections(this._info.port)) return;
5047
+ await sleep2(100);
5048
+ }
5049
+ if (serverPid !== void 0 && pidIsAlive(serverPid)) {
5050
+ this.log("Force killing server process after timeout...");
5051
+ signalIfRunning(serverPid, "SIGKILL");
5052
+ }
5053
+ }
5032
5054
  /**
5033
5055
  * Wait for server to be ready
5034
5056
  */
@@ -5248,6 +5270,23 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
5248
5270
  function sleep2(ms) {
5249
5271
  return new Promise((resolve) => setTimeout(resolve, ms));
5250
5272
  }
5273
+ function signalIfRunning(pid, signal) {
5274
+ try {
5275
+ process.kill(pid, signal);
5276
+ } catch (error) {
5277
+ if (error.code !== "ESRCH") throw error;
5278
+ }
5279
+ }
5280
+ function acceptsConnections(port) {
5281
+ return new Promise((resolve) => {
5282
+ const socket = connect(port, "127.0.0.1");
5283
+ socket.once("connect", () => {
5284
+ socket.destroy();
5285
+ resolve(true);
5286
+ });
5287
+ socket.once("error", () => resolve(false));
5288
+ });
5289
+ }
5251
5290
  function findListeningPid(port) {
5252
5291
  if (process.platform === "win32") return Promise.resolve(void 0);
5253
5292
  return new Promise((resolve) => {
package/esm/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/testing",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "E2E testing framework for FrontMCP servers - MCP client, auth mocks, Playwright integration",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -87,9 +87,9 @@
87
87
  "./esm": null
88
88
  },
89
89
  "peerDependencies": {
90
- "@frontmcp/lazy-zod": "1.9.1",
91
- "@frontmcp/sdk": "1.9.1",
92
- "@frontmcp/ui": "1.9.1",
90
+ "@frontmcp/lazy-zod": "1.9.2",
91
+ "@frontmcp/sdk": "1.9.2",
92
+ "@frontmcp/ui": "1.9.2",
93
93
  "@jest/globals": "^29.0.0 || ^30.0.0",
94
94
  "@playwright/test": "^1.40.0",
95
95
  "jest": "^29.0.0 || ^30.0.0"
@@ -112,8 +112,8 @@
112
112
  "node": ">=24.0.0"
113
113
  },
114
114
  "dependencies": {
115
- "@frontmcp/protocol": "1.9.1",
116
- "@frontmcp/utils": "1.9.1",
115
+ "@frontmcp/protocol": "1.9.2",
116
+ "@frontmcp/utils": "1.9.2",
117
117
  "@swc/core": "~1.15.41",
118
118
  "@swc/jest": "~0.2.38",
119
119
  "jose": "^6.0.11",
@@ -3756,6 +3756,7 @@ var ProgressCollector = class {
3756
3756
 
3757
3757
  // libs/testing/src/server/test-server.ts
3758
3758
  import { execFile, spawn } from "child_process";
3759
+ import { connect } from "net";
3759
3760
  import { sha256Hex } from "@frontmcp/utils";
3760
3761
 
3761
3762
  // libs/testing/src/errors/index.ts
@@ -3984,6 +3985,7 @@ async function releasePort(port) {
3984
3985
  }
3985
3986
 
3986
3987
  // libs/testing/src/server/test-server.ts
3988
+ var SERVER_EXIT_TIMEOUT_MS = 12e3;
3987
3989
  var DEBUG_SERVER = process.env["DEBUG_SERVER"] === "1" || process.env["DEBUG"] === "1";
3988
3990
  var TestServer = class _TestServer {
3989
3991
  process = null;
@@ -4139,9 +4141,12 @@ var TestServer = class _TestServer {
4139
4141
  async stopProcess() {
4140
4142
  if (this.process) {
4141
4143
  this.log("Stopping server...");
4144
+ const serverPid = this._info.pid !== this._info.shellPid ? this._info.pid : void 0;
4142
4145
  if (this.process.exitCode !== null || this.process.signalCode !== null) {
4143
4146
  this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
4144
4147
  this.process = null;
4148
+ if (serverPid !== void 0) signalIfRunning(serverPid, "SIGTERM");
4149
+ await this.waitForServerGone(serverPid);
4145
4150
  return;
4146
4151
  }
4147
4152
  const pid = this.process.pid;
@@ -4179,9 +4184,26 @@ var TestServer = class _TestServer {
4179
4184
  await exitPromise;
4180
4185
  clearTimeout(killTimeout);
4181
4186
  this.process = null;
4187
+ await this.waitForServerGone(serverPid);
4182
4188
  this.log("Server stopped");
4183
4189
  }
4184
4190
  }
4191
+ /**
4192
+ * Wait until the server process has exited and its port refuses connections. A server that
4193
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
4194
+ */
4195
+ async waitForServerGone(serverPid) {
4196
+ const deadline = Date.now() + SERVER_EXIT_TIMEOUT_MS;
4197
+ while (Date.now() < deadline) {
4198
+ const serverRunning = serverPid !== void 0 && pidIsAlive(serverPid);
4199
+ if (!serverRunning && !await acceptsConnections(this._info.port)) return;
4200
+ await sleep4(100);
4201
+ }
4202
+ if (serverPid !== void 0 && pidIsAlive(serverPid)) {
4203
+ this.log("Force killing server process after timeout...");
4204
+ signalIfRunning(serverPid, "SIGKILL");
4205
+ }
4206
+ }
4185
4207
  /**
4186
4208
  * Wait for server to be ready
4187
4209
  */
@@ -4401,6 +4423,23 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
4401
4423
  function sleep4(ms) {
4402
4424
  return new Promise((resolve) => setTimeout(resolve, ms));
4403
4425
  }
4426
+ function signalIfRunning(pid, signal) {
4427
+ try {
4428
+ process.kill(pid, signal);
4429
+ } catch (error) {
4430
+ if (error.code !== "ESRCH") throw error;
4431
+ }
4432
+ }
4433
+ function acceptsConnections(port) {
4434
+ return new Promise((resolve) => {
4435
+ const socket = connect(port, "127.0.0.1");
4436
+ socket.once("connect", () => {
4437
+ socket.destroy();
4438
+ resolve(true);
4439
+ });
4440
+ socket.once("error", () => resolve(false));
4441
+ });
4442
+ }
4404
4443
  function findListeningPid(port) {
4405
4444
  if (process.platform === "win32") return Promise.resolve(void 0);
4406
4445
  return new Promise((resolve) => {
package/fixtures/index.js CHANGED
@@ -3009,6 +3009,7 @@ var ProgressCollector = class {
3009
3009
 
3010
3010
  // libs/testing/src/server/test-server.ts
3011
3011
  var import_child_process = require("child_process");
3012
+ var import_net2 = require("net");
3012
3013
  var import_utils = require("@frontmcp/utils");
3013
3014
 
3014
3015
  // libs/testing/src/errors/index.ts
@@ -3237,6 +3238,7 @@ async function releasePort(port) {
3237
3238
  }
3238
3239
 
3239
3240
  // libs/testing/src/server/test-server.ts
3241
+ var SERVER_EXIT_TIMEOUT_MS = 12e3;
3240
3242
  var DEBUG_SERVER = process.env["DEBUG_SERVER"] === "1" || process.env["DEBUG"] === "1";
3241
3243
  var TestServer = class _TestServer {
3242
3244
  process = null;
@@ -3392,9 +3394,12 @@ var TestServer = class _TestServer {
3392
3394
  async stopProcess() {
3393
3395
  if (this.process) {
3394
3396
  this.log("Stopping server...");
3397
+ const serverPid = this._info.pid !== this._info.shellPid ? this._info.pid : void 0;
3395
3398
  if (this.process.exitCode !== null || this.process.signalCode !== null) {
3396
3399
  this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
3397
3400
  this.process = null;
3401
+ if (serverPid !== void 0) signalIfRunning(serverPid, "SIGTERM");
3402
+ await this.waitForServerGone(serverPid);
3398
3403
  return;
3399
3404
  }
3400
3405
  const pid = this.process.pid;
@@ -3432,9 +3437,26 @@ var TestServer = class _TestServer {
3432
3437
  await exitPromise;
3433
3438
  clearTimeout(killTimeout);
3434
3439
  this.process = null;
3440
+ await this.waitForServerGone(serverPid);
3435
3441
  this.log("Server stopped");
3436
3442
  }
3437
3443
  }
3444
+ /**
3445
+ * Wait until the server process has exited and its port refuses connections. A server that
3446
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
3447
+ */
3448
+ async waitForServerGone(serverPid) {
3449
+ const deadline = Date.now() + SERVER_EXIT_TIMEOUT_MS;
3450
+ while (Date.now() < deadline) {
3451
+ const serverRunning = serverPid !== void 0 && pidIsAlive(serverPid);
3452
+ if (!serverRunning && !await acceptsConnections(this._info.port)) return;
3453
+ await sleep2(100);
3454
+ }
3455
+ if (serverPid !== void 0 && pidIsAlive(serverPid)) {
3456
+ this.log("Force killing server process after timeout...");
3457
+ signalIfRunning(serverPid, "SIGKILL");
3458
+ }
3459
+ }
3438
3460
  /**
3439
3461
  * Wait for server to be ready
3440
3462
  */
@@ -3654,6 +3676,23 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
3654
3676
  function sleep2(ms) {
3655
3677
  return new Promise((resolve) => setTimeout(resolve, ms));
3656
3678
  }
3679
+ function signalIfRunning(pid, signal) {
3680
+ try {
3681
+ process.kill(pid, signal);
3682
+ } catch (error) {
3683
+ if (error.code !== "ESRCH") throw error;
3684
+ }
3685
+ }
3686
+ function acceptsConnections(port) {
3687
+ return new Promise((resolve) => {
3688
+ const socket = (0, import_net2.connect)(port, "127.0.0.1");
3689
+ socket.once("connect", () => {
3690
+ socket.destroy();
3691
+ resolve(true);
3692
+ });
3693
+ socket.once("error", () => resolve(false));
3694
+ });
3695
+ }
3657
3696
  function findListeningPid(port) {
3658
3697
  if (process.platform === "win32") return Promise.resolve(void 0);
3659
3698
  return new Promise((resolve) => {
package/index.js CHANGED
@@ -4684,6 +4684,7 @@ var MockCimdServer = class {
4684
4684
 
4685
4685
  // libs/testing/src/server/test-server.ts
4686
4686
  var import_child_process = require("child_process");
4687
+ var import_net2 = require("net");
4687
4688
  var import_utils2 = require("@frontmcp/utils");
4688
4689
 
4689
4690
  // libs/testing/src/errors/index.ts
@@ -4948,6 +4949,7 @@ async function releasePort(port) {
4948
4949
  }
4949
4950
 
4950
4951
  // libs/testing/src/server/test-server.ts
4952
+ var SERVER_EXIT_TIMEOUT_MS = 12e3;
4951
4953
  var DEBUG_SERVER = process.env["DEBUG_SERVER"] === "1" || process.env["DEBUG"] === "1";
4952
4954
  var TestServer = class _TestServer {
4953
4955
  process = null;
@@ -5103,9 +5105,12 @@ var TestServer = class _TestServer {
5103
5105
  async stopProcess() {
5104
5106
  if (this.process) {
5105
5107
  this.log("Stopping server...");
5108
+ const serverPid = this._info.pid !== this._info.shellPid ? this._info.pid : void 0;
5106
5109
  if (this.process.exitCode !== null || this.process.signalCode !== null) {
5107
5110
  this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
5108
5111
  this.process = null;
5112
+ if (serverPid !== void 0) signalIfRunning(serverPid, "SIGTERM");
5113
+ await this.waitForServerGone(serverPid);
5109
5114
  return;
5110
5115
  }
5111
5116
  const pid = this.process.pid;
@@ -5143,9 +5148,26 @@ var TestServer = class _TestServer {
5143
5148
  await exitPromise;
5144
5149
  clearTimeout(killTimeout);
5145
5150
  this.process = null;
5151
+ await this.waitForServerGone(serverPid);
5146
5152
  this.log("Server stopped");
5147
5153
  }
5148
5154
  }
5155
+ /**
5156
+ * Wait until the server process has exited and its port refuses connections. A server that
5157
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
5158
+ */
5159
+ async waitForServerGone(serverPid) {
5160
+ const deadline = Date.now() + SERVER_EXIT_TIMEOUT_MS;
5161
+ while (Date.now() < deadline) {
5162
+ const serverRunning = serverPid !== void 0 && pidIsAlive(serverPid);
5163
+ if (!serverRunning && !await acceptsConnections(this._info.port)) return;
5164
+ await sleep2(100);
5165
+ }
5166
+ if (serverPid !== void 0 && pidIsAlive(serverPid)) {
5167
+ this.log("Force killing server process after timeout...");
5168
+ signalIfRunning(serverPid, "SIGKILL");
5169
+ }
5170
+ }
5149
5171
  /**
5150
5172
  * Wait for server to be ready
5151
5173
  */
@@ -5365,6 +5387,23 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
5365
5387
  function sleep2(ms) {
5366
5388
  return new Promise((resolve) => setTimeout(resolve, ms));
5367
5389
  }
5390
+ function signalIfRunning(pid, signal) {
5391
+ try {
5392
+ process.kill(pid, signal);
5393
+ } catch (error) {
5394
+ if (error.code !== "ESRCH") throw error;
5395
+ }
5396
+ }
5397
+ function acceptsConnections(port) {
5398
+ return new Promise((resolve) => {
5399
+ const socket = (0, import_net2.connect)(port, "127.0.0.1");
5400
+ socket.once("connect", () => {
5401
+ socket.destroy();
5402
+ resolve(true);
5403
+ });
5404
+ socket.once("error", () => resolve(false));
5405
+ });
5406
+ }
5368
5407
  function findListeningPid(port) {
5369
5408
  if (process.platform === "win32") return Promise.resolve(void 0);
5370
5409
  return new Promise((resolve) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/testing",
3
- "version": "1.9.1",
3
+ "version": "1.9.2",
4
4
  "description": "E2E testing framework for FrontMCP servers - MCP client, auth mocks, Playwright integration",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -87,9 +87,9 @@
87
87
  "./esm": null
88
88
  },
89
89
  "peerDependencies": {
90
- "@frontmcp/lazy-zod": "1.9.1",
91
- "@frontmcp/sdk": "1.9.1",
92
- "@frontmcp/ui": "1.9.1",
90
+ "@frontmcp/lazy-zod": "1.9.2",
91
+ "@frontmcp/sdk": "1.9.2",
92
+ "@frontmcp/ui": "1.9.2",
93
93
  "@jest/globals": "^29.0.0 || ^30.0.0",
94
94
  "@playwright/test": "^1.40.0",
95
95
  "jest": "^29.0.0 || ^30.0.0"
@@ -112,8 +112,8 @@
112
112
  "node": ">=24.0.0"
113
113
  },
114
114
  "dependencies": {
115
- "@frontmcp/protocol": "1.9.1",
116
- "@frontmcp/utils": "1.9.1",
115
+ "@frontmcp/protocol": "1.9.2",
116
+ "@frontmcp/utils": "1.9.2",
117
117
  "@swc/core": "~1.15.41",
118
118
  "@swc/jest": "~0.2.38",
119
119
  "jose": "^6.0.11",
package/perf/index.js CHANGED
@@ -3805,6 +3805,7 @@ var ProgressCollector = class {
3805
3805
 
3806
3806
  // libs/testing/src/server/test-server.ts
3807
3807
  var import_child_process = require("child_process");
3808
+ var import_net2 = require("net");
3808
3809
  var import_utils = require("@frontmcp/utils");
3809
3810
 
3810
3811
  // libs/testing/src/errors/index.ts
@@ -4033,6 +4034,7 @@ async function releasePort(port) {
4033
4034
  }
4034
4035
 
4035
4036
  // libs/testing/src/server/test-server.ts
4037
+ var SERVER_EXIT_TIMEOUT_MS = 12e3;
4036
4038
  var DEBUG_SERVER = process.env["DEBUG_SERVER"] === "1" || process.env["DEBUG"] === "1";
4037
4039
  var TestServer = class _TestServer {
4038
4040
  process = null;
@@ -4188,9 +4190,12 @@ var TestServer = class _TestServer {
4188
4190
  async stopProcess() {
4189
4191
  if (this.process) {
4190
4192
  this.log("Stopping server...");
4193
+ const serverPid = this._info.pid !== this._info.shellPid ? this._info.pid : void 0;
4191
4194
  if (this.process.exitCode !== null || this.process.signalCode !== null) {
4192
4195
  this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
4193
4196
  this.process = null;
4197
+ if (serverPid !== void 0) signalIfRunning(serverPid, "SIGTERM");
4198
+ await this.waitForServerGone(serverPid);
4194
4199
  return;
4195
4200
  }
4196
4201
  const pid = this.process.pid;
@@ -4228,9 +4233,26 @@ var TestServer = class _TestServer {
4228
4233
  await exitPromise;
4229
4234
  clearTimeout(killTimeout);
4230
4235
  this.process = null;
4236
+ await this.waitForServerGone(serverPid);
4231
4237
  this.log("Server stopped");
4232
4238
  }
4233
4239
  }
4240
+ /**
4241
+ * Wait until the server process has exited and its port refuses connections. A server that
4242
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
4243
+ */
4244
+ async waitForServerGone(serverPid) {
4245
+ const deadline = Date.now() + SERVER_EXIT_TIMEOUT_MS;
4246
+ while (Date.now() < deadline) {
4247
+ const serverRunning = serverPid !== void 0 && pidIsAlive(serverPid);
4248
+ if (!serverRunning && !await acceptsConnections(this._info.port)) return;
4249
+ await sleep4(100);
4250
+ }
4251
+ if (serverPid !== void 0 && pidIsAlive(serverPid)) {
4252
+ this.log("Force killing server process after timeout...");
4253
+ signalIfRunning(serverPid, "SIGKILL");
4254
+ }
4255
+ }
4234
4256
  /**
4235
4257
  * Wait for server to be ready
4236
4258
  */
@@ -4450,6 +4472,23 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
4450
4472
  function sleep4(ms) {
4451
4473
  return new Promise((resolve) => setTimeout(resolve, ms));
4452
4474
  }
4475
+ function signalIfRunning(pid, signal) {
4476
+ try {
4477
+ process.kill(pid, signal);
4478
+ } catch (error) {
4479
+ if (error.code !== "ESRCH") throw error;
4480
+ }
4481
+ }
4482
+ function acceptsConnections(port) {
4483
+ return new Promise((resolve) => {
4484
+ const socket = (0, import_net2.connect)(port, "127.0.0.1");
4485
+ socket.once("connect", () => {
4486
+ socket.destroy();
4487
+ resolve(true);
4488
+ });
4489
+ socket.once("error", () => resolve(false));
4490
+ });
4491
+ }
4453
4492
  function findListeningPid(port) {
4454
4493
  if (process.platform === "win32") return Promise.resolve(void 0);
4455
4494
  return new Promise((resolve) => {
@@ -198,6 +198,7 @@ export declare const E2E_PORT_RANGES: {
198
198
  };
199
199
  };
200
200
  export type E2EProject = keyof typeof E2E_PORT_RANGES;
201
+ export declare function pidIsAlive(pid: number): boolean;
201
202
  /**
202
203
  * Get the port range for a project
203
204
  */
@@ -1 +1 @@
1
- {"version":3,"file":"port-registry.d.ts","sourceRoot":"","sources":["../../src/server/port-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiElB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,eAAe,CAAC;AAyGtD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAM7E;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAgE7F;AAmDD;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAWxE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAM/F"}
1
+ {"version":3,"file":"port-registry.d.ts","sourceRoot":"","sources":["../../src/server/port-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH;;;;;;GAMG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiElB,CAAC;AAEX,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,eAAe,CAAC;AA2CtD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAO/C;AAuDD;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAM7E;AAED;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,OAAO,EAAE,MAAM,EACf,aAAa,CAAC,EAAE,MAAM,GACrB,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,aAAa,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;CAAE,CAAC,CAgE7F;AAmDD;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAWxE;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAMrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC,CAM/F"}
@@ -86,6 +86,11 @@ export declare class TestServer {
86
86
  */
87
87
  stop(): Promise<void>;
88
88
  private stopProcess;
89
+ /**
90
+ * Wait until the server process has exited and its port refuses connections. A server that
91
+ * drains on SIGTERM can keep listening after the shell it was started through has exited.
92
+ */
93
+ private waitForServerGone;
89
94
  /**
90
95
  * Wait for server to be ready
91
96
  */
@@ -1 +1 @@
1
- {"version":3,"file":"test-server.d.ts","sourceRoot":"","sources":["../../src/server/test-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAgBH,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsE;IAC9F,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,IAAI,CAAgB;IAC5B,oFAAoF;IACpF,OAAO,CAAC,WAAW,CAAsC;IACzD,2EAA2E;IAC3E,OAAO,CAAC,iBAAiB,CAAsC;IAE/D,OAAO;IAyBP;;OAEG;WACU,KAAK,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;IA4CnE;;OAEG;WACU,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,iBAAiB,CAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA0DpG;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAoB3C;;OAEG;IACH,IAAI,IAAI,IAAI,cAAc,CAEzB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAYb,WAAW;IAwDzB;;OAEG;IACG,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BnD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,OAAO,IAAI,MAAM,EAAE;IAInB;;OAEG;IACH,SAAS,IAAI,IAAI;YAQH,YAAY;IA+G1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;YACW,6BAA6B;IAqE3C,OAAO,CAAC,GAAG;CAKZ;AA+KD,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,KAAK,UAAU,GAChB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"test-server.d.ts","sourceRoot":"","sources":["../../src/server/test-server.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAoBH,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wBAAwB;IACxB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7B,kEAAkE;IAClE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,2BAA2B;IAC3B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAMD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsE;IAC9F,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,IAAI,CAAgB;IAC5B,oFAAoF;IACpF,OAAO,CAAC,WAAW,CAAsC;IACzD,2EAA2E;IAC3E,OAAO,CAAC,iBAAiB,CAAsC;IAE/D,OAAO;IAyBP;;OAEG;WACU,KAAK,CAAC,OAAO,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;IA4CnE;;OAEG;WACU,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,OAAO,CAAC,iBAAiB,CAAM,GAAG,OAAO,CAAC,UAAU,CAAC;IA0DpG;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU;IAoB3C;;OAEG;IACH,IAAI,IAAI,IAAI,cAAc,CAEzB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YAYb,WAAW;IA4DzB;;;OAGG;YACW,iBAAiB;IAa/B;;OAEG;IACG,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA2BnD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAM9B;;OAEG;IACH,OAAO,IAAI,MAAM,EAAE;IAInB;;OAEG;IACH,SAAS,IAAI,IAAI;YAQH,YAAY;IA+G1B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAY1B;;OAEG;YACW,6BAA6B;IAqE3C,OAAO,CAAC,GAAG;CAKZ;AAwMD,OAAO,EACL,WAAW,EACX,cAAc,EACd,eAAe,EACf,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,KAAK,UAAU,GAChB,MAAM,iBAAiB,CAAC"}