or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cache-messaging.mdcloud-services.mdcompose.mdcore-containers.mddatabase-containers.mdindex.mdsearch-analytics.mdwaiting-strategies.mdweb-testing.md

waiting-strategies.mddocs/

0

# Waiting Strategies and Utilities

1

2

Robust container readiness detection, log monitoring, and condition waiting utilities for reliable test execution across different container types and startup behaviors. Essential for ensuring containers are fully ready before test execution begins.

3

4

## Capabilities

5

6

### Container Readiness Decorator

7

8

Decorator for automatic retry logic when connecting to containers, handling transient errors and ensuring reliable container readiness detection.

9

10

```python { .api }

11

def wait_container_is_ready(*transient_exceptions: type[BaseException]) -> Callable:

12

"""

13

Decorator for container readiness checks with retry logic.

14

15

Automatically retries decorated function until success or timeout.

16

Handles common transient exceptions plus any additional specified exceptions.

17

18

Args:

19

*transient_exceptions: Additional exception types to treat as transient

20

21

Returns:

22

Decorator function that wraps the target method

23

24

Usage:

25

@wait_container_is_ready(CustomException)

26

def _connect(self):

27

# Connection logic that may fail transiently

28

pass

29

"""

30

```

31

32

### Log-Based Waiting

33

34

Wait for specific log output to appear in container logs, supporting both string patterns and custom predicates.

35

36

```python { .api }

37

def wait_for_logs(

38

container: DockerContainer,

39

predicate: Union[Callable[..., bool], str],

40

timeout: Union[float, None] = None,

41

interval: float = 1,

42

predicate_streams_and: bool = False,

43

raise_on_exit: bool = False

44

) -> float:

45

"""

46

Wait for specific log output from container.

47

48

Args:

49

container: Container to monitor

50

predicate: String to search for or callable returning bool

51

timeout: Maximum wait time in seconds

52

interval: Polling interval in seconds

53

predicate_streams_and: Apply predicate to both stdout and stderr

54

raise_on_exit: Raise exception if container exits

55

56

Returns:

57

Time elapsed until condition was met

58

59

Raises:

60

TimeoutError: If timeout reached without condition being met

61

ContainerStartException: If container exits unexpectedly

62

"""

63

```

64

65

### Generic Condition Waiting

66

67

Wait for arbitrary conditions to be met with configurable timeout and polling intervals.

68

69

```python { .api }

70

def wait_for(

71

condition: Callable[[], bool],

72

timeout: float = 120,

73

interval: float = 1

74

) -> bool:

75

"""

76

Wait for generic condition to be met.

77

78

Args:

79

condition: Function returning True when condition is met

80

timeout: Maximum wait time in seconds

81

interval: Polling interval in seconds

82

83

Returns:

84

True if condition was met, False if timeout reached

85

"""

86

```

87

88

## Configuration

89

90

### Global Timeout Settings

91

92

Container readiness waiting behavior is controlled by global configuration:

93

94

```python { .api }

95

from testcontainers.core.config import testcontainers_config

96

97

# Configure waiting behavior

98

testcontainers_config.max_tries: int = 120 # Maximum retry attempts

99

testcontainers_config.sleep_time: int = 1 # Sleep between retries (seconds)

100

testcontainers_config.timeout: int = 120 # Total timeout (seconds)

101

```

102

103

### Transient Exceptions

104

105

Default transient exceptions that trigger automatic retries:

106

107

```python { .api }

108

TRANSIENT_EXCEPTIONS = (TimeoutError, ConnectionError)

109

```

110

111

## Usage Examples

112

113

### Basic Log Waiting

114

115

```python

116

from testcontainers.core.container import DockerContainer

117

from testcontainers.core.waiting_utils import wait_for_logs

118

119

# Wait for application startup message

120

with DockerContainer("my-app:latest") as container:

121

# Wait for specific log message indicating readiness

122

delay = wait_for_logs(container, "Server started successfully")

123

print(f"Application ready after {delay:.2f} seconds")

124

125

# Now safe to connect to the application

126

app_port = container.get_exposed_port(8080)

127

# Make requests to the application...

128

```

129

130

### Pattern Matching in Logs

131

132

```python

133

from testcontainers.core.container import DockerContainer

134

from testcontainers.core.waiting_utils import wait_for_logs

135

import re

136

137

with DockerContainer("postgres:13") as postgres:

138

postgres.with_env("POSTGRES_PASSWORD", "test")

139

140

# Wait for PostgreSQL to be ready using regex pattern

141

def postgres_ready(log_line):

142

return re.search(r"database system is ready to accept connections", log_line) is not None

143

144

delay = wait_for_logs(postgres, postgres_ready, timeout=30)

145

print(f"PostgreSQL ready after {delay:.2f} seconds")

146

```

147

148

### Custom Condition Waiting

149

150

```python

151

from testcontainers.redis import RedisContainer

152

from testcontainers.core.waiting_utils import wait_for

153

import redis

154

import time

155

156

with RedisContainer() as redis_container:

157

redis_client = redis_container.get_client()

158

159

# Wait for Redis to accept connections

160

def redis_ready():

161

try:

162

return redis_client.ping()

163

except:

164

return False

165

166

success = wait_for(redis_ready, timeout=30, interval=0.5)

167

if success:

168

print("Redis is ready for connections")

169

else:

170

print("Redis failed to become ready within timeout")

171

```

172

173

### HTTP Endpoint Waiting

174

175

```python

176

from testcontainers.core.container import DockerContainer

177

from testcontainers.core.waiting_utils import wait_for

178

import requests

179

180

with DockerContainer("nginx:alpine") as web_server:

181

web_server.with_exposed_ports(80)

182

183

host = web_server.get_container_host_ip()

184

port = web_server.get_exposed_port(80)

185

186

# Wait for HTTP endpoint to respond

187

def http_ready():

188

try:

189

response = requests.get(f"http://{host}:{port}/", timeout=1)

190

return response.status_code == 200

191

except:

192

return False

193

194

if wait_for(http_ready, timeout=60, interval=2):

195

print("Web server is responding to HTTP requests")

196

# Proceed with tests...

197

```

198

199

### Database Connection Waiting

200

201

```python

202

from testcontainers.postgres import PostgresContainer

203

from testcontainers.core.waiting_utils import wait_container_is_ready

204

import psycopg2

205

206

class CustomPostgresContainer(PostgresContainer):

207

@wait_container_is_ready(psycopg2.OperationalError)

208

def _connect(self):

209

"""Custom connection method with automatic retry."""

210

conn = psycopg2.connect(self.get_connection_url())

211

cursor = conn.cursor()

212

cursor.execute("SELECT 1")

213

cursor.fetchone()

214

conn.close()

215

216

# Use custom container with automatic connection retry

217

with CustomPostgresContainer("postgres:13") as postgres:

218

# Container automatically waits for successful connection

219

connection_url = postgres.get_connection_url()

220

print(f"PostgreSQL ready at: {connection_url}")

221

```

222

223

### Complex Readiness Checking

224

225

```python

226

from testcontainers.core.container import DockerContainer

227

from testcontainers.core.waiting_utils import wait_for_logs, wait_for

228

import requests

229

import time

230

231

class WebAppContainer(DockerContainer):

232

def __init__(self, image):

233

super().__init__(image)

234

self.with_exposed_ports(8080)

235

236

def wait_for_readiness(self):

237

"""Wait for multiple readiness conditions."""

238

# First, wait for application startup logs

239

wait_for_logs(self, "Application started", timeout=60)

240

241

# Then wait for health endpoint to respond

242

host = self.get_container_host_ip()

243

port = self.get_exposed_port(8080)

244

245

def health_check():

246

try:

247

response = requests.get(f"http://{host}:{port}/health", timeout=2)

248

return response.status_code == 200 and response.json().get("status") == "healthy"

249

except:

250

return False

251

252

if not wait_for(health_check, timeout=30):

253

raise Exception("Application failed health check")

254

255

print("Application is fully ready")

256

257

# Use comprehensive readiness checking

258

with WebAppContainer("my-web-app:latest") as app:

259

app.wait_for_readiness()

260

# Application is now fully ready for testing

261

```

262

263

### Waiting with Custom Timeouts

264

265

```python

266

from testcontainers.core.container import DockerContainer

267

from testcontainers.core.waiting_utils import wait_for_logs

268

269

# Different containers may need different timeout strategies

270

containers = [

271

("redis:6", "Ready to accept connections", 15),

272

("postgres:13", "database system is ready", 45),

273

("elasticsearch:7.15.0", "started", 120)

274

]

275

276

for image, log_pattern, timeout in containers:

277

with DockerContainer(image) as container:

278

try:

279

delay = wait_for_logs(container, log_pattern, timeout=timeout)

280

print(f"{image} ready after {delay:.2f}s")

281

except TimeoutError:

282

print(f"{image} failed to start within {timeout}s")

283

# Handle timeout appropriately

284

```

285

286

### Parallel Container Startup

287

288

```python

289

from testcontainers.postgres import PostgresContainer

290

from testcontainers.redis import RedisContainer

291

from testcontainers.core.waiting_utils import wait_for

292

import threading

293

import time

294

295

def start_and_wait(container, name):

296

"""Start container and wait for readiness."""

297

container.start()

298

299

# Different waiting strategies per container type

300

if isinstance(container, PostgresContainer):

301

def pg_ready():

302

try:

303

import psycopg2

304

conn = psycopg2.connect(container.get_connection_url())

305

conn.close()

306

return True

307

except:

308

return False

309

wait_for(pg_ready, timeout=45)

310

311

elif isinstance(container, RedisContainer):

312

client = container.get_client()

313

wait_for(lambda: client.ping(), timeout=15)

314

315

print(f"{name} is ready")

316

317

# Start containers in parallel

318

postgres = PostgresContainer("postgres:13")

319

redis = RedisContainer("redis:6")

320

321

threads = [

322

threading.Thread(target=start_and_wait, args=(postgres, "PostgreSQL")),

323

threading.Thread(target=start_and_wait, args=(redis, "Redis"))

324

]

325

326

start_time = time.time()

327

for thread in threads:

328

thread.start()

329

330

for thread in threads:

331

thread.join()

332

333

print(f"All containers ready in {time.time() - start_time:.2f} seconds")

334

335

# Clean up

336

postgres.stop()

337

redis.stop()

338

```

339

340

## Error Handling

341

342

### Common Exceptions

343

344

```python { .api }

345

from testcontainers.core.exceptions import (

346

ContainerStartException,

347

ContainerConnectException,

348

TimeoutError

349

)

350

351

try:

352

with DockerContainer("problematic-image") as container:

353

wait_for_logs(container, "ready", timeout=30)

354

except ContainerStartException:

355

print("Container failed to start")

356

except TimeoutError:

357

print("Container did not become ready within timeout")

358

except ContainerConnectException:

359

print("Failed to connect to container")

360

```

361

362

### Graceful Timeout Handling

363

364

```python

365

from testcontainers.core.waiting_utils import wait_for

366

import logging

367

368

def wait_with_fallback(condition, primary_timeout=60, fallback_timeout=30):

369

"""Wait with fallback strategy."""

370

try:

371

if wait_for(condition, timeout=primary_timeout):

372

return True

373

else:

374

logging.warning(f"Primary wait timed out after {primary_timeout}s, trying fallback")

375

return wait_for(condition, timeout=fallback_timeout, interval=0.1)

376

except Exception as e:

377

logging.error(f"Wait failed: {e}")

378

return False

379

```