Skip to content
Merged
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
99 changes: 50 additions & 49 deletions bin/lib/handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class ContextHandler extends BaseHandler {

async handleClock(command, method) {
const context = this.validateResource(this.contexts, command.contextId, 'Context')?.context;

const registry = CommandRegistry.create({
install: async () => {
await context.clock.install(command.options);
Expand Down Expand Up @@ -148,24 +148,24 @@ class ContextHandler extends BaseHandler {
async waitForPopup(context, command) {
const timeout = command.timeout || 30000;
const requestId = command.requestId || this.generateId('popup_req');
logger.info('Starting context popup coordination', {
contextId: command.contextId,
timeout,
requestId

logger.info('Starting context popup coordination', {
contextId: command.contextId,
timeout,
requestId
});

// Create coordination phases
const phases = PopupCoordinator.createPopupPhases('context', {
pages: this.pages,
pageContexts: this.pageContexts,
setupPageEventListeners: this.setupPageEventListeners?.bind(this),
generateId: this.generateId.bind(this)
});

// Register the async command
globalCoordinator.registerAsyncCommand(requestId, phases);

try {
// Start execution with initial data
const result = await globalCoordinator.executeNextPhase(requestId, {
Expand All @@ -174,29 +174,29 @@ class ContextHandler extends BaseHandler {
timeout,
requestId
});

if (result.type === 'callback') {
// Command is waiting for callback - this is expected for popup coordination
logger.debug('Context popup coordination waiting for callback', { requestId, callbackType: result.callbackType });
return result;
}

if (result.completed) {
const validation = PopupCoordinator.validatePopupResult(result.result);
if (!validation.valid) {
logger.error('Invalid popup result', { requestId, error: validation.error });
return { popupPageId: null };
}

return result.result;
}

return { popupPageId: null };

} catch (error) {
logger.error('Context popup coordination failed', {
requestId,
error: error.message
logger.error('Context popup coordination failed', {
requestId,
error: error.message
});
return { popupPageId: null };
}
Expand Down Expand Up @@ -232,6 +232,7 @@ class PageHandler extends BaseHandler {
goBack: () => page.goBack(command.options),
goForward: () => page.goForward(command.options),
reload: () => page.reload(command.options),
waitForLoadState: () => page.waitForLoadState(command.state || 'load', command.options),
frames: () => this.getFrames(page),
frame: () => this.getFrame(page, command),
waitForPopup: () => this.waitForPopup(page, command)
Expand Down Expand Up @@ -370,24 +371,24 @@ class PageHandler extends BaseHandler {
async waitForPopup(page, command) {
const timeout = command.timeout || 30000;
const requestId = command.requestId || this.generateId('popup_req');
logger.info('Starting page popup coordination', {
pageId: command.pageId,
timeout,
requestId

logger.info('Starting page popup coordination', {
pageId: command.pageId,
timeout,
requestId
});

// Create coordination phases
const phases = PopupCoordinator.createPopupPhases('page', {
pages: this.pages,
pageContexts: this.pageContexts,
setupPageEventListeners: this.setupPageEventListeners?.bind(this),
generateId: this.generateId.bind(this)
});

// Register the async command
globalCoordinator.registerAsyncCommand(requestId, phases);

try {
// Start execution with initial data
const result = await globalCoordinator.executeNextPhase(requestId, {
Expand All @@ -396,58 +397,58 @@ class PageHandler extends BaseHandler {
timeout,
requestId
});

if (result.type === 'callback') {
// Command is waiting for callback - this is expected for popup coordination
logger.debug('Page popup coordination waiting for callback', { requestId, callbackType: result.callbackType });
return result;
}

if (result.completed) {
const validation = PopupCoordinator.validatePopupResult(result.result);
if (!validation.valid) {
logger.error('Invalid popup result', { requestId, error: validation.error });
return { popupPageId: null };
}

// Ensure popup page is registered in main pages Map
const popupPageId = result.result.popupPageId;
const popup = result.result.popup;

if (popup && !this.pages.has(popupPageId)) {
// Re-register the popup page in the main pages Map
this.pages.set(popupPageId, popup);

// Set up context mapping
const contextId = this.pageContexts.get(command.pageId);
if (contextId) {
this.pageContexts.set(popupPageId, contextId);
}

logger.debug('Re-registered popup page in main pages Map', {
popupPageId,
contextId,
totalPages: this.pages.size
});
}

// Verify registration before returning
const isRegistered = this.pages.has(popupPageId);
logger.info('Page popup coordination completed', {
popupPageId,
isRegistered,
totalPages: this.pages.size
});

return result.result;
}

return { popupPageId: null };

} catch (error) {
logger.error('Page popup coordination failed', {
requestId,
error: error.message
logger.error('Page popup coordination failed', {
requestId,
error: error.message
});
return { popupPageId: null };
}
Expand Down Expand Up @@ -532,25 +533,25 @@ class LocatorHandler extends BaseHandler {
}

async handleDragAndDrop(page, command) {
logger.debug('Handling drag and drop', {
selector: command.selector,
target: command.target,
options: command.options
logger.debug('Handling drag and drop', {
selector: command.selector,
target: command.target,
options: command.options
});

try {
// Use page.dragAndDrop which is the native Playwright method
await page.dragAndDrop(command.selector, command.target, {
strict: true,
...command.options
});

return this.createValueResult(true);
} catch (error) {
logger.error('Drag and drop failed', {
selector: command.selector,
target: command.target,
error: error.message
logger.error('Drag and drop failed', {
selector: command.selector,
target: command.target,
error: error.message
});
throw error;
}
Expand Down Expand Up @@ -603,7 +604,7 @@ class FrameHandler extends BaseHandler {
name: () => evalInFrame(() => window.name || '').then(v => this.createValueResult(v ?? '')),
url: () => evalInFrame(() => document.location.href).then(v => this.createValueResult(v ?? '')),
isDetached: () => this.checkDetached(isMainFrame, frameLocator),
waitForLoadState: () => this.waitForLoadState(page, frameLocator, isMainFrame, command),
waitForLoadState: () => isMainFrame ? page.waitForLoadState(command.state || 'load', command.options) : this.waitForLoadState(page, frameLocator, isMainFrame, command),
parent: () => this.getParent(isMainFrame, command.frameSelector),
children: () => this.getChildren(page, frameLocator, isMainFrame, command.frameSelector)
});
Expand Down
28 changes: 17 additions & 11 deletions tests/Integration/Page/PageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Playwright\Locator\LocatorInterface;
use Playwright\Network\ResponseInterface;
use Playwright\Page\Page;
use Playwright\Testing\PlaywrightTestCaseTrait;
use Playwright\Tests\Support\RouteServerTestTrait;
Expand Down Expand Up @@ -151,16 +153,25 @@ public function itWaitsForAResponse(): void
{
$response = $this->page->waitForResponse('**/page2.html', ['action' => "document.querySelector('a').click()"]);

$this->assertInstanceOf(\Playwright\Network\ResponseInterface::class, $response);
$this->assertInstanceOf(ResponseInterface::class, $response);
$this->assertStringContainsString('/page2.html', $response->url());
$this->assertEquals(200, $response->status());
}

#[Test]
public function itWaitsForLoadState(): void
{
$this->page->click('a');
$this->page->waitForLoadState();

$this->assertStringContainsString('/page2.html', $this->page->url());
}

#[Test]
public function itCanGetByText(): void
{
$locator = $this->page->getByText('Hello World');
$this->assertInstanceOf(\Playwright\Locator\LocatorInterface::class, $locator);
$this->assertInstanceOf(LocatorInterface::class, $locator);
$text = $locator->textContent();
$this->assertSame('Hello World', $text);
}
Expand All @@ -169,7 +180,7 @@ public function itCanGetByText(): void
public function itCanGetByPlaceholder(): void
{
$locator = $this->page->getByPlaceholder('Username');
$this->assertInstanceOf(\Playwright\Locator\LocatorInterface::class, $locator);
$this->assertInstanceOf(LocatorInterface::class, $locator);
$placeholder = $locator->getAttribute('placeholder');
$this->assertSame('Username', $placeholder);
}
Expand All @@ -179,7 +190,7 @@ public function itCanGetByTitle(): void
{
$this->page->evaluate("document.querySelector('h1').setAttribute('title', 'Main Heading')");
$locator = $this->page->getByTitle('Main Heading');
$this->assertInstanceOf(\Playwright\Locator\LocatorInterface::class, $locator);
$this->assertInstanceOf(LocatorInterface::class, $locator);
$text = $locator->textContent();
$this->assertSame('Hello World', $text);
}
Expand All @@ -189,7 +200,7 @@ public function itCanGetByTestId(): void
{
$this->page->evaluate("document.querySelector('button').setAttribute('data-testid', 'submit-button')");
$locator = $this->page->getByTestId('submit-button');
$this->assertInstanceOf(\Playwright\Locator\LocatorInterface::class, $locator);
$this->assertInstanceOf(LocatorInterface::class, $locator);
$text = $locator->textContent();
$this->assertSame('Test Button', $text);
}
Expand All @@ -199,7 +210,7 @@ public function itCanGetByAltText(): void
{
$this->page->setContent('<img src="/logo.png" alt="Company Logo" />');
$locator = $this->page->getByAltText('Company Logo');
$this->assertInstanceOf(\Playwright\Locator\LocatorInterface::class, $locator);
$this->assertInstanceOf(LocatorInterface::class, $locator);
$alt = $locator->getAttribute('alt');
$this->assertSame('Company Logo', $alt);
}
Expand All @@ -218,9 +229,4 @@ public function itCanFillInputUsingGetByPlaceholder(): void
$value = $this->page->getByPlaceholder('Username')->inputValue();
$this->assertSame('testuser', $value);
}

private static function findFreePort(): int
{
return 0;
}
}