Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support waiting for requests #126

Merged
merged 4 commits into from
May 30, 2022
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
42 changes: 42 additions & 0 deletions src/page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -94,6 +95,47 @@ 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",
) 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<never>;
};
await result.promise;
}

/**
* Either get the href/url for the page, or set the location
*
Expand Down
35 changes: 32 additions & 3 deletions src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ export class Protocol {
*/
public notifications: Map<
string,
Deferred<Record<string, unknown>>
Deferred<Record<string, unknown>> | {
params: Record<string, unknown>;
promise: Deferred<Record<string, unknown>>;
}
> = new Map();

/**
Expand Down Expand Up @@ -126,9 +129,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;
}
}
}

Expand All @@ -151,7 +180,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) {
Expand Down
1 change: 1 addition & 0 deletions tests/deps.ts
Original file line number Diff line number Diff line change
@@ -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";
35 changes: 33 additions & 2 deletions tests/server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Drash } from "./deps.ts";
import { delay, Drash } from "./deps.ts";

class HomeResource extends Drash.Resource {
public paths = ["/"];
Expand All @@ -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(`
<form action="/wait-for-requests" method="POST">
<button type="submit">Click</button>
</form>
<button id="second-button" type="button">Click</button>
<script>
document.getElementById("second-button").addEventListener('click', async e => {
await fetch("/wait-for-requests", {
method: "POST",
})
e.target.textContent = "done";
})
</script>
`);
}

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",
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/page_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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!!");
});
});
});
}