Skip to content

Commit 3ea707e

Browse files
authored
feat: [deno-web-test] Add docs, meta-tests, and an ability to propagate console messages (#506)
1 parent 0a8bddf commit 3ea707e

File tree

23 files changed

+309
-47
lines changed

23 files changed

+309
-47
lines changed
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
const ED25519_FLAG = "--enable-experimental-web-platform-features";
22
export default {
3-
astral: {
4-
product: "chrome",
5-
args: [ED25519_FLAG],
6-
headless: true,
7-
}
3+
product: "chrome",
4+
args: [ED25519_FLAG],
85
};
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# deno-web-test
2+
3+
`deno-web-test` is a test runner for running Deno tests in a browser. This is
4+
used in code compatible with both Deno and browsers in order to test browser
5+
functionality.
6+
7+
## Usage
8+
9+
Write a test using `Deno.test`:
10+
11+
```ts
12+
// add.test.ts
13+
import { assert } from "@std/assert";
14+
15+
Deno.test("add", function () {
16+
assert((5 + 5) === 10, "math checks out");
17+
});
18+
```
19+
20+
Optionally add a `deno-web-test.config.ts` to the project root to configure the
21+
runner. See [config.ts](/typescript/packages/deno-web-test/config.ts) for all
22+
options.
23+
24+
```ts
25+
export default {
26+
headless: true,
27+
devtools: false,
28+
product: "chrome",
29+
args: ["--enable-experimental-web-platform-features"],
30+
pipeConsole: true,
31+
};
32+
```
33+
34+
Finally, run `deno-web-test/cli.ts`, which takes a glob of files to test.
35+
36+
```json
37+
{
38+
"tasks": {
39+
"test": "deno run -A deno-web-test/cli.ts *.test.ts"
40+
}
41+
}
42+
```
43+
44+
## Support
45+
46+
Currently only the `Deno.test(string, fn)` signature works. Using other
47+
signatures, or the BDD framework in `@std/testing/bdd` is not yet supported.
48+
49+
## Testing
50+
51+
For testing `deno-web-test` itself, the test suites (running in Deno itself) run
52+
`deno-web-test` for subprojects to test features. Due to being in a workspace,
53+
and not wanting to clutter the workspace with these test directories, and Deno
54+
attempting to enforce this, the test packages are moved to a temporary directory
55+
and the test task rewritten to target the local `cli.ts` export. This could be
56+
relaxed if moved outside of the workspace.

typescript/packages/deno-web-test/browser.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
import { Browser, launch, Page } from "jsr:@astral/astral";
1+
import { Browser, ConsoleEvent, launch, Page } from "jsr:@astral/astral";
22
import { Manifest } from "./manifest.ts";
33
import { tsToJs, wait } from "./utils.ts";
44
import { TestResult } from "./interface.ts";
5+
import { extractAstralConfig } from "./config.ts";
56

6-
export class BrowserController {
7+
export class BrowserController extends EventTarget {
78
private manifest: Manifest;
89
private page: Page | null;
910
private browser: Browser | null;
1011

1112
constructor(manifest: Manifest) {
13+
super();
1214
this.manifest = manifest;
1315
this.browser = null;
1416
this.page = null;
@@ -23,8 +25,18 @@ export class BrowserController {
2325
if (this.page) {
2426
await this.page.goto(testUrl);
2527
} else {
26-
this.browser = await launch(config.astral ?? {});
28+
this.browser = await launch(extractAstralConfig(config));
2729
this.page = await this.browser.newPage(testUrl);
30+
this.page.addEventListener("console", (e) => {
31+
// Not sure why this event needs reconstructed in order
32+
// to re-fire, rather than just passing it into `dispatchEvent`.
33+
this.dispatchEvent(
34+
new ConsoleEvent({
35+
type: e.detail.type,
36+
text: e.detail.text,
37+
}),
38+
);
39+
});
2840
}
2941
await this.waitUntilReady();
3042
}

typescript/packages/deno-web-test/cli.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const manifest = await Manifest.create(Deno.cwd(), [...Deno.args]);
99
await buildTestDir(manifest);
1010

1111
const server = new TestServer(manifest);
12-
await server.start(manifest.port);
12+
server.start(manifest.port);
1313

1414
const runner = new Runner(manifest);
1515
const success = await runner.run();
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { LaunchOptions } from "@astral/astral";
2+
import * as path from "@std/path";
3+
import { exists } from "@std/fs/exists";
4+
5+
// These configurations can be applied
6+
// by placing a `deno-web-test.config.ts` in package root.
7+
export type Config = {
8+
// Whether the test runner should run headlessly. Default: true.
9+
headless?: boolean;
10+
// Whether devtools should be enabled. Default: false.
11+
devtools?: boolean;
12+
// What browser to run.
13+
product?: "chrome" | "firefox";
14+
// Arguments to be passed into the browser.
15+
args?: string[];
16+
// Whether or not console commands should be propagated to the deno-web-test process.
17+
pipeConsole?: boolean;
18+
};
19+
20+
export const applyDefaults = (config: object): Config => {
21+
return Object.assign({
22+
headless: true,
23+
devtools: false,
24+
}, config);
25+
};
26+
27+
export const extractAstralConfig = (config: Config): LaunchOptions => {
28+
const astralConfig = {};
29+
for (const prop of ["headless", "devtools", "product", "args"]) {
30+
if (prop in config) {
31+
astralConfig[prop] = config[prop];
32+
}
33+
}
34+
return astralConfig;
35+
};
36+
37+
export const getConfig = async (projectDir: string): Promise<Config> => {
38+
const configPath = path.join(projectDir, "deno-web-test.config.ts");
39+
40+
if (await exists(configPath, { isFile: true })) {
41+
// Try to evaluate it
42+
try {
43+
const config = (await import(configPath)).default;
44+
return applyDefaults(config);
45+
} catch (e) {
46+
console.error(`Unable to execute deno-web-test.config.ts`);
47+
}
48+
}
49+
return applyDefaults({});
50+
};

typescript/packages/deno-web-test/deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@commontools/deno-web-test",
33
"tasks": {
4-
"test": "deno run --allow-env --allow-read --allow-write --allow-run --allow-net ./cli.ts test/*.test.ts"
4+
"test": "deno test --allow-env --allow-read --allow-write --allow-run --allow-net test/*.test.ts"
55
},
66
"exports": {
77
".": "./mod.ts",

typescript/packages/deno-web-test/manifest.ts

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as path from "@std/path";
2-
import { Config } from "./interface.ts";
2+
import { Config, getConfig } from "./config.ts";
33
import { exists } from "@std/fs/exists";
44

55
export class Manifest {
@@ -30,22 +30,7 @@ export class Manifest {
3030

3131
static async create(projectDir: string, tests: string[]): Promise<Manifest> {
3232
const serverDir = await Deno.makeTempDir();
33-
const config = await Manifest.getConfig(projectDir);
33+
const config = await getConfig(projectDir);
3434
return new Manifest(projectDir, tests, serverDir, config);
3535
}
36-
37-
static async getConfig(projectDir: string): Promise<Config> {
38-
const configPath = path.join(projectDir, "deno-web-test.config.ts");
39-
40-
if (await exists(configPath, { isFile: true })) {
41-
// Try to evaluate it
42-
try {
43-
const config = (await import(configPath)).default;
44-
return config as Config;
45-
} catch (e) {
46-
console.error(`Unable to execute deno-web-test.config.ts`);
47-
}
48-
}
49-
return {} as Config;
50-
}
5136
}

typescript/packages/deno-web-test/runner.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ export class Runner {
1313
constructor(manifest: Manifest) {
1414
this.manifest = manifest;
1515
this.reporter = new Reporter();
16-
this.browser = new BrowserController(manifest);
1716
this.results = [];
17+
this.browser = new BrowserController(manifest);
18+
this.browser.addEventListener("console", (e) => this.onConsole(e));
1819
}
1920

2021
// Runs all tests in the browser. Return value
@@ -57,4 +58,20 @@ export class Runner {
5758
await this.browser.close();
5859
return summary.failed.length === 0;
5960
}
61+
62+
onConsole(e: ConsoleEvent) {
63+
if (this.manifest.config.pipeConsole) {
64+
switch (e.detail.type) {
65+
case "log":
66+
console.log(`deno-web-test: ${e.detail.text}`);
67+
break;
68+
case "warn":
69+
console.warn(`deno-web-test: ${e.detail.text}`);
70+
break;
71+
case "error":
72+
console.error(`deno-web-test: ${e.detail.text}`);
73+
break;
74+
}
75+
}
76+
}
6077
}

typescript/packages/deno-web-test/server.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,9 @@ export class TestServer {
1313
this.manifest = manifest;
1414
}
1515

16-
async start(port: number) {
16+
start(port: number) {
1717
this.server = Deno.serve(
18-
{ port, hostname: "127.0.0.1" },
18+
{ port, hostname: "127.0.0.1", onListen({ path }) {} },
1919
(req: Request) =>
2020
serveDir(req, {
2121
fsRoot: this.manifest.serverDir,
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,18 @@
11
import { assert } from "@std/assert";
2+
import { decode, runDenoWebTest } from "./utils.ts";
23

3-
Deno.test("Base test", async function () {
4-
assert(true, "This should be true");
4+
const dirname = import.meta.dirname as string;
5+
6+
Deno.test("smoke test", async function () {
7+
const { success, stdout, stderr } = await runDenoWebTest("success-project");
8+
const stdoutText = decode(stdout);
9+
const stderrText = decode(stderr);
10+
11+
assert(success, "test successful");
12+
assert(/add-sync ... ok/.test(stdoutText), "test output ok");
13+
assert(/add-async ... ok/.test(stdoutText), "test output ok");
14+
assert(/ok | 2 passed | 0 failed/.test(stdoutText), "test output ok");
15+
assert(/deno run/.test(stderrText), "stderr has deno task run");
16+
assert(stderrText.split("\n").length === 2, "stderr has no other messages");
17+
assert(stderrText.split("\n")[1] === "", "stderr has no other messages");
518
});

0 commit comments

Comments
 (0)