Skip to content

Commit d2f3962

Browse files
committed
wip
1 parent cc41076 commit d2f3962

5 files changed

Lines changed: 530 additions & 15 deletions

File tree

src/Playwright/Concerns/InteractsWithPlaywright.php

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,81 @@ private function processResultResponse(Generator $response): mixed
3333
/** @var array{result: array{value: mixed}} $message */
3434
foreach ($response as $message) {
3535
if (isset($message['result']['value'])) {
36-
return $message['result']['value'];
36+
return $this->parseValue($message['result']['value']);
3737
}
3838
}
3939

4040
return null;
4141
}
4242

43+
/**
44+
* Parse a value from Playwright's serialization format back to PHP value.
45+
* Based on the Ruby ValueParser implementation.
46+
*/
47+
private function parseValue(mixed $value): mixed
48+
{
49+
if ($value === null) {
50+
return null;
51+
}
52+
53+
if (!is_array($value)) {
54+
return $value;
55+
}
56+
57+
// Handle primitive values
58+
if (isset($value['n'])) {
59+
return $value['n']; // number
60+
}
61+
62+
if (isset($value['s'])) {
63+
return $value['s']; // string
64+
}
65+
66+
if (isset($value['b'])) {
67+
return $value['b']; // boolean
68+
}
69+
70+
if (isset($value['v'])) {
71+
return match ($value['v']) {
72+
'undefined', 'null' => null,
73+
'NaN' => NAN,
74+
'Infinity' => INF,
75+
'-Infinity' => -INF,
76+
'-0' => -0.0,
77+
default => $value['v'],
78+
};
79+
}
80+
81+
if (isset($value['bi'])) {
82+
return (int) $value['bi']; // big integer
83+
}
84+
85+
if (isset($value['d'])) {
86+
return new \DateTime($value['d']); // date
87+
}
88+
89+
if (isset($value['a'])) {
90+
// array
91+
$result = [];
92+
foreach ($value['a'] as $item) {
93+
$result[] = $this->parseValue($item);
94+
}
95+
return $result;
96+
}
97+
98+
if (isset($value['o'])) {
99+
// object
100+
$result = [];
101+
foreach ($value['o'] as $obj) {
102+
$result[$obj['k']] = $this->parseValue($obj['v']);
103+
}
104+
return $result;
105+
}
106+
107+
// For unknown formats, return as-is
108+
return $value;
109+
}
110+
43111
/**
44112
* Process response and extract string result
45113
*/

src/Playwright/JSHandle.php

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Pest\Browser\Playwright;
6+
7+
use Pest\Browser\Playwright\Concerns\InteractsWithPlaywright;
8+
9+
/**
10+
* JSHandle represents a handle to a JavaScript object in the browser.
11+
* It can be used to interact with the object or evaluate expressions on it.
12+
*
13+
* @internal
14+
*/
15+
final class JSHandle
16+
{
17+
use InteractsWithPlaywright;
18+
19+
/**
20+
* Constructs new JSHandle
21+
*/
22+
public function __construct(
23+
public string $guid,
24+
) {
25+
//
26+
}
27+
28+
/**
29+
* Evaluate a JavaScript expression on this handle.
30+
*/
31+
public function evaluate(string $pageFunction, mixed $arg = null): mixed
32+
{
33+
$params = [
34+
'expression' => $pageFunction,
35+
];
36+
37+
if ($arg !== null) {
38+
$params['arg'] = $this->serializeArgument($arg);
39+
}
40+
41+
$response = $this->sendMessage('evaluateExpression', $params);
42+
43+
return $this->processResultResponse($response);
44+
}
45+
46+
/**
47+
* Get the JSON representation of this handle's value.
48+
*/
49+
public function jsonValue(): mixed
50+
{
51+
$response = $this->sendMessage('jsonValue');
52+
53+
return $this->processResultResponse($response);
54+
}
55+
56+
/**
57+
* Dispose of this handle.
58+
*/
59+
public function dispose(): void
60+
{
61+
$response = $this->sendMessage('dispose');
62+
$this->processVoidResponse($response);
63+
}
64+
65+
/**
66+
* Get the string representation of this handle.
67+
*/
68+
public function toString(): string
69+
{
70+
return $this->jsonValue();
71+
}
72+
73+
/**
74+
* Serialize arguments for JavaScript evaluation according to Playwright protocol.
75+
* Based on the Ruby ValueSerializer implementation.
76+
*/
77+
private function serializeArgument(mixed $value): array
78+
{
79+
return [
80+
'value' => $this->serializeValue($value),
81+
'handles' => [], // JSHandles would be added here in a full implementation
82+
];
83+
}
84+
85+
/**
86+
* Serialize a value according to Playwright's serialization format.
87+
*/
88+
private function serializeValue(mixed $value): array
89+
{
90+
if ($value === null) {
91+
return ['v' => 'null'];
92+
}
93+
94+
if (is_bool($value)) {
95+
return ['b' => $value];
96+
}
97+
98+
if (is_int($value) || is_float($value)) {
99+
if (is_float($value) && is_nan($value)) {
100+
return ['v' => 'NaN'];
101+
}
102+
if (is_float($value) && is_infinite($value)) {
103+
return ['v' => $value > 0 ? 'Infinity' : '-Infinity'];
104+
}
105+
if (is_int($value) && ($value < -9007199254740992 || $value > 9007199254740991)) {
106+
return ['bi' => (string) $value];
107+
}
108+
109+
return ['n' => $value];
110+
}
111+
112+
if (is_string($value)) {
113+
return ['s' => $value];
114+
}
115+
116+
if (is_array($value)) {
117+
$result = [];
118+
foreach ($value as $item) {
119+
$result[] = $this->serializeValue($item);
120+
}
121+
122+
return ['a' => $result];
123+
}
124+
125+
if (is_object($value)) {
126+
if ($value instanceof \DateTimeImmutable) {
127+
return ['d' => $value->format('c')];
128+
}
129+
130+
// Convert object to associative array
131+
$result = [];
132+
foreach (get_object_vars($value) as $key => $val) {
133+
$result[] = ['k' => $key, 'v' => $this->serializeValue($val)];
134+
}
135+
136+
return ['o' => $result];
137+
}
138+
139+
// Fallback for unsupported types
140+
return ['s' => (string) $value];
141+
}
142+
}

src/Playwright/Page.php

Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Pest\Browser\Playwright;
66

7+
use DateTimeImmutable;
78
use Generator;
89
use Pest\Browser\Playwright\Concerns\InteractsWithPlaywright;
910
use Pest\Browser\ServerManager;
@@ -536,31 +537,29 @@ public function selectOption(
536537
*/
537538
public function evaluate(string $pageFunction, mixed $arg = null): mixed
538539
{
539-
$params = ['expression' => $pageFunction];
540-
541-
if ($arg !== null) {
542-
$params['arg'] = $arg;
543-
}
540+
$params = [
541+
'expression' => $pageFunction,
542+
'arg' => $this->serializeArgument($arg),
543+
];
544544

545-
$response = $this->sendMessage('evaluate', $params);
545+
$response = $this->sendMessage('evaluateExpression', $params);
546546

547547
return $this->processResultResponse($response);
548548
}
549549

550550
/**
551551
* Evaluates a JavaScript expression and returns a JSHandle.
552552
*/
553-
public function evaluateHandle(string $pageFunction, mixed $arg = null): mixed
553+
public function evaluateHandle(string $pageFunction, mixed $arg = null): ?JSHandle
554554
{
555-
$params = ['expression' => $pageFunction];
556-
557-
if ($arg !== null) {
558-
$params['arg'] = $arg;
559-
}
555+
$params = [
556+
'expression' => $pageFunction,
557+
'arg' => $this->serializeArgument($arg),
558+
];
560559

561-
$response = $this->sendMessage('evaluateHandle', $params);
560+
$response = $this->sendMessage('evaluateExpressionHandle', $params);
562561

563-
return $this->processResultResponse($response);
562+
return $this->processJSHandleResponse($response);
564563
}
565564

566565
/**
@@ -628,6 +627,25 @@ private function processNavigationResponse(Generator $response): void
628627
}
629628
}
630629

630+
/**
631+
* Process response to handle JSHandle creation.
632+
*/
633+
private function processJSHandleResponse(Generator $response): ?JSHandle
634+
{
635+
/** @var array{method?: string|null, params: array{type?: string|null, guid?: string}} $message */
636+
foreach ($response as $message) {
637+
if (
638+
isset($message['method'], $message['params']['type'], $message['params']['guid'])
639+
&& $message['method'] === '__create__'
640+
&& $message['params']['type'] === 'JSHandle'
641+
) {
642+
return new JSHandle($message['params']['guid']);
643+
}
644+
}
645+
646+
return null;
647+
}
648+
631649
/**
632650
* Send a message to the frame (for frame-related operations)
633651
*
@@ -657,4 +675,74 @@ private function isPageLevelOperation(string $method): bool
657675

658676
return in_array($method, $pageLevelOperations, true);
659677
}
678+
679+
/**
680+
* Serialize arguments for JavaScript evaluation according to Playwright protocol.
681+
* Based on the Ruby ValueSerializer implementation.
682+
*/
683+
private function serializeArgument(mixed $value): array
684+
{
685+
return [
686+
'value' => $this->serializeValue($value),
687+
'handles' => [], // JSHandles would be added here in a full implementation
688+
];
689+
}
690+
691+
/**
692+
* Serialize a value according to Playwright's serialization format.
693+
*/
694+
private function serializeValue(mixed $value): array
695+
{
696+
if ($value === null) {
697+
return ['v' => 'null'];
698+
}
699+
700+
if (is_bool($value)) {
701+
return ['b' => $value];
702+
}
703+
704+
if (is_int($value) || is_float($value)) {
705+
if (is_float($value) && is_nan($value)) {
706+
return ['v' => 'NaN'];
707+
}
708+
if (is_float($value) && is_infinite($value)) {
709+
return ['v' => $value > 0 ? 'Infinity' : '-Infinity'];
710+
}
711+
if (is_int($value) && ($value < -9007199254740992 || $value > 9007199254740991)) {
712+
return ['bi' => (string) $value];
713+
}
714+
715+
return ['n' => $value];
716+
}
717+
718+
if (is_string($value)) {
719+
return ['s' => $value];
720+
}
721+
722+
if (is_array($value)) {
723+
$result = [];
724+
foreach ($value as $item) {
725+
$result[] = $this->serializeValue($item);
726+
}
727+
728+
return ['a' => $result];
729+
}
730+
731+
if (is_object($value)) {
732+
if ($value instanceof DateTimeImmutable) {
733+
return ['d' => $value->format('c')];
734+
}
735+
736+
// Convert object to associative array
737+
$result = [];
738+
foreach (get_object_vars($value) as $key => $val) {
739+
$result[] = ['k' => $key, 'v' => $this->serializeValue($val)];
740+
}
741+
742+
return ['o' => $result];
743+
}
744+
745+
// Fallback for unsupported types
746+
return ['s' => (string) $value];
747+
}
660748
}

0 commit comments

Comments
 (0)