Skip to content

Commit

Permalink
Use real http server for browser testing (#506)
Browse files Browse the repository at this point in the history
  • Loading branch information
kateinoigakukun authored Nov 21, 2024
1 parent 4f7be4f commit c5904e3
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 23 deletions.
6 changes: 3 additions & 3 deletions Sources/CartonHelpers/StaticArchive.swift

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Sources/carton-frontend-slim/BundleLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ struct BundleLayout {
<body>
<script type="module">
import { testBrowser } from "./index.js";
testBrowser([], true);
testBrowser([], {}, true);
</script>
</body>
Expand Down Expand Up @@ -209,8 +209,8 @@ struct BundleLayout {
return internalInstantiate(options, imports);
}
export async function testBrowser(args, inPage = false) {
await internalTestBrowser(instantiate, wasmFileName, args, import.meta.url, inPage);
export async function testBrowser(args, options, inPage = false) {
await internalTestBrowser(instantiate, wasmFileName, args, import.meta.url, options, inPage);
}
export async function testNode(args) {
Expand Down
3 changes: 2 additions & 1 deletion Sources/carton-release/HashArchive.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ struct HashArchive: AsyncParsableCommand {
let arguments = [
"esbuild", "--bundle", "entrypoint/\(tsFilename)", "--outfile=static/\(filename)",
"--external:node:url", "--external:node:path",
"--external:node:module", "--external:node:fs/promises",
"--external:node:module", "--external:node:http",
"--external:node:fs/promises", "--external:node:fs",
"--external:playwright",
"--format=esm",
"--external:./JavaScriptKit_JavaScriptKit.resources/Runtime/index.mjs",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export class SwiftRuntime {
constructor({ sharedMemory: booleam });
setInstance(instance: WebAssembly.Instance): void;
main?(): void;
readonly wasmImports: ImportedFunctions;
Expand Down
71 changes: 55 additions & 16 deletions entrypoint/intrinsics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { WASI, File, OpenFile, ConsoleStdout, PreopenDirectory, WASIProcExit, In
import type { SwiftRuntime, SwiftRuntimeConstructor } from "./JavaScriptKit_JavaScriptKit.resources/Runtime/index";
import { polyfill as polyfillWebAssemblyTypeReflection } from "wasm-imports-parser/polyfill";
import type { ImportEntry } from "wasm-imports-parser";
import { AddressInfo } from "node:net";

// Apply polyfill for WebAssembly Type Reflection JS API to inspect imported memory info.
// https://github.com/WebAssembly/js-types/blob/main/proposals/js-types/Overview.md
Expand Down Expand Up @@ -65,7 +66,14 @@ export async function instantiate(rawOptions: InstantiationOptions, extraWasmImp

let swift: SwiftRuntime | undefined = options.swift;
if (!swift && options.SwiftRuntime) {
swift = new options.SwiftRuntime();
let sharedMemory = false;
for (const importEntry of WebAssembly.Module.imports(options.module)) {
if (importEntry.module === "env" && importEntry.name === "memory" && importEntry.kind === "memory") {
sharedMemory = true;
break;
}
}
swift = new options.SwiftRuntime({ sharedMemory });
}

let stdoutLine: LineDecoder | undefined = undefined;
Expand Down Expand Up @@ -242,10 +250,54 @@ async function extractAndSaveFile(rootFs: Map<string, Inode>, path: string): Pro
return false;
}

export async function testBrowser(instantiate: Instantiate, wasmFileName: string, args: string[], indexJsUrl: string, inPage: boolean) {
export async function testBrowser(
instantiate: Instantiate,
wasmFileName: string,
args: string[],
indexJsUrl: string,
options: { contentTypes?: (fileName: string) => string } = {},
inPage: boolean = false
) {
if (inPage) {
return await testBrowserInPage(instantiate, wasmFileName, args);
}

const { fileURLToPath } = await import("node:url");
const path = await import("node:path");
const fs = await import("node:fs/promises");
const { existsSync } = await import("node:fs");
const indexJsPath = fileURLToPath(indexJsUrl);
const webRoot = path.dirname(indexJsPath);

const http = await import("node:http");
const defaultContentTypes: Record<string, string> = {
".html": "text/html",
".js": "text/javascript",
".mjs": "text/javascript",
".wasm": "application/wasm",
};
const server = http.createServer(async (req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`);
const pathname = url.pathname;
const filePath = path.join(webRoot, pathname);
if (existsSync(filePath) && (await fs.stat(filePath)).isFile()) {
const data = await fs.readFile(filePath);
const ext = pathname.slice(pathname.lastIndexOf("."));
const contentType = options.contentTypes?.(pathname) || defaultContentTypes[ext] || "text/plain";
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
} else if (pathname === "/process-info.json") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ env: process.env }));
} else {
res.writeHead(404);
res.end();
}
});

await new Promise<void>((resolve) => server.listen({ host: "localhost", port: 0 }, () => resolve()));
const address = server.address() as AddressInfo;

const playwright = await (async () => {
try {
// @ts-ignore
Expand All @@ -263,29 +315,16 @@ Please run the following command to install it:
const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
const { fileURLToPath } = await import("node:url");
const path = await import("node:path");
const indexJsPath = fileURLToPath(indexJsUrl);
const webRoot = path.dirname(indexJsPath);

// Forward console messages in the page to the Node.js console
page.on("console", (message: any) => {
console.log(message.text());
});

await page.route("http://example.com/**/*", async (route: any) => {
const url = route.request().url();
const urlPath = new URL(url).pathname;
if (urlPath === "/process-info.json") {
route.fulfill({ body: JSON.stringify({ env: process.env }) });
return;
}
route.fulfill({ path: path.join(webRoot, urlPath.slice(1)) });
});
const onExit = new Promise<number>((resolve) => {
page.exposeFunction("exitTest", resolve);
});
await page.goto("http://example.com/test.browser.html");
await page.goto(`http://localhost:${address.port}/test.browser.html`);
const exitCode = await onExit;
await browser.close();
process.exit(exitCode);
Expand Down

0 comments on commit c5904e3

Please sign in to comment.