utils_devops 0.1.158__tar.gz → 0.1.160__tar.gz

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.
Files changed (24) hide show
  1. {utils_devops-0.1.158 → utils_devops-0.1.160}/PKG-INFO +1 -1
  2. {utils_devops-0.1.158 → utils_devops-0.1.160}/pyproject.toml +1 -1
  3. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/docker_ops.py +221 -109
  4. {utils_devops-0.1.158 → utils_devops-0.1.160}/README.md +0 -0
  5. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/__init__.py +0 -0
  6. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/__init__.py +0 -0
  7. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/datetimes.py +0 -0
  8. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/envs.py +0 -0
  9. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/files.py +0 -0
  10. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/logs.py +0 -0
  11. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/script_helpers.py +0 -0
  12. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/strings.py +0 -0
  13. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/core/systems.py +0 -0
  14. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/__init__.py +0 -0
  15. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/aws_ops.py +0 -0
  16. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/git_ops.py +0 -0
  17. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/interaction_ops.py +0 -0
  18. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/metrics_ops.py +0 -0
  19. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/network_ops.py +0 -0
  20. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/nginx_ops.py +0 -0
  21. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/notification_ops.py +0 -0
  22. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/performance_ops.py +0 -0
  23. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/ssh_ops.py +0 -0
  24. {utils_devops-0.1.158 → utils_devops-0.1.160}/src/utils_devops/extras/vault_ops.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: utils_devops
3
- Version: 0.1.158
3
+ Version: 0.1.160
4
4
  Summary: Lightweight DevOps utilities for automation scripts: config editing (YAML/JSON/INI/.env), templating, diffing, and CLI tools
5
5
  License: MIT
6
6
  Keywords: devops,automation,nginx,cli,jinja2,yaml,config,diff,templating,logging,docker,compose,file-ops
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "utils_devops"
3
- version = "0.1.158" # Bumped for new string features + diffing
3
+ version = "0.1.160" # Bumped for new string features + diffing
4
4
  description = "Lightweight DevOps utilities for automation scripts: config editing (YAML/JSON/INI/.env), templating, diffing, and CLI tools"
5
5
  authors = ["Hamed Sheikhan <sh.sheikhan.m@gmail.com>"]
6
6
  license = "MIT"
@@ -8,7 +8,6 @@ import json
8
8
  import os
9
9
  import re
10
10
  from logging import Logger
11
- import re
12
11
  import sys
13
12
  import tarfile
14
13
  import tempfile
@@ -618,7 +617,7 @@ def compose_restart(compose_file: str, remove_volumes: bool = False,
618
617
 
619
618
  def compose_ps(compose_file: str, project_name: Optional[str] = None, env_file: Optional[str] = None) -> List[ContainerInfo]:
620
619
  """List compose services status using detected compose command"""
621
- result = run_compose_command(compose_file, ["ps", "--format", "json"], project_name , env_file)
620
+ result = run_compose_command(compose_file, ["ps" ,"-a", "--format", "json"], project_name , env_file)
622
621
 
623
622
  containers = []
624
623
  try:
@@ -909,7 +908,7 @@ def check_health_from_compose_ps(compose_file: str) -> Dict[str, Any]:
909
908
  """
910
909
  try:
911
910
  # Run docker compose ps with JSON format
912
- result = run_compose_command(compose_file, ["ps", "--format", "json"])
911
+ result = run_compose_command(compose_file, ["ps","-a", "--format", "json"])
913
912
  if result.rc != 0:
914
913
  return {"healthy": False, "error": f"docker compose ps failed: {result.stderr}"}
915
914
 
@@ -2220,8 +2219,7 @@ def playwright_test_compose(
2220
2219
  pull_missing: bool = True,
2221
2220
  no_build: bool = True,
2222
2221
  no_pull: bool = True,
2223
- keep_up: bool = False,
2224
- # Keep services up after each test_compose call so we can sequence them
2222
+ keep_compose_up: bool = False,
2225
2223
  dry_run: bool = False,
2226
2224
  project_name: Optional[str] = None,
2227
2225
  logger: Optional[logger] = None,
@@ -2231,7 +2229,7 @@ def playwright_test_compose(
2231
2229
  application compose and a separate Playwright compose. This function reuses the
2232
2230
  existing `test_compose` function to perform validation, startup and health checks for
2233
2231
  the application compose.
2234
-
2232
+
2235
2233
  Behavior summary:
2236
2234
  1. Validate inputs and (optionally) update version as in test_compose.
2237
2235
  2. Set STATIC_CAPTCHA=true in the system process environment (without modifying the file).
@@ -2241,19 +2239,34 @@ def playwright_test_compose(
2241
2239
  5. Follow logs (simulating -f) for the Playwright service(s) while polling the compose state
2242
2240
  to detect completion, and determine pass/fail from exit status and log patterns.
2243
2241
  6. Return a detailed result dict similar in structure to test_compose.
2244
-
2242
+
2245
2243
  Notes:
2246
2244
  - Reuses `test_compose` for application startup and health checks.
2247
2245
  - Sets STATIC_CAPTCHA in the process environment only (os.environ).
2248
2246
  - Does not automatically bring services down; that is left to the caller.
2249
2247
  - Success is determined by all containers exiting with code 0 and/or presence of success patterns in logs.
2250
-
2248
+
2251
2249
  Returns:
2252
2250
  Dict with keys: test_id, success, steps, app_result, logs_captured, duration, error
2253
2251
  """
2252
+
2253
+ # Minimal sticker set - only what's necessary
2254
+ STICKERS = {
2255
+ 'start': '🎬',
2256
+ 'success': '✅',
2257
+ 'error': '❌',
2258
+ 'info': 'ℹ️',
2259
+ 'waiting': '⏳',
2260
+ 'complete': '🏁',
2261
+ 'logs': '📝',
2262
+ 'analysis': '📊',
2263
+ 'cleanup': '🧹',
2264
+ }
2265
+
2254
2266
  logger = logger or DEFAULT_LOGGER
2255
2267
  test_id = str(uuid.uuid4())[:8]
2256
2268
  start_time = dt_ops.current_datetime()
2269
+
2257
2270
  result = {
2258
2271
  "test_id": test_id,
2259
2272
  "success": False,
@@ -2263,40 +2276,50 @@ def playwright_test_compose(
2263
2276
  "duration": 0.0,
2264
2277
  "error": None,
2265
2278
  }
2279
+
2280
+ project_name_playwright = project_name
2281
+
2266
2282
  try:
2267
- logger.info(f"▶️ Playwright test workflow starting ({test_id})")
2268
- logger.info(f" Compose files: app={compose_file} playwright={playwright_compose_file}")
2269
- # Step 0: Basic validation
2270
- logger.info("Step 0: validating compose and env files")
2283
+ # Start header - clean and professional
2284
+ logger.info(f"{STICKERS['start']} Playwright test starting (ID: {test_id})")
2285
+ logger.info(f"{STICKERS['info']} App: {compose_file}")
2286
+ logger.info(f"{STICKERS['info']} Playwright: {playwright_compose_file}")
2287
+ if dry_run:
2288
+ logger.info(f"{STICKERS['info']} Dry run mode")
2289
+ logger.info("")
2290
+
2291
+ # STEP 0: Validation
2292
+ logger.info(f"{STICKERS['info']} STEP 0: Validating files")
2271
2293
  if not files.file_exists(compose_file):
2272
2294
  raise DockerOpsError(f"Application compose file not found: {compose_file}")
2273
2295
  if not files.file_exists(playwright_compose_file):
2274
2296
  raise DockerOpsError(f"Playwright compose file not found: {playwright_compose_file}")
2275
- # Step 1: Optional version update
2276
- if update_version and not dry_run:
2277
- logger.info("Step 1: updating version in env (requested)")
2278
- new_version = _update_env_version(env_file=env_file, source=version_source, logger=logger)
2279
- if new_version:
2280
- logger.info(f" Version updated to {new_version}")
2281
- # make sure runtime env sees new values
2282
- envs.import_env_to_system(get_env_compose(env_file))
2283
- elif update_version and dry_run:
2284
- logger.info("Step 1: version update skipped (dry run)")
2285
- else:
2286
- logger.info("Step 1: version update not requested")
2287
- # Step 2: Set STATIC_CAPTCHA=true in system process env
2288
- logger.info("Step 2: setting STATIC_CAPTCHA=true in process environment")
2297
+ logger.info(f"{STICKERS['success']} Validation complete")
2298
+ logger.info("")
2299
+
2300
+ # STEP 1: Version update
2301
+ if update_version:
2302
+ logger.info(f"{STICKERS['info']} STEP 1: Updating version")
2303
+ if not dry_run:
2304
+ new_version = _update_env_version(env_file=env_file, source=version_source, logger=logger)
2305
+ if new_version:
2306
+ logger.info(f"{STICKERS['success']} Version updated to {new_version}")
2307
+ envs.set_system_env(key='STATIC_CAPTCHA', value='true')
2308
+ else:
2309
+ logger.info(f"{STICKERS['info']} Version update skipped (dry run)")
2310
+ logger.info("")
2311
+
2312
+ # STEP 2: Environment
2289
2313
  if not dry_run:
2290
2314
  envs.set_system_env(key='STATIC_CAPTCHA', value='true')
2291
- else:
2292
- logger.info("Step 2: env set skipped (dry run)")
2293
- # Step 3: Start application compose using test_compose but keep services up
2294
- logger.info("Step 3: bringing up application stack (health checks via test_compose)")
2315
+
2316
+ # STEP 3: Application stack
2317
+ logger.info(f"{STICKERS['info']} STEP 3: Starting application stack")
2295
2318
  if not dry_run:
2296
2319
  app_result = test_compose(
2297
2320
  compose_file=compose_file,
2298
2321
  env_file=env_file,
2299
- update_version=False, # already handled
2322
+ update_version=False,
2300
2323
  services=services,
2301
2324
  health_timeout=health_timeout,
2302
2325
  health_interval=health_interval,
@@ -2305,126 +2328,215 @@ def playwright_test_compose(
2305
2328
  pull_missing=pull_missing,
2306
2329
  no_build=no_build,
2307
2330
  no_pull=no_pull,
2308
- keep_compose_up=True, # DO NOT tear down
2331
+ keep_compose_up=True,
2309
2332
  project_name=project_name,
2310
2333
  dry_run=dry_run,
2311
2334
  logger=logger,
2312
2335
  )
2336
+
2313
2337
  result['app_result'] = app_result
2314
2338
  if not app_result.get('success'):
2315
- raise DockerOpsError("Application compose failed to start or pass health checks")
2316
- logger.info(" Application stack is up and healthy")
2339
+ raise DockerOpsError("Application stack failed")
2340
+
2341
+ logger.info(f"{STICKERS['success']} Application stack ready")
2342
+ logger.info("")
2317
2343
  else:
2318
- logger.info("Step 3: application start skipped (dry run)")
2319
- # Step 4: Start Playwright compose using compose_up
2320
- logger.info("Step 4: bringing up Playwright runner stack with compose_up")
2344
+ logger.info(f"{STICKERS['info']} Application start skipped (dry run)")
2345
+ logger.info("")
2346
+
2347
+ # STEP 4: Playwright stack
2348
+ logger.info(f"{STICKERS['info']} STEP 4: Starting Playwright runner")
2321
2349
  if not dry_run:
2350
+ logger.info(f"{STICKERS['info']} Project: {project_name_playwright}")
2322
2351
  up_success = compose_up(
2323
2352
  playwright_compose_file,
2324
2353
  services=playwright_services,
2325
- project_name=project_name,
2326
- # Assume detached by default or add param if needed; help shows no param for detached, perhaps always detached
2354
+ project_name=project_name_playwright,
2327
2355
  )
2356
+
2328
2357
  if not up_success:
2329
2358
  raise DockerOpsError("Playwright compose_up failed")
2330
- logger.info(" Playwright stack started - now following logs until completion")
2359
+
2360
+ logger.info(f"{STICKERS['success']} Playwright started")
2361
+ logger.info(f"{STICKERS['waiting']} Following logs until completion...")
2362
+ logger.info("")
2331
2363
  else:
2332
- logger.info("Step 4: playwright start skipped (dry run)")
2333
- # Step 5: Follow logs and wait for completion (simulate logs -f with polling)
2364
+ logger.info(f"{STICKERS['info']} Playwright start skipped (dry run)")
2365
+ logger.info("")
2366
+
2367
+ # STEP 5: Log capture
2368
+ pw_services = []
2369
+ logs_acc = {}
2370
+
2334
2371
  if not dry_run and capture_logs:
2335
- logger.info("Step 5: following Playwright logs and waiting for completion")
2336
- pw_services = playwright_services or list(read_compose_file(playwright_compose_file, env_file).get('services', {}).keys())
2337
- logs_acc = {s: [] for s in pw_services}
2372
+ logger.info(f"{STICKERS['logs']} STEP 5: Capturing logs")
2373
+
2374
+ # Determine services to monitor
2375
+ compose_config = read_compose_file(playwright_compose_file, env_file)
2376
+ all_services = list(compose_config.get('services', {}).keys())
2377
+ pw_services = playwright_services or all_services
2378
+
2379
+ logger.info(f"{STICKERS['info']} Services: {', '.join(pw_services)}")
2380
+ logger.info(f"{STICKERS['info']} Tail: {log_tail} lines, Timeout: {health_timeout}s")
2381
+ logger.info("")
2382
+
2383
+ logs_acc = {service: [] for service in pw_services}
2338
2384
  deadline = time.time() + health_timeout
2339
2385
  finished = False
2340
- timed_out = False
2341
- while not finished and not timed_out:
2342
- # Fetch current log snapshot
2343
- for svc in pw_services:
2386
+
2387
+ # Wait for containers to initialize
2388
+ time.sleep(3)
2389
+
2390
+ while not finished:
2391
+ current_time = time.time()
2392
+ if current_time > deadline:
2393
+ raise DockerOpsError(f"Playwright runner timed out after {health_timeout}s")
2394
+
2395
+ for service in pw_services:
2344
2396
  try:
2345
- lines = [l.text for l in compose_logs(playwright_compose_file, services=[svc], tail=log_tail)]
2397
+ # Get fresh logs for this service
2398
+ log_lines = compose_logs(
2399
+ playwright_compose_file,
2400
+ services=[service],
2401
+ tail=log_tail,
2402
+ project_name=project_name_playwright,
2403
+ follow=True,
2404
+
2405
+ )
2406
+
2407
+ for log_line in log_lines:
2408
+ if hasattr(log_line, 'message'):
2409
+ log_message = log_line.message
2410
+ # Add only new log lines
2411
+ if log_message not in logs_acc[service]:
2412
+ logs_acc[service].append(log_message)
2413
+ # Print with clean formatting: only service name at the beginning
2414
+ logger.info(f"{STICKERS['info']} {service}")
2415
+ logger.info(log_message)
2346
2416
  except Exception as e:
2347
- lines = [f"Error fetching logs: {str(e)}"]
2348
- # Append new unique lines
2349
- for ln in lines:
2350
- if ln not in logs_acc[svc]:
2351
- logs_acc[svc].append(ln)
2352
- logger.info(f"[{svc}] {ln}") # Simulate real-time logging
2353
- # Check if all services have exited
2354
- ps = compose_ps(playwright_compose_file, project_name=project_name)
2355
- running = any(
2356
- 'running' in (c.status or '').lower() or 'up' in (c.status or '').lower()
2357
- for c in ps if (c.service or '') in pw_services
2358
- )
2359
- if not running:
2417
+ error_msg = f"Error fetching logs: {str(e)}"
2418
+ logger.error(f"{STICKERS['error']} {service} - {error_msg}")
2419
+
2420
+ # Check if all services have finished
2421
+ ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2422
+ running_count = 0
2423
+
2424
+ for container in ps:
2425
+ service_name = container.service or ''
2426
+ if service_name in pw_services:
2427
+ status = (container.status or '').lower()
2428
+ if 'running' in status or 'up' in status:
2429
+ running_count += 1
2430
+
2431
+ if running_count == 0:
2360
2432
  finished = True
2361
- if time.time() > deadline:
2362
- timed_out = True
2363
- if not finished and not timed_out:
2433
+ logger.info(f"{STICKERS['complete']} All services completed")
2434
+ logger.info("")
2435
+ else:
2364
2436
  time.sleep(health_interval)
2437
+
2365
2438
  result['logs_captured'] = logs_acc
2366
- result['steps']['playwright_logs'] = {
2367
- 'finished': finished,
2368
- 'timed_out': timed_out,
2369
- }
2370
- if timed_out:
2371
- raise DockerOpsError("Playwright runner timed out")
2372
- else:
2373
- if dry_run:
2374
- logger.info("Step 5: log following skipped (dry run)")
2375
- # Step 6: Analyze outcome
2376
- logger.info("Step 6: analyzing Playwright runner outcome")
2439
+
2440
+ # STEP 6: Analysis
2441
+ logger.info(f"{STICKERS['analysis']} STEP 6: Analyzing results")
2442
+
2377
2443
  if not dry_run:
2378
2444
  # Get exit codes
2379
- ps = compose_ps(playwright_compose_file, project_name=project_name)
2445
+ ps = compose_ps(playwright_compose_file, project_name=project_name_playwright)
2380
2446
  exit_codes = {}
2381
- for c in ps:
2382
- if (c.service or '') in pw_services:
2383
- exit_code = getattr(c, 'exit_code', None) or 1 # Assume non-zero if missing
2384
- exit_codes[c.service] = exit_code
2447
+
2448
+ for container in ps:
2449
+ service_name = container.service or ''
2450
+ if service_name in pw_services:
2451
+ exit_code = getattr(container, 'exit_code', None) or 1
2452
+ exit_codes[service_name] = exit_code
2453
+
2454
+ # Check overall success
2385
2455
  all_success = all(code == 0 for code in exit_codes.values())
2386
- # Log analysis
2387
- aggregated_logs = '\n'.join(' '.join(v) for v in logs_acc.values())
2388
- failed = any(re.search(pat, aggregated_logs, re.I) for pat in ['FAILED', 'FAIL', 'ERROR', 'AssertionError', 'Test Failed'])
2389
- passed = any(re.search(pat, aggregated_logs, re.I) for pat in ['All tests passed', 'Tests passed', 'PASS'])
2390
- if all_success and (passed or not failed):
2456
+
2457
+ if all_success:
2391
2458
  result['success'] = True
2392
- logger.info(f"Playwright run successful ({test_id})")
2459
+ logger.info(f"{STICKERS['success']} " + "="*50)
2460
+ logger.info(f"{STICKERS['success']} TESTS PASSED")
2461
+ logger.info(f"{STICKERS['success']} Test ID: {test_id}")
2462
+ logger.info(f"{STICKERS['success']} " + "="*50)
2393
2463
  else:
2394
2464
  result['success'] = False
2395
- result['error'] = 'Playwright tests failed based on exit codes or logs'
2396
- logger.error(f"Playwright run failed ({test_id})")
2397
- result['steps']['analysis'] = {
2398
- 'exit_codes': exit_codes,
2399
- 'passed_patterns': passed,
2400
- 'failed_patterns': failed,
2401
- }
2465
+ result['error'] = 'Playwright tests failed'
2466
+ logger.error(f"{STICKERS['error']} " + "="*50)
2467
+ logger.error(f"{STICKERS['error']} TESTS FAILED")
2468
+ logger.error(f"{STICKERS['error']} Exit codes: {exit_codes}")
2469
+ logger.error(f"{STICKERS['error']} " + "="*50)
2402
2470
  else:
2403
- result['success'] = True # Assume success in dry run
2471
+ result['success'] = True
2472
+ logger.info(f"{STICKERS['info']} Analysis skipped (dry run)")
2473
+
2474
+ # Final timing
2404
2475
  result['duration'] = (dt_ops.current_datetime() - start_time).total_seconds()
2405
- if not keep_up :
2476
+ logger.info(f"{STICKERS['info']} Duration: {result['duration']:.2f}s")
2477
+ logger.info("")
2478
+
2479
+ # Cleanup
2480
+ if not keep_compose_up and not dry_run:
2481
+ logger.info(f"{STICKERS['cleanup']} Cleaning up")
2482
+ logger.info(f"{STICKERS['info']} Stopping Playwright stack...")
2406
2483
  compose_down(
2407
2484
  compose_file=playwright_compose_file,
2485
+ project_name=project_name_playwright
2408
2486
  )
2409
- logger.info("Playwright compose down")
2487
+ logger.info(f"{STICKERS['success']} Playwright stopped")
2488
+
2489
+ logger.info(f"{STICKERS['info']} Stopping Application stack...")
2410
2490
  compose_down(
2411
2491
  compose_file=compose_file,
2492
+ project_name=project_name
2412
2493
  )
2413
- logger.info("all servise compose down")
2494
+ logger.info(f"{STICKERS['success']} Application stopped")
2495
+ logger.info(f"{STICKERS['success']} Cleanup complete")
2496
+ logger.info("")
2497
+
2498
+ # Final summary
2499
+ if result['success']:
2500
+ logger.info(f"{STICKERS['complete']} " + "="*70)
2501
+ logger.info(f"{STICKERS['complete']} TEST COMPLETE: SUCCESS")
2502
+ logger.info(f"{STICKERS['complete']} Duration: {result['duration']:.2f}s")
2503
+ logger.info(f"{STICKERS['complete']} Test ID: {test_id}")
2504
+ logger.info(f"{STICKERS['complete']} " + "="*70)
2505
+ else:
2506
+ logger.error(f"{STICKERS['error']} " + "="*70)
2507
+ logger.error(f"{STICKERS['error']} TEST COMPLETE: FAILED")
2508
+ logger.error(f"{STICKERS['error']} Duration: {result['duration']:.2f}s")
2509
+ logger.error(f"{STICKERS['error']} Test ID: {test_id}")
2510
+ logger.error(f"{STICKERS['error']} " + "="*70)
2511
+
2414
2512
  return result
2513
+
2415
2514
  except Exception as e:
2416
2515
  result['duration'] = (dt_ops.current_datetime() - start_time).total_seconds()
2417
2516
  result['error'] = str(e)
2418
- logger.error(f"Playwright test workflow failed ({test_id}): {e}")
2419
- if not keep_up :
2420
- compose_down(
2421
- compose_file=playwright_compose_file,
2422
- )
2423
- logger.info("Playwright compose down")
2424
- compose_down(
2425
- compose_file=compose_file,
2426
- )
2427
- logger.info("all servise compose down")
2517
+
2518
+ logger.error(f"{STICKERS['error']} Test failed: {e}")
2519
+ logger.error(f"{STICKERS['info']} Duration: {result['duration']:.2f}s")
2520
+
2521
+ # Emergency cleanup
2522
+ if not keep_compose_up and not dry_run:
2523
+ logger.info(f"{STICKERS['cleanup']} Emergency cleanup")
2524
+ try:
2525
+ compose_down(
2526
+ compose_file=playwright_compose_file,
2527
+ project_name=project_name_playwright
2528
+ )
2529
+ except:
2530
+ pass
2531
+
2532
+ try:
2533
+ compose_down(
2534
+ compose_file=compose_file,
2535
+ project_name=project_name
2536
+ )
2537
+ except:
2538
+ pass
2539
+
2428
2540
  return result
2429
2541
 
2430
2542
 
File without changes