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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 6 additions & 36 deletions src/Browser/StorageState.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

namespace Playwright\Browser;

use Playwright\Cookie;
use Playwright\Exception\RuntimeException;

/**
Expand All @@ -22,8 +23,8 @@
final readonly class StorageState
{
/**
* @param array<array{name: string, value: string, domain: string, path: string, expires: int, httpOnly: bool, secure: bool, sameSite: 'Strict'|'Lax'|'None'}> $cookies
* @param array<array{origin: string, localStorage?: array<array{name: string, value: string}>}> $origins
* @param array<array{name: string, value: string, domain: string, path: string, expires: int|float, httpOnly: bool, secure: bool, sameSite: 'Strict'|'Lax'|'None'}> $cookies
* @param array<array{origin: string, localStorage?: array<array{name: string, value: string}>}> $origins
*/
public function __construct(
public array $cookies = [],
Expand Down Expand Up @@ -140,7 +141,7 @@ public function getOriginCount(): int
}

/**
* @return array<array{name: string, value: string, domain: string, path: string, expires: int, httpOnly: bool, secure: bool, sameSite: 'Strict'|'Lax'|'None'}>
* @return array<array{name: string, value: string, domain: string, path: string, expires: int|float, httpOnly: bool, secure: bool, sameSite: 'Strict'|'Lax'|'None'}>
*/
public function getCookiesForDomain(string $domain): array
{
Expand All @@ -164,7 +165,7 @@ public function getLocalStorageForOrigin(string $origin): array
/**
* @param array<mixed, mixed> $cookies
*
* @return array<array{name: string, value: string, domain: string, path: string, expires: int, httpOnly: bool, secure: bool, sameSite: 'Lax'|'None'|'Strict'}>
* @return array<array{name: string, value: string, domain: string, path: string, expires: int|float, httpOnly: bool, secure: bool, sameSite: 'Lax'|'None'|'Strict'}>
*/
private static function validateCookies(array $cookies): array
{
Expand All @@ -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;
Expand Down
84 changes: 84 additions & 0 deletions src/Cookie.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

/*
* This file is part of the community-maintained Playwright PHP project.
* It is not affiliated with or endorsed by Microsoft.
*
* (c) 2025-Present - Playwright PHP - https://github.com/playwright-php
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Playwright;

/**
* @internal
*/
final class Cookie
{
/**
* @param 'Strict'|'Lax'|'None' $sameSite
*/
private function __construct(
public string $name,
public string $value,
public string $domain,
public string $path,
public int|float $expires,
public bool $httpOnly,
public bool $secure,
public string $sameSite,
) {
}

/**
* @param array<mixed, mixed> $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,
];
}
}
39 changes: 9 additions & 30 deletions src/Page/Page.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -673,6 +674,11 @@ public function context(): BrowserContextInterface
return $this->context;
}

/**
* @param array<string>|null $urls
*
* @return array<array{name: string, value: string, domain: string, path: string, expires: int|float, httpOnly: bool, secure: bool, sameSite: 'Strict'|'Lax'|'None'}>
*/
public function cookies(?array $urls = null): array
{
$response = $this->sendCommand('cookies', ['urls' => $urls]);
Expand All @@ -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;
Expand Down
41 changes: 41 additions & 0 deletions tests/Unit/Browser/StorageStateTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
118 changes: 118 additions & 0 deletions tests/Unit/CookieTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

declare(strict_types=1);

/*
* This file is part of the community-maintained Playwright PHP project.
* It is not affiliated with or endorsed by Microsoft.
*
* (c) 2025-Present - Playwright PHP - https://github.com/playwright-php
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Playwright\Tests\Unit;

use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Playwright\Cookie;

#[CoversClass(Cookie::class)]
final class CookieTest extends TestCase
{
#[DataProvider('validCookieProvider')]
#[Test]
public function itCreatesFromArrayValidCookie(array $cookieData, int|float $expires): void
{
$cookie = Cookie::fromArray($cookieData);

$this->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()
);
}
}