Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions src/Api/Concerns/InteractsWithDialogs.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Pest\Browser\Api\Concerns;

use Closure;
use Pest\Browser\Playwright\Dialog;

trait InteractsWithDialogs
{
/**
* Set up a dialog handler for this page.
*/
public function onDialog(Closure $handler): self
{
$this->page->onDialog($handler);

return $this;
}

/**
* Remove any previously set dialog handler.
*/
public function removeDialogHandler(): self
{
$this->page->removeDialogHandler();

return $this;
}

/**
* Check if a dialog handler is currently set.
*/
public function hasDialogHandler(): bool
{
return $this->page->hasDialogHandler();
}

/**
* Set up automatic dialog acceptance for all future dialogs.
*/
public function acceptAllDialogs(?string $promptText = null): self
{
$this->page->onDialog(function (Dialog $dialog) use ($promptText): void {
$dialog->accept($promptText);
});

return $this;
}

/**
* Set up automatic dialog dismissal for all future dialogs.
*/
public function dismissAllDialogs(): self
{
$this->page->onDialog(function (Dialog $dialog): void {
$dialog->dismiss();
});

return $this;
}

/**
* Set up a dialog handler that accepts confirm dialogs and dismisses all others.
*/
public function acceptingConfirms(): self
{
$this->page->onDialog(function (Dialog $dialog): void {
if ($dialog->type() === 'confirm') {
$dialog->accept();
} else {
$dialog->dismiss();
}
});

return $this;
}

/**
* Set up a dialog handler that dismisses confirm dialogs and accepts all others.
*/
public function dismissingConfirms(): self
{
$this->page->onDialog(function (Dialog $dialog): void {
if ($dialog->type() === 'confirm') {
$dialog->dismiss();
} else {
$dialog->accept();
}
});

return $this;
}
}
1 change: 1 addition & 0 deletions src/Api/Webpage.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
final readonly class Webpage
{
use Concerns\HasWaitCapabilities,
Concerns\InteractsWithDialogs,
Concerns\InteractsWithElements,
Concerns\InteractsWithFrames,
Concerns\InteractsWithScreen,
Expand Down
42 changes: 41 additions & 1 deletion src/Playwright/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ final class Client
*/
private ?WebsocketConnection $websocketConnection = null;

/**
* Current page instance for handling events.
*/
private ?Page $currentPage = null;

/**
* Default timeout for requests in milliseconds.
*/
Expand Down Expand Up @@ -87,8 +92,10 @@ public function execute(string $guid, string $method, array $params = [], array
$this->websocketConnection->sendText($requestJson);

while (true) {
// @phpstan-ignore-next-line
$responseJson = $this->fetch($this->websocketConnection);
/** @var array{id: string|null, params: array{add: string|null}, error: array{error: array{message: string|null}}} $response */

/** @var array{id: string|null, method?: string, params: array{add: string|null, type: string|null, guid: string|null, initializer: array{type: string, message: string, defaultValue: string}|null }, error: array{error: array{message: string|null}}} $response */
$response = json_decode($responseJson, true);

if (isset($response['error']['error']['message'])) {
Expand All @@ -101,6 +108,12 @@ public function execute(string $guid, string $method, array $params = [], array
throw new ExpectationFailedException($message);
}

if (isset($response['method']) && $response['method'] === '__create__'
&& isset($response['params']['type']) && $response['params']['type'] === 'Dialog'
&& isset($response['params']['guid'], $response['params']['initializer'])) {
$this->handleDialogCreation($response['params']['guid'], $response['params']['initializer']);
}

yield $response;

if (
Expand Down Expand Up @@ -128,6 +141,33 @@ public function timeout(): int
return $this->timeout;
}

/**
* Sets the current page for event handling.
*/
public function setCurrentPage(Page $page): void
{
$this->currentPage = $page;
}

/**
* Handles dialog creation events.
*
* @param array{type: string, message: string, defaultValue: string} $initializer
*/
private function handleDialogCreation(string $dialogGuid, array $initializer): void
{
if ($this->currentPage instanceof Page && $this->currentPage->hasDialogHandler()) {
$dialog = new Dialog(
$dialogGuid,
$initializer['type'],
$initializer['message'],
$initializer['defaultValue']
);

$this->currentPage->handleDialogEvent($dialog);
}
}

/**
* Fetches the response from the Playwright server.
*/
Expand Down
93 changes: 93 additions & 0 deletions src/Playwright/Dialog.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?php

declare(strict_types=1);

namespace Pest\Browser\Playwright;

use Generator;
use Pest\Browser\Playwright\Concerns\InteractsWithPlaywright;

/**
* @internal
*/
final readonly class Dialog
{
use InteractsWithPlaywright;

/**
* Creates a new dialog instance.
*/
public function __construct(
private string $guid,
private string $type,
private string $message,
private string $defaultValue,
) {
//
}

/**
* Returns the dialog's GUID for debugging.
*/
public function guid(): string
{
return $this->guid;
}

/**
* Returns the dialog's message.
*/
public function message(): string
{
return $this->message;
}

/**
* Returns the dialog's type (alert, confirm, prompt).
*/
public function type(): string
{
return $this->type;
}

/**
* Returns the dialog's default value for prompt dialogs.
*/
public function defaultValue(): string
{
return $this->defaultValue;
}

/**
* Accepts the dialog.
*/
public function accept(?string $promptText = null): void
{
$params = [];
if ($promptText !== null) {
$params['promptText'] = $promptText;
}

$response = $this->sendMessage('accept', $params);
$this->processVoidResponse($response);
}

/**
* Dismisses the dialog.
*/
public function dismiss(): void
{
$response = $this->sendMessage('dismiss');
$this->processVoidResponse($response);
}

/**
* Send a message to the dialog via the channel.
*
* @param array<string, mixed> $params
*/
private function sendMessage(string $method, array $params = []): Generator
{
return Client::instance()->execute($this->guid, $method, $params);
}
}
71 changes: 70 additions & 1 deletion src/Playwright/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Pest\Browser\Playwright;

use Closure;
use Exception;
use Generator;
use Pest\Browser\Execution;
use Pest\Browser\Support\ImageDiffView;
Expand Down Expand Up @@ -32,6 +34,11 @@ final class Page
*/
private bool $strictLocators = true;

/**
* Dialog event handler.
*/
private ?Closure $dialogHandler = null;

/**
* Creates a new page instance.
*/
Expand All @@ -40,7 +47,7 @@ public function __construct(
private readonly string $guid,
private readonly string $frameGuid,
) {
//
Client::instance()->setCurrentPage($this);
}

/**
Expand Down Expand Up @@ -566,6 +573,68 @@ public function expectScreenshot(bool $fullPage, bool $openDiff): void
}
}

/**
* Sets up a dialog handler for this page.
*/
public function onDialog(Closure $handler): void
{
$this->dialogHandler = $handler;

$response = Client::instance()->execute($this->guid, 'updateSubscription', [
'event' => 'dialog',
'enabled' => true,
]);

$this->processVoidResponse($response);
}

/**
* Removes any previously set dialog handler.
*/
public function removeDialogHandler(): void
{
$this->dialogHandler = null;

$response = Client::instance()->execute($this->guid, 'updateSubscription', [
'event' => 'dialog',
'enabled' => false,
]);

$this->processVoidResponse($response);
}

/**
* Checks if a dialog handler is currently set.
*/
public function hasDialogHandler(): bool
{
return $this->dialogHandler instanceof Closure;
}

/**
* Gets the current dialog handler.
*/
public function getDialogHandler(): ?Closure
{
return $this->dialogHandler;
}

/**
* Handles a dialog event from the Playwright server.
*/
public function handleDialogEvent(Dialog $dialog): void
{
if ($this->dialogHandler instanceof Closure) {
try {
($this->dialogHandler)($dialog);
} catch (Exception $e) {
$dialog->dismiss();

throw $e;
}
}
}

/**
* Closes the page.
*/
Expand Down
Loading