@camperaid/watest 2.5.1 → 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/core.js +43 -15
- package/core/deps.js +211 -0
- package/core/{process_args.js → process-args.js} +8 -0
- package/core/series.js +41 -40
- package/core/settings.js +27 -9
- package/core/util.js +1 -1
- package/index.js +5 -3
- package/interfaces/servicer.js +13 -2
- 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/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} +2 -2
- package/tests/e2e/{t_loader_mixed.js → t-loader-mixed.js} +5 -5
- package/tests/e2e/{t_loader_multiple_patterns.js → t-loader-multiple-patterns.js} +6 -6
- package/tests/e2e/{t_loader_multiple.js → t-loader-multiple.js} +5 -5
- package/tests/e2e/{t_loader.js → t-loader.js} +2 -2
- package/tests/e2e/{t_single.js → t-single.js} +2 -2
- package/tests/e2e/{t_wd_firefox_chrome_pattern.js → t-wd-firefox-chrome-pattern.js} +6 -6
- package/tests/e2e/{t_wd_firefox_chrome.js → t-wd-firefox-chrome.js} +5 -5
- package/tests/e2e/{t_wd_firefox.js → t-wd-firefox.js} +3 -3
- package/tests/e2e/{t_wd_mixed_firefox_chrome.js → t-wd-mixed-firefox-chrome.js} +7 -7
- package/tests/e2e/{t_wd_mixed_firefox.js → t-wd-mixed-firefox.js} +5 -5
- package/tests/meta.js +1 -1
- 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} +1 -1
- 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 → t-servicer.js} +5 -38
- 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_system.js → t-system.js} +0 -0
- /package/tests/base/{t_test_.js → t-test-.js} +0 -0
- /package/tests/base/{t_throws.js → t-throws.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_pattern_filtering.js → t-pattern-filtering.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/perform/{t_failure_notest.js → t-failure-notest.js} +0 -0
- /package/tests/series/perform/{t_failure.js → t-failure.js} +0 -0
- /package/tests/series/perform/{t_intermittent_global.js → t-intermittent-global.js} +0 -0
- /package/tests/series/perform/{t_intermittent.js → t-intermittent.js} +0 -0
- /package/tests/series/perform/{t_missing_perma.js → t-missing-perma.js} +0 -0
- /package/tests/series/perform/{t_nested.js → t-nested.js} +0 -0
- /package/tests/series/perform/{t_perma.js → t-perma.js} +0 -0
- /package/tests/series/perform/{t_success.js → t-success.js} +0 -0
- /package/tests/series/run/{t_debunk_failure.js → t-debunk-failure.js} +0 -0
- /package/tests/series/run/{t_debunk_success.js → t-debunk-success.js} +0 -0
- /package/tests/series/run/{t_nested.js → t-nested.js} +0 -0
- /package/tests/series/run/{t_verify_webdriver.js → t-verify-webdriver.js} +0 -0
- /package/tests/series/run/{t_verify.js → t-verify.js} +0 -0
- /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();
|
|
@@ -154,7 +154,7 @@ class Series {
|
|
|
154
154
|
async runFor(patterns, name_postfix = '') {
|
|
155
155
|
let tests = await this.build({
|
|
156
156
|
patterns,
|
|
157
|
-
folder:
|
|
157
|
+
folder: rootFolder(),
|
|
158
158
|
virtual_folder: this.invocation,
|
|
159
159
|
});
|
|
160
160
|
|
|
@@ -178,8 +178,10 @@ class Series {
|
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
shutdown() {
|
|
181
|
-
|
|
181
|
+
this.shutdownServicer();
|
|
182
182
|
testflow.unlock();
|
|
183
|
+
|
|
184
|
+
console.log(`Testsuite: shutdown`);
|
|
183
185
|
return this.failures.length > 0 ? this.failures : null;
|
|
184
186
|
}
|
|
185
187
|
|
|
@@ -442,23 +444,16 @@ class Series {
|
|
|
442
444
|
test_module.expected_failures || [],
|
|
443
445
|
);
|
|
444
446
|
|
|
445
|
-
// Set services if given.
|
|
446
|
-
if (test_module.servicer) {
|
|
447
|
-
if (this.#servicerType) {
|
|
448
|
-
throw new Error(`No nested servicers are supported`);
|
|
449
|
-
}
|
|
450
|
-
this.#servicerType = test_module.servicer;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
447
|
// Initialize.
|
|
454
|
-
|
|
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) {
|
|
455
453
|
let init = async () => {
|
|
456
|
-
//
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
chain = chain.then(() => this.getServicer().start(service));
|
|
460
|
-
}
|
|
461
|
-
await chain;
|
|
454
|
+
// Initialize servicer with services (if any).
|
|
455
|
+
const servicer = this.getServicer(test_module.servicer);
|
|
456
|
+
await servicer.init(test_module.services);
|
|
462
457
|
|
|
463
458
|
// Do initialization if any.
|
|
464
459
|
if (test_module.init) {
|
|
@@ -506,23 +501,14 @@ class Series {
|
|
|
506
501
|
}
|
|
507
502
|
|
|
508
503
|
// Uninitialize.
|
|
509
|
-
if (
|
|
504
|
+
if (has_servicer || has_init) {
|
|
510
505
|
let uninit = async () => {
|
|
511
506
|
// Deinitalize test env.
|
|
512
507
|
if (test_module.uninit) {
|
|
513
508
|
await test_module.uninit();
|
|
514
509
|
}
|
|
515
|
-
//
|
|
516
|
-
await
|
|
517
|
-
[...(test_module.services || [])]
|
|
518
|
-
.reverse()
|
|
519
|
-
.map(s => this.getServicer().stop(s)),
|
|
520
|
-
);
|
|
521
|
-
|
|
522
|
-
// Clean up service-level servicer
|
|
523
|
-
await this.#servicer?.shutdown();
|
|
524
|
-
this.#servicer = null;
|
|
525
|
-
this.#servicerType = null;
|
|
510
|
+
// Deinitialize servicer with services.
|
|
511
|
+
await this.#servicer?.deinit(test_module.services);
|
|
526
512
|
};
|
|
527
513
|
tests.push({
|
|
528
514
|
name: `${virtual_folder}/uninit`,
|
|
@@ -937,7 +923,7 @@ class Series {
|
|
|
937
923
|
.readdirSync(nodepath.join(root_dir, folder))
|
|
938
924
|
.filter(
|
|
939
925
|
n =>
|
|
940
|
-
|
|
926
|
+
settings.testFilePattern.test(n) &&
|
|
941
927
|
(!settings.ignorePattern || !settings.ignorePattern.test(n)),
|
|
942
928
|
);
|
|
943
929
|
}
|
|
@@ -965,7 +951,10 @@ class Series {
|
|
|
965
951
|
args.push('--webdriver', webdriver);
|
|
966
952
|
}
|
|
967
953
|
|
|
968
|
-
|
|
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 =>
|
|
969
958
|
this.processChildProcessOutput(name, buffer),
|
|
970
959
|
).catch(e => {
|
|
971
960
|
log_error(e);
|
|
@@ -1060,9 +1049,15 @@ class Series {
|
|
|
1060
1049
|
}
|
|
1061
1050
|
}
|
|
1062
1051
|
|
|
1063
|
-
getServicer() {
|
|
1064
|
-
|
|
1065
|
-
|
|
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);
|
|
1066
1061
|
}
|
|
1067
1062
|
return this.#servicer;
|
|
1068
1063
|
}
|
|
@@ -1071,8 +1066,14 @@ class Series {
|
|
|
1071
1066
|
return settings.getServicer(servicerType);
|
|
1072
1067
|
}
|
|
1073
1068
|
|
|
1069
|
+
shutdownServicer() {
|
|
1070
|
+
if (this.#servicer) {
|
|
1071
|
+
this.#servicer.shutdown();
|
|
1072
|
+
this.#servicer = null;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1074
1076
|
#servicer;
|
|
1075
|
-
#servicerType;
|
|
1076
1077
|
}
|
|
1077
1078
|
|
|
1078
1079
|
export { Series };
|
package/core/settings.js
CHANGED
|
@@ -7,9 +7,11 @@ 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 = (
|
|
@@ -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/util.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,10 +22,10 @@ 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';
|
|
29
30
|
import {
|
|
30
31
|
runCommand,
|
|
@@ -41,6 +42,7 @@ export {
|
|
|
41
42
|
execCommand,
|
|
42
43
|
failed,
|
|
43
44
|
fail,
|
|
45
|
+
getServicer,
|
|
44
46
|
group,
|
|
45
47
|
is,
|
|
46
48
|
is_output,
|
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,8 +27,7 @@ class Servicer {
|
|
|
15
27
|
/**
|
|
16
28
|
* Called when the testsuite gets shutdown.
|
|
17
29
|
*/
|
|
18
|
-
shutdown() {
|
|
19
|
-
}
|
|
30
|
+
shutdown() {}
|
|
20
31
|
|
|
21
32
|
/**
|
|
22
33
|
* Called when test is started.
|
package/logging/logging.js
CHANGED
package/logging/logpipe.js
CHANGED
|
@@ -2,7 +2,7 @@ import path from 'path';
|
|
|
2
2
|
|
|
3
3
|
import { FileStream as DefaultFileStream } from './filestream.js';
|
|
4
4
|
import { log, log_error } from './logging.js';
|
|
5
|
-
import settings from '../core/settings.js';
|
|
5
|
+
import { settings } from '../core/settings.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* A single instance of a logpipe writing to std and file streams.
|
|
@@ -15,6 +15,7 @@ class LogPipeInstance {
|
|
|
15
15
|
|
|
16
16
|
attach(invocation) {
|
|
17
17
|
this.invocation = invocation;
|
|
18
|
+
this.run = settings.run;
|
|
18
19
|
this.log_dir = path.join(settings.log_dir, this.invocation);
|
|
19
20
|
if (this.suppress_logging) {
|
|
20
21
|
return;
|
|
@@ -28,7 +29,7 @@ class LogPipeInstance {
|
|
|
28
29
|
log_error(e);
|
|
29
30
|
});
|
|
30
31
|
|
|
31
|
-
return settings.logger.testRunStarted({ run:
|
|
32
|
+
return settings.logger.testRunStarted({ run: this.run, invocation });
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
async logScreenshot(pic) {
|
|
@@ -41,7 +42,7 @@ class LogPipeInstance {
|
|
|
41
42
|
log(`Screenshot is captured and written to ${stream.filepath}`);
|
|
42
43
|
|
|
43
44
|
await settings.logger.writeLogFile({
|
|
44
|
-
run:
|
|
45
|
+
run: this.run,
|
|
45
46
|
invocation: this.invocation,
|
|
46
47
|
name,
|
|
47
48
|
content,
|
|
@@ -50,30 +51,42 @@ class LogPipeInstance {
|
|
|
50
51
|
|
|
51
52
|
logSourceMap() {
|
|
52
53
|
return settings.logger.writeSourceMap({
|
|
53
|
-
run:
|
|
54
|
+
run: this.run,
|
|
54
55
|
invocation: this.invocation,
|
|
55
56
|
});
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
release() {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
60
|
+
if (this.suppress_logging || !this.fstream) {
|
|
61
|
+
return Promise.resolve();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const filepath = this.fstream.filepath;
|
|
65
|
+
|
|
66
|
+
return this.fstream
|
|
67
|
+
.end()
|
|
68
|
+
.then(() => this.fstream.readFile())
|
|
69
|
+
.then(content =>
|
|
70
|
+
settings.logger.writeLogFile({
|
|
71
|
+
run: this.run,
|
|
72
|
+
invocation: this.invocation,
|
|
73
|
+
name: this.fname,
|
|
74
|
+
content,
|
|
75
|
+
zip: true,
|
|
76
|
+
}),
|
|
77
|
+
)
|
|
78
|
+
.catch(e => {
|
|
79
|
+
log_error(
|
|
80
|
+
`[logpipe:error] ✗ Logging shutdown failed for ${filepath}:`,
|
|
81
|
+
{
|
|
82
|
+
filepath,
|
|
67
83
|
invocation: this.invocation,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
log_error(`Logging shutdown rejected: ${e}`);
|
|
75
|
-
})
|
|
76
|
-
);
|
|
84
|
+
error: e.message,
|
|
85
|
+
code: e.code,
|
|
86
|
+
stack: e.stack,
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
});
|
|
77
90
|
}
|
|
78
91
|
}
|
|
79
92
|
|
|
@@ -127,6 +140,10 @@ class LogPipe {
|
|
|
127
140
|
// Remove the pipe from the stack to prevent anyone writing into it after
|
|
128
141
|
// its release.
|
|
129
142
|
let pipeToRelease = this.stack.pop();
|
|
143
|
+
if (!pipeToRelease) {
|
|
144
|
+
return Promise.resolve();
|
|
145
|
+
}
|
|
146
|
+
|
|
130
147
|
await pipeToRelease.release(...args);
|
|
131
148
|
|
|
132
149
|
if (this.suppress_logging) {
|
package/package.json
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
import { colorify, group, fail, success, is_output } from './test.js';
|
|
2
2
|
|
|
3
3
|
export async function test() {
|
|
4
|
-
// console.log
|
|
5
|
-
|
|
4
|
+
// console.log - Node adds yellow color codes to numbers in TTY
|
|
5
|
+
const colored3 = `\x1b[33m3\x1b[39m\n`;
|
|
6
|
+
|
|
7
|
+
await is_output(
|
|
8
|
+
() => console.log(3),
|
|
9
|
+
[colored3],
|
|
10
|
+
[],
|
|
11
|
+
`console.log(3)`,
|
|
12
|
+
);
|
|
6
13
|
|
|
7
14
|
// console.error
|
|
8
15
|
await is_output(
|
|
9
16
|
() => console.error(3),
|
|
10
17
|
[],
|
|
11
|
-
[
|
|
18
|
+
[colored3],
|
|
12
19
|
`console.error(3)`,
|
|
13
20
|
);
|
|
14
21
|
|