-
-
Notifications
You must be signed in to change notification settings - Fork 51
/
Copy pathtest.js
188 lines (169 loc) · 6.5 KB
/
test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/** @type {import('./test.d.ts').testBrowser} */
export async function testBrowser(
options = {},
) {
const { fileURLToPath } = await import("node:url");
const path = await import("node:path");
const fs = await import("node:fs/promises");
const os = await import("node:os");
const { existsSync } = await import("node:fs");
const selfUrl = fileURLToPath(import.meta.url);
const webRoot = path.dirname(selfUrl);
const http = await import("node:http");
const defaultContentTypes = {
".html": "text/html",
".js": "text/javascript",
".mjs": "text/javascript",
".wasm": "application/wasm",
};
const preludeScriptPath = "/prelude.js"
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);
res.setHeader("Cross-Origin-Embedder-Policy", "require-corp");
res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
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" });
const info = {
env: process.env,
args: options.args,
};
if (options.preludeScript) {
info.preludeScript = preludeScriptPath;
}
res.end(JSON.stringify(info));
} else if (pathname === preludeScriptPath) {
res.writeHead(200, { "Content-Type": "text/javascript" });
res.end(await fs.readFile(options.preludeScript, "utf-8"));
} else {
res.writeHead(404);
res.end();
}
});
async function tryListen(port) {
try {
await new Promise((resolve) => {
server.listen({ host: "localhost", port }, () => resolve());
server.once("error", (error) => {
if (error.code === "EADDRINUSE") {
resolve(null);
} else {
throw error;
}
});
});
return server.address();
} catch {
return null;
}
}
// Try to listen on port 3000, if it's already in use, try a random available port
let address = await tryListen(3000);
if (!address) {
address = await tryListen(0);
}
if (options.inspect) {
console.log("Serving test page at http://localhost:" + address.port + "/test.browser.html");
console.log("Inspect mode: Press Ctrl+C to exit");
await new Promise((resolve) => process.on("SIGINT", resolve));
process.exit(128 + os.constants.signals.SIGINT);
}
const playwright = await (async () => {
try {
// @ts-ignore
return await import("playwright")
} catch {
// Playwright is not available in the current environment
console.error(`Playwright is not available in the current environment.
Please run the following command to install it:
$ npm install playwright && npx playwright install chromium
`);
process.exit(1);
}
})();
const browser = await playwright.chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
// Forward console messages in the page to the Node.js console
page.on("console", (message) => {
console.log(message.text());
});
const onExit = new Promise((resolve) => {
page.exposeFunction("exitTest", resolve);
});
await page.goto(`http://localhost:${address.port}/test.browser.html`);
const exitCode = await onExit;
await browser.close();
return exitCode;
}
/** @type {import('./test.d.ts').testBrowserInPage} */
export async function testBrowserInPage(options, processInfo) {
const exitTest = (code) => {
const fn = window.exitTest;
if (fn) { fn(code); }
}
const handleError = (error) => {
console.error(error);
exitTest(1);
};
// There are 6 cases to exit test
// 1. Successfully finished XCTest with `exit(0)` synchronously
// 2. Unsuccessfully finished XCTest with `exit(non - zero)` synchronously
// 3. Successfully finished XCTest with `exit(0)` asynchronously
// 4. Unsuccessfully finished XCTest with `exit(non - zero)` asynchronously
// 5. Crash by throwing JS exception synchronously
// 6. Crash by throwing JS exception asynchronously
class ExitError extends Error {
constructor(code) {
super(`Process exited with code ${code}`);
this.code = code;
}
}
const handleExitOrError = (error) => {
if (error instanceof ExitError) {
exitTest(error.code);
} else {
handleError(error) // something wrong happens during test
}
}
// Handle asynchronous exits (case 3, 4, 6)
window.addEventListener("unhandledrejection", event => {
event.preventDefault();
const error = event.reason;
handleExitOrError(error);
});
const { instantiate } = await import("./instantiate.js");
let setupOptions = (options, _) => { return options };
if (processInfo.preludeScript) {
const prelude = await import(processInfo.preludeScript);
if (prelude.setupOptions) {
setupOptions = prelude.setupOptions;
}
}
options = await setupOptions(options, { isMainThread: true });
try {
// Instantiate the WebAssembly file
return await instantiate({
...options,
addToCoreImports: (imports) => {
options.addToCoreImports?.(imports);
imports["wasi_snapshot_preview1"]["proc_exit"] = (code) => {
exitTest(code);
throw new ExitError(code);
};
},
});
// When JavaScriptEventLoop executor is still running,
// reachable here without catch (case 3, 4, 6)
} catch (error) {
// Handle synchronous exits (case 1, 2, 5)
handleExitOrError(error);
}
}