From dcb42b6a41f82a9450813cd4d432117005ce437d Mon Sep 17 00:00:00 2001 From: Edward Bebbington Date: Thu, 28 Apr 2022 23:10:29 +0100 Subject: [PATCH 1/4] feat: Support waiting for requests --- src/element.ts | 1 + src/page.ts | 31 +++++++++++++++++++++++++++++++ src/protocol.ts | 36 +++++++++++++++++++++++++++++++++--- tests/deps.ts | 1 + tests/server.ts | 35 +++++++++++++++++++++++++++++++++-- tests/unit/page_test.ts | 35 +++++++++++++++++++++++++++++++++++ 6 files changed, 134 insertions(+), 5 deletions(-) diff --git a/src/element.ts b/src/element.ts index 56a9c86a..e5bf2691 100644 --- a/src/element.ts +++ b/src/element.ts @@ -248,6 +248,7 @@ export class Element { middle: 4, }; + console.log("clicking"); await this.#protocol.send("Input.dispatchMouseEvent", { type: "mouseMoved", button: options.button, diff --git a/src/page.ts b/src/page.ts index 05aff67a..34b96d39 100644 --- a/src/page.ts +++ b/src/page.ts @@ -4,6 +4,7 @@ import { Element } from "./element.ts"; import { Protocol as ProtocolClass } from "./protocol.ts"; import { Cookie, ScreenshotOptions } from "./interfaces.ts"; import { Client } from "./client.ts"; +import type { Deferred } from "../deps.ts"; /** * A representation of the page the client is on, allowing the client to action @@ -94,6 +95,36 @@ export class Page { return []; } + public expectWaitForRequest() { + const requestWillBeSendMethod = "Network.requestWillBeSent"; + this.#protocol.notifications.set(requestWillBeSendMethod, deferred()); + } + + public async waitForRequest() { + const params = await this.#protocol.notifications.get( + "Network.requestWillBeSent", + ) as { + requestId: string; + }; + if (!params) { + throw new Error( + `Unable to wait for a request because \`.expectWaitForRequest()\` was not called.`, + ); + } + const { requestId } = params; + const method = "Network.loadingFinished"; + this.#protocol.notifications.set(method, { + params: { + requestId, + }, + promise: deferred(), + }); + const result = this.#protocol.notifications.get(method) as unknown as { + promise: Deferred; + }; + await result.promise; + } + /** * Either get the href/url for the page, or set the location * diff --git a/src/protocol.ts b/src/protocol.ts index e2b9b399..3e151fad 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -40,7 +40,10 @@ export class Protocol { */ public notifications: Map< string, - Deferred> + Deferred> | { + params: Record; + promise: Deferred>; + } > = new Map(); /** @@ -55,6 +58,7 @@ export class Protocol { // Register on message listener this.socket.onmessage = (msg) => { const data = JSON.parse(msg.data); + console.log(data); this.#handleSocketMessage(data); }; } @@ -126,9 +130,35 @@ export class Protocol { } const resolvable = this.notifications.get(message.method); - if (resolvable) { + if (!resolvable) { + return; + } + if ("resolve" in resolvable && "reject" in resolvable) { resolvable.resolve(message.params); } + if ("params" in resolvable && "promise" in resolvable) { + let allMatch = false; + Object.keys(resolvable.params).forEach((paramName) => { + if ( + allMatch === true && + (message.params[paramName] as string | number).toString() !== + (resolvable.params[paramName] as string | number).toString() + ) { + allMatch = false; + return; + } + if ( + (message.params[paramName] as string | number).toString() === + (resolvable.params[paramName] as string | number).toString() + ) { + allMatch = true; + } + }); + if (allMatch) { + resolvable.promise.resolve(message.params); + } + return; + } } } @@ -151,7 +181,7 @@ export class Protocol { if (getFrameId) { protocol.notifications.set("Runtime.executionContextCreated", deferred()); } - for (const method of ["Page", "Log", "Runtime"]) { + for (const method of ["Page", "Log", "Runtime", "Network"]) { await protocol.send(`${method}.enable`); } if (getFrameId) { diff --git a/tests/deps.ts b/tests/deps.ts index 555492e4..0113ce55 100644 --- a/tests/deps.ts +++ b/tests/deps.ts @@ -1 +1,2 @@ export * as Drash from "https://deno.land/x/drash@v2.5.4/mod.ts"; +export { delay } from "https://deno.land/std@0.126.0/async/delay.ts"; diff --git a/tests/server.ts b/tests/server.ts index 66764d8b..39ca166d 100644 --- a/tests/server.ts +++ b/tests/server.ts @@ -1,4 +1,4 @@ -import { Drash } from "./deps.ts"; +import { delay, Drash } from "./deps.ts"; class HomeResource extends Drash.Resource { public paths = ["/"]; @@ -23,8 +23,39 @@ class PopupsResource extends Drash.Resource { } } +class WaitForRequestsResource extends Drash.Resource { + public paths = ["/wait-for-requests"]; + + public GET(_r: Drash.Request, res: Drash.Response) { + return res.html(` +
+ +
+ + + `); + } + + public async POST(_r: Drash.Request, res: Drash.Response) { + await delay(2000); + return res.text("Done!!"); + } +} + export const server = new Drash.Server({ - resources: [HomeResource, JSResource, PopupsResource], + resources: [ + HomeResource, + JSResource, + PopupsResource, + WaitForRequestsResource, + ], protocol: "http", port: 1447, hostname: "localhost", diff --git a/tests/unit/page_test.ts b/tests/unit/page_test.ts index 792c0e09..746b4c4a 100644 --- a/tests/unit/page_test.ts +++ b/tests/unit/page_test.ts @@ -226,5 +226,40 @@ for (const browserItem of browserList) { assertEquals(page.socket.readyState, page.socket.CLOSED); }); }); + + await t.step("waitForRequest()", async (t) => { + await t.step(`Should wait for a request via JS`, async () => { + const { browser, page } = await buildFor(browserItem.name); + server.run(); + await page.location(server.address + "/wait-for-requests"); + const elem = await page.querySelector("#second-button"); + page.expectWaitForRequest(); + await elem.click(); + await page.waitForRequest(); + const value = await page.evaluate( + `document.querySelector("#second-button").textContent`, + ); + await page.close(); + await browser.close(); + await server.close(); + assertEquals(value, "done"); + }); + await t.step(`Should wait for a request via inline forms`, async () => { + const { browser, page } = await buildFor(browserItem.name); + server.run(); + await page.location(server.address + "/wait-for-requests"); + const elem = await page.querySelector(`button[type="submit"]`); + page.expectWaitForRequest(); + await elem.click(); + await page.waitForRequest(); + const value = await page.evaluate(() => { + return document.body.innerText; + }); + await page.close(); + await browser.close(); + await server.close(); + assertEquals(value, "Done!!"); + }); + }); }); } From c96b2c3cd3804f2598858102f795841c01c65724 Mon Sep 17 00:00:00 2001 From: Edward Bebbington Date: Thu, 28 Apr 2022 23:12:24 +0100 Subject: [PATCH 2/4] docblocks --- src/page.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/page.ts b/src/page.ts index 34b96d39..e8ec7cfb 100644 --- a/src/page.ts +++ b/src/page.ts @@ -95,11 +95,23 @@ export class Page { return []; } + /** + * Tell Sinco that you will be expecting to wait for a request + */ public expectWaitForRequest() { const requestWillBeSendMethod = "Network.requestWillBeSent"; this.#protocol.notifications.set(requestWillBeSendMethod, deferred()); } + /** + * Wait for a request to finish loading. + * + * Can be used to wait for: + * - Clicking a button that (via JS) will send a HTTO request via axios/fetch etc + * - Submitting an inline form + * - ... and many others + * + */ public async waitForRequest() { const params = await this.#protocol.notifications.get( "Network.requestWillBeSent", From 1a49bc7d11559ad7770b6e2ba0bf241143152660 Mon Sep 17 00:00:00 2001 From: Edward Bebbington Date: Thu, 28 Apr 2022 23:14:07 +0100 Subject: [PATCH 3/4] rm logging --- src/element.ts | 1 - src/protocol.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/src/element.ts b/src/element.ts index e5bf2691..56a9c86a 100644 --- a/src/element.ts +++ b/src/element.ts @@ -248,7 +248,6 @@ export class Element { middle: 4, }; - console.log("clicking"); await this.#protocol.send("Input.dispatchMouseEvent", { type: "mouseMoved", button: options.button, diff --git a/src/protocol.ts b/src/protocol.ts index 3e151fad..d41f8449 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -58,7 +58,6 @@ export class Protocol { // Register on message listener this.socket.onmessage = (msg) => { const data = JSON.parse(msg.data); - console.log(data); this.#handleSocketMessage(data); }; } From 20b46162c5693d7391170e1638f08505bd8523d5 Mon Sep 17 00:00:00 2001 From: Edward Bebbington Date: Thu, 28 Apr 2022 23:35:50 +0100 Subject: [PATCH 4/4] fmt --- src/page.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/page.ts b/src/page.ts index e8ec7cfb..833e64aa 100644 --- a/src/page.ts +++ b/src/page.ts @@ -105,12 +105,11 @@ export class Page { /** * Wait for a request to finish loading. - * + * * Can be used to wait for: * - Clicking a button that (via JS) will send a HTTO request via axios/fetch etc * - Submitting an inline form * - ... and many others - * */ public async waitForRequest() { const params = await this.#protocol.notifications.get(