@w-lfpup/jackrabbit 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.github/workflows/browsers.json +45 -0
  2. package/.github/workflows/browsers.macos.json +51 -0
  3. package/.github/workflows/browsers.windows.json +19 -0
  4. package/.github/workflows/tests.yml +42 -0
  5. package/README.md +151 -8
  6. package/browser/dist/logger.js +43 -0
  7. package/browser/dist/mod.js +26 -0
  8. package/browser/dist/queue.js +27 -0
  9. package/browser/dist/runner.js +20 -0
  10. package/{cli → browser}/package.json +1 -1
  11. package/browser/src/logger.ts +57 -0
  12. package/browser/src/mod.ts +30 -0
  13. package/browser/src/runner.ts +22 -0
  14. package/browser/tsconfig.json +11 -0
  15. package/browser/tsconfig.tsbuildinfo +1 -0
  16. package/browsers.json +38 -0
  17. package/core/dist/jackrabbit_types.d.ts +62 -28
  18. package/core/dist/mod.d.ts +2 -2
  19. package/core/dist/mod.js +1 -1
  20. package/core/dist/run_steps.d.ts +2 -2
  21. package/core/dist/run_steps.js +83 -67
  22. package/core/src/jackrabbit_types.ts +73 -29
  23. package/core/src/mod.ts +2 -8
  24. package/core/src/run_steps.ts +111 -80
  25. package/examples/hello_world/goodbye_world.ts +1 -1
  26. package/examples/hello_world/hello_world.ts +1 -1
  27. package/nodejs/dist/logger.js +161 -0
  28. package/nodejs/dist/mod.js +31 -0
  29. package/nodejs/dist/results.js +139 -0
  30. package/nodejs/dist/results_str.js +147 -0
  31. package/nodejs/dist/runner.js +17 -0
  32. package/nodejs/src/logger.ts +193 -0
  33. package/nodejs/src/mod.ts +37 -0
  34. package/nodejs/src/results_str.ts +234 -0
  35. package/{cli → nodejs}/tsconfig.json +2 -1
  36. package/nodejs/tsconfig.tsbuildinfo +1 -0
  37. package/package.json +9 -6
  38. package/tests/dist/mod.d.ts +14 -3
  39. package/tests/dist/mod.js +33 -13
  40. package/tests/dist/test_error.test.d.ts +9 -0
  41. package/tests/dist/test_error.test.js +27 -0
  42. package/tests/dist/test_errors.test.d.ts +9 -0
  43. package/tests/dist/test_errors.test.js +27 -0
  44. package/tests/dist/test_logger.d.ts +3 -2
  45. package/tests/dist/test_logger.js +5 -1
  46. package/tests/src/mod.ts +31 -15
  47. package/tests/src/test_error.test.ts +32 -0
  48. package/tests/src/test_logger.ts +6 -1
  49. package/tests/tsconfig.tsbuildinfo +1 -1
  50. package/tsconfig.json +1 -1
  51. package/webdriver/dist/config.js +57 -0
  52. package/webdriver/dist/eventbus.js +18 -0
  53. package/webdriver/dist/listeners.js +21 -0
  54. package/webdriver/dist/logger.js +203 -0
  55. package/webdriver/dist/mod.js +36 -0
  56. package/webdriver/dist/results_str.js +167 -0
  57. package/webdriver/dist/routes.js +172 -0
  58. package/webdriver/dist/routes2.js +163 -0
  59. package/webdriver/dist/test_hangar.js +20 -0
  60. package/webdriver/dist/webdriver.js +273 -0
  61. package/webdriver/package.json +8 -0
  62. package/webdriver/src/config.ts +89 -0
  63. package/webdriver/src/eventbus.ts +104 -0
  64. package/webdriver/src/logger.ts +247 -0
  65. package/webdriver/src/mod.ts +43 -0
  66. package/webdriver/src/results.ts +56 -0
  67. package/webdriver/src/results_str.ts +222 -0
  68. package/webdriver/src/routes.ts +211 -0
  69. package/webdriver/src/test_hangar.ts +25 -0
  70. package/webdriver/src/webdriver.ts +372 -0
  71. package/{nodejs_cli → webdriver}/tsconfig.json +1 -0
  72. package/webdriver/tsconfig.tsbuildinfo +1 -0
  73. package/.github/workflows/build_and_test.yml +0 -18
  74. package/cli/dist/cli.d.ts +0 -3
  75. package/cli/dist/cli.js +0 -8
  76. package/cli/dist/cli_types.d.ts +0 -7
  77. package/cli/dist/config.d.ts +0 -5
  78. package/cli/dist/config.js +0 -27
  79. package/cli/dist/importer.d.ts +0 -7
  80. package/cli/dist/importer.js +0 -16
  81. package/cli/dist/logger.d.ts +0 -7
  82. package/cli/dist/logger.js +0 -88
  83. package/cli/dist/mod.d.ts +0 -6
  84. package/cli/dist/mod.js +0 -4
  85. package/cli/src/cli.ts +0 -17
  86. package/cli/src/cli_types.ts +0 -9
  87. package/cli/src/config.ts +0 -36
  88. package/cli/src/importer.ts +0 -25
  89. package/cli/src/logger.ts +0 -126
  90. package/cli/src/mod.ts +0 -7
  91. package/cli/tsconfig.tsbuildinfo +0 -1
  92. package/nodejs_cli/dist/mod.d.ts +0 -2
  93. package/nodejs_cli/dist/mod.js +0 -20
  94. package/nodejs_cli/src/mod.ts +0 -25
  95. package/nodejs_cli/tsconfig.tsbuildinfo +0 -1
  96. package/test_guide.md +0 -114
  97. /package/{nodejs_cli → nodejs}/package.json +0 -0
  98. /package/{cli/dist/cli_types.js → webdriver/dist/results.js} +0 -0
@@ -0,0 +1,273 @@
1
+ import { exec } from "child_process";
2
+ let headers = new Headers([["Content-Type", "application/json"]]);
3
+ export class WebDrivers {
4
+ #config;
5
+ #eventbus;
6
+ #webdrivers = [];
7
+ #currentIndex = 0;
8
+ constructor(config, eventbus) {
9
+ this.#eventbus = eventbus;
10
+ this.#config = config;
11
+ for (const params of config.webdrivers) {
12
+ this.#webdrivers.push(new WebdriverSession(params, config.hostAndPort, eventbus));
13
+ }
14
+ }
15
+ run() {
16
+ this.#eventbus.addListener("session_closed", (action) => {
17
+ if (action.id !== this.#config.webdrivers[this.#currentIndex]?.jrId)
18
+ return;
19
+ this.#currentIndex += 1;
20
+ let webdriver = this.#webdrivers[this.#currentIndex];
21
+ webdriver
22
+ ? webdriver.run()
23
+ : this.#eventbus.dispatchAction({
24
+ type: "end",
25
+ });
26
+ });
27
+ let webdriver = this.#webdrivers[this.#currentIndex];
28
+ webdriver
29
+ ? webdriver.run()
30
+ : this.#eventbus.dispatchAction({
31
+ type: "end",
32
+ });
33
+ }
34
+ runAll() {
35
+ this.#eventbus.addListener("session_closed", (action) => {
36
+ let { id } = action;
37
+ let [indexStr] = id.split(":");
38
+ let index = parseInt(indexStr);
39
+ if (this.#webdrivers[index]) {
40
+ if (id === this.#config.webdrivers[index]?.jrId)
41
+ this.#currentIndex += 1;
42
+ }
43
+ if (this.#currentIndex === this.#webdrivers.length) {
44
+ this.#eventbus.dispatchAction({
45
+ type: "end",
46
+ });
47
+ }
48
+ });
49
+ for (let webdriver of this.#webdrivers) {
50
+ webdriver.run();
51
+ }
52
+ }
53
+ }
54
+ class WebdriverSession {
55
+ #params;
56
+ #hostAndPort;
57
+ #eventbus;
58
+ #process;
59
+ #signal;
60
+ #abortController;
61
+ #sessionId;
62
+ constructor(params, hostAndPort, eventbus) {
63
+ this.#params = params;
64
+ this.#hostAndPort = hostAndPort;
65
+ this.#eventbus = eventbus;
66
+ this.#abortController = new AbortController();
67
+ this.#eventbus.addListener("run_complete", (action) => {
68
+ if (action.id === this.#params.jrId)
69
+ this.#down();
70
+ });
71
+ }
72
+ async run() {
73
+ if (this.#process)
74
+ return;
75
+ let { jrId } = this.#params;
76
+ this.#eventbus.dispatchAction({
77
+ id: jrId,
78
+ type: "session_start",
79
+ });
80
+ this.#signal = setupSignal(this.#params, this.#eventbus, this.#abortController.signal);
81
+ this.#process = setupProcess(this.#params, this.#eventbus, this.#abortController.signal);
82
+ try {
83
+ await untilWebdriverReady(this.#params, this.#signal);
84
+ this.#sessionId = await getSession(this.#params, this.#signal);
85
+ await goToPing(this.#params, this.#signal, this.#sessionId, this.#hostAndPort);
86
+ await setCookie(this.#params, this.#signal, this.#sessionId);
87
+ await goToTestPage(this.#params, this.#signal, this.#sessionId, this.#hostAndPort);
88
+ }
89
+ catch (e) {
90
+ let errOutput;
91
+ if (e instanceof Error) {
92
+ errOutput = e.name + "\n" + e.message + (e.cause ? "\n" + e.cause : "");
93
+ }
94
+ if (!errOutput)
95
+ errOutput = e?.toString();
96
+ this.#eventbus.dispatchAction({
97
+ type: "session_error",
98
+ id: this.#params.jrId,
99
+ error: errOutput ?? "Unknown error creating browser session",
100
+ });
101
+ this.#abortController.abort();
102
+ }
103
+ }
104
+ async #down() {
105
+ if (!this.#process)
106
+ return;
107
+ await deleteSession(this.#params, this.#signal, this.#eventbus, this.#sessionId);
108
+ this.#process.kill();
109
+ this.#process = undefined;
110
+ }
111
+ }
112
+ function setupSignal(params, eventbus, externalSignal) {
113
+ let { jrId, timeoutMs } = params;
114
+ let signal = AbortSignal.any([
115
+ externalSignal,
116
+ AbortSignal.timeout(timeoutMs),
117
+ ]);
118
+ signal.addEventListener("abort", function () {
119
+ eventbus.dispatchAction({
120
+ type: "session_closed",
121
+ id: jrId,
122
+ });
123
+ });
124
+ return signal;
125
+ }
126
+ function setupProcess(params, eventbus, externalSignal) {
127
+ let { command, jrId } = params;
128
+ let process = exec(command, { signal: externalSignal }, (error, _stdout, stderr) => {
129
+ if (stderr) {
130
+ eventbus.dispatchAction({
131
+ id: jrId,
132
+ type: "stderr",
133
+ output: stderr,
134
+ });
135
+ }
136
+ });
137
+ process.addListener("error", (error) => {
138
+ eventbus.dispatchAction({
139
+ id: jrId,
140
+ type: "session_error",
141
+ error: error.toString(),
142
+ });
143
+ });
144
+ process.addListener("exit", (statusCode) => {
145
+ if (statusCode) {
146
+ eventbus.dispatchAction({
147
+ type: "session_error",
148
+ id: jrId,
149
+ error: `Process returned status code: ${statusCode}`,
150
+ });
151
+ }
152
+ eventbus.dispatchAction({
153
+ type: "session_closed",
154
+ id: jrId,
155
+ });
156
+ });
157
+ return process;
158
+ }
159
+ async function untilWebdriverReady(params, signal) {
160
+ let { url } = params;
161
+ while (signal && !signal.aborted) {
162
+ try {
163
+ let res = await fetch(new URL("/status", url), {
164
+ method: "GET",
165
+ headers,
166
+ body: null,
167
+ signal,
168
+ });
169
+ if (200 === res.status) {
170
+ let json = await res.json();
171
+ let { ready } = json?.value;
172
+ if (typeof ready === "boolean" && ready)
173
+ return;
174
+ }
175
+ }
176
+ catch { }
177
+ await sleep(30);
178
+ }
179
+ throw new Error("Webdriver was never ready.");
180
+ }
181
+ async function getSession(params, signal) {
182
+ let { url, capabilities } = params;
183
+ let res = await fetch(new URL("/session", url), {
184
+ method: "POST",
185
+ headers,
186
+ body: JSON.stringify({ capabilities: capabilities ?? {} }),
187
+ signal,
188
+ });
189
+ if (200 !== res.status) {
190
+ let cause = await res.text();
191
+ throw new Error("Failed to create a session", { cause });
192
+ }
193
+ let json = await res.json();
194
+ let { sessionId } = json?.value;
195
+ if (typeof sessionId !== "string")
196
+ throw new Error("session is not a string");
197
+ return sessionId;
198
+ }
199
+ async function goToPing(params, signal, sessionId, hostAndPort) {
200
+ let { url } = params;
201
+ let pingUrl = new URL("/ping", hostAndPort);
202
+ let getCookie = await fetch(new URL(`/session/${sessionId}/url`, url), {
203
+ method: "POST",
204
+ headers,
205
+ body: JSON.stringify({ url: pingUrl }),
206
+ signal,
207
+ });
208
+ if (200 !== getCookie.status) {
209
+ let cause = await getCookie.json();
210
+ throw new Error("go-to-cookie request failed", { cause });
211
+ }
212
+ }
213
+ async function setCookie(params, signal, sessionId) {
214
+ let { url, jrId } = params;
215
+ let cookieReq = await fetch(new URL(`/session/${sessionId}/cookie`, url), {
216
+ method: "POST",
217
+ headers,
218
+ body: JSON.stringify({
219
+ cookie: {
220
+ name: "jackrabbit",
221
+ value: jrId,
222
+ // domain: this.#hostAndPort (issues in firefox)
223
+ path: "/",
224
+ httpOnly: true,
225
+ },
226
+ }),
227
+ signal,
228
+ });
229
+ if (200 !== cookieReq.status) {
230
+ let cause = await cookieReq.json();
231
+ throw new Error("set-cookie request failed", { cause });
232
+ }
233
+ }
234
+ async function goToTestPage(params, signal, sessionId, hostAndPort) {
235
+ let { url } = params;
236
+ let goToUrlRes = await fetch(new URL(`/session/${sessionId}/url`, url), {
237
+ method: "POST",
238
+ headers,
239
+ body: JSON.stringify({ url: hostAndPort }),
240
+ signal,
241
+ });
242
+ if (200 !== goToUrlRes.status)
243
+ throw new Error("go-to-url request failed");
244
+ }
245
+ async function deleteSession(params, signal, eventbus, sessionId) {
246
+ let { url } = params;
247
+ try {
248
+ let delReqest = await fetch(new URL(`/session/${sessionId}`, url), {
249
+ method: "DELETE",
250
+ headers,
251
+ body: null,
252
+ signal: signal,
253
+ });
254
+ if (200 !== delReqest.status) {
255
+ let cause = await delReqest.json();
256
+ throw new Error("delete-cookie request failed", { cause });
257
+ }
258
+ }
259
+ catch (e) {
260
+ eventbus.dispatchAction({
261
+ type: "session_error",
262
+ id: params.jrId,
263
+ error: e?.toString() ?? "failed to delete browser session error",
264
+ });
265
+ }
266
+ }
267
+ function sleep(timeMs) {
268
+ return new Promise(function (resolve) {
269
+ setTimeout(function () {
270
+ resolve();
271
+ }, timeMs);
272
+ });
273
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "webdriver",
3
+ "type": "module",
4
+ "main": "dist/mod.js",
5
+ "scripts": {
6
+ "build": "npx tsc --build"
7
+ }
8
+ }
@@ -0,0 +1,89 @@
1
+ import * as path from "path";
2
+
3
+ interface WebdriverConfig {
4
+ command: string;
5
+ url: URL;
6
+ title: string;
7
+ timeoutMs: number;
8
+ capabilities?: unknown;
9
+ }
10
+
11
+ export interface WebdriverParams extends WebdriverConfig {
12
+ jrId: string;
13
+ }
14
+
15
+ export interface ConfigInterface {
16
+ hostAndPort: URL;
17
+ runAsynchronously?: boolean;
18
+ webdrivers: WebdriverParams[];
19
+ }
20
+
21
+ export async function createConfig(
22
+ args: string[],
23
+ ): Promise<ConfigInterface | Error> {
24
+ let configFilepath = args[0];
25
+ let relPath = path.resolve(process.cwd(), configFilepath);
26
+
27
+ try {
28
+ // windows might need a "file://<relPath>" situation
29
+ let { default: json } = await import(`file://${relPath}`, {
30
+ with: { type: "json" },
31
+ });
32
+
33
+ let hostAndPort: URL | null = URL.parse(json.host_and_port);
34
+ if (!hostAndPort)
35
+ throw new Error(`Config: invalid host_and_port json property`);
36
+
37
+ let { run_asynchronously: runAsynchronously } = json;
38
+ if (
39
+ typeof runAsynchronously !== "boolean" &&
40
+ undefined !== runAsynchronously
41
+ )
42
+ throw new Error(
43
+ "Config: the property runAsynchronously is not a boolean or undefined",
44
+ );
45
+
46
+ let webdrivers: WebdriverParams[] = [];
47
+ if (Array.isArray(json.webdrivers))
48
+ for (const [index, webdriverParams] of json.webdrivers.entries()) {
49
+ let params = createWebdriverParams(webdriverParams);
50
+ if (params instanceof Error) return params;
51
+
52
+ let session = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
53
+ let jrId = `${index}:${session.toString(32)}`;
54
+
55
+ webdrivers.push({ ...params, jrId });
56
+ }
57
+ return {
58
+ hostAndPort,
59
+ runAsynchronously,
60
+ webdrivers,
61
+ };
62
+ } catch (e) {
63
+ if (e instanceof Error) return e;
64
+ return new Error("Config: failed to parse config params from string");
65
+ }
66
+ }
67
+
68
+ export function createWebdriverParams(json: any): WebdriverConfig | Error {
69
+ let { command, url, title, timeout_ms, capabilities } = json;
70
+
71
+ if (typeof command !== "string")
72
+ return new Error("WebdriverParams: command is not a string");
73
+
74
+ let parsedUrl: URL | null = URL.parse(url);
75
+ if (null === parsedUrl)
76
+ return new Error("WebdriverParams: url is not a valid URL");
77
+ if (typeof title !== "string")
78
+ return new Error("WebdriverParams: title is not a string");
79
+ if (typeof timeout_ms !== "number")
80
+ return new Error("WebdriverParams: timeout_ms is not a number");
81
+
82
+ return {
83
+ command,
84
+ url: parsedUrl,
85
+ title,
86
+ timeoutMs: timeout_ms,
87
+ capabilities,
88
+ };
89
+ }
@@ -0,0 +1,104 @@
1
+ import type { LoggerAction } from "../../core/dist/jackrabbit_types.js";
2
+
3
+ export interface WebdriverSessionAction {
4
+ id: string;
5
+ }
6
+
7
+ export interface WebdriverSessionStartAction extends WebdriverSessionAction {
8
+ type: "session_start";
9
+ }
10
+
11
+ export interface WebdriverSessionErrorAction extends WebdriverSessionAction {
12
+ type: "session_error";
13
+ error: string;
14
+ }
15
+
16
+ export interface WebdriverSessionClosedAction extends WebdriverSessionAction {
17
+ type: "session_closed";
18
+ }
19
+
20
+ export interface WebdriverRunCompleteAction extends WebdriverSessionAction {
21
+ type: "run_complete";
22
+ }
23
+
24
+ export interface WebdriverLogAction extends WebdriverSessionAction {
25
+ type: "log";
26
+ loggerAction: LoggerAction;
27
+ }
28
+
29
+ export interface WebdriverCliOutpuAction extends WebdriverSessionAction {
30
+ type: "stdout";
31
+ output: string;
32
+ }
33
+
34
+ export interface WebdriverCliErrorOutpuAction extends WebdriverSessionAction {
35
+ type: "stderr";
36
+ output: string;
37
+ }
38
+
39
+ export interface WebdriverEndAction {
40
+ type: "end";
41
+ }
42
+
43
+ export interface WebdriverActionMap {
44
+ end: WebdriverEndAction;
45
+ log: WebdriverLogAction;
46
+ run_complete: WebdriverRunCompleteAction;
47
+ session_closed: WebdriverSessionClosedAction;
48
+ session_error: WebdriverSessionErrorAction;
49
+ session_start: WebdriverSessionStartAction;
50
+ stderr: WebdriverCliErrorOutpuAction;
51
+ stdout: WebdriverCliOutpuAction;
52
+ }
53
+
54
+ export type WebdriverActions =
55
+ | WebdriverCliErrorOutpuAction
56
+ | WebdriverCliOutpuAction
57
+ | WebdriverEndAction
58
+ | WebdriverLogAction
59
+ | WebdriverRunCompleteAction
60
+ | WebdriverSessionClosedAction
61
+ | WebdriverSessionErrorAction
62
+ | WebdriverSessionStartAction;
63
+
64
+ interface TypedEventBusListener<
65
+ K extends keyof WebdriverActionMap = keyof WebdriverActionMap,
66
+ > {
67
+ (action: WebdriverActionMap[K]): void;
68
+ }
69
+
70
+ interface EventBusListener extends TypedEventBusListener {
71
+ (action: WebdriverActions): void;
72
+ }
73
+
74
+ export interface EventBusInterface {
75
+ addListener<K extends keyof WebdriverActionMap>(
76
+ type: K,
77
+ listener: TypedEventBusListener<K>,
78
+ ): void;
79
+ dispatchAction(action: WebdriverActions): void;
80
+ }
81
+
82
+ export class EventBus implements EventBusInterface {
83
+ #eventMap: Map<string, EventBusListener[]> = new Map();
84
+
85
+ addListener<K extends keyof WebdriverActionMap>(
86
+ type: K,
87
+ cb: TypedEventBusListener<K>,
88
+ ) {
89
+ let listeners = this.#eventMap.get(type);
90
+ if (!listeners) {
91
+ listeners = [];
92
+ this.#eventMap.set(type, listeners);
93
+ }
94
+ listeners.push(cb as EventBusListener);
95
+ }
96
+
97
+ dispatchAction(action: WebdriverActions) {
98
+ let listeners = this.#eventMap.get(action.type);
99
+ if (listeners)
100
+ for (const listener of listeners) {
101
+ listener(action);
102
+ }
103
+ }
104
+ }