From 9825eb279273a4ad7beece2430d6a017fe056c6b Mon Sep 17 00:00:00 2001 From: Joseph Bielawski Date: Fri, 13 Feb 2026 12:28:39 +0100 Subject: [PATCH] Fix `page.waitForLoadState` handling --- bin/lib/handlers.js | 99 +++++++++++++++-------------- tests/Integration/Page/PageTest.php | 28 ++++---- 2 files changed, 67 insertions(+), 60 deletions(-) diff --git a/bin/lib/handlers.js b/bin/lib/handlers.js index e39f72f..9bb5ee6 100644 --- a/bin/lib/handlers.js +++ b/bin/lib/handlers.js @@ -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); @@ -148,13 +148,13 @@ 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, @@ -162,10 +162,10 @@ class ContextHandler extends BaseHandler { 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, { @@ -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 }; } @@ -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) @@ -370,13 +371,13 @@ 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, @@ -384,10 +385,10 @@ class PageHandler extends BaseHandler { 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, { @@ -396,41 +397,41 @@ 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', { @@ -438,16 +439,16 @@ class PageHandler extends BaseHandler { 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 }; } @@ -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; } @@ -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) }); diff --git a/tests/Integration/Page/PageTest.php b/tests/Integration/Page/PageTest.php index 8709c30..a0d31ac 100644 --- a/tests/Integration/Page/PageTest.php +++ b/tests/Integration/Page/PageTest.php @@ -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; @@ -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); } @@ -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); } @@ -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); } @@ -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); } @@ -199,7 +210,7 @@ public function itCanGetByAltText(): void { $this->page->setContent('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); } @@ -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; - } }