@nexustools/nwjstestrunner 0.0.0-stage → 1.0.0-beta.1

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 (79) hide show
  1. package/AGENTS.md +21 -0
  2. package/README.md +142 -2
  3. package/bin/nwjstestrunner.js +13 -0
  4. package/dist/cli/nwjs-cli-launcher.d.ts +10 -0
  5. package/dist/cli/nwjs-cli-launcher.d.ts.map +1 -0
  6. package/dist/cli/nwjs-cli-launcher.js +281 -0
  7. package/dist/cli/nwjs-cli-launcher.js.map +1 -0
  8. package/dist/config/harness-config-loader.d.ts +10 -0
  9. package/dist/config/harness-config-loader.d.ts.map +1 -0
  10. package/dist/config/harness-config-loader.js +242 -0
  11. package/dist/config/harness-config-loader.js.map +1 -0
  12. package/dist/core/test-runner.d.ts +61 -0
  13. package/dist/core/test-runner.d.ts.map +1 -0
  14. package/dist/core/test-runner.js +450 -0
  15. package/dist/core/test-runner.js.map +1 -0
  16. package/dist/esm/index.d.mts +2 -0
  17. package/dist/esm/index.mjs +26 -0
  18. package/dist/harness/graphical-overlay-view.d.ts +18 -0
  19. package/dist/harness/graphical-overlay-view.d.ts.map +1 -0
  20. package/dist/harness/graphical-overlay-view.js +213 -0
  21. package/dist/harness/graphical-overlay-view.js.map +1 -0
  22. package/dist/harness/nwjs-graphical-test-harness.d.ts +40 -0
  23. package/dist/harness/nwjs-graphical-test-harness.d.ts.map +1 -0
  24. package/dist/harness/nwjs-graphical-test-harness.js +398 -0
  25. package/dist/harness/nwjs-graphical-test-harness.js.map +1 -0
  26. package/dist/harness/test-http-server.d.ts +12 -0
  27. package/dist/harness/test-http-server.d.ts.map +1 -0
  28. package/dist/harness/test-http-server.js +183 -0
  29. package/dist/harness/test-http-server.js.map +1 -0
  30. package/dist/harness/test-log-writer.d.ts +29 -0
  31. package/dist/harness/test-log-writer.d.ts.map +1 -0
  32. package/dist/harness/test-log-writer.js +173 -0
  33. package/dist/harness/test-log-writer.js.map +1 -0
  34. package/dist/harness/webview-test-controller.d.ts +18 -0
  35. package/dist/harness/webview-test-controller.d.ts.map +1 -0
  36. package/dist/harness/webview-test-controller.js +163 -0
  37. package/dist/harness/webview-test-controller.js.map +1 -0
  38. package/dist/index.d.ts +13 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +52 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/matchers/deep-equality-helper.d.ts +10 -0
  43. package/dist/matchers/deep-equality-helper.d.ts.map +1 -0
  44. package/dist/matchers/deep-equality-helper.js +188 -0
  45. package/dist/matchers/deep-equality-helper.js.map +1 -0
  46. package/dist/matchers/expect-matcher.d.ts +7 -0
  47. package/dist/matchers/expect-matcher.d.ts.map +1 -0
  48. package/dist/matchers/expect-matcher.js +261 -0
  49. package/dist/matchers/expect-matcher.js.map +1 -0
  50. package/dist/mock/jest-mock-factory.d.ts +20 -0
  51. package/dist/mock/jest-mock-factory.d.ts.map +1 -0
  52. package/dist/mock/jest-mock-factory.js +141 -0
  53. package/dist/mock/jest-mock-factory.js.map +1 -0
  54. package/dist/tests/NwjsTestRunnerSuites.d.ts +3 -0
  55. package/dist/tests/NwjsTestRunnerSuites.d.ts.map +1 -0
  56. package/dist/tests/NwjsTestRunnerSuites.js +266 -0
  57. package/dist/tests/NwjsTestRunnerSuites.js.map +1 -0
  58. package/dist/types/test-runner-types.d.ts +321 -0
  59. package/dist/types/test-runner-types.d.ts.map +1 -0
  60. package/dist/types/test-runner-types.js +3 -0
  61. package/dist/types/test-runner-types.js.map +1 -0
  62. package/package.json +56 -4
  63. package/runner/demo-client.html +70 -0
  64. package/runner/index.html +223 -0
  65. package/runner/package.json +27 -0
  66. package/src/cli/nwjs-cli-launcher.ts +243 -0
  67. package/src/config/harness-config-loader.ts +214 -0
  68. package/src/core/test-runner.ts +553 -0
  69. package/src/harness/graphical-overlay-view.ts +219 -0
  70. package/src/harness/nwjs-graphical-test-harness.ts +468 -0
  71. package/src/harness/test-http-server.ts +162 -0
  72. package/src/harness/test-log-writer.ts +154 -0
  73. package/src/harness/webview-test-controller.ts +192 -0
  74. package/src/index.ts +28 -0
  75. package/src/matchers/deep-equality-helper.ts +193 -0
  76. package/src/matchers/expect-matcher.ts +361 -0
  77. package/src/mock/jest-mock-factory.ts +170 -0
  78. package/src/tests/NwjsTestRunnerSuites.ts +267 -0
  79. package/src/types/test-runner-types.ts +335 -0
@@ -0,0 +1,162 @@
1
+ import * as fs from 'fs';
2
+ import * as http from 'http';
3
+ import * as path from 'path';
4
+ import { TestHttpServerOptions } from '../types/test-runner-types';
5
+
6
+ const DEFAULT_MIME_TYPES: Record<string, string> = {
7
+ '.html': 'text/html; charset=utf-8',
8
+ '.htm': 'text/html; charset=utf-8',
9
+ '.js': 'application/javascript; charset=utf-8',
10
+ '.mjs': 'application/javascript; charset=utf-8',
11
+ '.cjs': 'application/javascript; charset=utf-8',
12
+ '.css': 'text/css; charset=utf-8',
13
+ '.json': 'application/json; charset=utf-8',
14
+ '.map': 'application/json; charset=utf-8',
15
+ '.svg': 'image/svg+xml',
16
+ '.png': 'image/png',
17
+ '.jpg': 'image/jpeg',
18
+ '.jpeg': 'image/jpeg',
19
+ '.gif': 'image/gif',
20
+ '.webp': 'image/webp',
21
+ '.bmp': 'image/bmp',
22
+ '.ico': 'image/x-icon',
23
+ '.woff': 'font/woff',
24
+ '.woff2': 'font/woff2',
25
+ '.ttf': 'font/ttf',
26
+ '.otf': 'font/otf',
27
+ '.wasm': 'application/wasm',
28
+ '.txt': 'text/plain; charset=utf-8',
29
+ '.xml': 'application/xml; charset=utf-8'
30
+ };
31
+
32
+ export class TestHttpServer {
33
+ private readonly options: TestHttpServerOptions;
34
+ private server: http.Server | null = null;
35
+ private boundPort = 0;
36
+
37
+ constructor(options: TestHttpServerOptions) {
38
+ this.options = options;
39
+ }
40
+
41
+ public getPort(): number {
42
+ return this.boundPort;
43
+ }
44
+
45
+ public getBaseUrl(): string {
46
+ const host = this.options.host || '127.0.0.1';
47
+ return `http://${host}:${this.boundPort}`;
48
+ }
49
+
50
+ public async start(): Promise<number> {
51
+ if (this.server && this.boundPort > 0) {
52
+ return this.boundPort;
53
+ }
54
+
55
+ const projectRootDir = path.resolve(this.options.projectRootDir);
56
+ const defaultEntry = this.options.defaultEntryFile || 'index.html';
57
+ const host = this.options.host || '127.0.0.1';
58
+ const port = this.options.port ?? 0;
59
+ const ignore404 = new Set(this.options.ignore404Paths ?? ['/favicon.ico']);
60
+ const mimeTypes: Record<string, string> = {
61
+ ...DEFAULT_MIME_TYPES,
62
+ ...(this.options.customMimeTypes || {})
63
+ };
64
+
65
+ this.server = http.createServer(async (req, res) => {
66
+ try {
67
+ const reqUrl = req.url || '/';
68
+ const rawPath = reqUrl.split('?')[0] || '/';
69
+ const reqPath = decodeURIComponent(rawPath);
70
+
71
+ if (this.options.routeHandler) {
72
+ const handled = await this.options.routeHandler(req, res, {
73
+ projectRootDir,
74
+ reqPath,
75
+ reqUrl
76
+ });
77
+ if (handled === true || res.writableEnded || res.headersSent) {
78
+ return;
79
+ }
80
+ }
81
+
82
+ const relPath =
83
+ reqPath === '/'
84
+ ? defaultEntry
85
+ : reqPath.startsWith('/')
86
+ ? reqPath.slice(1)
87
+ : reqPath;
88
+ const filePath = path.resolve(projectRootDir, relPath);
89
+
90
+ // Guard against directory traversal outside projectRootDir
91
+ const normalizedRoot = projectRootDir.endsWith(path.sep)
92
+ ? projectRootDir
93
+ : projectRootDir + path.sep;
94
+ const isInsideProject =
95
+ filePath === projectRootDir || filePath.startsWith(normalizedRoot);
96
+
97
+ if (
98
+ isInsideProject &&
99
+ relPath &&
100
+ fs.existsSync(filePath) &&
101
+ fs.statSync(filePath).isFile()
102
+ ) {
103
+ const ext = path.extname(filePath).toLowerCase();
104
+ const contentType = mimeTypes[ext] || 'application/octet-stream';
105
+ res.writeHead(200, {
106
+ 'Content-Type': contentType,
107
+ 'Access-Control-Allow-Origin': '*',
108
+ 'Cache-Control': 'no-store'
109
+ });
110
+ fs.createReadStream(filePath).pipe(res);
111
+ return;
112
+ }
113
+
114
+ if (!ignore404.has(reqPath) && this.options.onHttp404) {
115
+ this.options.onHttp404(reqPath, filePath);
116
+ }
117
+
118
+ res.writeHead(404, {
119
+ 'Content-Type': 'text/plain; charset=utf-8',
120
+ 'Access-Control-Allow-Origin': '*'
121
+ });
122
+ res.end(`404 Not Found: ${reqPath}`);
123
+ } catch (err: any) {
124
+ if (!res.headersSent) {
125
+ res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
126
+ }
127
+ res.end(`500 Internal Server Error: ${err?.message || String(err)}`);
128
+ }
129
+ });
130
+
131
+ return new Promise<number>((resolve, reject) => {
132
+ if (!this.server) {
133
+ reject(new Error('HTTP server not created'));
134
+ return;
135
+ }
136
+ this.server.once('error', (err) => reject(err));
137
+ this.server.listen(port, host, () => {
138
+ const addr = this.server?.address();
139
+ if (addr && typeof addr === 'object') {
140
+ this.boundPort = addr.port;
141
+ resolve(this.boundPort);
142
+ } else {
143
+ reject(new Error('Failed to resolve bound port for TestHttpServer'));
144
+ }
145
+ });
146
+ });
147
+ }
148
+
149
+ public async stop(): Promise<void> {
150
+ if (!this.server) return;
151
+ const srv = this.server;
152
+ this.server = null;
153
+ this.boundPort = 0;
154
+ return new Promise<void>((resolve) => {
155
+ try {
156
+ srv.close(() => resolve());
157
+ } catch (_e) {
158
+ resolve();
159
+ }
160
+ });
161
+ }
162
+ }
@@ -0,0 +1,154 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+
4
+ export class TestLogWriter {
5
+ private readonly logFilePath: string;
6
+ private readonly headerTitle: string;
7
+ private readonly errorLogs: string[] = [];
8
+ private totalTests = 0;
9
+ private passedCount = 0;
10
+ private failedCount = 0;
11
+
12
+ constructor(logFilePath: string, headerTitle = 'NW.js Desktop Client Test Harness Log') {
13
+ this.logFilePath = path.resolve(logFilePath);
14
+ this.headerTitle = headerTitle;
15
+ this.ensureDirectory();
16
+ }
17
+
18
+ private ensureDirectory(): void {
19
+ try {
20
+ fs.mkdirSync(path.dirname(this.logFilePath), { recursive: true });
21
+ } catch (_e) {}
22
+ }
23
+
24
+ public getLogFilePath(): string {
25
+ return this.logFilePath;
26
+ }
27
+
28
+ public getErrorLogs(): ReadonlyArray<string> {
29
+ return this.errorLogs;
30
+ }
31
+
32
+ public getMetrics(): { totalTests: number; passedCount: number; failedCount: number } {
33
+ return {
34
+ totalTests: this.totalTests,
35
+ passedCount: this.passedCount,
36
+ failedCount: this.failedCount
37
+ };
38
+ }
39
+
40
+ public setTotalTests(total: number): void {
41
+ this.totalTests = Math.max(0, total);
42
+ }
43
+
44
+ public setCounts(passed: number, failed: number): void {
45
+ this.passedCount = Math.max(0, passed);
46
+ this.failedCount = Math.max(0, failed);
47
+ }
48
+
49
+ public incrementPassed(delta = 1): void {
50
+ this.passedCount += delta;
51
+ }
52
+
53
+ public incrementFailed(delta = 1): void {
54
+ this.failedCount += delta;
55
+ }
56
+
57
+ public writeInitialInterruptedBanner(): void {
58
+ try {
59
+ this.ensureDirectory();
60
+ const lines = [
61
+ '==================================================',
62
+ this.headerTitle,
63
+ '==================================================',
64
+ `Started at: ${new Date().toISOString()}`,
65
+ 'Status: Tests interupted'
66
+ ];
67
+ fs.writeFileSync(this.logFilePath, lines.join('\n'), 'utf-8');
68
+ } catch (_e) {}
69
+ }
70
+
71
+ public writeProgressSnapshot(statusLabel = 'Tests interupted'): void {
72
+ try {
73
+ this.ensureDirectory();
74
+ const lines = [
75
+ '==================================================',
76
+ this.headerTitle,
77
+ '==================================================',
78
+ `Updated at: ${new Date().toISOString()}`,
79
+ `Status: ${statusLabel || 'Tests interupted'}`,
80
+ `Total Tests: ${this.totalTests}`,
81
+ `Passed: ${this.passedCount}`,
82
+ `Failed: ${this.failedCount}`,
83
+ '=================================================='
84
+ ];
85
+ if (this.errorLogs.length > 0) {
86
+ lines.push('FAILURES & ERRORS:');
87
+ for (const err of this.errorLogs) {
88
+ lines.push(err);
89
+ }
90
+ }
91
+ fs.writeFileSync(this.logFilePath, lines.join('\n'), 'utf-8');
92
+ } catch (_e) {}
93
+ }
94
+
95
+ public recordError(source: string, err: any): string {
96
+ const detail =
97
+ err && typeof err === 'object' && (err.stack || err.message)
98
+ ? err.stack || err.message
99
+ : String(err);
100
+ const errMsg = `[ERROR][${source}] ${detail}`;
101
+ this.errorLogs.push(errMsg);
102
+ console.error(errMsg);
103
+ this.writeProgressSnapshot('Tests interupted (Running with errors)');
104
+ return errMsg;
105
+ }
106
+
107
+ public writeFinalSummary(): { exitCode: number; logText: string } {
108
+ const totalPassed = this.passedCount;
109
+ let totalFailed = this.failedCount;
110
+
111
+ if (this.totalTests > 0 && totalPassed + totalFailed !== this.totalTests) {
112
+ this.recordError(
113
+ 'TestCountMismatchError',
114
+ `Test count mismatch: total expected ${this.totalTests}, but executed passed (${totalPassed}) + failed (${totalFailed}) = ${totalPassed + totalFailed}`
115
+ );
116
+ }
117
+
118
+ if (this.errorLogs.length > 0 && totalFailed === 0) {
119
+ totalFailed = 1;
120
+ }
121
+
122
+ const totalAll = this.totalTests || totalPassed + totalFailed;
123
+ const testLogLines = [
124
+ '==================================================',
125
+ this.headerTitle,
126
+ '==================================================',
127
+ `Completed at: ${new Date().toISOString()}`,
128
+ `Total Tests: ${totalAll}`,
129
+ `Passed: ${totalPassed}`,
130
+ `Failed: ${totalFailed}`,
131
+ '=================================================='
132
+ ];
133
+
134
+ if (this.errorLogs.length > 0) {
135
+ testLogLines.push('FAILURES & ERRORS:');
136
+ for (const err of this.errorLogs) {
137
+ testLogLines.push(err);
138
+ }
139
+ } else {
140
+ testLogLines.push('SUCCESS: All tests completed cleanly.');
141
+ }
142
+
143
+ const logText = testLogLines.join('\n');
144
+ try {
145
+ this.ensureDirectory();
146
+ fs.writeFileSync(this.logFilePath, logText, 'utf-8');
147
+ } catch (e) {
148
+ console.error('Failed to write test log:', e);
149
+ }
150
+
151
+ const exitCode = this.errorLogs.length > 0 || totalFailed > 0 ? 1 : 0;
152
+ return { exitCode, logText };
153
+ }
154
+ }
@@ -0,0 +1,192 @@
1
+ import {
2
+ WaitForScreenLoadOptions,
3
+ WebviewExecutionResult
4
+ } from '../types/test-runner-types';
5
+
6
+ export class WebviewTestController {
7
+ private readonly webview: any;
8
+ private readonly onError?: (source: string, err: any) => void;
9
+ private readonly onLoadAbort?: (reason: string) => void;
10
+ private readonly consoleErrorLevel: number;
11
+
12
+ constructor(
13
+ webview: any,
14
+ options?: {
15
+ consoleErrorLevel?: number;
16
+ onError?: (source: string, err: any) => void;
17
+ onLoadAbort?: (reason: string) => void;
18
+ }
19
+ ) {
20
+ this.webview = webview;
21
+ this.consoleErrorLevel = options?.consoleErrorLevel ?? 2;
22
+ this.onError = options?.onError;
23
+ this.onLoadAbort = options?.onLoadAbort;
24
+ }
25
+
26
+ public attachEventListeners(onContentLoad: () => void): void {
27
+ if (!this.webview || typeof this.webview.addEventListener !== 'function') {
28
+ return;
29
+ }
30
+
31
+ this.webview.addEventListener('console-message', (e: any) => {
32
+ if (e && typeof e.level === 'number' && e.level >= this.consoleErrorLevel) {
33
+ this.onError?.(`WebviewConsole[L${e.level}]`, e.message);
34
+ }
35
+ });
36
+
37
+ this.webview.addEventListener('loadabort', (e: any) => {
38
+ const reason = e?.reason || 'Unknown reason';
39
+ this.onError?.('WebviewLoadAbort', `Failed to load client in webview: ${reason}`);
40
+ this.onLoadAbort?.(reason);
41
+ });
42
+
43
+ this.webview.addEventListener('contentload', () => {
44
+ onContentLoad();
45
+ });
46
+ }
47
+
48
+ public navigate(url: string): void {
49
+ if (this.webview) {
50
+ this.webview.src = url;
51
+ }
52
+ }
53
+
54
+ public execScriptWithTimeout(code: string, timeoutMs = 1500): Promise<any> {
55
+ return new Promise((resolve) => {
56
+ if (!this.webview || typeof this.webview.executeScript !== 'function') {
57
+ resolve(null);
58
+ return;
59
+ }
60
+
61
+ let done = false;
62
+ const timer = setTimeout(() => {
63
+ if (!done) {
64
+ done = true;
65
+ resolve(null);
66
+ }
67
+ }, timeoutMs);
68
+
69
+ try {
70
+ this.webview.executeScript({ code }, (results: any[]) => {
71
+ if (!done) {
72
+ done = true;
73
+ clearTimeout(timer);
74
+ resolve(results && results[0] !== undefined ? results[0] : null);
75
+ }
76
+ });
77
+ } catch (_e) {
78
+ if (!done) {
79
+ done = true;
80
+ clearTimeout(timer);
81
+ resolve(null);
82
+ }
83
+ }
84
+ });
85
+ }
86
+
87
+ public async waitForScreenLoad(options: WaitForScreenLoadOptions = {}): Promise<boolean> {
88
+ const timeoutMs = options.timeoutMs ?? 4000;
89
+ const pollIntervalMs = options.pollIntervalMs ?? 100;
90
+ const requiredIds = options.requiredElementIds ?? [];
91
+ const requiredSelectors = options.requiredSelectors ?? [];
92
+ const customPredicate = options.customPredicateCode;
93
+
94
+ const maxAttempts = Math.max(1, Math.ceil(timeoutMs / pollIntervalMs));
95
+ const checkScript = `
96
+ (function() {
97
+ if (document.readyState !== 'complete' && document.readyState !== 'interactive') {
98
+ return false;
99
+ }
100
+ const reqIds = ${JSON.stringify(requiredIds)};
101
+ for (let i = 0; i < reqIds.length; i++) {
102
+ if (!document.getElementById(reqIds[i])) return false;
103
+ }
104
+ const reqSels = ${JSON.stringify(requiredSelectors)};
105
+ for (let i = 0; i < reqSels.length; i++) {
106
+ if (!document.querySelector(reqSels[i])) return false;
107
+ }
108
+ ${customPredicate ? `return Boolean(${customPredicate});` : 'return true;'}
109
+ })();
110
+ `;
111
+
112
+ for (let i = 0; i < maxAttempts; i++) {
113
+ const res = await this.execScriptWithTimeout(checkScript, Math.min(500, timeoutMs));
114
+ if (res === true || res === 'true') {
115
+ return true;
116
+ }
117
+ await new Promise((r) => setTimeout(r, pollIntervalMs));
118
+ }
119
+ return false;
120
+ }
121
+
122
+ public async executeWebviewTest(
123
+ codeOrFn: string | (() => Promise<void> | void),
124
+ timeoutMs = 4000
125
+ ): Promise<WebviewExecutionResult> {
126
+ const bodyExpr =
127
+ typeof codeOrFn === 'function' ? `await (${codeOrFn.toString()})();` : codeOrFn;
128
+
129
+ return new Promise<WebviewExecutionResult>((resolve) => {
130
+ let finished = false;
131
+
132
+ const timer = setTimeout(() => {
133
+ if (!finished) {
134
+ finished = true;
135
+ resolve({
136
+ ok: false,
137
+ errors: [`Test timed out after ${timeoutMs}ms`],
138
+ error: `Test timed out after ${timeoutMs}ms`
139
+ });
140
+ }
141
+ }, timeoutMs);
142
+
143
+ const wrappedCode = `
144
+ (function() {
145
+ window.__testDone = false;
146
+ window.__testErrors = [];
147
+ function runInMainWorld(jsBody) {
148
+ const s = document.createElement('script');
149
+ s.textContent = '(function(){ ' + jsBody + ' })();';
150
+ document.body.appendChild(s);
151
+ s.remove();
152
+ }
153
+ (async function() {
154
+ try {
155
+ ${bodyExpr}
156
+ } catch (e) {
157
+ window.__testErrors.push(e && (e.stack || e.message) ? (e.stack || e.message) : String(e));
158
+ } finally {
159
+ window.__testDone = true;
160
+ }
161
+ })();
162
+ return 'started';
163
+ })();
164
+ `;
165
+
166
+ this.execScriptWithTimeout(wrappedCode, 1000).then(() => {
167
+ const pollInterval = setInterval(async () => {
168
+ if (finished) {
169
+ clearInterval(pollInterval);
170
+ return;
171
+ }
172
+ const rawData = await this.execScriptWithTimeout(
173
+ `(function() { return JSON.stringify({ done: !!window.__testDone, errors: window.__testErrors || [] }); })();`,
174
+ 500
175
+ );
176
+ try {
177
+ const data = rawData ? JSON.parse(rawData) : null;
178
+ if (data && data.done) {
179
+ finished = true;
180
+ clearTimeout(timer);
181
+ clearInterval(pollInterval);
182
+ resolve({
183
+ ok: Array.isArray(data.errors) && data.errors.length === 0,
184
+ errors: Array.isArray(data.errors) ? data.errors : []
185
+ });
186
+ }
187
+ } catch (_e) {}
188
+ }, 75);
189
+ });
190
+ });
191
+ }
192
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ export * from './types/test-runner-types';
2
+ export { DeepEqualityHelper } from './matchers/deep-equality-helper';
3
+ export { ExpectMatcherBuilder, expect } from './matchers/expect-matcher';
4
+ export { JestMockFactory, jest } from './mock/jest-mock-factory';
5
+ export {
6
+ SuiteBuilderScope,
7
+ TestRunner,
8
+ DescribeFunction,
9
+ ItFunction,
10
+ describe,
11
+ it,
12
+ test,
13
+ beforeAll,
14
+ afterAll,
15
+ beforeEach,
16
+ afterEach,
17
+ installJestGlobals
18
+ } from './core/test-runner';
19
+ export { TestLogWriter } from './harness/test-log-writer';
20
+ export { TestHttpServer } from './harness/test-http-server';
21
+ export { GraphicalOverlayView } from './harness/graphical-overlay-view';
22
+ export { WebviewTestController } from './harness/webview-test-controller';
23
+ export {
24
+ NwjsGraphicalTestHarness,
25
+ runNwjsTestHarness
26
+ } from './harness/nwjs-graphical-test-harness';
27
+ export { HarnessConfigLoader } from './config/harness-config-loader';
28
+ export { NwjsCliLauncher } from './cli/nwjs-cli-launcher';