diff --git a/deps.ts b/deps.ts index cd57629..d59b83d 100644 --- a/deps.ts +++ b/deps.ts @@ -2,7 +2,6 @@ import type { Protocol } from "https://unpkg.com/devtools-protocol@0.0.979918/ty export { Protocol }; export { assertEquals, - AssertionError, assertNotEquals, } from "https://deno.land/std@0.139.0/testing/asserts.ts"; export { deferred } from "https://deno.land/std@0.139.0/async/deferred.ts"; diff --git a/src/page.ts b/src/page.ts index 0b8679d..d661618 100644 --- a/src/page.ts +++ b/src/page.ts @@ -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"; @@ -31,6 +31,8 @@ export class Page { readonly client: Client; + #console_errors: string[] = []; + constructor( protocol: ProtocolClass, targetId: string, @@ -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).detail); + }; + + addEventListener("Log.entryAdded", onError); + addEventListener("Runtime.exceptionThrow", onError); + } + + /** + * @example + * ```ts + * const waitForNewPage = page.waitFor("Page.windowOpen"); + * await elem.click(); + * await waitForNewPage + * const page2 = browser.page(2) + * ``` + * + * @param methodName + */ + public async waitFor(methodName: string): Promise { + const p = deferred(); + const listener = (event: Event) => { + p.resolve((event as CustomEvent).detail); + }; + addEventListener(methodName, listener); + const result = await p as T; + removeEventListener(methodName, listener); + return result; } public get socket() { @@ -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 { + // 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; } /** diff --git a/src/protocol.ts b/src/protocol.ts index a6c7529..20e5431 100644 --- a/src/protocol.ts +++ b/src/protocol.ts @@ -46,11 +46,6 @@ 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, ) { @@ -58,6 +53,7 @@ export class Protocol { // Register on message listener this.socket.onmessage = (msg) => { const data = JSON.parse(msg.data); + console.log(data); this.#handleSocketMessage(data); }; } @@ -94,9 +90,10 @@ export class Protocol { #handleSocketMessage( message: MessageResponse | NotificationResponse, ) { - // TODO :: make it unique eg `.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 `.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; @@ -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, + }), + ); } } diff --git a/tests/server.ts b/tests/server.ts index 57b7f18..3d4f2d8 100644 --- a/tests/server.ts +++ b/tests/server.ts @@ -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(` + Download + `); + } +} + class InputResource extends Drash.Resource { public paths = ["/input"]; @@ -89,6 +102,7 @@ export const server = new Drash.Server({ WaitForRequestsResource, InputResource, DialogsResource, + DownloadResource, ], protocol: "http", port: 1447, diff --git a/tests/unit/element_test.ts b/tests/unit/element_test.ts index cef2c3a..a7e8be5 100644 --- a/tests/unit/element_test.ts +++ b/tests/unit/element_test.ts @@ -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 () => { diff --git a/tests/unit/page_test.ts b/tests/unit/page_test.ts index 66d006c..23a0342 100644 --- a/tests/unit/page_test.ts +++ b/tests/unit/page_test.ts @@ -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 () => { @@ -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