diff --git a/src/Browser/StorageState.php b/src/Browser/StorageState.php index e2d566e..f2b6c55 100644 --- a/src/Browser/StorageState.php +++ b/src/Browser/StorageState.php @@ -14,6 +14,7 @@ namespace Playwright\Browser; +use Playwright\Cookie; use Playwright\Exception\RuntimeException; /** @@ -22,8 +23,8 @@ final readonly class StorageState { /** - * @param array $cookies - * @param array}> $origins + * @param array $cookies + * @param array}> $origins */ public function __construct( public array $cookies = [], @@ -140,7 +141,7 @@ public function getOriginCount(): int } /** - * @return array + * @return array */ public function getCookiesForDomain(string $domain): array { @@ -164,7 +165,7 @@ public function getLocalStorageForOrigin(string $origin): array /** * @param array $cookies * - * @return array + * @return array */ private static function validateCookies(array $cookies): array { @@ -174,38 +175,7 @@ private static function validateCookies(array $cookies): array throw new \InvalidArgumentException('Invalid cookie data structure'); } - $name = $cookie['name'] ?? null; - $value = $cookie['value'] ?? null; - $domain = $cookie['domain'] ?? null; - $path = $cookie['path'] ?? null; - $expires = $cookie['expires'] ?? null; - $httpOnly = $cookie['httpOnly'] ?? null; - $secure = $cookie['secure'] ?? null; - $sameSite = $cookie['sameSite'] ?? null; - - if (!is_string($name) - || !is_string($value) - || !is_string($domain) - || !is_string($path) - || !is_int($expires) - || !is_bool($httpOnly) - || !is_bool($secure) - || !is_string($sameSite) - || !in_array($sameSite, ['Lax', 'None', 'Strict'], true) - ) { - throw new \InvalidArgumentException('Invalid cookie fields'); - } - - $result[] = [ - 'name' => $name, - 'value' => $value, - 'domain' => $domain, - 'path' => $path, - 'expires' => $expires, - 'httpOnly' => $httpOnly, - 'secure' => $secure, - 'sameSite' => $sameSite, - ]; + $result[] = Cookie::fromArray($cookie)->toArray(); } return $result; diff --git a/src/Cookie.php b/src/Cookie.php new file mode 100644 index 0000000..177426c --- /dev/null +++ b/src/Cookie.php @@ -0,0 +1,84 @@ + $cookie + */ + public static function fromArray(array $cookie): self + { + $sameSite = $cookie['sameSite'] ?? null; + + if (!is_string($cookie['name'] ?? null) + || !is_string($cookie['value'] ?? null) + || !is_string($cookie['domain'] ?? null) + || !is_string($cookie['path'] ?? null) + || (!is_int($cookie['expires'] ?? null) && !is_float($cookie['expires'] ?? null)) + || !is_bool($cookie['httpOnly'] ?? null) + || !is_bool($cookie['secure'] ?? null) + || !in_array($sameSite, ['Lax', 'None', 'Strict'], true) + ) { + throw new \InvalidArgumentException('Invalid cookie fields'); + } + + return new self( + name: $cookie['name'], + value: $cookie['value'], + domain: $cookie['domain'], + path: $cookie['path'], + expires: $cookie['expires'], + httpOnly: $cookie['httpOnly'], + secure: $cookie['secure'], + sameSite: $sameSite, + ); + } + + /** + * @return array{name: string, value: string, domain: string, path: string, expires: int|float, httpOnly: bool, secure: bool, sameSite: 'Strict'|'Lax'|'None'} + */ + public function toArray(): array + { + return [ + 'name' => $this->name, + 'value' => $this->value, + 'domain' => $this->domain, + 'path' => $this->path, + 'expires' => $this->expires, + 'httpOnly' => $this->httpOnly, + 'secure' => $this->secure, + 'sameSite' => $this->sameSite, + ]; + } +} diff --git a/src/Page/Page.php b/src/Page/Page.php index ca0c4a3..a794b43 100644 --- a/src/Page/Page.php +++ b/src/Page/Page.php @@ -20,6 +20,7 @@ use Playwright\Clock\NullClock; use Playwright\Configuration\PlaywrightConfig; use Playwright\Console\ConsoleMessage; +use Playwright\Cookie; use Playwright\Dialog\Dialog; use Playwright\Event\EventDispatcherInterface; use Playwright\Exception\NetworkException; @@ -673,6 +674,11 @@ public function context(): BrowserContextInterface return $this->context; } + /** + * @param array|null $urls + * + * @return array + */ public function cookies(?array $urls = null): array { $response = $this->sendCommand('cookies', ['urls' => $urls]); @@ -688,38 +694,11 @@ public function cookies(?array $urls = null): array continue; } - $name = $cookie['name'] ?? null; - $value = $cookie['value'] ?? null; - $domain = $cookie['domain'] ?? null; - $path = $cookie['path'] ?? null; - $expires = $cookie['expires'] ?? null; - $httpOnly = $cookie['httpOnly'] ?? null; - $secure = $cookie['secure'] ?? null; - $sameSite = $cookie['sameSite'] ?? null; - - if (!is_string($name) - || !is_string($value) - || !is_string($domain) - || !is_string($path) - || !is_int($expires) - || !is_bool($httpOnly) - || !is_bool($secure) - || !is_string($sameSite) - || !in_array($sameSite, ['Lax', 'None', 'Strict'], true) - ) { + try { + $validatedCookies[] = Cookie::fromArray($cookie)->toArray(); + } catch (\InvalidArgumentException) { continue; } - - $validatedCookies[] = [ - 'name' => $name, - 'value' => $value, - 'domain' => $domain, - 'path' => $path, - 'expires' => $expires, - 'httpOnly' => $httpOnly, - 'secure' => $secure, - 'sameSite' => $sameSite, - ]; } return $validatedCookies; diff --git a/tests/Unit/Browser/StorageStateTest.php b/tests/Unit/Browser/StorageStateTest.php index ae0eccb..b472318 100644 --- a/tests/Unit/Browser/StorageStateTest.php +++ b/tests/Unit/Browser/StorageStateTest.php @@ -82,6 +82,47 @@ public function itCreatesStorageStateWithData(): void $this->assertEquals(1, $storageState->getOriginCount()); } + #[Test] + public function itCreatesStorageStateWithCookieDataWithFloatExpires(): void + { + $cookies = [ + [ + 'name' => 'session_id', + 'value' => 'abc123', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => 12345678.90, + 'httpOnly' => true, + 'secure' => true, + 'sameSite' => 'Strict', + ], + ]; + + $storageState = StorageState::fromArray(['cookies' => $cookies]); + + $this->assertEquals($cookies, $storageState->cookies); + $this->assertFalse($storageState->isEmpty()); + $this->assertEquals(1, $storageState->getCookieCount()); + } + + #[Test] + public function itThrowsExceptionForInvalidCookieData(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid cookie fields'); + + StorageState::fromArray(['cookies' => [['invalid' => 'data']]]); + } + + #[Test] + public function itThrowsExceptionForInvalidCookies(): void + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid cookie data structure'); + + StorageState::fromArray(['cookies' => ['invalid' => 'data']]); + } + #[Test] public function itCreatesFromJson(): void { diff --git a/tests/Unit/CookieTest.php b/tests/Unit/CookieTest.php new file mode 100644 index 0000000..1081fb6 --- /dev/null +++ b/tests/Unit/CookieTest.php @@ -0,0 +1,118 @@ +assertSame('test', $cookie->name); + $this->assertSame('value', $cookie->value); + $this->assertSame('example.com', $cookie->domain); + $this->assertSame('/', $cookie->path); + $this->assertSame($expires, $cookie->expires); + $this->assertTrue($cookie->httpOnly); + $this->assertTrue($cookie->secure); + $this->assertSame('Strict', $cookie->sameSite); + } + + public static function validCookieProvider(): iterable + { + yield 'expires with int' => [ + [ + 'name' => 'test', + 'value' => 'value', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => 3600, + 'httpOnly' => true, + 'secure' => true, + 'sameSite' => 'Strict', + ], + 3600, + ]; + + yield 'expires with float' => [ + [ + 'name' => 'test', + 'value' => 'value', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => 3600.5, + 'httpOnly' => true, + 'secure' => true, + 'sameSite' => 'Strict', + ], + 3600.5, + ]; + } + + #[DataProvider('invalidCookieProvider')] + #[Test] + public function itFailsToCreateFromArrayInvalidCookie(array $cookieData): void + { + $this->expectException(\InvalidArgumentException::class); + + Cookie::fromArray($cookieData); + } + + public static function invalidCookieProvider(): iterable + { + yield 'Invalid cookie data structure' => [['invalid' => 'data']]; + yield 'Invalid cookie expires as random string' => [['name' => 'test', 'value' => 'value', 'domain' => 'example.com', 'path' => '/', 'expires' => 'invalid', 'httpOnly' => true, 'secure' => true, 'sameSite' => 'Strict']]; + yield 'Invalid cookie expires as string' => [['name' => 'test', 'value' => 'value', 'domain' => 'example.com', 'path' => '/', 'expires' => '123', 'httpOnly' => true, 'secure' => true, 'sameSite' => 'Strict']]; + yield 'Invalid cookie sameSite' => [['name' => 'test', 'value' => 'value', 'domain' => 'example.com', 'path' => '/', 'expires' => 123, 'httpOnly' => true, 'secure' => true, 'sameSite' => 'Random']]; + } + + #[Test] + public function itReturnsValidCookieArray(): void + { + $cookie = Cookie::fromArray([ + 'name' => 'test', + 'value' => 'value', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => 3600, + 'httpOnly' => true, + 'secure' => true, + 'sameSite' => 'Strict', + ]); + + $this->assertSame( + [ + 'name' => 'test', + 'value' => 'value', + 'domain' => 'example.com', + 'path' => '/', + 'expires' => 3600, + 'httpOnly' => true, + 'secure' => true, + 'sameSite' => 'Strict', + ], + $cookie->toArray() + ); + } +}