@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/README.md
CHANGED
|
@@ -1,215 +1,360 @@
|
|
|
1
1
|
# watest
|
|
2
2
|
|
|
3
|
-
WATest
|
|
4
|
-
testsuite designed for webdriver-based testing. It also can great be for
|
|
5
|
-
unit and integration testing.
|
|
3
|
+
WATest (Web Application Testsuite) is a lightweight, minimal-dependency test framework designed for webdriver-based E2E testing. Also works great for unit and integration tests.
|
|
6
4
|
|
|
7
5
|
## Install
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```
|
|
7
|
+
```bash
|
|
12
8
|
npm install @camperaid/watest --save
|
|
13
9
|
```
|
|
14
10
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Use `watest` command to run tests located in `tests/` folder. For example, add
|
|
11
|
+
Add to `package.json`:
|
|
18
12
|
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "watest"
|
|
17
|
+
}
|
|
22
18
|
}
|
|
23
19
|
```
|
|
24
20
|
|
|
25
|
-
|
|
21
|
+
Run tests:
|
|
26
22
|
|
|
27
|
-
```
|
|
23
|
+
```bash
|
|
28
24
|
npm test
|
|
29
25
|
```
|
|
30
26
|
|
|
31
|
-
## Structure
|
|
27
|
+
## Project Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
tests/
|
|
31
|
+
├── meta.js # Root test configuration
|
|
32
|
+
├── unit/
|
|
33
|
+
│ ├── meta.js # Folder-specific config
|
|
34
|
+
│ ├── t_foo.js # Test file (t_ prefix)
|
|
35
|
+
│ └── t_bar.js
|
|
36
|
+
└── e2e/
|
|
37
|
+
├── meta.js
|
|
38
|
+
└── t_login.js
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### meta.js Options
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
`meta.js` file describing the test flow.
|
|
43
|
+
Each test folder can have a `meta.js` file:
|
|
35
44
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
45
|
+
```javascript
|
|
46
|
+
// tests/e2e/meta.js
|
|
47
|
+
export const folders = ['login', 'checkout']; // Nested test folders
|
|
48
|
+
export const services = ['db', 'ws']; // Services to start
|
|
49
|
+
export const servicer = 'docker'; // Service manager: 'docker' | 'kubernetes'
|
|
50
|
+
export const webdriver = true; // Enable browser testing
|
|
51
|
+
export const init = async () => { /* setup */ };
|
|
52
|
+
export const uninit = async () => { /* teardown */ };
|
|
53
|
+
```
|
|
40
54
|
|
|
41
55
|
## Configuration
|
|
42
56
|
|
|
43
|
-
|
|
57
|
+
### Project Configuration File
|
|
58
|
+
|
|
59
|
+
Create `.watestrc.js` in your **project root** (not in watest):
|
|
60
|
+
|
|
61
|
+
```javascript
|
|
62
|
+
export default {
|
|
63
|
+
// Test run identifiers (from env or auto-generated)
|
|
64
|
+
run: process.env.WATEST_RUN,
|
|
65
|
+
invocation: process.env.WATEST_INVOCATION,
|
|
66
|
+
|
|
67
|
+
// Directories
|
|
68
|
+
log_dir: process.env.WATEST_LOG_DIR,
|
|
69
|
+
tmp_dir: process.env.WATEST_TMP_DIR || '/tmp',
|
|
70
|
+
|
|
71
|
+
// Test behavior
|
|
72
|
+
timeout: process.env.WATEST_TIMEOUT || 30000,
|
|
73
|
+
debunk_limit: process.env.WATEST_DEBUNK_LIMIT || 5,
|
|
74
|
+
|
|
75
|
+
// WebDriver settings
|
|
76
|
+
webdrivers: process.env.WATEST_WEBDRIVERS,
|
|
77
|
+
webdriver_headless: process.env.WATEST_WEBDRIVER_HEADLESS,
|
|
78
|
+
webdriver_loglevel: process.env.WATEST_WEBDRIVER_LOGLEVEL,
|
|
79
|
+
webdriver_window_width: process.env.WATEST_WEBDRIVER_WINDOW_WIDTH,
|
|
80
|
+
webdriver_window_height: process.env.WATEST_WEBDRIVER_WINDOW_HEIGHT,
|
|
81
|
+
|
|
82
|
+
// Integration hooks (absolute paths to your project files)
|
|
83
|
+
servicer: './tests/servicer.js', // Manages test services
|
|
84
|
+
logger: './tests/logserver.js', // Remote test logging
|
|
85
|
+
|
|
86
|
+
// Optional: ignore pattern
|
|
87
|
+
ignore_pattern: process.env.WATEST_IGNORE_PATTERN,
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Note**: This file should be created in your project root (where you run `watest`), not inside the watest directory itself.
|
|
92
|
+
|
|
93
|
+
### Environment Variables
|
|
94
|
+
|
|
95
|
+
Watest reads configuration from environment variables. Create `.env` in your project root or export them:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
# Test execution
|
|
99
|
+
WATEST_RUN=run-123 # Optional: Group multiple test invocations
|
|
100
|
+
WATEST_INVOCATION=inv-456 # Optional: Unique invocation identifier
|
|
101
|
+
WATEST_TIMEOUT=30000 # Test timeout in milliseconds
|
|
102
|
+
WATEST_DEBUNK_LIMIT=5 # Max retry attempts for flaky tests
|
|
103
|
+
|
|
104
|
+
# Directories
|
|
105
|
+
WATEST_LOG_DIR=/tmp/watest # Test logs directory
|
|
106
|
+
WATEST_TMP_DIR=/tmp # Temporary files directory
|
|
107
|
+
|
|
108
|
+
# WebDriver
|
|
109
|
+
WATEST_WEBDRIVERS='["chrome","firefox"]' # Browser list (JSON array)
|
|
110
|
+
WATEST_WEBDRIVER_HEADLESS=true # Run browsers headless
|
|
111
|
+
WATEST_WEBDRIVER_LOGLEVEL=info # WebDriver log level
|
|
112
|
+
WATEST_WEBDRIVER_WINDOW_WIDTH=1280 # Browser window width
|
|
113
|
+
WATEST_WEBDRIVER_WINDOW_HEIGHT=1024 # Browser window height
|
|
114
|
+
|
|
115
|
+
# Integration
|
|
116
|
+
WATEST_LOGGER_MODULE=./tests/logserver.js # Path to logger module
|
|
117
|
+
WATEST_SERVICER_MODULE=./tests/servicer.js # Path to servicer module
|
|
118
|
+
WATEST_IGNORE_PATTERN=_browser\.js$ # Regex for files to skip
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Webdrivers
|
|
122
|
+
|
|
123
|
+
Built-in webdriver configurations:
|
|
124
|
+
|
|
125
|
+
- `chrome` - Chrome browser
|
|
126
|
+
- `chrome-mobile` - Chrome with iPhone emulation
|
|
127
|
+
- `firefox` - Firefox browser
|
|
128
|
+
- `safari` - Safari browser
|
|
129
|
+
|
|
130
|
+
## CLI Options
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
watest [options] [patterns...]
|
|
134
|
+
|
|
135
|
+
Options:
|
|
136
|
+
--skip-on-fail Skip remaining tests in folder after first failure
|
|
137
|
+
--timeout Set webdriver condition timeout (ms)
|
|
138
|
+
--debunk Re-run passing test until it fails (flakiness detection)
|
|
139
|
+
-v, --verify Re-run failing tests
|
|
140
|
+
--deps Output metadata (servicers, services) as JSON
|
|
141
|
+
--grid Output grid metadata for distributed testing
|
|
142
|
+
-h, --help Show help
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Examples
|
|
44
146
|
|
|
45
|
-
|
|
147
|
+
```bash
|
|
148
|
+
watest # Run all tests
|
|
149
|
+
watest tests/unit # Run specific folder
|
|
150
|
+
watest tests/e2e/t_login.js # Run specific file
|
|
151
|
+
watest chrome firefox # Run with multiple browsers
|
|
152
|
+
watest --debunk tests/e2e # Find flaky tests
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Test API
|
|
46
156
|
|
|
47
|
-
|
|
48
|
-
- `chrome-mobile` to run tests in Chrome on iPhone
|
|
49
|
-
- `firefox` to run tests in Firefox
|
|
50
|
-
- `safari` to run tests in Safari
|
|
157
|
+
### Assertions
|
|
51
158
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
Here's an example of `.env` file:
|
|
159
|
+
```javascript
|
|
160
|
+
import { ok, is, contains, throws, no_throws } from '@camperaid/watest';
|
|
55
161
|
|
|
162
|
+
ok(value, 'value is truthy');
|
|
163
|
+
is(got, expected, 'values match');
|
|
164
|
+
contains(array, expected, 'array contains values');
|
|
165
|
+
throws(() => fn(), 'throws error');
|
|
166
|
+
no_throws(() => fn(), 'does not throw');
|
|
56
167
|
```
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
168
|
+
|
|
169
|
+
### Reporting
|
|
170
|
+
|
|
171
|
+
```javascript
|
|
172
|
+
import { success, fail, info, warn, todo, group } from '@camperaid/watest';
|
|
173
|
+
|
|
174
|
+
group('User authentication');
|
|
175
|
+
success('login completed');
|
|
176
|
+
fail('validation failed');
|
|
177
|
+
info('processing 100 items');
|
|
178
|
+
warn('deprecated API used');
|
|
179
|
+
todo('implement logout');
|
|
63
180
|
```
|
|
64
181
|
|
|
65
|
-
|
|
182
|
+
### Utilities
|
|
66
183
|
|
|
67
|
-
|
|
184
|
+
```javascript
|
|
185
|
+
import { assert, not_reached, inspect } from '@camperaid/watest';
|
|
68
186
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
187
|
+
assert(condition, 'condition must be true'); // Fails with stack trace
|
|
188
|
+
not_reached('should not get here'); // Always fails with stack trace
|
|
189
|
+
inspect(object); // Pretty-print object
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Test Helpers
|
|
73
193
|
|
|
74
|
-
|
|
194
|
+
```javascript
|
|
195
|
+
import { test_is, test_contains } from '@camperaid/watest';
|
|
75
196
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
- `intermittent(msg)` to print an intermittent message
|
|
81
|
-
- `todo(msg)` to print a todo message
|
|
82
|
-
- `warn(msg)` to print a warning message
|
|
197
|
+
// Silent checks (no success/fail logging)
|
|
198
|
+
if (test_is(got, expected)) { /* ... */ }
|
|
199
|
+
if (test_contains(array, subset)) { /* ... */ }
|
|
200
|
+
```
|
|
83
201
|
|
|
84
|
-
## Integration
|
|
202
|
+
## Integration Testing
|
|
85
203
|
|
|
86
|
-
|
|
204
|
+
Configure a servicer in `.watestrc.js` to manage services. The servicer interface (`interfaces/servicer.js`) must implement:
|
|
87
205
|
|
|
88
|
-
|
|
206
|
+
- `start(services)` - Start services
|
|
207
|
+
- `stop()` - Stop services
|
|
89
208
|
|
|
90
|
-
|
|
209
|
+
When watest encounters `services` in `meta.js`, it calls the servicer to start/stop services.
|
|
91
210
|
|
|
92
|
-
|
|
211
|
+
## E2E Testing
|
|
93
212
|
|
|
94
213
|
### Driver
|
|
95
214
|
|
|
96
|
-
|
|
97
|
-
|
|
215
|
+
Chainable wrapper around Selenium WebDriver:
|
|
216
|
+
|
|
217
|
+
```javascript
|
|
218
|
+
import { scope } from '@camperaid/watest';
|
|
219
|
+
|
|
220
|
+
export const test = scope('https://example.com', async session => {
|
|
221
|
+
await session.driver
|
|
222
|
+
.get('/')
|
|
223
|
+
.findElement('#login')
|
|
224
|
+
.click()
|
|
225
|
+
.findElement('#email')
|
|
226
|
+
.sendKeys('user@example.com');
|
|
227
|
+
});
|
|
228
|
+
```
|
|
98
229
|
|
|
99
230
|
### AppDriver
|
|
100
231
|
|
|
101
|
-
|
|
102
|
-
application block would look the following way. Let's you have a chat
|
|
103
|
-
webapp, so you might want to have `Chat` appdriver like this:
|
|
232
|
+
Base class for page object patterns:
|
|
104
233
|
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
sendMessage({ from, to, message }) {
|
|
108
|
-
return this.chain(() => this.action(`Send a message from ${from} to ${to})).
|
|
109
|
-
sendKeys(this.MainInput, message, `send keys to main input field`).
|
|
110
|
-
hitEnter());
|
|
111
|
-
}
|
|
234
|
+
```javascript
|
|
235
|
+
import { AppDriver } from '@camperaid/watest';
|
|
112
236
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
237
|
+
class LoginPage extends AppDriver {
|
|
238
|
+
login({ email, password }) {
|
|
239
|
+
return this.chain(() =>
|
|
240
|
+
this.action('Login')
|
|
241
|
+
.sendKeys(this.Email, email)
|
|
242
|
+
.sendKeys(this.Password, password)
|
|
243
|
+
.click(this.Submit)
|
|
244
|
+
);
|
|
116
245
|
}
|
|
117
246
|
|
|
118
247
|
getSelectors() {
|
|
119
248
|
return {
|
|
120
|
-
Self: '#
|
|
121
|
-
|
|
122
|
-
|
|
249
|
+
Self: '#login-page',
|
|
250
|
+
Email: '#email',
|
|
251
|
+
Password: '#password',
|
|
252
|
+
Submit: 'button[type=submit]',
|
|
123
253
|
};
|
|
124
254
|
}
|
|
125
255
|
}
|
|
126
|
-
module.exports = Chat;
|
|
127
256
|
```
|
|
128
257
|
|
|
129
|
-
|
|
258
|
+
### ControlDriver
|
|
130
259
|
|
|
131
|
-
|
|
132
|
-
module.exports.test = scope(url, async session => {
|
|
133
|
-
await session.Chat.get().
|
|
134
|
-
sendMessage({ from: 'me', to: 'you', 'hi' }).
|
|
135
|
-
checkMessages({ messages: ['hi']});
|
|
136
|
-
})
|
|
137
|
-
```
|
|
260
|
+
Base class for reusable UI component drivers (dropdowns, modals, etc.).
|
|
138
261
|
|
|
139
|
-
##
|
|
262
|
+
## Expected Failures
|
|
140
263
|
|
|
141
|
-
|
|
142
|
-
perma failures:
|
|
264
|
+
Handle known intermittent or permanent failures in `meta.js`:
|
|
143
265
|
|
|
144
|
-
```
|
|
145
|
-
|
|
266
|
+
```javascript
|
|
267
|
+
export const expected_failures = [
|
|
146
268
|
[
|
|
147
|
-
test_file,
|
|
269
|
+
'test_file.js', // or '*' for all files
|
|
148
270
|
[
|
|
149
|
-
[platform,
|
|
271
|
+
[platform, type, group, failures, description],
|
|
150
272
|
],
|
|
151
273
|
],
|
|
152
274
|
];
|
|
153
275
|
```
|
|
154
276
|
|
|
155
|
-
|
|
277
|
+
- `platform`: `'all'`, `'darwin'`, `'linux'`, `'darwin-chrome'`, etc.
|
|
278
|
+
- `type`: `'perma'` or `'intermittent'`
|
|
279
|
+
- `group`: Test group name or `'*'`
|
|
280
|
+
- `failures`: Array of failure message patterns
|
|
281
|
+
- `description`: Human-readable description
|
|
156
282
|
|
|
157
|
-
|
|
158
|
-
it is a dash separated `process.platform` and webdriver name, for example,
|
|
159
|
-
`darwin-chrome`, can be `all` to indicate that failure can happen on any
|
|
160
|
-
platform
|
|
161
|
-
- `failure_type` is either 'perma' or 'intermittent'
|
|
162
|
-
- `test_group` is a test group name, can be `*`
|
|
163
|
-
- `...failures` is a list of expected failures
|
|
283
|
+
Example:
|
|
164
284
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```
|
|
168
|
-
module.exports.expected_failures = [
|
|
285
|
+
```javascript
|
|
286
|
+
export const expected_failures = [
|
|
169
287
|
[
|
|
170
288
|
'*',
|
|
171
289
|
[
|
|
172
|
-
['all', 'intermittent', '*', [
|
|
173
|
-
[
|
|
174
|
-
'all',
|
|
175
|
-
'intermittent',
|
|
176
|
-
'*',
|
|
177
|
-
[`[map:bounds] map retrieveBounds timeout`, `*`],
|
|
178
|
-
`GoogleMaps is not loaded`,
|
|
179
|
-
],
|
|
180
|
-
[
|
|
181
|
-
'darwin-safari',
|
|
182
|
-
'intermittent',
|
|
183
|
-
'*',
|
|
184
|
-
[`Wait for LocSearch focus`, `Waiting until element is focused`],
|
|
185
|
-
`LocSearch is not focused`,
|
|
186
|
-
],
|
|
290
|
+
['all', 'intermittent', '*', ['socket hang up'], 'Network flakiness'],
|
|
291
|
+
['darwin-safari', 'perma', '*', ['WebDriver timeout'], 'Safari bug'],
|
|
187
292
|
],
|
|
188
293
|
],
|
|
189
294
|
];
|
|
190
295
|
```
|
|
191
296
|
|
|
192
|
-
## Mocking
|
|
297
|
+
## ES Module Mocking
|
|
193
298
|
|
|
194
|
-
|
|
195
|
-
module loader. For example, you can substitute a module path by a mock module
|
|
196
|
-
path this way:
|
|
299
|
+
Enable custom module resolution in `meta.js`:
|
|
197
300
|
|
|
198
|
-
```
|
|
301
|
+
```javascript
|
|
199
302
|
export const loader = true;
|
|
200
303
|
|
|
201
304
|
export async function resolve(specifier, context, defaultResolve) {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
specifier = './base_mock.mjs';
|
|
205
|
-
break;
|
|
305
|
+
if (specifier === './api.js') {
|
|
306
|
+
specifier = './api_mock.js';
|
|
206
307
|
}
|
|
207
308
|
return defaultResolve(specifier, context, defaultResolve);
|
|
208
309
|
}
|
|
209
310
|
```
|
|
210
311
|
|
|
211
|
-
##
|
|
312
|
+
## Grid Metadata for Distributed Testing
|
|
313
|
+
|
|
314
|
+
Watest provides metadata helpers for external orchestrators to distribute tests across multiple workers. Define grid splits in `meta.js`:
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
// tests/meta.js
|
|
318
|
+
export const grid = {
|
|
319
|
+
'e2e+': ['tests/e2e'], // '+' = split per browser
|
|
320
|
+
'www+': ['tests/www'],
|
|
321
|
+
'services': ['tests/services', 'tests/integration'],
|
|
322
|
+
'misc': ['tests/lib', 'tests/ops'],
|
|
323
|
+
};
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Query metadata for orchestration:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
# Get grid metadata as JSON
|
|
330
|
+
watest --grid
|
|
331
|
+
watest --grid chrome firefox
|
|
332
|
+
watest --grid tests/e2e tests/www
|
|
333
|
+
|
|
334
|
+
# Get dependency metadata (servicers, services)
|
|
335
|
+
watest --deps tests/e2e
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Note**: Watest doesn't implement distributed test execution. These flags output metadata (JSON) that orchestration tools use to spawn workers and split test workload.
|
|
339
|
+
|
|
340
|
+
## System Commands
|
|
341
|
+
|
|
342
|
+
```javascript
|
|
343
|
+
import { runCommand, execCommand, spawn, runBashScript } from '@camperaid/watest';
|
|
344
|
+
|
|
345
|
+
// Run command, log output
|
|
346
|
+
await runCommand('npm', ['install']);
|
|
347
|
+
|
|
348
|
+
// Run command, capture output
|
|
349
|
+
const { stdout, stderr, code } = await execCommand('git', ['status']);
|
|
350
|
+
|
|
351
|
+
// Spawn with custom handling
|
|
352
|
+
const proc = spawn('node', ['server.js']);
|
|
353
|
+
|
|
354
|
+
// Run bash script
|
|
355
|
+
await runBashScript('setup.sh');
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
## License
|
|
212
359
|
|
|
213
|
-
-
|
|
214
|
-
- `-v` or `--verify` to re-run failing tests
|
|
215
|
-
- `--timeout` to set up a custom timeout for webdriver tests, for example, to break wd condition early
|
|
360
|
+
MPL-2.0
|
package/bin/watest.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { ProcessArgs } from '../core/
|
|
4
|
-
import '../core/settings.js';
|
|
3
|
+
import { ProcessArgs } from '../core/process-args.js';
|
|
5
4
|
import { runSeries } from '../core/series.js';
|
|
5
|
+
import { generateGridTasks, collectDeps, parseGridArgs } from '../core/deps.js';
|
|
6
|
+
import { settings } from '../core/settings.js';
|
|
7
|
+
import { pathToFileURL } from 'node:url';
|
|
8
|
+
import { join } from 'node:path';
|
|
6
9
|
|
|
7
10
|
const args = ProcessArgs.asObject();
|
|
11
|
+
|
|
8
12
|
if (args.showHelp) {
|
|
9
13
|
console.log(`Runs tests.`);
|
|
10
14
|
console.log('Usage: watest [options] patterns');
|
|
@@ -14,9 +18,39 @@ Options:
|
|
|
14
18
|
--timeout\t sets a timeout for webdriver tests, any webdriver condition is aborted after timeout making the test fail
|
|
15
19
|
--debunk\t re-runs succeeding test until it fails or debunk limit is reached
|
|
16
20
|
-v|--verify\t re-runs all failing tests
|
|
21
|
+
--deps\t outputs metadata (servicers, services) for given paths
|
|
22
|
+
--grid\t outputs grid metadata as JSON for distributed testing
|
|
17
23
|
-h|--help\t shows this help
|
|
18
24
|
`);
|
|
19
25
|
process.exit();
|
|
20
26
|
}
|
|
21
27
|
|
|
28
|
+
// Handle --deps flag (simple metadata from paths, for container rebuild decisions)
|
|
29
|
+
if (args.deps) {
|
|
30
|
+
await settings.initialize({ silent: true });
|
|
31
|
+
const { paths } = parseGridArgs(args.patterns);
|
|
32
|
+
const targetPaths = paths.length === 0 ? [settings.testsFolder] : paths;
|
|
33
|
+
const result = await collectDeps(targetPaths);
|
|
34
|
+
console.log(JSON.stringify(result));
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Handle --grid flag
|
|
39
|
+
if (args.grid) {
|
|
40
|
+
await settings.initialize({ silent: true });
|
|
41
|
+
const metaPath = join(process.cwd(), settings.testsFolder, 'meta.js');
|
|
42
|
+
const metaUrl = pathToFileURL(metaPath).href;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
const meta = await import(metaUrl);
|
|
46
|
+
const grid = meta.grid || {};
|
|
47
|
+
const tasks = await generateGridTasks(grid, args.patterns);
|
|
48
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
49
|
+
process.exit(0);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error('Error generating grid metadata:', err.message);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
22
56
|
runSeries(args.patterns, args).then(failures => failures && process.exit(1));
|
package/core/base.js
CHANGED
|
@@ -551,9 +551,16 @@ function is_out(got, expected, msg) {
|
|
|
551
551
|
return false;
|
|
552
552
|
}
|
|
553
553
|
|
|
554
|
-
function throws(func,
|
|
555
|
-
const on_no_exception = () => fail(`${msg}: no '${
|
|
556
|
-
const on_exception = e =>
|
|
554
|
+
function throws(func, expected, msg) {
|
|
555
|
+
const on_no_exception = () => fail(`${msg}: no '${expected}' exception`);
|
|
556
|
+
const on_exception = e =>
|
|
557
|
+
is(
|
|
558
|
+
expected instanceof RegExp || typeof expected === 'string'
|
|
559
|
+
? e?.message
|
|
560
|
+
: e,
|
|
561
|
+
expected,
|
|
562
|
+
msg,
|
|
563
|
+
);
|
|
557
564
|
return throw_internal(func, on_no_exception, on_exception);
|
|
558
565
|
}
|
|
559
566
|
|
package/core/core.js
CHANGED
|
@@ -57,7 +57,8 @@ class Core {
|
|
|
57
57
|
fail(msg) {
|
|
58
58
|
if (typeof msg == 'object') {
|
|
59
59
|
inspect(msg);
|
|
60
|
-
|
|
60
|
+
const errorMessage = msg?.message || msg?.toString() || 'Unexpected exception';
|
|
61
|
+
this.unconditional_fail(errorMessage);
|
|
61
62
|
return;
|
|
62
63
|
}
|
|
63
64
|
|
|
@@ -200,28 +201,55 @@ class Core {
|
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
let primeCore = new Core();
|
|
203
|
-
|
|
204
|
+
|
|
205
|
+
// Stack-based context management for nested operations
|
|
206
|
+
const contextStack = [];
|
|
207
|
+
|
|
208
|
+
function getCurrentContext() {
|
|
209
|
+
return contextStack.length > 0
|
|
210
|
+
? contextStack[contextStack.length - 1]
|
|
211
|
+
: { core: primeCore, series: null };
|
|
212
|
+
}
|
|
204
213
|
|
|
205
214
|
export { Core };
|
|
206
215
|
|
|
207
216
|
export const testflow = {
|
|
208
|
-
lock({ core, timeout } = {}) {
|
|
209
|
-
|
|
217
|
+
lock({ core, timeout, series } = {}) {
|
|
218
|
+
const newCore = core || new Core(timeout);
|
|
219
|
+
contextStack.push({
|
|
220
|
+
core: newCore,
|
|
221
|
+
series,
|
|
222
|
+
});
|
|
210
223
|
},
|
|
224
|
+
|
|
211
225
|
unlock() {
|
|
212
|
-
|
|
226
|
+
if (contextStack.length > 0) {
|
|
227
|
+
contextStack.pop();
|
|
228
|
+
}
|
|
229
|
+
// Always maintain at least one context (the prime core)
|
|
213
230
|
},
|
|
231
|
+
|
|
214
232
|
get core() {
|
|
215
|
-
return
|
|
233
|
+
return getCurrentContext().core;
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
get series() {
|
|
237
|
+
return getCurrentContext().series;
|
|
238
|
+
},
|
|
239
|
+
|
|
240
|
+
// Debug helper to see stack depth
|
|
241
|
+
get stackDepth() {
|
|
242
|
+
return contextStack.length;
|
|
216
243
|
},
|
|
217
244
|
};
|
|
218
245
|
|
|
219
|
-
export const assert = (...args) =>
|
|
220
|
-
export const info = (...args) =>
|
|
221
|
-
export const not_reached = (...args) =>
|
|
222
|
-
export const group = (...args) =>
|
|
223
|
-
export const fail = (...args) =>
|
|
224
|
-
export const todo = (...args) =>
|
|
225
|
-
export const warn = (...args) =>
|
|
226
|
-
export const success = (...args) =>
|
|
227
|
-
export const failed = () =>
|
|
246
|
+
export const assert = (...args) => testflow.core.assert(...args);
|
|
247
|
+
export const info = (...args) => testflow.core.info(...args);
|
|
248
|
+
export const not_reached = (...args) => testflow.core.not_reached(...args);
|
|
249
|
+
export const group = (...args) => testflow.core.group(...args);
|
|
250
|
+
export const fail = (...args) => testflow.core.fail(...args);
|
|
251
|
+
export const todo = (...args) => testflow.core.todo(...args);
|
|
252
|
+
export const warn = (...args) => testflow.core.warn(...args);
|
|
253
|
+
export const success = (...args) => testflow.core.success(...args);
|
|
254
|
+
export const failed = () => testflow.core.failed();
|
|
255
|
+
export const getServicer = () => testflow.series?.getServicer();
|