Class PooledDriverProvider

java.lang.Object
io.karatelabs.driver.PooledDriverProvider
All Implemented Interfaces:
DriverProvider

public class PooledDriverProvider extends Object implements DriverProvider
Driver provider that maintains a bounded pool of drivers.

This provider works correctly with virtual threads where each task gets a new thread ID. Drivers are pooled and reused across scenarios regardless of which thread executes them.

The pool size automatically matches the Suite's parallelism (threadCount), ensuring exactly the right number of drivers are created.

 // Usage - pool size auto-detected from Runner.parallel(N)
 Runner.path("features/")
     .driverProvider(new PooledDriverProvider())
     .parallel(4);  // Pool will have 4 drivers

 // Or with Testcontainers - override createDriver()
 Runner.path("features/")
     .driverProvider(new PooledDriverProvider() {
         protected Driver createDriver(Map<String, Object> config) {
             return CdpDriver.connect(container.getCdpUrl(), options);
         }
     })
     .parallel(4);
 
  • Constructor Details

    • PooledDriverProvider

      public PooledDriverProvider()
      Create a pooled driver provider with auto-detected pool size. Pool size will match the Suite's parallelism (threadCount).
    • PooledDriverProvider

      public PooledDriverProvider(int poolSize)
      Create a pooled driver provider with explicit pool size. Use this when you need to override the auto-detected size.
      Parameters:
      poolSize - maximum number of drivers to create
  • Method Details

    • acquire

      public Driver acquire(ScenarioRuntime runtime, Map<String,Object> config)
      Description copied from interface: DriverProvider
      Acquire a driver instance for a scenario.

      Called when a scenario first accesses the driver (lazy initialization). The provider may create a new driver, return one from a pool, or return a thread-local cached instance.

      Specified by:
      acquire in interface DriverProvider
      Parameters:
      runtime - the scenario runtime requesting the driver
      config - driver configuration from configure driver = {...}
      Returns:
      a driver instance
    • release

      public void release(ScenarioRuntime runtime, Driver driver)
      Description copied from interface: DriverProvider
      Release a driver instance after scenario completion.

      Called when a scenario ends. The provider may close the driver, return it to a pool, or keep it cached for the thread.

      Specified by:
      release in interface DriverProvider
      Parameters:
      runtime - the scenario runtime that used the driver
      driver - the driver to release
    • cleanScenarioState

      protected void cleanScenarioState(Driver driver)
      Tear down scenario-scoped driver state at owner-scenario exit. Called from release(io.karatelabs.core.ScenarioRuntime, io.karatelabs.driver.Driver) before the driver is offered back to the pool.

      Only state that belongs to the scenario's scope should be cleaned here — NOT cookies or page state, those belong in resetDriver(io.karatelabs.driver.Driver) at acquire time (per-scenario setup vs per-scenario teardown).

      Subclasses overriding this should call super.cleanScenarioState(driver) to preserve intercept/dialog teardown.

    • shutdown

      public void shutdown()
      Description copied from interface: DriverProvider
      Shutdown the provider and close all managed drivers.

      Called when the Suite completes. All drivers should be closed.

      Specified by:
      shutdown in interface DriverProvider
    • resetDriver

      protected boolean resetDriver(Driver driver)
      Reset driver state between scenarios. Override to customize reset behavior.

      Returns true if the driver is healthy and safe to return to the pool, false if it should be discarded and replaced. Callers (see release(ScenarioRuntime, Driver)) MUST honor the false return — otherwise a poisoned driver can pollute every subsequent scenario.

      Context for the health-check + discard logic: without this, observed in CI as deterministic "CDP timeout for: Page.navigate" failures after tab-switch.feature — that scenario's repeated activateTarget/attachToTarget calls leave the driver's CDP channel stuck on navigation commands. The pool kept handing the stuck driver back out and every other cookie/keys scenario timed out at 30s. The fix is to probe liveness (see Driver.isResponsive()) and recycle drivers that fail the probe. Subclasses overriding this should preserve the boolean contract.

    • createDriver

      protected Driver createDriver(Map<String,Object> config)
      Create a new driver from config. Subclasses should override for custom driver creation (e.g., Testcontainers).

      With no webSocketUrl each pooled driver launches its own browser process, so slots are isolated by construction. Against a SHARED browser the isolation has to be asked for: a browser-level endpoint gets each slot its own incognito context via CdpDriver.connectNewContext(java.lang.String, io.karatelabs.driver.cdp.CdpDriverOptions), because pooled slots sharing one browser context share its cookie jar, and the per-acquire clearCookies() in resetDriver(io.karatelabs.driver.Driver) is context-wide — one slot resetting would wipe the cookies of every other slot mid-scenario.

      A page-level endpoint is taken at face value and attached to directly: the caller named a specific tab, so it owns that tab's lifetime and isolation. Pointing a pool of more than one slot at a single page endpoint means every slot drives the SAME tab — that is inherent to the request, not something this method can fix.

    • getPoolSize

      public int getPoolSize()
      Get current pool size (may be -1 if not yet initialized).
    • getStats

      public String getStats()
      Get current pool statistics for monitoring.