diff --git a/src/FileChooser/FileChooser.php b/src/FileChooser/FileChooser.php index c26ba57..ef4fbee 100644 --- a/src/FileChooser/FileChooser.php +++ b/src/FileChooser/FileChooser.php @@ -14,6 +14,7 @@ namespace Playwright\FileChooser; +use Playwright\FileChooser\Options\SetFilesOptions; use Playwright\Page\PageInterface; use Playwright\Transport\TransportInterface; @@ -50,10 +51,11 @@ public function page(): PageInterface /** * @param string|array|array{name: string, mimeType: string, buffer: string}|array $files - * @param array{noWaitAfter?: bool, timeout?: int} $options + * @param array|SetFilesOptions $options */ - public function setFiles(string|array $files, array $options = []): void + public function setFiles(string|array $files, array|SetFilesOptions $options = []): void { + $options = SetFilesOptions::from($options); $normalizedFiles = $this->normalizeFiles($files); $payload = [ @@ -62,8 +64,9 @@ public function setFiles(string|array $files, array $options = []): void 'files' => $normalizedFiles, ]; - if (!empty($options)) { - $payload['options'] = $options; + $optionsArray = $options->toArray(); + if (!empty($optionsArray)) { + $payload['options'] = $optionsArray; } $fileChooserId = $this->data['fileChooserId'] ?? null; diff --git a/src/FileChooser/FileChooserInterface.php b/src/FileChooser/FileChooserInterface.php index 68db292..4a95e70 100644 --- a/src/FileChooser/FileChooserInterface.php +++ b/src/FileChooser/FileChooserInterface.php @@ -14,6 +14,7 @@ namespace Playwright\FileChooser; +use Playwright\FileChooser\Options\SetFilesOptions; use Playwright\Page\PageInterface; /** @@ -42,9 +43,7 @@ public function page(): PageInterface; * For empty array, clears the selected files. * * @param string|array|array{name: string, mimeType: string, buffer: string}|array $files - * @param array{noWaitAfter?: bool, timeout?: int} $options - * @param string|string[]|array{name: string, mimeType: string, buffer: string}|array $files - * @param array $options + * @param array|SetFilesOptions $options */ - public function setFiles(string|array $files, array $options = []): void; + public function setFiles(string|array $files, array|SetFilesOptions $options = []): void; } diff --git a/src/FileChooser/Options/SetFilesOptions.php b/src/FileChooser/Options/SetFilesOptions.php new file mode 100644 index 0000000..90044ad --- /dev/null +++ b/src/FileChooser/Options/SetFilesOptions.php @@ -0,0 +1,58 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|int|null $timeoutValue */ + $timeoutValue = $options['timeout'] ?? null; + $timeout = null !== $timeoutValue ? (float) $timeoutValue : null; + + return new self($noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Locator.php b/src/Locator/Locator.php index 2c14f98..13072ff 100644 --- a/src/Locator/Locator.php +++ b/src/Locator/Locator.php @@ -19,6 +19,25 @@ use Playwright\Exception\TimeoutException; use Playwright\Frame\FrameLocator; use Playwright\Frame\FrameLocatorInterface; +use Playwright\Locator\Options\CheckOptions; +use Playwright\Locator\Options\ClearOptions; +use Playwright\Locator\Options\ClickOptions; +use Playwright\Locator\Options\DblClickOptions; +use Playwright\Locator\Options\DragToOptions; +use Playwright\Locator\Options\FillOptions; +use Playwright\Locator\Options\FilterOptions; +use Playwright\Locator\Options\GetAttributeOptions; +use Playwright\Locator\Options\GetByOptions; +use Playwright\Locator\Options\GetByRoleOptions; +use Playwright\Locator\Options\HoverOptions; +use Playwright\Locator\Options\LocatorScreenshotOptions; +use Playwright\Locator\Options\PressOptions; +use Playwright\Locator\Options\SelectOptionOptions; +use Playwright\Locator\Options\SetInputFilesOptions; +use Playwright\Locator\Options\TextContentOptions; +use Playwright\Locator\Options\TypeOptions; +use Playwright\Locator\Options\UncheckOptions; +use Playwright\Locator\Options\WaitForOptions; use Playwright\Transport\TransportInterface; use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; @@ -75,28 +94,31 @@ public function getOptions(): array } /** - * @param array $options + * @param array|ClickOptions $options */ - public function click(array $options = []): void + public function click(array|ClickOptions $options = []): void { + $options = ClickOptions::from($options); $this->waitForActionable(); - $this->sendCommand('locator.click', ['options' => $options]); + $this->sendCommand('locator.click', ['options' => $options->toArray()]); } /** - * @param array $options + * @param array|DblClickOptions $options */ - public function dblclick(array $options = []): void + public function dblclick(array|DblClickOptions $options = []): void { - $this->sendCommand('locator.dblclick', ['options' => $options]); + $options = DblClickOptions::from($options); + $this->sendCommand('locator.dblclick', ['options' => $options->toArray()]); } /** - * @param array $options + * @param array|ClearOptions $options */ - public function clear(array $options = []): void + public function clear(array|ClearOptions $options = []): void { - $this->sendCommand('locator.clear', ['options' => $options]); + $options = ClearOptions::from($options); + $this->sendCommand('locator.clear', ['options' => $options->toArray()]); } public function focus(): void @@ -110,14 +132,15 @@ public function blur(): void } /** - * @param array $options + * @param array|LocatorScreenshotOptions $options */ - public function screenshot(?string $path = null, array $options = []): ?string + public function screenshot(?string $path = null, array|LocatorScreenshotOptions $options = []): ?string { + $options = LocatorScreenshotOptions::from($options); if ($path) { - $options['path'] = $path; + $options->path = $path; } - $response = $this->sendCommand('locator.screenshot', ['options' => $options]); + $response = $this->sendCommand('locator.screenshot', ['options' => $options->toArray()]); if ($path) { return null; @@ -255,33 +278,43 @@ public function locator(string $selector): self /** * @param array $options */ - public function getByAltText(string $text, array $options = []): self + /** + * @param array|GetByOptions $options + */ + public function getByAltText(string $text, array|GetByOptions $options = []): self { + $options = GetByOptions::from($options); + return $this->locator(\sprintf('[alt="%s"]', $text)); } /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByLabel(string $text, array $options = []): self + public function getByLabel(string $text, array|GetByOptions $options = []): self { + $options = GetByOptions::from($options); + return $this->locator(\sprintf('label:text-is("%s") >> nth=0', $text)); } /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByPlaceholder(string $text, array $options = []): self + public function getByPlaceholder(string $text, array|GetByOptions $options = []): self { + $options = GetByOptions::from($options); + return $this->locator(\sprintf('[placeholder="%s"]', $text)); } /** - * @param array $options + * @param array|GetByRoleOptions $options */ - public function getByRole(string $role, array $options = []): self + public function getByRole(string $role, array|GetByRoleOptions $options = []): self { - $selector = RoleSelectorBuilder::buildSelector($role, $options); + $options = GetByRoleOptions::from($options); + $selector = RoleSelectorBuilder::buildSelector($role, $options->toArray()); return $this->locator($selector); } @@ -292,38 +325,44 @@ public function getByTestId(string $testId): self } /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByText(string $text, array $options = []): self + public function getByText(string $text, array|GetByOptions $options = []): self { + $options = GetByOptions::from($options); + return $this->locator(\sprintf('text="%s"', $text)); } /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByTitle(string $text, array $options = []): self + public function getByTitle(string $text, array|GetByOptions $options = []): self { + $options = GetByOptions::from($options); + return $this->locator(\sprintf('[title="%s"]', $text)); } /** - * @param array $options + * @param array|WaitForOptions $options */ - public function waitFor(array $options = []): void + public function waitFor(array|WaitForOptions $options = []): void { - $this->sendCommand('locator.waitFor', ['options' => $options]); + $options = WaitForOptions::from($options); + $this->sendCommand('locator.waitFor', ['options' => $options->toArray()]); } /** - * @param string|array $values - * @param array $options + * @param string|array $values + * @param array|SelectOptionOptions $options * * @return array */ - public function selectOption(string|array $values, array $options = []): array + public function selectOption(string|array $values, array|SelectOptionOptions $options = []): array { - $response = $this->sendCommand('locator.selectOption', ['values' => $values, 'options' => $options]); + $options = SelectOptionOptions::from($options); + $response = $this->sendCommand('locator.selectOption', ['values' => $values, 'options' => $options->toArray()]); $values = $response['values'] ?? []; if (!is_array($values)) { return []; @@ -333,11 +372,12 @@ public function selectOption(string|array $values, array $options = []): array } /** - * @param string|array $files - * @param array $options + * @param string|array $files + * @param array|SetInputFilesOptions $options */ - public function setInputFiles(string|array $files, array $options = []): void + public function setInputFiles(string|array $files, array|SetInputFilesOptions $options = []): void { + $options = SetInputFilesOptions::from($options); $fileArray = \is_array($files) ? $files : [$files]; $this->logger->debug('Setting input files on locator', [ @@ -353,7 +393,7 @@ public function setInputFiles(string|array $files, array $options = []): void } try { - $this->sendCommand('locator.setInputFiles', ['files' => $fileArray, 'options' => $options]); + $this->sendCommand('locator.setInputFiles', ['files' => $fileArray, 'options' => $options->toArray()]); $this->logger->info('Successfully set input files on locator', [ 'selector' => (string) $this->selectorChain, 'fileCount' => \count($fileArray), @@ -370,52 +410,58 @@ public function setInputFiles(string|array $files, array $options = []): void } /** - * @param array $options + * @param array|FillOptions $options */ - public function fill(string $value, array $options = []): void + public function fill(string $value, array|FillOptions $options = []): void { + $options = FillOptions::from($options); $this->waitForActionable(); - $this->sendCommand('locator.fill', ['value' => $value, 'options' => $options]); + $this->sendCommand('locator.fill', ['value' => $value, 'options' => $options->toArray()]); } /** - * @param array $options + * @param array|TypeOptions $options */ - public function type(string $text, array $options = []): void + public function type(string $text, array|TypeOptions $options = []): void { - $this->sendCommand('locator.type', ['text' => $text, 'options' => $options]); + $options = TypeOptions::from($options); + $this->sendCommand('locator.type', ['text' => $text, 'options' => $options->toArray()]); } /** - * @param array $options + * @param array|PressOptions $options */ - public function press(string $key, array $options = []): void + public function press(string $key, array|PressOptions $options = []): void { - $this->sendCommand('locator.press', ['key' => $key, 'options' => $options]); + $options = PressOptions::from($options); + $this->sendCommand('locator.press', ['key' => $key, 'options' => $options->toArray()]); } /** - * @param array $options + * @param array|CheckOptions $options */ - public function check(array $options = []): void + public function check(array|CheckOptions $options = []): void { - $this->sendCommand('locator.check', ['options' => $options]); + $options = CheckOptions::from($options); + $this->sendCommand('locator.check', ['options' => $options->toArray()]); } /** - * @param array $options + * @param array|UncheckOptions $options */ - public function uncheck(array $options = []): void + public function uncheck(array|UncheckOptions $options = []): void { - $this->sendCommand('locator.uncheck', ['options' => $options]); + $options = UncheckOptions::from($options); + $this->sendCommand('locator.uncheck', ['options' => $options->toArray()]); } /** - * @param array $options + * @param array|HoverOptions $options */ - public function hover(array $options = []): void + public function hover(array|HoverOptions $options = []): void { - $this->sendCommand('locator.hover', ['options' => $options]); + $options = HoverOptions::from($options); + $this->sendCommand('locator.hover', ['options' => $options->toArray()]); $this->transport->processEvents(); } @@ -429,39 +475,42 @@ public function hover(array $options = []): void * - force: bool - Whether to bypass the actionability checks * - timeout: int - Maximum time in milliseconds * - * @param array $options + * @param array|DragToOptions $options */ - public function dragTo(LocatorInterface $target, array $options = []): void + public function dragTo(LocatorInterface $target, array|DragToOptions $options = []): void { + $options = DragToOptions::from($options); $this->waitForActionable(); $targetSelector = $target->getSelector(); $this->sendCommand('locator.dragAndDrop', [ 'target' => $targetSelector, - 'options' => $options, + 'options' => $options->toArray(), ]); $this->transport->processEvents(); } /** - * @param array $options + * @param array|TextContentOptions $options */ - public function textContent(array $options = []): ?string + public function textContent(array|TextContentOptions $options = []): ?string { - $response = $this->sendCommand('locator.textContent', ['options' => $options]); + $options = TextContentOptions::from($options); + $response = $this->sendCommand('locator.textContent', ['options' => $options->toArray()]); $value = $response['value'] ?? null; return is_string($value) ? $value : null; } /** - * @param array $options + * @param array|GetAttributeOptions $options */ - public function getAttribute(string $name, array $options = []): ?string + public function getAttribute(string $name, array|GetAttributeOptions $options = []): ?string { - $response = $this->sendCommand('locator.getAttribute', ['name' => $name, 'options' => $options]); + $options = GetAttributeOptions::from($options); + $response = $this->sendCommand('locator.getAttribute', ['name' => $name, 'options' => $options->toArray()]); $value = $response['value'] ?? null; return is_string($value) ? $value : null; @@ -695,21 +744,27 @@ public function waitForText(string $text, array $options = []): void } /** - * @param array $options + * @param array|FilterOptions $options */ - public function filter(array $options = []): self + public function filter(array|FilterOptions $options = []): self { + $options = FilterOptions::from($options); $chain = clone $this->selectorChain; - if (isset($options['hasText']) && is_scalar($options['hasText'])) { - $chain = $chain->append(\sprintf(':has-text("%s")', $options['hasText'])); + if (null !== $options->hasText) { + $chain->addFilter(\sprintf(':has-text("%s")', $options->hasText)); } - if (isset($options['has'])) { - $has = $options['has']; - if ($has instanceof LocatorInterface) { - $chain = $chain->append(\sprintf(':has(%s)', $has->getSelector())); - } + if (null !== $options->hasNotText) { + $chain->addFilter(\sprintf(':not(:has-text("%s"))', $options->hasNotText)); + } + + if (null !== $options->has) { + $chain->addFilter(\sprintf(':has(%s)', $options->has->getSelector())); + } + + if (null !== $options->hasNot) { + $chain->addFilter(\sprintf(':not(:has(%s))', $options->hasNot->getSelector())); } return new self($this->transport, $this->pageId, $chain, $this->frameSelector, $this->logger); diff --git a/src/Locator/LocatorInterface.php b/src/Locator/LocatorInterface.php index 69af05c..75c62aa 100644 --- a/src/Locator/LocatorInterface.php +++ b/src/Locator/LocatorInterface.php @@ -15,69 +15,88 @@ namespace Playwright\Locator; use Playwright\Frame\FrameLocatorInterface; +use Playwright\Locator\Options\CheckOptions; +use Playwright\Locator\Options\ClearOptions; +use Playwright\Locator\Options\ClickOptions; +use Playwright\Locator\Options\DblClickOptions; +use Playwright\Locator\Options\DragToOptions; +use Playwright\Locator\Options\FillOptions; +use Playwright\Locator\Options\FilterOptions; +use Playwright\Locator\Options\GetAttributeOptions; +use Playwright\Locator\Options\GetByOptions; +use Playwright\Locator\Options\GetByRoleOptions; +use Playwright\Locator\Options\HoverOptions; +use Playwright\Locator\Options\LocatorScreenshotOptions; +use Playwright\Locator\Options\PressOptions; +use Playwright\Locator\Options\SelectOptionOptions; +use Playwright\Locator\Options\SetInputFilesOptions; +use Playwright\Locator\Options\TextContentOptions; +use Playwright\Locator\Options\TypeOptions; +use Playwright\Locator\Options\UncheckOptions; +use Playwright\Locator\Options\WaitForOptions; interface LocatorInterface { /** - * @param array $options + * @param array|ClickOptions $options */ - public function click(array $options = []): void; + public function click(array|ClickOptions $options = []): void; /** - * @param array $options + * @param array|FillOptions $options */ - public function fill(string $value, array $options = []): void; + public function fill(string $value, array|FillOptions $options = []): void; /** - * @param array $options + * @param array|TypeOptions $options */ - public function type(string $value, array $options = []): void; + public function type(string $value, array|TypeOptions $options = []): void; /** - * @param array $options + * @param array|PressOptions $options */ - public function press(string $key, array $options = []): void; + public function press(string $key, array|PressOptions $options = []): void; /** - * @param array $options + * @param array|CheckOptions $options */ - public function check(array $options = []): void; + public function check(array|CheckOptions $options = []): void; /** - * @param array $options + * @param array|UncheckOptions $options */ - public function uncheck(array $options = []): void; + public function uncheck(array|UncheckOptions $options = []): void; /** - * @param array $options + * @param array|HoverOptions $options */ - public function hover(array $options = []): void; + public function hover(array|HoverOptions $options = []): void; /** * Drag this element to target element. * - * @param array $options + * @param array|DragToOptions $options */ - public function dragTo(LocatorInterface $target, array $options = []): void; + public function dragTo(LocatorInterface $target, array|DragToOptions $options = []): void; /** - * @param array $options + * @param array|DblClickOptions $options */ - public function dblclick(array $options = []): void; + public function dblclick(array|DblClickOptions $options = []): void; /** - * @param array $options + * @param array|ClearOptions $options */ - public function clear(array $options = []): void; + public function clear(array|ClearOptions $options = []): void; public function focus(): void; public function blur(): void; /** - * @param array $options + * @param array|LocatorScreenshotOptions $options */ - public function screenshot(?string $path = null, array $options = []): ?string; + public function screenshot(?string $path = null, array|LocatorScreenshotOptions $options = []): ?string; /** * @return array @@ -114,65 +133,65 @@ public function isVisible(): bool; public function locator(string $selector): self; /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByAltText(string $text, array $options = []): self; + public function getByAltText(string $text, array|GetByOptions $options = []): self; /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByLabel(string $text, array $options = []): self; + public function getByLabel(string $text, array|GetByOptions $options = []): self; /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByPlaceholder(string $text, array $options = []): self; + public function getByPlaceholder(string $text, array|GetByOptions $options = []): self; /** - * @param array $options + * @param array|GetByRoleOptions $options */ - public function getByRole(string $role, array $options = []): self; + public function getByRole(string $role, array|GetByRoleOptions $options = []): self; public function getByTestId(string $testId): self; /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByText(string $text, array $options = []): self; + public function getByText(string $text, array|GetByOptions $options = []): self; /** - * @param array $options + * @param array|GetByOptions $options */ - public function getByTitle(string $text, array $options = []): self; + public function getByTitle(string $text, array|GetByOptions $options = []): self; /** - * @param array $options + * @param array|WaitForOptions $options */ - public function waitFor(array $options = []): void; + public function waitFor(array|WaitForOptions $options = []): void; /** - * @param string|array $values - * @param array $options + * @param string|array $values + * @param array|SelectOptionOptions $options * * @return array */ - public function selectOption(string|array $values, array $options = []): array; + public function selectOption(string|array $values, array|SelectOptionOptions $options = []): array; /** - * @param string|array $files - * @param array $options + * @param string|array $files + * @param array|SetInputFilesOptions $options */ - public function setInputFiles(string|array $files, array $options = []): void; + public function setInputFiles(string|array $files, array|SetInputFilesOptions $options = []): void; /** - * @param array $options + * @param array|TextContentOptions $options */ - public function textContent(array $options = []): ?string; + public function textContent(array|TextContentOptions $options = []): ?string; /** - * @param array $options + * @param array|GetAttributeOptions $options */ - public function getAttribute(string $name, array $options = []): ?string; + public function getAttribute(string $name, array|GetAttributeOptions $options = []): ?string; public function count(): int; @@ -194,9 +213,9 @@ public function frameLocator(string $selector): FrameLocatorInterface; public function getSelector(): string; /** - * @param array $options + * @param array|FilterOptions $options */ - public function filter(array $options = []): self; + public function filter(array|FilterOptions $options = []): self; public function and(LocatorInterface $locator): self; diff --git a/src/Locator/Options/CheckOptions.php b/src/Locator/Options/CheckOptions.php new file mode 100644 index 0000000..e6c1bab --- /dev/null +++ b/src/Locator/Options/CheckOptions.php @@ -0,0 +1,78 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->position) { + $options['position'] = $this->position; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var array{x: float, y: float}|null $position */ + $position = $options['position'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + + return new self($position, $force, $noWaitAfter, $timeout, $trial); + } +} diff --git a/src/Locator/Options/ClearOptions.php b/src/Locator/Options/ClearOptions.php new file mode 100644 index 0000000..acbc0f4 --- /dev/null +++ b/src/Locator/Options/ClearOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($force, $noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Options/ClickOptions.php b/src/Locator/Options/ClickOptions.php new file mode 100644 index 0000000..aee7dc1 --- /dev/null +++ b/src/Locator/Options/ClickOptions.php @@ -0,0 +1,106 @@ +|null $modifiers + * @param array{x: float, y: float}|null $position + */ + public function __construct( + public ?string $button = null, + public ?int $clickCount = null, + public ?float $delay = null, + public ?array $position = null, + public ?array $modifiers = null, + public ?bool $force = null, + public ?bool $noWaitAfter = null, + public ?float $timeout = null, + public ?bool $trial = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $options = []; + if (null !== $this->button) { + $options['button'] = $this->button; + } + if (null !== $this->clickCount) { + $options['clickCount'] = $this->clickCount; + } + if (null !== $this->delay) { + $options['delay'] = $this->delay; + } + if (null !== $this->position) { + $options['position'] = $this->position; + } + if (null !== $this->modifiers) { + $options['modifiers'] = $this->modifiers; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var 'left'|'right'|'middle'|null $button */ + $button = $options['button'] ?? null; + /** @var int|null $clickCount */ + $clickCount = $options['clickCount'] ?? null; + /** @var float|null $delay */ + $delay = $options['delay'] ?? null; + /** @var array{x: float, y: float}|null $position */ + $position = $options['position'] ?? null; + /** @var array|null $modifiers */ + $modifiers = $options['modifiers'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + + return new self($button, $clickCount, $delay, $position, $modifiers, $force, $noWaitAfter, $timeout, $trial); + } +} diff --git a/src/Locator/Options/DblClickOptions.php b/src/Locator/Options/DblClickOptions.php new file mode 100644 index 0000000..cc1899a --- /dev/null +++ b/src/Locator/Options/DblClickOptions.php @@ -0,0 +1,104 @@ +|null $modifiers + * @param array{x: float, y: float}|null $position + */ + public function __construct( + public ?string $button = null, + public ?float $delay = null, + public ?array $modifiers = null, + public ?array $position = null, + public ?bool $force = null, + public ?bool $noWaitAfter = null, + public ?int $steps = null, + public ?float $timeout = null, + public ?bool $trial = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $options = []; + if (null !== $this->button) { + $options['button'] = $this->button; + } + if (null !== $this->delay) { + $options['delay'] = $this->delay; + } + if (null !== $this->modifiers) { + $options['modifiers'] = $this->modifiers; + } + if (null !== $this->position) { + $options['position'] = $this->position; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->steps) { + $options['steps'] = $this->steps; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var 'left'|'right'|'middle'|null $button */ + $button = $options['button'] ?? null; + /** @var float|null $delay */ + $delay = $options['delay'] ?? null; + /** @var array|null $modifiers */ + $modifiers = $options['modifiers'] ?? null; + /** @var array{x: float, y: float}|null $position */ + $position = $options['position'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var int|null $steps */ + $steps = $options['steps'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + + return new self($button, $delay, $modifiers, $position, $force, $noWaitAfter, $steps, $timeout, $trial); + } +} diff --git a/src/Locator/Options/DragToOptions.php b/src/Locator/Options/DragToOptions.php new file mode 100644 index 0000000..086eb3c --- /dev/null +++ b/src/Locator/Options/DragToOptions.php @@ -0,0 +1,91 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->sourcePosition) { + $options['sourcePosition'] = $this->sourcePosition; + } + if (null !== $this->targetPosition) { + $options['targetPosition'] = $this->targetPosition; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->steps) { + $options['steps'] = $this->steps; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var array{x: float, y: float}|null $sourcePosition */ + $sourcePosition = $options['sourcePosition'] ?? null; + /** @var array{x: float, y: float}|null $targetPosition */ + $targetPosition = $options['targetPosition'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var int|null $steps */ + $steps = $options['steps'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + + return new self($sourcePosition, $targetPosition, $force, $noWaitAfter, $steps, $timeout, $trial); + } +} diff --git a/src/Locator/Options/FillOptions.php b/src/Locator/Options/FillOptions.php new file mode 100644 index 0000000..c3035c1 --- /dev/null +++ b/src/Locator/Options/FillOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($force, $noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Options/FilterOptions.php b/src/Locator/Options/FilterOptions.php new file mode 100644 index 0000000..2d0ddaf --- /dev/null +++ b/src/Locator/Options/FilterOptions.php @@ -0,0 +1,71 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->has) { + $options['has'] = $this->has; + } + if (null !== $this->hasNot) { + $options['hasNot'] = $this->hasNot; + } + if (null !== $this->hasText) { + $options['hasText'] = $this->hasText; + } + if (null !== $this->hasNotText) { + $options['hasNotText'] = $this->hasNotText; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var LocatorInterface|null $has */ + $has = $options['has'] ?? null; + /** @var LocatorInterface|null $hasNot */ + $hasNot = $options['hasNot'] ?? null; + /** @var string|null $hasText */ + $hasText = $options['hasText'] ?? null; + /** @var string|null $hasNotText */ + $hasNotText = $options['hasNotText'] ?? null; + + return new self($has, $hasNot, $hasText, $hasNotText); + } +} diff --git a/src/Locator/Options/GetAttributeOptions.php b/src/Locator/Options/GetAttributeOptions.php new file mode 100644 index 0000000..0f8a0f4 --- /dev/null +++ b/src/Locator/Options/GetAttributeOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($timeout); + } +} diff --git a/src/Locator/Options/GetByOptions.php b/src/Locator/Options/GetByOptions.php new file mode 100644 index 0000000..f703bb4 --- /dev/null +++ b/src/Locator/Options/GetByOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->exact) { + $options['exact'] = $this->exact; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $exact */ + $exact = $options['exact'] ?? null; + + return new self($exact); + } +} diff --git a/src/Locator/Options/GetByRoleOptions.php b/src/Locator/Options/GetByRoleOptions.php new file mode 100644 index 0000000..7cc4395 --- /dev/null +++ b/src/Locator/Options/GetByRoleOptions.php @@ -0,0 +1,212 @@ +|self $options + */ + public static function from(array|self $options): self + { + if ($options instanceof self) { + return $options; + } + + $locatorOptions = LocatorOptions::fromArray($options); + + return new self( + checked: self::extractBool($options, 'checked'), + disabled: self::extractBool($options, 'disabled'), + exact: self::extractBool($options, 'exact'), + expanded: self::extractBool($options, 'expanded'), + includeHidden: self::extractBool($options, 'includeHidden'), + level: self::extractLevel($options), + name: self::extractName($options), + pressed: self::extractPressed($options), + selected: self::extractBool($options, 'selected'), + locatorOptions: $locatorOptions, + ); + } + + /** + * @return array + */ + public function toArray(): array + { + $options = $this->filteredLocatorOptions(); + + $this->appendIfNotNull($options, 'checked', $this->checked); + $this->appendIfNotNull($options, 'disabled', $this->disabled); + $this->appendIfNotNull($options, 'exact', $this->exact); + $this->appendIfNotNull($options, 'expanded', $this->expanded); + $this->appendIfNotNull($options, 'includeHidden', $this->includeHidden); + $this->appendIfNotNull($options, 'level', $this->level); + $this->appendIfNotNull($options, 'name', $this->name); + $this->appendIfNotNull($options, 'pressed', $this->pressed); + $this->appendIfNotNull($options, 'selected', $this->selected); + + return $options; + } + + /** + * @param array $options + */ + private static function extractBool(array $options, string $key): ?bool + { + if (!array_key_exists($key, $options)) { + return null; + } + + $value = $options[$key]; + if (null === $value) { + return null; + } + + if (!is_bool($value)) { + throw new RuntimeException(sprintf('getByRole option "%s" must be boolean.', $key)); + } + + return $value; + } + + /** + * @param array $options + */ + private static function extractLevel(array $options): ?int + { + if (!array_key_exists('level', $options)) { + return null; + } + + $value = $options['level']; + if (null === $value) { + return null; + } + + if (is_int($value)) { + return $value; + } + + if (is_string($value) && ctype_digit($value)) { + return (int) $value; + } + + throw new RuntimeException('getByRole option "level" must be an integer.'); + } + + /** + * @param array $options + */ + private static function extractPressed(array $options): bool|string|null + { + if (!array_key_exists('pressed', $options)) { + return null; + } + + $value = $options['pressed']; + if (null === $value) { + return null; + } + + if (is_bool($value)) { + return $value; + } + + if (is_string($value) && 'mixed' === $value) { + return 'mixed'; + } + + throw new RuntimeException('getByRole option "pressed" must be boolean or "mixed".'); + } + + /** + * @param array $options + */ + private static function extractName(array $options): ?string + { + if (!array_key_exists('name', $options)) { + return null; + } + + $value = $options['name']; + if (null === $value) { + return null; + } + + if (is_scalar($value) || $value instanceof \Stringable) { + return (string) $value; + } + + throw new RuntimeException('getByRole option "name" must be stringable.'); + } + + /** + * @return array + */ + private function filteredLocatorOptions(): array + { + $options = $this->locatorOptions->toArray(); + foreach (self::ROLE_KEYS as $key) { + if (array_key_exists($key, $options)) { + unset($options[$key]); + } + } + + return $options; + } + + /** + * @param array $options + */ + private function appendIfNotNull(array &$options, string $key, mixed $value): void + { + if (null === $value) { + return; + } + + $options[$key] = $value; + } +} diff --git a/src/Locator/Options/HoverOptions.php b/src/Locator/Options/HoverOptions.php new file mode 100644 index 0000000..f94eb76 --- /dev/null +++ b/src/Locator/Options/HoverOptions.php @@ -0,0 +1,85 @@ +|null $modifiers + * @param array{x: float, y: float}|null $position + */ + public function __construct( + public ?array $modifiers = null, + public ?array $position = null, + public ?bool $force = null, + public ?bool $noWaitAfter = null, + public ?float $timeout = null, + public ?bool $trial = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $options = []; + if (null !== $this->modifiers) { + $options['modifiers'] = $this->modifiers; + } + if (null !== $this->position) { + $options['position'] = $this->position; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var array|null $modifiers */ + $modifiers = $options['modifiers'] ?? null; + /** @var array{x: float, y: float}|null $position */ + $position = $options['position'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + + return new self($modifiers, $position, $force, $noWaitAfter, $timeout, $trial); + } +} diff --git a/src/Locator/Options/LocatorOptions.php b/src/Locator/Options/LocatorOptions.php new file mode 100644 index 0000000..8c57089 --- /dev/null +++ b/src/Locator/Options/LocatorOptions.php @@ -0,0 +1,173 @@ + $extras + */ + public function __construct( + public ?LocatorInterface $has = null, + public ?LocatorInterface $hasNot = null, + public ?string $hasText = null, + public ?string $hasNotText = null, + public ?bool $strict = null, + private array $extras = [], + ) { + } + + /** + * @param array|self $options + */ + public static function from(array|self $options): self + { + if ($options instanceof self) { + return $options; + } + + return self::fromArray($options); + } + + /** + * @param array $options + */ + public static function fromArray(array $options): self + { + $has = self::extractLocator($options, 'has'); + $hasNot = self::extractLocator($options, 'hasNot'); + $hasText = self::extractString($options, 'hasText'); + $hasNotText = self::extractString($options, 'hasNotText'); + $strict = array_key_exists('strict', $options) ? self::extractBool($options['strict'], 'strict') : null; + + $extras = self::collectExtras($options); + + return new self($has, $hasNot, $hasText, $hasNotText, $strict, $extras); + } + + /** + * @return array + */ + public function toArray(): array + { + $options = $this->extras; + + if (null !== $this->has) { + $options['has'] = $this->has; + } + if (null !== $this->hasNot) { + $options['hasNot'] = $this->hasNot; + } + if (null !== $this->hasText) { + $options['hasText'] = $this->hasText; + } + if (null !== $this->hasNotText) { + $options['hasNotText'] = $this->hasNotText; + } + if (null !== $this->strict) { + $options['strict'] = $this->strict; + } + + return $options; + } + + /** + * @param array $options + */ + private static function extractLocator(array $options, string $key): ?LocatorInterface + { + if (!array_key_exists($key, $options)) { + return null; + } + + $value = $options[$key]; + if (null === $value) { + return null; + } + + if (!$value instanceof LocatorInterface) { + throw new RuntimeException(sprintf('Locator option "%s" must be a Locator instance or null.', $key)); + } + + return $value; + } + + /** + * @param array $options + */ + private static function extractString(array $options, string $key): ?string + { + if (!array_key_exists($key, $options)) { + return null; + } + + $value = $options[$key]; + if (null === $value) { + return null; + } + + if (!is_scalar($value) && !$value instanceof \Stringable) { + throw new RuntimeException(sprintf('Locator option "%s" must be stringable.', $key)); + } + + return (string) $value; + } + + private static function extractBool(mixed $value, string $key): bool + { + if (!is_bool($value)) { + throw new RuntimeException(sprintf('Locator option "%s" must be boolean.', $key)); + } + + return $value; + } + + /** + * @param array $options + * + * @return array + */ + private static function collectExtras(array $options): array + { + $extras = []; + foreach ($options as $key => $value) { + if (!is_string($key)) { + continue; + } + + if (in_array($key, self::KNOWN_KEYS, true)) { + continue; + } + + $extras[$key] = $value; + } + + return $extras; + } +} diff --git a/src/Locator/Options/LocatorScreenshotOptions.php b/src/Locator/Options/LocatorScreenshotOptions.php new file mode 100644 index 0000000..e42b37b --- /dev/null +++ b/src/Locator/Options/LocatorScreenshotOptions.php @@ -0,0 +1,118 @@ +|null $mask + */ + public function __construct( + public ?string $path = null, + public ?string $type = null, + public ?int $quality = null, + public ?bool $omitBackground = null, + public ?float $timeout = null, + public ?string $animations = null, + public ?string $caret = null, + public ?string $scale = null, + public ?array $mask = null, + public ?string $maskColor = null, + public ?string $style = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $options = []; + if (null !== $this->path) { + $options['path'] = $this->path; + } + if (null !== $this->type) { + $options['type'] = $this->type; + } + if (null !== $this->quality) { + $options['quality'] = $this->quality; + } + if (null !== $this->omitBackground) { + $options['omitBackground'] = $this->omitBackground; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->animations) { + $options['animations'] = $this->animations; + } + if (null !== $this->caret) { + $options['caret'] = $this->caret; + } + if (null !== $this->scale) { + $options['scale'] = $this->scale; + } + if (null !== $this->mask) { + $options['mask'] = $this->mask; + } + if (null !== $this->maskColor) { + $options['maskColor'] = $this->maskColor; + } + if (null !== $this->style) { + $options['style'] = $this->style; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $path */ + $path = $options['path'] ?? null; + /** @var 'png'|'jpeg'|null $type */ + $type = $options['type'] ?? null; + /** @var int|null $quality */ + $quality = $options['quality'] ?? null; + /** @var bool|null $omitBackground */ + $omitBackground = $options['omitBackground'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var 'disabled'|'allow'|null $animations */ + $animations = $options['animations'] ?? null; + /** @var 'hide'|'initial'|null $caret */ + $caret = $options['caret'] ?? null; + /** @var 'css'|'device'|null $scale */ + $scale = $options['scale'] ?? null; + /** @var array|null $mask */ + $mask = $options['mask'] ?? null; + /** @var string|null $maskColor */ + $maskColor = $options['maskColor'] ?? null; + /** @var string|null $style */ + $style = $options['style'] ?? null; + + return new self($path, $type, $quality, $omitBackground, $timeout, $animations, $caret, $scale, $mask, $maskColor, $style); + } +} diff --git a/src/Locator/Options/PressOptions.php b/src/Locator/Options/PressOptions.php new file mode 100644 index 0000000..3d1d7bc --- /dev/null +++ b/src/Locator/Options/PressOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->delay) { + $options['delay'] = $this->delay; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $delay */ + $delay = $options['delay'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($delay, $noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Options/SelectOptionOptions.php b/src/Locator/Options/SelectOptionOptions.php new file mode 100644 index 0000000..46f50e6 --- /dev/null +++ b/src/Locator/Options/SelectOptionOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($force, $noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Options/SetInputFilesOptions.php b/src/Locator/Options/SetInputFilesOptions.php new file mode 100644 index 0000000..57e538d --- /dev/null +++ b/src/Locator/Options/SetInputFilesOptions.php @@ -0,0 +1,57 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Options/TextContentOptions.php b/src/Locator/Options/TextContentOptions.php new file mode 100644 index 0000000..cc45cf3 --- /dev/null +++ b/src/Locator/Options/TextContentOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($timeout); + } +} diff --git a/src/Locator/Options/TypeOptions.php b/src/Locator/Options/TypeOptions.php new file mode 100644 index 0000000..6bc8707 --- /dev/null +++ b/src/Locator/Options/TypeOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->delay) { + $options['delay'] = $this->delay; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $delay */ + $delay = $options['delay'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($delay, $noWaitAfter, $timeout); + } +} diff --git a/src/Locator/Options/UncheckOptions.php b/src/Locator/Options/UncheckOptions.php new file mode 100644 index 0000000..0927c0a --- /dev/null +++ b/src/Locator/Options/UncheckOptions.php @@ -0,0 +1,78 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->position) { + $options['position'] = $this->position; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var array{x: float, y: float}|null $position */ + $position = $options['position'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + + return new self($position, $force, $noWaitAfter, $timeout, $trial); + } +} diff --git a/src/Locator/Options/WaitForOptions.php b/src/Locator/Options/WaitForOptions.php new file mode 100644 index 0000000..50e6b84 --- /dev/null +++ b/src/Locator/Options/WaitForOptions.php @@ -0,0 +1,60 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->state) { + $options['state'] = $this->state; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var 'attached'|'detached'|'visible'|'hidden'|null $state */ + $state = $options['state'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($state, $timeout); + } +} diff --git a/src/Locator/SelectorChain.php b/src/Locator/SelectorChain.php index 4b82212..4153106 100644 --- a/src/Locator/SelectorChain.php +++ b/src/Locator/SelectorChain.php @@ -31,6 +31,18 @@ public function append(string $selector): self return $this; } + public function addFilter(string $filter): self + { + if (empty($this->selectors)) { + $this->selectors[] = $filter; + } else { + $last = array_pop($this->selectors); + $this->selectors[] = $last.$filter; + } + + return $this; + } + public function __toString(): string { return implode(' >> ', $this->selectors); diff --git a/src/Page/Options/ClickOptions.php b/src/Page/Options/ClickOptions.php new file mode 100644 index 0000000..8576965 --- /dev/null +++ b/src/Page/Options/ClickOptions.php @@ -0,0 +1,112 @@ +|null $modifiers + * @param array{x: float, y: float}|null $position + */ + public function __construct( + public ?string $button = null, + public ?int $clickCount = null, + public ?float $delay = null, + public ?array $position = null, + public ?array $modifiers = null, + public ?bool $force = null, + public ?bool $noWaitAfter = null, + public ?float $timeout = null, + public ?bool $trial = null, + public ?bool $strict = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $options = []; + if (null !== $this->button) { + $options['button'] = $this->button; + } + if (null !== $this->clickCount) { + $options['clickCount'] = $this->clickCount; + } + if (null !== $this->delay) { + $options['delay'] = $this->delay; + } + if (null !== $this->position) { + $options['position'] = $this->position; + } + if (null !== $this->modifiers) { + $options['modifiers'] = $this->modifiers; + } + if (null !== $this->force) { + $options['force'] = $this->force; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->trial) { + $options['trial'] = $this->trial; + } + if (null !== $this->strict) { + $options['strict'] = $this->strict; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var 'left'|'right'|'middle'|null $button */ + $button = $options['button'] ?? null; + /** @var int|null $clickCount */ + $clickCount = $options['clickCount'] ?? null; + /** @var float|null $delay */ + $delay = $options['delay'] ?? null; + /** @var array{x: float, y: float}|null $position */ + $position = $options['position'] ?? null; + /** @var array|null $modifiers */ + $modifiers = $options['modifiers'] ?? null; + /** @var bool|null $force */ + $force = $options['force'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $trial */ + $trial = $options['trial'] ?? null; + /** @var bool|null $strict */ + $strict = $options['strict'] ?? null; + + return new self($button, $clickCount, $delay, $position, $modifiers, $force, $noWaitAfter, $timeout, $trial, $strict); + } +} diff --git a/src/Page/Options/FrameQueryOptions.php b/src/Page/Options/FrameQueryOptions.php new file mode 100644 index 0000000..484d8ef --- /dev/null +++ b/src/Page/Options/FrameQueryOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->name) { + $options['name'] = $this->name; + } + if (null !== $this->url) { + $options['url'] = $this->url; + } + if (null !== $this->urlRegex) { + $options['urlRegex'] = $this->urlRegex; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $name */ + $name = $options['name'] ?? null; + /** @var string|null $url */ + $url = $options['url'] ?? null; + /** @var string|null $urlRegex */ + $urlRegex = $options['urlRegex'] ?? null; + + return new self($name, $url, $urlRegex); + } +} diff --git a/src/Page/Options/GotoOptions.php b/src/Page/Options/GotoOptions.php new file mode 100644 index 0000000..fb61bf6 --- /dev/null +++ b/src/Page/Options/GotoOptions.php @@ -0,0 +1,69 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->referer) { + $options['referer'] = $this->referer; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->waitUntil) { + $options['waitUntil'] = $this->waitUntil; + } + if (null !== $this->navigationRequest) { + $options['navigationRequest'] = $this->navigationRequest; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $referer */ + $referer = $options['referer'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var string|null $waitUntil */ + $waitUntil = $options['waitUntil'] ?? null; + /** @var bool|null $navigationRequest */ + $navigationRequest = $options['navigationRequest'] ?? null; + + return new self($referer, $timeout, $waitUntil, $navigationRequest); + } +} diff --git a/src/Page/Options/NavigationHistoryOptions.php b/src/Page/Options/NavigationHistoryOptions.php new file mode 100644 index 0000000..fb4f336 --- /dev/null +++ b/src/Page/Options/NavigationHistoryOptions.php @@ -0,0 +1,60 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->waitUntil) { + $options['waitUntil'] = $this->waitUntil; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var 'load'|'domcontentloaded'|'networkidle'|'commit'|null $waitUntil */ + $waitUntil = $options['waitUntil'] ?? null; + + return new self($timeout, $waitUntil); + } +} diff --git a/src/Page/Options/PdfOptions.php b/src/Page/Options/PdfOptions.php index a0fa30d..7aff0d1 100644 --- a/src/Page/Options/PdfOptions.php +++ b/src/Page/Options/PdfOptions.php @@ -126,25 +126,66 @@ public static function fromArray(array $options): self $scale = (float) $options['scale']; } + $path = self::extractString($options, 'path'); + $format = self::extractString($options, 'format'); + $width = self::extractString($options, 'width'); + $height = self::extractString($options, 'height'); + $footerTemplate = self::extractString($options, 'footerTemplate'); + $headerTemplate = self::extractString($options, 'headerTemplate'); + $pageRanges = self::extractString($options, 'pageRanges'); + + $landscape = self::extractBool($options, 'landscape'); + $printBackground = self::extractBool($options, 'printBackground'); + $displayHeaderFooter = self::extractBool($options, 'displayHeaderFooter'); + $outline = self::extractBool($options, 'outline'); + $preferCSSPageSize = self::extractBool($options, 'preferCSSPageSize'); + $tagged = self::extractBool($options, 'tagged'); + return new self( - path: isset($options['path']) ? (string) $options['path'] : null, - format: isset($options['format']) ? (string) $options['format'] : null, - landscape: isset($options['landscape']) ? (bool) $options['landscape'] : null, + path: $path, + format: $format, + landscape: $landscape, scale: $scale, - printBackground: isset($options['printBackground']) ? (bool) $options['printBackground'] : null, - width: isset($options['width']) ? (string) $options['width'] : null, - height: isset($options['height']) ? (string) $options['height'] : null, + printBackground: $printBackground, + width: $width, + height: $height, margin: $options['margin'] ?? null, - displayHeaderFooter: isset($options['displayHeaderFooter']) ? (bool) $options['displayHeaderFooter'] : null, - footerTemplate: isset($options['footerTemplate']) ? (string) $options['footerTemplate'] : null, - headerTemplate: isset($options['headerTemplate']) ? (string) $options['headerTemplate'] : null, - outline: isset($options['outline']) ? (bool) $options['outline'] : null, - pageRanges: isset($options['pageRanges']) ? (string) $options['pageRanges'] : null, - preferCSSPageSize: isset($options['preferCSSPageSize']) ? (bool) $options['preferCSSPageSize'] : null, - tagged: isset($options['tagged']) ? (bool) $options['tagged'] : null, + displayHeaderFooter: $displayHeaderFooter, + footerTemplate: $footerTemplate, + headerTemplate: $headerTemplate, + outline: $outline, + pageRanges: $pageRanges, + preferCSSPageSize: $preferCSSPageSize, + tagged: $tagged, ); } + /** + * @param array $options + */ + private static function extractString(array $options, string $key): ?string + { + if (!isset($options[$key])) { + return null; + } + + $value = $options[$key]; + + return is_scalar($value) ? (string) $value : null; + } + + /** + * @param array $options + */ + private static function extractBool(array $options, string $key): ?bool + { + if (!isset($options[$key])) { + return null; + } + + return (bool) $options[$key]; + } + public function path(): ?string { return $this->path; @@ -262,7 +303,12 @@ private static function normalizeMargin(mixed $margin): ?array continue; } - $normalizedValue = self::normalizeNullableString((string) $value); + if (!is_scalar($value)) { + continue; + } + + $stringValue = (string) $value; + $normalizedValue = self::normalizeNullableString($stringValue); if (null !== $normalizedValue) { $normalized[$edge] = $normalizedValue; } diff --git a/src/Page/Options/ScreenshotOptions.php b/src/Page/Options/ScreenshotOptions.php new file mode 100644 index 0000000..9f69553 --- /dev/null +++ b/src/Page/Options/ScreenshotOptions.php @@ -0,0 +1,125 @@ +|null $mask + */ + public function __construct( + public ?string $path = null, + public ?string $type = null, + public ?int $quality = null, + public ?bool $fullPage = null, + public ?array $clip = null, + public ?bool $omitBackground = null, + public ?float $timeout = null, + public ?string $animations = null, + public ?string $caret = null, + public ?string $scale = null, + public ?array $mask = null, + public ?string $maskColor = null, + ) { + } + + /** + * @return array + */ + public function toArray(): array + { + $options = []; + if (null !== $this->path) { + $options['path'] = $this->path; + } + if (null !== $this->type) { + $options['type'] = $this->type; + } + if (null !== $this->quality) { + $options['quality'] = $this->quality; + } + if (null !== $this->fullPage) { + $options['fullPage'] = $this->fullPage; + } + if (null !== $this->clip) { + $options['clip'] = $this->clip; + } + if (null !== $this->omitBackground) { + $options['omitBackground'] = $this->omitBackground; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->animations) { + $options['animations'] = $this->animations; + } + if (null !== $this->caret) { + $options['caret'] = $this->caret; + } + if (null !== $this->scale) { + $options['scale'] = $this->scale; + } + if (null !== $this->mask) { + $options['mask'] = $this->mask; + } + if (null !== $this->maskColor) { + $options['maskColor'] = $this->maskColor; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $path */ + $path = $options['path'] ?? null; + /** @var 'png'|'jpeg'|null $type */ + $type = $options['type'] ?? null; + /** @var int|null $quality */ + $quality = $options['quality'] ?? null; + /** @var bool|null $fullPage */ + $fullPage = $options['fullPage'] ?? null; + /** @var array{x: float, y: float, width: float, height: float}|null $clip */ + $clip = $options['clip'] ?? null; + /** @var bool|null $omitBackground */ + $omitBackground = $options['omitBackground'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var 'disabled'|'allow'|null $animations */ + $animations = $options['animations'] ?? null; + /** @var 'hide'|'initial'|null $caret */ + $caret = $options['caret'] ?? null; + /** @var 'css'|'device'|null $scale */ + $scale = $options['scale'] ?? null; + /** @var array|null $mask */ + $mask = $options['mask'] ?? null; + /** @var string|null $maskColor */ + $maskColor = $options['maskColor'] ?? null; + + return new self($path, $type, $quality, $fullPage, $clip, $omitBackground, $timeout, $animations, $caret, $scale, $mask, $maskColor); + } +} diff --git a/src/Page/Options/ScriptTagOptions.php b/src/Page/Options/ScriptTagOptions.php new file mode 100644 index 0000000..5b5ce0c --- /dev/null +++ b/src/Page/Options/ScriptTagOptions.php @@ -0,0 +1,69 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->url) { + $options['url'] = $this->url; + } + if (null !== $this->path) { + $options['path'] = $this->path; + } + if (null !== $this->content) { + $options['content'] = $this->content; + } + if (null !== $this->type) { + $options['type'] = $this->type; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $url */ + $url = $options['url'] ?? null; + /** @var string|null $path */ + $path = $options['path'] ?? null; + /** @var string|null $content */ + $content = $options['content'] ?? null; + /** @var string|null $type */ + $type = $options['type'] ?? null; + + return new self($url, $path, $content, $type); + } +} diff --git a/src/Page/Options/SetContentOptions.php b/src/Page/Options/SetContentOptions.php new file mode 100644 index 0000000..8f19522 --- /dev/null +++ b/src/Page/Options/SetContentOptions.php @@ -0,0 +1,60 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->waitUntil) { + $options['waitUntil'] = $this->waitUntil; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var 'load'|'domcontentloaded'|'networkidle'|'commit'|null $waitUntil */ + $waitUntil = $options['waitUntil'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($waitUntil, $timeout); + } +} diff --git a/src/Page/Options/SetInputFilesOptions.php b/src/Page/Options/SetInputFilesOptions.php new file mode 100644 index 0000000..faab9ef --- /dev/null +++ b/src/Page/Options/SetInputFilesOptions.php @@ -0,0 +1,57 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($noWaitAfter, $timeout); + } +} diff --git a/src/Page/Options/StyleTagOptions.php b/src/Page/Options/StyleTagOptions.php new file mode 100644 index 0000000..818223a --- /dev/null +++ b/src/Page/Options/StyleTagOptions.php @@ -0,0 +1,63 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->url) { + $options['url'] = $this->url; + } + if (null !== $this->path) { + $options['path'] = $this->path; + } + if (null !== $this->content) { + $options['content'] = $this->content; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $url */ + $url = $options['url'] ?? null; + /** @var string|null $path */ + $path = $options['path'] ?? null; + /** @var string|null $content */ + $content = $options['content'] ?? null; + + return new self($url, $path, $content); + } +} diff --git a/src/Page/Options/TypeOptions.php b/src/Page/Options/TypeOptions.php new file mode 100644 index 0000000..c564d3c --- /dev/null +++ b/src/Page/Options/TypeOptions.php @@ -0,0 +1,69 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->delay) { + $options['delay'] = $this->delay; + } + if (null !== $this->noWaitAfter) { + $options['noWaitAfter'] = $this->noWaitAfter; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->strict) { + $options['strict'] = $this->strict; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $delay */ + $delay = $options['delay'] ?? null; + /** @var bool|null $noWaitAfter */ + $noWaitAfter = $options['noWaitAfter'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $strict */ + $strict = $options['strict'] ?? null; + + return new self($delay, $noWaitAfter, $timeout, $strict); + } +} diff --git a/src/Page/Options/WaitForLoadStateOptions.php b/src/Page/Options/WaitForLoadStateOptions.php new file mode 100644 index 0000000..7bad8a2 --- /dev/null +++ b/src/Page/Options/WaitForLoadStateOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($timeout); + } +} diff --git a/src/Page/Options/WaitForPopupOptions.php b/src/Page/Options/WaitForPopupOptions.php new file mode 100644 index 0000000..8d09b20 --- /dev/null +++ b/src/Page/Options/WaitForPopupOptions.php @@ -0,0 +1,60 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->predicate) { + $options['predicate'] = $this->predicate; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var callable|null $predicate */ + $predicate = $options['predicate'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($predicate, $timeout); + } +} diff --git a/src/Page/Options/WaitForResponseOptions.php b/src/Page/Options/WaitForResponseOptions.php new file mode 100644 index 0000000..2299d9e --- /dev/null +++ b/src/Page/Options/WaitForResponseOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($timeout); + } +} diff --git a/src/Page/Options/WaitForSelectorOptions.php b/src/Page/Options/WaitForSelectorOptions.php new file mode 100644 index 0000000..feee042 --- /dev/null +++ b/src/Page/Options/WaitForSelectorOptions.php @@ -0,0 +1,66 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->state) { + $options['state'] = $this->state; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->strict) { + $options['strict'] = $this->strict; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var 'attached'|'detached'|'visible'|'hidden'|null $state */ + $state = $options['state'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var bool|null $strict */ + $strict = $options['strict'] ?? null; + + return new self($state, $timeout, $strict); + } +} diff --git a/src/Page/Options/WaitForUrlOptions.php b/src/Page/Options/WaitForUrlOptions.php new file mode 100644 index 0000000..1fee288 --- /dev/null +++ b/src/Page/Options/WaitForUrlOptions.php @@ -0,0 +1,60 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + if (null !== $this->waitUntil) { + $options['waitUntil'] = $this->waitUntil; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + /** @var 'load'|'domcontentloaded'|'networkidle'|'commit'|null $waitUntil */ + $waitUntil = $options['waitUntil'] ?? null; + + return new self($timeout, $waitUntil); + } +} diff --git a/src/Page/Page.php b/src/Page/Page.php index 2d357a2..ca0c4a3 100644 --- a/src/Page/Page.php +++ b/src/Page/Page.php @@ -38,12 +38,28 @@ use Playwright\Input\MouseInterface; use Playwright\Locator\Locator; use Playwright\Locator\LocatorInterface; -use Playwright\Locator\RoleSelectorBuilder; +use Playwright\Locator\Options\GetByRoleOptions; +use Playwright\Locator\Options\LocatorOptions; use Playwright\Network\Request; use Playwright\Network\Response; use Playwright\Network\ResponseInterface; use Playwright\Network\Route; +use Playwright\Page\Options\ClickOptions; +use Playwright\Page\Options\FrameQueryOptions; +use Playwright\Page\Options\GotoOptions; +use Playwright\Page\Options\NavigationHistoryOptions; use Playwright\Page\Options\PdfOptions; +use Playwright\Page\Options\ScreenshotOptions; +use Playwright\Page\Options\ScriptTagOptions; +use Playwright\Page\Options\SetContentOptions; +use Playwright\Page\Options\SetInputFilesOptions; +use Playwright\Page\Options\StyleTagOptions; +use Playwright\Page\Options\TypeOptions; +use Playwright\Page\Options\WaitForLoadStateOptions; +use Playwright\Page\Options\WaitForPopupOptions; +use Playwright\Page\Options\WaitForResponseOptions; +use Playwright\Page\Options\WaitForSelectorOptions; +use Playwright\Page\Options\WaitForUrlOptions; use Playwright\Screenshot\ScreenshotHelper; use Playwright\Transport\TransportInterface; use Psr\Log\LoggerInterface; @@ -180,77 +196,83 @@ public function events(): PageEventHandlerInterface } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function locator(string $selector, array $options = []): LocatorInterface - { - return new Locator($this->transport, $this->pageId, $selector, null, null, $options); + public function locator(string $selector, array|LocatorOptions $options = []): LocatorInterface + { + return new Locator( + $this->transport, + $this->pageId, + $selector, + null, + null, + $this->normalizeLocatorOptions($options) + ); } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByAltText(string $text, array $options = []): LocatorInterface + public function getByAltText(string $text, array|LocatorOptions $options = []): LocatorInterface { - return $this->locator(\sprintf('[alt="%s"]', $text), $options); + return $this->locator(\sprintf('[alt="%s"]', $text), $this->normalizeLocatorOptions($options)); } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByLabel(string $text, array $options = []): LocatorInterface + public function getByLabel(string $text, array|LocatorOptions $options = []): LocatorInterface { - return $this->locator(\sprintf('label:text-is("%s") >> nth=0', $text), $options); + return $this->locator(\sprintf('label:text-is("%s") >> nth=0', $text), $this->normalizeLocatorOptions($options)); } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByPlaceholder(string $text, array $options = []): LocatorInterface + public function getByPlaceholder(string $text, array|LocatorOptions $options = []): LocatorInterface { - return $this->locator(\sprintf('[placeholder="%s"]', $text), $options); + return $this->locator(\sprintf('[placeholder="%s"]', $text), $this->normalizeLocatorOptions($options)); } /** - * @param array $options + * @param array|GetByRoleOptions $options */ - public function getByRole(string $role, array $options = []): LocatorInterface + public function getByRole(string $role, array|GetByRoleOptions $options = []): LocatorInterface { - $selector = RoleSelectorBuilder::buildSelector($role, $options); - $locatorOptions = RoleSelectorBuilder::filterLocatorOptions($options); - - return $this->locator($selector, $locatorOptions); + return $this->locator($role, $this->normalizeGetByRoleOptions($options)); } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByTestId(string $testId, array $options = []): LocatorInterface + public function getByTestId(string $testId, array|LocatorOptions $options = []): LocatorInterface { return $this->locator(\sprintf('[data-testid="%s"]', $testId), $options); } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByText(string $text, array $options = []): LocatorInterface + public function getByText(string $text, array|LocatorOptions $options = []): LocatorInterface { - return $this->locator(\sprintf('text="%s"', $text), $options); + return $this->locator(\sprintf('text="%s"', $text), $this->normalizeLocatorOptions($options)); } /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByTitle(string $text, array $options = []): LocatorInterface + public function getByTitle(string $text, array|LocatorOptions $options = []): LocatorInterface { - return $this->locator(\sprintf('[title="%s"]', $text), $options); + return $this->locator(\sprintf('[title="%s"]', $text), $this->normalizeLocatorOptions($options)); } /** - * @param array $options + * @param array|GotoOptions $options */ - public function goto(string $url, array $options = []): ?ResponseInterface + public function goto(string $url, array|GotoOptions $options = []): ?ResponseInterface { + $options = GotoOptions::from($options)->toArray(); + $this->logger->debug('Navigating to URL', ['url' => $url, 'options' => $options]); try { @@ -273,13 +295,15 @@ public function goto(string $url, array $options = []): ?ResponseInterface /** * Take a screenshot of the page. * - * @param string|null $path Screenshot path. If null, auto-generates based on current URL and datetime - * @param array $options Screenshot options (quality, fullPage, etc.) + * @param string|null $path Screenshot path. If null, auto-generates based on current URL and datetime + * @param array|ScreenshotOptions $options Screenshot options (quality, fullPage, etc.) * * @return string Returns the screenshot file path */ - public function screenshot(?string $path = null, array $options = []): string + public function screenshot(?string $path = null, array|ScreenshotOptions $options = []): string { + $options = ScreenshotOptions::from($options)->toArray(); + $finalPath = $path ?? $options['path'] ?? ScreenshotHelper::generateFilename( $this->url(), $this->getScreenshotDirectory() @@ -404,7 +428,7 @@ public function pdfContent(array|PdfOptions $options = []): string return $content; } finally { - if (is_string($tempPath) && file_exists($tempPath)) { + if (file_exists($tempPath)) { @unlink($tempPath); } } @@ -465,10 +489,11 @@ public function evaluate(string $expression, mixed $arg = null): mixed } /** - * @param array $options + * @param array|WaitForSelectorOptions $options */ - public function waitForSelector(string $selector, array $options = []): LocatorInterface + public function waitForSelector(string $selector, array|WaitForSelectorOptions $options = []): LocatorInterface { + $options = WaitForSelectorOptions::from($options)->toArray(); $this->sendCommand('waitForSelector', ['selector' => $selector, 'options' => $options]); return $this->locator($selector); @@ -487,10 +512,11 @@ public function isClosed(): bool } /** - * @param array $options + * @param array|ClickOptions $options */ - public function click(string $selector, array $options = []): self + public function click(string $selector, array|ClickOptions $options = []): self { + $options = ClickOptions::from($options)->toArray(); $this->locator($selector)->click($options); $this->transport->processEvents(); @@ -499,34 +525,44 @@ public function click(string $selector, array $options = []): self } /** - * @param array $options + * @param array|ClickOptions $options */ - public function altClick(string $selector, array $options = []): self + public function altClick(string $selector, array|ClickOptions $options = []): self { - return $this->click($selector, [...$options, 'modifiers' => ModifierKey::Alt]); + $options = ClickOptions::from($options)->toArray(); + $options['modifiers'] = [...((array) ($options['modifiers'] ?? [])), ModifierKey::Alt->value]; + + return $this->click($selector, $options); } /** - * @param array $options + * @param array|ClickOptions $options */ - public function controlClick(string $selector, array $options = []): self + public function controlClick(string $selector, array|ClickOptions $options = []): self { - return $this->click($selector, [...$options, 'modifiers' => ModifierKey::Control]); + $options = ClickOptions::from($options)->toArray(); + $options['modifiers'] = [...((array) ($options['modifiers'] ?? [])), ModifierKey::Control->value]; + + return $this->click($selector, $options); } /** - * @param array $options + * @param array|ClickOptions $options */ - public function shiftClick(string $selector, array $options = []): self + public function shiftClick(string $selector, array|ClickOptions $options = []): self { - return $this->click($selector, [...$options, 'modifiers' => ModifierKey::Shift]); + $options = ClickOptions::from($options)->toArray(); + $options['modifiers'] = [...((array) ($options['modifiers'] ?? [])), ModifierKey::Shift->value]; + + return $this->click($selector, $options); } /** - * @param array $options + * @param array|TypeOptions $options */ - public function type(string $selector, string $text, array $options = []): self + public function type(string $selector, string $text, array|TypeOptions $options = []): self { + $options = TypeOptions::from($options)->toArray(); $this->locator($selector)->type($text, $options); return $this; @@ -542,6 +578,26 @@ public function pause(): self return $this; } + /** + * @param array|LocatorOptions $options + * + * @return array + */ + private function normalizeLocatorOptions(array|LocatorOptions $options): array + { + return LocatorOptions::from($options)->toArray(); + } + + /** + * @param array|GetByRoleOptions $options + * + * @return array + */ + private function normalizeGetByRoleOptions(array|GetByRoleOptions $options): array + { + return GetByRoleOptions::from($options)->toArray(); + } + /** * @param array $params * @@ -670,40 +726,44 @@ public function cookies(?array $urls = null): array } /** - * @param array $options + * @param array|NavigationHistoryOptions $options */ - public function goBack(array $options = []): self + public function goBack(array|NavigationHistoryOptions $options = []): self { + $options = NavigationHistoryOptions::from($options)->toArray(); $this->sendCommand('goBack', ['options' => $options]); return $this; } /** - * @param array $options + * @param array|NavigationHistoryOptions $options */ - public function goForward(array $options = []): self + public function goForward(array|NavigationHistoryOptions $options = []): self { + $options = NavigationHistoryOptions::from($options)->toArray(); $this->sendCommand('goForward', ['options' => $options]); return $this; } /** - * @param array $options + * @param array|NavigationHistoryOptions $options */ - public function reload(array $options = []): self + public function reload(array|NavigationHistoryOptions $options = []): self { + $options = NavigationHistoryOptions::from($options)->toArray(); $this->sendCommand('reload', ['options' => $options]); return $this; } /** - * @param array $options + * @param array|SetContentOptions $options */ - public function setContent(string $html, array $options = []): self + public function setContent(string $html, array|SetContentOptions $options = []): self { + $options = SetContentOptions::from($options)->toArray(); $this->sendCommand('setContent', ['html' => $html, 'options' => $options]); return $this; @@ -778,20 +838,22 @@ public function setDefaultTimeout(int $timeout): self } /** - * @param array $options + * @param array|WaitForLoadStateOptions $options */ - public function waitForLoadState(string $state = 'load', array $options = []): self + public function waitForLoadState(string $state = 'load', array|WaitForLoadStateOptions $options = []): self { + $options = WaitForLoadStateOptions::from($options)->toArray(); $this->sendCommand('waitForLoadState', ['state' => $state, 'options' => $options]); return $this; } /** - * @param array $options + * @param array|WaitForUrlOptions $options */ - public function waitForURL($url, array $options = []): self + public function waitForURL($url, array|WaitForUrlOptions $options = []): self { + $options = WaitForUrlOptions::from($options)->toArray(); $this->sendCommand('waitForURL', ['url' => $url, 'options' => $options]); $this->transport->processEvents(); @@ -800,28 +862,34 @@ public function waitForURL($url, array $options = []): self } /** - * @param string|callable $url - * @param array $options + * @param string|callable $url + * @param array|WaitForResponseOptions $options */ - public function waitForResponse($url, array $options = []): ResponseInterface + public function waitForResponse($url, array|WaitForResponseOptions $options = []): ResponseInterface { - $action = $options['action'] ?? null; - unset($options['action']); + $action = null; + if (is_array($options)) { + $action = $options['action'] ?? null; + unset($options['action']); + } - $response = $this->sendCommand('waitForResponse', ['url' => $url, 'options' => $options, 'jsAction' => $action]); + $options = WaitForResponseOptions::from($options); + $response = $this->sendCommand('waitForResponse', ['url' => $url, 'options' => $options->toArray(), 'jsAction' => $action]); return $this->createResponse($this->pageId, $response['response']); } - public function addScriptTag(array $options): self + public function addScriptTag(array|ScriptTagOptions $options): self { + $options = ScriptTagOptions::from($options)->toArray(); $this->sendCommand('addScriptTag', ['options' => $options]); return $this; } - public function addStyleTag(array $options): self + public function addStyleTag(array|StyleTagOptions $options): self { + $options = StyleTagOptions::from($options)->toArray(); $this->sendCommand('addStyleTag', ['options' => $options]); return $this; @@ -859,10 +927,11 @@ public function frames(): array } /** - * @param array{name?: string, url?: string, urlRegex?: string} $options + * @param array{name?: string, url?: string, urlRegex?: string}|FrameQueryOptions $options */ - public function frame(array $options): ?FrameInterface + public function frame(array|FrameQueryOptions $options): ?FrameInterface { + $options = FrameQueryOptions::from($options)->toArray(); $response = $this->sendCommand('frame', ['options' => $options]); $selector = $response['selector'] ?? null; if (\is_string($selector)) { @@ -908,10 +977,11 @@ public function waitForEvents(): void } /** - * @param array $options + * @param array|WaitForPopupOptions $options */ - public function waitForPopup(callable $action, array $options = []): self + public function waitForPopup(callable $action, array|WaitForPopupOptions $options = []): self { + $options = WaitForPopupOptions::from($options)->toArray(); $timeout = $options['timeout'] ?? 30000; $requestId = uniqid('popup_', true); @@ -937,10 +1007,12 @@ public function waitForPopup(callable $action, array $options = []): self } /** - * @param array $options + * @param array|SetInputFilesOptions $options */ - public function setInputFiles(string $selector, array $files, array $options = []): self + public function setInputFiles(string $selector, array $files, array|SetInputFilesOptions $options = []): self { + $options = SetInputFilesOptions::from($options)->toArray(); + $this->logger->debug('Setting input files', ['selector' => $selector, 'files' => $files]); foreach ($files as $file) { diff --git a/src/Page/PageInterface.php b/src/Page/PageInterface.php index 8c5292b..cd011a3 100644 --- a/src/Page/PageInterface.php +++ b/src/Page/PageInterface.php @@ -21,79 +21,102 @@ use Playwright\Input\KeyboardInterface; use Playwright\Input\MouseInterface; use Playwright\Locator\LocatorInterface; +use Playwright\Locator\Options\GetByRoleOptions; +use Playwright\Locator\Options\LocatorOptions; use Playwright\Network\ResponseInterface; +use Playwright\Page\Options\ClickOptions; +use Playwright\Page\Options\FrameQueryOptions; +use Playwright\Page\Options\GotoOptions; +use Playwright\Page\Options\NavigationHistoryOptions; use Playwright\Page\Options\PdfOptions; +use Playwright\Page\Options\ScreenshotOptions; +use Playwright\Page\Options\ScriptTagOptions; +use Playwright\Page\Options\SetContentOptions; +use Playwright\Page\Options\SetInputFilesOptions; +use Playwright\Page\Options\StyleTagOptions; +use Playwright\Page\Options\TypeOptions; +use Playwright\Page\Options\WaitForLoadStateOptions; +use Playwright\Page\Options\WaitForPopupOptions; +use Playwright\Page\Options\WaitForResponseOptions; +use Playwright\Page\Options\WaitForSelectorOptions; +use Playwright\Page\Options\WaitForUrlOptions; interface PageInterface { - public function locator(string $selector): LocatorInterface; + /** + * @param array|LocatorOptions $options + */ + public function locator(string $selector, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByAltText(string $text, array $options = []): LocatorInterface; + public function getByAltText(string $text, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByLabel(string $text, array $options = []): LocatorInterface; + public function getByLabel(string $text, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByPlaceholder(string $text, array $options = []): LocatorInterface; + public function getByPlaceholder(string $text, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|GetByRoleOptions $options */ - public function getByRole(string $role, array $options = []): LocatorInterface; + public function getByRole(string $role, array|GetByRoleOptions $options = []): LocatorInterface; - public function getByTestId(string $testId): LocatorInterface; + /** + * @param array|LocatorOptions $options + */ + public function getByTestId(string $testId, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByText(string $text, array $options = []): LocatorInterface; + public function getByText(string $text, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|LocatorOptions $options */ - public function getByTitle(string $text, array $options = []): LocatorInterface; + public function getByTitle(string $text, array|LocatorOptions $options = []): LocatorInterface; /** - * @param array $options + * @param array|GotoOptions $options */ - public function goto(string $url, array $options = []): ?ResponseInterface; + public function goto(string $url, array|GotoOptions $options = []): ?ResponseInterface; /** - * @param array $options + * @param array|ClickOptions $options */ - public function click(string $selector, array $options = []): self; + public function click(string $selector, array|ClickOptions $options = []): self; /** - * @param array $options + * @param array|ClickOptions $options */ - public function altClick(string $selector, array $options = []): self; + public function altClick(string $selector, array|ClickOptions $options = []): self; /** - * @param array $options + * @param array|ClickOptions $options */ - public function controlClick(string $selector, array $options = []): self; + public function controlClick(string $selector, array|ClickOptions $options = []): self; /** - * @param array $options + * @param array|ClickOptions $options */ - public function shiftClick(string $selector, array $options = []): self; + public function shiftClick(string $selector, array|ClickOptions $options = []): self; /** - * @param array $options + * @param array|TypeOptions $options */ - public function type(string $selector, string $text, array $options = []): self; + public function type(string $selector, string $text, array|TypeOptions $options = []): self; /** - * @param array $options + * @param array|ScreenshotOptions $options */ - public function screenshot(?string $path = null, array $options = []): string; + public function screenshot(?string $path = null, array|ScreenshotOptions $options = []): string; /** * @param array|PdfOptions $options @@ -110,9 +133,9 @@ public function content(): ?string; public function evaluate(string $expression, mixed $arg = null): mixed; /** - * @param array $options + * @param array|WaitForSelectorOptions $options */ - public function waitForSelector(string $selector, array $options = []): ?LocatorInterface; + public function waitForSelector(string $selector, array|WaitForSelectorOptions $options = []): ?LocatorInterface; public function close(): void; @@ -131,24 +154,24 @@ public function context(): BrowserContextInterface; public function cookies(?array $urls = null): array; /** - * @param array $options + * @param array|NavigationHistoryOptions $options */ - public function goBack(array $options = []): self; + public function goBack(array|NavigationHistoryOptions $options = []): self; /** - * @param array $options + * @param array|NavigationHistoryOptions $options */ - public function goForward(array $options = []): self; + public function goForward(array|NavigationHistoryOptions $options = []): self; /** - * @param array $options + * @param array|NavigationHistoryOptions $options */ - public function reload(array $options = []): self; + public function reload(array|NavigationHistoryOptions $options = []): self; /** - * @param array $options + * @param array|SetContentOptions $options */ - public function setContent(string $html, array $options = []): self; + public function setContent(string $html, array|SetContentOptions $options = []): self; public function url(): string; @@ -166,25 +189,25 @@ public function setDefaultNavigationTimeout(int $timeout): self; public function setDefaultTimeout(int $timeout): self; /** - * @param array $options + * @param array|WaitForLoadStateOptions $options */ - public function waitForLoadState(string $state = 'load', array $options = []): self; + public function waitForLoadState(string $state = 'load', array|WaitForLoadStateOptions $options = []): self; /** - * @param string|callable $url - * @param array $options + * @param string|callable $url + * @param array|WaitForUrlOptions $options */ - public function waitForURL($url, array $options = []): self; + public function waitForURL($url, array|WaitForUrlOptions $options = []): self; /** - * @param array{url?: string, path?: string, content?: string, type?: string} $options + * @param array{url?: string, path?: string, content?: string, type?: string}|ScriptTagOptions $options */ - public function addScriptTag(array $options): self; + public function addScriptTag(array|ScriptTagOptions $options): self; /** - * @param array{url?: string, path?: string, content?: string} $options + * @param array{url?: string, path?: string, content?: string}|StyleTagOptions $options */ - public function addStyleTag(array $options): self; + public function addStyleTag(array|StyleTagOptions $options): self; public function frameLocator(string $selector): FrameLocatorInterface; @@ -205,18 +228,24 @@ public function getPageIdForTransport(): string; public function waitForEvents(): void; /** - * @param array $options + * @param array|WaitForPopupOptions $options + */ + public function waitForPopup(callable $action, array|WaitForPopupOptions $options = []): self; + + /** + * @param string|callable $url + * @param array|WaitForResponseOptions $options */ - public function waitForPopup(callable $action, array $options = []): self; + public function waitForResponse($url, array|WaitForResponseOptions $options = []): ResponseInterface; /** * Set files to an input element with type="file". * - * @param string $selector The input selector - * @param array $files Array of file paths to set - * @param array $options Additional options + * @param string $selector The input selector + * @param array $files Array of file paths to set + * @param array|SetInputFilesOptions $options Additional options */ - public function setInputFiles(string $selector, array $files, array $options = []): self; + public function setInputFiles(string $selector, array $files, array|SetInputFilesOptions $options = []): self; /** * Get a handle to the main frame. @@ -233,9 +262,9 @@ public function frames(): array; /** * Find a top-level frame by name or URL. * - * @param array{name?: string, url?: string, urlRegex?: string} $options + * @param array{name?: string, url?: string, urlRegex?: string}|FrameQueryOptions $options */ - public function frame(array $options): ?FrameInterface; + public function frame(array|FrameQueryOptions $options): ?FrameInterface; /** * API testing helper associated with this page. diff --git a/src/Selector/Options/RegisterOptions.php b/src/Selector/Options/RegisterOptions.php new file mode 100644 index 0000000..1033bf6 --- /dev/null +++ b/src/Selector/Options/RegisterOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->contentScript) { + $options['contentScript'] = $this->contentScript; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var bool|null $contentScript */ + $contentScript = $options['contentScript'] ?? null; + + return new self($contentScript); + } +} diff --git a/src/Selector/Selectors.php b/src/Selector/Selectors.php index ce8aeb4..bfc5972 100644 --- a/src/Selector/Selectors.php +++ b/src/Selector/Selectors.php @@ -14,6 +14,7 @@ namespace Playwright\Selector; +use Playwright\Selector\Options\RegisterOptions; use Playwright\Transport\TransportInterface; /** @@ -29,15 +30,16 @@ public function __construct( } /** - * @param array $options + * @param array|RegisterOptions $options */ - public function register(string $name, string $script, array $options = []): void + public function register(string $name, string $script, array|RegisterOptions $options = []): void { + $options = RegisterOptions::from($options); $this->transport->send([ 'action' => 'selectors.register', 'name' => $name, 'script' => $script, - 'options' => $options, + 'options' => $options->toArray(), ]); } diff --git a/src/Selector/SelectorsInterface.php b/src/Selector/SelectorsInterface.php index 31ddfb8..952825c 100644 --- a/src/Selector/SelectorsInterface.php +++ b/src/Selector/SelectorsInterface.php @@ -14,6 +14,8 @@ namespace Playwright\Selector; +use Playwright\Selector\Options\RegisterOptions; + /** * @author Simon André */ @@ -22,9 +24,9 @@ interface SelectorsInterface /** * Register a custom selector engine. * - * @param array $options + * @param array|RegisterOptions $options */ - public function register(string $name, string $script, array $options = []): void; + public function register(string $name, string $script, array|RegisterOptions $options = []): void; /** * Set the test id attribute name. diff --git a/src/Tracing/Options/StartChunkOptions.php b/src/Tracing/Options/StartChunkOptions.php new file mode 100644 index 0000000..c3f31e5 --- /dev/null +++ b/src/Tracing/Options/StartChunkOptions.php @@ -0,0 +1,57 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->name) { + $options['name'] = $this->name; + } + if (null !== $this->title) { + $options['title'] = $this->title; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $name */ + $name = $options['name'] ?? null; + /** @var string|null $title */ + $title = $options['title'] ?? null; + + return new self($name, $title); + } +} diff --git a/src/Tracing/Options/StartOptions.php b/src/Tracing/Options/StartOptions.php new file mode 100644 index 0000000..3d5c3e2 --- /dev/null +++ b/src/Tracing/Options/StartOptions.php @@ -0,0 +1,75 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->name) { + $options['name'] = $this->name; + } + if (null !== $this->screenshots) { + $options['screenshots'] = $this->screenshots; + } + if (null !== $this->snapshots) { + $options['snapshots'] = $this->snapshots; + } + if (null !== $this->sources) { + $options['sources'] = $this->sources; + } + if (null !== $this->title) { + $options['title'] = $this->title; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $name */ + $name = $options['name'] ?? null; + /** @var bool|null $screenshots */ + $screenshots = $options['screenshots'] ?? null; + /** @var bool|null $snapshots */ + $snapshots = $options['snapshots'] ?? null; + /** @var bool|null $sources */ + $sources = $options['sources'] ?? null; + /** @var string|null $title */ + $title = $options['title'] ?? null; + + return new self($name, $screenshots, $snapshots, $sources, $title); + } +} diff --git a/src/Tracing/Options/StopChunkOptions.php b/src/Tracing/Options/StopChunkOptions.php new file mode 100644 index 0000000..87fdbb9 --- /dev/null +++ b/src/Tracing/Options/StopChunkOptions.php @@ -0,0 +1,54 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->path) { + $options['path'] = $this->path; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $path */ + $path = $options['path'] ?? null; + + return new self($path); + } +} diff --git a/src/Tracing/Options/StopOptions.php b/src/Tracing/Options/StopOptions.php new file mode 100644 index 0000000..d1f44bd --- /dev/null +++ b/src/Tracing/Options/StopOptions.php @@ -0,0 +1,51 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->path) { + $options['path'] = $this->path; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var string|null $path */ + $path = $options['path'] ?? null; + + return new self($path); + } +} diff --git a/src/Tracing/Tracing.php b/src/Tracing/Tracing.php index a8e3ffb..e504f07 100644 --- a/src/Tracing/Tracing.php +++ b/src/Tracing/Tracing.php @@ -14,6 +14,10 @@ namespace Playwright\Tracing; +use Playwright\Tracing\Options\StartChunkOptions; +use Playwright\Tracing\Options\StartOptions; +use Playwright\Tracing\Options\StopChunkOptions; +use Playwright\Tracing\Options\StopOptions; use Playwright\Transport\TransportInterface; final class Tracing implements TracingInterface @@ -24,39 +28,43 @@ public function __construct( ) { } - public function start(array $options = []): void + public function start(array|StartOptions $options = []): void { + $options = StartOptions::from($options); $this->transport->send([ 'action' => 'tracingStart', 'contextId' => $this->contextId, - 'options' => $options, + 'options' => $options->toArray(), ]); } - public function startChunk(array $options = []): void + public function startChunk(array|StartChunkOptions $options = []): void { + $options = StartChunkOptions::from($options); $this->transport->send([ 'action' => 'tracingStartChunk', 'contextId' => $this->contextId, - 'options' => $options, + 'options' => $options->toArray(), ]); } - public function stop(array $options = []): void + public function stop(array|StopOptions $options = []): void { + $options = StopOptions::from($options); $this->transport->send([ 'action' => 'tracingStop', 'contextId' => $this->contextId, - 'options' => $options, + 'options' => $options->toArray(), ]); } - public function stopChunk(array $options = []): void + public function stopChunk(array|StopChunkOptions $options = []): void { + $options = StopChunkOptions::from($options); $this->transport->send([ 'action' => 'tracingStopChunk', 'contextId' => $this->contextId, - 'options' => $options, + 'options' => $options->toArray(), ]); } diff --git a/src/Tracing/TracingInterface.php b/src/Tracing/TracingInterface.php index 5e263d9..3747b44 100644 --- a/src/Tracing/TracingInterface.php +++ b/src/Tracing/TracingInterface.php @@ -14,35 +14,40 @@ namespace Playwright\Tracing; +use Playwright\Tracing\Options\StartChunkOptions; +use Playwright\Tracing\Options\StartOptions; +use Playwright\Tracing\Options\StopChunkOptions; +use Playwright\Tracing\Options\StopOptions; + interface TracingInterface { /** * Start tracing. * - * @param array{name?: string, screenshots?: bool, snapshots?: bool, sources?: bool, title?: string} $options + * @param array|StartOptions $options */ - public function start(array $options = []): void; + public function start(array|StartOptions $options = []): void; /** * Start a new trace chunk. If tracing was already started, this creates a new trace chunk. * - * @param array{name?: string, title?: string} $options + * @param array|StartChunkOptions $options */ - public function startChunk(array $options = []): void; + public function startChunk(array|StartChunkOptions $options = []): void; /** * Stop tracing. * - * @param array{path?: string} $options + * @param array|StopOptions $options */ - public function stop(array $options = []): void; + public function stop(array|StopOptions $options = []): void; /** * Stop the trace chunk. See startChunk() for more details. * - * @param array{path?: string} $options + * @param array|StopChunkOptions $options */ - public function stopChunk(array $options = []): void; + public function stopChunk(array|StopChunkOptions $options = []): void; /** * @deprecated Use test.step() instead diff --git a/src/WebSocket/Options/CloseOptions.php b/src/WebSocket/Options/CloseOptions.php new file mode 100644 index 0000000..0288744 --- /dev/null +++ b/src/WebSocket/Options/CloseOptions.php @@ -0,0 +1,57 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->code) { + $options['code'] = $this->code; + } + if (null !== $this->reason) { + $options['reason'] = $this->reason; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var int|null $code */ + $code = $options['code'] ?? null; + /** @var string|null $reason */ + $reason = $options['reason'] ?? null; + + return new self($code, $reason); + } +} diff --git a/src/WebSocket/Options/WaitForEventOptions.php b/src/WebSocket/Options/WaitForEventOptions.php new file mode 100644 index 0000000..66681a7 --- /dev/null +++ b/src/WebSocket/Options/WaitForEventOptions.php @@ -0,0 +1,60 @@ + + */ + public function toArray(): array + { + $options = []; + if (null !== $this->predicate) { + $options['predicate'] = $this->predicate; + } + if (null !== $this->timeout) { + $options['timeout'] = $this->timeout; + } + + return $options; + } + + /** + * @param array|self $options + */ + public static function from(array|self $options = []): self + { + if ($options instanceof self) { + return $options; + } + + /** @var callable|null $predicate */ + $predicate = $options['predicate'] ?? null; + /** @var float|null $timeout */ + $timeout = $options['timeout'] ?? null; + + return new self($predicate, $timeout); + } +} diff --git a/src/WebSocket/WebSocket.php b/src/WebSocket/WebSocket.php index 55dfbf5..6964df9 100644 --- a/src/WebSocket/WebSocket.php +++ b/src/WebSocket/WebSocket.php @@ -18,6 +18,7 @@ use Playwright\Event\EventEmitter; use Playwright\Exception\TimeoutException; use Playwright\Transport\TransportInterface; +use Playwright\WebSocket\Options\WaitForEventOptions; /** * WebSocket implementation aligned with Playwright's WebSocket API. @@ -84,14 +85,15 @@ public function dispatchEvent(string $eventName, array $params): void * Wait locally for an event, optionally filtered by predicate, with timeout. * Falls back to pumping transport events via processEvents. * - * @param array{predicate?: callable, timeout?: int} $options + * @param array|WaitForEventOptions $options * * @return array */ - public function waitForEvent(string $event, array $options = []): array + public function waitForEvent(string $event, array|WaitForEventOptions $options = []): array { - $timeout = isset($options['timeout']) && \is_int($options['timeout']) ? $options['timeout'] : 30000; - $predicate = isset($options['predicate']) && \is_callable($options['predicate']) ? $options['predicate'] : null; + $options = WaitForEventOptions::from($options); + $timeout = isset($options->timeout) ? (int) $options->timeout : 30000; + $predicate = isset($options->predicate) && \is_callable($options->predicate) ? $options->predicate : null; $resolved = false; /** @var array $result */ diff --git a/src/WebSocket/WebSocketInterface.php b/src/WebSocket/WebSocketInterface.php index 3ec849d..30740e5 100644 --- a/src/WebSocket/WebSocketInterface.php +++ b/src/WebSocket/WebSocketInterface.php @@ -14,6 +14,8 @@ namespace Playwright\WebSocket; +use Playwright\WebSocket\Options\WaitForEventOptions; + interface WebSocketInterface { /** @@ -45,9 +47,9 @@ public function removeListener(string $event, callable $listener): void; /** * Waits for event to fire and passes its value into the predicate function. * - * @param array{predicate?: callable, timeout?: int} $options + * @param array|WaitForEventOptions $options * * @return array */ - public function waitForEvent(string $event, array $options = []): array; + public function waitForEvent(string $event, array|WaitForEventOptions $options = []): array; } diff --git a/src/WebSocket/WebSocketRoute.php b/src/WebSocket/WebSocketRoute.php index 3512dba..4b0dea1 100644 --- a/src/WebSocket/WebSocketRoute.php +++ b/src/WebSocket/WebSocketRoute.php @@ -16,6 +16,7 @@ use Playwright\Event\EventDispatcherInterface; use Playwright\Transport\TransportInterface; +use Playwright\WebSocket\Options\CloseOptions; final class WebSocketRoute implements WebSocketRouteInterface, EventDispatcherInterface { @@ -45,12 +46,13 @@ public function url(): string } /** - * @param array{code?: int, reason?: string} $options + * @param array|CloseOptions $options */ - public function close(array $options = []): void + public function close(array|CloseOptions $options = []): void { - $code = isset($options['code']) && \is_int($options['code']) ? $options['code'] : null; - $reason = isset($options['reason']) && \is_string($options['reason']) ? $options['reason'] : null; + $options = CloseOptions::from($options); + $code = $options->code; + $reason = $options->reason; $this->transport->sendAsync([ 'action' => 'websocketRoute.close', diff --git a/src/WebSocket/WebSocketRouteInterface.php b/src/WebSocket/WebSocketRouteInterface.php index 8b7129c..ceb4919 100644 --- a/src/WebSocket/WebSocketRouteInterface.php +++ b/src/WebSocket/WebSocketRouteInterface.php @@ -14,14 +14,16 @@ namespace Playwright\WebSocket; +use Playwright\WebSocket\Options\CloseOptions; + interface WebSocketRouteInterface { /** * Closes one side of the WebSocket connection. * - * @param array{code?: int, reason?: string} $options + * @param array|CloseOptions $options */ - public function close(array $options = []): void; + public function close(array|CloseOptions $options = []): void; /** * Connects to the actual WebSocket server. diff --git a/tests/Integration/Input/FileUploadIntegrationTest.php b/tests/Integration/Input/FileUploadIntegrationTest.php index b15eeaf..a9e1e18 100644 --- a/tests/Integration/Input/FileUploadIntegrationTest.php +++ b/tests/Integration/Input/FileUploadIntegrationTest.php @@ -82,4 +82,17 @@ public function itUploadsAFileWithLocator(): void $name = $this->page->evaluate('() => document.body.dataset.filename'); $this->assertSame(basename($this->tempFile), $name); } + + #[Test] + public function itUploadsAFileWithLocatorAndOptionsObject(): void + { + $this->page->locator('#file')->setInputFiles( + [$this->tempFile], + new \Playwright\Locator\Options\SetInputFilesOptions(noWaitAfter: true) + ); + usleep(100000); + + $name = $this->page->evaluate('() => document.body.dataset.filename'); + $this->assertSame(basename($this->tempFile), $name); + } } diff --git a/tests/Integration/Locator/LocatorFilterTest.php b/tests/Integration/Locator/LocatorFilterTest.php index c98ff38..755158e 100644 --- a/tests/Integration/Locator/LocatorFilterTest.php +++ b/tests/Integration/Locator/LocatorFilterTest.php @@ -68,4 +68,23 @@ public function itGetsContentFrame(): void $heading = $frameLocator->locator('h1'); $this->assertSame('Frame Content', $heading->textContent()); } + + #[Test] + public function itFiltersLocatorsWithOptionsObject(): void + { + $items = $this->page->locator('.item'); + + // Debug: check if manual selector works + $manual = $this->page->locator('.item >> :has-text("First")'); + // echo "Manual count: " . $manual->count() . "\n"; + + // Filter by text + $first = $items->filter(new \Playwright\Locator\Options\FilterOptions(hasText: 'First')); + $this->assertSame(1, $first->count(), 'Filter by hasText failed'); + $this->assertSame('First item', $first->textContent()); + + // Filter by not text + $notFirst = $items->filter(new \Playwright\Locator\Options\FilterOptions(hasNotText: 'First')); + $this->assertSame(2, $notFirst->count(), 'Filter by hasNotText failed'); + } } diff --git a/tests/Integration/Locator/LocatorTest.php b/tests/Integration/Locator/LocatorTest.php index eb2bbab..f5f23df 100644 --- a/tests/Integration/Locator/LocatorTest.php +++ b/tests/Integration/Locator/LocatorTest.php @@ -352,6 +352,40 @@ public function itUsesGetByTitleChaining(): void $this->assertSame('Home', $locator->textContent()); } + #[Test] + public function testClickWithOptionsObject(): void + { + $this->page->setContent(''); + $locator = $this->page->locator('#btn'); + + $locator->click(new \Playwright\Locator\Options\ClickOptions(clickCount: 2)); + + $clicked = $this->page->evaluate('window.clicked'); + $this->assertEquals(2, $clicked); + } + + #[Test] + public function testTypeWithOptionsObject(): void + { + $this->page->setContent(''); + $locator = $this->page->locator('#input'); + + $locator->type('hello', new \Playwright\Locator\Options\TypeOptions(delay: 10.0)); + + $this->assertEquals('hello', $locator->inputValue()); + } + + #[Test] + public function testUncheckWithOptionsObject(): void + { + $this->page->setContent(''); + $locator = $this->page->locator('#checkbox'); + + $locator->uncheck(new \Playwright\Locator\Options\UncheckOptions(force: true)); + + $this->assertFalse($locator->isChecked()); + } + private static function findFreePort(): int { return 0; diff --git a/tests/Unit/FileChooser/FileChooserTest.php b/tests/Unit/FileChooser/FileChooserTest.php index ccee0c2..cef2576 100644 --- a/tests/Unit/FileChooser/FileChooserTest.php +++ b/tests/Unit/FileChooser/FileChooserTest.php @@ -99,7 +99,7 @@ public function testSetFilesWithOptions(): void ->with($this->callback(function ($payload) { return 'fileChooser.setFiles' === $payload['action'] && isset($payload['options']) - && 1000 === $payload['options']['timeout']; + && 1000.0 === $payload['options']['timeout']; })); $page = $this->createMock(PageInterface::class); diff --git a/tests/Unit/FileChooser/Options/SetFilesOptionsTest.php b/tests/Unit/FileChooser/Options/SetFilesOptionsTest.php new file mode 100644 index 0000000..e46bc90 --- /dev/null +++ b/tests/Unit/FileChooser/Options/SetFilesOptionsTest.php @@ -0,0 +1,48 @@ +assertTrue($options->noWaitAfter); + $this->assertSame(1000.0, $options->timeout); + $this->assertEquals(['noWaitAfter' => true, 'timeout' => 1000.0], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = SetFilesOptions::from(['noWaitAfter' => true, 'timeout' => 1000.0]); + + $this->assertTrue($options->noWaitAfter); + $this->assertSame(1000.0, $options->timeout); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new SetFilesOptions(timeout: 1000.0); + $options = SetFilesOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/LocatorFilterTest.php b/tests/Unit/Locator/LocatorFilterTest.php index 5770473..b53ce4c 100644 --- a/tests/Unit/Locator/LocatorFilterTest.php +++ b/tests/Unit/Locator/LocatorFilterTest.php @@ -36,7 +36,7 @@ public function testFilterWithHasText(): void $filtered = $this->locator->filter(['hasText' => 'foo']); $this->assertInstanceOf(Locator::class, $filtered); - $this->assertSame('Locator(selector=".items >> :has-text("foo")")', (string) $filtered); + $this->assertSame('Locator(selector=".items:has-text("foo")")', (string) $filtered); } public function testFilterWithHas(): void @@ -45,7 +45,24 @@ public function testFilterWithHas(): void $filtered = $this->locator->filter(['has' => $inner]); $this->assertInstanceOf(Locator::class, $filtered); - $this->assertSame('Locator(selector=".items >> :has(.inner)")', (string) $filtered); + $this->assertSame('Locator(selector=".items:has(.inner)")', (string) $filtered); + } + + public function testFilterWithHasNotText(): void + { + $filtered = $this->locator->filter(['hasNotText' => 'bar']); + + $this->assertInstanceOf(Locator::class, $filtered); + $this->assertSame('Locator(selector=".items:not(:has-text("bar"))")', (string) $filtered); + } + + public function testFilterWithHasNot(): void + { + $inner = new Locator($this->transport, 'page1', '.inner'); + $filtered = $this->locator->filter(['hasNot' => $inner]); + + $this->assertInstanceOf(Locator::class, $filtered); + $this->assertSame('Locator(selector=".items:not(:has(.inner))")', (string) $filtered); } public function testAnd(): void @@ -101,11 +118,4 @@ public function testFilterWithBothOptions(): void $this->assertStringContainsString(':has-text("foo")', (string) $filtered); $this->assertStringContainsString(':has(.inner)', (string) $filtered); } - - public function testFilterWithNonLocatorHas(): void - { - $filtered = $this->locator->filter(['has' => 'not-a-locator']); - - $this->assertInstanceOf(Locator::class, $filtered); - } } diff --git a/tests/Unit/Locator/Options/CheckOptionsTest.php b/tests/Unit/Locator/Options/CheckOptionsTest.php new file mode 100644 index 0000000..33bc19d --- /dev/null +++ b/tests/Unit/Locator/Options/CheckOptionsTest.php @@ -0,0 +1,65 @@ + ['x' => 5.0, 'y' => 10.0], + 'force' => true, + 'noWaitAfter' => false, + 'timeout' => 4000.0, + 'trial' => true, + ]); + + $result = $options->toArray(); + + $this->assertSame(['x' => 5.0, 'y' => 10.0], $result['position']); + $this->assertTrue($result['force']); + $this->assertFalse($result['noWaitAfter']); + $this->assertSame(4000.0, $result['timeout']); + $this->assertTrue($result['trial']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = CheckOptions::from([ + 'force' => true, + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('force', $result); + $this->assertArrayNotHasKey('position', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('timeout', $result); + $this->assertArrayNotHasKey('trial', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new CheckOptions(force: true); + $result = CheckOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/ClearOptionsTest.php b/tests/Unit/Locator/Options/ClearOptionsTest.php new file mode 100644 index 0000000..5a1fa3c --- /dev/null +++ b/tests/Unit/Locator/Options/ClearOptionsTest.php @@ -0,0 +1,59 @@ + false, + 'noWaitAfter' => true, + 'timeout' => 6500.0, + ]); + + $result = $options->toArray(); + + $this->assertFalse($result['force']); + $this->assertTrue($result['noWaitAfter']); + $this->assertSame(6500.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = ClearOptions::from([ + 'noWaitAfter' => true, + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('force', $result); + $this->assertArrayNotHasKey('timeout', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new ClearOptions(force: false); + $result = ClearOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/ClickOptionsTest.php b/tests/Unit/Locator/Options/ClickOptionsTest.php new file mode 100644 index 0000000..f5f3f65 --- /dev/null +++ b/tests/Unit/Locator/Options/ClickOptionsTest.php @@ -0,0 +1,94 @@ + 10, 'y' => 20], + modifiers: [ModifierKey::Alt], + force: true, + noWaitAfter: true, + timeout: 5000.0, + trial: true, + ); + + $this->assertEquals('right', $options->button); + $this->assertEquals(2, $options->clickCount); + $this->assertEquals(100.0, $options->delay); + $this->assertEquals(['x' => 10, 'y' => 20], $options->position); + $this->assertEquals([ModifierKey::Alt], $options->modifiers); + $this->assertTrue($options->force); + $this->assertTrue($options->noWaitAfter); + $this->assertEquals(5000.0, $options->timeout); + $this->assertTrue($options->trial); + + $this->assertEquals([ + 'button' => 'right', + 'clickCount' => 2, + 'delay' => 100.0, + 'position' => ['x' => 10, 'y' => 20], + 'modifiers' => [ModifierKey::Alt], + 'force' => true, + 'noWaitAfter' => true, + 'timeout' => 5000.0, + 'trial' => true, + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = ClickOptions::from([ + 'button' => 'middle', + 'clickCount' => 3, + 'delay' => 200.0, + 'position' => ['x' => 30, 'y' => 40], + 'modifiers' => [ModifierKey::Control], + 'force' => false, + 'noWaitAfter' => false, + 'timeout' => 1000.0, + 'trial' => false, + ]); + + $this->assertEquals('middle', $options->button); + $this->assertEquals(3, $options->clickCount); + $this->assertEquals(200.0, $options->delay); + $this->assertEquals(['x' => 30, 'y' => 40], $options->position); + $this->assertEquals([ModifierKey::Control], $options->modifiers); + $this->assertFalse($options->force); + $this->assertFalse($options->noWaitAfter); + $this->assertEquals(1000.0, $options->timeout); + $this->assertFalse($options->trial); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new ClickOptions(button: 'left'); + $options = ClickOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/Options/DblClickOptionsTest.php b/tests/Unit/Locator/Options/DblClickOptionsTest.php new file mode 100644 index 0000000..68241b0 --- /dev/null +++ b/tests/Unit/Locator/Options/DblClickOptionsTest.php @@ -0,0 +1,77 @@ + 'right', + 'delay' => 100.5, + 'modifiers' => ['Shift', 'Control'], + 'position' => ['x' => 10.0, 'y' => 20.0], + 'force' => true, + 'noWaitAfter' => false, + 'steps' => 5, + 'timeout' => 5000.0, + 'trial' => true, + ]); + + $result = $options->toArray(); + + $this->assertSame('right', $result['button']); + $this->assertSame(100.5, $result['delay']); + $this->assertSame(['Shift', 'Control'], $result['modifiers']); + $this->assertSame(['x' => 10.0, 'y' => 20.0], $result['position']); + $this->assertTrue($result['force']); + $this->assertFalse($result['noWaitAfter']); + $this->assertSame(5, $result['steps']); + $this->assertSame(5000.0, $result['timeout']); + $this->assertTrue($result['trial']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = DblClickOptions::from([ + 'button' => 'left', + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('button', $result); + $this->assertArrayNotHasKey('delay', $result); + $this->assertArrayNotHasKey('modifiers', $result); + $this->assertArrayNotHasKey('position', $result); + $this->assertArrayNotHasKey('force', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('steps', $result); + $this->assertArrayNotHasKey('timeout', $result); + $this->assertArrayNotHasKey('trial', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new DblClickOptions(button: 'middle'); + $result = DblClickOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/DragToOptionsTest.php b/tests/Unit/Locator/Options/DragToOptionsTest.php new file mode 100644 index 0000000..eca804b --- /dev/null +++ b/tests/Unit/Locator/Options/DragToOptionsTest.php @@ -0,0 +1,71 @@ + ['x' => 10.0, 'y' => 20.0], + 'targetPosition' => ['x' => 30.0, 'y' => 40.0], + 'force' => true, + 'noWaitAfter' => false, + 'steps' => 10, + 'timeout' => 7000.0, + 'trial' => true, + ]); + + $result = $options->toArray(); + + $this->assertSame(['x' => 10.0, 'y' => 20.0], $result['sourcePosition']); + $this->assertSame(['x' => 30.0, 'y' => 40.0], $result['targetPosition']); + $this->assertTrue($result['force']); + $this->assertFalse($result['noWaitAfter']); + $this->assertSame(10, $result['steps']); + $this->assertSame(7000.0, $result['timeout']); + $this->assertTrue($result['trial']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = DragToOptions::from([ + 'sourcePosition' => ['x' => 5.0, 'y' => 5.0], + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('sourcePosition', $result); + $this->assertArrayNotHasKey('targetPosition', $result); + $this->assertArrayNotHasKey('force', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('steps', $result); + $this->assertArrayNotHasKey('timeout', $result); + $this->assertArrayNotHasKey('trial', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new DragToOptions(sourcePosition: ['x' => 1.0, 'y' => 2.0]); + $result = DragToOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/FillOptionsTest.php b/tests/Unit/Locator/Options/FillOptionsTest.php new file mode 100644 index 0000000..34d057c --- /dev/null +++ b/tests/Unit/Locator/Options/FillOptionsTest.php @@ -0,0 +1,59 @@ + true, + 'noWaitAfter' => false, + 'timeout' => 3000.0, + ]); + + $result = $options->toArray(); + + $this->assertTrue($result['force']); + $this->assertFalse($result['noWaitAfter']); + $this->assertSame(3000.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = FillOptions::from([ + 'force' => true, + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('force', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('timeout', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new FillOptions(force: true); + $result = FillOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/FilterOptionsTest.php b/tests/Unit/Locator/Options/FilterOptionsTest.php new file mode 100644 index 0000000..d1ae956 --- /dev/null +++ b/tests/Unit/Locator/Options/FilterOptionsTest.php @@ -0,0 +1,75 @@ +createMock(LocatorInterface::class); + $hasNot = $this->createMock(LocatorInterface::class); + + $options = new FilterOptions( + has: $has, + hasNot: $hasNot, + hasText: 'foo', + hasNotText: 'bar', + ); + + $this->assertSame($has, $options->has); + $this->assertSame($hasNot, $options->hasNot); + $this->assertEquals('foo', $options->hasText); + $this->assertEquals('bar', $options->hasNotText); + + $this->assertEquals([ + 'has' => $has, + 'hasNot' => $hasNot, + 'hasText' => 'foo', + 'hasNotText' => 'bar', + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $has = $this->createMock(LocatorInterface::class); + $hasNot = $this->createMock(LocatorInterface::class); + + $options = FilterOptions::from([ + 'has' => $has, + 'hasNot' => $hasNot, + 'hasText' => 'foo', + 'hasNotText' => 'bar', + ]); + + $this->assertSame($has, $options->has); + $this->assertSame($hasNot, $options->hasNot); + $this->assertEquals('foo', $options->hasText); + $this->assertEquals('bar', $options->hasNotText); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new FilterOptions(hasText: 'foo'); + $options = FilterOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/Options/GetAttributeOptionsTest.php b/tests/Unit/Locator/Options/GetAttributeOptionsTest.php new file mode 100644 index 0000000..8203400 --- /dev/null +++ b/tests/Unit/Locator/Options/GetAttributeOptionsTest.php @@ -0,0 +1,51 @@ + 4500.0, + ]); + + $result = $options->toArray(); + + $this->assertSame(4500.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = GetAttributeOptions::from([]); + + $result = $options->toArray(); + + $this->assertArrayNotHasKey('timeout', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new GetAttributeOptions(timeout: 3000.0); + $result = GetAttributeOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/GetByOptionsTest.php b/tests/Unit/Locator/Options/GetByOptionsTest.php new file mode 100644 index 0000000..25a72d9 --- /dev/null +++ b/tests/Unit/Locator/Options/GetByOptionsTest.php @@ -0,0 +1,46 @@ +assertTrue($options->exact); + $this->assertEquals(['exact' => true], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = GetByOptions::from(['exact' => false]); + + $this->assertFalse($options->exact); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new GetByOptions(exact: true); + $options = GetByOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/Options/GetByRoleOptionsTest.php b/tests/Unit/Locator/Options/GetByRoleOptionsTest.php new file mode 100644 index 0000000..11eb967 --- /dev/null +++ b/tests/Unit/Locator/Options/GetByRoleOptionsTest.php @@ -0,0 +1,146 @@ + 'child', + 'checked' => true, + 'includeHidden' => true, + 'level' => '3', + 'name' => new class implements \Stringable { + public function __toString(): string + { + return 'Save'; + } + }, + 'timeout' => 250, + ]); + + $result = $options->toArray(); + + $this->assertSame('child', $result['hasText']); + $this->assertTrue($result['checked']); + $this->assertTrue($result['includeHidden']); + $this->assertSame(3, $result['level']); + $this->assertSame('Save', $result['name']); + $this->assertSame(250, $result['timeout']); + } + + public function testFromArrayAcceptsIntLevel(): void + { + $options = GetByRoleOptions::from(['level' => 3]); + $this->assertSame(3, $options->level); + } + + public function testFromArrayAcceptsStringName(): void + { + $options = GetByRoleOptions::from(['name' => 'Submit']); + $this->assertSame('Submit', $options->name); + } + + public function testFromArrayAcceptsPressedOptions(): void + { + // Boolean true + $options1 = GetByRoleOptions::from(['pressed' => true]); + $this->assertTrue($options1->pressed); + + // Boolean false + $options2 = GetByRoleOptions::from(['pressed' => false]); + $this->assertFalse($options2->pressed); + + // String "mixed" + $options3 = GetByRoleOptions::from(['pressed' => 'mixed']); + $this->assertSame('mixed', $options3->pressed); + } + + public function testFromArrayRejectsInvalidPressedOption(): void + { + $this->expectExceptionMessage('getByRole option "pressed" must be boolean or "mixed".'); + GetByRoleOptions::from(['pressed' => 'invalid']); + } + + public function testFromArrayRejectsInvalidCheckedFlag(): void + { + $this->expectExceptionMessage('getByRole option "checked" must be boolean.'); + + GetByRoleOptions::from(['checked' => 'yes']); + } + + public function testFromArrayRejectsInvalidLevel(): void + { + $this->expectExceptionMessage('getByRole option "level" must be an integer.'); + + GetByRoleOptions::from(['level' => 1.5]); + } + + public function testCanBeInstantiatedDirectly(): void + { + $options = new GetByRoleOptions( + pressed: true, + exact: true, + locatorOptions: new LocatorOptions(hasNotText: 'disabled') + ); + + $result = $options->toArray(); + + $this->assertTrue($result['pressed']); + $this->assertTrue($result['exact']); + $this->assertSame('disabled', $result['hasNotText']); + } + + public function testExactOption(): void + { + $options = GetByRoleOptions::from(['exact' => true]); + $this->assertTrue($options->exact); + $this->assertTrue($options->toArray()['exact']); + } + + public function testFromReturnsSameInstance(): void + { + $options = new GetByRoleOptions(checked: true); + $this->assertSame($options, GetByRoleOptions::from($options)); + } + + public function testFromArrayRejectsInvalidName(): void + { + $this->expectExceptionMessage('getByRole option "name" must be stringable.'); + GetByRoleOptions::from(['name' => []]); + } + + public function testFromArrayHandlesNullValues(): void + { + $options = GetByRoleOptions::from([ + 'checked' => null, + 'level' => null, + 'name' => null, + ]); + + $result = $options->toArray(); + + $this->assertArrayNotHasKey('checked', $result); + $this->assertArrayNotHasKey('level', $result); + $this->assertArrayNotHasKey('name', $result); + } +} diff --git a/tests/Unit/Locator/Options/HoverOptionsTest.php b/tests/Unit/Locator/Options/HoverOptionsTest.php new file mode 100644 index 0000000..998df78 --- /dev/null +++ b/tests/Unit/Locator/Options/HoverOptionsTest.php @@ -0,0 +1,68 @@ + ['Alt', 'Control'], + 'position' => ['x' => 15.5, 'y' => 25.5], + 'force' => false, + 'noWaitAfter' => true, + 'timeout' => 6000.0, + 'trial' => false, + ]); + + $result = $options->toArray(); + + $this->assertSame(['Alt', 'Control'], $result['modifiers']); + $this->assertSame(['x' => 15.5, 'y' => 25.5], $result['position']); + $this->assertFalse($result['force']); + $this->assertTrue($result['noWaitAfter']); + $this->assertSame(6000.0, $result['timeout']); + $this->assertFalse($result['trial']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = HoverOptions::from([ + 'force' => true, + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('force', $result); + $this->assertArrayNotHasKey('modifiers', $result); + $this->assertArrayNotHasKey('position', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('timeout', $result); + $this->assertArrayNotHasKey('trial', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new HoverOptions(force: true); + $result = HoverOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/LocatorOptionsTest.php b/tests/Unit/Locator/Options/LocatorOptionsTest.php new file mode 100644 index 0000000..82376de --- /dev/null +++ b/tests/Unit/Locator/Options/LocatorOptionsTest.php @@ -0,0 +1,94 @@ +createMock(LocatorInterface::class); + + $options = LocatorOptions::from([ + 'has' => $hasLocator, + 'hasText' => 'Submit', + 'strict' => true, + 'timeout' => 5000, + ]); + + $result = $options->toArray(); + + $this->assertSame($hasLocator, $result['has']); + $this->assertSame('Submit', $result['hasText']); + $this->assertTrue($result['strict']); + $this->assertSame(5000, $result['timeout']); + } + + public function testFromArrayRejectsInvalidStrictFlag(): void + { + $this->expectExceptionMessage('Locator option "strict" must be boolean.'); + + LocatorOptions::from(['strict' => 'yes']); + } + + public function testFromReturnsSameInstance(): void + { + $options = new LocatorOptions(strict: true); + $this->assertSame($options, LocatorOptions::from($options)); + } + + public function testFromArrayRejectsInvalidLocator(): void + { + $this->expectExceptionMessage('Locator option "has" must be a Locator instance or null.'); + LocatorOptions::from(['has' => 'invalid']); + } + + public function testFromArrayRejectsInvalidString(): void + { + $this->expectExceptionMessage('Locator option "hasText" must be stringable.'); + LocatorOptions::from(['hasText' => []]); + } + + public function testFromArrayHandlesNullValues(): void + { + $options = LocatorOptions::from([ + 'has' => null, + 'hasText' => null, + ]); + + $result = $options->toArray(); + + $this->assertArrayNotHasKey('has', $result); + $this->assertArrayNotHasKey('hasText', $result); + } + + public function testFromArrayIgnoresNonStringKeys(): void + { + $options = LocatorOptions::from([ + 'valid' => 'value', + 0 => 'ignored', + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('valid', $result); + $this->assertArrayNotHasKey(0, $result); + } +} diff --git a/tests/Unit/Locator/Options/LocatorScreenshotOptionsTest.php b/tests/Unit/Locator/Options/LocatorScreenshotOptionsTest.php new file mode 100644 index 0000000..02d34d5 --- /dev/null +++ b/tests/Unit/Locator/Options/LocatorScreenshotOptionsTest.php @@ -0,0 +1,83 @@ + '/path/to/screenshot.png', + 'type' => 'png', + 'quality' => 80, + 'omitBackground' => true, + 'timeout' => 8000.0, + 'animations' => 'disabled', + 'caret' => 'hide', + 'scale' => 'css', + 'mask' => ['selector1', 'selector2'], + 'maskColor' => '#00FF00', + 'style' => 'background: red;', + ]); + + $result = $options->toArray(); + + $this->assertSame('/path/to/screenshot.png', $result['path']); + $this->assertSame('png', $result['type']); + $this->assertSame(80, $result['quality']); + $this->assertTrue($result['omitBackground']); + $this->assertSame(8000.0, $result['timeout']); + $this->assertSame('disabled', $result['animations']); + $this->assertSame('hide', $result['caret']); + $this->assertSame('css', $result['scale']); + $this->assertSame(['selector1', 'selector2'], $result['mask']); + $this->assertSame('#00FF00', $result['maskColor']); + $this->assertSame('background: red;', $result['style']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = LocatorScreenshotOptions::from([ + 'path' => '/path/to/image.png', + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('path', $result); + $this->assertArrayNotHasKey('type', $result); + $this->assertArrayNotHasKey('quality', $result); + $this->assertArrayNotHasKey('omitBackground', $result); + $this->assertArrayNotHasKey('timeout', $result); + $this->assertArrayNotHasKey('animations', $result); + $this->assertArrayNotHasKey('caret', $result); + $this->assertArrayNotHasKey('scale', $result); + $this->assertArrayNotHasKey('mask', $result); + $this->assertArrayNotHasKey('maskColor', $result); + $this->assertArrayNotHasKey('style', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new LocatorScreenshotOptions(path: '/test.png'); + $result = LocatorScreenshotOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/PressOptionsTest.php b/tests/Unit/Locator/Options/PressOptionsTest.php new file mode 100644 index 0000000..868a151 --- /dev/null +++ b/tests/Unit/Locator/Options/PressOptionsTest.php @@ -0,0 +1,59 @@ + 50.0, + 'noWaitAfter' => true, + 'timeout' => 2000.0, + ]); + + $result = $options->toArray(); + + $this->assertSame(50.0, $result['delay']); + $this->assertTrue($result['noWaitAfter']); + $this->assertSame(2000.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = PressOptions::from([ + 'delay' => 100.0, + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('delay', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + $this->assertArrayNotHasKey('timeout', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new PressOptions(delay: 75.0); + $result = PressOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/SelectOptionOptionsTest.php b/tests/Unit/Locator/Options/SelectOptionOptionsTest.php new file mode 100644 index 0000000..9243628 --- /dev/null +++ b/tests/Unit/Locator/Options/SelectOptionOptionsTest.php @@ -0,0 +1,59 @@ + true, + 'noWaitAfter' => false, + 'timeout' => 5500.0, + ]); + + $result = $options->toArray(); + + $this->assertTrue($result['force']); + $this->assertFalse($result['noWaitAfter']); + $this->assertSame(5500.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = SelectOptionOptions::from([ + 'timeout' => 1000.0, + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('timeout', $result); + $this->assertArrayNotHasKey('force', $result); + $this->assertArrayNotHasKey('noWaitAfter', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new SelectOptionOptions(force: true); + $result = SelectOptionOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/SetInputFilesOptionsTest.php b/tests/Unit/Locator/Options/SetInputFilesOptionsTest.php new file mode 100644 index 0000000..3a4e3f8 --- /dev/null +++ b/tests/Unit/Locator/Options/SetInputFilesOptionsTest.php @@ -0,0 +1,58 @@ +assertTrue($options->noWaitAfter); + $this->assertEquals(5000.0, $options->timeout); + + $this->assertEquals([ + 'noWaitAfter' => true, + 'timeout' => 5000.0, + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = SetInputFilesOptions::from([ + 'noWaitAfter' => false, + 'timeout' => 1000.0, + ]); + + $this->assertFalse($options->noWaitAfter); + $this->assertEquals(1000.0, $options->timeout); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new SetInputFilesOptions(timeout: 50.0); + $options = SetInputFilesOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/Options/TextContentOptionsTest.php b/tests/Unit/Locator/Options/TextContentOptionsTest.php new file mode 100644 index 0000000..84c0982 --- /dev/null +++ b/tests/Unit/Locator/Options/TextContentOptionsTest.php @@ -0,0 +1,51 @@ + 3500.0, + ]); + + $result = $options->toArray(); + + $this->assertSame(3500.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = TextContentOptions::from([]); + + $result = $options->toArray(); + + $this->assertArrayNotHasKey('timeout', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new TextContentOptions(timeout: 2000.0); + $result = TextContentOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Locator/Options/TypeOptionsTest.php b/tests/Unit/Locator/Options/TypeOptionsTest.php new file mode 100644 index 0000000..aab1bea --- /dev/null +++ b/tests/Unit/Locator/Options/TypeOptionsTest.php @@ -0,0 +1,63 @@ +assertEquals(100.0, $options->delay); + $this->assertTrue($options->noWaitAfter); + $this->assertEquals(5000.0, $options->timeout); + + $this->assertEquals([ + 'delay' => 100.0, + 'noWaitAfter' => true, + 'timeout' => 5000.0, + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = TypeOptions::from([ + 'delay' => 200.0, + 'noWaitAfter' => false, + 'timeout' => 1000.0, + ]); + + $this->assertEquals(200.0, $options->delay); + $this->assertFalse($options->noWaitAfter); + $this->assertEquals(1000.0, $options->timeout); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new TypeOptions(delay: 50.0); + $options = TypeOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/Options/UncheckOptionsTest.php b/tests/Unit/Locator/Options/UncheckOptionsTest.php new file mode 100644 index 0000000..3de5473 --- /dev/null +++ b/tests/Unit/Locator/Options/UncheckOptionsTest.php @@ -0,0 +1,73 @@ + 10, 'y' => 20], + force: true, + noWaitAfter: true, + timeout: 5000.0, + trial: true, + ); + + $this->assertEquals(['x' => 10, 'y' => 20], $options->position); + $this->assertTrue($options->force); + $this->assertTrue($options->noWaitAfter); + $this->assertEquals(5000.0, $options->timeout); + $this->assertTrue($options->trial); + + $this->assertEquals([ + 'position' => ['x' => 10, 'y' => 20], + 'force' => true, + 'noWaitAfter' => true, + 'timeout' => 5000.0, + 'trial' => true, + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = UncheckOptions::from([ + 'position' => ['x' => 30, 'y' => 40], + 'force' => false, + 'noWaitAfter' => false, + 'timeout' => 1000.0, + 'trial' => false, + ]); + + $this->assertEquals(['x' => 30, 'y' => 40], $options->position); + $this->assertFalse($options->force); + $this->assertFalse($options->noWaitAfter); + $this->assertEquals(1000.0, $options->timeout); + $this->assertFalse($options->trial); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new UncheckOptions(force: true); + $options = UncheckOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Locator/Options/WaitForOptionsTest.php b/tests/Unit/Locator/Options/WaitForOptionsTest.php new file mode 100644 index 0000000..929f0cc --- /dev/null +++ b/tests/Unit/Locator/Options/WaitForOptionsTest.php @@ -0,0 +1,56 @@ + 'visible', + 'timeout' => 9000.0, + ]); + + $result = $options->toArray(); + + $this->assertSame('visible', $result['state']); + $this->assertSame(9000.0, $result['timeout']); + } + + public function testToArrayExcludesNullProperties(): void + { + $options = WaitForOptions::from([ + 'state' => 'hidden', + ]); + + $result = $options->toArray(); + + $this->assertArrayHasKey('state', $result); + $this->assertArrayNotHasKey('timeout', $result); + } + + public function testFromAcceptsSelfInstance(): void + { + $original = new WaitForOptions(state: 'attached'); + $result = WaitForOptions::from($original); + + $this->assertSame($original, $result); + } +} diff --git a/tests/Unit/Page/Options/ClickOptionsTest.php b/tests/Unit/Page/Options/ClickOptionsTest.php new file mode 100644 index 0000000..7d5e7d5 --- /dev/null +++ b/tests/Unit/Page/Options/ClickOptionsTest.php @@ -0,0 +1,80 @@ +assertSame($options, ClickOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = ClickOptions::from(['clickCount' => 2, 'delay' => 100.0]); + $this->assertSame(2, $options->clickCount); + $this->assertSame(100.0, $options->delay); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + ClickOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new ClickOptions( + button: 'right', + clickCount: 2, + delay: 100.0, + position: ['x' => 10, 'y' => 20], + modifiers: ['Alt'], + force: true, + noWaitAfter: true, + timeout: 5000.0, + trial: true, + strict: true + ); + + $expected = [ + 'button' => 'right', + 'clickCount' => 2, + 'delay' => 100.0, + 'position' => ['x' => 10, 'y' => 20], + 'modifiers' => ['Alt'], + 'force' => true, + 'noWaitAfter' => true, + 'timeout' => 5000.0, + 'trial' => true, + 'strict' => true, + ]; + + $this->assertSame($expected, $options->toArray()); + } + + public function testToReturnArrayWithNulls(): void + { + $options = new ClickOptions(clickCount: 1); + $this->assertSame(['clickCount' => 1], $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/FrameQueryOptionsTest.php b/tests/Unit/Page/Options/FrameQueryOptionsTest.php new file mode 100644 index 0000000..1c80d24 --- /dev/null +++ b/tests/Unit/Page/Options/FrameQueryOptionsTest.php @@ -0,0 +1,60 @@ +assertSame($options, FrameQueryOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = FrameQueryOptions::from(['name' => 'frame1', 'url' => 'http://example.com']); + $this->assertSame('frame1', $options->name); + $this->assertSame('http://example.com', $options->url); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + FrameQueryOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new FrameQueryOptions( + name: 'frame1', + url: 'http://example.com', + urlRegex: '.*example.com.*' + ); + + $expected = [ + 'name' => 'frame1', + 'url' => 'http://example.com', + 'urlRegex' => '.*example.com.*', + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/GotoOptionsTest.php b/tests/Unit/Page/Options/GotoOptionsTest.php new file mode 100644 index 0000000..280c20b --- /dev/null +++ b/tests/Unit/Page/Options/GotoOptionsTest.php @@ -0,0 +1,62 @@ +assertSame($options, GotoOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = GotoOptions::from(['timeout' => 5000.0, 'waitUntil' => 'networkidle']); + $this->assertSame(5000.0, $options->timeout); + $this->assertSame('networkidle', $options->waitUntil); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + GotoOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new GotoOptions( + referer: 'http://example.com', + timeout: 5000.0, + waitUntil: 'load', + navigationRequest: true + ); + + $expected = [ + 'referer' => 'http://example.com', + 'timeout' => 5000.0, + 'waitUntil' => 'load', + 'navigationRequest' => true, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/NavigationHistoryOptionsTest.php b/tests/Unit/Page/Options/NavigationHistoryOptionsTest.php new file mode 100644 index 0000000..317abc0 --- /dev/null +++ b/tests/Unit/Page/Options/NavigationHistoryOptionsTest.php @@ -0,0 +1,58 @@ +assertSame($options, NavigationHistoryOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = NavigationHistoryOptions::from(['timeout' => 5000.0, 'waitUntil' => 'load']); + $this->assertSame(5000.0, $options->timeout); + $this->assertSame('load', $options->waitUntil); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + NavigationHistoryOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new NavigationHistoryOptions( + timeout: 5000.0, + waitUntil: 'networkidle' + ); + + $expected = [ + 'timeout' => 5000.0, + 'waitUntil' => 'networkidle', + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/ScreenshotOptionsTest.php b/tests/Unit/Page/Options/ScreenshotOptionsTest.php new file mode 100644 index 0000000..543a60d --- /dev/null +++ b/tests/Unit/Page/Options/ScreenshotOptionsTest.php @@ -0,0 +1,78 @@ +assertSame($options, ScreenshotOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = ScreenshotOptions::from(['fullPage' => true, 'type' => 'jpeg']); + $this->assertTrue($options->fullPage); + $this->assertSame('jpeg', $options->type); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + ScreenshotOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new ScreenshotOptions( + path: 'screenshot.png', + type: 'png', + quality: 80, + fullPage: true, + clip: ['x' => 0, 'y' => 0, 'width' => 100, 'height' => 100], + omitBackground: true, + timeout: 5000.0, + animations: 'disabled', + caret: 'hide', + scale: 'css', + mask: [], + maskColor: '#000000' + ); + + $expected = [ + 'path' => 'screenshot.png', + 'type' => 'png', + 'quality' => 80, + 'fullPage' => true, + 'clip' => ['x' => 0, 'y' => 0, 'width' => 100, 'height' => 100], + 'omitBackground' => true, + 'timeout' => 5000.0, + 'animations' => 'disabled', + 'caret' => 'hide', + 'scale' => 'css', + 'mask' => [], + 'maskColor' => '#000000', + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/ScriptTagOptionsTest.php b/tests/Unit/Page/Options/ScriptTagOptionsTest.php new file mode 100644 index 0000000..4b1a797 --- /dev/null +++ b/tests/Unit/Page/Options/ScriptTagOptionsTest.php @@ -0,0 +1,62 @@ +assertSame($options, ScriptTagOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = ScriptTagOptions::from(['url' => 'http://example.com/script.js', 'type' => 'module']); + $this->assertSame('http://example.com/script.js', $options->url); + $this->assertSame('module', $options->type); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + ScriptTagOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new ScriptTagOptions( + url: 'http://example.com/script.js', + path: 'script.js', + content: 'console.log("hello")', + type: 'module' + ); + + $expected = [ + 'url' => 'http://example.com/script.js', + 'path' => 'script.js', + 'content' => 'console.log("hello")', + 'type' => 'module', + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/SetContentOptionsTest.php b/tests/Unit/Page/Options/SetContentOptionsTest.php new file mode 100644 index 0000000..9e07add --- /dev/null +++ b/tests/Unit/Page/Options/SetContentOptionsTest.php @@ -0,0 +1,58 @@ +assertSame($options, SetContentOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = SetContentOptions::from(['waitUntil' => 'load', 'timeout' => 5000.0]); + $this->assertSame('load', $options->waitUntil); + $this->assertSame(5000.0, $options->timeout); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + SetContentOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new SetContentOptions( + waitUntil: 'networkidle', + timeout: 5000.0 + ); + + $expected = [ + 'waitUntil' => 'networkidle', + 'timeout' => 5000.0, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/SetInputFilesOptionsTest.php b/tests/Unit/Page/Options/SetInputFilesOptionsTest.php new file mode 100644 index 0000000..a0eb1ae --- /dev/null +++ b/tests/Unit/Page/Options/SetInputFilesOptionsTest.php @@ -0,0 +1,58 @@ +assertSame($options, SetInputFilesOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = SetInputFilesOptions::from(['noWaitAfter' => true, 'timeout' => 5000.0]); + $this->assertTrue($options->noWaitAfter); + $this->assertSame(5000.0, $options->timeout); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + SetInputFilesOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new SetInputFilesOptions( + noWaitAfter: true, + timeout: 5000.0 + ); + + $expected = [ + 'noWaitAfter' => true, + 'timeout' => 5000.0, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/StyleTagOptionsTest.php b/tests/Unit/Page/Options/StyleTagOptionsTest.php new file mode 100644 index 0000000..8e4fb01 --- /dev/null +++ b/tests/Unit/Page/Options/StyleTagOptionsTest.php @@ -0,0 +1,60 @@ +assertSame($options, StyleTagOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = StyleTagOptions::from(['url' => 'http://example.com/style.css', 'content' => 'body { color: red; }']); + $this->assertSame('http://example.com/style.css', $options->url); + $this->assertSame('body { color: red; }', $options->content); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + StyleTagOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new StyleTagOptions( + url: 'http://example.com/style.css', + path: 'style.css', + content: 'body { color: red; }' + ); + + $expected = [ + 'url' => 'http://example.com/style.css', + 'path' => 'style.css', + 'content' => 'body { color: red; }', + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/TypeOptionsTest.php b/tests/Unit/Page/Options/TypeOptionsTest.php new file mode 100644 index 0000000..cf82bb1 --- /dev/null +++ b/tests/Unit/Page/Options/TypeOptionsTest.php @@ -0,0 +1,62 @@ +assertSame($options, TypeOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = TypeOptions::from(['delay' => 100.0, 'noWaitAfter' => true]); + $this->assertSame(100.0, $options->delay); + $this->assertTrue($options->noWaitAfter); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + TypeOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new TypeOptions( + delay: 100.0, + noWaitAfter: true, + timeout: 5000.0, + strict: true + ); + + $expected = [ + 'delay' => 100.0, + 'noWaitAfter' => true, + 'timeout' => 5000.0, + 'strict' => true, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/WaitForLoadStateOptionsTest.php b/tests/Unit/Page/Options/WaitForLoadStateOptionsTest.php new file mode 100644 index 0000000..90587d1 --- /dev/null +++ b/tests/Unit/Page/Options/WaitForLoadStateOptionsTest.php @@ -0,0 +1,55 @@ +assertSame($options, WaitForLoadStateOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = WaitForLoadStateOptions::from(['timeout' => 5000.0]); + $this->assertSame(5000.0, $options->timeout); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + WaitForLoadStateOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new WaitForLoadStateOptions( + timeout: 5000.0 + ); + + $expected = [ + 'timeout' => 5000.0, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/WaitForPopupOptionsTest.php b/tests/Unit/Page/Options/WaitForPopupOptionsTest.php new file mode 100644 index 0000000..787aa20 --- /dev/null +++ b/tests/Unit/Page/Options/WaitForPopupOptionsTest.php @@ -0,0 +1,60 @@ +assertSame($options, WaitForPopupOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $predicate = function () { return true; }; + $options = WaitForPopupOptions::from(['timeout' => 5000.0, 'predicate' => $predicate]); + $this->assertSame(5000.0, $options->timeout); + $this->assertSame($predicate, $options->predicate); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + WaitForPopupOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $predicate = function () { return true; }; + $options = new WaitForPopupOptions( + predicate: $predicate, + timeout: 5000.0 + ); + + $expected = [ + 'predicate' => $predicate, + 'timeout' => 5000.0, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/WaitForResponseOptionsTest.php b/tests/Unit/Page/Options/WaitForResponseOptionsTest.php new file mode 100644 index 0000000..4af9778 --- /dev/null +++ b/tests/Unit/Page/Options/WaitForResponseOptionsTest.php @@ -0,0 +1,46 @@ +assertSame(1000.0, $options->timeout); + $this->assertEquals(['timeout' => 1000.0], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = WaitForResponseOptions::from(['timeout' => 1000.0]); + + $this->assertSame(1000.0, $options->timeout); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new WaitForResponseOptions(timeout: 1000.0); + $options = WaitForResponseOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Page/Options/WaitForSelectorOptionsTest.php b/tests/Unit/Page/Options/WaitForSelectorOptionsTest.php new file mode 100644 index 0000000..1f30c5c --- /dev/null +++ b/tests/Unit/Page/Options/WaitForSelectorOptionsTest.php @@ -0,0 +1,60 @@ +assertSame($options, WaitForSelectorOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = WaitForSelectorOptions::from(['state' => 'visible', 'timeout' => 5000.0]); + $this->assertSame('visible', $options->state); + $this->assertSame(5000.0, $options->timeout); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + WaitForSelectorOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new WaitForSelectorOptions( + state: 'visible', + timeout: 5000.0, + strict: true + ); + + $expected = [ + 'state' => 'visible', + 'timeout' => 5000.0, + 'strict' => true, + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/Options/WaitForUrlOptionsTest.php b/tests/Unit/Page/Options/WaitForUrlOptionsTest.php new file mode 100644 index 0000000..0075331 --- /dev/null +++ b/tests/Unit/Page/Options/WaitForUrlOptionsTest.php @@ -0,0 +1,58 @@ +assertSame($options, WaitForUrlOptions::from($options)); + } + + public function testItCreatesFromArray(): void + { + $options = WaitForUrlOptions::from(['timeout' => 5000.0, 'waitUntil' => 'load']); + $this->assertSame(5000.0, $options->timeout); + $this->assertSame('load', $options->waitUntil); + } + + public function testItThrowsExceptionForInvalidInput(): void + { + $this->expectException(\TypeError::class); + + WaitForUrlOptions::from('invalid'); + } + + public function testToReturnArray(): void + { + $options = new WaitForUrlOptions( + timeout: 5000.0, + waitUntil: 'networkidle' + ); + + $expected = [ + 'timeout' => 5000.0, + 'waitUntil' => 'networkidle', + ]; + + $this->assertSame($expected, $options->toArray()); + } +} diff --git a/tests/Unit/Page/PageEventHandlerTest.php b/tests/Unit/Page/PageEventHandlerTest.php index 410f559..4d208d6 100644 --- a/tests/Unit/Page/PageEventHandlerTest.php +++ b/tests/Unit/Page/PageEventHandlerTest.php @@ -129,4 +129,42 @@ public function testOnResponseRegistersResponseHandler(): void $this->assertTrue($wasCalled); $this->assertSame($response, $receivedResponse); } + + public function testOnRequestFailedRegistersRequestFailedHandler(): void + { + $handler = new PageEventHandler(); + $wasCalled = false; + $receivedRequest = null; + + $request = new Request(['url' => 'https://example.com', 'method' => 'GET', 'headers' => [], 'resourceType' => 'document', 'failure' => 'net::ERR_FAILED']); + + $handler->onRequestFailed(function (Request $r) use (&$wasCalled, &$receivedRequest) { + $wasCalled = true; + $receivedRequest = $r; + }); + + $handler->publicEmit('requestfailed', [$request]); + + $this->assertTrue($wasCalled); + $this->assertSame($request, $receivedRequest); + } + + public function testOnRouteRegistersRouteHandler(): void + { + $handler = new PageEventHandler(); + $wasCalled = false; + $receivedRoute = null; + + $route = new \Playwright\Network\Route($this->transport, 'route1', ['url' => 'https://example.com']); + + $handler->onRoute(function ($r) use (&$wasCalled, &$receivedRoute) { + $wasCalled = true; + $receivedRoute = $r; + }); + + $handler->publicEmit('route', [$route]); + + $this->assertTrue($wasCalled); + $this->assertSame($route, $receivedRoute); + } } diff --git a/tests/Unit/Page/PagePopupTest.php b/tests/Unit/Page/PagePopupTest.php index 58b929f..bd843f4 100644 --- a/tests/Unit/Page/PagePopupTest.php +++ b/tests/Unit/Page/PagePopupTest.php @@ -75,7 +75,7 @@ public function testWaitForPopupWithCustomTimeout(): void $this->assertTrue($actionExecuted); return 'page.waitForPopup' === $payload['action'] - && 5000 === $payload['timeout']; + && 5000.0 === (float) $payload['timeout']; })) ->willReturn(['popupPageId' => 'popup456']); diff --git a/tests/Unit/Page/PageTest.php b/tests/Unit/Page/PageTest.php index b2e7342..e0e6862 100644 --- a/tests/Unit/Page/PageTest.php +++ b/tests/Unit/Page/PageTest.php @@ -19,6 +19,9 @@ use Playwright\Browser\BrowserContextInterface; use Playwright\Input\KeyboardInterface; use Playwright\Input\MouseInterface; +use Playwright\Locator\Locator; +use Playwright\Locator\Options\GetByRoleOptions; +use Playwright\Locator\Options\LocatorOptions; use Playwright\Page\Page; use Playwright\Page\PageEventHandlerInterface; use Playwright\Transport\TransportInterface; @@ -27,14 +30,15 @@ class PageTest extends TestCase { protected Page $page; + protected \PHPUnit\Framework\MockObject\MockObject $transport; protected function setUp(): void { - $transport = $this->createMock(TransportInterface::class); + $this->transport = $this->createMock(TransportInterface::class); $context = $this->createMock(BrowserContextInterface::class); $pageId = 'page-id'; - $this->page = new Page($transport, $context, $pageId); + $this->page = new Page($this->transport, $context, $pageId); } public function testGetKeyboard(): void @@ -57,4 +61,305 @@ public function testGetEvents(): void $this->assertInstanceOf(PageEventHandlerInterface::class, $events); } + + public function testLocatorAcceptsLocatorOptions(): void + { + $options = new LocatorOptions(hasText: 'Save', strict: true); + + $locator = $this->page->locator('button.save', $options); + + $this->assertInstanceOf(Locator::class, $locator); + $this->assertSame([ + 'hasText' => 'Save', + 'strict' => true, + ], $locator->getOptions()); + } + + public function testGetByRoleAcceptsGetByRoleOptions(): void + { + $options = new GetByRoleOptions( + checked: true, + locatorOptions: new LocatorOptions(hasNotText: 'Loading') + ); + + $locator = $this->page->getByRole('button', $options); + + $this->assertInstanceOf(Locator::class, $locator); + $result = $locator->getOptions(); + + $this->assertTrue($result['checked']); + $this->assertSame('Loading', $result['hasNotText']); + } + + public function testGotoSendsCommandAndReturnsResponse(): void + { + $url = 'https://example.com'; + $responseData = ['url' => $url, 'status' => 200, 'statusText' => 'OK', 'headers' => [], 'responseId' => 'res-1']; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'url' => $url, + 'options' => [], + 'action' => 'page.goto', + 'pageId' => 'page-id', + ]) + ->willReturn(['response' => $responseData]); + + $response = $this->page->goto($url); + + $this->assertInstanceOf(\Playwright\Network\ResponseInterface::class, $response); + $this->assertSame(200, $response->status()); + } + + public function testClickSendsCommand(): void + { + $selector = 'button'; + $options = ['force' => true]; + + $this->transport->expects($this->exactly(3)) + ->method('send') + ->willReturnCallback(function (array $payload) { + if ('locator.isVisible' === $payload['action']) { + return ['value' => true]; + } + if ('locator.isEnabled' === $payload['action']) { + return ['value' => true]; + } + if ('locator.click' === $payload['action']) { + $this->assertSame(['force' => true], $payload['options']); + + return []; + } + $this->fail('Unexpected action: '.$payload['action']); + }); + + $this->page->click($selector, $options); + } + + public function testTypeSendsCommand(): void + { + $selector = 'input'; + $text = 'hello'; + $options = ['delay' => 100.0]; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'text' => $text, + 'options' => ['delay' => 100.0], + 'action' => 'locator.type', + 'pageId' => 'page-id', + 'selector' => $selector, + ]) + ->willReturn([]); + + $this->page->type($selector, $text, $options); + } + + public function testScreenshotSendsCommandAndReturnsPath(): void + { + $path = 'screenshot.png'; + $options = ['fullPage' => true]; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'options' => ['fullPage' => true, 'path' => $path], + 'action' => 'page.screenshot', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $result = $this->page->screenshot($path, $options); + + $this->assertSame($path, $result); + } + + public function testTitleSendsCommandAndReturnsString(): void + { + $title = 'Page Title'; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'action' => 'page.title', + 'pageId' => 'page-id', + ]) + ->willReturn(['value' => $title]); + + $result = $this->page->title(); + + $this->assertSame($title, $result); + } + + public function testUrlSendsCommandAndReturnsString(): void + { + $url = 'https://example.com'; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'action' => 'page.url', + 'pageId' => 'page-id', + ]) + ->willReturn(['value' => $url]); + + $result = $this->page->url(); + + $this->assertSame($url, $result); + } + + public function testContentSendsCommandAndReturnsString(): void + { + $content = ''; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'action' => 'page.content', + 'pageId' => 'page-id', + ]) + ->willReturn(['content' => $content]); + + $result = $this->page->content(); + + $this->assertSame($content, $result); + } + + public function testSetContentSendsCommand(): void + { + $html = ''; + $options = ['timeout' => 1000.0]; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'html' => $html, + 'options' => ['timeout' => 1000.0], + 'action' => 'page.setContent', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $this->page->setContent($html, $options); + } + + public function testEvaluateSendsCommandAndReturnsResult(): void + { + $expression = '1 + 1'; + $result = 2; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'expression' => $expression, + 'arg' => null, + 'action' => 'page.evaluate', + 'pageId' => 'page-id', + ]) + ->willReturn(['result' => $result]); + + $this->assertSame($result, $this->page->evaluate($expression)); + } + + public function testWaitForSelectorSendsCommandAndReturnsLocator(): void + { + $selector = 'div'; + $options = ['state' => 'visible']; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'selector' => $selector, + 'options' => ['state' => 'visible'], + 'action' => 'page.waitForSelector', + 'pageId' => 'page-id', + ]) + ->willReturn(['element' => ['guid' => 'element-guid']]); + + $locator = $this->page->waitForSelector($selector, $options); + $this->assertInstanceOf(\Playwright\Locator\LocatorInterface::class, $locator); + } + + public function testWaitForLoadStateSendsCommand(): void + { + $state = 'networkidle'; + $options = ['timeout' => 5000.0]; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'state' => $state, + 'options' => ['timeout' => 5000.0], + 'action' => 'page.waitForLoadState', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $this->page->waitForLoadState($state, $options); + } + + public function testWaitForURLSendsCommand(): void + { + $url = 'https://example.com'; + $options = ['timeout' => 5000.0]; + + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'url' => $url, + 'options' => ['timeout' => 5000.0], + 'action' => 'page.waitForURL', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $this->page->waitForURL($url, $options); + } + + public function testGoBackSendsCommandAndReturnsSelf(): void + { + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'options' => [], + 'action' => 'page.goBack', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $result = $this->page->goBack(); + $this->assertSame($this->page, $result); + } + + public function testGoForwardSendsCommandAndReturnsSelf(): void + { + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'options' => [], + 'action' => 'page.goForward', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $result = $this->page->goForward(); + $this->assertSame($this->page, $result); + } + + public function testReloadSendsCommandAndReturnsSelf(): void + { + $this->transport->expects($this->once()) + ->method('send') + ->with([ + 'options' => [], + 'action' => 'page.reload', + 'pageId' => 'page-id', + ]) + ->willReturn([]); + + $result = $this->page->reload(); + $this->assertSame($this->page, $result); + } } diff --git a/tests/Unit/Selector/Options/RegisterOptionsTest.php b/tests/Unit/Selector/Options/RegisterOptionsTest.php new file mode 100644 index 0000000..8183e0c --- /dev/null +++ b/tests/Unit/Selector/Options/RegisterOptionsTest.php @@ -0,0 +1,46 @@ +assertTrue($options->contentScript); + $this->assertEquals(['contentScript' => true], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = RegisterOptions::from(['contentScript' => true]); + + $this->assertTrue($options->contentScript); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new RegisterOptions(contentScript: true); + $options = RegisterOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Tracing/Options/StartChunkOptionsTest.php b/tests/Unit/Tracing/Options/StartChunkOptionsTest.php new file mode 100644 index 0000000..b3e8ca7 --- /dev/null +++ b/tests/Unit/Tracing/Options/StartChunkOptionsTest.php @@ -0,0 +1,58 @@ +assertSame('chunk-name', $options->name); + $this->assertSame('Chunk Title', $options->title); + + $this->assertEquals([ + 'name' => 'chunk-name', + 'title' => 'Chunk Title', + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = StartChunkOptions::from([ + 'name' => 'chunk-name', + 'title' => 'Chunk Title', + ]); + + $this->assertSame('chunk-name', $options->name); + $this->assertSame('Chunk Title', $options->title); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new StartChunkOptions(name: 'chunk-name'); + $options = StartChunkOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Tracing/Options/StartOptionsTest.php b/tests/Unit/Tracing/Options/StartOptionsTest.php new file mode 100644 index 0000000..1e342bc --- /dev/null +++ b/tests/Unit/Tracing/Options/StartOptionsTest.php @@ -0,0 +1,73 @@ +assertSame('trace-name', $options->name); + $this->assertTrue($options->screenshots); + $this->assertTrue($options->snapshots); + $this->assertFalse($options->sources); + $this->assertSame('Trace Title', $options->title); + + $this->assertEquals([ + 'name' => 'trace-name', + 'screenshots' => true, + 'snapshots' => true, + 'sources' => false, + 'title' => 'Trace Title', + ], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = StartOptions::from([ + 'name' => 'trace-name', + 'screenshots' => true, + 'snapshots' => true, + 'sources' => false, + 'title' => 'Trace Title', + ]); + + $this->assertSame('trace-name', $options->name); + $this->assertTrue($options->screenshots); + $this->assertTrue($options->snapshots); + $this->assertFalse($options->sources); + $this->assertSame('Trace Title', $options->title); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new StartOptions(name: 'trace-name'); + $options = StartOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/Tracing/Options/StopOptionsTest.php b/tests/Unit/Tracing/Options/StopOptionsTest.php new file mode 100644 index 0000000..74ac775 --- /dev/null +++ b/tests/Unit/Tracing/Options/StopOptionsTest.php @@ -0,0 +1,46 @@ +assertSame('trace.zip', $options->path); + $this->assertEquals(['path' => 'trace.zip'], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = StopOptions::from(['path' => 'trace.zip']); + + $this->assertSame('trace.zip', $options->path); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new StopOptions(path: 'trace.zip'); + $options = StopOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/WebSocket/Options/CloseOptionsTest.php b/tests/Unit/WebSocket/Options/CloseOptionsTest.php new file mode 100644 index 0000000..fb424f3 --- /dev/null +++ b/tests/Unit/WebSocket/Options/CloseOptionsTest.php @@ -0,0 +1,48 @@ +assertSame(1000, $options->code); + $this->assertSame('Normal Closure', $options->reason); + $this->assertEquals(['code' => 1000, 'reason' => 'Normal Closure'], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $options = CloseOptions::from(['code' => 1000, 'reason' => 'Normal Closure']); + + $this->assertSame(1000, $options->code); + $this->assertSame('Normal Closure', $options->reason); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new CloseOptions(code: 1000); + $options = CloseOptions::from($original); + + $this->assertSame($original, $options); + } +} diff --git a/tests/Unit/WebSocket/Options/WaitForEventOptionsTest.php b/tests/Unit/WebSocket/Options/WaitForEventOptionsTest.php new file mode 100644 index 0000000..c4035c4 --- /dev/null +++ b/tests/Unit/WebSocket/Options/WaitForEventOptionsTest.php @@ -0,0 +1,50 @@ + true; + $options = new WaitForEventOptions(predicate: $predicate, timeout: 1000.0); + + $this->assertSame($predicate, $options->predicate); + $this->assertSame(1000.0, $options->timeout); + $this->assertEquals(['predicate' => $predicate, 'timeout' => 1000.0], $options->toArray()); + } + + public function testCanBeCreatedFromArray(): void + { + $predicate = fn () => true; + $options = WaitForEventOptions::from(['predicate' => $predicate, 'timeout' => 1000.0]); + + $this->assertSame($predicate, $options->predicate); + $this->assertSame(1000.0, $options->timeout); + } + + public function testCanBeCreatedFromSelf(): void + { + $original = new WaitForEventOptions(timeout: 1000.0); + $options = WaitForEventOptions::from($original); + + $this->assertSame($original, $options); + } +}