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: Add assertNoConsoleErrors() #98

Merged
merged 7 commits into from
Dec 18, 2021
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
8 changes: 4 additions & 4 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ jobs:
run: |
cd tests/integration/docker_test
docker-compose up -d
docker exec drivers deno test -A --config tsconfig.json tests/integration
docker exec drivers deno test -A --config tsconfig.json tests/unit
docker exec drivers deno test -A --config tsconfig.json --no-check=remote tests/integration
docker exec drivers deno test -A --config tsconfig.json --no-check=remote tests/unit


tests:
Expand All @@ -38,11 +38,11 @@ jobs:

- name: Run Integration Tests
run: |
deno test -A tests/integration --config tsconfig.json
deno test -A tests/integration --config tsconfig.json --no-check=remote

- name: Run Unit Tests
run: |
deno test -A --config tsconfig.json tests/unit
deno test -A --config tsconfig.json tests/unit --no-check=remote

linter:
# Only one OS is required since fmt is cross platform
Expand Down
6 changes: 5 additions & 1 deletion deps.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import type Protocol from "https://unpkg.com/devtools-protocol@0.0.818844/types/protocol.d.ts";
export { Protocol };
export { assertEquals } from "https://deno.land/std@0.118.0/testing/asserts.ts";
export {
assertEquals,
AssertionError,
assertThrows,
} from "https://deno.land/std@0.118.0/testing/asserts.ts";
export { readLines } from "https://deno.land/std@0.118.0/io/mod.ts";
export { deferred } from "https://deno.land/std@0.118.0/async/deferred.ts";
export type { Deferred } from "https://deno.land/std@0.118.0/async/deferred.ts";
1 change: 1 addition & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ export class Client {
);
await protocol.sendWebSocketMessage("Page.enable");
await protocol.sendWebSocketMessage("Runtime.enable");
await protocol.sendWebSocketMessage("Log.enable");
return new Client(protocol);
}
}
55 changes: 54 additions & 1 deletion src/page.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertEquals, deferred, Protocol } from "../deps.ts";
import { assertEquals, AssertionError, 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 @@ -185,6 +185,59 @@ export class Page {
return new Element("document.querySelector", selector, this);
}

/**
* 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
*/
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();
}, 1000);
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.#protocol.done();
throw new AssertionError(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you dingus, this will always throw

"Expected console to show no errors. Instead got:\n" +
filteredNotifs.join("\n"),
);
}

/**
* Take a screenshot of the page and save it to `filename` in `path` folder, with a `format` and `quality` (jpeg format only)
* If `selector` is passed in, it will take a screenshot of only that element
Expand Down
50 changes: 38 additions & 12 deletions src/protocol.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Deferred, deferred } from "../deps.ts";
import { existsSync } from "./utility.ts";
import type { Browsers } from "./types.ts";
import { Protocol as ProtocolTypes } from "../deps.ts";

interface MessageResponse { // For when we send an event to get one back, eg running a JS expression
id: number;
Expand All @@ -10,7 +11,7 @@ interface MessageResponse { // For when we send an event to get one back, eg run

interface NotificationResponse { // Not entirely sure when, but when we send the `Network.enable` method
method: string;
params: unknown;
params: Record<string, unknown>;
}

export class Protocol {
Expand Down Expand Up @@ -58,6 +59,11 @@ export class Protocol {
*/
public browser_process_closed = false;

/**
* 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,
browserProcess: Deno.Process,
Expand Down Expand Up @@ -197,22 +203,42 @@ export class Protocol {
) {
if ("id" in message) { // message response
const resolvable = this.resolvables.get(message.id);
if (resolvable) {
if ("result" in message) { // success response
if ("errorText" in message.result!) {
const r = this.notification_resolvables.get("Page.loadEventFired");
if (r) {
r.resolve();
}
if (!resolvable) {
return;
}
if ("result" in message) { // success response
if ("errorText" in message.result!) {
const r = this.notification_resolvables.get("Page.loadEventFired");
if (r) {
r.resolve();
}
resolvable.resolve(message.result);
}
if ("error" in message) { // error response
resolvable.resolve(message.error);
}
resolvable.resolve(message.result);
}
if ("error" in message) { // error response
resolvable.resolve(message.error);
}
}
if ("method" in message) { // Notification response
// Store certain methods for if we need to query them later
if (message.method === "Runtime.exceptionThrown") {
const params = message
.params as unknown as ProtocolTypes.Runtime.ExceptionThrownEvent;
const errorMessage = params.exceptionDetails.exception?.description;
if (errorMessage) {
this.console_errors.push(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);
}
}
}
const resolvable = this.notification_resolvables.get(message.method);
if (resolvable) {
resolvable.resolve();
Expand Down
2 changes: 1 addition & 1 deletion src/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ export function getChromePath(): string {
export function getChromeArgs(port: number, binaryPath?: string): string[] {
return [
binaryPath || getChromePath(),
"--headless",
"--remote-debugging-port=" + port,
"--headless",
"--disable-gpu",
"--no-sandbox",
];
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 { Rhum } from "https://deno.land/x/rhum@v1.1.12/mod.ts";
export * as Drash from "https://deno.land/x/drash@v2.2.0/mod.ts";
24 changes: 24 additions & 0 deletions tests/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Drash } from "./deps.ts";

class HomeResource extends Drash.Resource {
public paths = ["/"];
public GET(_request: Drash.Request, response: Drash.Response) {
response.html(
"<script src='index.js'></script><link href='styles.css' rel='stylesheet' />",
);
}
}
class JSResource extends Drash.Resource {
public paths = ["/.*\.js"];
public GET(_request: Drash.Request, response: Drash.Response) {
response.text("callUser()");
response.headers.set("content-type", "application/javascript");
}
}

export const server = new Drash.Server({
resources: [HomeResource, JSResource],
protocol: "http",
port: 1447,
hostname: "localhost",
});
77 changes: 77 additions & 0 deletions tests/unit/page_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const ScreenshotsFolder = "./tests/unit/Screenshots";
import { buildFor } from "../../mod.ts";
import { assertEquals } from "../../deps.ts";
import { existsSync } from "../../src/utility.ts";
import { server } from "../server.ts";

for (const browserItem of browserList) {
Deno.test(
Expand Down Expand Up @@ -222,4 +223,80 @@ for (const browserItem of browserList) {
await Sinco.done();
assertEquals(cookies, browserItem.cookies);
});

Deno.test(`[${browserItem.name}] assertNoConsoleErrors() | Should throw when errors`, async () => {
server.run();
const Sinco = await buildFor(browserItem.name);
// 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
const page = await Sinco.goTo(
server.address,
);
let errMsg = "";
try {
await page.assertNoConsoleErrors();
} catch (e) {
errMsg = e.message;
}
await Sinco.done();
await server.close();
try {
assertEquals(
errMsg,
`Expected console to show no errors. Instead got:
ReferenceError: callUser is not defined
at http://localhost:1447/index.js:1:1
Failed to load resource: the server responded with a status of 404 (Not Found)`,
);
} catch (_e) {
assertEquals(
errMsg,
`Expected console to show no errors. Instead got:
Failed to load resource: the server responded with a status of 404 (Not Found)
ReferenceError: callUser is not defined
at http://localhost:1447/index.js:1:1
Failed to load resource: the server responded with a status of 404 (Not Found)`,
);
}
});

Deno.test(`[${browserItem.name}] assertNoConsoleErrors() | Should not throw when no errors`, async () => {
const Sinco = await buildFor(browserItem.name);
const page = await Sinco.goTo(
"https://drash.land",
);
await page.assertNoConsoleErrors();
await Sinco.done();
});

Deno.test(`[${browserItem.name}] assertNoConsoleErrors() | Should exclude messages`, async () => {
server.run();
const Sinco = await buildFor(browserItem.name);
const page = await Sinco.goTo(
server.address,
);
let errMsg = "";
try {
await page.assertNoConsoleErrors(["callUser"]);
} catch (e) {
errMsg = e.message;
}
await server.close();
await Sinco.done();
try {
assertEquals(
errMsg,
`Expected console to show no errors. Instead got:
Failed to load resource: the server responded with a status of 404 (Not Found)
Failed to load resource: the server responded with a status of 404 (Not Found)`,
);
} catch (_e) {
assertEquals(
errMsg,
`Expected console to show no errors. Instead got:
Failed to load resource: the server responded with a status of 404 (Not Found)`,
);
}
});
break;
}