@camperaid/watest 2.5.0 → 2.5.2
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/README.md +274 -129
- package/bin/watest.js +36 -2
- package/core/base.js +10 -3
- package/core/core.js +43 -15
- package/core/deps.js +211 -0
- package/core/{process_args.js → process-args.js} +8 -0
- package/core/series.js +70 -28
- package/core/settings.js +28 -10
- package/core/system.js +68 -0
- package/core/util.js +1 -1
- package/eslint.config.js +1 -1
- package/index.js +15 -3
- package/interfaces/servicer.js +13 -3
- package/logging/logging.js +1 -1
- package/logging/logpipe.js +38 -21
- package/package.json +1 -1
- package/tests/base/{t_core.js → t-core.js} +10 -3
- package/tests/base/t-system.js +59 -0
- package/tests/base/{t_throws.js → t-throws.js} +67 -0
- package/tests/base/test.js +1 -2
- package/tests/deps/samples/nested/.watestrc.js +3 -0
- package/tests/deps/samples/nested/tests/meta.js +1 -0
- package/tests/deps/samples/nested/tests/services/meta.js +1 -0
- package/tests/deps/samples/nested/tests/services/ws/meta.js +1 -0
- package/tests/deps/samples/nested/tests/services/ws/webservice/meta.js +2 -0
- package/tests/deps/samples/nested/tests/services/ws/webservice/t-ws.js +3 -0
- package/tests/deps/samples/unified/.watestrc.js +3 -0
- package/tests/deps/samples/unified/tests/e2e/meta.js +4 -0
- package/tests/deps/samples/unified/tests/e2e/pages/meta.js +1 -0
- package/tests/deps/samples/unified/tests/e2e/pages/t-example.js +3 -0
- package/tests/deps/samples/unified/tests/e2e/t-example.js +3 -0
- package/tests/deps/samples/unified/tests/integration/meta.js +3 -0
- package/tests/deps/samples/unified/tests/lib/meta.js +0 -0
- package/tests/deps/samples/unified/tests/lib/t-example.js +3 -0
- package/tests/deps/samples/unified/tests/meta.js +8 -0
- package/tests/deps/samples/unified/tests/services/meta.js +3 -0
- package/tests/deps/samples/unified/tests/services/request/meta.js +1 -0
- package/tests/deps/samples/unified/tests/services/t-example.js +3 -0
- package/tests/deps/t-parse-cell-syntax.js +6 -0
- package/tests/deps/t-parse-grid-args.js +11 -0
- package/tests/deps/t-watest-deps.js +37 -0
- package/tests/deps/t-watest-grid.js +122 -0
- package/tests/deps/test.js +63 -0
- package/tests/e2e/samples/folder/package-lock.json +3 -1
- package/tests/e2e/samples/loader/package-lock.json +3 -1
- package/tests/e2e/samples/loader/tests/meta.js +1 -1
- package/tests/e2e/samples/{loader_mixed → loader-mixed}/package-lock.json +3 -1
- package/tests/e2e/samples/{loader_multiple/tests/core → loader-mixed/tests/ui}/meta.js +1 -1
- package/tests/e2e/samples/{loader_multiple → loader-multiple}/package-lock.json +3 -1
- package/tests/e2e/samples/{loader_multiple → loader-multiple}/tests/base/meta.js +1 -1
- package/tests/e2e/samples/{loader_mixed/tests/ui → loader-multiple/tests/core}/meta.js +1 -1
- package/tests/e2e/samples/single/package-lock.json +3 -1
- package/tests/e2e/samples/{wd_mixed → wd-mixed}/package-lock.json +3 -1
- package/tests/e2e/samples/{wd_single → wd-single}/package-lock.json +3 -1
- package/tests/e2e/{t_folder.js → t-folder.js} +3 -3
- package/tests/e2e/{t_loader_mixed.js → t-loader-mixed.js} +7 -7
- package/tests/e2e/{t_loader_multiple_patterns.js → t-loader-multiple-patterns.js} +9 -9
- package/tests/e2e/{t_loader_multiple.js → t-loader-multiple.js} +8 -8
- package/tests/e2e/{t_loader.js → t-loader.js} +4 -4
- package/tests/e2e/{t_single.js → t-single.js} +3 -3
- package/tests/e2e/{t_wd_firefox_chrome_pattern.js → t-wd-firefox-chrome-pattern.js} +8 -8
- package/tests/e2e/{t_wd_firefox_chrome.js → t-wd-firefox-chrome.js} +7 -7
- package/tests/e2e/{t_wd_firefox.js → t-wd-firefox.js} +5 -5
- package/tests/e2e/{t_wd_mixed_firefox_chrome.js → t-wd-mixed-firefox-chrome.js} +9 -9
- package/tests/e2e/{t_wd_mixed_firefox.js → t-wd-mixed-firefox.js} +7 -7
- package/tests/meta.js +1 -1
- package/tests/series/build/t-pattern-filtering.js +175 -0
- package/tests/series/build/{t_webdriver_services.js → t-webdriver-services.js} +1 -0
- package/tests/series/logging/{t_failures.js → t-failures.js} +1 -1
- package/tests/series/logging/{t_success.js → t-success.js} +1 -1
- package/tests/series/logging/{t_verify.js → t-verify.js} +2 -2
- package/tests/series/meta.js +1 -0
- package/tests/series/perform/{t_failure_notest.js → t-failure-notest.js} +1 -0
- package/tests/series/perform/{t_failure.js → t-failure.js} +1 -0
- package/tests/series/perform/{t_intermittent_global.js → t-intermittent-global.js} +1 -0
- package/tests/series/perform/{t_intermittent.js → t-intermittent.js} +2 -0
- package/tests/series/perform/{t_missing_perma.js → t-missing-perma.js} +2 -0
- package/tests/series/perform/{t_nested.js → t-nested.js} +1 -0
- package/tests/series/perform/{t_perma.js → t-perma.js} +1 -0
- package/tests/series/perform/{t_success.js → t-success.js} +2 -0
- package/tests/series/servicer/mock-servicer.js +68 -0
- package/tests/series/servicer/t-nested-servicer-lifecycle.js +99 -0
- package/tests/series/servicer/t-servicer-no-services.js +53 -0
- package/tests/series/servicer/t-servicer-type-switching.js +139 -0
- package/tests/series/servicer/t-servicer.js +51 -0
- package/tests/series/test.js +1 -1
- package/tests/test.js +2 -0
- package/tests/webdriver/test.js +3 -3
- package/webdriver/{control_driver.js → control-driver.js} +1 -1
- package/webdriver/{driver_base.js → driver-base.js} +3 -1
- package/webdriver/driver.js +1 -1
- package/webdriver/session.js +57 -30
- package/tests/base/{t_api.js → t-api.js} +0 -0
- package/tests/base/{t_contains.js → t-contains.js} +0 -0
- package/tests/base/{t_format.js → t-format.js} +0 -0
- package/tests/base/{t_is_object.js → t-is-object.js} +0 -0
- package/tests/base/{t_is_primitive.js → t-is-primitive.js} +0 -0
- package/tests/base/{t_is_string.js → t-is-string.js} +0 -0
- package/tests/base/{t_is.js → t-is.js} +0 -0
- package/tests/base/{t_ok.js → t-ok.js} +0 -0
- package/tests/base/{t_stringify.js → t-stringify.js} +0 -0
- package/tests/base/{t_test_.js → t-test-.js} +0 -0
- package/tests/e2e/samples/folder/tests/unit/{t_test.js → t-test.js} +0 -0
- package/tests/e2e/samples/{loader_multiple/tests/module_mock.js → loader/tests/module-mock.js} +0 -0
- package/tests/e2e/samples/loader/tests/{t_test.js → t-test.js} +0 -0
- package/tests/e2e/samples/{loader_mixed → loader-mixed}/.watestrc.js +0 -0
- package/tests/e2e/samples/{loader_mixed → loader-mixed}/package.json +0 -0
- package/tests/e2e/samples/{loader_mixed → loader-mixed}/tests/meta.js +0 -0
- package/tests/e2e/samples/{loader/tests/module_mock.js → loader-mixed/tests/module-mock.js} +0 -0
- package/tests/e2e/samples/{loader_mixed → loader-mixed}/tests/module.js +0 -0
- package/tests/e2e/samples/{loader_mixed/tests/ui/t_test.js → loader-mixed/tests/ui/t-test.js} +0 -0
- package/tests/e2e/samples/{loader_mixed/tests/unit/t_test.js → loader-mixed/tests/unit/t-test.js} +0 -0
- package/tests/e2e/samples/{loader_multiple → loader-multiple}/.watestrc.js +0 -0
- package/tests/e2e/samples/{loader_multiple → loader-multiple}/package.json +0 -0
- package/tests/e2e/samples/{loader_multiple/tests/base/t_btest.js → loader-multiple/tests/base/t-btest.js} +0 -0
- package/tests/e2e/samples/{loader_multiple/tests/core/t_ctest.js → loader-multiple/tests/core/t-ctest.js} +0 -0
- package/tests/e2e/samples/{loader_multiple → loader-multiple}/tests/meta.js +0 -0
- package/tests/e2e/samples/{loader_mixed/tests/module_mock.js → loader-multiple/tests/module-mock.js} +0 -0
- package/tests/e2e/samples/{loader_multiple → loader-multiple}/tests/module.js +0 -0
- package/tests/e2e/samples/single/tests/{t_test.js → t-test.js} +0 -0
- package/tests/e2e/samples/{wd_mixed → wd-mixed}/.watestrc.js +0 -0
- package/tests/e2e/samples/{wd_mixed → wd-mixed}/package.json +0 -0
- package/tests/e2e/samples/{wd_mixed → wd-mixed}/tests/meta.js +0 -0
- package/tests/e2e/samples/{wd_mixed → wd-mixed}/tests/ui/meta.js +0 -0
- package/tests/e2e/samples/{wd_mixed/tests/ui/t_test.js → wd-mixed/tests/ui/t-test.js} +0 -0
- package/tests/e2e/samples/{wd_mixed/tests/unit/t_test.js → wd-mixed/tests/unit/t-test.js} +0 -0
- package/tests/e2e/samples/{wd_single → wd-single}/.watestrc.js +0 -0
- package/tests/e2e/samples/{wd_single → wd-single}/package.json +0 -0
- package/tests/e2e/samples/{wd_single → wd-single}/tests/meta.js +0 -0
- package/tests/e2e/samples/{wd_single/tests/t_test.js → wd-single/tests/t-test.js} +0 -0
- package/tests/series/build/{t_adjust_names_webdriver.js → t-adjust-names-webdriver.js} +0 -0
- package/tests/series/build/{t_adjust_names.js → t-adjust-names.js} +0 -0
- package/tests/series/build/{t_expected_failures.js → t-expected-failures.js} +0 -0
- package/tests/series/build/{t_loader_mixed.js → t-loader-mixed.js} +0 -0
- package/tests/series/build/{t_loader.js → t-loader.js} +0 -0
- package/tests/series/build/{t_mixed.js → t-mixed.js} +0 -0
- package/tests/series/build/{t_nested.js → t-nested.js} +0 -0
- package/tests/series/build/{t_patterns_loader.js → t-patterns-loader.js} +0 -0
- package/tests/series/build/{t_patterns_webdriver.js → t-patterns-webdriver.js} +0 -0
- package/tests/series/build/{t_webdriver_firefox_mixed.js → t-webdriver-firefox-mixed.js} +0 -0
- package/tests/series/build/{t_webdriver_nested.js → t-webdriver-nested.js} +0 -0
- package/tests/series/build/{t_webdriver.js → t-webdriver.js} +0 -0
- package/tests/series/generic/{t_failures_info.js → t-failures-info.js} +0 -0
- package/tests/series/{mock_series.js → mock-series.js} +0 -0
- package/tests/series/run/{t_debunk_failure.js → t-debunk-failure.js} +1 -1
- package/tests/series/run/{t_debunk_success.js → t-debunk-success.js} +1 -1
- package/tests/series/run/{t_nested.js → t-nested.js} +1 -1
- package/tests/series/run/{t_verify_webdriver.js → t-verify-webdriver.js} +1 -1
- package/tests/series/run/{t_verify.js → t-verify.js} +1 -1
- /package/tests/webdriver/{t_app_driver_selectors.js → t-app-driver-selectors.js} +0 -0
- /package/tests/webdriver/{t_app_driver.js → t-app-driver.js} +0 -0
- /package/tests/webdriver/{t_attribute_all.js → t-attribute-all.js} +0 -0
- /package/tests/webdriver/{t_attribute.js → t-attribute.js} +0 -0
- /package/tests/webdriver/{t_doubleclick.js → t-doubleclick.js} +0 -0
- /package/tests/webdriver/{t_doubleclickat.js → t-doubleclickat.js} +0 -0
- /package/tests/webdriver/{t_execute.js → t-execute.js} +0 -0
- /package/tests/webdriver/{t_if_has_elements.js → t-if-has-elements.js} +0 -0
- /package/tests/webdriver/{t_if_no_elements.js → t-if-no-elements.js} +0 -0
- /package/tests/webdriver/{t_no_elements_or_not_visible.js → t-no-elements-or-not-visible.js} +0 -0
- /package/tests/webdriver/{t_properties.js → t-properties.js} +0 -0
- /package/tests/webdriver/{t_script.js → t-script.js} +0 -0
- /package/tests/webdriver/{t_select_all.js → t-select-all.js} +0 -0
- /package/tests/webdriver/{t_selection.js → t-selection.js} +0 -0
- /package/tests/webdriver/{t_text_all.js → t-text-all.js} +0 -0
- /package/tests/webdriver/{t_text.js → t-text.js} +0 -0
- /package/webdriver/{app_driver.js → app-driver.js} +0 -0
package/core/deps.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dependency and grid metadata extraction for distributed testing.
|
|
3
|
+
* Parses test folder metadata and generates grid configurations.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { settings } from './settings.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Parse cell syntax: 'e2e+' → { name: 'e2e', split: true }
|
|
12
|
+
*/
|
|
13
|
+
export function parseCellSyntax(cellKey) {
|
|
14
|
+
if (cellKey.endsWith('+')) {
|
|
15
|
+
return { name: cellKey.slice(0, -1), split: true };
|
|
16
|
+
}
|
|
17
|
+
return { name: cellKey, split: false };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Separate paths (contain '/') from webdrivers (don't contain '/')
|
|
22
|
+
*/
|
|
23
|
+
export function parseGridArgs(args) {
|
|
24
|
+
const paths = [];
|
|
25
|
+
const webdrivers = [];
|
|
26
|
+
|
|
27
|
+
for (const arg of args) {
|
|
28
|
+
if (arg.includes('/')) {
|
|
29
|
+
paths.push(arg);
|
|
30
|
+
} else {
|
|
31
|
+
webdrivers.push(arg);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { paths, webdrivers };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Extract service name from service definition.
|
|
40
|
+
* Services can be either:
|
|
41
|
+
* - A string: 'db'
|
|
42
|
+
* - An array: ['nginx', { env: {...} }] where first element is the name
|
|
43
|
+
*/
|
|
44
|
+
function getServiceName(service) {
|
|
45
|
+
return Array.isArray(service) ? service[0] : service;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check if a folder path should be traversed based on target paths.
|
|
50
|
+
* Similar to series.js matchedPatterns logic:
|
|
51
|
+
* - folderPath starts with target: we're IN or PAST the target
|
|
52
|
+
* - target starts with folderPath: we're ON THE WAY to the target
|
|
53
|
+
*/
|
|
54
|
+
function matchesPath(folderPath, targetPaths) {
|
|
55
|
+
return targetPaths.some(
|
|
56
|
+
target => folderPath.startsWith(target) || target.startsWith(folderPath),
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Collect metadata from a single directory's meta.js
|
|
62
|
+
* Helper for collectDeps
|
|
63
|
+
*/
|
|
64
|
+
async function collectMetaFromDir(dirPath, result) {
|
|
65
|
+
try {
|
|
66
|
+
const metaPath = join(process.cwd(), dirPath, 'meta.js');
|
|
67
|
+
const metaUrl = pathToFileURL(metaPath).href;
|
|
68
|
+
const meta = await import(metaUrl);
|
|
69
|
+
|
|
70
|
+
if (meta.servicer && !result.servicers.includes(meta.servicer)) {
|
|
71
|
+
result.servicers.push(meta.servicer);
|
|
72
|
+
}
|
|
73
|
+
if (meta.webdriver) {
|
|
74
|
+
result.webdriver = true;
|
|
75
|
+
}
|
|
76
|
+
if (meta.services) {
|
|
77
|
+
for (const service of meta.services) {
|
|
78
|
+
const serviceName = getServiceName(service);
|
|
79
|
+
if (!result.services.includes(serviceName)) {
|
|
80
|
+
result.services.push(serviceName);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return meta;
|
|
86
|
+
} catch {
|
|
87
|
+
// No meta.js or error loading it - that's ok
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Recursively collect metadata from nested meta.js files.
|
|
94
|
+
* Starts from root folder, walks DOWN the tree, only following branches that match target paths.
|
|
95
|
+
* Similar to how series.js builds tests - uses bidirectional path matching.
|
|
96
|
+
*
|
|
97
|
+
* @param {string} folder - Current folder being traversed
|
|
98
|
+
* @param {string[]} targetPaths - The specific test paths we're collecting deps for
|
|
99
|
+
* @param {Object} result - Accumulated result object
|
|
100
|
+
*/
|
|
101
|
+
async function collectDepsRecursive(folder, targetPaths, result) {
|
|
102
|
+
// Collect metadata from current folder
|
|
103
|
+
const meta = await collectMetaFromDir(folder, result);
|
|
104
|
+
|
|
105
|
+
// If this folder has subfolders, filter and recurse
|
|
106
|
+
if (meta?.folders) {
|
|
107
|
+
for (const subfolder of meta.folders) {
|
|
108
|
+
const subfolderPath = join(folder, subfolder);
|
|
109
|
+
|
|
110
|
+
// Only follow this branch if it matches any target path
|
|
111
|
+
if (matchesPath(subfolderPath, targetPaths)) {
|
|
112
|
+
await collectDepsRecursive(subfolderPath, targetPaths, result);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Collect metadata for given test paths.
|
|
120
|
+
* Walks the tree from tests root, following only branches that lead to target paths.
|
|
121
|
+
*
|
|
122
|
+
* @param {string[]} paths - Test paths to collect metadata for (defaults to entire tree)
|
|
123
|
+
* @param {Object} result - Initial result object
|
|
124
|
+
*/
|
|
125
|
+
export async function collectDeps(
|
|
126
|
+
paths = [settings.testsFolder],
|
|
127
|
+
result = { servicers: [], webdriver: false, services: [] },
|
|
128
|
+
) {
|
|
129
|
+
const rootFolder = settings.testsFolder;
|
|
130
|
+
|
|
131
|
+
// Filter to paths under root folder
|
|
132
|
+
const targetPaths = paths.filter(
|
|
133
|
+
p => p === rootFolder || p.startsWith(rootFolder + '/'),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (targetPaths.length > 0) {
|
|
137
|
+
await collectDepsRecursive(rootFolder, targetPaths, result);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate expanded grid from grid config and args.
|
|
145
|
+
* Returns cell→metadata mapping with + cells expanded per browser.
|
|
146
|
+
*
|
|
147
|
+
* Example:
|
|
148
|
+
* watest --grid-meta tests/e2e firefox chrome
|
|
149
|
+
* → {
|
|
150
|
+
* "e2e-firefox": { paths: ["tests/e2e"], webdriver: "firefox", servicers: [...], services: [...] },
|
|
151
|
+
* "e2e-chrome": { paths: ["tests/e2e"], webdriver: "chrome", servicers: [...], services: [...] }
|
|
152
|
+
* }
|
|
153
|
+
*/
|
|
154
|
+
export async function generateGridTasks(gridConfig, args) {
|
|
155
|
+
const { paths, webdrivers } = parseGridArgs(args);
|
|
156
|
+
const browsers =
|
|
157
|
+
webdrivers.length > 0 ? webdrivers : settings.webdrivers || [];
|
|
158
|
+
|
|
159
|
+
// Build cell → paths mapping
|
|
160
|
+
const cellPathsMap = new Map();
|
|
161
|
+
|
|
162
|
+
if (paths.length > 0) {
|
|
163
|
+
// Map each requested path to its cell
|
|
164
|
+
for (const [cellKey, cellPaths] of Object.entries(gridConfig)) {
|
|
165
|
+
const matchingPaths = paths.filter(p =>
|
|
166
|
+
cellPaths.some(cp => p.startsWith(cp)),
|
|
167
|
+
);
|
|
168
|
+
if (matchingPaths.length > 0) {
|
|
169
|
+
cellPathsMap.set(cellKey, matchingPaths);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
// Use all cells with their configured paths
|
|
174
|
+
for (const [cellKey, cellPaths] of Object.entries(gridConfig)) {
|
|
175
|
+
cellPathsMap.set(cellKey, cellPaths);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Build expanded grid with metadata
|
|
180
|
+
const expandedGrid = {};
|
|
181
|
+
for (const [cellKey, cellPaths] of cellPathsMap) {
|
|
182
|
+
const { name, split } = parseCellSyntax(cellKey);
|
|
183
|
+
const meta = await collectDeps(cellPaths);
|
|
184
|
+
|
|
185
|
+
if (split && meta.webdriver && browsers.length > 1) {
|
|
186
|
+
// Split: one entry per browser
|
|
187
|
+
for (const wd of browsers) {
|
|
188
|
+
expandedGrid[`${name}-${wd}`] = {
|
|
189
|
+
paths: cellPaths,
|
|
190
|
+
webdrivers: wd,
|
|
191
|
+
servicers: meta.servicers,
|
|
192
|
+
services: meta.services,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
} else {
|
|
196
|
+
// No split: single entry with all browsers as space-separated string
|
|
197
|
+
let webdriversValue = '';
|
|
198
|
+
if (meta.webdriver && browsers.length > 0) {
|
|
199
|
+
webdriversValue = browsers.join(' ');
|
|
200
|
+
}
|
|
201
|
+
expandedGrid[name] = {
|
|
202
|
+
paths: cellPaths,
|
|
203
|
+
webdrivers: webdriversValue,
|
|
204
|
+
servicers: meta.servicers,
|
|
205
|
+
services: meta.services,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return expandedGrid;
|
|
211
|
+
}
|
package/core/series.js
CHANGED
|
@@ -4,13 +4,13 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
|
|
5
5
|
import { assert, fail, testflow } from './core.js';
|
|
6
6
|
import { parse, parse_failure } from './format.js';
|
|
7
|
-
import { ProcessArgs } from './
|
|
8
|
-
import settings from './settings.js';
|
|
7
|
+
import { ProcessArgs } from './process-args.js';
|
|
8
|
+
import { settings } from './settings.js';
|
|
9
9
|
import { spawn } from './spawn.js';
|
|
10
10
|
import { stringify } from './util.js';
|
|
11
11
|
import { log, log_error } from '../logging/logging.js';
|
|
12
12
|
import { LogPipe } from '../logging/logpipe.js';
|
|
13
|
-
import { DriverBase } from '../webdriver/
|
|
13
|
+
import { DriverBase } from '../webdriver/driver-base.js';
|
|
14
14
|
|
|
15
15
|
import {
|
|
16
16
|
format_started,
|
|
@@ -27,7 +27,7 @@ import {
|
|
|
27
27
|
const __filename = fileURLToPath(import.meta.url);
|
|
28
28
|
const __dirname = nodepath.dirname(__filename);
|
|
29
29
|
|
|
30
|
-
const
|
|
30
|
+
const rootFolder = () => settings.testsFolder;
|
|
31
31
|
const root_dir = nodepath.resolve('.');
|
|
32
32
|
|
|
33
33
|
const kKungFuDeathGripTimeout = {};
|
|
@@ -107,7 +107,7 @@ class Series {
|
|
|
107
107
|
this.ocnt = 0;
|
|
108
108
|
|
|
109
109
|
this.core = core || testflow.core;
|
|
110
|
-
testflow.lock({ core: this.core });
|
|
110
|
+
testflow.lock({ core: this.core, series: this });
|
|
111
111
|
|
|
112
112
|
this.core.setTimeout(timeout);
|
|
113
113
|
this.core.clearStats();
|
|
@@ -145,9 +145,6 @@ class Series {
|
|
|
145
145
|
await this.runFor(this.failures, '2');
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
|
-
|
|
149
|
-
await settings.servicer.shutdown();
|
|
150
|
-
|
|
151
148
|
log(`Elapsed: ${Date.now() - start_time}ms`);
|
|
152
149
|
}
|
|
153
150
|
|
|
@@ -157,7 +154,7 @@ class Series {
|
|
|
157
154
|
async runFor(patterns, name_postfix = '') {
|
|
158
155
|
let tests = await this.build({
|
|
159
156
|
patterns,
|
|
160
|
-
folder:
|
|
157
|
+
folder: rootFolder(),
|
|
161
158
|
virtual_folder: this.invocation,
|
|
162
159
|
});
|
|
163
160
|
|
|
@@ -181,7 +178,10 @@ class Series {
|
|
|
181
178
|
}
|
|
182
179
|
|
|
183
180
|
shutdown() {
|
|
181
|
+
this.shutdownServicer();
|
|
184
182
|
testflow.unlock();
|
|
183
|
+
|
|
184
|
+
console.log(`Testsuite: shutdown`);
|
|
185
185
|
return this.failures.length > 0 ? this.failures : null;
|
|
186
186
|
}
|
|
187
187
|
|
|
@@ -367,8 +367,24 @@ class Series {
|
|
|
367
367
|
return [];
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
+
// Filter subfolders to only process those that match the patterns
|
|
371
|
+
let filteredSubfolders = subfolders;
|
|
372
|
+
if (patterns.length > 0) {
|
|
373
|
+
filteredSubfolders = subfolders.filter(subfolder => {
|
|
374
|
+
const subfolderPath = `${folder}/${subfolder}`;
|
|
375
|
+
return (
|
|
376
|
+
this.matchedPatterns({
|
|
377
|
+
path: subfolderPath,
|
|
378
|
+
webdriver,
|
|
379
|
+
patterns,
|
|
380
|
+
path_is_not_final: true,
|
|
381
|
+
}).length > 0
|
|
382
|
+
);
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
370
386
|
let subtests_for_subfolders = await Promise.all(
|
|
371
|
-
|
|
387
|
+
filteredSubfolders.map(subfolder =>
|
|
372
388
|
this.build({
|
|
373
389
|
patterns,
|
|
374
390
|
folder: `${folder}/${subfolder}`,
|
|
@@ -428,15 +444,16 @@ class Series {
|
|
|
428
444
|
test_module.expected_failures || [],
|
|
429
445
|
);
|
|
430
446
|
|
|
431
|
-
// Initialize
|
|
432
|
-
|
|
447
|
+
// Initialize.
|
|
448
|
+
// Servicer is only created when explicitly requested via 'servicer' property.
|
|
449
|
+
// Tests with just 'services' but no 'servicer' won't trigger servicer creation.
|
|
450
|
+
const has_servicer = test_module.servicer !== undefined;
|
|
451
|
+
const has_init = test_module.init;
|
|
452
|
+
if (has_servicer || has_init) {
|
|
433
453
|
let init = async () => {
|
|
434
|
-
//
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
chain = chain.then(() => settings.servicer.start(service));
|
|
438
|
-
}
|
|
439
|
-
await chain;
|
|
454
|
+
// Initialize servicer with services (if any).
|
|
455
|
+
const servicer = this.getServicer(test_module.servicer);
|
|
456
|
+
await servicer.init(test_module.services);
|
|
440
457
|
|
|
441
458
|
// Do initialization if any.
|
|
442
459
|
if (test_module.init) {
|
|
@@ -460,7 +477,7 @@ class Series {
|
|
|
460
477
|
// A function to notify the servicer the test is about to start and then
|
|
461
478
|
// to invoke the test.
|
|
462
479
|
const test_wrap = () =>
|
|
463
|
-
Promise.resolve(
|
|
480
|
+
Promise.resolve(this.#servicer?.ontest(name)).then(test);
|
|
464
481
|
|
|
465
482
|
let failures_info = Series.failuresInfo({
|
|
466
483
|
failures: expected_failures,
|
|
@@ -484,18 +501,14 @@ class Series {
|
|
|
484
501
|
}
|
|
485
502
|
|
|
486
503
|
// Uninitialize.
|
|
487
|
-
if (
|
|
504
|
+
if (has_servicer || has_init) {
|
|
488
505
|
let uninit = async () => {
|
|
489
506
|
// Deinitalize test env.
|
|
490
507
|
if (test_module.uninit) {
|
|
491
508
|
await test_module.uninit();
|
|
492
509
|
}
|
|
493
|
-
//
|
|
494
|
-
await
|
|
495
|
-
[...(test_module.services || [])]
|
|
496
|
-
.reverse()
|
|
497
|
-
.map(s => settings.servicer.stop(s)),
|
|
498
|
-
);
|
|
510
|
+
// Deinitialize servicer with services.
|
|
511
|
+
await this.#servicer?.deinit(test_module.services);
|
|
499
512
|
};
|
|
500
513
|
tests.push({
|
|
501
514
|
name: `${virtual_folder}/uninit`,
|
|
@@ -910,7 +923,7 @@ class Series {
|
|
|
910
923
|
.readdirSync(nodepath.join(root_dir, folder))
|
|
911
924
|
.filter(
|
|
912
925
|
n =>
|
|
913
|
-
|
|
926
|
+
settings.testFilePattern.test(n) &&
|
|
914
927
|
(!settings.ignorePattern || !settings.ignorePattern.test(n)),
|
|
915
928
|
);
|
|
916
929
|
}
|
|
@@ -938,7 +951,10 @@ class Series {
|
|
|
938
951
|
args.push('--webdriver', webdriver);
|
|
939
952
|
}
|
|
940
953
|
|
|
941
|
-
|
|
954
|
+
// Pass run ID to child process to ensure consistent test artifact IDs
|
|
955
|
+
const env = { ...process.env, WATEST_RUN: settings.run };
|
|
956
|
+
|
|
957
|
+
return spawn('node', args, { env }, buffer =>
|
|
942
958
|
this.processChildProcessOutput(name, buffer),
|
|
943
959
|
).catch(e => {
|
|
944
960
|
log_error(e);
|
|
@@ -1032,6 +1048,32 @@ class Series {
|
|
|
1032
1048
|
}
|
|
1033
1049
|
}
|
|
1034
1050
|
}
|
|
1051
|
+
|
|
1052
|
+
getServicer(requestedType) {
|
|
1053
|
+
// Create servicer if none exists or if switching to a different type.
|
|
1054
|
+
// Note: we don't shutdown the old servicer here - the servicer factory
|
|
1055
|
+
// handles stopping conflicting services for better performance.
|
|
1056
|
+
if (
|
|
1057
|
+
!this.#servicer ||
|
|
1058
|
+
(requestedType !== undefined && this.#servicer.type !== requestedType)
|
|
1059
|
+
) {
|
|
1060
|
+
this.#servicer = this.createServicer(requestedType);
|
|
1061
|
+
}
|
|
1062
|
+
return this.#servicer;
|
|
1063
|
+
}
|
|
1064
|
+
|
|
1065
|
+
createServicer(servicerType) {
|
|
1066
|
+
return settings.getServicer(servicerType);
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
shutdownServicer() {
|
|
1070
|
+
if (this.#servicer) {
|
|
1071
|
+
this.#servicer.shutdown();
|
|
1072
|
+
this.#servicer = null;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
#servicer;
|
|
1035
1077
|
}
|
|
1036
1078
|
|
|
1037
1079
|
export { Series };
|
package/core/settings.js
CHANGED
|
@@ -7,16 +7,18 @@ class Settings {
|
|
|
7
7
|
this.tmp_storage_dir = '';
|
|
8
8
|
this.logger = null;
|
|
9
9
|
this.servicer = null;
|
|
10
|
+
this.silent = false;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
async initialize() {
|
|
13
|
+
async initialize(options = {}) {
|
|
14
|
+
this.silent = options.silent || false;
|
|
13
15
|
this.rc = (await import(path.resolve('.', './.watestrc.js'))).default;
|
|
14
16
|
|
|
15
17
|
this.logger = (
|
|
16
18
|
await import(this.rc.logger || '../interfaces/logger.js')
|
|
17
19
|
).default;
|
|
18
20
|
|
|
19
|
-
this.
|
|
21
|
+
this.getServicer = (
|
|
20
22
|
await import(this.rc.servicer || '../interfaces/servicer.js')
|
|
21
23
|
).default;
|
|
22
24
|
|
|
@@ -47,33 +49,49 @@ class Settings {
|
|
|
47
49
|
return parseInt(this.rc.debunk_limit) || 5;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
get testsFolder() {
|
|
53
|
+
return this.rc?.tests_folder ?? 'tests';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get testFilePattern() {
|
|
57
|
+
return this.rc.test_file_pattern || /^t[-_]/;
|
|
58
|
+
}
|
|
59
|
+
|
|
50
60
|
get timeout() {
|
|
51
61
|
return parseInt(this.rc.timeout) || 0;
|
|
52
62
|
}
|
|
53
63
|
|
|
54
64
|
setupTmpStorageDir() {
|
|
55
65
|
if (!this.rc.tmp_dir) {
|
|
56
|
-
|
|
66
|
+
if (!this.silent) {
|
|
67
|
+
console.log(`Settings: no temporary storage dir`);
|
|
68
|
+
}
|
|
57
69
|
return;
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
this.tmp_storage_dir = path.join(this.rc.tmp_dir, 'watest-tmpstorage');
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
if (!this.silent) {
|
|
74
|
+
console.log(
|
|
75
|
+
`Settings: temporary storage dir is at ${this.tmp_storage_dir}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
64
78
|
}
|
|
65
79
|
|
|
66
80
|
setupLogDir() {
|
|
67
81
|
const log_dir = this.rc.log_dir;
|
|
68
82
|
if (!log_dir) {
|
|
69
|
-
|
|
83
|
+
if (!this.silent) {
|
|
84
|
+
console.log('Settings: no file logging');
|
|
85
|
+
}
|
|
70
86
|
return;
|
|
71
87
|
}
|
|
72
88
|
|
|
73
89
|
this.run = this.rc.run || `${parseInt(Date.now() / 1000)}`;
|
|
74
90
|
|
|
75
91
|
this.log_dir = path.join(log_dir, this.run);
|
|
76
|
-
|
|
92
|
+
if (!this.silent) {
|
|
93
|
+
console.log(`Settings: logging into ${log_dir}`);
|
|
94
|
+
}
|
|
77
95
|
}
|
|
78
96
|
|
|
79
97
|
setupWebdrivers() {
|
|
@@ -100,10 +118,10 @@ class Settings {
|
|
|
100
118
|
this.webdriver_window_height =
|
|
101
119
|
parseInt(this.rc.webdriver_window_height) || 768;
|
|
102
120
|
|
|
103
|
-
if (this.webdrivers) {
|
|
121
|
+
if (this.webdrivers && !this.silent) {
|
|
104
122
|
console.log(`Settings: ${this.webdrivers.join(', ')} webdrivers`);
|
|
105
123
|
}
|
|
106
124
|
}
|
|
107
125
|
}
|
|
108
126
|
|
|
109
|
-
export
|
|
127
|
+
export const settings = new Settings();
|
package/core/system.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { spawn } from './spawn.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Run a shell command and capture its output
|
|
5
|
+
* @param {string} cmd - Command to run
|
|
6
|
+
* @param {string[]} args - Command arguments
|
|
7
|
+
* @param {object} options - Spawn options
|
|
8
|
+
* @returns {Promise<{stdout: string, stderr: string, exitCode: number, stdoutLines: string[], stderrLines: string[]}>}
|
|
9
|
+
*/
|
|
10
|
+
export async function runCommand(cmd, args = [], options = {}) {
|
|
11
|
+
let stdout = [];
|
|
12
|
+
let stderr = [];
|
|
13
|
+
let exitCode = 0;
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
exitCode = await spawn(cmd, args, options, buffer => {
|
|
17
|
+
for (let { str_data, is_stdout } of buffer) {
|
|
18
|
+
const lines = str_data.split('\n').filter(line => line);
|
|
19
|
+
if (is_stdout) {
|
|
20
|
+
stdout.push(...lines);
|
|
21
|
+
} else {
|
|
22
|
+
stderr.push(...lines);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
} catch (code) {
|
|
27
|
+
exitCode = code;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
stdout: stdout.join('\n'),
|
|
32
|
+
stderr: stderr.join('\n'),
|
|
33
|
+
exitCode,
|
|
34
|
+
stdoutLines: stdout,
|
|
35
|
+
stderrLines: stderr,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Run a bash script with proper error handling
|
|
41
|
+
* @param {string} script - Bash script content
|
|
42
|
+
* @param {string[]} args - Script arguments
|
|
43
|
+
* @param {object} options - Spawn options
|
|
44
|
+
* @returns {Promise<{stdout: string, stderr: string, exitCode: number, stdoutLines: string[], stderrLines: string[]}>}
|
|
45
|
+
*/
|
|
46
|
+
export async function runBashScript(script, args = [], options = {}) {
|
|
47
|
+
return runCommand('bash', ['-c', script, '--', ...args], options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Run a shell command and return only stdout (for simple cases)
|
|
52
|
+
* @param {string} cmd - Command to run
|
|
53
|
+
* @param {string[]} args - Command arguments
|
|
54
|
+
* @param {object} options - Spawn options
|
|
55
|
+
* @returns {Promise<string>} stdout content
|
|
56
|
+
*/
|
|
57
|
+
export async function execCommand(cmd, args = [], options = {}) {
|
|
58
|
+
const result = await runCommand(cmd, args, options);
|
|
59
|
+
if (result.exitCode !== 0) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`Command failed with exit code ${result.exitCode}: ${result.stderr}`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return result.stdout;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Export the raw spawn function for advanced use cases
|
|
68
|
+
export { spawn };
|
package/core/util.js
CHANGED
package/eslint.config.js
CHANGED
package/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
success,
|
|
9
9
|
todo,
|
|
10
10
|
warn,
|
|
11
|
+
getServicer,
|
|
11
12
|
} from './core/core.js';
|
|
12
13
|
|
|
13
14
|
import {
|
|
@@ -21,19 +22,27 @@ import {
|
|
|
21
22
|
test_contains,
|
|
22
23
|
} from './core/base.js';
|
|
23
24
|
|
|
24
|
-
import settings from './core/settings.js';
|
|
25
|
+
import { settings } from './core/settings.js';
|
|
25
26
|
import { inspect } from './core/util.js';
|
|
26
|
-
import { AppDriver } from './webdriver/
|
|
27
|
-
import { ControlDriver } from './webdriver/
|
|
27
|
+
import { AppDriver } from './webdriver/app-driver.js';
|
|
28
|
+
import { ControlDriver } from './webdriver/control-driver.js';
|
|
28
29
|
import { start_session, scope } from './webdriver/session.js';
|
|
30
|
+
import {
|
|
31
|
+
runCommand,
|
|
32
|
+
runBashScript,
|
|
33
|
+
execCommand,
|
|
34
|
+
spawn,
|
|
35
|
+
} from './core/system.js';
|
|
29
36
|
|
|
30
37
|
export {
|
|
31
38
|
AppDriver,
|
|
32
39
|
ControlDriver,
|
|
33
40
|
assert,
|
|
34
41
|
contains,
|
|
42
|
+
execCommand,
|
|
35
43
|
failed,
|
|
36
44
|
fail,
|
|
45
|
+
getServicer,
|
|
37
46
|
group,
|
|
38
47
|
is,
|
|
39
48
|
is_output,
|
|
@@ -42,7 +51,10 @@ export {
|
|
|
42
51
|
not_reached,
|
|
43
52
|
no_throws,
|
|
44
53
|
ok,
|
|
54
|
+
runBashScript,
|
|
55
|
+
runCommand,
|
|
45
56
|
scope,
|
|
57
|
+
spawn,
|
|
46
58
|
start_session,
|
|
47
59
|
success,
|
|
48
60
|
todo,
|
package/interfaces/servicer.js
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
* Manages services requested by a testsuite.
|
|
3
3
|
*/
|
|
4
4
|
class Servicer {
|
|
5
|
+
/**
|
|
6
|
+
* Initialize servicer and optionally start services.
|
|
7
|
+
* Called at the beginning of a test folder.
|
|
8
|
+
*/
|
|
9
|
+
async init(/* services */) {}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Deinitialize servicer and stop services.
|
|
13
|
+
* Called at the end of a test folder.
|
|
14
|
+
*/
|
|
15
|
+
async deinit(/* services */) {}
|
|
16
|
+
|
|
5
17
|
/**
|
|
6
18
|
* Starts a service.
|
|
7
19
|
*/
|
|
@@ -15,9 +27,7 @@ class Servicer {
|
|
|
15
27
|
/**
|
|
16
28
|
* Called when the testsuite gets shutdown.
|
|
17
29
|
*/
|
|
18
|
-
shutdown() {
|
|
19
|
-
console.log(`Testsuite: shutdown`);
|
|
20
|
-
}
|
|
30
|
+
shutdown() {}
|
|
21
31
|
|
|
22
32
|
/**
|
|
23
33
|
* Called when test is started.
|
package/logging/logging.js
CHANGED