Skip to content

Commit

Permalink
Replace assertNoConsoleErrors with consoleErrors
Browse files Browse the repository at this point in the history
  • Loading branch information
ebebbington committed Apr 30, 2024
1 parent b49f838 commit 72949ea
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 109 deletions.
1 change: 0 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Protocol } from "https://unpkg.com/[email protected]/ty
export { Protocol };
export {
assertEquals,
AssertionError,
assertNotEquals,
} from "https://deno.land/[email protected]/testing/asserts.ts";
export { deferred } from "https://deno.land/[email protected]/async/deferred.ts";
Expand Down
89 changes: 40 additions & 49 deletions src/page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AssertionError, deferred, Protocol } from "../deps.ts";
import { deferred, Protocol } from "../deps.ts";
import { existsSync, generateTimestamp } from "./utility.ts";
import { Element } from "./element.ts";
import { Protocol as ProtocolClass } from "./protocol.ts";
Expand Down Expand Up @@ -31,6 +31,8 @@ export class Page {

readonly client: Client;

#console_errors: string[] = [];

constructor(
protocol: ProtocolClass,
targetId: string,
Expand All @@ -41,6 +43,35 @@ export class Page {
this.target_id = targetId;
this.client = client;
this.#frame_id = frameId;

const onError = (event: Event) => {
this.#console_errors.push((event as CustomEvent<string>).detail);
};

addEventListener("Log.entryAdded", onError);
addEventListener("Runtime.exceptionThrow", onError);
}

/**
* @example
* ```ts
* const waitForNewPage = page.waitFor<ProtocolTypes.Page.WindowOpen>("Page.windowOpen");
* await elem.click();
* await waitForNewPage
* const page2 = browser.page(2)
* ```
*
* @param methodName
*/
public async waitFor<T>(methodName: string): Promise<T> {
const p = deferred();
const listener = (event: Event) => {
p.resolve((event as CustomEvent<T>).detail);
};
addEventListener(methodName, listener);
const result = await p as T;
removeEventListener(methodName, listener);
return result;
}

public get socket() {
Expand Down Expand Up @@ -383,56 +414,16 @@ export class Page {
}

/**
* Assert that there are no errors in the developer console, such as:
* - 404's (favicon for example)
* - Issues with JavaScript files
* - etc
*
* @param exceptions - A list of strings that if matched, will be ignored such as ["favicon.ico"] if you want/need to ignore a 404 error for this file
*
* @throws AssertionError
* Return the current list of console errors present in the dev tools
*/
public async assertNoConsoleErrors(exceptions: string[] = []) {
const forMessages = deferred();
let notifCount = 0;
// deno-lint-ignore no-this-alias
const self = this;
const interval = setInterval(function () {
const notifs = self.#protocol.console_errors;
// If stored notifs is greater than what we've got, then
// more notifs are being sent to us, so wait again
if (notifs.length > notifCount) {
notifCount = notifs.length;
return;
}
// Otherwise, we have not gotten anymore notifs in the last .5s
clearInterval(interval);
forMessages.resolve();
public async consoleErrors(): Promise<string[]> {
// Give it some extra time in case to pick up some more
const p = deferred();
setTimeout(() => {
p.resolve();
}, 500);
await forMessages;
const errorNotifs = this.#protocol.console_errors;
const filteredNotifs = !exceptions.length
? errorNotifs
: errorNotifs.filter((notif) => {
const notifCanBeIgnored = exceptions.find((exception) => {
if (notif.includes(exception)) {
return true;
}
return false;
});
if (notifCanBeIgnored) {
return false;
}
return true;
});
if (!filteredNotifs.length) {
return;
}
await this.client.close(
"Expected console to show no errors. Instead got:\n" +
filteredNotifs.join("\n"),
AssertionError,
);
await p;
return this.#console_errors;
}

/**
Expand Down
36 changes: 21 additions & 15 deletions src/protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,14 @@ export class Protocol {
}
> = new Map();

/**
* Map of notifications, where the key is the method and the value is an array of the events
*/
public console_errors: string[] = [];

constructor(
socket: WebSocket,
) {
this.socket = socket;
// Register on message listener
this.socket.onmessage = (msg) => {
const data = JSON.parse(msg.data);
console.log(data);
this.#handleSocketMessage(data);
};
}
Expand Down Expand Up @@ -94,9 +90,10 @@ export class Protocol {
#handleSocketMessage(
message: MessageResponse | NotificationResponse,
) {
// TODO :: make it unique eg `<frame-id>.message` so say another page instance wont pick up events for the wrong websocket
dispatchEvent(new CustomEvent("message", { detail: message }));
if ("id" in message) { // message response
// TODO :: make it unique eg `<frame-id>.message` so say another page instance wont pick up events for the wrong websocket
dispatchEvent(new CustomEvent("message", { detail: message }));

const resolvable = this.#messages.get(message.id);
if (!resolvable) {
return;
Expand All @@ -109,24 +106,33 @@ export class Protocol {
}
}
if ("method" in message) { // Notification response
// Store certain methods for if we need to query them later
dispatchEvent(
new CustomEvent(message.method, {
detail: message.params,
}),
);

// Handle console errors
if (message.method === "Runtime.exceptionThrown") {
const params = message
.params as unknown as ProtocolTypes.Runtime.ExceptionThrownEvent;
const errorMessage = params.exceptionDetails.exception?.description ??
params.exceptionDetails.text;
if (errorMessage) {
this.console_errors.push(errorMessage);
}
dispatchEvent(
new CustomEvent("consoleError", {
detail: errorMessage,
}),
);
}
if (message.method === "Log.entryAdded") {
const params = message
.params as unknown as ProtocolTypes.Log.EntryAddedEvent;
if (params.entry.level === "error") {
const errorMessage = params.entry.text;
if (errorMessage) {
this.console_errors.push(errorMessage);
}
dispatchEvent(
new CustomEvent("consoleError", {
detail: params.entry.text,
}),
);
}
}

Expand Down
14 changes: 14 additions & 0 deletions tests/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,19 @@ class DialogsResource extends Drash.Resource {
}
}

class DownloadResource extends Drash.Resource {
public paths = ["/downloads", "/downloads/download"];

public GET(r: Drash.Request, res: Drash.Response) {
if (r.url.includes("downloads/download")) {
return res.download("./mod.ts", "application/typescript");
}
return res.html(`
<a target="blank" href="/downloads/download">Download</a>
`);
}
}

class InputResource extends Drash.Resource {
public paths = ["/input"];

Expand Down Expand Up @@ -89,6 +102,7 @@ export const server = new Drash.Server({
WaitForRequestsResource,
InputResource,
DialogsResource,
DownloadResource,
],
protocol: "http",
port: 1447,
Expand Down
6 changes: 6 additions & 0 deletions tests/unit/element_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ const serverAdd = `http://${
for (const browserItem of browserList) {
Deno.test(browserItem.name, async (t) => {
await t.step("click()", async (t) => {
await t.step(
"Can handle things like downloads opening new tab then closing",
async () => {
},
);

await t.step(
"It should allow clicking of elements and update location",
async () => {
Expand Down
55 changes: 11 additions & 44 deletions tests/unit/page_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,6 @@ for (const browserItem of browserList) {
});

await t.step("location()", async (t) => {
// TODO
await t.step(
"Handles correctly and doesnt hang when invalid URL",
async () => {
Expand Down Expand Up @@ -217,71 +216,39 @@ for (const browserItem of browserList) {
});

await t.step({
name: "assertNoConsoleErrors()",
name: "consoleErrors()",
fn: async (t) => {
await t.step(`Should throw when errors`, async () => {
server.run();
const { browser, page } = await buildFor(browserItem.name, {
remote,
});
// I (ed) knows this page shows errors, but if we ever need to change it in the future,
// can always spin up a drash web app and add errors in the js to produce console errors
await page.location(
serverAdd,
);
let errMsg = "";
try {
await page.assertNoConsoleErrors();
} catch (e) {
errMsg = e.message;
}
const errors = await page.consoleErrors();
await browser.close();
await server.close();
assertEquals(
errMsg.startsWith(
`Expected console to show no errors. Instead got:\n`,
),
true,
errors,
[
"Failed to load resource: the server responded with a status of 404 (Not Found)",
"ReferenceError: callUser is not defined\n" +
` at ${serverAdd}/index.js:1:1`,
],
);
assertEquals(errMsg.includes("Not Found"), true);
assertEquals(errMsg.includes("callUser"), true);
});

await t.step(`Should not throw when no errors`, async () => {
await t.step(`Should be empty if no errors`, async () => {
const { browser, page } = await buildFor(browserItem.name, {
remote,
});
await page.location(
"https://drash.land",
);
await page.assertNoConsoleErrors();
const errors = await page.consoleErrors();
await browser.close();
});

await t.step(` Should exclude messages`, async () => {
server.run();
const { browser, page } = await buildFor(browserItem.name, {
remote,
});
await page.location(
serverAdd,
);
let errMsg = "";
try {
await page.assertNoConsoleErrors(["callUser"]);
} catch (e) {
errMsg = e.message;
}
await server.close();
await browser.close();
assertEquals(
errMsg.startsWith(
"Expected console to show no errors. Instead got",
),
true,
);
assertEquals(errMsg.includes("Not Found"), true);
assertEquals(errMsg.includes("callUser"), false);
assertEquals(errors, []);
});
},
}); //Ignoring until we figure out a way to run the server on a remote container accesible to the remote browser
Expand Down

0 comments on commit 72949ea

Please sign in to comment.