@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.
- package/AGENTS.md +21 -0
- package/README.md +142 -2
- package/bin/nwjstestrunner.js +13 -0
- package/dist/cli/nwjs-cli-launcher.d.ts +10 -0
- package/dist/cli/nwjs-cli-launcher.d.ts.map +1 -0
- package/dist/cli/nwjs-cli-launcher.js +281 -0
- package/dist/cli/nwjs-cli-launcher.js.map +1 -0
- package/dist/config/harness-config-loader.d.ts +10 -0
- package/dist/config/harness-config-loader.d.ts.map +1 -0
- package/dist/config/harness-config-loader.js +242 -0
- package/dist/config/harness-config-loader.js.map +1 -0
- package/dist/core/test-runner.d.ts +61 -0
- package/dist/core/test-runner.d.ts.map +1 -0
- package/dist/core/test-runner.js +450 -0
- package/dist/core/test-runner.js.map +1 -0
- package/dist/esm/index.d.mts +2 -0
- package/dist/esm/index.mjs +26 -0
- package/dist/harness/graphical-overlay-view.d.ts +18 -0
- package/dist/harness/graphical-overlay-view.d.ts.map +1 -0
- package/dist/harness/graphical-overlay-view.js +213 -0
- package/dist/harness/graphical-overlay-view.js.map +1 -0
- package/dist/harness/nwjs-graphical-test-harness.d.ts +40 -0
- package/dist/harness/nwjs-graphical-test-harness.d.ts.map +1 -0
- package/dist/harness/nwjs-graphical-test-harness.js +398 -0
- package/dist/harness/nwjs-graphical-test-harness.js.map +1 -0
- package/dist/harness/test-http-server.d.ts +12 -0
- package/dist/harness/test-http-server.d.ts.map +1 -0
- package/dist/harness/test-http-server.js +183 -0
- package/dist/harness/test-http-server.js.map +1 -0
- package/dist/harness/test-log-writer.d.ts +29 -0
- package/dist/harness/test-log-writer.d.ts.map +1 -0
- package/dist/harness/test-log-writer.js +173 -0
- package/dist/harness/test-log-writer.js.map +1 -0
- package/dist/harness/webview-test-controller.d.ts +18 -0
- package/dist/harness/webview-test-controller.d.ts.map +1 -0
- package/dist/harness/webview-test-controller.js +163 -0
- package/dist/harness/webview-test-controller.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +52 -0
- package/dist/index.js.map +1 -0
- package/dist/matchers/deep-equality-helper.d.ts +10 -0
- package/dist/matchers/deep-equality-helper.d.ts.map +1 -0
- package/dist/matchers/deep-equality-helper.js +188 -0
- package/dist/matchers/deep-equality-helper.js.map +1 -0
- package/dist/matchers/expect-matcher.d.ts +7 -0
- package/dist/matchers/expect-matcher.d.ts.map +1 -0
- package/dist/matchers/expect-matcher.js +261 -0
- package/dist/matchers/expect-matcher.js.map +1 -0
- package/dist/mock/jest-mock-factory.d.ts +20 -0
- package/dist/mock/jest-mock-factory.d.ts.map +1 -0
- package/dist/mock/jest-mock-factory.js +141 -0
- package/dist/mock/jest-mock-factory.js.map +1 -0
- package/dist/tests/NwjsTestRunnerSuites.d.ts +3 -0
- package/dist/tests/NwjsTestRunnerSuites.d.ts.map +1 -0
- package/dist/tests/NwjsTestRunnerSuites.js +266 -0
- package/dist/tests/NwjsTestRunnerSuites.js.map +1 -0
- package/dist/types/test-runner-types.d.ts +321 -0
- package/dist/types/test-runner-types.d.ts.map +1 -0
- package/dist/types/test-runner-types.js +3 -0
- package/dist/types/test-runner-types.js.map +1 -0
- package/package.json +56 -4
- package/runner/demo-client.html +70 -0
- package/runner/index.html +223 -0
- package/runner/package.json +27 -0
- package/src/cli/nwjs-cli-launcher.ts +243 -0
- package/src/config/harness-config-loader.ts +214 -0
- package/src/core/test-runner.ts +553 -0
- package/src/harness/graphical-overlay-view.ts +219 -0
- package/src/harness/nwjs-graphical-test-harness.ts +468 -0
- package/src/harness/test-http-server.ts +162 -0
- package/src/harness/test-log-writer.ts +154 -0
- package/src/harness/webview-test-controller.ts +192 -0
- package/src/index.ts +28 -0
- package/src/matchers/deep-equality-helper.ts +193 -0
- package/src/matchers/expect-matcher.ts +361 -0
- package/src/mock/jest-mock-factory.ts +170 -0
- package/src/tests/NwjsTestRunnerSuites.ts +267 -0
- package/src/types/test-runner-types.ts +335 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>NW.js Graphical Webview Test Harness</title>
|
|
6
|
+
<style id="nwjstestrunner-styles">
|
|
7
|
+
body, html {
|
|
8
|
+
margin: 0;
|
|
9
|
+
padding: 0;
|
|
10
|
+
width: 100vw;
|
|
11
|
+
height: 100vh;
|
|
12
|
+
background: #11111b;
|
|
13
|
+
overflow: hidden;
|
|
14
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#webview-container {
|
|
18
|
+
width: 100vw;
|
|
19
|
+
height: 100vh;
|
|
20
|
+
position: absolute;
|
|
21
|
+
top: 0;
|
|
22
|
+
left: 0;
|
|
23
|
+
z-index: 1;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
webview {
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
border: none;
|
|
30
|
+
opacity: 0.95;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#test-runner-overlay {
|
|
34
|
+
pointer-events: none;
|
|
35
|
+
position: fixed;
|
|
36
|
+
top: 0;
|
|
37
|
+
left: 0;
|
|
38
|
+
width: 100vw;
|
|
39
|
+
height: 100vh;
|
|
40
|
+
z-index: 9999;
|
|
41
|
+
background: rgba(0, 0, 0, 0.28);
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
align-items: center;
|
|
45
|
+
justify-content: center;
|
|
46
|
+
color: #cdd6f4;
|
|
47
|
+
text-align: center;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.overlay-card {
|
|
51
|
+
background: #1e1e2e;
|
|
52
|
+
border: 1px solid #313244;
|
|
53
|
+
padding: 30px 45px;
|
|
54
|
+
border-radius: 12px;
|
|
55
|
+
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.55);
|
|
56
|
+
min-width: 440px;
|
|
57
|
+
max-width: 560px;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.overlay-title {
|
|
61
|
+
font-size: 22px;
|
|
62
|
+
font-weight: bold;
|
|
63
|
+
margin-bottom: 12px;
|
|
64
|
+
color: #cba6f7;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.overlay-status {
|
|
68
|
+
font-size: 14px;
|
|
69
|
+
margin-bottom: 14px;
|
|
70
|
+
color: #89b4fa;
|
|
71
|
+
font-family: monospace;
|
|
72
|
+
white-space: nowrap;
|
|
73
|
+
overflow: hidden;
|
|
74
|
+
text-overflow: ellipsis;
|
|
75
|
+
max-width: 460px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.overlay-progress-track {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 6px;
|
|
81
|
+
background: #313244;
|
|
82
|
+
border-radius: 3px;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
margin-bottom: 16px;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.overlay-progress-fill {
|
|
88
|
+
width: 0%;
|
|
89
|
+
height: 100%;
|
|
90
|
+
background: linear-gradient(90deg, #89b4fa, #cba6f7);
|
|
91
|
+
transition: width 0.12s ease-out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.overlay-metric {
|
|
95
|
+
font-size: 18px;
|
|
96
|
+
margin: 8px 0;
|
|
97
|
+
font-weight: 600;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: space-between;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.status-running { color: #89b4fa; }
|
|
104
|
+
.status-passed { color: #a6e3a1; }
|
|
105
|
+
.status-failed { color: #f38ba8; }
|
|
106
|
+
</style>
|
|
107
|
+
</head>
|
|
108
|
+
<body>
|
|
109
|
+
<div id="webview-container">
|
|
110
|
+
<webview id="client-webview" allownw partition="trusted"></webview>
|
|
111
|
+
</div>
|
|
112
|
+
|
|
113
|
+
<div id="test-runner-overlay">
|
|
114
|
+
<div class="overlay-card">
|
|
115
|
+
<div id="overlay-title" class="overlay-title">🧪 NW.js Live Webview Test Runner</div>
|
|
116
|
+
<div id="overlay-status" class="overlay-status">Initializing test harness...</div>
|
|
117
|
+
<div class="overlay-progress-track">
|
|
118
|
+
<div id="overlay-progress-fill" class="overlay-progress-fill"></div>
|
|
119
|
+
</div>
|
|
120
|
+
<div class="overlay-metric status-running">
|
|
121
|
+
<span>Completed:</span>
|
|
122
|
+
<span id="metric-current">0 of 0</span>
|
|
123
|
+
</div>
|
|
124
|
+
<div class="overlay-metric status-passed">
|
|
125
|
+
<span>✅ Tests Passed:</span>
|
|
126
|
+
<span id="metric-passed">0</span>
|
|
127
|
+
</div>
|
|
128
|
+
<div class="overlay-metric status-failed">
|
|
129
|
+
<span>❌ Tests Failed:</span>
|
|
130
|
+
<span id="metric-failed">0</span>
|
|
131
|
+
</div>
|
|
132
|
+
</div>
|
|
133
|
+
</div>
|
|
134
|
+
|
|
135
|
+
<script>
|
|
136
|
+
// Bootstrap logger & initialization watchdog (isolated in its own <script> block so even a syntax error in the main harness script is caught and logged)
|
|
137
|
+
(function() {
|
|
138
|
+
try {
|
|
139
|
+
const fs = require('fs');
|
|
140
|
+
const path = require('path');
|
|
141
|
+
const projectRootDir = process.env.NWJS_TEST_PROJECT_ROOT || process.cwd();
|
|
142
|
+
const defaultResultsDir = path.join(projectRootDir, 'testrunner', 'test-results');
|
|
143
|
+
const testLogPath = process.env.NWJS_TEST_LOG_PATH || path.join(defaultResultsDir, 'desktop-tests.log');
|
|
144
|
+
fs.mkdirSync(path.dirname(testLogPath), { recursive: true });
|
|
145
|
+
|
|
146
|
+
window.__testLogPath = testLogPath;
|
|
147
|
+
window.__harnessInitialized = false;
|
|
148
|
+
window.__harnessCompleted = false;
|
|
149
|
+
|
|
150
|
+
// Immediately write "Tests interupted" on startup so if the window is closed by the user or crashes early, there is always readable output
|
|
151
|
+
fs.writeFileSync(
|
|
152
|
+
testLogPath,
|
|
153
|
+
[
|
|
154
|
+
'==================================================',
|
|
155
|
+
'NW.js Desktop Client Test Harness Log',
|
|
156
|
+
'==================================================',
|
|
157
|
+
'Started at: ' + new Date().toISOString(),
|
|
158
|
+
'Status: Tests interupted'
|
|
159
|
+
].join('\n'),
|
|
160
|
+
'utf-8'
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
window.__appendBootstrapFailure = function(source, err) {
|
|
164
|
+
try {
|
|
165
|
+
const msg = '[ERROR][' + source + '] ' + (err && (err.stack || err.message) ? (err.stack || err.message) : String(err));
|
|
166
|
+
fs.writeFileSync(
|
|
167
|
+
testLogPath,
|
|
168
|
+
[
|
|
169
|
+
'==================================================',
|
|
170
|
+
'NW.js Desktop Client Test Harness Log',
|
|
171
|
+
'==================================================',
|
|
172
|
+
'Interrupted at: ' + new Date().toISOString(),
|
|
173
|
+
'Status: Tests interupted',
|
|
174
|
+
'FAILURES & ERRORS:',
|
|
175
|
+
msg
|
|
176
|
+
].join('\n'),
|
|
177
|
+
'utf-8'
|
|
178
|
+
);
|
|
179
|
+
} catch (_e) {}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
window.__initWatchdogTimer = setTimeout(function() {
|
|
183
|
+
if (!window.__harnessInitialized) {
|
|
184
|
+
window.__appendBootstrapFailure('InitializationTimeout', 'Tests interupted: Test runner stuck at initializing and timed out after 8 seconds.');
|
|
185
|
+
process.exit(1);
|
|
186
|
+
}
|
|
187
|
+
}, 8000);
|
|
188
|
+
|
|
189
|
+
window.onerror = function(message, source, lineno, colno, error) {
|
|
190
|
+
if (!window.__harnessInitialized) {
|
|
191
|
+
window.__appendBootstrapFailure('BootstrapWindowOnError', error || (message + ' (' + source + ':' + lineno + ':' + colno + ')'));
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
} catch (_bootErr) {}
|
|
196
|
+
})();
|
|
197
|
+
</script>
|
|
198
|
+
<script>
|
|
199
|
+
(function() {
|
|
200
|
+
try {
|
|
201
|
+
const path = require('path');
|
|
202
|
+
const pkgRoot = process.env.NWJS_TEST_PACKAGE_ROOT || path.resolve(process.cwd());
|
|
203
|
+
let runnerLib;
|
|
204
|
+
try {
|
|
205
|
+
runnerLib = require(path.join(pkgRoot, 'dist', 'index.js'));
|
|
206
|
+
} catch (_e) {
|
|
207
|
+
runnerLib = require('@nexustools/nwjstestrunner');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const { HarnessConfigLoader, NwjsGraphicalTestHarness } = runnerLib;
|
|
211
|
+
const config = HarnessConfigLoader.loadConfig();
|
|
212
|
+
const harness = new NwjsGraphicalTestHarness(config);
|
|
213
|
+
harness.start();
|
|
214
|
+
} catch (err) {
|
|
215
|
+
if (window.__appendBootstrapFailure) {
|
|
216
|
+
window.__appendBootstrapFailure('BootstrapLoadError', err);
|
|
217
|
+
}
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
})();
|
|
221
|
+
</script>
|
|
222
|
+
</body>
|
|
223
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nwjstestrunner-window",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.html",
|
|
5
|
+
"node-remote": [
|
|
6
|
+
"http://*",
|
|
7
|
+
"https://*"
|
|
8
|
+
],
|
|
9
|
+
"chromium-args": "--allow-file-access-from-files --allow-file-access",
|
|
10
|
+
"window": {
|
|
11
|
+
"title": "NW.js Graphical Webview Test Harness",
|
|
12
|
+
"width": 1280,
|
|
13
|
+
"height": 850,
|
|
14
|
+
"position": "center",
|
|
15
|
+
"show": true
|
|
16
|
+
},
|
|
17
|
+
"webview": {
|
|
18
|
+
"partitions": [
|
|
19
|
+
{
|
|
20
|
+
"name": "trusted",
|
|
21
|
+
"accessible_resources": [
|
|
22
|
+
"*"
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { HarnessConfigLoader } from '../config/harness-config-loader';
|
|
5
|
+
import { NwjsCliOptions } from '../types/test-runner-types';
|
|
6
|
+
|
|
7
|
+
export class NwjsCliLauncher {
|
|
8
|
+
public static parseCliArgs(argv: string[]): NwjsCliOptions & { help?: boolean } {
|
|
9
|
+
const options: NwjsCliOptions & { help?: boolean } = {
|
|
10
|
+
testFiles: []
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < argv.length; i++) {
|
|
14
|
+
const arg = argv[i];
|
|
15
|
+
if (arg === '--help' || arg === '-h') {
|
|
16
|
+
options.help = true;
|
|
17
|
+
} else if ((arg === '--project-root' || arg === '-r') && argv[i + 1]) {
|
|
18
|
+
options.projectRootDir = argv[++i];
|
|
19
|
+
} else if (arg.startsWith('--project-root=')) {
|
|
20
|
+
options.projectRootDir = arg.slice('--project-root='.length);
|
|
21
|
+
} else if ((arg === '--config' || arg === '-c') && argv[i + 1]) {
|
|
22
|
+
options.configPath = argv[++i];
|
|
23
|
+
} else if (arg.startsWith('--config=')) {
|
|
24
|
+
options.configPath = arg.slice('--config='.length);
|
|
25
|
+
} else if ((arg === '--filter' || arg === '-t') && argv[i + 1]) {
|
|
26
|
+
options.testFilter = argv[++i];
|
|
27
|
+
} else if (arg.startsWith('--filter=')) {
|
|
28
|
+
options.testFilter = arg.slice('--filter='.length);
|
|
29
|
+
} else if ((arg === '--log-path' || arg === '-o') && argv[i + 1]) {
|
|
30
|
+
options.logPath = argv[++i];
|
|
31
|
+
} else if (arg.startsWith('--log-path=')) {
|
|
32
|
+
options.logPath = arg.slice('--log-path='.length);
|
|
33
|
+
} else if (arg === '--title' && argv[i + 1]) {
|
|
34
|
+
options.title = argv[++i];
|
|
35
|
+
} else if (arg.startsWith('--title=')) {
|
|
36
|
+
options.title = arg.slice('--title='.length);
|
|
37
|
+
} else if (arg === '--webview-entry' && argv[i + 1]) {
|
|
38
|
+
options.webviewEntryHtml = argv[++i];
|
|
39
|
+
} else if (arg.startsWith('--webview-entry=')) {
|
|
40
|
+
options.webviewEntryHtml = arg.slice('--webview-entry='.length);
|
|
41
|
+
} else if (arg === '--enable-webview') {
|
|
42
|
+
options.enableWebview = true;
|
|
43
|
+
} else if (arg === '--no-webview') {
|
|
44
|
+
options.enableWebview = false;
|
|
45
|
+
} else if (arg === '--init-timeout' && argv[i + 1]) {
|
|
46
|
+
options.initTimeoutMs = Number(argv[++i]);
|
|
47
|
+
} else if (arg.startsWith('--init-timeout=')) {
|
|
48
|
+
options.initTimeoutMs = Number(arg.slice('--init-timeout='.length));
|
|
49
|
+
} else if (arg === '--timeout' && argv[i + 1]) {
|
|
50
|
+
options.globalTimeoutMs = Number(argv[++i]);
|
|
51
|
+
} else if (arg.startsWith('--timeout=')) {
|
|
52
|
+
options.globalTimeoutMs = Number(arg.slice('--timeout='.length));
|
|
53
|
+
} else if (arg === '--user-data-dir' && argv[i + 1]) {
|
|
54
|
+
options.userDataDir = argv[++i];
|
|
55
|
+
} else if (arg.startsWith('--user-data-dir=')) {
|
|
56
|
+
options.userDataDir = arg.slice('--user-data-dir='.length);
|
|
57
|
+
} else if (arg === '--nw-path' && argv[i + 1]) {
|
|
58
|
+
options.nwExecutablePath = argv[++i];
|
|
59
|
+
} else if (arg.startsWith('--nw-path=')) {
|
|
60
|
+
options.nwExecutablePath = arg.slice('--nw-path='.length);
|
|
61
|
+
} else if (arg === '--keep-open') {
|
|
62
|
+
options.keepOpen = true;
|
|
63
|
+
} else if (arg === '--test-files' && argv[i + 1]) {
|
|
64
|
+
const parts = argv[++i]
|
|
65
|
+
.split(',')
|
|
66
|
+
.map((s) => s.trim())
|
|
67
|
+
.filter(Boolean);
|
|
68
|
+
options.testFiles!.push(...parts);
|
|
69
|
+
} else if (!arg.startsWith('-')) {
|
|
70
|
+
options.testFiles!.push(arg);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (options.testFiles && options.testFiles.length === 0) {
|
|
75
|
+
delete options.testFiles;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return options;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
public static getHelpText(): string {
|
|
82
|
+
return [
|
|
83
|
+
'Usage: nwjstestrunner [options] [testFilePatterns...]',
|
|
84
|
+
'',
|
|
85
|
+
'Options:',
|
|
86
|
+
' -r, --project-root <dir> Project root directory (default: process.cwd())',
|
|
87
|
+
' -c, --config <file> Path to nwjstestrunner.config.{cjs,js,json}',
|
|
88
|
+
' -t, --filter <pattern> Run only tests matching the given substring',
|
|
89
|
+
' -o, --log-path <file> Output path for desktop-tests.log',
|
|
90
|
+
' --title <title> Window & overlay card title',
|
|
91
|
+
' --webview-entry <html> Entry HTML file to load into <webview>',
|
|
92
|
+
' --enable-webview Force enable <webview> client loading',
|
|
93
|
+
' --no-webview Disable <webview> and run Jest unit suites only',
|
|
94
|
+
' --init-timeout <ms> Watchdog initialization timeout (default: 8000)',
|
|
95
|
+
' --timeout <ms> Global harness deadline timeout (default: 35000)',
|
|
96
|
+
' --user-data-dir <dir> Custom Chromium user-data-dir for NW.js',
|
|
97
|
+
' --nw-path <executable> Explicit path to NW.js executable',
|
|
98
|
+
' --keep-open Keep the graphical NW.js window open after tests finish',
|
|
99
|
+
' -h, --help Display this help message'
|
|
100
|
+
].join('\n');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
public static resolveNwExecutable(projectRootDir: string, explicitPath?: string): string {
|
|
104
|
+
if (explicitPath && fs.existsSync(explicitPath)) {
|
|
105
|
+
return path.resolve(explicitPath);
|
|
106
|
+
}
|
|
107
|
+
if (process.env.NWJS_EXECUTABLE_PATH && fs.existsSync(process.env.NWJS_EXECUTABLE_PATH)) {
|
|
108
|
+
return path.resolve(process.env.NWJS_EXECUTABLE_PATH);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const packageRoot = path.resolve(__dirname, '..', '..');
|
|
112
|
+
const searchPaths = [projectRootDir, packageRoot, process.cwd()];
|
|
113
|
+
const isWin = process.platform === 'win32';
|
|
114
|
+
|
|
115
|
+
// Prefer direct native NW.js executable (nw.exe / nw) inside node_modules/nw/nwjs* first
|
|
116
|
+
for (const base of searchPaths) {
|
|
117
|
+
let dir = path.resolve(base);
|
|
118
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
119
|
+
const nwPkgDir = path.join(dir, 'node_modules', 'nw');
|
|
120
|
+
if (fs.existsSync(nwPkgDir) && fs.statSync(nwPkgDir).isDirectory()) {
|
|
121
|
+
try {
|
|
122
|
+
const subEntries = fs.readdirSync(nwPkgDir, { withFileTypes: true });
|
|
123
|
+
for (const sub of subEntries) {
|
|
124
|
+
if ((sub.isDirectory() || sub.isSymbolicLink()) && sub.name.startsWith('nwjs')) {
|
|
125
|
+
const candidate = isWin
|
|
126
|
+
? path.join(nwPkgDir, sub.name, 'nw.exe')
|
|
127
|
+
: process.platform === 'darwin'
|
|
128
|
+
? path.join(nwPkgDir, sub.name, 'nwjs.app', 'Contents', 'MacOS', 'nwjs')
|
|
129
|
+
: path.join(nwPkgDir, sub.name, 'nw');
|
|
130
|
+
if (fs.existsSync(candidate)) {
|
|
131
|
+
return candidate;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
} catch (_e) {}
|
|
136
|
+
}
|
|
137
|
+
const parent = path.dirname(dir);
|
|
138
|
+
if (parent === dir) break;
|
|
139
|
+
dir = parent;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
for (const base of searchPaths) {
|
|
144
|
+
try {
|
|
145
|
+
const nwModPath = require.resolve('nw', { paths: [base] });
|
|
146
|
+
const nwMod = require(nwModPath);
|
|
147
|
+
if (nwMod && typeof nwMod.findpath === 'function') {
|
|
148
|
+
const binPath = nwMod.findpath();
|
|
149
|
+
if (typeof binPath === 'string' && fs.existsSync(binPath)) {
|
|
150
|
+
return binPath;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
} catch (_e) {}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
for (const base of searchPaths) {
|
|
157
|
+
let dir = path.resolve(base);
|
|
158
|
+
for (let depth = 0; depth < 4; depth++) {
|
|
159
|
+
const dotBin = isWin
|
|
160
|
+
? path.join(dir, 'node_modules', '.bin', 'nw.cmd')
|
|
161
|
+
: path.join(dir, 'node_modules', '.bin', 'nw');
|
|
162
|
+
if (typeof dotBin === 'string' && fs.existsSync(dotBin)) {
|
|
163
|
+
return dotBin;
|
|
164
|
+
}
|
|
165
|
+
const parent = path.dirname(dir);
|
|
166
|
+
if (parent === dir) break;
|
|
167
|
+
dir = parent;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return isWin ? 'nw.exe' : 'nw';
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
public static async launch(cliOptions: NwjsCliOptions = {}): Promise<number> {
|
|
175
|
+
const projectRootDir = HarnessConfigLoader.resolveProjectRoot(cliOptions.projectRootDir);
|
|
176
|
+
const configFile = HarnessConfigLoader.findConfigFile(projectRootDir, cliOptions.configPath);
|
|
177
|
+
const mergedConfig = HarnessConfigLoader.loadConfig(
|
|
178
|
+
projectRootDir,
|
|
179
|
+
configFile || undefined,
|
|
180
|
+
cliOptions
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const logPath =
|
|
184
|
+
mergedConfig.testLogPath ||
|
|
185
|
+
path.join(projectRootDir, 'testrunner', 'test-results', 'desktop-tests.log');
|
|
186
|
+
|
|
187
|
+
const userDataDir = cliOptions.userDataDir
|
|
188
|
+
? path.resolve(projectRootDir, cliOptions.userDataDir)
|
|
189
|
+
: path.join(projectRootDir, '.nw-userdata-test');
|
|
190
|
+
|
|
191
|
+
const packageRoot = path.resolve(__dirname, '..', '..');
|
|
192
|
+
const runnerDir = path.join(packageRoot, 'runner');
|
|
193
|
+
const nwExe = NwjsCliLauncher.resolveNwExecutable(projectRootDir, cliOptions.nwExecutablePath);
|
|
194
|
+
|
|
195
|
+
const args = [runnerDir, `--user-data-dir=${userDataDir}`, ...(cliOptions.headlessArgs || [])];
|
|
196
|
+
|
|
197
|
+
const env: NodeJS.ProcessEnv = {
|
|
198
|
+
...process.env,
|
|
199
|
+
NWJS_TEST_PROJECT_ROOT: projectRootDir,
|
|
200
|
+
NWJS_TEST_LOG_PATH: logPath,
|
|
201
|
+
NWJS_TEST_PACKAGE_ROOT: packageRoot,
|
|
202
|
+
NWJS_TEST_CLI_OVERRIDES: JSON.stringify(cliOptions)
|
|
203
|
+
};
|
|
204
|
+
if (configFile) {
|
|
205
|
+
env.NWJS_TEST_CONFIG_PATH = configFile;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const useShell = process.platform === 'win32' && /\.(cmd|bat)$/i.test(nwExe);
|
|
209
|
+
|
|
210
|
+
return new Promise<number>((resolve) => {
|
|
211
|
+
const child = spawn(nwExe, args, {
|
|
212
|
+
cwd: projectRootDir,
|
|
213
|
+
env,
|
|
214
|
+
stdio: 'inherit',
|
|
215
|
+
shell: useShell
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
child.on('error', (err) => {
|
|
219
|
+
console.error(`[nwjstestrunner] Failed to spawn NW.js (${nwExe}):`, err.message);
|
|
220
|
+
resolve(1);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
child.on('close', (code) => {
|
|
224
|
+
let logContent = '';
|
|
225
|
+
if (fs.existsSync(logPath)) {
|
|
226
|
+
try {
|
|
227
|
+
logContent = fs.readFileSync(logPath, 'utf-8');
|
|
228
|
+
console.log('\n' + logContent + '\n');
|
|
229
|
+
} catch (_e) {}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const logFailed =
|
|
233
|
+
!logContent ||
|
|
234
|
+
logContent.includes('Status: Tests interupted') ||
|
|
235
|
+
logContent.includes('FAILURES & ERRORS:') ||
|
|
236
|
+
!logContent.includes('SUCCESS: All tests completed cleanly.');
|
|
237
|
+
|
|
238
|
+
const exitCode = code === 0 && !logFailed ? 0 : 1;
|
|
239
|
+
resolve(exitCode);
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|