[Bug]: Browser contexts never cleaned up when tests live outside the default tests/ directory
Nobody has claimed this yet.
- Dominant language
- PHP
- Stars
- 11.7k
- Forks
- 538
- Avg merge
- 4d 11h
- Merged PRs (30d)
- 8
Description
Description
When browser tests are located outside the default tests/ directory (e.g. app/Domain/*/Tests/), the plugin's afterEach hook that calls Playwright::reset() never fires. This causes browser contexts to accumulate across tests, and the Playwright Node.js process hangs indefinitely during shutdown.
Environment
- Pest v4.x
- pest-plugin-browser v4.2.x
- macOS / Linux
Root Cause
In Plugin.php, the afterEach hook that calls Playwright::reset() (which closes browser contexts between tests) is scoped using $this->in():
// Plugin.php boot()
$this->afterEach(function (): void {
Playwright::reset();
})->in($this->in());
The in() method returns:
private function in(): string
{
return TestSuite::getInstance()->rootPath
. DIRECTORY_SEPARATOR
. TestSuite::getInstance()->testPath;
}
testPath defaults to 'tests' (set in vendor/pestphp/pest/bin/pest). This means the afterEach hook only registers for test files inside the tests/ directory.
For projects with browser tests in other directories (like app/Domain/), the hook never fires. As a result:
- Each
visit()call creates a new browser context viaBrowser::newContext() Playwright::reset()is never called — contexts are never closed- Contexts accumulate across all tests (we observed 48 contexts across 21 tests)
- During
Plugin::terminate(),PlaywrightNpmServer::stop()sends SIGTERM to the Node.js process - Playwright attempts to gracefully close all accumulated contexts
- The Node.js process hangs,
Symfony\Process::stop()blocks, and the PHP process never exits
Workaround
Add an explicit afterEach in tests/Pest.php with an absolute path:
uses()->afterEach(function () {
if (isset($this->page)) {
$this->page->page()->close();
}
if (class_exists(\Pest\Browser\Playwright\Playwright::class, false)) {
\Pest\Browser\ServerManager::instance()->http()->flush();
\Pest\Browser\Playwright\Playwright::reset();
}
})->in(dirname(__DIR__) . '/app/Domain');
Note: using a relative path like ->in('app/Domain') does not work because UsesCall resolves it relative to the file's directory (tests/), resulting in the non-existent path tests/app/Domain/.
Suggested Fix
Plugin::in() should account for all directories that contain test files, not just the default testPath. Possible approaches:
- Scan all configured PHPUnit test suite directories, not just
testPath - Register the
afterEachglobally (without path scoping) and check inside the callback whether the current test uses browser features - Allow users to configure additional test directories for the browser plugin
How to Reproduce
-
Structure browser tests outside
tests/, e.g.:app/ Domain/ Feature1/Tests/SomeBrowserTest.php Feature2/Tests/AnotherBrowserTest.php ... -
Register the test directory in
Pest.phpandphpunit.xml:// tests/Pest.php pest()->extend(Tests\TestCase::class)->in('app/Domain'); -
Run 5+ browser test files:
php vendor/bin/pest --filter='Test1|Test2|Test3|Test4|Test5' -
The process prints all test results but never terminates.
Running 4 or fewer test files works fine — the threshold depends on how many browser contexts accumulate (we observed 48 contexts across 21 tests) before the Playwright server can no longer shut down gracefully.
Sample Repository
No response
Pest Version
4.3.2
PHP Version
8.3
Operation System
macOS
Notes
No response
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Read Plugin.php, especially boot() and in(), and reproduce the shutdown hang with browser tests under app/Domain as described. Compare the hook's test-path scope with the tests/Pest.php workaround, then verify that browser contexts are cleaned up for tests outside tests/ and the process terminates normally.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- php
- Domain
- testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100