Skip to content

Playwright: restart: true leaks one browser per test file #5784

Closed

Description

@danielrentz

What are you trying to achieve?

Run a suite with the Playwright helper and restart: true (one browser per test), with every browser closed after its test.

What do you get instead?

Each test file leaves one Chromium instance running until the process ends. In our CI, all 15 parallel jobs (about 125 tests each) were OOMKilled after 25 to 30 minutes (memory limit 2.5 GiB). A local run ended with 36 Chromium processes and 4 GB of memory, against 5 processes and 1 GB with the workaround below.

The helper starts two browsers for the first test of each file:

  1. _beforeSuite() starts a browser because isRunning is false (the _after() of the previous test stopped its browser):
    _beforeSuite() {
    // Skip browser start in dry-run mode (used by check command)
    if (store.dryRun) {
    this.debugSection('Dry Run', 'Skipping browser start')
    return
    }
    // Start browser if not manually started and not already running
    // Browser should start in singleton mode (restart: false) or when restart strategy is enabled
    if (!this.options.manualStart && !this.isRunning) {
    this.debugSection('Session', 'Starting singleton browser session')
    return this._startBrowser()
    }
  2. _before() of the first test sees isRunning === true, takes the restartsBrowser() branch and calls _startBrowser() again. That overwrites this.browser without closing the browser from step 1:
    // Start browser if needed (initial start or browser restart strategy)
    if (!this.isRunning && !this.options.manualStart) await this._startBrowser()
    else if (restartsBrowser() && !this.options.manualStart) {
    // Browser restart strategy: start browser for each test
    await this._startBrowser()
    }
  3. _after() closes only this.browser, so the browser from step 1 is never closed.

The later tests of the same file are fine, because _after() sets isRunning to false and _before() starts exactly one browser.

Workaround

We skip _beforeSuite() when every test starts its own browser anyway:

const { _beforeSuite } = Playwright.prototype
Playwright.prototype._beforeSuite = function () {
  return (this.options.restart === true) ? undefined : _beforeSuite.call(this)
}

In the helper, the same check fits into _beforeSuite(): if (!this.options.manualStart && !this.isRunning && !restartsBrowser()). That also covers restart: 'browser'.

Steps to reproduce

  1. Configure the Playwright helper with restart: true.
  2. Create a few test files with one scenario each, e.g. I.amOnPage('about:blank').
  3. Run them in one process and count the Chromium processes during and after the run (or run with --debug: each file logs "Starting singleton browser session" from _beforeSuite(), followed by a second browser start in _before()).

Details

  • CodeceptJS version: 4.2.2 (same code on 4.x at 744dba8)
  • NodeJS Version: 24.21.0 locally, CI image wolfi/playwright:1.64.1
  • Operating System: Windows 11 locally, Linux in CI
  • playwright version: 1.64.1 (Chromium 156.0.8078.4)
  • Configuration file (relevant part):
helpers: {
  Playwright: {
    browser: 'chromium',
    restart: true
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions