playwright icon indicating copy to clipboard operation
playwright copied to clipboard

[BUG] Processes continue running forever when test runner is closed early via CTRL+C

Open viveleroi opened this issue 2 years ago • 15 comments

We've been setting up Playwright for the first time and while tests work and pass, we've been noticing Chromium processes sometimes continue running and accumulate and start driving our cpu up. We were confused why our laptops were getting so warm over a week or so of work and then we noticed this:

image

It appears that this happens when we CTRL+C quit the testing process before it's completed, usually because we're developing and realized we made a mistake or something needs to be changed. I asked about this in discord and was advised to report a bug.

I'm using a fixture based on the example in the playwright docs for shared authentication, because we modify server state. Not sure if that has anything to do with this.

System info

  • Playwright Version: 1.37.1
  • Operating System: Windows 11
  • Browser: Chromium
  • Other info: Node v18.17.0

Source code

playwright.config.js:

import { defineConfig, devices, type PlaywrightTestConfig } from '@playwright/test'
import dotenv from 'dotenv'
import dotenvExpand from 'dotenv-expand'

dotenvExpand.expand(dotenv.config({ override: true }))

// eslint-disable-next-line @typescript-eslint/naming-convention
const { CONTEXT_PATH, E2E_RUNSERVER, HOST, PLAYWRIGHT_WORKERS, PORT } = process.env

if (!PORT || !PLAYWRIGHT_WORKERS) {
  throw new Error('Env variables missing')
}

const config: PlaywrightTestConfig = {
  testDir: './tests/e2e',
  timeout: 30_000,
  expect: {
    timeout: 30_000
  },
  fullyParallel: true,
  reporter: 'html',
  workers: Number.parseInt(PLAYWRIGHT_WORKERS, 10),
  use: {
    actionTimeout: 0,
    baseURL: `${HOST}:5173${CONTEXT_PATH}/`,
    screenshot: 'only-on-failure',
    trace: 'on-first-retry'
  },
  projects: [
    {
      name: 'chromium',
      use: {
        ...devices['Desktop Chrome']
      }
    }
  ],
  outputDir: 'tests/e2e/artifacts'
}

if (E2E_RUNSERVER) {
  config.webServer = {
    command: 'yarn dev',
    // Wait until tomcat server is up before running tests (vite should always be up by now)
    port: Number.parseInt(PORT, 10),
    stdout: 'pipe',
    stderr: 'pipe',
    timeout: 45_000
  }
}

export default defineConfig(config)

fixtures.ts:

import { test as baseTest, expect } from '@playwright/test'
import config from '../../playwright.config'
import fs from 'node:fs'
import path from 'node:path'

/**
 * Create an authentication fixture.
 *
 * Given our current database setup and testing approach, this solution was
 * chosen based on the recommendations at https://playwright.dev/docs/auth
 */
export * from '@playwright/test'
// eslint-disable-next-line @typescript-eslint/ban-types
export const test = baseTest.extend<{}, { workerStorageState: string }>({
  // Use the same storage state for all tests in this worker.
  storageState: ({ workerStorageState }, use) => use(workerStorageState),

  // Authenticate once per worker with a worker-scoped fixture.
  workerStorageState: [
    // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
    async ({ browser }, use) => {
      // Use parallelIndex as a unique identifier for each worker.
      const id = test.info().parallelIndex
      const fileName = path.resolve(test.info().project.outputDir, `.auth/${id}.json`)

      const { PASSWORD, USERNAME } = process.env
      expect(PASSWORD).toBeTruthy()
      expect(USERNAME).toBeTruthy()

      // This is really only needed to typescript, it doesn't know expect() is narrowing
      if (!PASSWORD || !USERNAME) {
        return
      }

      if (fs.existsSync(fileName)) {
        // Reuse existing authentication state if any.
        await use(fileName)
        return
      }

      // Important: make sure we authenticate in a clean environment by unsetting storage state.
      // eslint-disable-next-line no-undefined
      const page = await browser.newPage({ storageState: undefined })

      await page.goto(`${config.use?.baseURL ?? '/'}login`)
      await page.locator('[name=username]').fill(USERNAME)
      await page.locator('[name=password]').fill(PASSWORD)
      await page.getByText('Log In').click()

      await expect(page.locator('_react=NavigationMenu')).toBeVisible()

      await page.context().storageState({ path: fileName })
      await page.close()
      await use(fileName)
    },
    { scope: 'worker' }
  ]
})

an example test:

import { expect, test } from '../../fixtures'
import { ModalPOM } from '../../poms/components/modal.pom'
import { NumericLogsPOM } from '../poms/numeric-logs.pom'

test.describe('calculation details modal', () => {
  test('opens from a numeric logs main grid sequence', async ({ page }) => {
    const numericLogs = new NumericLogsPOM(page)
    await numericLogs.goto()

    // Expand tree nodes so that the necessary node exists
    await numericLogs.navigateViaTree(['USNS Evers', 'Engine', 'Daily Vessel Report', 'Ship Information'])

    // Right click first cell that has calculation details
    await numericLogs.mainGrid.centerContainer.cell(4, 0).click({
      button: 'right'
    })

    // Click the context menu link
    await page.getByText('Calculation Details...').click()

    const modal = new ModalPOM(page.locator('_react=CalculationDetailsModal'))
    await modal.isVisible()

    // Verify modal title
    await expect(modal.header).toContainText('Calculation')
  })
})

Steps

  • Run tests
  • CTRL+C quit before the suite is done.

Expected

All processes close.

Actual

See pic above. We have to hit "End Task" in task manager for these to close properly.

viveleroi avatar Sep 09 '23 21:09 viveleroi

@viveleroi Unfortunately, we are not able to reproduce the issue, and we haven't seen similar complaints in the recent past. Most likely, there is something about your environment that triggers this issue - antivirus, extensions, etc.

I'll keep this issue open to see whether someone else experiences this problem and could help us with figuring out the culprit.

dgozman avatar Sep 12 '23 17:09 dgozman

I will try to find more about how to reproduce it. Several users did add on to my discord question saying they had seen the same problem but they had less info than I did.

viveleroi avatar Sep 12 '23 18:09 viveleroi

We are also facing similar issue as pointed out in all 3 below bug requests, unfortunately we are not able to share a code repo where the issue can be reproduced. https://github.com/microsoft/playwright/issues/26794 https://github.com/microsoft/playwright/issues/26671 https://github.com/microsoft/playwright/issues/26656 We are still trying to debug on what is causing the issue. When we downgraded the version to 1.33 and ran the tests again, it all works fine and the issue is not seen. From what we noticed, version 1.34 and above if any test case fails the worker/thread doesn't get closed until we manually kill the nodes in activity monitor but the browser closes and the test run is stuck.

JasKirk70 avatar Sep 12 '23 18:09 JasKirk70

It happens for us all the time when we run the npm script via intellij and then stop it using intellij button in the middle of the test runs we are running tests in a parallel mode and we are using windows 11

Smrtnyk avatar Sep 13 '23 07:09 Smrtnyk

@Smrtnyk Unfortunately, we cannot figure out whether the bug is in Playwright or in IntelliJ integration without a repro. Can you share detailed repro instructions with us?

dgozman avatar Sep 13 '23 14:09 dgozman

@dgozman I will try to assemble a clear repro instruction tomorrow and let you know

Smrtnyk avatar Sep 13 '23 20:09 Smrtnyk

@dgozman I just tried it out and how it happened for me is in this sequence

  1. open the project in intelliJ
  2. go to package.json
  3. find the test script (for us is like this "test:playwright:stable:chromium:local": "npm run test:playwright:stable:local -- -- --project=chromium --debug")
  4. on the left side there is a play button, press it
  5. select debugger option to run the script in debug mode
  6. when chromium opens and stops before executing test where you need to press play kill the test script using stop button in the intelliJ on top

Had to do this 2 or 3 times to accumulate orphan chromium processes in the task manager

Smrtnyk avatar Sep 15 '23 13:09 Smrtnyk

@Smrtnyk Perhaps the issue is related to --debug option. Let me take a look.

dgozman avatar Sep 15 '23 15:09 dgozman

I see this when debugging in webstorm. The only way to kill the debugger processes is to kill webstorm.

barrykaplan avatar Jan 15 '24 22:01 barrykaplan

Facing the same issue here with Windows on Parallels. Thought nothing could make the M1 warm but the always alive Chromium processes running in the background, even after quitting VSCode, ended up sucking a lot of battery and heating up the machine.

siddharth2023 avatar Feb 20 '24 22:02 siddharth2023

I am experiencing the same issue on Mac arm64. It takes a long time (around 5min) cpu usage reducing after pressing ctrl+c. My observation is that the longer the test, the more the number of nodes increases, and the longer it takes for the CPU to calm down.

No --debug or --headed.

image

kryaksy avatar Feb 26 '24 16:02 kryaksy

For me it was all Chromium instead of node that you are seeing.

siddharth2023 avatar Feb 26 '24 16:02 siddharth2023

Hello Team. Same issue on Windows OS.

 async with SEMAPHOR:
        # Process the wallet asynchronously
        settings = Settings()
        now = int(time.time())

        async with async_playwright() as p:
            if wallet.proxy:
                proxy = await Helper.setup_proxy(wallet.proxy)
            args = [
                f"--disable-extensions-except={EXTENTION_PATH}",
                f"--load-extension={EXTENTION_PATH}",
                "--no-sandbox",  # Add any other arguments you need
                '--lang=en-US',  # Set language to English (United States)
            ]
            if HEADLESS:
                args.append(f"--headless=new")

            context = await p.chromium.launch_persistent_context(
                '',
                headless=False,
                args=args,
                proxy=proxy,
            )
            
            
            // Code
           await context.close()
           
            async with LOCK:
                pass
Screenshot at May 21 04-17-18

Please could you advise why the chromium driver still running even after context.close() ?

Thanks

NekoCrypto avatar May 21 '24 02:05 NekoCrypto

I tried setting workers to be 1, and turning off parallels, but still getting this error. Seems to be related to --debug, because I only get it when running with that flag.

I'm on macOS too.

PatrickAlphaC avatar Feb 13 '25 02:02 PatrickAlphaC

this is happening to me a lot when running on terminal

this is my command:

npm run test:frontend:ui -- client-management.spec.js

any working solutions for this?

what is the best practice, because even when closing on the "X" button it happens sometimes and I don't know why

Sometimes it consumes 27gb of RAM on my PC because there are many processes open and I have to restart my PC to keep working

shuantsu avatar Nov 14 '25 12:11 shuantsu