microsoft / playwright

Playwright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.
https://playwright.dev
Apache License 2.0
65.47k stars 3.56k forks source link

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

Open viveleroi opened 12 months ago

viveleroi commented 12 months ago

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

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

Expected

All processes close.

Actual

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

dgozman commented 12 months ago

@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.

viveleroi commented 12 months ago

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.

JasKirk70 commented 12 months ago

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.

Smrtnyk commented 12 months ago

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

dgozman commented 12 months ago

@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?

Smrtnyk commented 11 months ago

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

Smrtnyk commented 11 months ago

@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

dgozman commented 11 months ago

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

barrykaplan commented 7 months ago

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

siddharth2023 commented 6 months ago

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.

kryaksy commented 6 months ago

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

siddharth2023 commented 6 months ago

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

NekoCrypto commented 3 months ago

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