Skip to content

No IDE autocompletion for Page|Components methods in Dusk tests #1176

Description

@SlFomin

Dusk Version

8.3.3

Laravel Version

12.19.3

PHP Version

8.4

PHPUnit Version

11.5.25

Database Driver & Version

No response

Description

Description

When writing Dusk tests following the official documentation, IDE support breaks when chaining page methods after Browser::visit(Page).

Example with Page:

$this->browse(function (Browser $browser) {
    $browser->visit(new PlaylistPage)
        ->createPlaylist('My Playlist')
        ->assertSee('My Playlist');
});

Example with Component:

$browser
    ->visit('/') // or ->visit(new AwesomePage)
    /** @var Browser|DatePicker $datePicker */
    ->within(new DatePicker, function ($datePicker) {
        $datePicker->selectDate(2019, 1, 30);
    })
    ->assertSee('January');

// Or:
$page = $browser->visit('/'); // or ->visit(new AwesomePage)
/** @var Browser|DatePicker $datePicker */
$datePicker = $page->component(new DatePicker);
$datePicker
    ->selectDate(2019, 1, 30)
    ->assertSee('January');

Issue:

  • Browser::visit() returns $this (Browser).

  • The magic __call correctly dispatches the method call to the Page class at runtime.

  • However, the return type remains Browser, so the IDE does not recognize the page methods, nor does it know that createPlaylist() returns a Browser or PlaylistPage.

  • Similarly, Browser::with() works with Components but the __call magic is used to execute component methods at runtime.

This makes the fluent chaining work at runtime but breaks static analysis and autocompletion.

Proposed solution:

One possible approach would be to provide a way for Page and Component classes to receive the Browser instance and return it explicitly. For example:

// Page method
public function createPlaylist(string $name): Browser
{
    return $this->browser
        ->type('name', $name)
        ->check('share')
        ->press('Create Playlist');
}

And in the Browser class:

if ($this->page && method_exists($this->page, $method)) {
    $this->page->setBrowser($this);
    return $this->page->{$method}(...$parameters);
}

This would allow tests to be written with proper type hints:

$this->browse(function (Browser $browser) {
    /** @var PlaylistPage|Browser $playlistPage */
    $playlistPage = $browser->visit(new PlaylistPage);
    $playlistPage
        ->createPlaylist('My Playlist')
        ->assertSee('My Playlist');
});

Clarification

Alternatively, an official pattern for fluent type resolution or static return types for Page methods would solve the problem for IDEs and static analyzers.

I understand this would require changes to Browser, Page, and Component contracts. However, having proper static type resolution would make writing and verifying new tests slightly easier — at least for me! 😄

Steps To Reproduce

  1. Create a Page class with a method createPlaylist(Browser $browser, string $name).
  2. In your Dusk test, call $browser->visit(new PlaylistPage)->createPlaylist('My Playlist').
  3. Observe that the IDE cannot find createPlaylist() because Browser::visit() returns Browser.
  4. The magic __call works at runtime, but static analysis fails.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels
    No fields configured for Feature.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions