diff --git a/bin/lib/core.js b/bin/lib/core.js index ae77ed7..641d0a3 100644 --- a/bin/lib/core.js +++ b/bin/lib/core.js @@ -167,6 +167,15 @@ class CommandRegistry { } } +class NavigationRedirects extends Map { + #waiters = new Map(); + fulfilled(pageId) { return new Promise(resolve => this.#waiters.set(pageId, resolve)); } + markFulfilled(pageId) { + this.#waiters.get(pageId)?.(); + this.#waiters.delete(pageId); + } +} + class BaseHandler { constructor(deps = {}) { Object.assign(this, deps); } validateResource(resourceMap, resourceId, resourceType) { @@ -190,7 +199,7 @@ class BaseHandler { return { handleId }; } async executeWithRegistry(registry, method, ...args) { return await registry.execute(method, ...args); } - async followNavigationRedirects(pageId, page, action) { + async followNavigationRedirects(pageId, page, action, settlesOnRedirect = false) { let nextAction = action; let redirectCount = 0; let result; @@ -199,7 +208,10 @@ class BaseHandler { this.navigationRedirects.delete(pageId); try { - result = await nextAction(); + // from Playwright 1.64 only goto() settles on the redirect's 204; a goto() to the same url before it does fails with net::ERR_ABORTED + result = await (settlesOnRedirect + ? nextAction() + : Promise.race([nextAction(), this.navigationRedirects.fulfilled(pageId)])); } catch (error) { if (!this.navigationRedirects.has(pageId)) throw error; } @@ -212,6 +224,7 @@ class BaseHandler { throw new Error(`Too many navigation redirects for page ${pageId}`); } nextAction = () => page.goto(target); + settlesOnRedirect = true; } } } @@ -330,5 +343,5 @@ const logger = new Logger(); module.exports = { logger, ErrorHandler, LspFraming, sendFramedResponse, - CommandRegistry, BaseHandler, PromiseUtils, FrameUtils, RouteUtils, + CommandRegistry, NavigationRedirects, BaseHandler, PromiseUtils, FrameUtils, RouteUtils, }; diff --git a/bin/lib/handlers.js b/bin/lib/handlers.js index 6210335..68f6016 100644 --- a/bin/lib/handlers.js +++ b/bin/lib/handlers.js @@ -440,7 +440,8 @@ class PageHandler extends BaseHandler { const gotoResponse = await this.followNavigationRedirects( command.pageId, page, - () => page.goto(command.url, command.options) + () => page.goto(command.url, command.options), + true ); return { response: this.serializeResponse(gotoResponse) }; } diff --git a/bin/playwright-server.js b/bin/playwright-server.js index 05fde22..561abf6 100644 --- a/bin/playwright-server.js +++ b/bin/playwright-server.js @@ -1,5 +1,5 @@ const {chromium, firefox, webkit, request} = require('playwright'); -const { logger, ErrorHandler, LspFraming, sendFramedResponse, CommandRegistry, BaseHandler } = require('./lib/core'); +const { logger, ErrorHandler, LspFraming, sendFramedResponse, CommandRegistry, NavigationRedirects, BaseHandler } = require('./lib/core'); const { APIRequestHandler, ContextHandler, PageHandler, LocatorHandler, InteractionHandler, FrameHandler, JSHandleHandler, SelectorsHandler, VideoHandler } = require('./lib/handlers'); const { globalCoordinator } = require('./lib/coordination'); @@ -20,7 +20,7 @@ class PlaywrightServer extends BaseHandler { this.dialogs = new Map(); this.elementHandles = new Map(); this.contextThrottling = new Map(); - this.navigationRedirects = new Map(); + this.navigationRedirects = new NavigationRedirects(); this.servers = new Map(); this.videos = new Map(); this.apiContexts = new Map(); @@ -148,6 +148,7 @@ class PlaywrightServer extends BaseHandler { if (!this.pages.has(info.contextId)) throw new Error('Navigation redirects require a page route'); this.navigationRedirects.set(info.contextId, command.url); await route.fulfill({ status: 204, headers: command.options?.headers }); + this.navigationRedirects.markFulfilled(info.contextId); } async continueRoute(route, info, command) { diff --git a/tests/Functional/Network/RouteTest.php b/tests/Functional/Network/RouteTest.php index 382850e..ea43baa 100644 --- a/tests/Functional/Network/RouteTest.php +++ b/tests/Functional/Network/RouteTest.php @@ -221,6 +221,20 @@ public function testCanRedirectNavigationRequestAfterClick(): void $this->assertSame($baseUrl.'/page-2.html', $this->page->url()); } + public function testCanRedirectNavigationRequestAfterFormSubmit(): void + { + $baseUrl = $this->getBaseUrl(); + $this->goto('/index.html'); + $this->page->evaluate("document.body.innerHTML = '
'"); + $this->page->route('**/login', static function (Route $route) use ($baseUrl): void { + $route->redirectNavigationRequest($baseUrl.'/page-2.html'); + }); + + $this->page->locator('button')->click(); + + $this->assertSame($baseUrl.'/page-2.html', $this->page->url()); + } + public function testCanRedirectNavigationRequestAfterReload(): void { $baseUrl = $this->getBaseUrl();