vusys / tetryon
PHP-native browser testing for PHPUnit. Firefox-first, WebDriver BiDi, no Node, no Selenium, no ChromeDriver.
Requires
- php: ^8.4
- ext-json: *
- ext-mbstring: *
- psr/log: ^3.0
Requires (Dev)
- infection/infection: ^0.29 || ^0.33 || ^0.34
- larastan/larastan: ^3.0
- laravel/pint: ^1.18
- orchestra/testbench: ^9.0 || ^10.0 || ^11.0
- phpstan/phpstan: ^2.0
- phpunit/phpunit: ^12.0 || ^13.0
- rector/rector: ^2.0
Suggests
- laravel/framework: For the Laravel integration: service provider, config publishing, tetryon:* artisan commands, and Laravel\BrowserTestCase.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-12 15:27:21 UTC
README
PHPUnit. Firefox. WebDriver BiDi. Composer install. Useful errors. No faff.
Tetryon is a PHP-native browser testing package for PHPUnit. Write browser tests that are readable, reliable, and easy to run locally or in CI — without Node, Playwright, Selenium Server, ChromeDriver, or Dusk-style setup pain.
It is Firefox-first and PHPUnit-first. You run your app however you like; Tetryon drives the browser.
Note
Status: 0.6.0 — beta, pre-1.0. The API below ships and is exercised against real Firefox on Linux and macOS in CI — including a real Vue 3 single-page app — but it may still change before 1.0. See compatibility.
The test you want to write
use Vusys\Tetryon\PHPUnit\BrowserTestCase; final class LoginTest extends BrowserTestCase { public function test_guest_can_log_in(): void { $this->browser() ->visit('/login') ->fill('Email', 'bryan@example.com') ->fill('Password', 'password') ->check('Remember me') ->press('Log in') ->assertSee('Dashboard'); } }
Run it through PHPUnit — Tetryon does not ship a competing test runner:
vendor/bin/phpunit --testsuite Browser
What works today
- A fluent, readable API — navigation, clicks, typing, forms, pointer and keyboard actions, and assertions, all driven by human labels rather than CSS.
- A selector engine —
'Email'/'Save changes'resolve via test attributes → label → accessible name → placeholder → button/link text → name → id → visible text, with@/CSS/XPath escape hatches. - Auto-waiting — every action waits for its target to be actionable; every
assertion retries until timeout. Never a
sleep(). Actions bring their target into view too — out of a scrollable pane, or clear of a fixed header/footer painted over the click point — without disturbing one that is already clear. - Native dialogs —
acceptDialog()/dismissDialog()/typeInDialog()drive awindow.confirmorpromptin either direction, and a dialog nobody expected fails the action with a readable message instead of wedging the run. - Failure diagnostics — on failure, a screenshot, page HTML, console log, BiDi command trace, and browser stderr land in an artifact directory, with a readable report pointing at them.
tetryon doctor— a preflight CLI that launches Firefox and checks the whole environment.- Natural-language steps —
->step('I fill "Email" with "x"')and->scenario()->given()->when()->then(), a deterministic layer over the same API. - First-class Laravel integration — auto-discovered provider,
tetryon:install, a self-bootingLaravel\BrowserTestCase(factories + DB), andloginAs().
It's proven against a real Vue 3 SPA — reactivity, async rendering, form validation, and client-side routing — with no manual waits.
A quick look
Selectors, auto-wait, and assertions:
$this->browser() ->visit('/settings') ->fill('Display name', 'Bryan') ->check('Email notifications') ->press('Save changes') ->assertSee('Saved'); // retries until it appears
Laravel, with a factory and login:
use App\Models\User; use Vusys\Tetryon\Laravel\BrowserTestCase; final class DashboardTest extends BrowserTestCase { public function test_a_user_sees_their_dashboard(): void { $user = User::factory()->create(); $this->loginAs($user) ->visit('/dashboard') ->assertSee('Welcome back'); } }
Natural language:
$this->scenario() ->given('I am on "/login"') ->when('I fill "Email" with "bryan@example.com"') ->and('I press "Log in"') ->then('I should see "Dashboard"');
Design pillars
- PHPUnit only. Supports PHPUnit 12 and 13. Normal PHPUnit tests — PHPUnit owns discovery, filtering, failures, grouping, exit codes, and CI behaviour.
- Firefox first. Firefox stable (and ESR where practical) on macOS and Linux. No Chrome/Edge/Safari/Windows/mobile in v1.
- WebDriver BiDi. Direct browser control with minimal moving parts. No Node, no Selenium Server, no ChromeDriver, no CDP as a core protocol.
- Bring your own server. Start your app with
php artisan serve,symfony serve,docker compose up,npm run dev, whatever — point Tetryon at the base URL. - Auto-waiting by default. You should rarely write a manual wait, never a
sleep(). - Failure diagnostics as a feature. Bad errors kill browser-testing adoption; good errors are the product.
Engineering standards
- PHPStan level 9, no baseline, no inline
@phpstan-ignore. - Laravel Pint for code style, Rector as a CI gate, Infection mutation testing.
- CI across PHP 8.4 / 8.5 and PHPUnit 12 / 13, plus a real-Firefox browser suite on Linux and macOS.
- OpenSSF Scorecard, Dependabot, and pinned-SHA GitHub Actions.
See CONTRIBUTING.md and CLAUDE.md.
Documentation
Full docs live in docs/:
installation ·
writing tests ·
selectors ·
interactions ·
natural language ·
waiting ·
assertions ·
configuration ·
diagnostics ·
Laravel ·
CI ·
troubleshooting.
Roadmap
| Milestone | Status | Theme |
|---|---|---|
| v0.1 | ✓ | Firefox protocol spike — direct WebDriver BiDi control |
| v0.2 | ✓ | PHPUnit alpha — fluent API, auto-wait, failure artifacts, doctor |
| v0.3 | ✓ | Natural-language steps |
| v0.4 | ✓ | Laravel integration — provider, tetryon:install, loginAs() |
| v0.5 | … | CI hardening — GitHub Actions, GitLab, Docker, parallelism |
| v0.6 | Stabilisation & docs |
License
MIT — see LICENSE.