@brillout/test-e2e 0.1.3
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/bin.js +2 -0
- package/dist/Logs.d.ts +29 -0
- package/dist/Logs.js +77 -0
- package/dist/buildTs.d.ts +2 -0
- package/dist/buildTs.js +22 -0
- package/dist/chai/JestChaiExpect.d.ts +2 -0
- package/dist/chai/JestChaiExpect.js +27 -0
- package/dist/chai/expect.d.ts +2 -0
- package/dist/chai/expect.js +30 -0
- package/dist/chai/types.d.ts +15 -0
- package/dist/chai/types.js +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +43 -0
- package/dist/editFile.d.ts +6 -0
- package/dist/editFile.js +49 -0
- package/dist/findTestFiles.d.ts +3 -0
- package/dist/findTestFiles.js +26 -0
- package/dist/getBrowser.d.ts +3 -0
- package/dist/getBrowser.js +15 -0
- package/dist/getTestInfo.d.ts +21 -0
- package/dist/getTestInfo.js +14 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +24 -0
- package/dist/logProgress.d.ts +2 -0
- package/dist/logProgress.js +41 -0
- package/dist/page.d.ts +3 -0
- package/dist/page.js +13 -0
- package/dist/runTestSuites.d.ts +6 -0
- package/dist/runTestSuites.js +41 -0
- package/dist/runTests.d.ts +3 -0
- package/dist/runTests.js +111 -0
- package/dist/setup.d.ts +36 -0
- package/dist/setup.js +465 -0
- package/dist/test.d.ts +2 -0
- package/dist/test.js +15 -0
- package/dist/utils/assert.d.ts +14 -0
- package/dist/utils/assert.js +72 -0
- package/dist/utils/createErrorWithCleanStackTrace.d.ts +2 -0
- package/dist/utils/createErrorWithCleanStackTrace.js +43 -0
- package/dist/utils/findFiles.d.ts +7 -0
- package/dist/utils/findFiles.js +46 -0
- package/dist/utils/humanizeTime.d.ts +1 -0
- package/dist/utils/humanizeTime.js +15 -0
- package/dist/utils/index.d.ts +6 -0
- package/dist/utils/index.js +22 -0
- package/dist/utils/isTTY.d.ts +1 -0
- package/dist/utils/isTTY.js +5 -0
- package/dist/utils/projectInfo.d.ts +5 -0
- package/dist/utils/projectInfo.js +8 -0
- package/dist/utils/runCommand.d.ts +6 -0
- package/dist/utils/runCommand.js +46 -0
- package/dist/utils/sleep.d.ts +2 -0
- package/dist/utils/sleep.js +7 -0
- package/package.json +37 -0
package/dist/runTests.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runTests = void 0;
|
|
4
|
+
const getTestInfo_1 = require("./getTestInfo");
|
|
5
|
+
const logProgress_1 = require("./logProgress");
|
|
6
|
+
const Logs_1 = require("./Logs");
|
|
7
|
+
const utils_1 = require("./utils");
|
|
8
|
+
const expect_1 = require("./chai/expect");
|
|
9
|
+
const iconWarning = '⚠️';
|
|
10
|
+
const iconSuccess = '✅';
|
|
11
|
+
const iconFailure = '❌';
|
|
12
|
+
async function runTests(browser) {
|
|
13
|
+
const testInfo = (0, getTestInfo_1.getTestInfo)();
|
|
14
|
+
if (utils_1.isTTY) {
|
|
15
|
+
console.log();
|
|
16
|
+
console.log(testInfo.testFile);
|
|
17
|
+
}
|
|
18
|
+
const page = await browser.newPage();
|
|
19
|
+
testInfo.page = page;
|
|
20
|
+
// Set when user calls `skip()`
|
|
21
|
+
if (testInfo.skipped) {
|
|
22
|
+
(0, utils_1.assertUsage)(!testInfo.runWasCalled, 'You cannot call `run()` after calling `skip()`');
|
|
23
|
+
(0, utils_1.assertUsage)(testInfo.tests === undefined, 'You cannot call `test()` after calling `skip()`');
|
|
24
|
+
logTestSuiteResult(false);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
// Set when user calls `run()`
|
|
28
|
+
(0, utils_1.assert)(testInfo.runWasCalled);
|
|
29
|
+
(0, utils_1.assert)(testInfo.beforeAll);
|
|
30
|
+
(0, utils_1.assert)(testInfo.afterAll);
|
|
31
|
+
(0, utils_1.assert)(testInfo.afterEach);
|
|
32
|
+
(0, utils_1.assert)(testInfo.testTimeout);
|
|
33
|
+
// Set when user calls `test()`
|
|
34
|
+
(0, utils_1.assert)(testInfo.tests);
|
|
35
|
+
await testInfo.beforeAll();
|
|
36
|
+
for (const { testDesc, testFn } of testInfo.tests) {
|
|
37
|
+
const done = (0, logProgress_1.logProgress)(`[test] ${testDesc}`);
|
|
38
|
+
let err;
|
|
39
|
+
try {
|
|
40
|
+
await runTest(testFn, testInfo.testTimeout);
|
|
41
|
+
}
|
|
42
|
+
catch (err_) {
|
|
43
|
+
err = err_;
|
|
44
|
+
}
|
|
45
|
+
done(!!err);
|
|
46
|
+
testInfo.afterEach(!!err);
|
|
47
|
+
const browserErrors = Logs_1.Logs.getBrowserErrors();
|
|
48
|
+
const isFailure = err || browserErrors.length > 0;
|
|
49
|
+
if (isFailure) {
|
|
50
|
+
Logs_1.Logs.flush();
|
|
51
|
+
}
|
|
52
|
+
Logs_1.Logs.clear();
|
|
53
|
+
if (isFailure) {
|
|
54
|
+
logTestSuiteResult(false);
|
|
55
|
+
if (browserErrors.length === 0) {
|
|
56
|
+
throw err;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
if (err) {
|
|
60
|
+
console.error(err);
|
|
61
|
+
}
|
|
62
|
+
// Display all browser errors
|
|
63
|
+
(0, expect_1.expect)(browserErrors).deep.equal([]);
|
|
64
|
+
(0, utils_1.assert)(false);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
await testInfo.afterAll();
|
|
69
|
+
await page.close();
|
|
70
|
+
logTestSuiteResult(true);
|
|
71
|
+
}
|
|
72
|
+
exports.runTests = runTests;
|
|
73
|
+
function runTest(testFn, testTimeout) {
|
|
74
|
+
let resolve;
|
|
75
|
+
let reject;
|
|
76
|
+
const promise = new Promise((resolve_, reject_) => {
|
|
77
|
+
resolve = resolve_;
|
|
78
|
+
reject = reject_;
|
|
79
|
+
});
|
|
80
|
+
const timeout = setTimeout(() => {
|
|
81
|
+
reject(new Error(`[test][timeout after ${(0, utils_1.humanizeTime)(testTimeout)}]`));
|
|
82
|
+
}, testTimeout);
|
|
83
|
+
const ret = testFn();
|
|
84
|
+
(async () => {
|
|
85
|
+
try {
|
|
86
|
+
await ret;
|
|
87
|
+
resolve();
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
reject(err);
|
|
91
|
+
}
|
|
92
|
+
finally {
|
|
93
|
+
clearTimeout(timeout);
|
|
94
|
+
}
|
|
95
|
+
})();
|
|
96
|
+
return promise;
|
|
97
|
+
}
|
|
98
|
+
function logTestSuiteResult(success) {
|
|
99
|
+
const testInfo = (0, getTestInfo_1.getTestInfo)();
|
|
100
|
+
if (success) {
|
|
101
|
+
(0, utils_1.assert)(!testInfo.skipped);
|
|
102
|
+
console.log(`${iconSuccess} ${testInfo.testFile} (SUCCESS)`);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (testInfo.skipped) {
|
|
106
|
+
console.log(`${iconWarning} ${testInfo.testFile} (SKIPPED: ${testInfo.skipped})`);
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
console.log(`${iconFailure} ${testInfo.testFile} (FAILURE)`);
|
|
110
|
+
}
|
|
111
|
+
}
|
package/dist/setup.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { sleep } from './utils';
|
|
2
|
+
import fetch_ from 'node-fetch';
|
|
3
|
+
export { partRegex } from '@brillout/part-regex';
|
|
4
|
+
export { autoRetry };
|
|
5
|
+
export { fetchHtml };
|
|
6
|
+
export { fetch };
|
|
7
|
+
export { expectBrowserError } from './Logs';
|
|
8
|
+
export { run };
|
|
9
|
+
export { skip };
|
|
10
|
+
export { isMinNodeVersion };
|
|
11
|
+
export { isGithubAction };
|
|
12
|
+
export { isLinux };
|
|
13
|
+
export { isWindows };
|
|
14
|
+
export { isMac };
|
|
15
|
+
export { sleep };
|
|
16
|
+
export declare let urlBase: string;
|
|
17
|
+
export declare const urlBaseChange: (url: string) => string;
|
|
18
|
+
export { editFile, editFileRevert } from './editFile';
|
|
19
|
+
declare function skip(reason: string): void;
|
|
20
|
+
declare function run(cmd: string, { additionalTimeout, serverIsReadyMessage, serverIsReadyDelay, debug, cwd, }?: {
|
|
21
|
+
additionalTimeout?: number;
|
|
22
|
+
serverIsReadyMessage?: string;
|
|
23
|
+
serverIsReadyDelay?: number;
|
|
24
|
+
debug?: boolean;
|
|
25
|
+
cwd?: string;
|
|
26
|
+
}): void;
|
|
27
|
+
declare function autoRetry(test: () => void | Promise<void>, { timeout }?: {
|
|
28
|
+
timeout?: number;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
declare function fetchHtml(pathname: string): Promise<string>;
|
|
31
|
+
declare function fetch(...args: Parameters<typeof fetch_>): Promise<import("node-fetch").Response>;
|
|
32
|
+
declare function isMinNodeVersion(minNodeVersion: 14): boolean;
|
|
33
|
+
declare function isWindows(): boolean;
|
|
34
|
+
declare function isLinux(): boolean;
|
|
35
|
+
declare function isMac(): boolean;
|
|
36
|
+
declare function isGithubAction(): boolean;
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,465 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.editFileRevert = exports.editFile = exports.urlBaseChange = exports.urlBase = exports.sleep = exports.isMac = exports.isWindows = exports.isLinux = exports.isGithubAction = exports.isMinNodeVersion = exports.skip = exports.run = exports.expectBrowserError = exports.fetch = exports.fetchHtml = exports.autoRetry = exports.partRegex = void 0;
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const path_1 = require("path");
|
|
9
|
+
const utils_1 = require("./utils");
|
|
10
|
+
Object.defineProperty(exports, "sleep", { enumerable: true, get: function () { return utils_1.sleep; } });
|
|
11
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
12
|
+
const utils_2 = require("./utils");
|
|
13
|
+
const Logs_1 = require("./Logs");
|
|
14
|
+
const strip_ansi_1 = __importDefault(require("strip-ansi"));
|
|
15
|
+
const editFile_1 = require("./editFile");
|
|
16
|
+
const getTestInfo_1 = require("./getTestInfo");
|
|
17
|
+
const page_1 = require("./page");
|
|
18
|
+
const logProgress_1 = require("./logProgress");
|
|
19
|
+
var part_regex_1 = require("@brillout/part-regex");
|
|
20
|
+
Object.defineProperty(exports, "partRegex", { enumerable: true, get: function () { return part_regex_1.partRegex; } });
|
|
21
|
+
var Logs_2 = require("./Logs");
|
|
22
|
+
Object.defineProperty(exports, "expectBrowserError", { enumerable: true, get: function () { return Logs_2.expectBrowserError; } });
|
|
23
|
+
exports.urlBase = 'http://localhost:3000';
|
|
24
|
+
const urlBaseChange = (url) => (exports.urlBase = url);
|
|
25
|
+
exports.urlBaseChange = urlBaseChange;
|
|
26
|
+
var editFile_2 = require("./editFile");
|
|
27
|
+
Object.defineProperty(exports, "editFile", { enumerable: true, get: function () { return editFile_2.editFile; } });
|
|
28
|
+
Object.defineProperty(exports, "editFileRevert", { enumerable: true, get: function () { return editFile_2.editFileRevert; } });
|
|
29
|
+
const TIMEOUT_NPM_SCRIPT = 30 * 1000 * (!isGithubAction() ? 1 : isLinux() ? 1 : isWindows() ? 5 : 4);
|
|
30
|
+
const TIMEOUT_JEST = 30 * 1000 * (!isGithubAction() ? 1 : isWindows() ? 10 : 6);
|
|
31
|
+
const TIMEOUT_AUTORETRY = 10 * 1000 * (!isGithubAction() || isLinux() ? 1 : 20); // TODO reduce `20`
|
|
32
|
+
const TIMEOUT_PROCESS_TERMINATION = 10 * 1000 * (!isGithubAction() ? 1 : isLinux() ? 1 : 4);
|
|
33
|
+
const TIMEOUT_PLAYWRIGHT = TIMEOUT_JEST;
|
|
34
|
+
function skip(reason) {
|
|
35
|
+
const testInfo = (0, getTestInfo_1.getTestInfo)();
|
|
36
|
+
testInfo.skipped = reason;
|
|
37
|
+
}
|
|
38
|
+
exports.skip = skip;
|
|
39
|
+
function run(cmd, {
|
|
40
|
+
//baseUrl = '',
|
|
41
|
+
additionalTimeout = 0, serverIsReadyMessage, serverIsReadyDelay = 1000, debug = !!process.env.DEBUG, cwd, } = {}) {
|
|
42
|
+
additionalTimeout += serverIsReadyDelay;
|
|
43
|
+
const testInfo = (0, getTestInfo_1.getTestInfo)();
|
|
44
|
+
testInfo.runWasCalled = true;
|
|
45
|
+
testInfo.testCmd = cmd;
|
|
46
|
+
testInfo.testTimeout = TIMEOUT_JEST + additionalTimeout;
|
|
47
|
+
/*
|
|
48
|
+
const browser = await getBrowser()
|
|
49
|
+
const page = await browser.newPage()
|
|
50
|
+
testInfo.page = page
|
|
51
|
+
|
|
52
|
+
const { page } = testInfo
|
|
53
|
+
// Set by `./runTests`
|
|
54
|
+
assert(page)
|
|
55
|
+
*/
|
|
56
|
+
if (debug) {
|
|
57
|
+
Logs_1.Logs.flushEagerly = true;
|
|
58
|
+
}
|
|
59
|
+
const testContext = {
|
|
60
|
+
cmd,
|
|
61
|
+
cwd: cwd || getCwd(),
|
|
62
|
+
testName: getTestName(),
|
|
63
|
+
additionalTimeout,
|
|
64
|
+
serverIsReadyMessage,
|
|
65
|
+
serverIsReadyDelay,
|
|
66
|
+
debug,
|
|
67
|
+
};
|
|
68
|
+
logJestStep('run start');
|
|
69
|
+
let runProcess = null;
|
|
70
|
+
testInfo.beforeAll = async () => {
|
|
71
|
+
logJestStep('beforeAll start');
|
|
72
|
+
runProcess = await start(testContext);
|
|
73
|
+
logJestStep('run done');
|
|
74
|
+
page_1.page.on('console', onConsole);
|
|
75
|
+
page_1.page.on('pageerror', onPageError);
|
|
76
|
+
// This setting will change the default maximum time for all the methods accepting timeout option.
|
|
77
|
+
// https://playwright.dev/docs/api/class-page#page-set-default-timeout
|
|
78
|
+
page_1.page.setDefaultTimeout(TIMEOUT_PLAYWRIGHT + additionalTimeout);
|
|
79
|
+
/*
|
|
80
|
+
await bailOnTimeout(
|
|
81
|
+
async () => {
|
|
82
|
+
await page.goto(urlBase + baseUrl)
|
|
83
|
+
},
|
|
84
|
+
{ timeout: TIMEOUT_PAGE_LOAD + additionalTimeout },
|
|
85
|
+
)
|
|
86
|
+
*/
|
|
87
|
+
logJestStep('beforeAll end');
|
|
88
|
+
};
|
|
89
|
+
testInfo.afterEach = (hasFailed) => {
|
|
90
|
+
if (!hasFailed) {
|
|
91
|
+
(0, editFile_1.editFileAssertReverted)();
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
(0, editFile_1.editFileRevert)();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
testInfo.afterAll = async () => {
|
|
98
|
+
logJestStep('afterAll start');
|
|
99
|
+
page_1.page.off('console', onConsole);
|
|
100
|
+
page_1.page.off('pageerror', onPageError);
|
|
101
|
+
// `runProcess` is `undefined` if `start()` failed.
|
|
102
|
+
if (runProcess) {
|
|
103
|
+
await runProcess.terminate('SIGINT');
|
|
104
|
+
}
|
|
105
|
+
logJestStep('afterAll end');
|
|
106
|
+
};
|
|
107
|
+
// return { page }
|
|
108
|
+
return;
|
|
109
|
+
// Also called when the page throws an error or a warning
|
|
110
|
+
function onConsole(msg) {
|
|
111
|
+
const type = msg.type();
|
|
112
|
+
Logs_1.Logs.add({
|
|
113
|
+
logType: type === 'error' ? 'Browser Error' : 'Browser Log',
|
|
114
|
+
logText: JSON.stringify({
|
|
115
|
+
type,
|
|
116
|
+
text: msg.text(),
|
|
117
|
+
location: msg.location(),
|
|
118
|
+
args: msg.args(),
|
|
119
|
+
}, null, 2),
|
|
120
|
+
testContext,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
// For uncaught exceptions
|
|
124
|
+
function onPageError(err) {
|
|
125
|
+
Logs_1.Logs.add({
|
|
126
|
+
logType: 'Browser Error',
|
|
127
|
+
logText: JSON.stringify({
|
|
128
|
+
text: err.message,
|
|
129
|
+
location: err.stack,
|
|
130
|
+
}, null, 2),
|
|
131
|
+
testContext,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
function logJestStep(stepName) {
|
|
135
|
+
if (!debug) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
Logs_1.Logs.add({
|
|
139
|
+
logType: 'Jest',
|
|
140
|
+
logText: stepName,
|
|
141
|
+
testContext,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
exports.run = run;
|
|
146
|
+
async function start(testContext) {
|
|
147
|
+
const { cmd, additionalTimeout, serverIsReadyMessage, serverIsReadyDelay } = testContext;
|
|
148
|
+
let hasStarted = false;
|
|
149
|
+
let resolveServerStart;
|
|
150
|
+
let rejectServerStart;
|
|
151
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
152
|
+
resolveServerStart = () => {
|
|
153
|
+
hasStarted = true;
|
|
154
|
+
clearTimeout(serverStartTimeout);
|
|
155
|
+
const runProcess = { terminate };
|
|
156
|
+
_resolve(runProcess);
|
|
157
|
+
};
|
|
158
|
+
rejectServerStart = async (err) => {
|
|
159
|
+
done(true);
|
|
160
|
+
Logs_1.Logs.add({
|
|
161
|
+
logType: 'stderr',
|
|
162
|
+
logText: String(err),
|
|
163
|
+
testContext,
|
|
164
|
+
});
|
|
165
|
+
clearTimeout(serverStartTimeout);
|
|
166
|
+
try {
|
|
167
|
+
await terminate('SIGKILL');
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
Logs_1.Logs.add({
|
|
171
|
+
logType: 'process',
|
|
172
|
+
logText: String(err),
|
|
173
|
+
testContext,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
_reject(err);
|
|
177
|
+
};
|
|
178
|
+
});
|
|
179
|
+
const timeoutTotal = TIMEOUT_NPM_SCRIPT + additionalTimeout;
|
|
180
|
+
const serverStartTimeout = setTimeout(() => {
|
|
181
|
+
let errMsg = '';
|
|
182
|
+
errMsg += `Server still didn't start after ${timeoutTotal / 1000} seconds of running the npm script \`${cmd}\`.`;
|
|
183
|
+
if (serverIsReadyMessage) {
|
|
184
|
+
errMsg += ` (The stdout of the npm script did not include: "${serverIsReadyMessage}".)`;
|
|
185
|
+
}
|
|
186
|
+
rejectServerStart(new Error(errMsg));
|
|
187
|
+
}, timeoutTotal);
|
|
188
|
+
// Kill any process that listens to port `3000`
|
|
189
|
+
if (!process.env.CI && isLinux()) {
|
|
190
|
+
await (0, utils_1.runCommand)('fuser -k 3000/tcp', { swallowError: true, timeout: 10 * 1000 });
|
|
191
|
+
}
|
|
192
|
+
const done = (0, logProgress_1.logProgress)(`[run] ${cmd}`);
|
|
193
|
+
const { terminate } = startScript(cmd, testContext, {
|
|
194
|
+
onError(err) {
|
|
195
|
+
rejectServerStart(err);
|
|
196
|
+
},
|
|
197
|
+
onExit() {
|
|
198
|
+
const exitIsExpected = hasStarted === true;
|
|
199
|
+
return exitIsExpected;
|
|
200
|
+
},
|
|
201
|
+
async onStdout(data) {
|
|
202
|
+
const text = (0, strip_ansi_1.default)(data);
|
|
203
|
+
const isServerReady =
|
|
204
|
+
// Custom
|
|
205
|
+
(serverIsReadyMessage && text.includes(serverIsReadyMessage)) ||
|
|
206
|
+
// Express.js server
|
|
207
|
+
text.includes('Server running at') ||
|
|
208
|
+
// npm package `serve`
|
|
209
|
+
text.includes('Accepting connections at') ||
|
|
210
|
+
// Vite
|
|
211
|
+
(text.includes('Local:') && text.includes('http://localhost:3000/'));
|
|
212
|
+
if (isServerReady) {
|
|
213
|
+
(0, utils_2.assert)(serverIsReadyDelay);
|
|
214
|
+
await (0, utils_1.sleep)(serverIsReadyDelay);
|
|
215
|
+
resolveServerStart();
|
|
216
|
+
done();
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
onStderr(data) {
|
|
220
|
+
if (data.includes('EADDRINUSE')) {
|
|
221
|
+
rejectServerStart(new Error('Port conflict? Port already in use EADDRINUSE.'));
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
});
|
|
225
|
+
return promise;
|
|
226
|
+
}
|
|
227
|
+
function stopProcess({ proc, cwd, cmd, signal, }) {
|
|
228
|
+
const prefix = `[Run Stop][${cwd}][${cmd}]`;
|
|
229
|
+
let resolve;
|
|
230
|
+
let reject;
|
|
231
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
232
|
+
resolve = _resolve;
|
|
233
|
+
reject = _reject;
|
|
234
|
+
});
|
|
235
|
+
const onProcessClose = (code) => {
|
|
236
|
+
if (code === 0 || code === null || (code === 1 && isWindows())) {
|
|
237
|
+
resolve();
|
|
238
|
+
}
|
|
239
|
+
else {
|
|
240
|
+
reject(new Error(`${prefix} Terminated with non-0 error code ${code}`));
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
proc.on('close', onProcessClose);
|
|
244
|
+
proc.on('exit', onProcessClose);
|
|
245
|
+
if (isWindows()) {
|
|
246
|
+
// - https://github.com/nodejs/node/issues/3617#issuecomment-377731194
|
|
247
|
+
// - https://stackoverflow.com/questions/23706055/why-can-i-not-kill-my-child-process-in-nodejs-on-windows/28163919#28163919
|
|
248
|
+
(0, child_process_1.spawn)('taskkill', ['/pid', String(proc.pid), '/f', '/t'], { stdio: ['ignore', 'ignore', 'inherit'] });
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
(0, utils_2.assert)(proc.pid);
|
|
252
|
+
const processGroup = -1 * proc.pid;
|
|
253
|
+
process.kill(processGroup, signal);
|
|
254
|
+
/*
|
|
255
|
+
try {
|
|
256
|
+
process.kill(-proc.pid, signal)
|
|
257
|
+
} catch (err: unknown) {
|
|
258
|
+
// ESRCH: No process or process group can be found corresponding to that specified by pid.
|
|
259
|
+
// => probably means that the process was killed already.
|
|
260
|
+
if (typeof err === 'object' && err !== null && 'code' in err && err['code'] === 'ESRCH') {
|
|
261
|
+
printLog('stdout', '=============== swallowError')
|
|
262
|
+
return
|
|
263
|
+
} else {
|
|
264
|
+
printLog('stdout', '=============== no swallowError')
|
|
265
|
+
throw err
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
*/
|
|
269
|
+
}
|
|
270
|
+
return promise;
|
|
271
|
+
}
|
|
272
|
+
function startScript(cmd, testContext, { onStdout, onStderr, onError, onExit, }) {
|
|
273
|
+
let [command, ...args] = cmd.split(' ');
|
|
274
|
+
let detached = true;
|
|
275
|
+
if (isWindows()) {
|
|
276
|
+
detached = false;
|
|
277
|
+
if (command === 'npm' || command === 'pnpm') {
|
|
278
|
+
command = command + '.cmd';
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
const { cwd } = testContext;
|
|
282
|
+
const proc = (0, child_process_1.spawn)(command, args, { cwd, detached });
|
|
283
|
+
const prefix = `[Run Start][${cwd}][${cmd}]`;
|
|
284
|
+
proc.stdin.on('data', async (data) => {
|
|
285
|
+
onError(new Error(`Command is \`${cmd}\` (${cwd}) is invoking \`stdin\`: ${data}.`));
|
|
286
|
+
});
|
|
287
|
+
proc.stdout.on('data', async (data) => {
|
|
288
|
+
data = data.toString();
|
|
289
|
+
Logs_1.Logs.add({
|
|
290
|
+
logType: 'stdout',
|
|
291
|
+
logText: data,
|
|
292
|
+
testContext,
|
|
293
|
+
});
|
|
294
|
+
onStdout?.(data);
|
|
295
|
+
});
|
|
296
|
+
proc.stderr.on('data', async (data) => {
|
|
297
|
+
data = data.toString();
|
|
298
|
+
Logs_1.Logs.add({
|
|
299
|
+
logType: 'stderr',
|
|
300
|
+
logText: data,
|
|
301
|
+
testContext,
|
|
302
|
+
});
|
|
303
|
+
onStderr?.(data);
|
|
304
|
+
});
|
|
305
|
+
proc.on('exit', async (code) => {
|
|
306
|
+
const exitIsExpected = onExit();
|
|
307
|
+
const isSuccessCode = [0, null].includes(code) || (isWindows() && code === 1);
|
|
308
|
+
const isExpected = isSuccessCode && exitIsExpected;
|
|
309
|
+
if (!isExpected) {
|
|
310
|
+
const errMsg = `${prefix} Unexpected premature process termination, exit code: ${code}`;
|
|
311
|
+
Logs_1.Logs.add({
|
|
312
|
+
logText: errMsg,
|
|
313
|
+
logType: 'stderr',
|
|
314
|
+
testContext,
|
|
315
|
+
});
|
|
316
|
+
onError(new Error(errMsg));
|
|
317
|
+
/*
|
|
318
|
+
try {
|
|
319
|
+
await terminate('SIGKILL')
|
|
320
|
+
} catch (err: unknown) {
|
|
321
|
+
if( !err.code === 'ESRCH' ) {
|
|
322
|
+
onError(err as Error)
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
*/
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
Logs_1.Logs.add({
|
|
329
|
+
logText: `${prefix} Process termination. (Nominal. Exit code: ${code}.)`,
|
|
330
|
+
logType: 'process',
|
|
331
|
+
testContext,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
return { terminate };
|
|
336
|
+
async function terminate(signal) {
|
|
337
|
+
let resolve;
|
|
338
|
+
let reject;
|
|
339
|
+
const promise = new Promise((_resolve, _reject) => {
|
|
340
|
+
resolve = _resolve;
|
|
341
|
+
reject = _reject;
|
|
342
|
+
});
|
|
343
|
+
const timeout = setTimeout(() => {
|
|
344
|
+
reject(new Error('Process termination timeout. Cmd: ' + cmd));
|
|
345
|
+
}, TIMEOUT_PROCESS_TERMINATION);
|
|
346
|
+
(0, utils_2.assert)(proc);
|
|
347
|
+
await stopProcess({
|
|
348
|
+
proc,
|
|
349
|
+
cwd,
|
|
350
|
+
cmd,
|
|
351
|
+
signal,
|
|
352
|
+
});
|
|
353
|
+
clearTimeout(timeout);
|
|
354
|
+
resolve();
|
|
355
|
+
return promise;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
async function autoRetry(test, { timeout = TIMEOUT_AUTORETRY } = {}) {
|
|
359
|
+
const period = 100;
|
|
360
|
+
const numberOfTries = timeout / period;
|
|
361
|
+
let i = 0;
|
|
362
|
+
while (true) {
|
|
363
|
+
try {
|
|
364
|
+
await test();
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
i = i + 1;
|
|
369
|
+
if (i > numberOfTries) {
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
await (0, utils_1.sleep)(period);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
exports.autoRetry = autoRetry;
|
|
377
|
+
async function fetchHtml(pathname) {
|
|
378
|
+
const response = await fetch(exports.urlBase + pathname);
|
|
379
|
+
const html = await response.text();
|
|
380
|
+
return html;
|
|
381
|
+
}
|
|
382
|
+
exports.fetchHtml = fetchHtml;
|
|
383
|
+
async function fetch(...args) {
|
|
384
|
+
try {
|
|
385
|
+
return await (0, node_fetch_1.default)(...args);
|
|
386
|
+
}
|
|
387
|
+
catch (err) {
|
|
388
|
+
Logs_1.Logs.add({
|
|
389
|
+
logType: 'Connection Error',
|
|
390
|
+
logText: `Couldn't connect to \`${args[0]}\`. Args: \`${JSON.stringify(args.slice(1))}\`. Err: \`${
|
|
391
|
+
// @ts-ignore
|
|
392
|
+
err.message}\``,
|
|
393
|
+
testContext: null,
|
|
394
|
+
});
|
|
395
|
+
throw new Error("Couldn't connect to server. See `Connection Error` log for more details.");
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
exports.fetch = fetch;
|
|
399
|
+
/*
|
|
400
|
+
async function bailOnTimeout(asyncFunc: () => Promise<void>, { timeout }: { timeout: number }) {
|
|
401
|
+
let resolve: () => void
|
|
402
|
+
let reject: (err: Error) => void
|
|
403
|
+
const promise = new Promise<void>((_resolve, _reject) => {
|
|
404
|
+
resolve = _resolve
|
|
405
|
+
reject = _reject
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
const t = setTimeout(() => {
|
|
409
|
+
reject(new Error(`Function timeout.`))
|
|
410
|
+
}, timeout)
|
|
411
|
+
await asyncFunc()
|
|
412
|
+
clearTimeout(t)
|
|
413
|
+
resolve()
|
|
414
|
+
|
|
415
|
+
return promise
|
|
416
|
+
}
|
|
417
|
+
*/
|
|
418
|
+
function isMinNodeVersion(minNodeVersion) {
|
|
419
|
+
const { version } = process;
|
|
420
|
+
(0, utils_2.assert)(version.startsWith('v'));
|
|
421
|
+
const major = parseInt(version[1] + version[2], 10);
|
|
422
|
+
(0, utils_2.assert)(12 <= major && major <= 50);
|
|
423
|
+
return major >= minNodeVersion;
|
|
424
|
+
}
|
|
425
|
+
exports.isMinNodeVersion = isMinNodeVersion;
|
|
426
|
+
function isWindows() {
|
|
427
|
+
return process.platform === 'win32';
|
|
428
|
+
}
|
|
429
|
+
exports.isWindows = isWindows;
|
|
430
|
+
function isLinux() {
|
|
431
|
+
return process.platform === 'linux';
|
|
432
|
+
}
|
|
433
|
+
exports.isLinux = isLinux;
|
|
434
|
+
function isMac() {
|
|
435
|
+
if (process.platform === 'darwin') {
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
(0, utils_2.assert)(isLinux() || isWindows());
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
exports.isMac = isMac;
|
|
442
|
+
function isGithubAction() {
|
|
443
|
+
return !!process.env.CI;
|
|
444
|
+
}
|
|
445
|
+
exports.isGithubAction = isGithubAction;
|
|
446
|
+
function getCwd() {
|
|
447
|
+
const { testFile } = (0, getTestInfo_1.getTestInfo)();
|
|
448
|
+
const cwd = (0, path_1.dirname)(testFile);
|
|
449
|
+
return cwd;
|
|
450
|
+
}
|
|
451
|
+
function getTestName() {
|
|
452
|
+
const { testFile } = (0, getTestInfo_1.getTestInfo)();
|
|
453
|
+
const pathRelative = removeRootDir(testFile);
|
|
454
|
+
if (testFile.includes('examples')) {
|
|
455
|
+
return (0, path_1.dirname)(pathRelative);
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
return pathRelative;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
function removeRootDir(filePath) {
|
|
462
|
+
const rootDir = process.cwd();
|
|
463
|
+
(0, utils_2.assert)(filePath.startsWith(rootDir));
|
|
464
|
+
return filePath.slice(rootDir.length);
|
|
465
|
+
}
|
package/dist/test.d.ts
ADDED
package/dist/test.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.test = void 0;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
const getTestInfo_1 = require("./getTestInfo");
|
|
6
|
+
function test(testDesc, testFn) {
|
|
7
|
+
const testInfo = (0, getTestInfo_1.getTestInfo)();
|
|
8
|
+
(0, utils_1.assert)(!testInfo.hasStartedRunning);
|
|
9
|
+
testInfo.tests = testInfo.tests ?? [];
|
|
10
|
+
testInfo.tests.push({
|
|
11
|
+
testDesc,
|
|
12
|
+
testFn,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
exports.test = test;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { assert };
|
|
2
|
+
export { assertUsage };
|
|
3
|
+
export { assertWarning };
|
|
4
|
+
export { assertInfo };
|
|
5
|
+
export { getProjectError };
|
|
6
|
+
export { errorPrefix as projectErrorPrefix };
|
|
7
|
+
declare const errorPrefix: string;
|
|
8
|
+
declare function assert(condition: unknown, debugInfo?: unknown): asserts condition;
|
|
9
|
+
declare function assertUsage(condition: unknown, errorMessage: string): asserts condition;
|
|
10
|
+
declare function getProjectError(errorMessage: string): Error;
|
|
11
|
+
declare function assertWarning(condition: unknown, errorMessage: string, { onlyOnce }: {
|
|
12
|
+
onlyOnce: boolean | string;
|
|
13
|
+
}): void;
|
|
14
|
+
declare function assertInfo(condition: unknown, errorMessage: string): void;
|